From 011327224ecec1312e0780865a1fb0dc83830a30 Mon Sep 17 00:00:00 2001 From: jon denning Date: Wed, 29 Jun 2022 20:52:00 -0400 Subject: Transform Snap: nearest face snap mode, snapping options, refactoring. This commit adds a new face nearest snapping mode, adds new snapping options, and (lightly) refactors code around snapping. The new face nearest snapping mode will snap transformed geometry to the nearest surface in world space. In contrast, the original face snapping mode uses projection (raycasting) to snap source to target geometry. Face snapping therefore only works with what is visible, while nearest face snapping can snap geometry to occluded parts of the scene. This new mode is critical for retopology work, where some of the target mesh might be occluded (ex: sliding an edge loop that wraps around the backside of target mesh). The nearest face snapping mode has two options: "Snap to Same Target" and "Face Nearest Steps". When the Snap to Same Object option is enabled, the selected source geometry will stay near the target that it is nearest before editing started, which prevents the source geometry from snapping to other targets. The Face Nearest Steps divides the overall transformation for each vertex into n smaller transformations, then applies those n transformations with surface snapping interlacing each step. This steps option handles transformations that cross U-shaped targets better. The new snapping options allow the artist to better control which target objects (objects to which the edited geometry is snapped) are considered when snapping. In particular, the only option for filtering target objects was a "Project onto Self", which allowed the currently edited mesh to be considered as a target. Now, the artist can choose any combination of the following to be considered as a target: the active object, any edited object that isn't active (see note below), any non- edited object. Additionally, the artist has another snapping option to exclude objects that are not selectable as potential targets. The Snapping Options dropdown has been lightly reorganized to allow for the additional options. Included in this patch: - Snap target selection is more controllable for artist with additional snapping options. - Renamed a few of the snap-related functions to better reflect what they actually do now. For example, `applySnapping` implies that this handles the snapping, while `applyProject` implies something entirely different is done there. However, better names would be `applySnappingAsGroup` and `applySnappingIndividual`, respectively, where `applySnappingIndividual` previously only does Face snapping. - Added an initial coordinate parameter to snapping functions so that the nearest target before transforming can be determined(for "Snap to Same Object"), and so the transformation can be broken into smaller steps (for "Face Nearest Steps"). - Separated the BVH Tree getter code from mesh/edit mesh to its own function to reduce code duplication. - Added icon for nearest face snapping. - The original "Project onto Self" was actually not correct! This option should be called "Project onto Active" instead, but that only matters when editing multiple meshes at the same time. This patch makes this change in the UI. Reviewed By: Campbell Barton, Germano Cavalcante Differential Revision: https://developer.blender.org/D14591 --- release/datafiles/blender_icons.svg | 49 +- .../blender_icons16/icon16_snap_face_nearest.dat | Bin 0 -> 1048 bytes .../blender_icons32/icon32_snap_face_nearest.dat | Bin 0 -> 4120 bytes release/scripts/startup/bl_ui/space_view3d.py | 46 +- source/blender/blenloader/intern/versioning_280.c | 2 +- source/blender/blenloader/intern/versioning_300.c | 11 + source/blender/editors/curve/editcurve.c | 5 +- .../gizmo_library/gizmo_types/move3d_gizmo.c | 3 +- .../gizmo_library/gizmo_types/snap3d_gizmo.c | 2 +- .../include/ED_transform_snap_object_context.h | 43 +- source/blender/editors/include/UI_icons.h | 2 +- source/blender/editors/mesh/editmesh_extrude.c | 2 +- source/blender/editors/mesh/editmesh_utils.c | 3 +- .../editors/space_view3d/view3d_cursor_snap.c | 11 +- source/blender/editors/space_view3d/view3d_edit.c | 3 +- .../editors/space_view3d/view3d_gizmo_ruler.c | 3 +- source/blender/editors/transform/transform.c | 2 +- source/blender/editors/transform/transform.h | 2 +- .../editors/transform/transform_constraints.c | 2 +- .../editors/transform/transform_convert_armature.c | 2 +- .../editors/transform/transform_convert_curve.c | 2 +- .../editors/transform/transform_convert_lattice.c | 2 +- .../editors/transform/transform_convert_mball.c | 2 +- .../editors/transform/transform_convert_mesh.c | 4 +- .../editors/transform/transform_convert_object.c | 2 +- .../transform/transform_convert_object_texspace.c | 2 +- .../editors/transform/transform_convert_particle.c | 2 +- .../transform/transform_mode_edge_rotate_normal.c | 2 +- .../transform/transform_mode_edge_seq_slide.c | 2 +- .../editors/transform/transform_mode_edge_slide.c | 4 +- .../editors/transform/transform_mode_resize.c | 2 +- .../editors/transform/transform_mode_rotate.c | 2 +- .../editors/transform/transform_mode_skin_resize.c | 2 +- .../editors/transform/transform_mode_translate.c | 2 +- .../editors/transform/transform_mode_vert_slide.c | 4 +- source/blender/editors/transform/transform_snap.c | 381 ++++++++++----- source/blender/editors/transform/transform_snap.h | 7 +- .../editors/transform/transform_snap_object.cc | 513 ++++++++++++++++++--- source/blender/makesdna/DNA_scene_defaults.h | 2 + source/blender/makesdna/DNA_scene_types.h | 52 ++- source/blender/makesrna/intern/rna_scene.c | 57 ++- 41 files changed, 964 insertions(+), 277 deletions(-) create mode 100644 release/datafiles/blender_icons16/icon16_snap_face_nearest.dat create mode 100644 release/datafiles/blender_icons32/icon32_snap_face_nearest.dat diff --git a/release/datafiles/blender_icons.svg b/release/datafiles/blender_icons.svg index f8164d1f646..985714d813f 100644 --- a/release/datafiles/blender_icons.svg +++ b/release/datafiles/blender_icons.svg @@ -11,9 +11,9 @@ height="640" id="svg2" sodipodi:version="0.32" - inkscape:version="1.0 (4035a4f, 2020-05-01)" + inkscape:version="1.0.2 (e86c870879, 2021-01-15)" version="1.0" - sodipodi:docname="blender_icons.svg" + sodipodi:docname="blender_icons.master.svg" inkscape:output_extension="org.inkscape.output.svg.inkscape" style="display:inline;enable-background:new" inkscape:export-filename="blender_icons.png" @@ -42,17 +42,17 @@ guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" - inkscape:window-width="1792" - inkscape:window-height="968" + inkscape:window-width="1618" + inkscape:window-height="846" id="namedview34335" showgrid="false" - inkscape:zoom="0.815521" - inkscape:cx="60.911776" - inkscape:cy="331.5525" - inkscape:window-x="-1" - inkscape:window-y="25" + inkscape:zoom="16" + inkscape:cx="18.1714" + inkscape:cy="166.10682" + inkscape:window-x="185" + inkscape:window-y="159" inkscape:window-maximized="0" - inkscape:current-layer="layer2" /> + inkscape:current-layer="layer8" /> + + + + + + + toolsettings->snap_mode = (1 << 1); /* SCE_SNAP_MODE_EDGE */ break; case 3: - scene->toolsettings->snap_mode = (1 << 2); /* SCE_SNAP_MODE_FACE */ + scene->toolsettings->snap_mode = (1 << 2); /* SCE_SNAP_MODE_FACE_RAYCAST */ break; case 4: scene->toolsettings->snap_mode = (1 << 3); /* SCE_SNAP_MODE_VOLUME */ diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index ea8653bd0f4..68df560a389 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -3164,6 +3164,17 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) } } } + + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + ToolSettings *tool_settings = scene->toolsettings; + /* Zero isn't a valid value, use for versioning. */ + if (tool_settings->snap_face_nearest_steps == 0) { + /* Minimum of snap steps for face nearest is 1. */ + tool_settings->snap_face_nearest_steps = 1; + /* Set snap to edited and non-edited as default. */ + tool_settings->snap_flag |= SCE_SNAP_TO_INCLUDE_EDITED | SCE_SNAP_TO_INCLUDE_NONEDITED; + } + } } if (!MAIN_VERSION_ATLEAST(bmain, 303, 4)) { diff --git a/source/blender/editors/curve/editcurve.c b/source/blender/editors/curve/editcurve.c index 4c0df48f65b..24302aca59b 100644 --- a/source/blender/editors/curve/editcurve.c +++ b/source/blender/editors/curve/editcurve.c @@ -5562,7 +5562,7 @@ static int add_vertex_invoke(bContext *C, wmOperator *op, const wmEvent *event) Curve *cu; float location[3]; const bool use_proj = ((vc.scene->toolsettings->snap_flag & SCE_SNAP) && - (vc.scene->toolsettings->snap_mode == SCE_SNAP_MODE_FACE)); + (vc.scene->toolsettings->snap_mode == SCE_SNAP_MODE_FACE_RAYCAST)); Nurb *nu; BezTriple *bezt; @@ -5595,12 +5595,13 @@ static int add_vertex_invoke(bContext *C, wmOperator *op, const wmEvent *event) vc.depsgraph, vc.region, vc.v3d, - SCE_SNAP_MODE_FACE, + SCE_SNAP_MODE_FACE_RAYCAST, &(const struct SnapObjectParams){ .snap_target_select = (vc.obedit != NULL) ? SCE_SNAP_TARGET_NOT_ACTIVE : SCE_SNAP_TARGET_ALL, .edit_mode_type = SNAP_GEOM_FINAL, }, + NULL, mval, NULL, NULL, diff --git a/source/blender/editors/gizmo_library/gizmo_types/move3d_gizmo.c b/source/blender/editors/gizmo_library/gizmo_types/move3d_gizmo.c index 1ce67185c1e..af1f09d7e25 100644 --- a/source/blender/editors/gizmo_library/gizmo_types/move3d_gizmo.c +++ b/source/blender/editors/gizmo_library/gizmo_types/move3d_gizmo.c @@ -278,12 +278,13 @@ static int gizmo_move_modal(bContext *C, CTX_data_ensure_evaluated_depsgraph(C), region, CTX_wm_view3d(C), - (SCE_SNAP_MODE_VERTEX | SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_FACE), + (SCE_SNAP_MODE_VERTEX | SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_FACE_RAYCAST), &(const struct SnapObjectParams){ .snap_target_select = SCE_SNAP_TARGET_ALL, .edit_mode_type = SNAP_GEOM_EDIT, .use_occlusion_test = true, }, + NULL, mval_fl, NULL, &dist_px, diff --git a/source/blender/editors/gizmo_library/gizmo_types/snap3d_gizmo.c b/source/blender/editors/gizmo_library/gizmo_types/snap3d_gizmo.c index d468906f127..c5a542c0bf3 100644 --- a/source/blender/editors/gizmo_library/gizmo_types/snap3d_gizmo.c +++ b/source/blender/editors/gizmo_library/gizmo_types/snap3d_gizmo.c @@ -345,7 +345,7 @@ static void GIZMO_GT_snap_3d(wmGizmoType *gzt) prop = RNA_def_enum_flag(gzt->srna, "snap_elements_force", rna_enum_snap_element_items, - SCE_SNAP_MODE_VERTEX | SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_FACE, + SCE_SNAP_MODE_VERTEX | SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_FACE_RAYCAST, "Snap Elements", ""); RNA_def_property_enum_funcs_runtime(prop, diff --git a/source/blender/editors/include/ED_transform_snap_object_context.h b/source/blender/editors/include/ED_transform_snap_object_context.h index 20353c21f93..db44d9af706 100644 --- a/source/blender/editors/include/ED_transform_snap_object_context.h +++ b/source/blender/editors/include/ED_transform_snap_object_context.h @@ -60,6 +60,10 @@ struct SnapObjectParams { bool use_occlusion_test : true; /* exclude back facing geometry from snapping */ bool use_backface_culling : true; + /* Break nearest face snapping into steps to improve transformations across U-shaped targets. */ + short face_nearest_steps; + /* Enable to force nearest face snapping to snap to target the source was initially near. */ + bool keep_on_same_target; }; typedef struct SnapObjectContext SnapObjectContext; @@ -114,12 +118,33 @@ bool ED_transform_snap_object_project_ray_all(SnapObjectContext *sctx, bool sort, struct ListBase *r_hit_list); +/** + * Perform snapping. + * + * Given a 2D region value, snap to vert/edge/face/grid. + * + * \param sctx: Snap context. + * \param snap_to: Target elements to snap source to. + * \param params: Addition snapping options. + * \param init_co: Initial world-space coordinate of source (optional). + * \param mval: Current transformed screen-space coordinate or mouse position (optional). + * \param prev_co: Current transformed world-space coordinate of source (optional). + * \param dist_px: Maximum distance to snap (in pixels). + * \param r_loc: Snapped world-space coordinate. + * \param r_no: Snapped world-space normal (optional). + * \param r_index: Index of snapped-to target element (optional). + * \param r_ob: Snapped-to target object (optional). + * \param r_obmat: Matrix of snapped-to target object (optional). + * \param r_face_nor: World-space normal of snapped-to target face (optional). + * \return Snapped-to element, #eSnapMode. + */ eSnapMode ED_transform_snap_object_project_view3d_ex(struct SnapObjectContext *sctx, struct Depsgraph *depsgraph, const ARegion *region, const View3D *v3d, - eSnapMode snap_to, + const eSnapMode snap_to, const struct SnapObjectParams *params, + const float init_co[3], const float mval[2], const float prev_co[3], float *dist_px, @@ -135,19 +160,23 @@ eSnapMode ED_transform_snap_object_project_view3d_ex(struct SnapObjectContext *s * Given a 2D region value, snap to vert/edge/face. * * \param sctx: Snap context. - * \param mval: Screenspace coordinate. - * \param prev_co: Coordinate for perpendicular point calculation (optional). + * \param snap_to: Target elements to snap source to. + * \param params: Addition snapping options. + * \param init_co: Initial world-space coordinate of source (optional). + * \param mval: Current transformed screen-space coordinate or mouse position (optional). + * \param prev_co: Current transformed world-space coordinate of source (optional). * \param dist_px: Maximum distance to snap (in pixels). - * \param r_loc: hit location. - * \param r_no: hit normal (optional). - * \return Snap success. + * \param r_loc: Snapped world-space coordinate. + * \param r_no: Snapped world-space normal (optional). + * \return Snapped-to element, #eSnapMode. */ eSnapMode ED_transform_snap_object_project_view3d(struct SnapObjectContext *sctx, struct Depsgraph *depsgraph, const ARegion *region, const View3D *v3d, - eSnapMode snap_to, + const eSnapMode snap_to, const struct SnapObjectParams *params, + const float init_co[3], const float mval[2], const float prev_co[3], float *dist_px, diff --git a/source/blender/editors/include/UI_icons.h b/source/blender/editors/include/UI_icons.h index f1c0acf43f7..09057fd846e 100644 --- a/source/blender/editors/include/UI_icons.h +++ b/source/blender/editors/include/UI_icons.h @@ -652,7 +652,7 @@ DEF_ICON(PARTICLE_TIP) DEF_ICON(PARTICLE_PATH) /* EDITING */ -DEF_ICON_BLANK(669) +DEF_ICON(SNAP_FACE_NEAREST) DEF_ICON(SNAP_FACE_CENTER) DEF_ICON(SNAP_PERPENDICULAR) DEF_ICON(SNAP_MIDPOINT) diff --git a/source/blender/editors/mesh/editmesh_extrude.c b/source/blender/editors/mesh/editmesh_extrude.c index 8d63b1ea020..330008d92d1 100644 --- a/source/blender/editors/mesh/editmesh_extrude.c +++ b/source/blender/editors/mesh/editmesh_extrude.c @@ -702,7 +702,7 @@ static int edbm_dupli_extrude_cursor_invoke(bContext *C, wmOperator *op, const w const bool rot_src = RNA_boolean_get(op->ptr, "rotate_source"); const bool use_proj = ((vc.scene->toolsettings->snap_flag & SCE_SNAP) && - (vc.scene->toolsettings->snap_mode == SCE_SNAP_MODE_FACE)); + (vc.scene->toolsettings->snap_mode == SCE_SNAP_MODE_FACE_RAYCAST)); /* First calculate the center of transformation. */ zero_v3(center); diff --git a/source/blender/editors/mesh/editmesh_utils.c b/source/blender/editors/mesh/editmesh_utils.c index 04030583f5c..83968955583 100644 --- a/source/blender/editors/mesh/editmesh_utils.c +++ b/source/blender/editors/mesh/editmesh_utils.c @@ -1646,12 +1646,13 @@ void EDBM_project_snap_verts( depsgraph, region, CTX_wm_view3d(C), - SCE_SNAP_MODE_FACE, + SCE_SNAP_MODE_FACE_RAYCAST, &(const struct SnapObjectParams){ .snap_target_select = SCE_SNAP_TARGET_NOT_ACTIVE, .edit_mode_type = SNAP_GEOM_FINAL, .use_occlusion_test = true, }, + NULL, mval, NULL, NULL, diff --git a/source/blender/editors/space_view3d/view3d_cursor_snap.c b/source/blender/editors/space_view3d/view3d_cursor_snap.c index a879a05d41a..4a1bd6ba945 100644 --- a/source/blender/editors/space_view3d/view3d_cursor_snap.c +++ b/source/blender/editors/space_view3d/view3d_cursor_snap.c @@ -597,9 +597,9 @@ static void v3d_cursor_snap_update(V3DSnapCursorState *state, eSnapMode snap_elements = v3d_cursor_snap_elements(state, scene); data_intern->snap_elem_hidden = SCE_SNAP_MODE_NONE; const bool calc_plane_omat = v3d_cursor_snap_calc_plane(); - if (calc_plane_omat && !(snap_elements & SCE_SNAP_MODE_FACE)) { - data_intern->snap_elem_hidden = SCE_SNAP_MODE_FACE; - snap_elements |= SCE_SNAP_MODE_FACE; + if (calc_plane_omat && !(snap_elements & SCE_SNAP_MODE_FACE_RAYCAST)) { + data_intern->snap_elem_hidden = SCE_SNAP_MODE_FACE_RAYCAST; + snap_elements |= SCE_SNAP_MODE_FACE_RAYCAST; } snap_data->is_enabled = true; @@ -614,7 +614,7 @@ static void v3d_cursor_snap_update(V3DSnapCursorState *state, snap_data->snap_elem = SCE_SNAP_MODE_NONE; return; } - snap_elements = data_intern->snap_elem_hidden = SCE_SNAP_MODE_FACE; + snap_elements = data_intern->snap_elem_hidden = SCE_SNAP_MODE_FACE_RAYCAST; } } #endif @@ -649,6 +649,7 @@ static void v3d_cursor_snap_update(V3DSnapCursorState *state, .edit_mode_type = edit_mode_type, .use_occlusion_test = use_occlusion_test, }, + NULL, mval_fl, prev_co, &dist_px, @@ -744,7 +745,7 @@ static void v3d_cursor_snap_update(V3DSnapCursorState *state, (SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_EDGE_MIDPOINT | SCE_SNAP_MODE_EDGE_PERPENDICULAR)) { snap_elem_index[1] = index; } - else if (snap_elem == SCE_SNAP_MODE_FACE) { + else if (snap_elem == SCE_SNAP_MODE_FACE_RAYCAST) { snap_elem_index[2] = index; } diff --git a/source/blender/editors/space_view3d/view3d_edit.c b/source/blender/editors/space_view3d/view3d_edit.c index 5fbdbef676c..041663b4a00 100644 --- a/source/blender/editors/space_view3d/view3d_edit.c +++ b/source/blender/editors/space_view3d/view3d_edit.c @@ -908,12 +908,13 @@ void ED_view3d_cursor3d_position_rotation(bContext *C, CTX_data_ensure_evaluated_depsgraph(C), region, v3d, - SCE_SNAP_MODE_FACE, + SCE_SNAP_MODE_FACE_RAYCAST, &(const struct SnapObjectParams){ .snap_target_select = SCE_SNAP_TARGET_ALL, .edit_mode_type = SNAP_GEOM_FINAL, .use_occlusion_test = true, }, + NULL, mval_fl, NULL, &dist_px, diff --git a/source/blender/editors/space_view3d/view3d_gizmo_ruler.c b/source/blender/editors/space_view3d/view3d_gizmo_ruler.c index 9aae30c4a7e..96f242dba9f 100644 --- a/source/blender/editors/space_view3d/view3d_gizmo_ruler.c +++ b/source/blender/editors/space_view3d/view3d_gizmo_ruler.c @@ -357,11 +357,12 @@ static bool view3d_ruler_item_mousemove(const bContext *C, depsgraph, ruler_info->region, v3d, - SCE_SNAP_MODE_FACE, + SCE_SNAP_MODE_FACE_RAYCAST, &(const struct SnapObjectParams){ .snap_target_select = SCE_SNAP_TARGET_ALL, .edit_mode_type = SNAP_GEOM_CAGE, }, + NULL, mval_fl, NULL, &dist_px, diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c index 960d9a25ca7..d9e23b98c66 100644 --- a/source/blender/editors/transform/transform.c +++ b/source/blender/editors/transform/transform.c @@ -1577,7 +1577,7 @@ void saveTransform(bContext *C, TransInfo *t, wmOperator *op) if (transformModeUseSnap(t)) { if (!(t->modifiers & MOD_SNAP) != !(t->tsnap.flag & SCE_SNAP)) { /* Type is #eSnapFlag, but type must match various snap attributes in #ToolSettings. */ - char *snap_flag_ptr; + short *snap_flag_ptr; wmMsgParams_RNA msg_key_params = {{0}}; RNA_pointer_create(&t->scene->id, &RNA_ToolSettings, ts, &msg_key_params.ptr); diff --git a/source/blender/editors/transform/transform.h b/source/blender/editors/transform/transform.h index a35942aedec..76573c85d52 100644 --- a/source/blender/editors/transform/transform.h +++ b/source/blender/editors/transform/transform.h @@ -306,9 +306,9 @@ typedef struct TransSnap { eSnapTargetSelect target_select; bool align; bool project; - bool snap_self; bool peel; bool use_backface_culling; + short face_nearest_steps; eTSnap status; /* Snapped Element Type (currently for objects only). */ eSnapMode snapElem; diff --git a/source/blender/editors/transform/transform_constraints.c b/source/blender/editors/transform/transform_constraints.c index 0bb00032561..658901a6991 100644 --- a/source/blender/editors/transform/transform_constraints.c +++ b/source/blender/editors/transform/transform_constraints.c @@ -402,7 +402,7 @@ static void applyAxisConstraintVec(const TransInfo *t, if (activeSnap(t)) { if (validSnap(t)) { is_snap_to_edge = (t->tsnap.snapElem & SCE_SNAP_MODE_EDGE) != 0; - is_snap_to_face = (t->tsnap.snapElem & SCE_SNAP_MODE_FACE) != 0; + is_snap_to_face = (t->tsnap.snapElem & SCE_SNAP_MODE_FACE_RAYCAST) != 0; is_snap_to_point = !is_snap_to_edge && !is_snap_to_face; } else if (t->tsnap.snapElem & SCE_SNAP_MODE_GRID) { diff --git a/source/blender/editors/transform/transform_convert_armature.c b/source/blender/editors/transform/transform_convert_armature.c index e1b25acb21e..a031a6cd45b 100644 --- a/source/blender/editors/transform/transform_convert_armature.c +++ b/source/blender/editors/transform/transform_convert_armature.c @@ -1192,7 +1192,7 @@ static void restoreBones(TransDataContainer *tc) void recalcData_edit_armature(TransInfo *t) { if (t->state != TRANS_CANCEL) { - applyProject(t); + applySnappingIndividual(t); } FOREACH_TRANS_DATA_CONTAINER (t, tc) { diff --git a/source/blender/editors/transform/transform_convert_curve.c b/source/blender/editors/transform/transform_convert_curve.c index 51acfb2a788..b9581aa1e31 100644 --- a/source/blender/editors/transform/transform_convert_curve.c +++ b/source/blender/editors/transform/transform_convert_curve.c @@ -418,7 +418,7 @@ void createTransCurveVerts(TransInfo *t) void recalcData_curve(TransInfo *t) { if (t->state != TRANS_CANCEL) { - applyProject(t); + applySnappingIndividual(t); } FOREACH_TRANS_DATA_CONTAINER (t, tc) { diff --git a/source/blender/editors/transform/transform_convert_lattice.c b/source/blender/editors/transform/transform_convert_lattice.c index f02d4e94448..d3c34697237 100644 --- a/source/blender/editors/transform/transform_convert_lattice.c +++ b/source/blender/editors/transform/transform_convert_lattice.c @@ -101,7 +101,7 @@ void createTransLatticeVerts(TransInfo *t) void recalcData_lattice(TransInfo *t) { if (t->state != TRANS_CANCEL) { - applyProject(t); + applySnappingIndividual(t); } FOREACH_TRANS_DATA_CONTAINER (t, tc) { diff --git a/source/blender/editors/transform/transform_convert_mball.c b/source/blender/editors/transform/transform_convert_mball.c index 7cba4f97886..5b2a1f8336d 100644 --- a/source/blender/editors/transform/transform_convert_mball.c +++ b/source/blender/editors/transform/transform_convert_mball.c @@ -122,7 +122,7 @@ void createTransMBallVerts(TransInfo *t) void recalcData_mball(TransInfo *t) { if (t->state != TRANS_CANCEL) { - applyProject(t); + applySnappingIndividual(t); } FOREACH_TRANS_DATA_CONTAINER (t, tc) { if (tc->data_len) { diff --git a/source/blender/editors/transform/transform_convert_mesh.c b/source/blender/editors/transform/transform_convert_mesh.c index d4b12142162..c8d943df3fa 100644 --- a/source/blender/editors/transform/transform_convert_mesh.c +++ b/source/blender/editors/transform/transform_convert_mesh.c @@ -1940,7 +1940,7 @@ static void tc_mesh_partial_types_calc(TransInfo *t, struct PartialTypeState *r_ } /* With projection, transform isn't affine. */ - if (activeSnap_with_project(t)) { + if (activeSnap_SnappingIndividual(t)) { if (partial_for_looptri == PARTIAL_TYPE_GROUP) { partial_for_looptri = PARTIAL_TYPE_ALL; } @@ -2056,7 +2056,7 @@ void recalcData_mesh(TransInfo *t) bool is_canceling = t->state == TRANS_CANCEL; /* Apply corrections. */ if (!is_canceling) { - applyProject(t); + applySnappingIndividual(t); bool do_mirror = !(t->flag & T_NO_MIRROR); FOREACH_TRANS_DATA_CONTAINER (t, tc) { diff --git a/source/blender/editors/transform/transform_convert_object.c b/source/blender/editors/transform/transform_convert_object.c index 5879a65eb4b..47e78656861 100644 --- a/source/blender/editors/transform/transform_convert_object.c +++ b/source/blender/editors/transform/transform_convert_object.c @@ -865,7 +865,7 @@ void recalcData_objects(TransInfo *t) bool motionpath_update = false; if (t->state != TRANS_CANCEL) { - applyProject(t); + applySnappingIndividual(t); } FOREACH_TRANS_DATA_CONTAINER (t, tc) { diff --git a/source/blender/editors/transform/transform_convert_object_texspace.c b/source/blender/editors/transform/transform_convert_object_texspace.c index 1f58ec80f02..e5a66f8a1d2 100644 --- a/source/blender/editors/transform/transform_convert_object_texspace.c +++ b/source/blender/editors/transform/transform_convert_object_texspace.c @@ -90,7 +90,7 @@ void recalcData_texspace(TransInfo *t) { if (t->state != TRANS_CANCEL) { - applyProject(t); + applySnappingIndividual(t); } FOREACH_TRANS_DATA_CONTAINER (t, tc) { diff --git a/source/blender/editors/transform/transform_convert_particle.c b/source/blender/editors/transform/transform_convert_particle.c index d7b0f378c8a..31e73288ad0 100644 --- a/source/blender/editors/transform/transform_convert_particle.c +++ b/source/blender/editors/transform/transform_convert_particle.c @@ -238,7 +238,7 @@ static void flushTransParticles(TransInfo *t) void recalcData_particles(TransInfo *t) { if (t->state != TRANS_CANCEL) { - applyProject(t); + applySnappingIndividual(t); } flushTransParticles(t); } diff --git a/source/blender/editors/transform/transform_mode_edge_rotate_normal.c b/source/blender/editors/transform/transform_mode_edge_rotate_normal.c index 2327aa4e9c4..8d790b4699b 100644 --- a/source/blender/editors/transform/transform_mode_edge_rotate_normal.c +++ b/source/blender/editors/transform/transform_mode_edge_rotate_normal.c @@ -84,7 +84,7 @@ static void applyNormalRotation(TransInfo *t, const int UNUSED(mval[2])) transform_snap_increment(t, &angle); - applySnapping(t, &angle); + applySnappingAsGroup(t, &angle); applyNumInput(&t->num, &angle); diff --git a/source/blender/editors/transform/transform_mode_edge_seq_slide.c b/source/blender/editors/transform/transform_mode_edge_seq_slide.c index 9a732562709..5ca1fdf75c6 100644 --- a/source/blender/editors/transform/transform_mode_edge_seq_slide.c +++ b/source/blender/editors/transform/transform_mode_edge_seq_slide.c @@ -87,7 +87,7 @@ static void applySeqSlide(TransInfo *t, const int UNUSED(mval[2])) } else { copy_v2_v2(values_final, t->values); - applySnapping(t, values_final); + applySnappingAsGroup(t, values_final); transform_convert_sequencer_channel_clamp(t, values_final); if (t->con.mode & CON_APPLY) { diff --git a/source/blender/editors/transform/transform_mode_edge_slide.c b/source/blender/editors/transform/transform_mode_edge_slide.c index b8e9a0d1a4d..a3c49d2362f 100644 --- a/source/blender/editors/transform/transform_mode_edge_slide.c +++ b/source/blender/editors/transform/transform_mode_edge_slide.c @@ -1292,7 +1292,7 @@ static void edge_slide_snap_apply(TransInfo *t, float *value) side_index = t_snap >= t_mid; } - if (t->tsnap.snapElem & (SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_FACE)) { + if (t->tsnap.snapElem & (SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_FACE_RAYCAST)) { float co_dir[3]; sub_v3_v3v3(co_dir, co_dest[side_index], co_orig); normalize_v3(co_dir); @@ -1444,7 +1444,7 @@ static void applyEdgeSlide(TransInfo *t, const int UNUSED(mval[2])) final = t->values[0] + t->values_modal_offset[0]; - applySnapping(t, &final); + applySnappingAsGroup(t, &final); if (!validSnap(t)) { transform_snap_increment(t, &final); } diff --git a/source/blender/editors/transform/transform_mode_resize.c b/source/blender/editors/transform/transform_mode_resize.c index ffae651e4aa..31d40486afc 100644 --- a/source/blender/editors/transform/transform_mode_resize.c +++ b/source/blender/editors/transform/transform_mode_resize.c @@ -105,7 +105,7 @@ static void applyResize(TransInfo *t, const int UNUSED(mval[2])) constraintNumInput(t, t->values_final); } - applySnapping(t, t->values_final); + applySnappingAsGroup(t, t->values_final); } size_to_mat3(mat, t->values_final); diff --git a/source/blender/editors/transform/transform_mode_rotate.c b/source/blender/editors/transform/transform_mode_rotate.c index f75c65448df..a7207b36578 100644 --- a/source/blender/editors/transform/transform_mode_rotate.c +++ b/source/blender/editors/transform/transform_mode_rotate.c @@ -305,7 +305,7 @@ static void applyRotation(TransInfo *t, const int UNUSED(mval[2])) final = large_rotation_limit(final); } else { - applySnapping(t, &final); + applySnappingAsGroup(t, &final); if (!(activeSnap(t) && validSnap(t))) { transform_snap_increment(t, &final); } diff --git a/source/blender/editors/transform/transform_mode_skin_resize.c b/source/blender/editors/transform/transform_mode_skin_resize.c index 8099449ec23..bdbb66b72f4 100644 --- a/source/blender/editors/transform/transform_mode_skin_resize.c +++ b/source/blender/editors/transform/transform_mode_skin_resize.c @@ -99,7 +99,7 @@ static void applySkinResize(TransInfo *t, const int UNUSED(mval[2])) constraintNumInput(t, t->values_final); } - applySnapping(t, t->values_final); + applySnappingAsGroup(t, t->values_final); } size_to_mat3(mat_final, t->values_final); diff --git a/source/blender/editors/transform/transform_mode_translate.c b/source/blender/editors/transform/transform_mode_translate.c index 3c6b6ea4117..65690f9069d 100644 --- a/source/blender/editors/transform/transform_mode_translate.c +++ b/source/blender/editors/transform/transform_mode_translate.c @@ -470,7 +470,7 @@ static void applyTranslation(TransInfo *t, const int UNUSED(mval[2])) } t->tsnap.snapElem = SCE_SNAP_MODE_NONE; - applySnapping(t, global_dir); + applySnappingAsGroup(t, global_dir); transform_snap_grid(t, global_dir); if (t->con.mode & CON_APPLY) { diff --git a/source/blender/editors/transform/transform_mode_vert_slide.c b/source/blender/editors/transform/transform_mode_vert_slide.c index 77c5707d814..674ffcf17a8 100644 --- a/source/blender/editors/transform/transform_mode_vert_slide.c +++ b/source/blender/editors/transform/transform_mode_vert_slide.c @@ -539,7 +539,7 @@ static void vert_slide_snap_apply(TransInfo *t, float *value) getSnapPoint(t, dvec); sub_v3_v3(dvec, t->tsnap.snapTarget); - if (t->tsnap.snapElem & (SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_FACE)) { + if (t->tsnap.snapElem & (SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_FACE_RAYCAST)) { float co_dir[3]; sub_v3_v3v3(co_dir, co_curr_3d, co_orig_3d); normalize_v3(co_dir); @@ -568,7 +568,7 @@ static void applyVertSlide(TransInfo *t, const int UNUSED(mval[2])) final = t->values[0] + t->values_modal_offset[0]; - applySnapping(t, &final); + applySnappingAsGroup(t, &final); if (!validSnap(t)) { transform_snap_increment(t, &final); } diff --git a/source/blender/editors/transform/transform_snap.c b/source/blender/editors/transform/transform_snap.c index 649217092aa..22d062a71dc 100644 --- a/source/blender/editors/transform/transform_snap.c +++ b/source/blender/editors/transform/transform_snap.c @@ -126,8 +126,12 @@ bool activeSnap(const TransInfo *t) ((t->modifiers & (MOD_SNAP | MOD_SNAP_INVERT)) == MOD_SNAP_INVERT); } -bool activeSnap_with_project(const TransInfo *t) +bool activeSnap_SnappingIndividual(const TransInfo *t) { + if (activeSnap(t) && t->tsnap.mode & SCE_SNAP_MODE_FACE_NEAREST) { + return true; + } + if (!t->tsnap.project) { return false; } @@ -143,6 +147,27 @@ bool activeSnap_with_project(const TransInfo *t) return true; } +bool activeSnap_SnappingAsGroup(const TransInfo *t) +{ + if (!activeSnap(t)) { + return false; + } + + if (t->tsnap.mode == SCE_SNAP_MODE_FACE_RAYCAST && t->tsnap.project) { + return false; + } + + if (t->tsnap.mode == SCE_SNAP_MODE_FACE_NEAREST) { + return false; + } + + if (doForceIncrementSnap(t)) { + return false; + } + + return true; +} + bool transformModeUseSnap(const TransInfo *t) { ToolSettings *ts = t->settings; @@ -343,93 +368,157 @@ eRedrawFlag handleSnapping(TransInfo *t, const wmEvent *event) return status; } -void applyProject(TransInfo *t) +static bool applyFaceProject(TransInfo *t, TransDataContainer *tc, TransData *td) { - if (!activeSnap_with_project(t)) { - return; + if (!(t->tsnap.mode & SCE_SNAP_MODE_FACE_RAYCAST)) { + return false; + } + + float iloc[3], loc[3], no[3]; + float mval_fl[2]; + + copy_v3_v3(iloc, td->loc); + if (tc->use_local_mat) { + mul_m4_v3(tc->mat, iloc); + } + else if (t->options & CTX_OBJECT) { + BKE_object_eval_transform_all(t->depsgraph, t->scene, td->ob); + copy_v3_v3(iloc, td->ob->obmat[3]); + } + + if (ED_view3d_project_float_global(t->region, iloc, mval_fl, V3D_PROJ_TEST_NOP) != + V3D_PROJ_RET_OK) { + return false; + } + + eSnapMode hit = ED_transform_snap_object_project_view3d( + t->tsnap.object_context, + t->depsgraph, + t->region, + t->view, + SCE_SNAP_MODE_FACE_RAYCAST, + &(const struct SnapObjectParams){ + .snap_target_select = t->tsnap.target_select, + .edit_mode_type = (t->flag & T_EDIT) != 0 ? SNAP_GEOM_EDIT : SNAP_GEOM_FINAL, + .use_occlusion_test = false, + .use_backface_culling = t->tsnap.use_backface_culling, + }, + NULL, + mval_fl, + NULL, + 0, + loc, + no); + if (hit != SCE_SNAP_MODE_FACE_RAYCAST) { + return false; } float tvec[3]; - int i; + sub_v3_v3v3(tvec, loc, iloc); - /* XXX FLICKER IN OBJECT MODE */ - FOREACH_TRANS_DATA_CONTAINER (t, tc) { - TransData *td = tc->data; - for (i = 0; i < tc->data_len; i++, td++) { - float iloc[3], loc[3], no[3]; - float mval_fl[2]; - if (td->flag & TD_SKIP) { - continue; - } + mul_m3_v3(td->smtx, tvec); - if ((t->flag & T_PROP_EDIT) && (td->factor == 0.0f)) { - continue; - } + add_v3_v3(td->loc, tvec); - copy_v3_v3(iloc, td->loc); - if (tc->use_local_mat) { - mul_m4_v3(tc->mat, iloc); - } - else if (t->options & CTX_OBJECT) { - BKE_object_eval_transform_all(t->depsgraph, t->scene, td->ob); - copy_v3_v3(iloc, td->ob->obmat[3]); - } + if (t->tsnap.align && (t->options & CTX_OBJECT)) { + /* handle alignment as well */ + const float *original_normal; + float mat[3][3]; - if (ED_view3d_project_float_global(t->region, iloc, mval_fl, V3D_PROJ_TEST_NOP) == - V3D_PROJ_RET_OK) { - eSnapMode hit = ED_transform_snap_object_project_view3d( - t->tsnap.object_context, - t->depsgraph, - t->region, - t->view, - SCE_SNAP_MODE_FACE, - &(const struct SnapObjectParams){ - .snap_target_select = t->tsnap.target_select, - .edit_mode_type = (t->flag & T_EDIT) != 0 ? SNAP_GEOM_EDIT : SNAP_GEOM_FINAL, - .use_occlusion_test = false, - .use_backface_culling = t->tsnap.use_backface_culling, - }, - mval_fl, - NULL, - 0, - loc, - no); - if (hit != SCE_SNAP_MODE_FACE) { - return; - } + /* In pose mode, we want to align normals with Y axis of bones. */ + original_normal = td->axismtx[2]; -#if 0 - if (tc->use_local_mat) { - mul_m4_v3(tc->imat, loc); - } -#endif + rotation_between_vecs_to_mat3(mat, original_normal, no); + + transform_data_ext_rotate(td, mat, true); + + /* TODO: support constraints for rotation too? see #ElementRotation. */ + } + return true; +} + +static void applyFaceNearest(TransInfo *t, TransDataContainer *tc, TransData *td) +{ + if (!(t->tsnap.mode & SCE_SNAP_MODE_FACE_NEAREST)) { + return; + } - sub_v3_v3v3(tvec, loc, iloc); + float init_loc[3]; + float prev_loc[3]; + float snap_loc[3], snap_no[3]; - mul_m3_v3(td->smtx, tvec); + copy_v3_v3(init_loc, td->iloc); + copy_v3_v3(prev_loc, td->loc); + if (tc->use_local_mat) { + mul_m4_v3(tc->mat, init_loc); + mul_m4_v3(tc->mat, prev_loc); + } + else if (t->options & CTX_OBJECT) { + BKE_object_eval_transform_all(t->depsgraph, t->scene, td->ob); + copy_v3_v3(init_loc, td->ob->obmat[3]); + } - add_v3_v3(td->loc, tvec); + eSnapMode hit = ED_transform_snap_object_project_view3d( + t->tsnap.object_context, + t->depsgraph, + t->region, + t->view, + SCE_SNAP_MODE_FACE_NEAREST, + &(const struct SnapObjectParams){ + .snap_target_select = t->tsnap.target_select, + .edit_mode_type = (t->flag & T_EDIT) != 0 ? SNAP_GEOM_EDIT : SNAP_GEOM_FINAL, + .use_occlusion_test = false, + .use_backface_culling = false, + .face_nearest_steps = t->tsnap.face_nearest_steps, + .keep_on_same_target = t->tsnap.flag & SCE_SNAP_KEEP_ON_SAME_OBJECT, + }, + init_loc, + NULL, + prev_loc, + 0, + snap_loc, + snap_no); + + if (hit != SCE_SNAP_MODE_FACE_NEAREST) { + return; + } - if (t->tsnap.align && (t->options & CTX_OBJECT)) { - /* handle alignment as well */ - const float *original_normal; - float mat[3][3]; + float tvec[3]; + sub_v3_v3v3(tvec, snap_loc, prev_loc); + mul_m3_v3(td->smtx, tvec); + add_v3_v3(td->loc, tvec); - /* In pose mode, we want to align normals with Y axis of bones... */ - original_normal = td->axismtx[2]; + /* TODO: support snap alignment similar to #SCE_SNAP_MODE_FACE_RAYCAST? */ +} - rotation_between_vecs_to_mat3(mat, original_normal, no); +void applySnappingIndividual(TransInfo *t) +{ + if (!activeSnap_SnappingIndividual(t)) { + return; + } - transform_data_ext_rotate(td, mat, true); + /* XXX FLICKER IN OBJECT MODE */ + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + TransData *td = tc->data; + for (int i = 0; i < tc->data_len; i++, td++) { + if (td->flag & TD_SKIP) { + continue; + } - /* TODO: support constraints for rotation too? see #ElementRotation. */ - } + if ((t->flag & T_PROP_EDIT) && (td->factor == 0.0f)) { + continue; } - } + /* If both face ray-cast and face nearest methods are enabled, start with face ray-cast and + * fallback to face nearest ray-cast does not hit. */ + bool hit = applyFaceProject(t, tc, td); + if (!hit) { + applyFaceNearest(t, tc, td); + } #if 0 /* TODO: support this? */ - constraintTransLim(t, td); + constraintTransLim(t, td); #endif + } } } @@ -483,15 +572,9 @@ void applyGridAbsolute(TransInfo *t) } } -void applySnapping(TransInfo *t, float *vec) +void applySnappingAsGroup(TransInfo *t, float *vec) { - /* Each Trans Data already makes the snap to face */ - if (doForceIncrementSnap(t)) { - return; - } - - if (t->tsnap.project && t->tsnap.mode == SCE_SNAP_MODE_FACE) { - /* A similar snap will be applied to each transdata in `applyProject`. */ + if (!activeSnap_SnappingAsGroup(t)) { return; } @@ -644,70 +727,76 @@ static eSnapMode snap_mode_from_spacetype(TransInfo *t) return SCE_SNAP_MODE_INCREMENT; } -static eSnapTargetSelect snap_select_type_get(TransInfo *t) +static eSnapTargetSelect snap_target_select_from_spacetype(TransInfo *t) { ViewLayer *view_layer = t->view_layer; Base *base_act = view_layer->basact; + + eSnapTargetSelect ret = SCE_SNAP_TARGET_ALL; + + bool use_snap_active = (t->tsnap.target_select & SCE_SNAP_TARGET_NOT_ACTIVE) == 0; + bool use_snap_edit = (t->tsnap.target_select & SCE_SNAP_TARGET_NOT_EDITED) == 0; + bool use_snap_nonedit = (t->tsnap.target_select & SCE_SNAP_TARGET_NOT_NONEDITED) == 0; + bool use_snap_selectable_only = (t->tsnap.target_select & SCE_SNAP_TARGET_ONLY_SELECTABLE) != 0; + if (ELEM(t->spacetype, SPACE_VIEW3D, SPACE_IMAGE) && !(t->options & CTX_CAMERA)) { + if (base_act && (base_act->object->mode & OB_MODE_PARTICLE_EDIT)) { + /* Particles edit mode. */ + return ret; + } + + if (use_snap_selectable_only) { + ret |= SCE_SNAP_TARGET_ONLY_SELECTABLE; + } + if (t->options & (CTX_GPENCIL_STROKES | CTX_CURSOR | CTX_OBMODE_XFORM_OBDATA)) { /* In "Edit Strokes" mode, * snap tool can perform snap to selected or active objects (see T49632) * TODO: perform self snap in gpencil_strokes. * * When we're moving the origins, allow snapping onto our own geometry (see T69132). */ - return SCE_SNAP_TARGET_ALL; + return ret; } const int obedit_type = t->obedit_type; if (obedit_type != -1) { /* Edit mode */ - if (ELEM(obedit_type, - OB_MESH, - OB_ARMATURE, - OB_CURVES_LEGACY, - OB_SURF, - OB_LATTICE, - OB_MBALL)) { - /* Temporary limited to edit mode meshes, armature, curves, lattice and metaballs. */ - - if ((obedit_type == OB_MESH) && (t->flag & T_PROP_EDIT)) { - /* Exclude editmesh if using proportional edit */ - return SCE_SNAP_TARGET_NOT_EDITED; + if (obedit_type == OB_MESH) { + /* Editing a mesh */ + if ((t->flag & T_PROP_EDIT) != 0) { + /* Exclude editmesh when using proportional edit */ + ret |= SCE_SNAP_TARGET_NOT_EDITED; } - - if (!t->tsnap.snap_self) { - return SCE_SNAP_TARGET_NOT_ACTIVE; + if (!use_snap_active) { + ret |= SCE_SNAP_TARGET_NOT_ACTIVE; + } + if (!use_snap_edit) { + ret |= SCE_SNAP_TARGET_NOT_EDITED; + } + if (!use_snap_nonedit) { + ret |= SCE_SNAP_TARGET_NOT_NONEDITED; } - - return SCE_SNAP_TARGET_NOT_SELECTED; } - - return SCE_SNAP_TARGET_ALL; + else if (ELEM(obedit_type, OB_ARMATURE, OB_CURVES_LEGACY, OB_SURF, OB_LATTICE, OB_MBALL)) { + /* Temporary limited to edit mode armature, curves, surfaces, lattices, and metaballs. */ + ret |= SCE_SNAP_TARGET_NOT_SELECTED; + } } - - if (base_act && (base_act->object->mode & OB_MODE_PARTICLE_EDIT)) { - /* Particles edit mode. */ - return SCE_SNAP_TARGET_ALL; + else { + /* Object or pose mode. */ + ret |= SCE_SNAP_TARGET_NOT_SELECTED | SCE_SNAP_TARGET_NOT_ACTIVE; } - - /* Object or pose mode. */ - return SCE_SNAP_TARGET_NOT_SELECTED; } - - if (ELEM(t->spacetype, SPACE_NODE, SPACE_SEQ)) { - return SCE_SNAP_TARGET_NOT_SELECTED; + else if (ELEM(t->spacetype, SPACE_NODE, SPACE_SEQ)) { + ret |= SCE_SNAP_TARGET_NOT_SELECTED; } - return SCE_SNAP_TARGET_ALL; + return ret; } static void initSnappingMode(TransInfo *t) { - ToolSettings *ts = t->settings; - t->tsnap.mode = snap_mode_from_spacetype(t); - t->tsnap.target_select = snap_select_type_get(t); - - if ((t->spacetype != SPACE_VIEW3D) || !(ts->snap_mode & SCE_SNAP_MODE_FACE)) { + if ((t->spacetype != SPACE_VIEW3D) || !(t->tsnap.mode & SCE_SNAP_MODE_FACE_RAYCAST)) { /* Force project off when not supported. */ t->tsnap.project = false; } @@ -753,9 +842,14 @@ static void initSnappingMode(TransInfo *t) void initSnapping(TransInfo *t, wmOperator *op) { + ToolSettings *ts = t->settings; + eSnapSourceSelect snap_source = ts->snap_target; + resetSnapping(t); + t->tsnap.mode = snap_mode_from_spacetype(t); t->tsnap.flag = snap_flag_from_spacetype(t); - eSnapSourceSelect snap_source = t->settings->snap_target; + t->tsnap.target_select = snap_target_select_from_spacetype(t); + t->tsnap.face_nearest_steps = max_ii(ts->snap_face_nearest_steps, 1); /* if snap property exists */ PropertyRNA *prop; @@ -764,11 +858,16 @@ void initSnapping(TransInfo *t, wmOperator *op) if (RNA_property_boolean_get(op->ptr, prop)) { t->modifiers |= MOD_SNAP; + if ((prop = RNA_struct_find_property(op->ptr, "snap_elements")) && + RNA_property_is_set(op->ptr, prop)) { + t->tsnap.mode = RNA_property_enum_get(op->ptr, prop); + } + + /* TODO(@gfxcoder): Rename `snap_target` to `snap_source` to avoid previous ambiguity of + * "target" (now, "source" is geometry to be moved and "target" is geometry to which moved + * geometry is snapped). */ if ((prop = RNA_struct_find_property(op->ptr, "snap_target")) && RNA_property_is_set(op->ptr, prop)) { - /* TODO(@gfxcoder): Rename `snap_target` to `snap_source` to avoid - * previous ambiguity of "target" (now, "source" is geometry to be moved and "target" is - * geometry to which moved geometry is snapped). */ snap_source = RNA_property_enum_get(op->ptr, prop); } @@ -791,9 +890,33 @@ void initSnapping(TransInfo *t, wmOperator *op) t->tsnap.project = RNA_property_boolean_get(op->ptr, prop); } + /* use_snap_self is misnamed and should be use_snap_active */ if ((prop = RNA_struct_find_property(op->ptr, "use_snap_self")) && RNA_property_is_set(op->ptr, prop)) { - t->tsnap.snap_self = RNA_property_boolean_get(op->ptr, prop); + SET_FLAG_FROM_TEST(t->tsnap.target_select, + !RNA_property_boolean_get(op->ptr, prop), + SCE_SNAP_TARGET_NOT_ACTIVE); + } + + if ((prop = RNA_struct_find_property(op->ptr, "use_snap_edit")) && + RNA_property_is_set(op->ptr, prop)) { + SET_FLAG_FROM_TEST(t->tsnap.target_select, + !RNA_property_boolean_get(op->ptr, prop), + SCE_SNAP_TARGET_NOT_EDITED); + } + + if ((prop = RNA_struct_find_property(op->ptr, "use_snap_nonedit")) && + RNA_property_is_set(op->ptr, prop)) { + SET_FLAG_FROM_TEST(t->tsnap.target_select, + !RNA_property_boolean_get(op->ptr, prop), + SCE_SNAP_TARGET_NOT_NONEDITED); + } + + if ((prop = RNA_struct_find_property(op->ptr, "use_snap_selectable")) && + RNA_property_is_set(op->ptr, prop)) { + SET_FLAG_FROM_TEST(t->tsnap.target_select, + RNA_property_boolean_get(op->ptr, prop), + SCE_SNAP_TARGET_ONLY_SELECTABLE); } } } @@ -805,8 +928,19 @@ void initSnapping(TransInfo *t, wmOperator *op) t->tsnap.align = ((t->tsnap.flag & SCE_SNAP_ROTATE) != 0); t->tsnap.project = ((t->tsnap.flag & SCE_SNAP_PROJECT) != 0); - t->tsnap.snap_self = !((t->tsnap.flag & SCE_SNAP_NO_SELF) != 0); t->tsnap.peel = ((t->tsnap.flag & SCE_SNAP_PROJECT) != 0); + SET_FLAG_FROM_TEST(t->tsnap.target_select, + (ts->snap_flag & SCE_SNAP_NOT_TO_ACTIVE), + SCE_SNAP_TARGET_NOT_ACTIVE); + SET_FLAG_FROM_TEST(t->tsnap.target_select, + !(ts->snap_flag & SCE_SNAP_TO_INCLUDE_EDITED), + SCE_SNAP_TARGET_NOT_EDITED); + SET_FLAG_FROM_TEST(t->tsnap.target_select, + !(ts->snap_flag & SCE_SNAP_TO_INCLUDE_NONEDITED), + SCE_SNAP_TARGET_NOT_NONEDITED); + SET_FLAG_FROM_TEST(t->tsnap.target_select, + (ts->snap_flag & SCE_SNAP_TO_ONLY_SELECTABLE), + SCE_SNAP_TARGET_ONLY_SELECTABLE); } t->tsnap.source_select = snap_source; @@ -991,8 +1125,8 @@ static void snap_calc_view3d_fn(TransInfo *t, float *UNUSED(vec)) found = (snap_elem != SCE_SNAP_MODE_NONE); } if ((found == false) && (t->tsnap.mode & SCE_SNAP_MODE_VOLUME)) { - found = peelObjectsTransform( - t, mval, (t->settings->snap_flag & SCE_SNAP_PEEL_OBJECT) != 0, loc, no, NULL); + bool use_peel = (t->settings->snap_flag & SCE_SNAP_PEEL_OBJECT) != 0; + found = peelObjectsTransform(t, mval, use_peel, loc, no, NULL); if (found) { snap_elem = SCE_SNAP_MODE_VOLUME; @@ -1026,7 +1160,7 @@ static void snap_calc_uv_fn(TransInfo *t, float *UNUSED(vec)) objects, objects_len, t->mval, - t->tsnap.target_select == SCE_SNAP_TARGET_NOT_SELECTED, + t->tsnap.target_select & SCE_SNAP_TARGET_NOT_SELECTED, &dist_sq, t->tsnap.snapPoint)) { t->tsnap.snapPoint[0] *= t->aspect[0]; @@ -1321,9 +1455,10 @@ eSnapMode snapObjectsTransform( &(const struct SnapObjectParams){ .snap_target_select = t->tsnap.target_select, .edit_mode_type = (t->flag & T_EDIT) != 0 ? SNAP_GEOM_EDIT : SNAP_GEOM_FINAL, - .use_occlusion_test = t->settings->snap_mode != SCE_SNAP_MODE_FACE, + .use_occlusion_test = t->settings->snap_mode != SCE_SNAP_MODE_FACE_RAYCAST, .use_backface_culling = t->tsnap.use_backface_culling, }, + NULL, mval, target, dist_px, @@ -1423,7 +1558,7 @@ bool peelObjectsTransform(TransInfo *t, static bool snapNodeTest(View2D *v2d, bNode *node, eSnapTargetSelect snap_target_select) { /* node is use for snapping only if a) snap mode matches and b) node is inside the view */ - return ((snap_target_select == SCE_SNAP_TARGET_NOT_SELECTED && !(node->flag & NODE_SELECT)) || + return (((snap_target_select & SCE_SNAP_TARGET_NOT_SELECTED) && !(node->flag & NODE_SELECT)) || (snap_target_select == SCE_SNAP_TARGET_ALL && !(node->flag & NODE_ACTIVE))) && (node->totr.xmin < v2d->cur.xmax && node->totr.xmax > v2d->cur.xmin && node->totr.ymin < v2d->cur.ymax && node->totr.ymax > v2d->cur.ymin); diff --git a/source/blender/editors/transform/transform_snap.h b/source/blender/editors/transform/transform_snap.h index 6db027df067..3672e76c778 100644 --- a/source/blender/editors/transform/transform_snap.h +++ b/source/blender/editors/transform/transform_snap.h @@ -40,15 +40,16 @@ float transform_snap_increment_get(const TransInfo *t); bool transform_snap_grid(TransInfo *t, float *val); bool activeSnap(const TransInfo *t); -bool activeSnap_with_project(const TransInfo *t); +bool activeSnap_SnappingIndividual(const TransInfo *t); +bool activeSnap_SnappingAsGroup(const TransInfo *t); bool validSnap(const TransInfo *t); void initSnapping(struct TransInfo *t, struct wmOperator *op); void freeSnapping(struct TransInfo *t); -void applyProject(TransInfo *t); +void applySnappingIndividual(TransInfo *t); void applyGridAbsolute(TransInfo *t); -void applySnapping(TransInfo *t, float *vec); +void applySnappingAsGroup(TransInfo *t, float *vec); void resetSnapping(TransInfo *t); eRedrawFlag handleSnapping(TransInfo *t, const struct wmEvent *event); void drawSnapping(const struct bContext *C, TransInfo *t); diff --git a/source/blender/editors/transform/transform_snap_object.cc b/source/blender/editors/transform/transform_snap_object.cc index bd3d29fac8a..8a1eca3a6f7 100644 --- a/source/blender/editors/transform/transform_snap_object.cc +++ b/source/blender/editors/transform/transform_snap_object.cc @@ -405,6 +405,62 @@ static SnapData_EditMesh *snap_object_data_editmesh_get(SnapObjectContext *sctx, return sod; } +static BVHTreeFromMesh *snap_object_data_mesh_treedata_get(SnapObjectContext *sctx, + Object *ob_eval, + const Mesh *me_eval, + bool use_hide) +{ + SnapData_Mesh *sod = snap_object_data_mesh_get(sctx, ob_eval, me_eval, use_hide); + return &sod->treedata_mesh; +} + +static BVHTreeFromEditMesh *snap_object_data_editmesh_treedata_get(SnapObjectContext *sctx, + Object *ob_eval, + BMEditMesh *em) +{ + SnapData_EditMesh *sod = snap_object_data_editmesh_get(sctx, ob_eval, em); + + BVHTreeFromEditMesh *treedata = &sod->treedata_editmesh; + + if (treedata->tree == nullptr) { + /* Operators only update the editmesh looptris of the original mesh. */ + BLI_assert(sod->treedata_editmesh.em == + BKE_editmesh_from_object(DEG_get_original_object(ob_eval))); + em = sod->treedata_editmesh.em; + + if (sctx->callbacks.edit_mesh.test_face_fn) { + BMesh *bm = em->bm; + BLI_assert(poly_to_tri_count(bm->totface, bm->totloop) == em->tottri); + + BLI_bitmap *elem_mask = BLI_BITMAP_NEW(em->tottri, __func__); + int looptri_num_active = BM_iter_mesh_bitmap_from_filter_tessface( + bm, + elem_mask, + sctx->callbacks.edit_mesh.test_face_fn, + sctx->callbacks.edit_mesh.user_data); + + bvhtree_from_editmesh_looptri_ex(treedata, em, elem_mask, looptri_num_active, 0.0f, 4, 6); + + MEM_freeN(elem_mask); + } + else { + /* Only cache if BVH-tree is created without a mask. + * This helps keep a standardized BVH-tree in cache. */ + BKE_bvhtree_from_editmesh_get(treedata, + em, + 4, + BVHTREE_FROM_EM_LOOPTRI, + &sod->mesh_runtime->bvh_cache, + static_cast(sod->mesh_runtime->eval_mutex)); + } + } + if (treedata == nullptr || treedata->tree == nullptr) { + return nullptr; + } + + return treedata; +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -419,16 +475,16 @@ using IterSnapObjsCallback = void (*)(SnapObjectContext *sctx, void *data); static bool snap_object_is_snappable(const SnapObjectContext *sctx, - const eSnapTargetSelect snap_select, + const eSnapTargetSelect snap_target_select, const Base *base_act, - const Base *base, - const bool is_in_object_mode) + const Base *base) { if (!BASE_VISIBLE(sctx->runtime.v3d, base)) { return false; } - if ((snap_select == SCE_SNAP_TARGET_ALL) || (base->flag_legacy & BA_TRANSFORM_LOCKED_IN_PLACE)) { + if ((snap_target_select == SCE_SNAP_TARGET_ALL) || + (base->flag_legacy & BA_TRANSFORM_LOCKED_IN_PLACE)) { return true; } @@ -436,25 +492,37 @@ static bool snap_object_is_snappable(const SnapObjectContext *sctx, return false; } - if (snap_select == SCE_SNAP_TARGET_NOT_ACTIVE) { - return base_act != base; - } + /* get base attributes */ + const bool is_active = (base_act == base); + const bool is_selected = (base->flag & BASE_SELECTED) || (base->flag_legacy & BA_WAS_SEL); + const bool is_edited = (base->object->mode == OB_MODE_EDIT); + const bool is_selectable = (base->flag & BASE_SELECTABLE); + const bool is_in_object_mode = (base_act == NULL) || (base_act->object->mode == OB_MODE_OBJECT); - if (snap_select == SCE_SNAP_TARGET_NOT_EDITED) { - return base->object->mode != OB_MODE_EDIT; + if (is_edited) { + if (is_active) { + if (snap_target_select & SCE_SNAP_TARGET_NOT_ACTIVE) { + return false; + } + } + else { + if (snap_target_select & SCE_SNAP_TARGET_NOT_EDITED) { + return false; + } + } } - if (snap_select == SCE_SNAP_TARGET_NOT_SELECTED) { - if (is_in_object_mode) { - return !((base->flag & BASE_SELECTED) || (base->flag_legacy & BA_WAS_SEL)); - } + if ((snap_target_select & SCE_SNAP_TARGET_NOT_NONEDITED) && !is_edited) { + return false; + } - /* What is selectable or not is part of the object and depends on the mode. */ - return true; + if ((snap_target_select & SCE_SNAP_TARGET_ONLY_SELECTABLE) && !is_selectable) { + return false; } - if (snap_select == SCE_SNAP_TARGET_ONLY_SELECTABLE) { - return (base->flag & BASE_SELECTABLE) != 0; + if ((snap_target_select & SCE_SNAP_TARGET_NOT_SELECTED) && is_in_object_mode && is_selected) { + /* What is selectable or not is part of the object and depends on the mode. */ + return false; } return true; @@ -470,11 +538,10 @@ static void iter_snap_objects(SnapObjectContext *sctx, { ViewLayer *view_layer = DEG_get_input_view_layer(sctx->runtime.depsgraph); const eSnapTargetSelect snap_target_select = params->snap_target_select; - Base *base_act = view_layer->basact; - const bool is_in_object_mode = !base_act || base_act->object->mode == OB_MODE_OBJECT; + LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { - if (!snap_object_is_snappable(sctx, snap_target_select, base_act, base, is_in_object_mode)) { + if (!snap_object_is_snappable(sctx, snap_target_select, base_act, base)) { continue; } @@ -850,40 +917,9 @@ static bool raycastEditMesh(SnapObjectContext *sctx, len_diff = 0.0f; } - BVHTreeFromEditMesh *treedata = &sod->treedata_editmesh; - - if (treedata->tree == nullptr) { - em = sod->treedata_editmesh.em; - - if (sctx->callbacks.edit_mesh.test_face_fn) { - BMesh *bm = em->bm; - BLI_assert(poly_to_tri_count(bm->totface, bm->totloop) == em->tottri); - - BLI_bitmap *elem_mask = BLI_BITMAP_NEW(em->tottri, __func__); - int looptri_num_active = BM_iter_mesh_bitmap_from_filter_tessface( - bm, - elem_mask, - sctx->callbacks.edit_mesh.test_face_fn, - sctx->callbacks.edit_mesh.user_data); - - bvhtree_from_editmesh_looptri_ex(treedata, em, elem_mask, looptri_num_active, 0.0f, 4, 6); - - MEM_freeN(elem_mask); - } - else { - /* Only cache if bvhtree is created without a mask. - * This helps keep a standardized bvhtree in cache. */ - BKE_bvhtree_from_editmesh_get(treedata, - em, - 4, - BVHTREE_FROM_EM_LOOPTRI, - &sod->mesh_runtime->bvh_cache, - static_cast(sod->mesh_runtime->eval_mutex)); - } - - if (treedata->tree == nullptr) { - return retval; - } + BVHTreeFromEditMesh *treedata = snap_object_data_editmesh_treedata_get(sctx, ob_eval, em); + if (treedata == nullptr) { + return retval; } float timat[3][3]; /* transpose inverse matrix for normals */ @@ -1098,7 +1134,7 @@ static void raycast_obj_fn(SnapObjectContext *sctx, * \param r_loc: Hit location. * \param r_no: Hit normal (optional). * \param r_index: Hit index or -1 when no valid index is found. - * (currently only set to the polygon index when using `snap_to == SCE_SNAP_MODE_FACE`). + * (currently only set to the polygon index when using `snap_to == SCE_SNAP_MODE_FACE_RAYCAST`). * \param r_ob: Hit object. * \param r_obmat: Object matrix (may not be #Object.obmat with dupli-instances). * \param r_hit_list: List of #SnapObjectHitDepth (caller must free). @@ -1149,6 +1185,324 @@ static bool raycastObjects(SnapObjectContext *sctx, /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Surface Snap Funcs + * \{ */ + +struct NearestWorldObjUserData { + const float *init_co; + const float *curr_co; + /* return args */ + float *r_loc; + float *r_no; + int *r_index; + float r_dist_sq; + Object **r_ob; + float (*r_obmat)[4]; + ListBase *r_hit_list; + bool ret; +}; + +static void nearest_world_tree_co(BVHTree *tree, + BVHTree_NearestPointCallback nearest_cb, + void *treedata, + float co[3], + float r_co[3], + float r_no[3], + int *r_index, + float *r_dist_sq) +{ + BVHTreeNearest nearest = {}; + nearest.index = -1; + copy_v3_fl(nearest.co, FLT_MAX); + nearest.dist_sq = FLT_MAX; + + BLI_bvhtree_find_nearest(tree, co, &nearest, nearest_cb, treedata); + + if (r_co) { + copy_v3_v3(r_co, nearest.co); + } + if (r_no) { + copy_v3_v3(r_no, nearest.no); + } + if (r_index) { + *r_index = nearest.index; + } + if (r_dist_sq) { + float diff[3]; + sub_v3_v3v3(diff, co, nearest.co); + *r_dist_sq = len_squared_v3(diff); + } +} + +static bool nearest_world_tree(SnapObjectContext *UNUSED(sctx), + const struct SnapObjectParams *params, + BVHTree *tree, + BVHTree_NearestPointCallback nearest_cb, + void *treedata, + const float (*obmat)[4], + const float init_co[3], + const float curr_co[3], + float *r_dist_sq, + float *r_loc, + float *r_no, + int *r_index) +{ + if (curr_co == nullptr || init_co == nullptr) { + /* No location to work with, so just return. */ + return false; + } + + float imat[4][4]; + invert_m4_m4(imat, obmat); + + float timat[3][3]; /* transpose inverse matrix for normals */ + transpose_m3_m4(timat, imat); + + /* compute offset between init co and prev co in local space */ + float init_co_local[3], curr_co_local[3]; + float delta_local[3]; + mul_v3_m4v3(init_co_local, imat, init_co); + mul_v3_m4v3(curr_co_local, imat, curr_co); + sub_v3_v3v3(delta_local, curr_co_local, init_co_local); + + float dist_sq; + if (params->keep_on_same_target) { + nearest_world_tree_co( + tree, nearest_cb, treedata, init_co_local, nullptr, nullptr, nullptr, &dist_sq); + } + else { + /* NOTE: when `params->face_nearest_steps == 1`, the return variables of function below contain + * the answer. We could return immediately after updating r_loc, r_no, r_index, but that would + * also complicate the code. Foregoing slight optimization for code clarity. */ + nearest_world_tree_co( + tree, nearest_cb, treedata, curr_co_local, nullptr, nullptr, nullptr, &dist_sq); + } + if (*r_dist_sq <= dist_sq) { + return false; + } + *r_dist_sq = dist_sq; + + /* scale to make `snap_face_nearest_steps` steps */ + float step_scale_factor = 1.0f / max_ff(1.0f, (float)params->face_nearest_steps); + mul_v3_fl(delta_local, step_scale_factor); + + float co_local[3]; + float no_local[3]; + int index; + + copy_v3_v3(co_local, init_co_local); + + for (int i = 0; i < params->face_nearest_steps; i++) { + add_v3_v3(co_local, delta_local); + nearest_world_tree_co( + tree, nearest_cb, treedata, co_local, co_local, no_local, &index, nullptr); + } + + mul_v3_m4v3(r_loc, obmat, co_local); + + if (r_no) { + mul_v3_m3v3(r_no, timat, no_local); + normalize_v3(r_no); + } + + if (r_index) { + *r_index = index; + } + + return true; +} + +static bool nearest_world_mesh(SnapObjectContext *sctx, + const struct SnapObjectParams *params, + Object *ob_eval, + const Mesh *me_eval, + const float (*obmat)[4], + bool use_hide, + const float init_co[3], + const float curr_co[3], + float *r_dist_sq, + float *r_loc, + float *r_no, + int *r_index) +{ + BVHTreeFromMesh *treedata = snap_object_data_mesh_treedata_get(sctx, ob_eval, me_eval, use_hide); + if (treedata == nullptr || treedata->tree == nullptr) { + return false; + } + + return nearest_world_tree(sctx, + params, + treedata->tree, + treedata->nearest_callback, + treedata, + obmat, + init_co, + curr_co, + r_dist_sq, + r_loc, + r_no, + r_index); +} + +static bool nearest_world_editmesh(SnapObjectContext *sctx, + const struct SnapObjectParams *params, + Object *ob_eval, + BMEditMesh *em, + const float (*obmat)[4], + const float init_co[3], + const float curr_co[3], + float *r_dist_sq, + float *r_loc, + float *r_no, + int *r_index) +{ + BVHTreeFromEditMesh *treedata = snap_object_data_editmesh_treedata_get(sctx, ob_eval, em); + if (treedata == nullptr || treedata->tree == nullptr) { + return false; + } + + return nearest_world_tree(sctx, + params, + treedata->tree, + treedata->nearest_callback, + treedata, + obmat, + init_co, + curr_co, + r_dist_sq, + r_loc, + r_no, + r_index); +} +static void nearest_world_object_fn(SnapObjectContext *sctx, + const struct SnapObjectParams *params, + Object *ob_eval, + const float obmat[4][4], + bool is_object_active, + void *data) +{ + struct NearestWorldObjUserData *dt = static_cast(data); + + bool retval = false; + switch (ob_eval->type) { + case OB_MESH: { + const eSnapEditType edit_mode_type = params->edit_mode_type; + bool use_hide = false; + const Mesh *me_eval = mesh_for_snap(ob_eval, edit_mode_type, &use_hide); + if (me_eval) { + retval = nearest_world_mesh(sctx, + params, + ob_eval, + me_eval, + obmat, + use_hide, + dt->init_co, + dt->curr_co, + &dt->r_dist_sq, + dt->r_loc, + dt->r_no, + dt->r_index); + } + else { + BMEditMesh *em = BKE_editmesh_from_object(ob_eval); + BLI_assert_msg(em == BKE_editmesh_from_object(DEG_get_original_object(ob_eval)), + "Make sure there is only one pointer for looptris"); + retval = nearest_world_editmesh(sctx, + params, + ob_eval, + em, + obmat, + dt->init_co, + dt->curr_co, + &dt->r_dist_sq, + dt->r_loc, + dt->r_no, + dt->r_index); + } + break; + } + case OB_CURVES_LEGACY: + case OB_SURF: + case OB_FONT: + if (!is_object_active) { + const Mesh *me_eval = BKE_object_get_evaluated_mesh(ob_eval); + if (me_eval) { + retval = nearest_world_mesh(sctx, + params, + ob_eval, + me_eval, + obmat, + false, + dt->init_co, + dt->curr_co, + &dt->r_dist_sq, + dt->r_loc, + dt->r_no, + dt->r_index); + } + } + break; + } + + if (retval) { + if (dt->r_ob) { + *dt->r_ob = ob_eval; + } + if (dt->r_obmat) { + copy_m4_m4(dt->r_obmat, obmat); + } + dt->ret = true; + } +} + +/** + * Main Nearest World Surface Function + * =================================== + * + * Walks through all objects in the scene to find the nearest location on target surface. + * + * \param sctx: Snap context to store data. + * \param params: Settings for snapping. + * \param init_co: Initial location of source point. + * \param prev_co: Current location of source point after transformation but before snapping. + * + * Output Args + * ----------- + * + * \param r_loc: Location of nearest point on target surface. + * \param r_no: Normal of nearest point on target surface. + * \param r_index: Index of nearest polygon on target surface. + * \param r_ob: Nearest target object. + * \param r_obmat: Nearest target matrix (may not be #Object.obmat with dupli-instances). + */ +static bool nearestWorldObjects(SnapObjectContext *sctx, + const struct SnapObjectParams *params, + const float init_co[3], + const float curr_co[3], + float *r_loc /* NOLINT */, + float *r_no /* NOLINT */, + int *r_index /* NOLINT */, + Object **r_ob, + float r_obmat[4][4]) +{ + NearestWorldObjUserData data = {}; + data.init_co = init_co; + data.curr_co = curr_co; + data.r_loc = r_loc; + data.r_no = r_no; + data.r_index = r_index; + data.r_dist_sq = FLT_MAX; + data.r_ob = r_ob; + data.r_obmat = r_obmat; + data.ret = false; + + iter_snap_objects(sctx, params, nearest_world_object_fn, &data); + return data.ret; +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Snap Nearest utilities * \{ */ @@ -1842,7 +2196,8 @@ static eSnapMode snapArmature(SnapObjectContext *sctx, { eSnapMode retval = SCE_SNAP_MODE_NONE; - if (sctx->runtime.snap_to_flag == SCE_SNAP_MODE_FACE) { /* Currently only edge and vert */ + if (sctx->runtime.snap_to_flag == SCE_SNAP_MODE_FACE_RAYCAST) { + /* Currently only edge and vert */ return retval; } @@ -2328,7 +2683,7 @@ static eSnapMode snapMesh(SnapObjectContext *sctx, float r_no[3], int *r_index) { - BLI_assert(sctx->runtime.snap_to_flag != SCE_SNAP_MODE_FACE); + BLI_assert(sctx->runtime.snap_to_flag != SCE_SNAP_MODE_FACE_RAYCAST); if (me_eval->totvert == 0) { return SCE_SNAP_MODE_NONE; } @@ -2506,9 +2861,9 @@ static eSnapMode snapEditMesh(SnapObjectContext *sctx, float r_no[3], int *r_index) { - BLI_assert(sctx->runtime.snap_to_flag != SCE_SNAP_MODE_FACE); + BLI_assert(sctx->runtime.snap_to_flag != SCE_SNAP_MODE_FACE_RAYCAST); - if ((sctx->runtime.snap_to_flag & ~SCE_SNAP_MODE_FACE) == SCE_SNAP_MODE_VERTEX) { + if ((sctx->runtime.snap_to_flag & ~SCE_SNAP_MODE_FACE_RAYCAST) == SCE_SNAP_MODE_VERTEX) { if (em->bm->totvert == 0) { return SCE_SNAP_MODE_NONE; } @@ -2811,7 +3166,7 @@ static void snap_obj_fn(SnapObjectContext *sctx, * \param r_loc: Hit location. * \param r_no: Hit normal (optional). * \param r_index: Hit index or -1 when no valid index is found. - * (currently only set to the polygon index when using `snap_to == SCE_SNAP_MODE_FACE`). + * (currently only set to the polygon index when using `snap_to == SCE_SNAP_MODE_FACE_RAYCAST`). * \param r_ob: Hit object. * \param r_obmat: Object matrix (may not be #Object.obmat with dupli-instances). */ @@ -3014,6 +3369,7 @@ static eSnapMode transform_snap_context_project_view3d_mixed_impl(SnapObjectCont const View3D *v3d, const eSnapMode snap_to_flag, const SnapObjectParams *params, + const float init_co[3], const float mval[2], const float prev_co[3], float *dist_px, @@ -3045,11 +3401,36 @@ static eSnapMode transform_snap_context_project_view3d_mixed_impl(SnapObjectCont bool use_occlusion_test = params->use_occlusion_test && !XRAY_ENABLED(v3d); - if (snap_to_flag & SCE_SNAP_MODE_FACE || use_occlusion_test) { + /* Note: if both face raycast and face nearest are enabled, first find result of nearest, then + * override with raycast. */ + if ((snap_to_flag & SCE_SNAP_MODE_FACE_NEAREST) && !has_hit) { + has_hit = nearestWorldObjects( + sctx, params, init_co, prev_co, loc, no, &index, &ob_eval, obmat); + + if (has_hit) { + retval = SCE_SNAP_MODE_FACE_NEAREST; + + copy_v3_v3(r_loc, loc); + if (r_no) { + copy_v3_v3(r_no, no); + } + if (r_ob) { + *r_ob = ob_eval; + } + if (r_obmat) { + copy_m4_m4(r_obmat, obmat); + } + if (r_index) { + *r_index = index; + } + } + } + + if (snap_to_flag & SCE_SNAP_MODE_FACE_RAYCAST || use_occlusion_test) { float ray_start[3], ray_normal[3]; if (!ED_view3d_win_to_ray_clipped_ex( depsgraph, region, v3d, mval, nullptr, ray_normal, ray_start, true)) { - return SCE_SNAP_MODE_NONE; + return retval; } float dummy_ray_depth = BVH_RAYCAST_DIST_MAX; @@ -3071,8 +3452,8 @@ static eSnapMode transform_snap_context_project_view3d_mixed_impl(SnapObjectCont copy_v3_v3(r_face_nor, no); } - if ((snap_to_flag & SCE_SNAP_MODE_FACE)) { - retval = SCE_SNAP_MODE_FACE; + if ((snap_to_flag & SCE_SNAP_MODE_FACE_RAYCAST)) { + retval = SCE_SNAP_MODE_FACE_RAYCAST; copy_v3_v3(r_loc, loc); if (r_no) { @@ -3193,6 +3574,7 @@ eSnapMode ED_transform_snap_object_project_view3d_ex(SnapObjectContext *sctx, const View3D *v3d, const eSnapMode snap_to, const SnapObjectParams *params, + const float init_co[3], const float mval[2], const float prev_co[3], float *dist_px, @@ -3209,6 +3591,7 @@ eSnapMode ED_transform_snap_object_project_view3d_ex(SnapObjectContext *sctx, v3d, snap_to, params, + init_co, mval, prev_co, dist_px, @@ -3226,6 +3609,7 @@ eSnapMode ED_transform_snap_object_project_view3d(SnapObjectContext *sctx, const View3D *v3d, const eSnapMode snap_to, const SnapObjectParams *params, + const float init_co[3], const float mval[2], const float prev_co[3], float *dist_px, @@ -3238,6 +3622,7 @@ eSnapMode ED_transform_snap_object_project_view3d(SnapObjectContext *sctx, v3d, snap_to, params, + init_co, mval, prev_co, dist_px, diff --git a/source/blender/makesdna/DNA_scene_defaults.h b/source/blender/makesdna/DNA_scene_defaults.h index 74db1d14bbc..6cc01d254ce 100644 --- a/source/blender/makesdna/DNA_scene_defaults.h +++ b/source/blender/makesdna/DNA_scene_defaults.h @@ -337,7 +337,9 @@ .snap_mode = SCE_SNAP_MODE_INCREMENT, \ .snap_node_mode = SCE_SNAP_MODE_GRID, \ .snap_uv_mode = SCE_SNAP_MODE_INCREMENT, \ + .snap_flag = SCE_SNAP_TO_INCLUDE_EDITED | SCE_SNAP_TO_INCLUDE_NONEDITED, \ .snap_transform_mode_flag = SCE_SNAP_TRANSFORM_MODE_TRANSLATE, \ + .snap_face_nearest_steps = 1, \ \ .curve_paint_settings = _DNA_DEFAULTS_CurvePaintSettings, \ \ diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 1c62a550e60..c45b06f8c85 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -1496,19 +1496,24 @@ typedef struct ToolSettings { char transform_pivot_point; char transform_flag; /** Snap elements (per spacetype), #eSnapMode. */ - char snap_mode; + char _pad1[1]; + short snap_mode; char snap_node_mode; char snap_uv_mode; /** Generic flags (per spacetype), #eSnapFlag. */ - char snap_flag; - char snap_flag_node; - char snap_flag_seq; - char snap_uv_flag; + short snap_flag; + short snap_flag_node; + short snap_flag_seq; + short snap_uv_flag; /** Default snap source, #eSnapSourceSelect. */ - /* TODO(@gfxcoder): Rename `snap_target` to `snap_source_point`, because target is incorrect. */ + /* TODO(@gfxcoder): Rename `snap_target` to `snap_source` to avoid previous ambiguity of + * "target" (now, "source" is geometry to be moved and "target" is geometry to which moved + * geometry is snapped). */ char snap_target; /** Snap mask for transform modes, #eSnapTransformMode. */ char snap_transform_mode_flag; + /** Steps to break transformation into with face nearest snapping */ + short snap_face_nearest_steps; char proportional_edit, prop_mode; /** Proportional edit, object mode. */ @@ -2085,10 +2090,15 @@ typedef enum eSnapFlag { SCE_SNAP = (1 << 0), SCE_SNAP_ROTATE = (1 << 1), SCE_SNAP_PEEL_OBJECT = (1 << 2), - SCE_SNAP_PROJECT = (1 << 3), - SCE_SNAP_NO_SELF = (1 << 4), + SCE_SNAP_PROJECT = (1 << 3), /* Project individual elements instead of whole object. */ + SCE_SNAP_NOT_TO_ACTIVE = (1 << 4), /* Was `SCE_SNAP_NO_SELF`, but self should be active. */ SCE_SNAP_ABS_GRID = (1 << 5), SCE_SNAP_BACKFACE_CULLING = (1 << 6), + SCE_SNAP_KEEP_ON_SAME_OBJECT = (1 << 7), + /* see #eSnapTargetSelect */ + SCE_SNAP_TO_INCLUDE_EDITED = (1 << 8), + SCE_SNAP_TO_INCLUDE_NONEDITED = (1 << 9), + SCE_SNAP_TO_ONLY_SELECTABLE = (1 << 10), } eSnapFlag; /* Due to dependency conflicts with Cycles, header cannot directly include `BLI_utildefines.h`. */ /* TODO: move this macro to a more general place. */ @@ -2096,7 +2106,7 @@ typedef enum eSnapFlag { ENUM_OPERATORS(eSnapFlag, SCE_SNAP_BACKFACE_CULLING) #endif -/** #ToolSettings.snap_target and #TransSnap.source_select */ +/** See #ToolSettings.snap_target (to be renamed `snap_source`) and #TransSnap.source_select */ typedef enum eSnapSourceSelect { SCE_SNAP_SOURCE_CLOSEST = 0, SCE_SNAP_SOURCE_CENTER = 1, @@ -2104,13 +2114,15 @@ typedef enum eSnapSourceSelect { SCE_SNAP_SOURCE_ACTIVE = 3, } eSnapSourceSelect; -/** #TransSnap.target_select and #ToolSettings.snap_flag (SCE_SNAP_NO_SELF) */ +/** #TransSnap.target_select and #ToolSettings.snap_flag (#SCE_SNAP_NOT_TO_ACTIVE, + * #SCE_SNAP_TO_INCLUDE_EDITED, #SCE_SNAP_TO_INCLUDE_NONEDITED, #SCE_SNAP_TO_ONLY_SELECTABLE) */ typedef enum eSnapTargetSelect { SCE_SNAP_TARGET_ALL = 0, - SCE_SNAP_TARGET_NOT_SELECTED = 1, - SCE_SNAP_TARGET_NOT_ACTIVE = 2, - SCE_SNAP_TARGET_NOT_EDITED = 3, - SCE_SNAP_TARGET_ONLY_SELECTABLE = 4, + SCE_SNAP_TARGET_NOT_SELECTED = (1 << 0), + SCE_SNAP_TARGET_NOT_ACTIVE = (1 << 1), + SCE_SNAP_TARGET_NOT_EDITED = (1 << 2), + SCE_SNAP_TARGET_ONLY_SELECTABLE = (1 << 3), + SCE_SNAP_TARGET_NOT_NONEDITED = (1 << 4), } eSnapTargetSelect; /** #ToolSettings.snap_mode */ @@ -2118,19 +2130,21 @@ typedef enum eSnapMode { SCE_SNAP_MODE_NONE = 0, SCE_SNAP_MODE_VERTEX = (1 << 0), SCE_SNAP_MODE_EDGE = (1 << 1), - SCE_SNAP_MODE_FACE = (1 << 2), /* TODO(@gfxcoder): Rename to `SCE_SNAP_MODE_FACE_RAYCAST` - when other face snapping methods are added. */ + SCE_SNAP_MODE_FACE_RAYCAST = (1 << 2), SCE_SNAP_MODE_VOLUME = (1 << 3), SCE_SNAP_MODE_EDGE_MIDPOINT = (1 << 4), SCE_SNAP_MODE_EDGE_PERPENDICULAR = (1 << 5), - SCE_SNAP_MODE_GEOM = (SCE_SNAP_MODE_VERTEX | SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_FACE | - SCE_SNAP_MODE_EDGE_PERPENDICULAR | SCE_SNAP_MODE_EDGE_MIDPOINT), + SCE_SNAP_MODE_FACE_NEAREST = (1 << 8), + + SCE_SNAP_MODE_GEOM = (SCE_SNAP_MODE_VERTEX | SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_FACE_RAYCAST | + SCE_SNAP_MODE_EDGE_PERPENDICULAR | SCE_SNAP_MODE_EDGE_MIDPOINT | + SCE_SNAP_MODE_FACE_NEAREST), /** #ToolSettings.snap_node_mode */ SCE_SNAP_MODE_NODE_X = (1 << 0), SCE_SNAP_MODE_NODE_Y = (1 << 1), - /* #ToolSettings.snap_mode and #ToolSettings.snap_node_mode and #ToolSettings.snap_uv_mode */ + /** #ToolSettings.snap_mode and #ToolSettings.snap_node_mode and #ToolSettings.snap_uv_mode */ SCE_SNAP_MODE_INCREMENT = (1 << 6), SCE_SNAP_MODE_GRID = (1 << 7), } eSnapMode; diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index cc7df54e648..daf4c99845d 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -151,7 +151,16 @@ const EnumPropertyItem rna_enum_snap_element_items[] = { "Snap to increments of grid"}, {SCE_SNAP_MODE_VERTEX, "VERTEX", ICON_SNAP_VERTEX, "Vertex", "Snap to vertices"}, {SCE_SNAP_MODE_EDGE, "EDGE", ICON_SNAP_EDGE, "Edge", "Snap to edges"}, - {SCE_SNAP_MODE_FACE, "FACE", ICON_SNAP_FACE, "Face", "Snap to faces"}, + {SCE_SNAP_MODE_FACE_RAYCAST, + "FACE", /* TODO(@gfxcoder): replace with "FACE_RAYCAST" as "FACE" is not descriptive. */ + ICON_SNAP_FACE, + "Face Project", + "Snap by projecting onto faces"}, + {SCE_SNAP_MODE_FACE_NEAREST, + "FACE_NEAREST", + ICON_SNAP_FACE_NEAREST, + "Face Nearest", + "Snap to nearest point on faces"}, {SCE_SNAP_MODE_VOLUME, "VOLUME", ICON_SNAP_VOLUME, "Volume", "Snap to volume"}, {SCE_SNAP_MODE_EDGE_MIDPOINT, "EDGE_MIDPOINT", @@ -3301,6 +3310,21 @@ static void rna_def_tool_settings(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Snap Element", "Type of element to snap to"); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); /* header redraw */ + prop = RNA_def_property(srna, "snap_face_nearest_steps", PROP_INT, PROP_FACTOR); + RNA_def_property_int_sdna(prop, NULL, "snap_face_nearest_steps"); + RNA_def_property_range(prop, 1, 100); + RNA_def_property_ui_text( + prop, + "Face Nearest Steps", + "Number of steps to break transformation into for face nearest snapping"); + + prop = RNA_def_property(srna, "use_snap_to_same_target", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "snap_flag", SCE_SNAP_KEEP_ON_SAME_OBJECT); + RNA_def_property_ui_text( + prop, + "Snap to Same Target", + "Snap only to target that source was initially near (Face Nearest Only)"); + /* node editor uses own set of snap modes */ prop = RNA_def_property(srna, "snap_node_element", PROP_ENUM, PROP_NONE); RNA_def_property_enum_bitflag_sdna(prop, NULL, "snap_node_mode"); @@ -3323,9 +3347,9 @@ static void rna_def_tool_settings(BlenderRNA *brna) "Absolute grid alignment while translating (based on the pivot center)"); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); /* header redraw */ - /* TODO(@gfxcoder): Rename `snap_target` to `snap_source` to avoid - * previous ambiguity of "target" (now, "source" is geometry to be moved and "target" is - * geometry to which moved geometry is snapped). */ + /* TODO(@gfxcoder): Rename `snap_target` to `snap_source` to avoid previous ambiguity of "target" + * (now, "source" is geometry to be moved and "target" is geometry to which moved geometry is + * snapped). */ prop = RNA_def_property(srna, "snap_target", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, NULL, "snap_target"); RNA_def_property_enum_items(prop, rna_enum_snap_source_items); @@ -3350,9 +3374,30 @@ static void rna_def_tool_settings(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Backface Culling", "Exclude back facing geometry from snapping"); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); /* header redraw */ + /* TODO(@gfxcoder): Rename `use_snap_self` to `use_snap_active`, because active is correct but + * self is not (breaks API). This only makes a difference when more than one mesh is edited. */ prop = RNA_def_property(srna, "use_snap_self", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_negative_sdna(prop, NULL, "snap_flag", SCE_SNAP_NO_SELF); - RNA_def_property_ui_text(prop, "Project onto Self", "Snap onto itself (Edit Mode Only)"); + RNA_def_property_boolean_negative_sdna(prop, NULL, "snap_flag", SCE_SNAP_NOT_TO_ACTIVE); + RNA_def_property_ui_text( + prop, "Snap onto Active", "Snap onto itself only if enabled (Edit Mode Only)"); + RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); /* header redraw */ + + prop = RNA_def_property(srna, "use_snap_edit", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "snap_flag", SCE_SNAP_TO_INCLUDE_EDITED); + RNA_def_property_ui_text( + prop, "Snap onto Edited", "Snap onto non-active objects in Edit Mode (Edit Mode Only)"); + RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); /* header redraw */ + + prop = RNA_def_property(srna, "use_snap_nonedit", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "snap_flag", SCE_SNAP_TO_INCLUDE_NONEDITED); + RNA_def_property_ui_text( + prop, "Snap onto Non-edited", "Snap onto objects not in Edit Mode (Edit Mode Only)"); + RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); /* header redraw */ + + prop = RNA_def_property(srna, "use_snap_selectable", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "snap_flag", SCE_SNAP_TO_ONLY_SELECTABLE); + RNA_def_property_ui_text( + prop, "Snap onto Selectable Only", "Snap only onto objects that are selectable"); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); /* header redraw */ prop = RNA_def_property(srna, "use_snap_translate", PROP_BOOLEAN, PROP_NONE); -- cgit v1.2.3 From 209f2b85d745dc7e31b8272db1f30778d7fe2aa8 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 30 Jun 2022 12:14:20 +1000 Subject: Cleanup: quiet warning, remove punctuation in description --- source/blender/makesdna/DNA_gpencil_modifier_defaults.h | 1 - source/blender/makesrna/intern/rna_gpencil_modifier.c | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h index 808e404cc1e..324252ca369 100644 --- a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h +++ b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h @@ -312,7 +312,6 @@ .shadow_camera_near = 0.1f, \ .shadow_camera_far = 200.0f, \ .shadow_camera_size = 200.0f, \ - .stroke_depth_offset = 0.05,\ } #define _DNA_DEFAULT_LengthGpencilModifierData \ diff --git a/source/blender/makesrna/intern/rna_gpencil_modifier.c b/source/blender/makesrna/intern/rna_gpencil_modifier.c index 89bfb5684e2..0df11c7fc8e 100644 --- a/source/blender/makesrna/intern/rna_gpencil_modifier.c +++ b/source/blender/makesrna/intern/rna_gpencil_modifier.c @@ -3268,7 +3268,7 @@ static void rna_def_modifier_gpencillineart(BlenderRNA *brna) "Crease Threshold", "Angles smaller than this will be treated as creases. Crease angle " "priority: object line art crease override > mesh auto smooth angle > " - "line art default crease."); + "line art default crease"); RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update"); prop = RNA_def_property(srna, "split_angle", PROP_FLOAT, PROP_ANGLE); -- cgit v1.2.3 From b6c28002acb802dcad8a23c0721209103f00f686 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 30 Jun 2022 12:14:22 +1000 Subject: Cleanup: spelling in comments --- intern/cycles/device/oneapi/device.cpp | 2 +- intern/cycles/device/oneapi/device_impl.cpp | 4 ++-- intern/cycles/device/oneapi/dll_interface.h | 2 +- intern/cycles/device/oneapi/queue.h | 2 +- intern/cycles/kernel/device/oneapi/compat.h | 2 +- .../kernel/device/oneapi/dll_interface_template.h | 2 +- intern/cycles/kernel/device/oneapi/image.h | 4 ++-- intern/cycles/kernel/device/oneapi/kernel.cpp | 4 ++-- .../cycles/kernel/device/oneapi/kernel_templates.h | 7 +++--- intern/cycles/kernel/osl/services.cpp | 4 ++-- intern/cycles/kernel/svm/ao.h | 2 +- intern/cycles/kernel/svm/bevel.h | 2 +- intern/cycles/kernel/svm/closure.h | 4 ++-- intern/cycles/scene/scene.cpp | 4 ++-- intern/sky/include/sky_model.h | 10 ++++---- intern/sky/source/sky_model.cpp | 2 +- intern/sky/source/sky_model_data.h | 2 +- source/blender/blenkernel/intern/dynamicpaint.c | 2 +- source/blender/blenkernel/intern/image.cc | 6 ++--- .../blender/blenloader/intern/versioning_legacy.c | 2 +- .../blender/compositor/intern/COM_NodeConverter.h | 2 +- .../blender/draw/engines/eevee/eevee_cryptomatte.c | 2 +- source/blender/draw/engines/eevee/eevee_engine.c | 6 ++--- source/blender/draw/engines/eevee/eevee_private.h | 2 +- .../draw/engines/eevee/eevee_screen_raytrace.c | 2 +- .../shaders/infos/eevee_material_info.hh | 2 +- .../draw/engines/overlay/overlay_antialiasing.c | 2 +- source/blender/editors/include/ED_gpencil.h | 2 +- .../editors/interface/interface_region_tooltip.c | 2 +- source/blender/editors/mask/mask_add.c | 2 +- source/blender/editors/space_nla/nla_draw.c | 2 +- source/blender/editors/space_nla/space_nla.c | 2 +- .../gpencil_modifiers/intern/lineart/MOD_lineart.h | 12 +++++----- .../gpencil_modifiers/intern/lineart/lineart_cpu.c | 8 +++---- .../intern/lineart/lineart_shadow.c | 28 ++++++++++++---------- source/blender/gpu/metal/mtl_command_buffer.mm | 2 +- .../blender/makesdna/DNA_gpencil_modifier_types.h | 4 ++-- source/blender/makesdna/DNA_lineart_types.h | 10 ++++---- source/blender/render/intern/pipeline.c | 2 +- source/blender/render/intern/render_result.c | 4 ++-- .../windowmanager/intern/wm_event_system.cc | 2 +- 41 files changed, 87 insertions(+), 82 deletions(-) diff --git a/intern/cycles/device/oneapi/device.cpp b/intern/cycles/device/oneapi/device.cpp index b6f0f0c2b42..f70425b32cf 100644 --- a/intern/cycles/device/oneapi/device.cpp +++ b/intern/cycles/device/oneapi/device.cpp @@ -76,7 +76,7 @@ bool device_oneapi_init() /* NOTE(@nsirgien): we need to enable JIT cache from here and * right now this cache policy is controlled by env. variables. */ /* NOTE(hallade) we also disable use of copy engine as it - * improves stability as of intel/llvm sycl-nightly/20220529. + * improves stability as of intel/LLVM SYCL-nightly/20220529. * All these env variable can be set beforehand by end-users and * will in that case -not- be overwritten. */ # ifdef _WIN32 diff --git a/intern/cycles/device/oneapi/device_impl.cpp b/intern/cycles/device/oneapi/device_impl.cpp index 8c8ab522b47..0c0afd1d2df 100644 --- a/intern/cycles/device/oneapi/device_impl.cpp +++ b/intern/cycles/device/oneapi/device_impl.cpp @@ -35,7 +35,7 @@ OneapiDevice::OneapiDevice(const DeviceInfo &info, oneapi_dll_.oneapi_set_error_cb(queue_error_cb, &oneapi_error_string_); - /* Oneapi calls should be initialised on this moment. */ + /* OneAPI calls should be initialized on this moment. */ assert(oneapi_dll_.oneapi_create_queue != nullptr); bool is_finished_ok = oneapi_dll_.oneapi_create_queue(device_queue_, info.num); @@ -93,7 +93,7 @@ BVHLayoutMask OneapiDevice::get_bvh_layout_mask() const bool OneapiDevice::load_kernels(const uint requested_features) { assert(device_queue_); - /* NOTE(@nsirgien): oneAPI can support compilation of kernel code with sertain feature set + /* NOTE(@nsirgien): oneAPI can support compilation of kernel code with certain feature set * with specialization constants, but it hasn't been implemented yet. */ (void)requested_features; diff --git a/intern/cycles/device/oneapi/dll_interface.h b/intern/cycles/device/oneapi/dll_interface.h index bc681ff8f64..0a888194e98 100644 --- a/intern/cycles/device/oneapi/dll_interface.h +++ b/intern/cycles/device/oneapi/dll_interface.h @@ -3,7 +3,7 @@ #pragma once -/* Include kernel header to get access to sycl-specific types, like SyclQueue and +/* Include kernel header to get access to SYCL-specific types, like SyclQueue and * OneAPIDeviceIteratorCallback. */ #include "kernel/device/oneapi/kernel.h" diff --git a/intern/cycles/device/oneapi/queue.h b/intern/cycles/device/oneapi/queue.h index 09a015303b6..716cbfdc88c 100644 --- a/intern/cycles/device/oneapi/queue.h +++ b/intern/cycles/device/oneapi/queue.h @@ -17,7 +17,7 @@ CCL_NAMESPACE_BEGIN class OneapiDevice; class device_memory; -/* Base class for Oneapi queues. */ +/* Base class for OneAPI queues. */ class OneapiDeviceQueue : public DeviceQueue { public: explicit OneapiDeviceQueue(OneapiDevice *device); diff --git a/intern/cycles/kernel/device/oneapi/compat.h b/intern/cycles/kernel/device/oneapi/compat.h index 30b0f088ede..1b25259bcf5 100644 --- a/intern/cycles/kernel/device/oneapi/compat.h +++ b/intern/cycles/kernel/device/oneapi/compat.h @@ -193,7 +193,7 @@ ccl_always_inline float3 make_float3(float x) #include "util/types.h" /* NOTE(@nsirgien): Declaring these functions after types headers is very important because they - * include oneAPI headers, which transitively include math.h headers which will cause redefintions + * include oneAPI headers, which transitively include math.h headers which will cause redefinitions * of the math defines because math.h also uses them and having them defined before math.h include * is actually UB. */ /* Use fast math functions - get them from sycl::native namespace for native math function diff --git a/intern/cycles/kernel/device/oneapi/dll_interface_template.h b/intern/cycles/kernel/device/oneapi/dll_interface_template.h index 2d740b4c64a..22804490a70 100644 --- a/intern/cycles/kernel/device/oneapi/dll_interface_template.h +++ b/intern/cycles/kernel/device/oneapi/dll_interface_template.h @@ -27,7 +27,7 @@ DLL_INTERFACE_CALL(oneapi_usm_memset, DLL_INTERFACE_CALL(oneapi_run_test_kernel, bool, SyclQueue *queue) /* Operation with Kernel globals structure - map of global/constant allocation - filled before - * render/kernel execution As we don't know in cycles sizeof this - Cycles will manage just as + * render/kernel execution As we don't know in cycles `sizeof` this - Cycles will manage just as * pointer. */ DLL_INTERFACE_CALL(oneapi_kernel_globals_size, bool, SyclQueue *queue, size_t &kernel_global_size) DLL_INTERFACE_CALL(oneapi_set_global_memory, diff --git a/intern/cycles/kernel/device/oneapi/image.h b/intern/cycles/kernel/device/oneapi/image.h index 892558d40bf..6681977a675 100644 --- a/intern/cycles/kernel/device/oneapi/image.h +++ b/intern/cycles/kernel/device/oneapi/image.h @@ -216,7 +216,7 @@ template struct NanoVDBInterpolator { int nix, niy, niz; int pix, piy, piz; int nnix, nniy, nniz; - /* Tricubic b-spline interpolation. */ + /* Tri-cubic b-spline interpolation. */ const float tx = svm_image_texture_frac(x - 0.5f, &ix); const float ty = svm_image_texture_frac(y - 0.5f, &iy); const float tz = svm_image_texture_frac(z - 0.5f, &iz); @@ -355,7 +355,7 @@ ccl_device float4 kernel_tex_image_interp_3d(KernelGlobals, int id, float3 P, in return r; } else { - /* Tricubic interpolation. */ + /* Tri-cubic interpolation. */ int ix, iy, iz; float tx = svm_image_texture_frac(x - 0.5f, &ix); float ty = svm_image_texture_frac(y - 0.5f, &iy); diff --git a/intern/cycles/kernel/device/oneapi/kernel.cpp b/intern/cycles/kernel/device/oneapi/kernel.cpp index 62affe6e58e..2b915027bda 100644 --- a/intern/cycles/kernel/device/oneapi/kernel.cpp +++ b/intern/cycles/kernel/device/oneapi/kernel.cpp @@ -328,8 +328,8 @@ bool oneapi_enqueue_kernel(KernelContext *kernel_context, int num_states = *((int *)(args[0])); /* Round up to the next work-group. */ size_t groups_count = (num_states + local_size - 1) / local_size; - /* NOTE(@nsirgien): As for now non-uniform workgroups don't work on most oneAPI devices, we - * extend work size to fit uniformity requirements. */ + /* NOTE(@nsirgien): As for now non-uniform work-groups don't work on most oneAPI devices, + * we extend work size to fit uniformity requirements. */ global_size = groups_count * local_size; # ifdef WITH_ONEAPI_SYCL_HOST_ENABLED diff --git a/intern/cycles/kernel/device/oneapi/kernel_templates.h b/intern/cycles/kernel/device/oneapi/kernel_templates.h index 2dfc96292ed..41f9a9ba583 100644 --- a/intern/cycles/kernel/device/oneapi/kernel_templates.h +++ b/intern/cycles/kernel/device/oneapi/kernel_templates.h @@ -1,10 +1,9 @@ #pragma once /* Some macro magic to generate templates for kernel arguments. - The resulting oneapi_call() template allows to call a SYCL/C++ kernel - with typed arguments by only giving it a void **args as given by Cycles. - The template will automatically cast from void* to the expectd type. - */ + * The resulting oneapi_call() template allows to call a SYCL/C++ kernel + * with typed arguments by only giving it a void `**args` as given by Cycles. + * The template will automatically cast from void* to the expected type. */ /* When expanded by the preprocessor, the generated templates will look like this example: */ #if 0 diff --git a/intern/cycles/kernel/osl/services.cpp b/intern/cycles/kernel/osl/services.cpp index 6e75ae54f33..78c23b858c4 100644 --- a/intern/cycles/kernel/osl/services.cpp +++ b/intern/cycles/kernel/osl/services.cpp @@ -1710,12 +1710,12 @@ bool OSLRenderServices::trace(TraceOpt &options, const KernelGlobalsCPU *kg = sd->osl_globals; - /* Can't raytrace from shaders like displacement, before BVH exists. */ + /* Can't ray-trace from shaders like displacement, before BVH exists. */ if (kernel_data.bvh.bvh_layout == BVH_LAYOUT_NONE) { return false; } - /* Raytrace, leaving out shadow opaque to avoid early exit. */ + /* Ray-trace, leaving out shadow opaque to avoid early exit. */ uint visibility = PATH_RAY_ALL_VISIBILITY - PATH_RAY_SHADOW_OPAQUE; tracedata->hit = scene_intersect(kg, &ray, visibility, &tracedata->isect); return tracedata->hit; diff --git a/intern/cycles/kernel/svm/ao.h b/intern/cycles/kernel/svm/ao.h index b477855dca3..e66c535824c 100644 --- a/intern/cycles/kernel/svm/ao.h +++ b/intern/cycles/kernel/svm/ao.h @@ -31,7 +31,7 @@ ccl_device float svm_ao( return 1.0f; } - /* Can't raytrace from shaders like displacement, before BVH exists. */ + /* Can't ray-trace from shaders like displacement, before BVH exists. */ if (kernel_data.bvh.bvh_layout == BVH_LAYOUT_NONE) { return 1.0f; } diff --git a/intern/cycles/kernel/svm/bevel.h b/intern/cycles/kernel/svm/bevel.h index f79bcae5cd2..790437d8e82 100644 --- a/intern/cycles/kernel/svm/bevel.h +++ b/intern/cycles/kernel/svm/bevel.h @@ -103,7 +103,7 @@ ccl_device float3 svm_bevel( return sd->N; } - /* Can't raytrace from shaders like displacement, before BVH exists. */ + /* Can't ray-trace from shaders like displacement, before BVH exists. */ if (kernel_data.bvh.bvh_layout == BVH_LAYOUT_NONE) { return sd->N; } diff --git a/intern/cycles/kernel/svm/closure.h b/intern/cycles/kernel/svm/closure.h index 305bd404d27..99a8fdd3be9 100644 --- a/intern/cycles/kernel/svm/closure.h +++ b/intern/cycles/kernel/svm/closure.h @@ -395,7 +395,7 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg, if (kernel_data.integrator.caustics_refractive || (path_flag & PATH_RAY_DIFFUSE) == 0) # endif { - /* This is to prevent mnee from receiving a null bsdf. */ + /* This is to prevent MNEE from receiving a null BSDF. */ float refraction_fresnel = fmaxf(0.0001f, 1.0f - fresnel); ccl_private MicrofacetBsdf *bsdf = (ccl_private MicrofacetBsdf *)bsdf_alloc( sd, sizeof(MicrofacetBsdf), base_color * glass_weight * refraction_fresnel); @@ -676,7 +676,7 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg, if (kernel_data.integrator.caustics_refractive || (path_flag & PATH_RAY_DIFFUSE) == 0) #endif { - /* This is to prevent mnee from receiving a null bsdf. */ + /* This is to prevent MNEE from receiving a null BSDF. */ float refraction_fresnel = fmaxf(0.0001f, 1.0f - fresnel); ccl_private MicrofacetBsdf *bsdf = (ccl_private MicrofacetBsdf *)bsdf_alloc( sd, sizeof(MicrofacetBsdf), weight * refraction_fresnel); diff --git a/intern/cycles/scene/scene.cpp b/intern/cycles/scene/scene.cpp index 1fcc3331337..eedb2a4fa3a 100644 --- a/intern/cycles/scene/scene.cpp +++ b/intern/cycles/scene/scene.cpp @@ -499,9 +499,9 @@ void Scene::update_kernel_features() kernel_features |= KERNEL_FEATURE_CAMERA_MOTION; } - /* Figure out whether the scene will use shader raytrace we need at least + /* Figure out whether the scene will use shader ray-trace we need at least * one caustic light, one caustic caster and one caustic receiver to use - * and enable the mnee code path. */ + * and enable the MNEE code path. */ bool has_caustics_receiver = false; bool has_caustics_caster = false; bool has_caustics_light = false; diff --git a/intern/sky/include/sky_model.h b/intern/sky/include/sky_model.h index 021bd0d9ae6..75770f8115c 100644 --- a/intern/sky/include/sky_model.h +++ b/intern/sky/include/sky_model.h @@ -25,7 +25,7 @@ Version history: 1.4a February 22nd, 2013 Removed unnecessary and counter-intuitive solar radius parameters - from the interface of the colourspace sky dome initialisation functions. + from the interface of the colourspace sky dome initialization functions. 1.4 February 11th, 2013 Fixed a bug which caused the relative brightness of the solar disc @@ -76,7 +76,7 @@ Usage information: ================== -Model initialisation +Model initialization -------------------- A separate ArHosekSkyModelState has to be maintained for each spectral @@ -101,12 +101,12 @@ is given in radians. solarElevation ); -Note that starting with version 1.3, there is also a second initialisation +Note that starting with version 1.3, there is also a second initialization function which generates skydome states for different solar emission spectra and solar radii: 'arhosekskymodelstate_alienworld_alloc_init()'. See the notes about the "Alien World" functionality provided further down for a -discussion of the usefulness and limits of that second initialisation function. +discussion of the usefulness and limits of that second initialization function. Sky model states that have been initialized with either function behave in a completely identical fashion during use and cleanup. @@ -236,7 +236,7 @@ CAVEAT #3: you have to provide a value for the solar intensity of the star fairly different in size from it, to still provide a reasonable and inhabitable amount of irradiance. Red stars will need to be much larger than our sun, while white or blue stars will have to be - comparatively tiny. The initialisation function handles this and + comparatively tiny. The initialization function handles this and computes a plausible solar radius for a given emission spectrum. In terms of absolute radiometric values, you should probably not stray all too far from a solar intensity value of 1.0. diff --git a/intern/sky/source/sky_model.cpp b/intern/sky/source/sky_model.cpp index f70a2a7588c..d67fe08772d 100644 --- a/intern/sky/source/sky_model.cpp +++ b/intern/sky/source/sky_model.cpp @@ -25,7 +25,7 @@ Version history: 1.4a February 22nd, 2013 Removed unnecessary and counter-intuitive solar radius parameters - from the interface of the colourspace sky dome initialisation functions. + from the interface of the color-space sky dome initialization functions. 1.4 February 11th, 2013 Fixed a bug which caused the relative brightness of the solar disc diff --git a/intern/sky/source/sky_model_data.h b/intern/sky/source/sky_model_data.h index f8398e0839c..6ed763951ee 100644 --- a/intern/sky/source/sky_model_data.h +++ b/intern/sky/source/sky_model_data.h @@ -25,7 +25,7 @@ Version history: 1.4a February 22nd, 2013 Removed unnecessary and counter-intuitive solar radius parameters - from the interface of the colourspace sky dome initialisation functions. + from the interface of the color-space sky dome initialization functions. 1.4 February 11th, 2013 Fixed a bug which caused the relative brightness of the solar disc diff --git a/source/blender/blenkernel/intern/dynamicpaint.c b/source/blender/blenkernel/intern/dynamicpaint.c index a4262e08e39..423e76fce8c 100644 --- a/source/blender/blenkernel/intern/dynamicpaint.c +++ b/source/blender/blenkernel/intern/dynamicpaint.c @@ -2552,7 +2552,7 @@ static void dynamic_paint_find_island_border(const DynamicPaintCreateUVSurfaceDa const int vert1 = mloop[loop_idx[(edge_idx + 1) % 3]].v; /* Use a pre-computed vert-to-looptri mapping, - * speeds up things a lot compared to looping over all loopti. */ + * speeds up things a lot compared to looping over all looptri. */ const MeshElemMap *map = &bdata->vert_to_looptri_map[vert0]; bool found_other = false; diff --git a/source/blender/blenkernel/intern/image.cc b/source/blender/blenkernel/intern/image.cc index 0c1f01c3796..afde8893b93 100644 --- a/source/blender/blenkernel/intern/image.cc +++ b/source/blender/blenkernel/intern/image.cc @@ -1950,7 +1950,7 @@ void BKE_image_stamp_buf(Scene *scene, y -= BUFF_MARGIN_Y * 2; } - /* Top left corner, below File, Date, Rendertime */ + /* Top left corner, below File, Date, Render-time */ if (TEXT_SIZE_CHECK(stamp_data.memory, w, h)) { y -= h; @@ -1973,7 +1973,7 @@ void BKE_image_stamp_buf(Scene *scene, y -= BUFF_MARGIN_Y * 2; } - /* Top left corner, below File, Date, Rendertime, Memory */ + /* Top left corner, below: File, Date, Render-time, Memory. */ if (TEXT_SIZE_CHECK(stamp_data.hostname, w, h)) { y -= h; @@ -1996,7 +1996,7 @@ void BKE_image_stamp_buf(Scene *scene, y -= BUFF_MARGIN_Y * 2; } - /* Top left corner, below File, Date, Memory, Rendertime, Hostname */ + /* Top left corner, below: File, Date, Memory, Render-time, Host-name. */ BLF_enable(mono, BLF_WORD_WRAP); if (TEXT_SIZE_CHECK_WORD_WRAP(stamp_data.note, w, h)) { y -= h; diff --git a/source/blender/blenloader/intern/versioning_legacy.c b/source/blender/blenloader/intern/versioning_legacy.c index 4c27b8b9016..a3f17878f68 100644 --- a/source/blender/blenloader/intern/versioning_legacy.c +++ b/source/blender/blenloader/intern/versioning_legacy.c @@ -2296,7 +2296,7 @@ void blo_do_versions_pre250(FileData *fd, Library *lib, Main *bmain) psys->vgroup[PSYS_VG_VEL] = paf->vertgroup_v; psys->vgroup[PSYS_VG_LENGTH] = paf->vertgroup_v; - /* dupliobjects */ + /* Dupli-objects. */ if (ob->transflag & OB_DUPLIVERTS) { Object *dup = bmain->objects.first; diff --git a/source/blender/compositor/intern/COM_NodeConverter.h b/source/blender/compositor/intern/COM_NodeConverter.h index a90531bad0e..ceaf04f11a0 100644 --- a/source/blender/compositor/intern/COM_NodeConverter.h +++ b/source/blender/compositor/intern/COM_NodeConverter.h @@ -90,7 +90,7 @@ class NodeConverter { /** * When a node has no valid data - * \note missing image / group pointer, or missing renderlayer from EXR + * \note missing image / group pointer, or missing render-layer from EXR. */ NodeOperation *set_invalid_output(NodeOutput *output); diff --git a/source/blender/draw/engines/eevee/eevee_cryptomatte.c b/source/blender/draw/engines/eevee/eevee_cryptomatte.c index 33063e14c03..53ea66bdea0 100644 --- a/source/blender/draw/engines/eevee/eevee_cryptomatte.c +++ b/source/blender/draw/engines/eevee/eevee_cryptomatte.c @@ -94,7 +94,7 @@ BLI_INLINE int eevee_cryptomatte_pixel_stride(const ViewLayer *view_layer) /** \} */ /* -------------------------------------------------------------------- */ -/** \name Init Renderpasses +/** \name Init Render-Passes * \{ */ void EEVEE_cryptomatte_renderpasses_init(EEVEE_Data *vedata) diff --git a/source/blender/draw/engines/eevee/eevee_engine.c b/source/blender/draw/engines/eevee/eevee_engine.c index 227757bad23..0cdeeaf314f 100644 --- a/source/blender/draw/engines/eevee/eevee_engine.c +++ b/source/blender/draw/engines/eevee/eevee_engine.c @@ -312,12 +312,12 @@ static void eevee_draw_scene(void *vedata) /* Volumetrics Resolve Opaque */ EEVEE_volumes_resolve(sldata, vedata); - /* Renderpasses */ + /* Render-passes. */ EEVEE_renderpasses_output_accumulate(sldata, vedata, false); /* Transparent */ - /* TODO(fclem): should be its own Frame-buffer. - * This is needed because dualsource blending only works with 1 color buffer. */ + /* TODO(@fclem): should be its own Frame-buffer. + * This is needed because dual-source blending only works with 1 color buffer. */ GPU_framebuffer_texture_attach(fbl->main_color_fb, dtxl->depth, 0, 0); GPU_framebuffer_bind(fbl->main_color_fb); DRW_draw_pass(psl->transparent_pass); diff --git a/source/blender/draw/engines/eevee/eevee_private.h b/source/blender/draw/engines/eevee/eevee_private.h index ad218d80cdf..8d47d80987c 100644 --- a/source/blender/draw/engines/eevee/eevee_private.h +++ b/source/blender/draw/engines/eevee/eevee_private.h @@ -1050,7 +1050,7 @@ typedef struct EEVEE_PrivateData { float studiolight_glossy_clamp; float studiolight_filter_quality; - /* Renderpasses */ + /* Render-passes */ /* Bitmask containing the active render_passes */ eViewLayerEEVEEPassType render_passes; uint aov_hash; diff --git a/source/blender/draw/engines/eevee/eevee_screen_raytrace.c b/source/blender/draw/engines/eevee/eevee_screen_raytrace.c index 5af794c9158..0d0e551f3dc 100644 --- a/source/blender/draw/engines/eevee/eevee_screen_raytrace.c +++ b/source/blender/draw/engines/eevee/eevee_screen_raytrace.c @@ -198,7 +198,7 @@ void EEVEE_reflection_compute(EEVEE_ViewLayerData *UNUSED(sldata), EEVEE_Data *v if (((effects->enabled_effects & EFFECT_SSR) != 0) && stl->g_data->valid_double_buffer) { DRW_stats_group_start("SSR"); - /* Raytrace. */ + /* Ray-trace. */ GPU_framebuffer_bind(fbl->screen_tracing_fb); DRW_draw_pass(psl->ssr_raytrace); diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh index a944bea402e..d9a6b6efd0c 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh @@ -85,7 +85,7 @@ GPU_SHADER_CREATE_INFO(eevee_surf_deferred) // .image_out(3, Qualifier::WRITE, GPU_R11F_G11F_B10F, "gbuff_reflection_color") // .image_out(4, Qualifier::WRITE, GPU_RGBA16F, "gbuff_reflection_normal") // .image_out(5, Qualifier::WRITE, GPU_R11F_G11F_B10F, "gbuff_emission") - /* Renderpasses. */ + /* Render-passes. */ // .image_out(6, Qualifier::READ_WRITE, GPU_RGBA16F, "rpass_volume_light") /* TODO: AOVs maybe? */ .fragment_source("eevee_surf_deferred_frag.glsl") diff --git a/source/blender/draw/engines/overlay/overlay_antialiasing.c b/source/blender/draw/engines/overlay/overlay_antialiasing.c index 27ee479cf36..780915b7fc4 100644 --- a/source/blender/draw/engines/overlay/overlay_antialiasing.c +++ b/source/blender/draw/engines/overlay/overlay_antialiasing.c @@ -52,7 +52,7 @@ void OVERLAY_antialiasing_init(OVERLAY_Data *vedata) OVERLAY_PrivateData *pd = vedata->stl->pd; DefaultTextureList *dtxl = DRW_viewport_texture_list_get(); - /* Small texture which will have very small impact on rendertime. */ + /* Small texture which will have very small impact on render-time. */ if (txl->dummy_depth_tx == NULL) { const float pixel[1] = {1.0f}; txl->dummy_depth_tx = DRW_texture_create_2d(1, 1, GPU_DEPTH_COMPONENT24, 0, pixel); diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h index 0943636a452..d844bd59c9d 100644 --- a/source/blender/editors/include/ED_gpencil.h +++ b/source/blender/editors/include/ED_gpencil.h @@ -239,7 +239,7 @@ void ED_annotation_draw_ex( /* ----------- Grease-Pencil AnimEdit API ------------------ */ /** - * Loops over the gp-frames for a gp-layer, and applies the given callback. + * Loops over the GP-frames for a GP-layer, and applies the given callback. */ bool ED_gpencil_layer_frames_looper(struct bGPDlayer *gpl, struct Scene *scene, diff --git a/source/blender/editors/interface/interface_region_tooltip.c b/source/blender/editors/interface/interface_region_tooltip.c index 82d4405e1b5..88fe866f717 100644 --- a/source/blender/editors/interface/interface_region_tooltip.c +++ b/source/blender/editors/interface/interface_region_tooltip.c @@ -1005,7 +1005,7 @@ static uiTooltipData *ui_tooltip_data_from_button_or_extra_icon(bContext *C, /* this could get its own 'BUT_GET_...' type */ /* never fails */ - /* move ownership (no need for re-alloc) */ + /* Move ownership (no need for re-allocation). */ if (rnaprop) { field->text = RNA_path_full_property_py_ex( CTX_data_main(C), &but->rnapoin, rnaprop, but->rnaindex, true); diff --git a/source/blender/editors/mask/mask_add.c b/source/blender/editors/mask/mask_add.c index 37c1815fca3..7ac326cb00c 100644 --- a/source/blender/editors/mask/mask_add.c +++ b/source/blender/editors/mask/mask_add.c @@ -874,7 +874,7 @@ void MASK_OT_primitive_circle_add(wmOperatorType *ot) /** \} */ /* -------------------------------------------------------------------- */ -/** \name Primitive Add Suqare Operator +/** \name Primitive Add Square Operator * \{ */ static int primitive_square_add_exec(bContext *C, wmOperator *op) diff --git a/source/blender/editors/space_nla/nla_draw.c b/source/blender/editors/space_nla/nla_draw.c index bb9e201d94a..798a65efdcc 100644 --- a/source/blender/editors/space_nla/nla_draw.c +++ b/source/blender/editors/space_nla/nla_draw.c @@ -617,7 +617,7 @@ static void nla_draw_strip(SpaceNla *snla, immUnbindProgram(); } -/* add the relevant text to the cache of text-strings to draw in pixelspace */ +/** Add the relevant text to the cache of text-strings to draw in pixel-space. */ static void nla_draw_strip_text(AnimData *adt, NlaTrack *nlt, NlaStrip *strip, diff --git a/source/blender/editors/space_nla/space_nla.c b/source/blender/editors/space_nla/space_nla.c index 42d3d841f4b..c50278518de 100644 --- a/source/blender/editors/space_nla/space_nla.c +++ b/source/blender/editors/space_nla/space_nla.c @@ -235,7 +235,7 @@ static void nla_main_region_draw(const bContext *C, ARegion *region) /* strips and backdrops */ draw_nla_main_data(&ac, snla, region); - /* text draw cached, in pixelspace now */ + /* Text draw cached, in pixel-space now. */ UI_view2d_text_cache_draw(region); } diff --git a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h index 5e9b2556fe0..d7005a4bc61 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h +++ b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h @@ -108,7 +108,7 @@ typedef struct LineartEdgeSegment { typedef struct LineartShadowEdge { struct LineartShadowEdge *next, *prev; - /* Two end points in framebuffer coordinates viewed from the light source. */ + /* Two end points in frame-buffer coordinates viewed from the light source. */ double fbc1[4], fbc2[4]; double g1[3], g2[3]; bool orig1, orig2; @@ -129,11 +129,11 @@ typedef struct LineartShadowSegment { /* eLineartShadowSegmentFlag */ int flag; /* The point after which a property of the segment is changed. e.g. shadow mask/target_ref etc. - * Coordinates in NDC during shadow caluclation but transformed to global linear before cutting + * Coordinates in NDC during shadow calculation but transformed to global linear before cutting * onto edges during the loading stage of the "actual" rendering. */ double ratio; /* Left and right pos, because when casting shadows at some point there will be - * non-continuous cuts, see #lineart_shadow_edge_cut for detailed explaination. */ + * non-continuous cuts, see #lineart_shadow_edge_cut for detailed explanation. */ double fbc1[4], fbc2[4]; /* Global position. */ double g1[4], g2[4]; @@ -276,7 +276,7 @@ typedef struct LineartData { * calculation is finished. */ LineartStaticMemPool *shadow_data_pool; - /* Storing shadow edge eln, array, and cuts for shadow information, so it's avaliable when line + /* Storing shadow edge eln, array, and cuts for shadow information, so it's available when line * art runs the second time for occlusion. Either a reference to LineartCache::shadow_data_pool * (shadow stage) or a reference to LineartData::render_data_pool (final stage). */ LineartStaticMemPool *edge_data_pool; @@ -746,8 +746,8 @@ BLI_INLINE int lineart_line_isec_2d_ignore_line2pos(const double a1[2], double *r_a_ratio) { /* The define here is used to check how vector or slope method handles boundary cases. The result - * of lim(div->0) and lim(k->0) could both produce some unwanted flickers in line art, the - * influence of which is still not fully understood, so keep the switch there for futher + * of `lim(div->0)` and `lim(k->0)` could both produce some unwanted flickers in line art, the + * influence of which is still not fully understood, so keep the switch there for further * investigations. */ #define USE_VECTOR_LINE_INTERSECTION_IGN #ifdef USE_VECTOR_LINE_INTERSECTION_IGN diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c index c17827b7228..236e2df1537 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c @@ -3658,7 +3658,7 @@ static LineartData *lineart_create_render_buffer(Scene *scene, ld->chain_data_pool = &lc->chain_data_pool; - /* See LineartData::edge_data_pool for explaination. */ + /* See #LineartData::edge_data_pool for explanation. */ ld->edge_data_pool = &ld->render_data_pool; BLI_spin_init(&ld->lock_task); @@ -4494,7 +4494,7 @@ static void lineart_create_edges_from_isec_data(LineartIsecData *d) return; } - /* We don't care about removing duplicated vert in this method, chaning can handle that, + /* We don't care about removing duplicated vert in this method, chaining can handle that, * and it saves us from using locks and look up tables. */ LineartVert *v = lineart_mem_acquire(ld->edge_data_pool, sizeof(LineartVert) * total_lines * 2); LineartEdge *e = lineart_mem_acquire(ld->edge_data_pool, sizeof(LineartEdge) * total_lines); @@ -4565,7 +4565,7 @@ static void lineart_create_edges_from_isec_data(LineartIsecData *d) } else { /* equal priority */ if (ob1 == ob2) { - /* object_ref should be ambigious if intersection lines comes from different objects. */ + /* object_ref should be ambiguous if intersection lines comes from different objects. */ e->object_ref = ob1; } } @@ -5165,7 +5165,7 @@ static void lineart_gpencil_generate(LineartCache *cache, } if (shaodow_selection) { if (ec->shadow_mask_bits != LRT_SHADOW_MASK_UNDEFINED) { - /* TODO(Yiming): Give a behaviour option for how to display undefined shadow info. */ + /* TODO(@Yiming): Give a behavior option for how to display undefined shadow info. */ if ((shaodow_selection == LRT_SHADOW_FILTER_LIT && (!(ec->shadow_mask_bits & LRT_SHADOW_MASK_LIT))) || (shaodow_selection == LRT_SHADOW_FILTER_SHADED && diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_shadow.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_shadow.c index ec1d9a14233..ad0137fe0f0 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_shadow.c +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_shadow.c @@ -193,10 +193,12 @@ static void lineart_shadow_segment_slice_get(double *fb_co_1, interp_v3_v3v3_db(r_gloc, gloc_1, gloc_2, ga); } -/* This function tries to get the closest projected segments along two end points. - * The x,y of s1, s2 are aligned in framebuffer coordinates, only z,w are different. +/** + * This function tries to get the closest projected segments along two end points. + * The x,y of s1, s2 are aligned in frame-buffer coordinates, only z,w are different. * We will get the closest z/w as well as the corresponding global coordinates. * + * \code{.unparsed} * (far side) * l-------r [s1] ^ * _-r [s2] | In this situation it will essentially return the coordinates of s2. @@ -209,9 +211,11 @@ static void lineart_shadow_segment_slice_get(double *fb_co_1, * l-----_c`-----r [s1] | and `r_new` will be assigned coordinates of `c`. * _-` | * l-` | + * \endcode * * Returns true when a new cut (`c`) is needed in the middle, otherwise returns false, and - * `*r_new_xxx` are not touched. */ + * `*r_new_xxx` are not touched. + */ static bool lineart_do_closest_segment(bool is_persp, double *s1_fb_co_1, double *s1_fb_co_2, @@ -309,7 +313,7 @@ static void lineart_shadow_create_shadow_edge_array(LineartData *ld, LRT_ITER_ALL_LINES_BEGIN { /* Only contour and loose edges can actually cast shadows. We allow light contour here because - * we want to see if it also doubles as a view contouror, in that case we also need to project + * we want to see if it also doubles as a view contour, in that case we also need to project * them. */ if (!(e->flags & accept_types)) { continue; @@ -383,7 +387,7 @@ static void lineart_shadow_create_shadow_edge_array(LineartData *ld, interp_v3_v3v3_db(sedge[i].g2, e->v1->gloc, e->v2->gloc, ga2); /* Assign an absurdly big W for initial distance so when triangles show up to catch the - * shadow, their w must certainlly be smaller than this value so the shadow catches + * shadow, their w must certainly be smaller than this value so the shadow catches * successfully. */ sedge[i].fbc1[3] = 1e30; sedge[i].fbc2[3] = 1e30; @@ -712,7 +716,7 @@ static bool lineart_shadow_cast_onto_triangle(LineartData *ld, /* Bound box check. Because we have already done occlusion in the shadow camera, so any visual * intersection found in this function must mean that the triangle is behind the given line so it - * will always project a shadow, hence no need to do depth boundbox check. */ + * will always project a shadow, hence no need to do depth bound-box check. */ if ((MAX3(FBC0[0], FBC1[0], FBC2[0]) < MIN2(LFBC[0], RFBC[0])) || (MIN3(FBC0[0], FBC1[0], FBC2[0]) > MAX2(LFBC[0], RFBC[0])) || (MAX3(FBC0[1], FBC1[1], FBC2[1]) < MIN2(LFBC[1], RFBC[1])) || @@ -1055,7 +1059,7 @@ static void lineart_shadow_register_enclosed_shapes(LineartData *ld, LineartData e = shadow_ld->pending_edges.array[i]; /* Only care about shade-on-light and light-on-light situations, hence we only need - * non-occludded segments in shadow buffer. */ + * non-occluded segments in shadow buffer. */ if (e->min_occ > 0) { continue; } @@ -1103,7 +1107,7 @@ static void lineart_shadow_register_enclosed_shapes(LineartData *ld, LineartData } } -/* This call would internally duplicate #original_ld, override necessary configureations for shadow +/* This call would internally duplicate #original_ld, override necessary configurations for shadow * computations. It will return: * * 1) Generated shadow edges in format of `LineartElementLinkNode` which can be directly loaded @@ -1151,7 +1155,7 @@ bool lineart_main_try_generate_shadow(Depsgraph *depsgraph, ld->conf.do_shadow_cast = true; ld->shadow_data_pool = shadow_data_pool; - /* See LineartData::edge_data_pool for explaination. */ + /* See LineartData::edge_data_pool for explanation. */ if (ld->conf.shadow_selection) { ld->edge_data_pool = shadow_data_pool; } @@ -1180,7 +1184,7 @@ bool lineart_main_try_generate_shadow(Depsgraph *depsgraph, } ld->qtree.recursive_level = is_persp ? LRT_TILE_RECURSIVE_PERSPECTIVE : LRT_TILE_RECURSIVE_ORTHO; - /* Contour and loose edge from light viewing direction will be casted as shadow, so only + /* Contour and loose edge from light viewing direction will be cast as shadow, so only * force them on. If we need lit/shaded information for other line types, they are then * enabled as-is so that cutting positions can also be calculated through shadow projection. */ @@ -1250,7 +1254,7 @@ bool lineart_main_try_generate_shadow(Depsgraph *depsgraph, } if (ld->conf.shadow_enclose_shapes) { - /* Need loaded data for reprojecting the 3rd time to get shape boundary against lit/shaded + /* Need loaded data for re-projecting the 3rd time to get shape boundary against lit/shaded * region. */ (*r_shadow_ld_if_reproject) = ld; } @@ -1342,7 +1346,7 @@ void lineart_main_transform_and_add_shadow(LineartData *ld, } /* Does the 3rd stage reprojection, will not re-load objects because #shadow_ld is not deleted. - * Only reprojects view camera edges and check visibility in light camera, then we can determine + * Only re-projects view camera edges and check visibility in light camera, then we can determine * whether an edge landed on a lit or shaded area. */ void lineart_main_make_enclosed_shapes(LineartData *ld, LineartData *shadow_ld) { diff --git a/source/blender/gpu/metal/mtl_command_buffer.mm b/source/blender/gpu/metal/mtl_command_buffer.mm index 434bc664ee6..4f6077e8159 100644 --- a/source/blender/gpu/metal/mtl_command_buffer.mm +++ b/source/blender/gpu/metal/mtl_command_buffer.mm @@ -25,7 +25,7 @@ unsigned long long MTLCommandBufferManager::event_signal_val = 0; int MTLCommandBufferManager::num_active_cmd_bufs = 0; /* -------------------------------------------------------------------- */ -/** \name MTLCommandBuffer initialisation and render coordination. +/** \name MTLCommandBuffer initialization and render coordination. * \{ */ void MTLCommandBufferManager::prepare(MTLContext *ctx, bool supports_render) diff --git a/source/blender/makesdna/DNA_gpencil_modifier_types.h b/source/blender/makesdna/DNA_gpencil_modifier_types.h index f6a54308efc..2bb95caddfb 100644 --- a/source/blender/makesdna/DNA_gpencil_modifier_types.h +++ b/source/blender/makesdna/DNA_gpencil_modifier_types.h @@ -1062,8 +1062,8 @@ typedef struct LineartGpencilModifierData { char source_vertex_group[64]; char vgname[64]; - /* Camera focal length is divided by (1 + overscan), before caluclation, which give a wider FOV, - * this doesn't change coordinates range internally (-1, 1), but makes the caluclated frame + /* Camera focal length is divided by (1 + over-scan), before calculation, which give a wider FOV, + * this doesn't change coordinates range internally (-1, 1), but makes the calculated frame * bigger than actual output. This is for the easier shifting calculation. A value of 0.5 means * the "internal" focal length become 2/3 of the actual camera. */ float overscan; diff --git a/source/blender/makesdna/DNA_lineart_types.h b/source/blender/makesdna/DNA_lineart_types.h index 4f736703203..1ff656f85ed 100644 --- a/source/blender/makesdna/DNA_lineart_types.h +++ b/source/blender/makesdna/DNA_lineart_types.h @@ -51,16 +51,18 @@ typedef enum eLineartEdgeFlag { LRT_EDGE_FLAG_LOOSE = (1 << 5), LRT_EDGE_FLAG_LIGHT_CONTOUR = (1 << 6), /* LRT_EDGE_FLAG_FOR_FUTURE = (1 << 7), */ - /* It's a legacy limit of 8 bits for feature lines that come from original mesh edges. It should - not be needed in current object loading scheme, but might still be relevant if we are to - impelment EditMesh loading, so don't exceed 8 bits just yet. */ + /** + * It's a legacy limit of 8 bits for feature lines that come from original mesh edges. It should + * not be needed in current object loading scheme, but might still be relevant if we are to + * implement edit-mesh loading, so don't exceed 8 bits just yet. + */ LRT_EDGE_FLAG_PROJECTED_SHADOW = (1 << 8), /* To determine an edge to be occluded from the front or back face it's lying on. */ LRT_EDGE_FLAG_SHADOW_FACING_LIGHT = (1 << 9), /** Also used as discarded line mark. */ LRT_EDGE_FLAG_CHAIN_PICKED = (1 << 10), LRT_EDGE_FLAG_CLIPPED = (1 << 11), - /** Used to specify contor from viewing camera when computing shadows. */ + /** Used to specify contour from viewing camera when computing shadows. */ LRT_EDGE_FLAG_CONTOUR_SECONDARY = (1 << 12), /** Limited to 16 bits for the entire thing. */ diff --git a/source/blender/render/intern/pipeline.c b/source/blender/render/intern/pipeline.c index 075f1ece647..154b689b4a5 100644 --- a/source/blender/render/intern/pipeline.c +++ b/source/blender/render/intern/pipeline.c @@ -1222,7 +1222,7 @@ static void do_render_compositor(Render *re) } } - /* weak... the display callback wants an active renderlayer pointer... */ + /* Weak: the display callback wants an active render-layer pointer. */ if (re->result != NULL) { re->result->renlay = render_get_active_layer(re, re->result); re->display_update(re->duh, re->result, NULL); diff --git a/source/blender/render/intern/render_result.c b/source/blender/render/intern/render_result.c index 9992d1a507f..3386a74daba 100644 --- a/source/blender/render/intern/render_result.c +++ b/source/blender/render/intern/render_result.c @@ -301,7 +301,7 @@ RenderResult *render_result_new(Render *re, } \ } while (false) - /* A renderlayer should always have a Combined pass. */ + /* A render-layer should always have a "Combined" pass. */ render_layer_add_pass(rr, rl, 4, "Combined", view, "RGBA", false); if (view_layer->passflag & SCE_PASS_Z) { @@ -398,7 +398,7 @@ RenderResult *render_result_new(Render *re, } } - /* a renderlayer should always have a Combined pass */ + /* A render-layer should always have a "Combined" pass. */ render_layer_add_pass(rr, rl, 4, RE_PASSNAME_COMBINED, view, "RGBA", false); } diff --git a/source/blender/windowmanager/intern/wm_event_system.cc b/source/blender/windowmanager/intern/wm_event_system.cc index 51486f664c7..c282cda4305 100644 --- a/source/blender/windowmanager/intern/wm_event_system.cc +++ b/source/blender/windowmanager/intern/wm_event_system.cc @@ -2355,7 +2355,7 @@ static int wm_handler_operator_call(bContext *C, } } - /* Important to run 'wm_operator_finished' before nullptr-ing the context members. */ + /* Important to run 'wm_operator_finished' before setting the context members to null. */ if (retval & OPERATOR_FINISHED) { wm_operator_finished(C, op, false, true); handler->op = nullptr; -- cgit v1.2.3 From feeb8310c80ec991d1dd060737dc30f3e1cff93a Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 30 Jun 2022 12:14:23 +1000 Subject: Cleanup: format --- build_files/build_environment/cmake/download.cmake | 1 - .../build_environment/cmake/dpcpp_deps.cmake | 1 - build_files/build_environment/cmake/gmmlib.cmake | 1 - build_files/build_environment/cmake/igc.cmake | 2 +- intern/cycles/blender/addon/properties.py | 11 ++++----- intern/cycles/blender/addon/ui.py | 2 ++ .../intern/lineart/lineart_chain.c | 2 +- tests/performance/tests/eevee.py | 26 +++++++++++----------- 8 files changed, 23 insertions(+), 23 deletions(-) diff --git a/build_files/build_environment/cmake/download.cmake b/build_files/build_environment/cmake/download.cmake index 8b210992ada..b7150525a65 100644 --- a/build_files/build_environment/cmake/download.cmake +++ b/build_files/build_environment/cmake/download.cmake @@ -116,4 +116,3 @@ download_source(IGC_SPIRV_TOOLS) download_source(IGC_SPIRV_TRANSLATOR) download_source(GMMLIB) download_source(OCLOC) - diff --git a/build_files/build_environment/cmake/dpcpp_deps.cmake b/build_files/build_environment/cmake/dpcpp_deps.cmake index 17cb9de3bf7..e66006993f6 100644 --- a/build_files/build_environment/cmake/dpcpp_deps.cmake +++ b/build_files/build_environment/cmake/dpcpp_deps.cmake @@ -59,4 +59,3 @@ ExternalProject_Add(external_spirvheaders BUILD_COMMAND echo . INSTALL_COMMAND echo . ) - diff --git a/build_files/build_environment/cmake/gmmlib.cmake b/build_files/build_environment/cmake/gmmlib.cmake index d3ddfd39ac6..c46f5c8943d 100644 --- a/build_files/build_environment/cmake/gmmlib.cmake +++ b/build_files/build_environment/cmake/gmmlib.cmake @@ -11,4 +11,3 @@ ExternalProject_Add(external_gmmlib CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${LIBDIR}/gmmlib ${DEFAULT_CMAKE_FLAGS} ${GMMLIB_EXTRA_ARGS} INSTALL_DIR ${LIBDIR}/gmmlib ) - diff --git a/build_files/build_environment/cmake/igc.cmake b/build_files/build_environment/cmake/igc.cmake index 64f30064a3a..3b488a77b3f 100644 --- a/build_files/build_environment/cmake/igc.cmake +++ b/build_files/build_environment/cmake/igc.cmake @@ -7,7 +7,7 @@ unpack_only(igc_spirv_tools) # # igc_opencl_clang contains patches that need to be applied # to external_igc_llvm and igc_spirv_translator, we unpack -# igc_opencl_clang first, then have the patch stages of +# igc_opencl_clang first, then have the patch stages of # external_igc_llvm and igc_spirv_translator apply them. # diff --git a/intern/cycles/blender/addon/properties.py b/intern/cycles/blender/addon/properties.py index 7d7ca78c15a..c41e1f02b75 100644 --- a/intern/cycles/blender/addon/properties.py +++ b/intern/cycles/blender/addon/properties.py @@ -1568,11 +1568,12 @@ class CyclesPreferences(bpy.types.AddonPreferences): for device in devices: import unicodedata - box.prop(device, "use", text=device.name - .replace('(TM)', unicodedata.lookup('TRADE MARK SIGN')) - .replace('(R)', unicodedata.lookup('REGISTERED SIGN')) - .replace('(C)', unicodedata.lookup('COPYRIGHT SIGN')) - ) + box.prop( + device, "use", text=device.name + .replace('(TM)', unicodedata.lookup('TRADE MARK SIGN')) + .replace('(R)', unicodedata.lookup('REGISTERED SIGN')) + .replace('(C)', unicodedata.lookup('COPYRIGHT SIGN')) + ) def draw_impl(self, layout, context): row = layout.row() diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index 5b8c3960c82..77da3f36685 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -110,11 +110,13 @@ def use_optix(context): return (get_device_type(context) == 'OPTIX' and cscene.device == 'GPU') + def use_oneapi(context): cscene = context.scene.cycles return (get_device_type(context) == 'ONEAPI' and cscene.device == 'GPU') + def use_multi_device(context): cscene = context.scene.cycles if cscene.device != 'GPU': diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c index 4ee18c6738f..988698771bf 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c @@ -1338,4 +1338,4 @@ void MOD_lineart_chain_find_silhouette_backdrop_objects(LineartData *ld) ec->silhouette_backdrop = eln->object_ref; } } -} \ No newline at end of file +} diff --git a/tests/performance/tests/eevee.py b/tests/performance/tests/eevee.py index e1da5e423c5..df7cac695da 100644 --- a/tests/performance/tests/eevee.py +++ b/tests/performance/tests/eevee.py @@ -29,10 +29,10 @@ def _run(args): bpy.app.handlers.frame_change_post.append(frame_change_handler) bpy.ops.screen.animation_play() - + def frame_change_handler(scene): import bpy - + global record_stage global start_time global start_record_time @@ -40,7 +40,7 @@ def frame_change_handler(scene): global warmup_frame global stop_record_time global playback_iteration - + if record_stage == RecordStage.INIT: screen = bpy.context.window_manager.windows[0].screen bpy.context.scene.sync_mode = 'NONE' @@ -50,10 +50,10 @@ def frame_change_handler(scene): space = area.spaces[0] space.shading.type = 'RENDERED' space.overlay.show_overlays = False - + start_time = time.perf_counter() record_stage = RecordStage.WAIT_SHADERS - + elif record_stage == RecordStage.WAIT_SHADERS: shaders_compiled = False if hasattr(bpy.app, 'is_job_running'): @@ -61,12 +61,12 @@ def frame_change_handler(scene): else: # Fallback when is_job_running doesn't exists by waiting for a time. shaders_compiled = time.perf_counter() - start_time > SHADER_FALLBACK_SECONDS - + if shaders_compiled: start_warmup_time = time.perf_counter() warmup_frame = 0 record_stage = RecordStage.WARMUP - + elif record_stage == RecordStage.WARMUP: warmup_frame += 1 if time.perf_counter() - start_warmup_time > WARMUP_SECONDS and warmup_frame > WARMUP_FRAMES: @@ -75,13 +75,13 @@ def frame_change_handler(scene): scene = bpy.context.scene scene.frame_set(scene.frame_start) record_stage = RecordStage.RECORD - + elif record_stage == RecordStage.RECORD: current_time = time.perf_counter() scene = bpy.context.scene if scene.frame_current == scene.frame_end: playback_iteration += 1 - + if playback_iteration >= RECORD_PLAYBACK_ITER: stop_record_time = current_time record_stage = RecordStage.FINISHED @@ -95,13 +95,14 @@ def frame_change_handler(scene): print(f"{LOG_KEY}{{'time': {avg_frame_time}, 'fps': {fps} }}") bpy.app.handlers.frame_change_post.remove(frame_change_handler) bpy.ops.wm.quit_blender() - + if __name__ == '__main__': _run(None) - + else: import api + class EeveeTest(api.Test): def __init__(self, filepath): self.filepath = filepath @@ -123,7 +124,6 @@ else: raise Exception("No playback performance result found in log.") - def generate(env): filepaths = env.find_blend_files('eevee/*') - return [EeveeTest(filepath) for filepath in filepaths] \ No newline at end of file + return [EeveeTest(filepath) for filepath in filepaths] -- cgit v1.2.3 From 3cefa13770a1a76f223e3183c494edf533739a2e Mon Sep 17 00:00:00 2001 From: Joseph Eagar Date: Wed, 29 Jun 2022 21:04:07 -0700 Subject: Fix T98886: PBVH_GRIDS ignores face smooth flag on first gpu build --- source/blender/blenkernel/intern/pbvh.c | 9 +++++++-- source/blender/gpu/GPU_buffers.h | 4 +++- source/blender/gpu/intern/gpu_buffers.c | 3 ++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/source/blender/blenkernel/intern/pbvh.c b/source/blender/blenkernel/intern/pbvh.c index 5e5443f48ca..00a4eee47e3 100644 --- a/source/blender/blenkernel/intern/pbvh.c +++ b/source/blender/blenkernel/intern/pbvh.c @@ -1316,9 +1316,14 @@ static void pbvh_update_draw_buffer_cb(void *__restrict userdata, if (node->flag & PBVH_RebuildDrawBuffers) { switch (pbvh->type) { - case PBVH_GRIDS: - node->draw_buffers = GPU_pbvh_grid_buffers_build(node->totprim, pbvh->grid_hidden); + case PBVH_GRIDS: { + bool smooth = node->totprim > 0 ? + pbvh->grid_flag_mats[node->prim_indices[0]].flag & ME_SMOOTH : + false; + + node->draw_buffers = GPU_pbvh_grid_buffers_build(node->totprim, pbvh->grid_hidden, smooth); break; + } case PBVH_FACES: node->draw_buffers = GPU_pbvh_mesh_buffers_build( pbvh->mpoly, diff --git a/source/blender/gpu/GPU_buffers.h b/source/blender/gpu/GPU_buffers.h index 1fe3b363687..89473ac0fe0 100644 --- a/source/blender/gpu/GPU_buffers.h +++ b/source/blender/gpu/GPU_buffers.h @@ -58,7 +58,9 @@ GPU_PBVH_Buffers *GPU_pbvh_mesh_buffers_build(const struct MPoly *mpoly, /** * Threaded: do not call any functions that use OpenGL calls! */ -GPU_PBVH_Buffers *GPU_pbvh_grid_buffers_build(int totgrid, unsigned int **grid_hidden); +GPU_PBVH_Buffers *GPU_pbvh_grid_buffers_build(int totgrid, + unsigned int **grid_hidden, + bool smooth); /** * Threaded: do not call any functions that use OpenGL calls! diff --git a/source/blender/gpu/intern/gpu_buffers.c b/source/blender/gpu/intern/gpu_buffers.c index a1fe3d79223..8665b49f4aa 100644 --- a/source/blender/gpu/intern/gpu_buffers.c +++ b/source/blender/gpu/intern/gpu_buffers.c @@ -881,13 +881,14 @@ void GPU_pbvh_grid_buffers_update(PBVHGPUFormat *vbo_id, buffers->show_overlay = !empty_mask || !default_face_set; } -GPU_PBVH_Buffers *GPU_pbvh_grid_buffers_build(int totgrid, BLI_bitmap **grid_hidden) +GPU_PBVH_Buffers *GPU_pbvh_grid_buffers_build(int totgrid, BLI_bitmap **grid_hidden, bool smooth) { GPU_PBVH_Buffers *buffers; buffers = MEM_callocN(sizeof(GPU_PBVH_Buffers), "GPU_Buffers"); buffers->grid_hidden = grid_hidden; buffers->totgrid = totgrid; + buffers->smooth = smooth; buffers->show_overlay = false; -- cgit v1.2.3 From f7c6d3705df8c182d70ca8ca12bb8ac160cdba8f Mon Sep 17 00:00:00 2001 From: Joseph Eagar Date: Wed, 29 Jun 2022 23:11:24 -0700 Subject: Cleanup: Renamed SCULPT_TOOL_NEEDS_COLOR to SCULPT_tool_is_paint Old name is confusing since SCULPT_TOOL_PAINT can paint on images too, and it's planned for smear to as well. --- source/blender/blenkernel/BKE_paint.h | 2 +- source/blender/blenkernel/intern/paint.c | 6 +++--- source/blender/editors/sculpt_paint/sculpt.c | 14 +++++++------- source/blender/editors/sculpt_paint/sculpt_intern.h | 5 ++++- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h index a2d93f5ede2..ffe80ff47b6 100644 --- a/source/blender/blenkernel/BKE_paint.h +++ b/source/blender/blenkernel/BKE_paint.h @@ -679,7 +679,7 @@ void BKE_sculpt_update_object_for_edit(struct Depsgraph *depsgraph, struct Object *ob_orig, bool need_pmap, bool need_mask, - bool need_colors); + bool is_paint_tool); void BKE_sculpt_update_object_before_eval(const struct Scene *scene, struct Object *ob_eval); void BKE_sculpt_update_object_after_eval(struct Depsgraph *depsgraph, struct Object *ob_eval); diff --git a/source/blender/blenkernel/intern/paint.c b/source/blender/blenkernel/intern/paint.c index 9b72b939f77..5d4a818b7e2 100644 --- a/source/blender/blenkernel/intern/paint.c +++ b/source/blender/blenkernel/intern/paint.c @@ -1613,7 +1613,7 @@ static void sculpt_update_object(Depsgraph *depsgraph, Mesh *me_eval, bool need_pmap, bool need_mask, - bool UNUSED(need_colors)) + bool is_paint_tool) { Scene *scene = DEG_get_input_scene(depsgraph); Sculpt *sd = scene->toolsettings->sculpt; @@ -1903,7 +1903,7 @@ void BKE_sculpt_color_layer_create_if_needed(struct Object *object) } void BKE_sculpt_update_object_for_edit( - Depsgraph *depsgraph, Object *ob_orig, bool need_pmap, bool need_mask, bool need_colors) + Depsgraph *depsgraph, Object *ob_orig, bool need_pmap, bool need_mask, bool is_paint_tool) { BLI_assert(ob_orig == DEG_get_original_object(ob_orig)); @@ -1911,7 +1911,7 @@ void BKE_sculpt_update_object_for_edit( Mesh *me_eval = BKE_object_get_evaluated_mesh(ob_eval); BLI_assert(me_eval != NULL); - sculpt_update_object(depsgraph, ob_orig, me_eval, need_pmap, need_mask, need_colors); + sculpt_update_object(depsgraph, ob_orig, me_eval, need_pmap, need_mask, is_paint_tool); } int BKE_sculpt_mask_layers_ensure(Object *ob, MultiresModifierData *mmd) diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index cbf7701efe9..41bc82874ae 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -3219,7 +3219,7 @@ static void do_brush_action_task_cb(void *__restrict userdata, SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_MASK); BKE_pbvh_node_mark_update_mask(data->nodes[n]); } - else if (SCULPT_TOOL_NEEDS_COLOR(data->brush->sculpt_tool)) { + else if (SCULPT_tool_is_paint(data->brush->sculpt_tool)) { SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COLOR); BKE_pbvh_node_mark_update_color(data->nodes[n]); } @@ -3246,7 +3246,7 @@ static void do_brush_action(Sculpt *sd, /* Check for unsupported features. */ PBVHType type = BKE_pbvh_type(ss->pbvh); - if (SCULPT_TOOL_NEEDS_COLOR(brush->sculpt_tool) && SCULPT_has_loop_colors(ob)) { + if (SCULPT_tool_is_paint(brush->sculpt_tool) && SCULPT_has_loop_colors(ob)) { if (type != PBVH_FACES) { return; } @@ -4693,8 +4693,8 @@ static bool sculpt_needs_connectivity_info(const Sculpt *sd, (brush->sculpt_tool == SCULPT_TOOL_POSE) || (brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) || (brush->sculpt_tool == SCULPT_TOOL_SLIDE_RELAX) || - SCULPT_TOOL_NEEDS_COLOR(brush->sculpt_tool) || - (brush->sculpt_tool == SCULPT_TOOL_CLOTH) || (brush->sculpt_tool == SCULPT_TOOL_SMEAR) || + SCULPT_tool_is_paint(brush->sculpt_tool) || (brush->sculpt_tool == SCULPT_TOOL_CLOTH) || + (brush->sculpt_tool == SCULPT_TOOL_SMEAR) || (brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS) || (brush->sculpt_tool == SCULPT_TOOL_DISPLACEMENT_SMEAR) || (brush->sculpt_tool == SCULPT_TOOL_PAINT)); @@ -5069,7 +5069,7 @@ static void sculpt_brush_stroke_init(bContext *C, wmOperator *op) sculpt_brush_init_tex(sd, ss); need_pmap = sculpt_needs_connectivity_info(sd, brush, ss, mode); - needs_colors = SCULPT_TOOL_NEEDS_COLOR(brush->sculpt_tool); + needs_colors = SCULPT_tool_is_paint(brush->sculpt_tool); if (needs_colors) { BKE_sculpt_color_layer_create_if_needed(ob); @@ -5316,7 +5316,7 @@ static bool sculpt_stroke_test_start(bContext *C, struct wmOperator *op, const f /* NOTE: This should be removed when paint mode is available. Paint mode can force based on the * canvas it is painting on. (ref. use_sculpt_texture_paint). */ - if (brush && SCULPT_TOOL_NEEDS_COLOR(brush->sculpt_tool)) { + if (brush && SCULPT_tool_is_paint(brush->sculpt_tool)) { View3D *v3d = CTX_wm_view3d(C); if (v3d->shading.type == OB_SOLID) { v3d->shading.color_type = V3D_SHADING_VERTEX_COLOR; @@ -5501,7 +5501,7 @@ static int sculpt_brush_stroke_invoke(bContext *C, wmOperator *op, const wmEvent Sculpt *sd = CTX_data_tool_settings(C)->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); - if (SCULPT_TOOL_NEEDS_COLOR(brush->sculpt_tool) && + if (SCULPT_tool_is_paint(brush->sculpt_tool) && !SCULPT_handles_colors_report(ob->sculpt, op->reports)) { return OPERATOR_CANCELLED; } diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index 2c4f2f7370a..8485c7fcbf9 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h @@ -1805,7 +1805,10 @@ void SCULPT_OT_brush_stroke(struct wmOperatorType *ot); /* end sculpt_ops.c */ -#define SCULPT_TOOL_NEEDS_COLOR(tool) ELEM(tool, SCULPT_TOOL_PAINT, SCULPT_TOOL_SMEAR) +BLI_INLINE bool SCULPT_tool_is_paint(int tool) +{ + return ELEM(tool, SCULPT_TOOL_PAINT, SCULPT_TOOL_SMEAR); +} #ifdef __cplusplus } -- cgit v1.2.3 From 21859432357a80d50eab71552350f6dd242abfb2 Mon Sep 17 00:00:00 2001 From: Joseph Eagar Date: Wed, 29 Jun 2022 23:22:06 -0700 Subject: Sculpt: Fix a few bugs revealed by SCULPT_TOOL_NEEDS_COLOR name change * Color attributes are no longer auto-created when painting an image. * Workbench shading type is no longer automatically changed to Attribute for image paint. --- source/blender/editors/sculpt_paint/sculpt.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index 41bc82874ae..a3e2678aa40 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -4710,7 +4710,8 @@ void SCULPT_stroke_modifiers_check(const bContext *C, Object *ob, const Brush *b if (ss->shapekey_active || ss->deform_modifiers_active || (!BKE_sculptsession_use_pbvh_draw(ob, v3d) && need_pmap)) { Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - BKE_sculpt_update_object_for_edit(depsgraph, ob, need_pmap, false, false); + BKE_sculpt_update_object_for_edit( + depsgraph, ob, need_pmap, false, SCULPT_tool_is_paint(brush->sculpt_tool)); } } @@ -5049,7 +5050,8 @@ static void sculpt_brush_init_tex(Sculpt *sd, SculptSession *ss) static void sculpt_brush_stroke_init(bContext *C, wmOperator *op) { Object *ob = CTX_data_active_object(C); - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + ToolSettings *tool_settings = CTX_data_tool_settings(C); + Sculpt *sd = tool_settings->sculpt; SculptSession *ss = CTX_data_active_object(C)->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); int mode = RNA_enum_get(op->ptr, "mode"); @@ -5069,7 +5071,8 @@ static void sculpt_brush_stroke_init(bContext *C, wmOperator *op) sculpt_brush_init_tex(sd, ss); need_pmap = sculpt_needs_connectivity_info(sd, brush, ss, mode); - needs_colors = SCULPT_tool_is_paint(brush->sculpt_tool); + needs_colors = SCULPT_tool_is_paint(brush->sculpt_tool) && + !SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob); if (needs_colors) { BKE_sculpt_color_layer_create_if_needed(ob); @@ -5316,7 +5319,8 @@ static bool sculpt_stroke_test_start(bContext *C, struct wmOperator *op, const f /* NOTE: This should be removed when paint mode is available. Paint mode can force based on the * canvas it is painting on. (ref. use_sculpt_texture_paint). */ - if (brush && SCULPT_tool_is_paint(brush->sculpt_tool)) { + if (brush && SCULPT_tool_is_paint(brush->sculpt_tool) && + !SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { View3D *v3d = CTX_wm_view3d(C); if (v3d->shading.type == OB_SOLID) { v3d->shading.color_type = V3D_SHADING_VERTEX_COLOR; @@ -5408,7 +5412,7 @@ static void sculpt_stroke_update_step(bContext *C, if (brush->sculpt_tool == SCULPT_TOOL_MASK) { SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK); } - else if (ELEM(brush->sculpt_tool, SCULPT_TOOL_PAINT, SCULPT_TOOL_SMEAR)) { + else if (SCULPT_tool_is_paint(brush->sculpt_tool)) { if (SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { SCULPT_flush_update_step(C, SCULPT_UPDATE_IMAGE); } -- cgit v1.2.3 From c39e9326317442f6751fb0b2929c49b3512309d5 Mon Sep 17 00:00:00 2001 From: Joseph Eagar Date: Wed, 29 Jun 2022 23:31:08 -0700 Subject: Fix T99196: sculpt_update_object calls paint updates for nonpaint tools Specifically BKE_texpaint_slots_refresh_object was being called, which causes cycles to reset at strange times (like moving the mouse cursor in pose, boundary and various other tools). This (along with some code that checks if the pbvh pixels need to be rebuilt) is only run if is_paint_mode (which used to be needs_colors) is true. --- source/blender/blenkernel/intern/paint.c | 43 ++++++++++++---------- .../blender/editors/sculpt_paint/paint_vertex.cc | 4 +- source/blender/editors/sculpt_paint/sculpt.c | 3 +- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/source/blender/blenkernel/intern/paint.c b/source/blender/blenkernel/intern/paint.c index 5d4a818b7e2..89cc25b31e6 100644 --- a/source/blender/blenkernel/intern/paint.c +++ b/source/blender/blenkernel/intern/paint.c @@ -1773,28 +1773,31 @@ static void sculpt_update_object(Depsgraph *depsgraph, } } - /* - * We should rebuild the PBVH_pixels when painting canvas changes. - * - * The relevant changes are stored/encoded in the paint canvas key. - * These include the active uv map, and resolutions. - */ - if (U.experimental.use_sculpt_texture_paint && ss->pbvh) { - char *paint_canvas_key = BKE_paint_canvas_key_get(&scene->toolsettings->paint_mode, ob); - if (ss->last_paint_canvas_key == NULL || !STREQ(paint_canvas_key, ss->last_paint_canvas_key)) { - MEM_SAFE_FREE(ss->last_paint_canvas_key); - ss->last_paint_canvas_key = paint_canvas_key; - BKE_pbvh_mark_rebuild_pixels(ss->pbvh); - } - else { - MEM_freeN(paint_canvas_key); + if (is_paint_tool) { + /* + * We should rebuild the PBVH_pixels when painting canvas changes. + * + * The relevant changes are stored/encoded in the paint canvas key. + * These include the active uv map, and resolutions. + */ + if (U.experimental.use_sculpt_texture_paint && ss->pbvh) { + char *paint_canvas_key = BKE_paint_canvas_key_get(&scene->toolsettings->paint_mode, ob); + if (ss->last_paint_canvas_key == NULL || + !STREQ(paint_canvas_key, ss->last_paint_canvas_key)) { + MEM_SAFE_FREE(ss->last_paint_canvas_key); + ss->last_paint_canvas_key = paint_canvas_key; + BKE_pbvh_mark_rebuild_pixels(ss->pbvh); + } + else { + MEM_freeN(paint_canvas_key); + } } - } - /* We could be more precise when we have access to the active tool. */ - const bool use_paint_slots = (ob->mode & OB_MODE_SCULPT) != 0; - if (use_paint_slots) { - BKE_texpaint_slots_refresh_object(scene, ob); + /* We could be more precise when we have access to the active tool. */ + const bool use_paint_slots = (ob->mode & OB_MODE_SCULPT) != 0; + if (use_paint_slots) { + BKE_texpaint_slots_refresh_object(scene, ob); + } } } diff --git a/source/blender/editors/sculpt_paint/paint_vertex.cc b/source/blender/editors/sculpt_paint/paint_vertex.cc index c93b0fc3aa8..6dc8375bb0d 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex.cc +++ b/source/blender/editors/sculpt_paint/paint_vertex.cc @@ -1176,12 +1176,12 @@ static void vertex_paint_init_session(Depsgraph *depsgraph, BLI_assert(ob->sculpt == nullptr); ob->sculpt = (SculptSession *)MEM_callocN(sizeof(SculptSession), "sculpt session"); ob->sculpt->mode_type = object_mode; - BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false); + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, true); } static void vwpaint_init_stroke(Depsgraph *depsgraph, Object *ob) { - BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false); + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, true); SculptSession *ss = ob->sculpt; /* Ensure ss->cache is allocated. It will mostly be initialized in diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index a3e2678aa40..906c4fd35fe 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -5081,7 +5081,8 @@ static void sculpt_brush_stroke_init(bContext *C, wmOperator *op) /* CTX_data_ensure_evaluated_depsgraph should be used at the end to include the updates of * earlier steps modifying the data. */ Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - BKE_sculpt_update_object_for_edit(depsgraph, ob, need_pmap, need_mask, needs_colors); + BKE_sculpt_update_object_for_edit( + depsgraph, ob, need_pmap, need_mask, SCULPT_tool_is_paint(brush->sculpt_tool)); ED_paint_tool_update_sticky_shading_color(C, ob); } -- cgit v1.2.3 From 5c726dd4ef09a8ffbd4f609d45106065c5bd04f3 Mon Sep 17 00:00:00 2001 From: Iyad Ahmed Date: Thu, 30 Jun 2022 10:13:05 +0200 Subject: Fix C++ STL importer unused function result warning Fix unused function result warnings for usages of `fread` in the C++ .stl importer, by actually using the returned error code. Reviewed By: sybren Differential Revision: https://developer.blender.org/D15189 --- source/blender/io/stl/importer/stl_import.cc | 23 ++++++++++++++++++++-- source/blender/io/stl/importer/stl_import.hh | 2 ++ .../io/stl/importer/stl_import_binary_reader.cc | 7 ++++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/source/blender/io/stl/importer/stl_import.cc b/source/blender/io/stl/importer/stl_import.cc index f358598a216..097d14b038c 100644 --- a/source/blender/io/stl/importer/stl_import.cc +++ b/source/blender/io/stl/importer/stl_import.cc @@ -29,6 +29,17 @@ namespace blender::io::stl { +void stl_import_report_error(FILE *file) +{ + fprintf(stderr, "STL Importer: failed to read file"); + if (feof(file)) { + fprintf(stderr, ", end of file reached.\n"); + } + else if (ferror(file)) { + perror("Error"); + } +} + void importer_main(bContext *C, const STLImportParams &import_params) { Main *bmain = CTX_data_main(C); @@ -56,7 +67,10 @@ void importer_main(Main *bmain, uint32_t num_tri = 0; size_t file_size = BLI_file_size(import_params.filepath); fseek(file, BINARY_HEADER_SIZE, SEEK_SET); - fread(&num_tri, sizeof(uint32_t), 1, file); + if (fread(&num_tri, sizeof(uint32_t), 1, file) != 1) { + stl_import_report_error(file); + return; + } bool is_ascii_stl = (file_size != (BINARY_HEADER_SIZE + 4 + BINARY_STRIDE * num_tri)); /* Name used for both mesh and object. */ @@ -64,7 +78,7 @@ void importer_main(Main *bmain, BLI_strncpy(ob_name, BLI_path_basename(import_params.filepath), FILE_MAX); BLI_path_extension_replace(ob_name, FILE_MAX, ""); - Mesh *mesh; + Mesh *mesh = nullptr; if (is_ascii_stl) { mesh = read_stl_ascii(import_params.filepath, bmain, ob_name, import_params.use_facet_normal); } @@ -72,6 +86,11 @@ void importer_main(Main *bmain, mesh = read_stl_binary(file, bmain, ob_name, import_params.use_facet_normal); } + if (mesh == nullptr) { + fprintf(stderr, "STL Importer: Failed to import mesh '%s'\n", import_params.filepath); + return; + } + if (import_params.use_mesh_validate) { bool verbose_validate = false; #ifdef DEBUG diff --git a/source/blender/io/stl/importer/stl_import.hh b/source/blender/io/stl/importer/stl_import.hh index 377544c26af..a5d252248a8 100644 --- a/source/blender/io/stl/importer/stl_import.hh +++ b/source/blender/io/stl/importer/stl_import.hh @@ -10,6 +10,8 @@ namespace blender::io::stl { +void stl_import_report_error(FILE *file); + /* Main import function used from within Blender. */ void importer_main(bContext *C, const STLImportParams &import_params); diff --git a/source/blender/io/stl/importer/stl_import_binary_reader.cc b/source/blender/io/stl/importer/stl_import_binary_reader.cc index 6eaed16160e..fb9dcea0a1d 100644 --- a/source/blender/io/stl/importer/stl_import_binary_reader.cc +++ b/source/blender/io/stl/importer/stl_import_binary_reader.cc @@ -15,6 +15,7 @@ #include "DNA_mesh_types.h" +#include "stl_import.hh" #include "stl_import_binary_reader.hh" #include "stl_import_mesh.hh" @@ -33,7 +34,11 @@ Mesh *read_stl_binary(FILE *file, Main *bmain, char *mesh_name, bool use_custom_ const int chunk_size = 1024; uint32_t num_tris = 0; fseek(file, BINARY_HEADER_SIZE, SEEK_SET); - fread(&num_tris, sizeof(uint32_t), 1, file); + if (fread(&num_tris, sizeof(uint32_t), 1, file) != 1) { + stl_import_report_error(file); + return nullptr; + } + if (num_tris == 0) { return BKE_mesh_add(bmain, mesh_name); } -- cgit v1.2.3 From edbf04ff37be172d6ac303ebb221fe747bbf8979 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Thu, 30 Jun 2022 10:22:50 +0200 Subject: Fix T99290: Wrong color management of plane track image preview Always consider images as "View as Render" for the plane track image drawing. --- source/blender/editors/space_clip/clip_draw.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/source/blender/editors/space_clip/clip_draw.c b/source/blender/editors/space_clip/clip_draw.c index 7800ce797aa..171fb580fb7 100644 --- a/source/blender/editors/space_clip/clip_draw.c +++ b/source/blender/editors/space_clip/clip_draw.c @@ -1175,17 +1175,9 @@ static void draw_plane_marker_image(Scene *scene, ibuf = BKE_image_acquire_ibuf(image, NULL, &lock); if (ibuf) { - uchar *display_buffer; void *cache_handle; - - if (image->flag & IMA_VIEW_AS_RENDER) { - display_buffer = IMB_display_buffer_acquire( - ibuf, &scene->view_settings, &scene->display_settings, &cache_handle); - } - else { - display_buffer = IMB_display_buffer_acquire( - ibuf, NULL, &scene->display_settings, &cache_handle); - } + uchar *display_buffer = IMB_display_buffer_acquire( + ibuf, &scene->view_settings, &scene->display_settings, &cache_handle); if (display_buffer) { float frame_corners[4][2] = {{0.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 1.0f}}; -- cgit v1.2.3 From acdc043c30a6741229536eb9ff70d3ec94ef7b25 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 29 Jun 2022 10:25:00 +0200 Subject: Revert "Revert "LibOverride: Handle dependencies in both directions in partial override cases."" This reverts commit rB31d80ddeaad, and fixes issue introduced in rBf0b4aa5d59e3 by not doing the 'reversed dependency check' in resync case. Rational here are: * Supporting reversed dependency in a reliable, coinsistent way in resync is likely to be a nightmare, if even possible at all. * Needs for such reversed dependency in resync should be close to 0% of cases, as long as users remain reasonable with their organization of their assets (it could only become a problem in extreme bad practice and corner cases, like a geometry object being added as a child of a rig in a completely new, otherwise un-overridden collection, in partial override context). This decision may need to be re-evaluated later in case we go more towards a very highly partial-override workflow, but even then I would expect current solution to work fine in all reasonable use cases. --- source/blender/blenkernel/intern/lib_override.cc | 59 +++++++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/source/blender/blenkernel/intern/lib_override.cc b/source/blender/blenkernel/intern/lib_override.cc index 22012662180..7bde609913b 100644 --- a/source/blender/blenkernel/intern/lib_override.cc +++ b/source/blender/blenkernel/intern/lib_override.cc @@ -687,6 +687,51 @@ static void lib_override_group_tag_data_clear(LibOverrideGroupTagData *data) memset(data, 0, sizeof(*data)); } +static void lib_override_hierarchy_dependencies_recursive_tag_from(LibOverrideGroupTagData *data) +{ + Main *bmain = data->bmain; + ID *id = data->id_root; + const bool is_override = data->is_override; + + if ((*(uint *)&id->tag & data->tag) == 0) { + /* This ID is not tagged, no reason to proceed further to its parents. */ + return; + } + + MainIDRelationsEntry *entry = static_cast( + BLI_ghash_lookup(bmain->relations->relations_from_pointers, id)); + BLI_assert(entry != nullptr); + + if (entry->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED_FROM) { + /* This ID has already been processed. */ + return; + } + /* This way we won't process again that ID, should we encounter it again through another + * relationship hierarchy. */ + entry->tags |= MAINIDRELATIONS_ENTRY_TAGS_PROCESSED_FROM; + + for (MainIDRelationsEntryItem *from_id_entry = entry->from_ids; from_id_entry != nullptr; + from_id_entry = from_id_entry->next) { + if ((from_id_entry->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) != 0) { + /* Never consider non-overridable relationships ('from', 'parents', 'owner' etc. pointers) + * as actual dependencies. */ + continue; + } + /* We only consider IDs from the same library. */ + ID *from_id = from_id_entry->id_pointer.from; + if (from_id == nullptr || from_id->lib != id->lib || + (is_override && !ID_IS_OVERRIDE_LIBRARY(from_id))) { + /* IDs from different libraries, or non-override IDs in case we are processing overrides, + * are both barriers of dependency. */ + continue; + } + from_id->tag |= data->tag; + LibOverrideGroupTagData sub_data = *data; + sub_data.id_root = from_id; + lib_override_hierarchy_dependencies_recursive_tag_from(&sub_data); + } +} + /* Tag all IDs in dependency relationships within an override hierarchy/group. * * Requires existing `Main.relations`. @@ -698,18 +743,19 @@ static bool lib_override_hierarchy_dependencies_recursive_tag(LibOverrideGroupTa Main *bmain = data->bmain; ID *id = data->id_root; const bool is_override = data->is_override; + const bool is_resync = data->is_resync; MainIDRelationsEntry *entry = static_cast( BLI_ghash_lookup(bmain->relations->relations_from_pointers, id)); BLI_assert(entry != nullptr); - if (entry->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED) { + if (entry->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED_TO) { /* This ID has already been processed. */ return (*(uint *)&id->tag & data->tag) != 0; } /* This way we won't process again that ID, should we encounter it again through another * relationship hierarchy. */ - entry->tags |= MAINIDRELATIONS_ENTRY_TAGS_PROCESSED; + entry->tags |= MAINIDRELATIONS_ENTRY_TAGS_PROCESSED_TO; for (MainIDRelationsEntryItem *to_id_entry = entry->to_ids; to_id_entry != nullptr; to_id_entry = to_id_entry->next) { @@ -733,6 +779,15 @@ static bool lib_override_hierarchy_dependencies_recursive_tag(LibOverrideGroupTa } } + /* If the current ID is/has been tagged for override above, then check its reversed dependencies + * (i.e. IDs that depend on the current one). + * + * This will cover e.g. the case where user override an armature, and would expect the mesh + * object deformed by that armature to also be overridden. */ + if ((*(uint *)&id->tag & data->tag) != 0 && !is_resync) { + lib_override_hierarchy_dependencies_recursive_tag_from(data); + } + return (*(uint *)&id->tag & data->tag) != 0; } -- cgit v1.2.3 From b544225202793435c54f63080a2bbd098727654d Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 29 Jun 2022 18:05:25 +0200 Subject: Fix (unreported) liboverride resync creating garbage data in some cases. Regression caused by the introduction of partial resync in February 2022 (rB1695d38989fd482d3c). Code was missing adding some existing overrides to the mapping in some specific cases, causing resync to create 'new' ones instead of re-using existing ones. This commit also adds a basic resync testcase that illustrates this issue. --- source/blender/blenkernel/intern/lib_override.cc | 9 +- tests/python/bl_blendfile_library_overrides.py | 109 +++++++++++++++++++++++ 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/source/blender/blenkernel/intern/lib_override.cc b/source/blender/blenkernel/intern/lib_override.cc index 7bde609913b..feb6fb95f5c 100644 --- a/source/blender/blenkernel/intern/lib_override.cc +++ b/source/blender/blenkernel/intern/lib_override.cc @@ -1749,11 +1749,16 @@ static bool lib_override_library_resync(Main *bmain, id->tag |= LIB_TAG_MISSING; } - if (id->tag & LIB_TAG_DOIT && (id->lib == id_root->lib) && ID_IS_OVERRIDE_LIBRARY(id)) { + if ((id->lib == id_root->lib) && ID_IS_OVERRIDE_LIBRARY(id)) { /* While this should not happen in typical cases (and won't be properly supported here), * user is free to do all kind of very bad things, including having different local * overrides of a same linked ID in a same hierarchy. */ IDOverrideLibrary *id_override_library = lib_override_get(bmain, id, nullptr); + + if (id_override_library->hierarchy_root != id_root->override_library->hierarchy_root) { + continue; + } + ID *reference_id = id_override_library->reference; if (GS(reference_id->name) != GS(id->name)) { switch (GS(id->name)) { @@ -1775,7 +1780,7 @@ static bool lib_override_library_resync(Main *bmain, if (!BLI_ghash_haskey(linkedref_to_old_override, reference_id)) { BLI_ghash_insert(linkedref_to_old_override, reference_id, id); - if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) { + if (!ID_IS_OVERRIDE_LIBRARY_REAL(id) || (id->tag & LIB_TAG_DOIT) == 0) { continue; } if ((id->override_library->reference->tag & LIB_TAG_DOIT) == 0) { diff --git a/tests/python/bl_blendfile_library_overrides.py b/tests/python/bl_blendfile_library_overrides.py index 1acc1e4d862..46954c08b8c 100644 --- a/tests/python/bl_blendfile_library_overrides.py +++ b/tests/python/bl_blendfile_library_overrides.py @@ -181,9 +181,118 @@ class TestLibraryTemplate(TestHelper, unittest.TestCase): assert(operation.operation == 'NOOP') +class TestLibraryOverridesResync(TestHelper, unittest.TestCase): + DATA_NAME_CONTAINER = "LibCollection" + DATA_NAME_RIGGED = "LibRigged" + DATA_NAME_RIG = "LibRig" + DATA_NAME_CONTROLLER_1 = "LibController1" + DATA_NAME_CONTROLLER_2 = "LibController2" + + def __init__(self, args): + self.args = args + + output_dir = pathlib.Path(self.args.output_dir) + self.ensure_path(str(output_dir)) + self.output_path = output_dir / "blendlib_overrides.blend" + self.test_output_path = output_dir / "blendlib_overrides_test.blend" + + bpy.ops.wm.read_homefile(use_empty=True, use_factory_startup=True) + + collection_container = bpy.data.collections.new(TestLibraryOverridesResync.DATA_NAME_CONTAINER) + bpy.context.collection.children.link(collection_container) + + mesh = bpy.data.meshes.new(TestLibraryOverridesResync.DATA_NAME_RIGGED) + obj_child = bpy.data.objects.new(TestLibraryOverridesResync.DATA_NAME_RIGGED, object_data=mesh) + collection_container.objects.link(obj_child) + armature = bpy.data.armatures.new(TestLibraryOverridesResync.DATA_NAME_RIG) + obj_armature = bpy.data.objects.new(TestLibraryOverridesResync.DATA_NAME_RIG, object_data=armature) + obj_child.parent = obj_armature + collection_container.objects.link(obj_armature) + + obj_child_modifier = obj_child.modifiers.new("", 'ARMATURE') + obj_child_modifier.object = obj_armature + + obj_ctrl1 = bpy.data.objects.new(TestLibraryOverridesResync.DATA_NAME_CONTROLLER_1, object_data=None) + collection_container.objects.link(obj_ctrl1) + + obj_armature_constraint = obj_armature.constraints.new('COPY_LOCATION') + obj_armature_constraint.target = obj_ctrl1 + + collection_sub = bpy.data.collections.new(TestLibraryOverridesResync.DATA_NAME_CONTROLLER_2) + collection_container.children.link(collection_sub) + obj_ctrl2 = bpy.data.objects.new(TestLibraryOverridesResync.DATA_NAME_CONTROLLER_2, object_data=None) + collection_sub.objects.link(obj_ctrl2) + + bpy.ops.wm.save_as_mainfile(filepath=str(self.output_path), check_existing=False, compress=False) + + def test_link_and_override_resync(self): + bpy.ops.wm.read_homefile(use_empty=True, use_factory_startup=True) + bpy.data.orphans_purge() + + link_dir = self.output_path / "Collection" + bpy.ops.wm.link(directory=str(link_dir), filename=TestLibraryOverridesResync.DATA_NAME_CONTAINER, instance_collections=False) + + linked_collection_container = bpy.data.collections[TestLibraryOverridesResync.DATA_NAME_CONTAINER] + assert(linked_collection_container.library is not None) + assert(linked_collection_container.override_library is None) + assert(len(bpy.data.collections) == 2) + assert(all(id_.library is not None for id_ in bpy.data.collections)) + assert(len(bpy.data.objects) == 4) + assert(all(id_.library is not None for id_ in bpy.data.objects)) + assert(len(bpy.data.meshes) == 1) + assert(all(id_.library is not None for id_ in bpy.data.meshes)) + assert(len(bpy.data.armatures) == 1) + assert(all(id_.library is not None for id_ in bpy.data.armatures)) + + override_collection_container = linked_collection_container.override_hierarchy_create(bpy.context.scene, bpy.context.view_layer) + assert(override_collection_container.library is None) + assert(override_collection_container.override_library is not None) + # Objects and collections are duplicated as overrides, but meshes and armatures remain only linked data. + assert(len(bpy.data.collections) == 4) + assert(all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.collections[:2])) + assert(len(bpy.data.objects) == 8) + assert(all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:4])) + assert(len(bpy.data.meshes) == 1) + assert(len(bpy.data.armatures) == 1) + + bpy.ops.wm.save_as_mainfile(filepath=str(self.test_output_path), check_existing=False, compress=False) + + # Re-open the lib file, and change its ID relationships. + bpy.ops.wm.open_mainfile(filepath=str(self.output_path)) + + obj_armature = bpy.data.objects[TestLibraryOverridesResync.DATA_NAME_RIG] + obj_armature_constraint = obj_armature.constraints[0] + obj_ctrl2 = bpy.data.objects[TestLibraryOverridesResync.DATA_NAME_CONTROLLER_2] + obj_armature_constraint.target = obj_ctrl2 + + bpy.ops.wm.save_as_mainfile(filepath=str(self.output_path), check_existing=False, compress=False) + + # Re-open the main file, and check that automatic resync did its work correctly, remapping the target of the + # armature constraint to controller 2, without creating unexpected garbage IDs along the line. + bpy.ops.wm.open_mainfile(filepath=str(self.test_output_path)) + + override_collection_container = bpy.data.collections[TestLibraryOverridesResync.DATA_NAME_CONTAINER] + assert(override_collection_container.library is None) + assert(override_collection_container.override_library is not None) + # Objects and collections are duplicated as overrides, but meshes and armatures remain only linked data. + assert(len(bpy.data.collections) == 4) + assert(all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.collections[:2])) + assert(len(bpy.data.objects) == 8) + assert(all((id_.library is None and id_.override_library is not None) for id_ in bpy.data.objects[:4])) + assert(len(bpy.data.meshes) == 1) + assert(len(bpy.data.armatures) == 1) + + obj_armature = bpy.data.objects[TestLibraryOverridesResync.DATA_NAME_RIG] + obj_ctrl2 = bpy.data.objects[TestLibraryOverridesResync.DATA_NAME_CONTROLLER_2] + assert(obj_armature.library is None and obj_armature.override_library is not None) + assert(obj_ctrl2.library is None and obj_ctrl2.override_library is not None) + assert(obj_armature.constraints[0].target == obj_ctrl2) + + TESTS = ( TestLibraryOverrides, TestLibraryTemplate, + TestLibraryOverridesResync, ) -- cgit v1.2.3 From c64d1b23df877178671e073a264536bdbfba28bb Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Thu, 30 Jun 2022 11:09:19 +0200 Subject: Fix numerical issues with plane track compositor node with empty input No changes in the interface, but avoids spam in the console about inability to find a solution for homography transform from singularity. --- .../compositor/operations/COM_PlaneDistortCommonOperation.cc | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.cc b/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.cc index 8bf8e339697..ddda02c5e80 100644 --- a/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.cc +++ b/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.cc @@ -66,9 +66,18 @@ void PlaneDistortWarpImageOperation::calculate_corners(const float corners[4][2] const NodeOperation *image = get_input_operation(0); const int width = image->get_width(); const int height = image->get_height(); + + MotionSample *sample_data = &samples_[sample]; + + /* If the image which is to be warped empty assume unit transform and don't attempt to calculate + * actual homography (otherwise homography solver will attempt to deal with singularity). */ + if (width == 0 || height == 0) { + unit_m3(sample_data->perspective_matrix); + return; + } + float frame_corners[4][2] = { {0.0f, 0.0f}, {(float)width, 0.0f}, {(float)width, (float)height}, {0.0f, (float)height}}; - MotionSample *sample_data = &samples_[sample]; BKE_tracking_homography_between_two_quads( sample_data->frame_space_corners, frame_corners, sample_data->perspective_matrix); } -- cgit v1.2.3 From 4a7e1c920979c016fc8a2de325e39617a3a9b94e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 3 Jun 2022 16:28:09 +0300 Subject: Constraints: rename and refactor custom space initialization. Rename and simplify the function for initializing the custom space, avoiding the need for the calling code to be aware of the internals of bConstraintOb. This patch should not change any behavior. This was split off from D9732. Differential Revision: https://developer.blender.org/D15252 --- source/blender/blenkernel/BKE_constraint.h | 10 +++++++- source/blender/blenkernel/intern/constraint.c | 33 ++++++++++---------------- source/blender/editors/armature/armature_add.c | 6 ++--- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/source/blender/blenkernel/BKE_constraint.h b/source/blender/blenkernel/BKE_constraint.h index 737b05fee0c..2be299f5b0c 100644 --- a/source/blender/blenkernel/BKE_constraint.h +++ b/source/blender/blenkernel/BKE_constraint.h @@ -334,7 +334,15 @@ void BKE_constraint_targets_for_solving_get(struct Depsgraph *depsgraph, struct bConstraintOb *ob, struct ListBase *targets, float ctime); -void BKE_constraint_custom_object_space_get(float r_mat[4][4], struct bConstraint *con); + +/** + * Initializes the custom coordinate space data if required by the constraint. + * + * \param cob Constraint evaluation context (contains the matrix to be initialized). + * \param con Constraint that is about to be evaluated. + */ +void BKE_constraint_custom_object_space_init(struct bConstraintOb *cob, struct bConstraint *con); + /** * This function is called whenever constraints need to be evaluated. Currently, all * constraints that can be evaluated are every time this gets run. diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c index aa09541c043..cee32dc557b 100644 --- a/source/blender/blenkernel/intern/constraint.c +++ b/source/blender/blenkernel/intern/constraint.c @@ -6323,33 +6323,24 @@ void BKE_constraint_targets_for_solving_get(struct Depsgraph *depsgraph, } } -void BKE_constraint_custom_object_space_get(float r_mat[4][4], bConstraint *con) +/** Initialize the Custom Space matrix inside cob. */ +void BKE_constraint_custom_object_space_init(bConstraintOb *cob, bConstraint *con) { - if (!con || - (con->ownspace != CONSTRAINT_SPACE_CUSTOM && con->tarspace != CONSTRAINT_SPACE_CUSTOM)) { - return; - } - bConstraintTarget *ct; - ListBase target = {NULL, NULL}; - SINGLETARGET_GET_TARS(con, con->space_object, con->space_subtarget, ct, &target); - - /* Basically default_get_tarmat but without the unused parameters. */ - if (VALID_CONS_TARGET(ct)) { - constraint_target_to_mat4(ct->tar, - ct->subtarget, + if (con && con->space_object && is_custom_space_needed(con)) { + /* Basically default_get_tarmat but without the unused parameters. */ + constraint_target_to_mat4(con->space_object, + con->space_subtarget, NULL, - ct->matrix, + cob->space_obj_world_matrix, CONSTRAINT_SPACE_WORLD, CONSTRAINT_SPACE_WORLD, 0, 0); - copy_m4_m4(r_mat, ct->matrix); - } - else { - unit_m4(r_mat); + + return; } - SINGLETARGET_FLUSH_TARS(con, con->space_object, con->space_subtarget, ct, &target, true); + unit_m4(cob->space_obj_world_matrix); } /* ---------- Evaluation ----------- */ @@ -6394,8 +6385,8 @@ void BKE_constraints_solve(struct Depsgraph *depsgraph, */ enf = con->enforce; - /* Get custom space matrix. */ - BKE_constraint_custom_object_space_get(cob->space_obj_world_matrix, con); + /* Initialize the custom space for use in calculating the matrices. */ + BKE_constraint_custom_object_space_init(cob, con); /* make copy of world-space matrix pre-constraint for use with blending later */ copy_m4_m4(oldmat, cob->matrix); diff --git a/source/blender/editors/armature/armature_add.c b/source/blender/editors/armature/armature_add.c index dd43e3e6613..2071f056f9e 100644 --- a/source/blender/editors/armature/armature_add.c +++ b/source/blender/editors/armature/armature_add.c @@ -422,7 +422,7 @@ static void updateDuplicateActionConstraintSettings( float mat[4][4]; bConstraintOb cob = {.depsgraph = NULL, .scene = NULL, .ob = ob, .pchan = pchan}; - BKE_constraint_custom_object_space_get(cob.space_obj_world_matrix, curcon); + BKE_constraint_custom_object_space_init(&cob, curcon); unit_m4(mat); bPoseChannel *target_pchan = BKE_pose_channel_find_name(ob->pose, act_con->subtarget); @@ -576,7 +576,7 @@ static void updateDuplicateLocRotConstraintSettings(Object *ob, unit_m4(local_mat); bConstraintOb cob = {.depsgraph = NULL, .scene = NULL, .ob = ob, .pchan = pchan}; - BKE_constraint_custom_object_space_get(cob.space_obj_world_matrix, curcon); + BKE_constraint_custom_object_space_init(&cob, curcon); BKE_constraint_mat_convertspace( ob, pchan, &cob, local_mat, curcon->ownspace, CONSTRAINT_SPACE_LOCAL, false); @@ -631,7 +631,7 @@ static void updateDuplicateTransformConstraintSettings(Object *ob, float target_mat[4][4], own_mat[4][4], imat[4][4]; bConstraintOb cob = {.depsgraph = NULL, .scene = NULL, .ob = ob, .pchan = pchan}; - BKE_constraint_custom_object_space_get(cob.space_obj_world_matrix, curcon); + BKE_constraint_custom_object_space_init(&cob, curcon); unit_m4(own_mat); BKE_constraint_mat_convertspace( -- cgit v1.2.3 From 0f22b5599a03d5ce61450528c74ad2f2e47cf913 Mon Sep 17 00:00:00 2001 From: Nate Rupsis Date: Thu, 30 Jun 2022 12:53:56 +0200 Subject: Normalize Weights: use valid default Subset for current context For the Normalize Weights operator, dynamically set the default 'Subset' parameter so that it is applicable to the current context. When the user's last use of Normalize Weights was set to "Deform Pose Bones", and then tries to use the operator on a mesh without armature, Blender would try to use the previous opertor properties and show an error message. This is resolved by switching to `WT_VGROUP_ACTIVE` in such cases. Reviewed By: zanqdo, sybren Maniphest Tasks: T95038 Differential Revision: https://developer.blender.org/D14961 --- source/blender/editors/object/object_vgroup.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/source/blender/editors/object/object_vgroup.c b/source/blender/editors/object/object_vgroup.c index 9ad36cacc7d..88b27bd4cea 100644 --- a/source/blender/editors/object/object_vgroup.c +++ b/source/blender/editors/object/object_vgroup.c @@ -693,7 +693,7 @@ static const EnumPropertyItem WT_vertex_group_select_item[] = { const EnumPropertyItem *ED_object_vgroup_selection_itemf_helper(const bContext *C, PointerRNA *UNUSED(ptr), - PropertyRNA *UNUSED(prop), + PropertyRNA *prop, bool *r_free, const uint selection_mask) { @@ -731,6 +731,10 @@ const EnumPropertyItem *ED_object_vgroup_selection_itemf_helper(const bContext * RNA_enum_items_add_value(&item, &totitem, WT_vertex_group_select_item, WT_VGROUP_ALL); } + /* Set `Deform Bone` as default selection if armature is present. */ + RNA_def_property_enum_default( + prop, BKE_modifiers_is_deformed_by_armature(ob) ? WT_VGROUP_BONE_DEFORM : WT_VGROUP_ALL); + RNA_enum_item_end(&item, &totitem); *r_free = true; -- cgit v1.2.3 From 416aef4e13ccc30e82ecaa691f26af54dbd5ee7e Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Thu, 30 Jun 2022 15:09:13 +0200 Subject: Curves: New tools for curves sculpt mode. This commit contains various new features for curves sculpt mode that have been developed in parallel. * Selection: * Operator to select points/curves randomly. * Operator to select endpoints of curves. * Operator to grow/shrink an existing selection. * New Brushes: * Pinch: Moves points towards the brush center. * Smooth: Makes individual curves straight without changing the root or tip position. * Puff: Makes curves stand up, aligning them with the surface normal. * Density: Add or remove curves to achieve a certain density defined by a minimum distance value. * Slide: Move root points on the surface. Differential Revision: https://developer.blender.org/D15134 --- .../keyconfig/keymap_data/blender_default.py | 1 + .../startup/bl_ui/space_toolsystem_toolbar.py | 50 ++ release/scripts/startup/bl_ui/space_view3d.py | 28 + source/blender/blenkernel/intern/brush.c | 1 + source/blender/blenloader/intern/versioning_300.c | 8 + source/blender/editors/curves/CMakeLists.txt | 13 + source/blender/editors/curves/intern/curves_ops.cc | 27 +- source/blender/editors/include/ED_curves.h | 8 +- source/blender/editors/sculpt_paint/CMakeLists.txt | 7 +- .../editors/sculpt_paint/curves_sculpt_density.cc | 834 ++++++++++++++++++ .../editors/sculpt_paint/curves_sculpt_intern.hh | 2 +- .../editors/sculpt_paint/curves_sculpt_ops.cc | 964 ++++++++++++++++++++- .../editors/sculpt_paint/curves_sculpt_pinch.cc | 307 +++++++ .../editors/sculpt_paint/curves_sculpt_puff.cc | 393 +++++++++ .../editors/sculpt_paint/curves_sculpt_slide.cc | 310 +++++++ .../editors/sculpt_paint/curves_sculpt_smooth.cc | 259 ++++++ source/blender/editors/sculpt_paint/paint_stroke.c | 2 +- source/blender/makesdna/DNA_brush_enums.h | 11 + source/blender/makesdna/DNA_brush_types.h | 7 + source/blender/makesrna/intern/rna_brush.c | 42 + 20 files changed, 3260 insertions(+), 14 deletions(-) create mode 100644 source/blender/editors/sculpt_paint/curves_sculpt_density.cc create mode 100644 source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc create mode 100644 source/blender/editors/sculpt_paint/curves_sculpt_puff.cc create mode 100644 source/blender/editors/sculpt_paint/curves_sculpt_slide.cc create mode 100644 source/blender/editors/sculpt_paint/curves_sculpt_smooth.cc diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 623ca2340d1..d12d68ee5a0 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -5624,6 +5624,7 @@ def km_sculpt_curves(params): ("curves.disable_selection", {"type": 'TWO', "value": 'PRESS', "alt": True}, None), *_template_paint_radial_control("curves_sculpt"), *_template_items_select_actions(params, "sculpt_curves.select_all"), + ("sculpt_curves.min_distance_edit", {"type": 'R', "value": 'PRESS', "shift": True}, {}), ]) return keymap diff --git a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py index 5831aa52cc1..9f7ca89b8c9 100644 --- a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -2377,6 +2377,51 @@ class _defs_curves_sculpt: data_block='GROW_SHRINK' ) + @ToolDef.from_fn + def pinch(): + return dict( + idname="builtin_brush.pinch", + label="Pinch", + icon="ops.curves.sculpt_pinch", + data_block='PINCH' + ) + + @ToolDef.from_fn + def smooth(): + return dict( + idname="builtin_brush.smooth", + label="Smooth", + icon="ops.curves.sculpt_smooth", + data_block='SMOOTH' + ) + + @ToolDef.from_fn + def puff(): + return dict( + idname="builtin_brush.puff", + label="Puff", + icon="ops.curves.sculpt_puff", + data_block='PUFF' + ) + + @ToolDef.from_fn + def density(): + return dict( + idname="builtin_brush.density", + label="Density", + icon="ops.curves.sculpt_density", + data_block="DENSITY" + ) + + @ToolDef.from_fn + def slide(): + return dict( + idname="builtin_brush.slide", + label="Slide", + icon="ops.curves.sculpt_slide", + data_block="SLIDE" + ) + class _defs_gpencil_vertex: @@ -3140,6 +3185,11 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel): _defs_curves_sculpt.delete, _defs_curves_sculpt.snake_hook, _defs_curves_sculpt.grow_shrink, + _defs_curves_sculpt.pinch, + _defs_curves_sculpt.smooth, + _defs_curves_sculpt.puff, + _defs_curves_sculpt.density, + _defs_curves_sculpt.slide, None, *_tools_annotate, ], diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 411bad65991..c2342e8949a 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -535,6 +535,31 @@ class _draw_tool_settings_context_mode: layout.prop(brush, "direction", expand=True, text="") layout.prop(brush, "falloff_shape", expand=True) layout.popover("VIEW3D_PT_tools_brush_falloff") + elif curves_tool == 'PINCH': + layout.prop(brush, "direction", expand=True, text="") + layout.prop(brush, "falloff_shape", expand=True) + layout.popover("VIEW3D_PT_tools_brush_falloff") + elif curves_tool == 'SMOOTH': + layout.prop(brush, "falloff_shape", expand=True) + layout.popover("VIEW3D_PT_tools_brush_falloff") + elif curves_tool == 'PUFF': + layout.prop(brush, "falloff_shape", expand=True) + layout.popover("VIEW3D_PT_tools_brush_falloff") + elif curves_tool == 'DENSITY': + layout.prop(brush, "falloff_shape", expand=True) + row = layout.row(align=True) + row.prop(brush.curves_sculpt_settings, "density_mode", text="", expand=True) + row = layout.row(align=True) + row.prop(brush.curves_sculpt_settings, "minimum_distance") + row.operator_context = 'INVOKE_REGION_WIN' + row.operator("sculpt_curves.min_distance_edit", text="", icon='DRIVER_DISTANCE') + row = layout.row(align=True) + row.enabled = brush.curves_sculpt_settings.density_mode != 'REMOVE' + row.prop(brush.curves_sculpt_settings, "density_add_attempts", text="Max Count") + layout.popover("VIEW3D_PT_tools_brush_falloff") + layout.popover("VIEW3D_PT_curves_sculpt_add_shape", text="Curve Shape") + elif curves_tool == "SLIDE": + layout.popover("VIEW3D_PT_tools_brush_falloff") class VIEW3D_HT_header(Header): @@ -2005,6 +2030,9 @@ class VIEW3D_MT_select_sculpt_curves(Menu): layout.operator("sculpt_curves.select_all", text="All").action = 'SELECT' layout.operator("sculpt_curves.select_all", text="None").action = 'DESELECT' layout.operator("sculpt_curves.select_all", text="Invert").action = 'INVERT' + layout.operator("sculpt_curves.select_random", text="Random") + layout.operator("sculpt_curves.select_end", text="Endpoints") + layout.operator("sculpt_curves.select_grow", text="Grow") class VIEW3D_MT_angle_control(Menu): diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c index 1cda0e8a4bb..dd38af126fe 100644 --- a/source/blender/blenkernel/intern/brush.c +++ b/source/blender/blenkernel/intern/brush.c @@ -1562,6 +1562,7 @@ void BKE_brush_init_curves_sculpt_settings(Brush *brush) settings->points_per_curve = 8; settings->minimum_length = 0.01f; settings->curve_length = 0.3f; + settings->density_add_attempts = 100; } struct Brush *BKE_brush_first_search(struct Main *bmain, const eObjectMode ob_mode) diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index 68df560a389..35b1367ca1e 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -3243,5 +3243,13 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) */ { /* Keep this block, even when empty. */ + + /* Initialize brush curves sculpt settings. */ + LISTBASE_FOREACH (Brush *, brush, &bmain->brushes) { + if (brush->ob_mode != OB_MODE_SCULPT_CURVES) { + continue; + } + brush->curves_sculpt_settings->density_add_attempts = 100; + } } } diff --git a/source/blender/editors/curves/CMakeLists.txt b/source/blender/editors/curves/CMakeLists.txt index 3c31e8014ff..303d2fb71dc 100644 --- a/source/blender/editors/curves/CMakeLists.txt +++ b/source/blender/editors/curves/CMakeLists.txt @@ -8,6 +8,7 @@ set(INC ../../depsgraph ../../functions ../../geometry + ../../gpu ../../makesdna ../../makesrna ../../windowmanager @@ -27,5 +28,17 @@ set(LIB bf_blenlib ) +if(WITH_TBB) + list(APPEND INC_SYS + ${TBB_INCLUDE_DIRS} + ) + add_definitions(-DWITH_TBB) + if(WIN32) + # TBB includes Windows.h which will define min/max macros + # that will collide with the stl versions. + add_definitions(-DNOMINMAX) + endif() +endif() + blender_add_lib(bf_editor_curves "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") add_dependencies(bf_editor_curves bf_rna) diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc index 25bcba6cfb3..dd7edf66920 100644 --- a/source/blender/editors/curves/intern/curves_ops.cc +++ b/source/blender/editors/curves/intern/curves_ops.cc @@ -72,7 +72,7 @@ static bool object_has_editable_curves(const Main &bmain, const Object &object) return true; } -static VectorSet get_unique_editable_curves(const bContext &C) +VectorSet get_unique_editable_curves(const bContext &C) { VectorSet unique_curves; @@ -715,7 +715,7 @@ static void CURVES_OT_snap_curves_to_surface(wmOperatorType *ot) "How to find the point on the surface to attach to"); } -static bool selection_poll(bContext *C) +bool selection_operator_poll(bContext *C) { const Object *object = CTX_data_active_object(C); if (object == nullptr) { @@ -784,7 +784,7 @@ static void CURVES_OT_set_selection_domain(wmOperatorType *ot) ot->description = "Change the mode used for selection masking in curves sculpt mode"; ot->exec = set_selection_domain::curves_set_selection_domain_exec; - ot->poll = selection_poll; + ot->poll = selection_operator_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -820,13 +820,11 @@ static void CURVES_OT_disable_selection(wmOperatorType *ot) ot->description = "Disable the drawing of influence of selection in sculpt mode"; ot->exec = disable_selection::curves_disable_selection_exec; - ot->poll = selection_poll; + ot->poll = selection_operator_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } -namespace select_all { - static bool varray_contains_nonzero(const VArray &data) { bool contains_nonzero = false; @@ -841,6 +839,19 @@ static bool varray_contains_nonzero(const VArray &data) return contains_nonzero; } +bool has_anything_selected(const Curves &curves_id) +{ + const CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); + switch (curves_id.selection_domain) { + case ATTR_DOMAIN_POINT: + return varray_contains_nonzero(curves.selection_point_float()); + case ATTR_DOMAIN_CURVE: + return varray_contains_nonzero(curves.selection_curve_float()); + } + BLI_assert_unreachable(); + return false; +} + static bool any_point_selected(const CurvesGeometry &curves) { return varray_contains_nonzero(curves.selection_point_float()); @@ -856,6 +867,8 @@ static bool any_point_selected(const Span curves_ids) return false; } +namespace select_all { + static void invert_selection(MutableSpan selection) { threading::parallel_for(selection.index_range(), 2048, [&](IndexRange range) { @@ -924,7 +937,7 @@ static void SCULPT_CURVES_OT_select_all(wmOperatorType *ot) ot->description = "(De)select all control points"; ot->exec = select_all::select_all_exec; - ot->poll = selection_poll; + ot->poll = selection_operator_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; diff --git a/source/blender/editors/include/ED_curves.h b/source/blender/editors/include/ED_curves.h index 9233b65b2ce..68e09fd1b12 100644 --- a/source/blender/editors/include/ED_curves.h +++ b/source/blender/editors/include/ED_curves.h @@ -6,6 +6,8 @@ #pragma once +struct bContext; + #ifdef __cplusplus extern "C" { #endif @@ -19,10 +21,14 @@ void ED_operatortypes_curves(void); #ifdef __cplusplus # include "BKE_curves.hh" +# include "BLI_vector_set.hh" namespace blender::ed::curves { bke::CurvesGeometry primitive_random_sphere(int curves_size, int points_per_curve); +bool selection_operator_poll(bContext *C); +bool has_anything_selected(const Curves &curves_id); +VectorSet get_unique_editable_curves(const bContext &C); -} +} // namespace blender::ed::curves #endif diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index 34247b4ef75..edb0f1cda4d 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -32,10 +32,15 @@ set(SRC curves_sculpt_brush.cc curves_sculpt_comb.cc curves_sculpt_delete.cc + curves_sculpt_density.cc curves_sculpt_grow_shrink.cc curves_sculpt_ops.cc - curves_sculpt_selection.cc + curves_sculpt_pinch.cc + curves_sculpt_puff.cc curves_sculpt_selection_paint.cc + curves_sculpt_selection.cc + curves_sculpt_slide.cc + curves_sculpt_smooth.cc curves_sculpt_snake_hook.cc paint_canvas.cc paint_cursor.c diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc new file mode 100644 index 00000000000..536c44ddd44 --- /dev/null +++ b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc @@ -0,0 +1,834 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include + +#include "BKE_brush.h" +#include "BKE_bvhutils.h" +#include "BKE_context.h" +#include "BKE_geometry_set.hh" +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_mesh_sample.hh" + +#include "ED_screen.h" +#include "ED_view3d.h" + +#include "DEG_depsgraph.h" + +#include "BLI_index_mask_ops.hh" +#include "BLI_kdtree.h" +#include "BLI_rand.hh" +#include "BLI_task.hh" + +#include "PIL_time.h" + +#include "GEO_add_curves_on_mesh.hh" + +#include "DNA_brush_types.h" +#include "DNA_mesh_types.h" + +#include "WM_api.h" + +#include "curves_sculpt_intern.hh" + +namespace blender::ed::sculpt_paint { + +class DensityAddOperation : public CurvesSculptStrokeOperation { + private: + /** Used when some data should be interpolated from existing curves. */ + KDTree_3d *curve_roots_kdtree_ = nullptr; + int original_curve_num_ = 0; + + friend struct DensityAddOperationExecutor; + + public: + ~DensityAddOperation() override + { + if (curve_roots_kdtree_ != nullptr) { + BLI_kdtree_3d_free(curve_roots_kdtree_); + } + } + + void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override; +}; + +struct DensityAddOperationExecutor { + DensityAddOperation *self_ = nullptr; + CurvesSculptCommonContext ctx_; + + Object *object_ = nullptr; + Curves *curves_id_ = nullptr; + CurvesGeometry *curves_ = nullptr; + + Object *surface_ob_ = nullptr; + Mesh *surface_ = nullptr; + Span surface_looptris_; + Span corner_normals_su_; + VArray_Span surface_uv_map_; + + const CurvesSculpt *curves_sculpt_ = nullptr; + const Brush *brush_ = nullptr; + const BrushCurvesSculptSettings *brush_settings_ = nullptr; + + float brush_strength_; + float brush_radius_re_; + float2 brush_pos_re_; + + CurvesSculptTransforms transforms_; + + BVHTreeFromMesh surface_bvh_; + + DensityAddOperationExecutor(const bContext &C) : ctx_(C) + { + } + + void execute(DensityAddOperation &self, + const bContext &C, + const StrokeExtension &stroke_extension) + { + self_ = &self; + object_ = CTX_data_active_object(&C); + curves_id_ = static_cast(object_->data); + curves_ = &CurvesGeometry::wrap(curves_id_->geometry); + + if (stroke_extension.is_first) { + self_->original_curve_num_ = curves_->curves_num(); + } + + if (curves_id_->surface == nullptr || curves_id_->surface->type != OB_MESH) { + return; + } + + surface_ob_ = curves_id_->surface; + surface_ = static_cast(surface_ob_->data); + + surface_looptris_ = {BKE_mesh_runtime_looptri_ensure(surface_), + BKE_mesh_runtime_looptri_len(surface_)}; + + transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface); + + if (!CustomData_has_layer(&surface_->ldata, CD_NORMAL)) { + BKE_mesh_calc_normals_split(surface_); + } + corner_normals_su_ = { + reinterpret_cast(CustomData_get_layer(&surface_->ldata, CD_NORMAL)), + surface_->totloop}; + + curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt; + brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint); + brush_settings_ = brush_->curves_sculpt_settings; + brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension); + brush_radius_re_ = brush_radius_get(*ctx_.scene, *brush_, stroke_extension); + brush_pos_re_ = stroke_extension.mouse_position; + + const eBrushFalloffShape falloff_shape = static_cast( + brush_->falloff_shape); + + BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); }); + + Vector new_bary_coords; + Vector new_looptri_indices; + Vector new_positions_cu; + const double time = PIL_check_seconds_timer() * 1000000.0; + RandomNumberGenerator rng{*(uint32_t *)(&time)}; + + /* Find potential new curve root points. */ + if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { + this->sample_projected_with_symmetry( + rng, new_bary_coords, new_looptri_indices, new_positions_cu); + } + else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { + this->sample_spherical_with_symmetry( + rng, new_bary_coords, new_looptri_indices, new_positions_cu); + } + else { + BLI_assert_unreachable(); + } + for (float3 &pos : new_positions_cu) { + pos = transforms_.surface_to_curves * pos; + } + + this->ensure_curve_roots_kdtree(); + + const int already_added_curves = curves_->curves_num() - self_->original_curve_num_; + KDTree_3d *new_roots_kdtree = BLI_kdtree_3d_new(already_added_curves + + new_positions_cu.size()); + BLI_SCOPED_DEFER([&]() { BLI_kdtree_3d_free(new_roots_kdtree); }); + + /* Used to tag all curves that are too close to existing curves or too close to other new + * curves. */ + Array new_curve_skipped(new_positions_cu.size(), false); + threading::parallel_invoke( + /* Build kdtree from root points created by the current stroke. */ + [&]() { + const Span positions_cu = curves_->positions(); + for (const int curve_i : curves_->curves_range().take_back(already_added_curves)) { + const float3 &root_pos_cu = positions_cu[curves_->offsets()[curve_i]]; + BLI_kdtree_3d_insert(new_roots_kdtree, curve_i, root_pos_cu); + } + for (const int new_i : new_positions_cu.index_range()) { + const int index_in_kdtree = curves_->curves_num() + new_i; + const float3 &root_pos_cu = new_positions_cu[new_i]; + BLI_kdtree_3d_insert(new_roots_kdtree, index_in_kdtree, root_pos_cu); + } + BLI_kdtree_3d_balance(new_roots_kdtree); + }, + /* Check which new root points are close to roots that existed before the current stroke + * started. */ + [&]() { + threading::parallel_for( + new_positions_cu.index_range(), 128, [&](const IndexRange range) { + for (const int new_i : range) { + const float3 &new_root_pos_cu = new_positions_cu[new_i]; + KDTreeNearest_3d nearest; + nearest.dist = FLT_MAX; + BLI_kdtree_3d_find_nearest( + self_->curve_roots_kdtree_, new_root_pos_cu, &nearest); + if (nearest.dist < brush_settings_->minimum_distance) { + new_curve_skipped[new_i] = true; + } + } + }); + }); + + /* Find new points that are too close too other new points. */ + for (const int new_i : new_positions_cu.index_range()) { + if (new_curve_skipped[new_i]) { + continue; + } + const float3 &root_pos_cu = new_positions_cu[new_i]; + BLI_kdtree_3d_range_search_cb_cpp( + new_roots_kdtree, + root_pos_cu, + brush_settings_->minimum_distance, + [&](const int other_i, const float *UNUSED(co), float UNUSED(dist_sq)) { + if (other_i < curves_->curves_num()) { + new_curve_skipped[new_i] = true; + return false; + } + const int other_new_i = other_i - curves_->curves_num(); + if (new_i == other_new_i) { + return true; + } + new_curve_skipped[other_new_i] = true; + return true; + }); + } + + /* Remove points that are too close to others. */ + for (int64_t i = new_positions_cu.size() - 1; i >= 0; i--) { + if (new_curve_skipped[i]) { + new_positions_cu.remove_and_reorder(i); + new_bary_coords.remove_and_reorder(i); + new_looptri_indices.remove_and_reorder(i); + } + } + + /* Find UV map. */ + VArray_Span surface_uv_map; + if (curves_id_->surface_uv_map != nullptr) { + MeshComponent surface_component; + surface_component.replace(surface_, GeometryOwnershipType::ReadOnly); + surface_uv_map = surface_component + .attribute_try_get_for_read(curves_id_->surface_uv_map, + ATTR_DOMAIN_CORNER) + .typed(); + } + + /* Find normals. */ + if (!CustomData_has_layer(&surface_->ldata, CD_NORMAL)) { + BKE_mesh_calc_normals_split(surface_); + } + const Span corner_normals_su = { + reinterpret_cast(CustomData_get_layer(&surface_->ldata, CD_NORMAL)), + surface_->totloop}; + + geometry::AddCurvesOnMeshInputs add_inputs; + add_inputs.root_positions_cu = new_positions_cu; + add_inputs.bary_coords = new_bary_coords; + add_inputs.looptri_indices = new_looptri_indices; + add_inputs.interpolate_length = brush_settings_->flag & + BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_LENGTH; + add_inputs.interpolate_shape = brush_settings_->flag & + BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_SHAPE; + add_inputs.interpolate_point_count = brush_settings_->flag & + BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_POINT_COUNT; + add_inputs.fallback_curve_length = brush_settings_->curve_length; + add_inputs.fallback_point_count = std::max(2, brush_settings_->points_per_curve); + add_inputs.surface = surface_; + add_inputs.surface_bvh = &surface_bvh_; + add_inputs.surface_looptris = surface_looptris_; + add_inputs.surface_uv_map = surface_uv_map; + add_inputs.corner_normals_su = corner_normals_su; + add_inputs.curves_to_surface_mat = transforms_.curves_to_surface; + add_inputs.surface_to_curves_normal_mat = transforms_.surface_to_curves_normal; + add_inputs.old_roots_kdtree = self_->curve_roots_kdtree_; + + geometry::add_curves_on_mesh(*curves_, add_inputs); + + DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); + ED_region_tag_redraw(ctx_.region); + } + + void ensure_curve_roots_kdtree() + { + if (self_->curve_roots_kdtree_ == nullptr) { + self_->curve_roots_kdtree_ = BLI_kdtree_3d_new(curves_->curves_num()); + for (const int curve_i : curves_->curves_range()) { + const int root_point_i = curves_->offsets()[curve_i]; + const float3 &root_pos_cu = curves_->positions()[root_point_i]; + BLI_kdtree_3d_insert(self_->curve_roots_kdtree_, curve_i, root_pos_cu); + } + BLI_kdtree_3d_balance(self_->curve_roots_kdtree_); + } + } + + void sample_projected_with_symmetry(RandomNumberGenerator &rng, + Vector &r_bary_coords, + Vector &r_looptri_indices, + Vector &r_positions_su) + { + float4x4 projection; + ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); + + const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( + eCurvesSymmetryType(curves_id_->symmetry)); + for (const float4x4 &brush_transform : symmetry_brush_transforms) { + const float4x4 brush_transform_inv = brush_transform.inverted(); + const float4x4 transform = transforms_.curves_to_surface * brush_transform * + transforms_.world_to_curves; + const int new_points = bke::mesh_surface_sample::sample_surface_points_projected( + rng, + *surface_, + surface_bvh_, + brush_pos_re_, + brush_radius_re_, + [&](const float2 &pos_re, float3 &r_start_su, float3 &r_end_su) { + float3 start_wo, end_wo; + ED_view3d_win_to_segment_clipped( + ctx_.depsgraph, ctx_.region, ctx_.v3d, pos_re, start_wo, end_wo, true); + r_start_su = transform * start_wo; + r_end_su = transform * end_wo; + }, + true, + brush_settings_->density_add_attempts, + brush_settings_->density_add_attempts, + r_bary_coords, + r_looptri_indices, + r_positions_su); + + /* Remove some sampled points randomly based on the brush falloff and strength. */ + const int old_points = r_bary_coords.size() - new_points; + for (int i = r_bary_coords.size() - 1; i >= old_points; i--) { + const float3 pos_su = r_positions_su[i]; + const float3 pos_cu = brush_transform_inv * transforms_.surface_to_curves * pos_su; + float2 pos_re; + ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values); + const float dist_to_brush_re = math::distance(brush_pos_re_, pos_re); + const float radius_falloff = BKE_brush_curve_strength( + brush_, dist_to_brush_re, brush_radius_re_); + const float weight = brush_strength_ * radius_falloff; + if (rng.get_float() > weight) { + r_bary_coords.remove_and_reorder(i); + r_looptri_indices.remove_and_reorder(i); + r_positions_su.remove_and_reorder(i); + } + } + } + } + + void sample_spherical_with_symmetry(RandomNumberGenerator &rng, + Vector &r_bary_coords, + Vector &r_looptri_indices, + Vector &r_positions_su) + { + const std::optional brush_3d = sample_curves_surface_3d_brush(*ctx_.depsgraph, + *ctx_.region, + *ctx_.v3d, + transforms_, + surface_bvh_, + brush_pos_re_, + brush_radius_re_); + if (!brush_3d.has_value()) { + return; + } + + const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( + eCurvesSymmetryType(curves_id_->symmetry)); + for (const float4x4 &brush_transform : symmetry_brush_transforms) { + const float3 brush_pos_cu = brush_transform * brush_3d->position_cu; + const float3 brush_pos_su = transforms_.curves_to_surface * brush_pos_cu; + const float brush_radius_su = transform_brush_radius( + transforms_.curves_to_surface, brush_pos_cu, brush_3d->radius_cu); + const float brush_radius_sq_su = pow2f(brush_radius_su); + + Vector looptri_indices; + BLI_bvhtree_range_query_cpp( + *surface_bvh_.tree, + brush_pos_su, + brush_radius_su, + [&](const int index, const float3 &UNUSED(co), const float UNUSED(dist_sq)) { + looptri_indices.append(index); + }); + + const float brush_plane_area_su = M_PI * brush_radius_sq_su; + const float approximate_density_su = brush_settings_->density_add_attempts / + brush_plane_area_su; + + const int new_points = bke::mesh_surface_sample::sample_surface_points_spherical( + rng, + *surface_, + looptri_indices, + brush_pos_su, + brush_radius_su, + approximate_density_su, + r_bary_coords, + r_looptri_indices, + r_positions_su); + + /* Remove some sampled points randomly based on the brush falloff and strength. */ + const int old_points = r_bary_coords.size() - new_points; + for (int i = r_bary_coords.size() - 1; i >= old_points; i--) { + const float3 pos_su = r_positions_su[i]; + const float3 pos_cu = transforms_.surface_to_curves * pos_su; + const float dist_to_brush_cu = math::distance(pos_cu, brush_pos_cu); + const float radius_falloff = BKE_brush_curve_strength( + brush_, dist_to_brush_cu, brush_3d->radius_cu); + const float weight = brush_strength_ * radius_falloff; + if (rng.get_float() > weight) { + r_bary_coords.remove_and_reorder(i); + r_looptri_indices.remove_and_reorder(i); + r_positions_su.remove_and_reorder(i); + } + } + } + } +}; + +void DensityAddOperation::on_stroke_extended(const bContext &C, + const StrokeExtension &stroke_extension) +{ + DensityAddOperationExecutor executor{C}; + executor.execute(*this, C, stroke_extension); +} + +class DensitySubtractOperation : public CurvesSculptStrokeOperation { + private: + /** Only used when a 3D brush is used. */ + CurvesBrush3D brush_3d_; + + friend struct DensitySubtractOperationExecutor; + + public: + void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override; +}; + +/** + * Utility class that actually executes the update when the stroke is updated. That's useful + * because it avoids passing a very large number of parameters between functions. + */ +struct DensitySubtractOperationExecutor { + DensitySubtractOperation *self_ = nullptr; + CurvesSculptCommonContext ctx_; + + Object *object_ = nullptr; + Curves *curves_id_ = nullptr; + CurvesGeometry *curves_ = nullptr; + + Vector selected_curve_indices_; + IndexMask curve_selection_; + + Object *surface_ob_ = nullptr; + Mesh *surface_ = nullptr; + + const CurvesSculpt *curves_sculpt_ = nullptr; + const Brush *brush_ = nullptr; + float brush_radius_base_re_; + float brush_radius_factor_; + float brush_strength_; + float2 brush_pos_re_; + + float minimum_distance_; + + CurvesSculptTransforms transforms_; + BVHTreeFromMesh surface_bvh_; + + KDTree_3d *root_points_kdtree_; + + DensitySubtractOperationExecutor(const bContext &C) : ctx_(C) + { + } + + void execute(DensitySubtractOperation &self, + const bContext &C, + const StrokeExtension &stroke_extension) + { + self_ = &self; + + object_ = CTX_data_active_object(&C); + + curves_id_ = static_cast(object_->data); + curves_ = &CurvesGeometry::wrap(curves_id_->geometry); + if (curves_->curves_num() == 0) { + return; + } + + surface_ob_ = curves_id_->surface; + if (surface_ob_ == nullptr) { + return; + } + surface_ = static_cast(surface_ob_->data); + + curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt; + brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint); + brush_radius_base_re_ = BKE_brush_size_get(ctx_.scene, brush_); + brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension); + brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension); + brush_pos_re_ = stroke_extension.mouse_position; + + minimum_distance_ = brush_->curves_sculpt_settings->minimum_distance; + + curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_); + + transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface); + const eBrushFalloffShape falloff_shape = static_cast( + brush_->falloff_shape); + BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); }); + + const Span positions_cu = curves_->positions(); + + root_points_kdtree_ = BLI_kdtree_3d_new(curve_selection_.size()); + BLI_SCOPED_DEFER([&]() { BLI_kdtree_3d_free(root_points_kdtree_); }); + for (const int curve_i : curve_selection_) { + const int first_point_i = curves_->offsets()[curve_i]; + const float3 &pos_cu = positions_cu[first_point_i]; + BLI_kdtree_3d_insert(root_points_kdtree_, curve_i, pos_cu); + } + BLI_kdtree_3d_balance(root_points_kdtree_); + + /* Find all curves that should be deleted. */ + Array curves_to_delete(curves_->curves_num(), false); + if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { + this->reduce_density_projected_with_symmetry(curves_to_delete); + } + else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { + this->reduce_density_spherical_with_symmetry(curves_to_delete); + } + else { + BLI_assert_unreachable(); + } + + Vector indices; + const IndexMask mask = index_mask_ops::find_indices_based_on_predicate( + curves_->curves_range(), 4096, indices, [&](const int curve_i) { + return curves_to_delete[curve_i]; + }); + + curves_->remove_curves(mask); + + DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); + ED_region_tag_redraw(ctx_.region); + } + + void reduce_density_projected_with_symmetry(MutableSpan curves_to_delete) + { + const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( + eCurvesSymmetryType(curves_id_->symmetry)); + for (const float4x4 &brush_transform : symmetry_brush_transforms) { + this->reduce_density_projected(brush_transform, curves_to_delete); + } + } + + void reduce_density_projected(const float4x4 &brush_transform, + MutableSpan curves_to_delete) + { + const Span positions_cu = curves_->positions(); + const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_; + const float brush_radius_sq_re = pow2f(brush_radius_re); + + float4x4 projection; + ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); + + const Span offsets = curves_->offsets(); + + /* Randomly select the curves that are allowed to be removed, based on the brush radius and + * strength. */ + Array allow_remove_curve(curves_->curves_num(), false); + threading::parallel_for(curves_->curves_range(), 512, [&](const IndexRange range) { + RandomNumberGenerator rng((int)(PIL_check_seconds_timer() * 1000000.0)); + + for (const int curve_i : range) { + if (curves_to_delete[curve_i]) { + allow_remove_curve[curve_i] = true; + continue; + } + const int first_point_i = offsets[curve_i]; + const float3 pos_cu = brush_transform * positions_cu[first_point_i]; + + float2 pos_re; + ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values); + const float dist_to_brush_sq_re = math::distance_squared(brush_pos_re_, pos_re); + if (dist_to_brush_sq_re > brush_radius_sq_re) { + continue; + } + const float dist_to_brush_re = std::sqrt(dist_to_brush_sq_re); + const float radius_falloff = BKE_brush_curve_strength( + brush_, dist_to_brush_re, brush_radius_re); + const float weight = brush_strength_ * radius_falloff; + if (rng.get_float() < weight) { + allow_remove_curve[curve_i] = true; + } + } + }); + + /* Detect curves that are too close to other existing curves. */ + for (const int curve_i : curve_selection_) { + if (curves_to_delete[curve_i]) { + continue; + } + if (!allow_remove_curve[curve_i]) { + continue; + } + const int first_point_i = offsets[curve_i]; + const float3 orig_pos_cu = positions_cu[first_point_i]; + const float3 pos_cu = brush_transform * orig_pos_cu; + float2 pos_re; + ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values); + const float dist_to_brush_sq_re = math::distance_squared(brush_pos_re_, pos_re); + if (dist_to_brush_sq_re > brush_radius_sq_re) { + continue; + } + BLI_kdtree_3d_range_search_cb_cpp( + root_points_kdtree_, + orig_pos_cu, + minimum_distance_, + [&](const int other_curve_i, const float *UNUSED(co), float UNUSED(dist_sq)) { + if (other_curve_i == curve_i) { + return true; + } + if (allow_remove_curve[other_curve_i]) { + curves_to_delete[other_curve_i] = true; + } + return true; + }); + } + } + + void reduce_density_spherical_with_symmetry(MutableSpan curves_to_delete) + { + const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_; + const std::optional brush_3d = sample_curves_surface_3d_brush(*ctx_.depsgraph, + *ctx_.region, + *ctx_.v3d, + transforms_, + surface_bvh_, + brush_pos_re_, + brush_radius_re); + if (!brush_3d.has_value()) { + return; + } + + const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( + eCurvesSymmetryType(curves_id_->symmetry)); + for (const float4x4 &brush_transform : symmetry_brush_transforms) { + const float3 brush_pos_cu = brush_transform * brush_3d->position_cu; + this->reduce_density_spherical(brush_pos_cu, brush_3d->radius_cu, curves_to_delete); + } + } + + void reduce_density_spherical(const float3 &brush_pos_cu, + const float brush_radius_cu, + MutableSpan curves_to_delete) + { + const float brush_radius_sq_cu = pow2f(brush_radius_cu); + const Span positions_cu = curves_->positions(); + const Span offsets = curves_->offsets(); + + /* Randomly select the curves that are allowed to be removed, based on the brush radius and + * strength. */ + Array allow_remove_curve(curves_->curves_num(), false); + threading::parallel_for(curves_->curves_range(), 512, [&](const IndexRange range) { + RandomNumberGenerator rng((int)(PIL_check_seconds_timer() * 1000000.0)); + + for (const int curve_i : range) { + if (curves_to_delete[curve_i]) { + allow_remove_curve[curve_i] = true; + continue; + } + const int first_point_i = offsets[curve_i]; + const float3 pos_cu = positions_cu[first_point_i]; + + const float dist_to_brush_sq_cu = math::distance_squared(brush_pos_cu, pos_cu); + if (dist_to_brush_sq_cu > brush_radius_sq_cu) { + continue; + } + const float dist_to_brush_cu = std::sqrt(dist_to_brush_sq_cu); + const float radius_falloff = BKE_brush_curve_strength( + brush_, dist_to_brush_cu, brush_radius_cu); + const float weight = brush_strength_ * radius_falloff; + if (rng.get_float() < weight) { + allow_remove_curve[curve_i] = true; + } + } + }); + + /* Detect curves that are too close to other existing curves. */ + for (const int curve_i : curve_selection_) { + if (curves_to_delete[curve_i]) { + continue; + } + if (!allow_remove_curve[curve_i]) { + continue; + } + const int first_point_i = offsets[curve_i]; + const float3 &pos_cu = positions_cu[first_point_i]; + const float dist_to_brush_sq_cu = math::distance_squared(pos_cu, brush_pos_cu); + if (dist_to_brush_sq_cu > brush_radius_sq_cu) { + continue; + } + + BLI_kdtree_3d_range_search_cb_cpp( + root_points_kdtree_, + pos_cu, + minimum_distance_, + [&](const int other_curve_i, const float *UNUSED(co), float UNUSED(dist_sq)) { + if (other_curve_i == curve_i) { + return true; + } + if (allow_remove_curve[other_curve_i]) { + curves_to_delete[other_curve_i] = true; + } + return true; + }); + } + } +}; + +void DensitySubtractOperation::on_stroke_extended(const bContext &C, + const StrokeExtension &stroke_extension) +{ + DensitySubtractOperationExecutor executor{C}; + executor.execute(*this, C, stroke_extension); +} + +/** + * Detects whether the brush should be in Add or Subtract mode. + */ +static bool use_add_density_mode(const BrushStrokeMode brush_mode, + const bContext &C, + const StrokeExtension &stroke_start) +{ + const Scene &scene = *CTX_data_scene(&C); + const Brush &brush = *BKE_paint_brush_for_read(&scene.toolsettings->curves_sculpt->paint); + const eBrushCurvesSculptDensityMode density_mode = static_cast( + brush.curves_sculpt_settings->density_mode); + const bool use_invert = brush_mode == BRUSH_STROKE_INVERT; + + if (density_mode == BRUSH_CURVES_SCULPT_DENSITY_MODE_ADD) { + return !use_invert; + } + if (density_mode == BRUSH_CURVES_SCULPT_DENSITY_MODE_REMOVE) { + return use_invert; + } + + const Object &curves_ob = *CTX_data_active_object(&C); + const Curves &curves_id = *static_cast(curves_ob.data); + const CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); + if (curves_id.surface == nullptr) { + /* The brush won't do anything in this case anyway. */ + return true; + } + if (curves.curves_num() <= 1) { + return true; + } + + const CurvesSculptTransforms transforms(curves_ob, curves_id.surface); + BVHTreeFromMesh surface_bvh; + BKE_bvhtree_from_mesh_get( + &surface_bvh, static_cast(curves_id.surface->data), BVHTREE_FROM_LOOPTRI, 2); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh); }); + + const Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(&C); + const ARegion ®ion = *CTX_wm_region(&C); + const View3D &v3d = *CTX_wm_view3d(&C); + + const float2 brush_pos_re = stroke_start.mouse_position; + /* Reduce radius so that only an inner circle is used to determine the existing density. */ + const float brush_radius_re = BKE_brush_size_get(&scene, &brush) * 0.5f; + + /* Find the surface point under the brush. */ + const std::optional brush_3d = sample_curves_surface_3d_brush( + depsgraph, region, v3d, transforms, surface_bvh, brush_pos_re, brush_radius_re); + if (!brush_3d.has_value()) { + return true; + } + + const float3 brush_pos_cu = brush_3d->position_cu; + const float brush_radius_cu = brush_3d->radius_cu; + const float brush_radius_sq_cu = pow2f(brush_radius_cu); + + const Span offsets = curves.offsets(); + const Span positions_cu = curves.positions(); + + /* Compute distance from brush to curve roots. */ + Array> distances_sq_to_brush(curves.curves_num()); + threading::EnumerableThreadSpecific valid_curve_count_by_thread; + threading::parallel_for(curves.curves_range(), 512, [&](const IndexRange range) { + int &valid_curve_count = valid_curve_count_by_thread.local(); + for (const int curve_i : range) { + const int root_point_i = offsets[curve_i]; + const float3 &root_pos_cu = positions_cu[root_point_i]; + const float dist_sq_cu = math::distance_squared(root_pos_cu, brush_pos_cu); + if (dist_sq_cu < brush_radius_sq_cu) { + distances_sq_to_brush[curve_i] = {math::distance_squared(root_pos_cu, brush_pos_cu), + curve_i}; + valid_curve_count++; + } + else { + distances_sq_to_brush[curve_i] = {FLT_MAX, -1}; + } + } + }); + const int valid_curve_count = std::accumulate( + valid_curve_count_by_thread.begin(), valid_curve_count_by_thread.end(), 0); + + /* Find a couple of curves that are closest to the brush center. */ + const int check_curve_count = std::min(8, valid_curve_count); + std::partial_sort(distances_sq_to_brush.begin(), + distances_sq_to_brush.begin() + check_curve_count, + distances_sq_to_brush.end()); + + /* Compute the minimum pair-wise distance between the curve roots that are close to the brush + * center. */ + float min_dist_sq_cu = FLT_MAX; + for (const int i : IndexRange(check_curve_count)) { + const float3 &pos_i = positions_cu[offsets[distances_sq_to_brush[i].second]]; + for (int j = i + 1; j < check_curve_count; j++) { + const float3 &pos_j = positions_cu[offsets[distances_sq_to_brush[j].second]]; + const float dist_sq_cu = math::distance_squared(pos_i, pos_j); + math::min_inplace(min_dist_sq_cu, dist_sq_cu); + } + } + + const float min_dist_cu = std::sqrt(min_dist_sq_cu); + if (min_dist_cu > brush.curves_sculpt_settings->minimum_distance) { + return true; + } + + return false; +} + +std::unique_ptr new_density_operation( + const BrushStrokeMode brush_mode, const bContext &C, const StrokeExtension &stroke_start) +{ + if (use_add_density_mode(brush_mode, C, stroke_start)) { + return std::make_unique(); + } + return std::make_unique(); +} + +} // namespace blender::ed::sculpt_paint diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh index ad3871bee45..b26649e746b 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh +++ b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh @@ -66,7 +66,7 @@ std::unique_ptr new_pinch_operation(const BrushStro std::unique_ptr new_smooth_operation(); std::unique_ptr new_puff_operation(); std::unique_ptr new_density_operation( - const BrushStrokeMode brush_mode, const bContext &C); + const BrushStrokeMode brush_mode, const bContext &C, const StrokeExtension &stroke_start); std::unique_ptr new_slide_operation(); struct CurvesBrush3D { diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc index e6da2039433..739c39f6196 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc @@ -1,8 +1,12 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "BLI_kdtree.h" +#include "BLI_rand.hh" #include "BLI_utildefines.h" +#include "BLI_vector_set.hh" #include "BKE_brush.h" +#include "BKE_bvhutils.h" #include "BKE_context.h" #include "BKE_curves.hh" #include "BKE_paint.h" @@ -11,10 +15,12 @@ #include "WM_message.h" #include "WM_toolsystem.h" +#include "ED_curves.h" #include "ED_curves_sculpt.h" #include "ED_image.h" #include "ED_object.h" #include "ED_screen.h" +#include "ED_space_api.h" #include "ED_view3d.h" #include "DEG_depsgraph.h" @@ -24,11 +30,21 @@ #include "DNA_screen_types.h" #include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_enum_types.h" #include "curves_sculpt_intern.h" #include "curves_sculpt_intern.hh" #include "paint_intern.h" +#include "UI_interface.h" +#include "UI_resources.h" + +#include "GPU_immediate.h" +#include "GPU_immediate_util.h" +#include "GPU_matrix.h" +#include "GPU_state.h" + /* -------------------------------------------------------------------- */ /** \name Poll Functions * \{ */ @@ -90,8 +106,8 @@ float brush_strength_get(const Scene &scene, return BKE_brush_alpha_get(&scene, &brush) * brush_strength_factor(brush, stroke_extension); } -static std::unique_ptr start_brush_operation(bContext &C, - wmOperator &op) +static std::unique_ptr start_brush_operation( + bContext &C, wmOperator &op, const StrokeExtension &stroke_start) { const BrushStrokeMode mode = static_cast(RNA_enum_get(op.ptr, "mode")); @@ -111,6 +127,16 @@ static std::unique_ptr start_brush_operation(bConte return new_grow_shrink_operation(mode, C); case CURVES_SCULPT_TOOL_SELECTION_PAINT: return new_selection_paint_operation(mode, C); + case CURVES_SCULPT_TOOL_PINCH: + return new_pinch_operation(mode, C); + case CURVES_SCULPT_TOOL_SMOOTH: + return new_smooth_operation(); + case CURVES_SCULPT_TOOL_PUFF: + return new_puff_operation(); + case CURVES_SCULPT_TOOL_DENSITY: + return new_density_operation(mode, C, stroke_start); + case CURVES_SCULPT_TOOL_SLIDE: + return new_slide_operation(); } BLI_assert_unreachable(); return {}; @@ -150,7 +176,7 @@ static void stroke_update_step(bContext *C, if (!op_data->operation) { stroke_extension.is_first = true; - op_data->operation = start_brush_operation(*C, *op); + op_data->operation = start_brush_operation(*C, *op, stroke_extension); } else { stroke_extension.is_first = false; @@ -316,6 +342,934 @@ static void CURVES_OT_sculptmode_toggle(wmOperatorType *ot) /** \} */ +namespace select_random { + +static int select_random_exec(bContext *C, wmOperator *op) +{ + VectorSet unique_curves = curves::get_unique_editable_curves(*C); + + const int seed = RNA_int_get(op->ptr, "seed"); + RandomNumberGenerator rng{static_cast(seed)}; + + const bool partial = RNA_boolean_get(op->ptr, "partial"); + const bool constant_per_curve = RNA_boolean_get(op->ptr, "constant_per_curve"); + const float probability = RNA_float_get(op->ptr, "probability"); + const float min_value = RNA_float_get(op->ptr, "min"); + const auto next_partial_random_value = [&]() { + return rng.get_float() * (1.0f - min_value) + min_value; + }; + const auto next_bool_random_value = [&]() { return rng.get_float() <= probability; }; + + for (Curves *curves_id : unique_curves) { + CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry); + const bool was_anything_selected = curves::has_anything_selected(*curves_id); + switch (curves_id->selection_domain) { + case ATTR_DOMAIN_POINT: { + MutableSpan selection = curves.selection_point_float_for_write(); + if (!was_anything_selected) { + selection.fill(1.0f); + } + if (partial) { + if (constant_per_curve) { + for (const int curve_i : curves.curves_range()) { + const float random_value = next_partial_random_value(); + const IndexRange points = curves.points_for_curve(curve_i); + for (const int point_i : points) { + selection[point_i] *= random_value; + } + } + } + else { + for (const int point_i : selection.index_range()) { + const float random_value = next_partial_random_value(); + selection[point_i] *= random_value; + } + } + } + else { + if (constant_per_curve) { + for (const int curve_i : curves.curves_range()) { + const bool random_value = next_bool_random_value(); + const IndexRange points = curves.points_for_curve(curve_i); + if (!random_value) { + selection.slice(points).fill(0.0f); + } + } + } + else { + for (const int point_i : selection.index_range()) { + const bool random_value = next_bool_random_value(); + if (!random_value) { + selection[point_i] = 0.0f; + } + } + } + } + break; + } + case ATTR_DOMAIN_CURVE: { + MutableSpan selection = curves.selection_curve_float_for_write(); + if (!was_anything_selected) { + selection.fill(1.0f); + } + if (partial) { + for (const int curve_i : curves.curves_range()) { + const float random_value = next_partial_random_value(); + selection[curve_i] *= random_value; + } + } + else { + for (const int curve_i : curves.curves_range()) { + const bool random_value = next_bool_random_value(); + if (!random_value) { + selection[curve_i] = 0.0f; + } + } + } + break; + } + } + MutableSpan selection = curves_id->selection_domain == ATTR_DOMAIN_POINT ? + curves.selection_point_float_for_write() : + curves.selection_curve_float_for_write(); + const bool was_any_selected = std::any_of( + selection.begin(), selection.end(), [](const float v) { return v > 0.0f; }); + if (was_any_selected) { + for (float &v : selection) { + v *= rng.get_float(); + } + } + else { + for (float &v : selection) { + v = rng.get_float(); + } + } + + /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic + * attribute for now. */ + DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id); + } + return OPERATOR_FINISHED; +} + +static void select_random_ui(bContext *UNUSED(C), wmOperator *op) +{ + uiLayout *layout = op->layout; + + uiItemR(layout, op->ptr, "seed", 0, nullptr, ICON_NONE); + uiItemR(layout, op->ptr, "constant_per_curve", 0, nullptr, ICON_NONE); + uiItemR(layout, op->ptr, "partial", 0, nullptr, ICON_NONE); + + if (RNA_boolean_get(op->ptr, "partial")) { + uiItemR(layout, op->ptr, "min", UI_ITEM_R_SLIDER, "Min", ICON_NONE); + } + else { + uiItemR(layout, op->ptr, "probability", UI_ITEM_R_SLIDER, "Probability", ICON_NONE); + } +} + +} // namespace select_random + +static void SCULPT_CURVES_OT_select_random(wmOperatorType *ot) +{ + ot->name = "Select Random"; + ot->idname = __func__; + ot->description = "Randomizes existing selection or create new random selection"; + + ot->exec = select_random::select_random_exec; + ot->poll = curves::selection_operator_poll; + ot->ui = select_random::select_random_ui; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_int(ot->srna, + "seed", + 0, + INT32_MIN, + INT32_MAX, + "Seed", + "Source of randomness", + INT32_MIN, + INT32_MAX); + RNA_def_boolean( + ot->srna, "partial", false, "Partial", "Allow points or curves to be selected partially"); + RNA_def_float(ot->srna, + "probability", + 0.5f, + 0.0f, + 1.0f, + "Probability", + "Chance of every point or curve being included in the selection", + 0.0f, + 1.0f); + RNA_def_float(ot->srna, + "min", + 0.0f, + 0.0f, + 1.0f, + "Min", + "Minimum value for the random selection", + 0.0f, + 1.0f); + RNA_def_boolean(ot->srna, + "constant_per_curve", + true, + "Constant per Curve", + "The generated random number is the same for every control point of a curve"); +} + +namespace select_end { +static bool select_end_poll(bContext *C) +{ + if (!curves::selection_operator_poll(C)) { + return false; + } + const Curves *curves_id = static_cast(CTX_data_active_object(C)->data); + if (curves_id->selection_domain != ATTR_DOMAIN_POINT) { + CTX_wm_operator_poll_msg_set(C, "Only available in point selection mode"); + return false; + } + return true; +} + +static int select_end_exec(bContext *C, wmOperator *op) +{ + VectorSet unique_curves = curves::get_unique_editable_curves(*C); + const bool end_points = RNA_boolean_get(op->ptr, "end_points"); + const int amount = RNA_int_get(op->ptr, "amount"); + + for (Curves *curves_id : unique_curves) { + CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry); + const bool was_anything_selected = curves::has_anything_selected(*curves_id); + MutableSpan selection = curves.selection_point_float_for_write(); + if (!was_anything_selected) { + selection.fill(1.0f); + } + threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange range) { + for (const int curve_i : range) { + const IndexRange points = curves.points_for_curve(curve_i); + if (end_points) { + selection.slice(points.drop_back(amount)).fill(0.0f); + } + else { + selection.slice(points.drop_front(amount)).fill(0.0f); + } + } + }); + + /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic + * attribute for now. */ + DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id); + } + + return OPERATOR_FINISHED; +} +} // namespace select_end + +static void SCULPT_CURVES_OT_select_end(wmOperatorType *ot) +{ + ot->name = "Select End"; + ot->idname = __func__; + ot->description = "Select end points of curves"; + + ot->exec = select_end::select_end_exec; + ot->poll = select_end::select_end_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean(ot->srna, + "end_points", + true, + "End Points", + "Select points at the end of the curve as opposed to the beginning"); + RNA_def_int( + ot->srna, "amount", 1, 0, INT32_MAX, "Amount", "Number of points to select", 0, INT32_MAX); +} + +namespace select_grow { + +struct GrowOperatorDataPerCurve : NonCopyable, NonMovable { + Curves *curves_id; + Vector selected_point_indices; + Vector unselected_point_indices; + Array distances_to_selected; + Array distances_to_unselected; + + Array original_selection; + float pixel_to_distance_factor; +}; + +struct GrowOperatorData { + int initial_mouse_x; + Vector> per_curve; +}; + +static void update_points_selection(const GrowOperatorDataPerCurve &data, + const float distance, + MutableSpan points_selection) +{ + if (distance > 0.0f) { + threading::parallel_for( + data.unselected_point_indices.index_range(), 256, [&](const IndexRange range) { + for (const int i : range) { + const int point_i = data.unselected_point_indices[i]; + const float distance_to_selected = data.distances_to_selected[i]; + const float selection = distance_to_selected <= distance ? 1.0f : 0.0f; + points_selection[point_i] = selection; + } + }); + threading::parallel_for( + data.selected_point_indices.index_range(), 512, [&](const IndexRange range) { + for (const int point_i : data.selected_point_indices.as_span().slice(range)) { + points_selection[point_i] = 1.0f; + } + }); + } + else { + threading::parallel_for( + data.selected_point_indices.index_range(), 256, [&](const IndexRange range) { + for (const int i : range) { + const int point_i = data.selected_point_indices[i]; + const float distance_to_unselected = data.distances_to_unselected[i]; + const float selection = distance_to_unselected <= -distance ? 0.0f : 1.0f; + points_selection[point_i] = selection; + } + }); + threading::parallel_for( + data.unselected_point_indices.index_range(), 512, [&](const IndexRange range) { + for (const int point_i : data.unselected_point_indices.as_span().slice(range)) { + points_selection[point_i] = 0.0f; + } + }); + } +} + +static int select_grow_update(bContext *C, wmOperator *op, const float mouse_diff_x) +{ + GrowOperatorData &op_data = *static_cast(op->customdata); + + for (std::unique_ptr &curve_op_data : op_data.per_curve) { + Curves &curves_id = *curve_op_data->curves_id; + CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); + const float distance = curve_op_data->pixel_to_distance_factor * mouse_diff_x; + + /* Grow or shrink selection based on precomputed distances. */ + switch (curves_id.selection_domain) { + case ATTR_DOMAIN_POINT: { + MutableSpan points_selection = curves.selection_point_float_for_write(); + update_points_selection(*curve_op_data, distance, points_selection); + break; + } + case ATTR_DOMAIN_CURVE: { + Array new_points_selection(curves.points_num()); + update_points_selection(*curve_op_data, distance, new_points_selection); + /* Propagate grown point selection to the curve selection. */ + MutableSpan curves_selection = curves.selection_curve_float_for_write(); + for (const int curve_i : curves.curves_range()) { + const IndexRange points = curves.points_for_curve(curve_i); + const Span points_selection = new_points_selection.as_span().slice(points); + const float max_selection = *std::max_element(points_selection.begin(), + points_selection.end()); + curves_selection[curve_i] = max_selection; + } + break; + } + } + + /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic + * attribute for now. */ + DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, &curves_id); + } + + return OPERATOR_FINISHED; +} + +static void select_grow_invoke_per_curve(Curves &curves_id, + Object &curves_ob, + const ARegion ®ion, + const View3D &v3d, + const RegionView3D &rv3d, + GrowOperatorDataPerCurve &curve_op_data) +{ + curve_op_data.curves_id = &curves_id; + CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); + const Span positions = curves.positions(); + + /* Find indices of selected and unselected points. */ + switch (curves_id.selection_domain) { + case ATTR_DOMAIN_POINT: { + const VArray points_selection = curves.selection_point_float(); + curve_op_data.original_selection.reinitialize(points_selection.size()); + points_selection.materialize(curve_op_data.original_selection); + for (const int point_i : points_selection.index_range()) { + const float point_selection = points_selection[point_i]; + if (point_selection > 0.0f) { + curve_op_data.selected_point_indices.append(point_i); + } + else { + curve_op_data.unselected_point_indices.append(point_i); + } + } + + break; + } + case ATTR_DOMAIN_CURVE: { + const VArray curves_selection = curves.selection_curve_float(); + curve_op_data.original_selection.reinitialize(curves_selection.size()); + curves_selection.materialize(curve_op_data.original_selection); + for (const int curve_i : curves_selection.index_range()) { + const float curve_selection = curves_selection[curve_i]; + const IndexRange points = curves.points_for_curve(curve_i); + if (curve_selection > 0.0f) { + for (const int point_i : points) { + curve_op_data.selected_point_indices.append(point_i); + } + } + else { + for (const int point_i : points) { + curve_op_data.unselected_point_indices.append(point_i); + } + } + } + break; + } + } + + threading::parallel_invoke( + [&]() { + /* Build kd-tree for the selected points. */ + KDTree_3d *kdtree = BLI_kdtree_3d_new(curve_op_data.selected_point_indices.size()); + BLI_SCOPED_DEFER([&]() { BLI_kdtree_3d_free(kdtree); }); + for (const int point_i : curve_op_data.selected_point_indices) { + const float3 &position = positions[point_i]; + BLI_kdtree_3d_insert(kdtree, point_i, position); + } + BLI_kdtree_3d_balance(kdtree); + + /* For each unselected point, compute the distance to the closest selected point. */ + curve_op_data.distances_to_selected.reinitialize( + curve_op_data.unselected_point_indices.size()); + threading::parallel_for(curve_op_data.unselected_point_indices.index_range(), + 256, + [&](const IndexRange range) { + for (const int i : range) { + const int point_i = curve_op_data.unselected_point_indices[i]; + const float3 &position = positions[point_i]; + KDTreeNearest_3d nearest; + BLI_kdtree_3d_find_nearest(kdtree, position, &nearest); + curve_op_data.distances_to_selected[i] = nearest.dist; + } + }); + }, + [&]() { + /* Build kd-tree for the unselected points. */ + KDTree_3d *kdtree = BLI_kdtree_3d_new(curve_op_data.unselected_point_indices.size()); + BLI_SCOPED_DEFER([&]() { BLI_kdtree_3d_free(kdtree); }); + for (const int point_i : curve_op_data.unselected_point_indices) { + const float3 &position = positions[point_i]; + BLI_kdtree_3d_insert(kdtree, point_i, position); + } + BLI_kdtree_3d_balance(kdtree); + + /* For each selected point, compute the distance to the closest unselected point. */ + curve_op_data.distances_to_unselected.reinitialize( + curve_op_data.selected_point_indices.size()); + threading::parallel_for( + curve_op_data.selected_point_indices.index_range(), 256, [&](const IndexRange range) { + for (const int i : range) { + const int point_i = curve_op_data.selected_point_indices[i]; + const float3 &position = positions[point_i]; + KDTreeNearest_3d nearest; + BLI_kdtree_3d_find_nearest(kdtree, position, &nearest); + curve_op_data.distances_to_unselected[i] = nearest.dist; + } + }); + }); + + float4x4 curves_to_world_mat = curves_ob.obmat; + float4x4 world_to_curves_mat = curves_to_world_mat.inverted(); + + float4x4 projection; + ED_view3d_ob_project_mat_get(&rv3d, &curves_ob, projection.values); + + /* Compute how mouse movements in screen space are converted into grow/shrink distances in + * object space. */ + curve_op_data.pixel_to_distance_factor = threading::parallel_reduce( + curve_op_data.selected_point_indices.index_range(), + 256, + FLT_MAX, + [&](const IndexRange range, float pixel_to_distance_factor) { + for (const int i : range) { + const int point_i = curve_op_data.selected_point_indices[i]; + const float3 &pos_cu = positions[point_i]; + + float2 pos_re; + ED_view3d_project_float_v2_m4(®ion, pos_cu, pos_re, projection.values); + if (pos_re.x < 0 || pos_re.y < 0 || pos_re.x > region.winx || pos_re.y > region.winy) { + continue; + } + /* Compute how far this point moves in curve space when it moves one unit in screen + * space. */ + const float2 pos_offset_re = pos_re + float2(1, 0); + float3 pos_offset_wo; + ED_view3d_win_to_3d( + &v3d, ®ion, curves_to_world_mat * pos_cu, pos_offset_re, pos_offset_wo); + const float3 pos_offset_cu = world_to_curves_mat * pos_offset_wo; + const float dist_cu = math::distance(pos_cu, pos_offset_cu); + const float dist_re = math::distance(pos_re, pos_offset_re); + const float factor = dist_cu / dist_re; + math::min_inplace(pixel_to_distance_factor, factor); + } + return pixel_to_distance_factor; + }, + [](const float a, const float b) { return std::min(a, b); }); +} + +static int select_grow_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + Object *active_ob = CTX_data_active_object(C); + ARegion *region = CTX_wm_region(C); + View3D *v3d = CTX_wm_view3d(C); + RegionView3D *rv3d = CTX_wm_region_view3d(C); + + GrowOperatorData *op_data = MEM_new(__func__); + op->customdata = op_data; + + op_data->initial_mouse_x = event->xy[0]; + + Curves &curves_id = *static_cast(active_ob->data); + auto curve_op_data = std::make_unique(); + select_grow_invoke_per_curve(curves_id, *active_ob, *region, *v3d, *rv3d, *curve_op_data); + op_data->per_curve.append(std::move(curve_op_data)); + + WM_event_add_modal_handler(C, op); + return OPERATOR_RUNNING_MODAL; +} + +static int select_grow_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + GrowOperatorData &op_data = *static_cast(op->customdata); + const int mouse_x = event->xy[0]; + const int mouse_diff_x = mouse_x - op_data.initial_mouse_x; + switch (event->type) { + case MOUSEMOVE: { + select_grow_update(C, op, mouse_diff_x); + break; + } + case LEFTMOUSE: { + MEM_delete(&op_data); + return OPERATOR_FINISHED; + } + case EVT_ESCKEY: + case RIGHTMOUSE: { + /* Undo operator by resetting the selection to the original value. */ + for (std::unique_ptr &curve_op_data : op_data.per_curve) { + Curves &curves_id = *curve_op_data->curves_id; + CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); + switch (curves_id.selection_domain) { + case ATTR_DOMAIN_POINT: { + MutableSpan points_selection = curves.selection_point_float_for_write(); + points_selection.copy_from(curve_op_data->original_selection); + break; + } + case ATTR_DOMAIN_CURVE: { + MutableSpan curves_seletion = curves.selection_curve_float_for_write(); + curves_seletion.copy_from(curve_op_data->original_selection); + break; + } + } + + /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic + * attribute for now. */ + DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, &curves_id); + } + MEM_delete(&op_data); + return OPERATOR_CANCELLED; + } + } + return OPERATOR_RUNNING_MODAL; +} + +} // namespace select_grow + +static void SCULPT_CURVES_OT_select_grow(wmOperatorType *ot) +{ + ot->name = "Select Grow"; + ot->idname = __func__; + ot->description = "Select curves which are close to curves that are selected already"; + + ot->invoke = select_grow::select_grow_invoke; + ot->modal = select_grow::select_grow_modal; + ot->poll = curves::selection_operator_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + PropertyRNA *prop; + prop = RNA_def_float(ot->srna, + "distance", + 0.1f, + -FLT_MAX, + FLT_MAX, + "Distance", + "By how much to grow the selection", + -10.0f, + 10.f); + RNA_def_property_subtype(prop, PROP_DISTANCE); +} + +namespace min_distance_edit { + +static bool min_distance_edit_poll(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + if (ob == nullptr) { + return false; + } + if (ob->type != OB_CURVES) { + return false; + } + Curves *curves_id = static_cast(ob->data); + if (curves_id->surface == nullptr || curves_id->surface->type != OB_MESH) { + CTX_wm_operator_poll_msg_set(C, "Curves must have a mesh surface object set"); + return false; + } + Scene *scene = CTX_data_scene(C); + const Brush *brush = BKE_paint_brush_for_read(&scene->toolsettings->curves_sculpt->paint); + if (brush == nullptr) { + return false; + } + if (brush->curves_sculpt_tool != CURVES_SCULPT_TOOL_DENSITY) { + return false; + } + return true; +} + +struct MinDistanceEditData { + /** Brush whose minimum distance is modified. */ + Brush *brush; + float4x4 curves_to_world_mat; + + /** Where the preview is drawn. */ + float3 pos_cu; + float3 normal_cu; + + int2 initial_mouse; + float initial_minimum_distance; + + /** The operator uses a new cursor, but the existing cursors should be restored afterwards. */ + ListBase orig_paintcursors; + void *cursor; + + /** Store the viewport region in case the operator was called from the header. */ + ARegion *region; + RegionView3D *rv3d; +}; + +static int calculate_points_per_side(bContext *C, MinDistanceEditData &op_data) +{ + Scene *scene = CTX_data_scene(C); + ARegion *region = op_data.region; + + const float min_distance = op_data.brush->curves_sculpt_settings->minimum_distance; + const float brush_radius = BKE_brush_size_get(scene, op_data.brush); + + float3 tangent_x_cu = math::cross(op_data.normal_cu, float3{0, 0, 1}); + if (math::is_zero(tangent_x_cu)) { + tangent_x_cu = math::cross(op_data.normal_cu, float3{0, 1, 0}); + } + tangent_x_cu = math::normalize(tangent_x_cu); + const float3 tangent_y_cu = math::normalize(math::cross(op_data.normal_cu, tangent_x_cu)); + + /* Sample a few points to get a good estimate of how large the grid has to be. */ + Vector points_wo; + points_wo.append(op_data.pos_cu + min_distance * tangent_x_cu); + points_wo.append(op_data.pos_cu + min_distance * tangent_y_cu); + points_wo.append(op_data.pos_cu - min_distance * tangent_x_cu); + points_wo.append(op_data.pos_cu - min_distance * tangent_y_cu); + + Vector points_re; + for (const float3 &pos_wo : points_wo) { + float2 pos_re; + ED_view3d_project_v2(region, pos_wo, pos_re); + points_re.append(pos_re); + } + + float2 origin_re; + ED_view3d_project_v2(region, op_data.pos_cu, origin_re); + + int needed_points = 0; + for (const float2 &pos_re : points_re) { + const float distance = math::length(pos_re - origin_re); + const int needed_points_iter = (brush_radius * 2.0f) / distance; + + if (needed_points_iter > needed_points) { + needed_points = needed_points_iter; + } + } + + /* Limit to a harcoded number since it only adds noise at some point. */ + return std::min(300, needed_points); +} + +static void min_distance_edit_draw(bContext *C, int UNUSED(x), int UNUSED(y), void *customdata) +{ + Scene *scene = CTX_data_scene(C); + MinDistanceEditData &op_data = *static_cast(customdata); + + const float min_distance = op_data.brush->curves_sculpt_settings->minimum_distance; + + float3 tangent_x_cu = math::cross(op_data.normal_cu, float3{0, 0, 1}); + if (math::is_zero(tangent_x_cu)) { + tangent_x_cu = math::cross(op_data.normal_cu, float3{0, 1, 0}); + } + tangent_x_cu = math::normalize(tangent_x_cu); + const float3 tangent_y_cu = math::normalize(math::cross(op_data.normal_cu, tangent_x_cu)); + + const int points_per_side = calculate_points_per_side(C, op_data); + const int points_per_axis_num = 2 * points_per_side + 1; + + Vector points_wo; + for (const int x_i : IndexRange(points_per_axis_num)) { + for (const int y_i : IndexRange(points_per_axis_num)) { + const float x_iter = min_distance * (x_i - (points_per_axis_num - 1) / 2.0f); + const float y_iter = min_distance * (y_i - (points_per_axis_num - 1) / 2.0f); + + const float3 point_pos_cu = op_data.pos_cu + op_data.normal_cu * 0.0001f + + x_iter * tangent_x_cu + y_iter * tangent_y_cu; + const float3 point_pos_wo = op_data.curves_to_world_mat * point_pos_cu; + points_wo.append(point_pos_wo); + } + } + + float4 circle_col = float4(op_data.brush->add_col); + float circle_alpha = op_data.brush->cursor_overlay_alpha; + float brush_radius_re = BKE_brush_size_get(scene, op_data.brush); + + /* Draw the grid. */ + GPU_matrix_push(); + GPU_matrix_push_projection(); + GPU_blend(GPU_BLEND_ALPHA); + + ARegion *region = op_data.region; + RegionView3D *rv3d = op_data.rv3d; + wmWindow *win = CTX_wm_window(C); + + /* It does the same as: `view3d_operator_needs_opengl(C);`. */ + wmViewport(®ion->winrct); + GPU_matrix_projection_set(rv3d->winmat); + GPU_matrix_set(rv3d->viewmat); + + GPUVertFormat *format3d = immVertexFormat(); + + const uint pos3d = GPU_vertformat_attr_add(format3d, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); + const uint col3d = GPU_vertformat_attr_add(format3d, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + + immBindBuiltinProgram(GPU_SHADER_3D_POINT_FIXED_SIZE_VARYING_COLOR); + + GPU_point_size(3.0f); + immBegin(GPU_PRIM_POINTS, points_wo.size()); + + float3 brush_origin_wo = op_data.curves_to_world_mat * op_data.pos_cu; + float2 brush_origin_re; + ED_view3d_project_v2(region, brush_origin_wo, brush_origin_re); + + /* Smooth alpha transition until the brush edge. */ + const int alpha_border_re = 20; + const float dist_to_inner_border_re = brush_radius_re - alpha_border_re; + + for (const float3 &pos_wo : points_wo) { + float2 pos_re; + ED_view3d_project_v2(region, pos_wo, pos_re); + + const float dist_to_point_re = math::distance(pos_re, brush_origin_re); + const float alpha = 1.0f - ((dist_to_point_re - dist_to_inner_border_re) / alpha_border_re); + + immAttr4f(col3d, 0.9f, 0.9f, 0.9f, alpha); + immVertex3fv(pos3d, pos_wo); + } + immEnd(); + immUnbindProgram(); + + /* Reset the drawing settings. */ + GPU_point_size(1.0f); + GPU_matrix_pop_projection(); + GPU_matrix_pop(); + + int4 scissor; + GPU_scissor_get(scissor); + wmWindowViewport(win); + GPU_scissor(scissor[0], scissor[1], scissor[2], scissor[3]); + + /* Draw the brush circle. */ + GPU_matrix_translate_2f((float)op_data.initial_mouse.x, (float)op_data.initial_mouse.y); + + GPUVertFormat *format = immVertexFormat(); + uint pos2d = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + immUniformColor3fvAlpha(circle_col, circle_alpha); + imm_draw_circle_wire_2d(pos2d, 0.0f, 0.0f, brush_radius_re, 80); + + immUnbindProgram(); + GPU_blend(GPU_BLEND_NONE); +} + +static int min_distance_edit_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + ARegion *region = CTX_wm_region(C); + View3D *v3d = CTX_wm_view3d(C); + Scene *scene = CTX_data_scene(C); + + Object &curves_ob = *CTX_data_active_object(C); + Curves &curves_id = *static_cast(curves_ob.data); + Object &surface_ob = *curves_id.surface; + Mesh &surface_me = *static_cast(surface_ob.data); + + BVHTreeFromMesh surface_bvh; + BKE_bvhtree_from_mesh_get(&surface_bvh, &surface_me, BVHTREE_FROM_LOOPTRI, 2); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh); }); + + const int2 mouse_pos_int_re{event->mval}; + const float2 mouse_pos_re{mouse_pos_int_re}; + + float3 ray_start_wo, ray_end_wo; + ED_view3d_win_to_segment_clipped( + depsgraph, region, v3d, mouse_pos_re, ray_start_wo, ray_end_wo, true); + + const float4x4 surface_to_world_mat = surface_ob.obmat; + const float4x4 world_to_surface_mat = surface_to_world_mat.inverted(); + + const float3 ray_start_su = world_to_surface_mat * ray_start_wo; + const float3 ray_end_su = world_to_surface_mat * ray_end_wo; + const float3 ray_direction_su = math::normalize(ray_end_su - ray_start_su); + + BVHTreeRayHit ray_hit; + ray_hit.dist = FLT_MAX; + ray_hit.index = -1; + BLI_bvhtree_ray_cast(surface_bvh.tree, + ray_start_su, + ray_direction_su, + 0.0f, + &ray_hit, + surface_bvh.raycast_callback, + &surface_bvh); + if (ray_hit.index == -1) { + WM_report(RPT_ERROR, "Cursor must be over the surface mesh"); + return OPERATOR_CANCELLED; + } + + const float3 hit_pos_su = ray_hit.co; + const float3 hit_normal_su = ray_hit.no; + const float4x4 curves_to_world_mat = curves_ob.obmat; + const float4x4 world_to_curves_mat = curves_to_world_mat.inverted(); + const float4x4 surface_to_curves_mat = world_to_curves_mat * surface_to_world_mat; + const float4x4 surface_to_curves_normal_mat = surface_to_curves_mat.inverted().transposed(); + + const float3 hit_pos_cu = surface_to_curves_mat * hit_pos_su; + const float3 hit_normal_cu = math::normalize(surface_to_curves_normal_mat * hit_normal_su); + + MinDistanceEditData *op_data = MEM_new(__func__); + op_data->curves_to_world_mat = curves_to_world_mat; + op_data->normal_cu = hit_normal_cu; + op_data->pos_cu = hit_pos_cu; + op_data->initial_mouse = event->xy; + op_data->brush = BKE_paint_brush(&scene->toolsettings->curves_sculpt->paint); + op_data->initial_minimum_distance = op_data->brush->curves_sculpt_settings->minimum_distance; + + if (op_data->initial_minimum_distance <= 0.0f) { + op_data->initial_minimum_distance = 0.01f; + } + + op->customdata = op_data; + + /* Temporarily disable other paint cursors. */ + wmWindowManager *wm = CTX_wm_manager(C); + op_data->orig_paintcursors = wm->paintcursors; + BLI_listbase_clear(&wm->paintcursors); + + /* Add minimum distance paint cursor. */ + op_data->cursor = WM_paint_cursor_activate( + SPACE_TYPE_ANY, RGN_TYPE_ANY, op->type->poll, min_distance_edit_draw, op_data); + + op_data->region = CTX_wm_region(C); + op_data->rv3d = CTX_wm_region_view3d(C); + + WM_event_add_modal_handler(C, op); + ED_region_tag_redraw(region); + return OPERATOR_RUNNING_MODAL; +} + +static int min_distance_edit_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + ARegion *region = CTX_wm_region(C); + MinDistanceEditData &op_data = *static_cast(op->customdata); + + auto finish = [&]() { + wmWindowManager *wm = CTX_wm_manager(C); + + /* Remove own cursor. */ + WM_paint_cursor_end(static_cast(op_data.cursor)); + /* Restore original paint cursors. */ + wm->paintcursors = op_data.orig_paintcursors; + + ED_region_tag_redraw(region); + MEM_freeN(&op_data); + }; + + switch (event->type) { + case MOUSEMOVE: { + const int2 mouse_pos_int_re{event->xy}; + const float2 mouse_pos_re{mouse_pos_int_re}; + + const float mouse_diff_x = mouse_pos_int_re.x - op_data.initial_mouse.x; + const float factor = powf(2, mouse_diff_x / UI_UNIT_X / 10.0f); + op_data.brush->curves_sculpt_settings->minimum_distance = op_data.initial_minimum_distance * + factor; + + ED_region_tag_redraw(region); + WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, nullptr); + break; + } + case LEFTMOUSE: { + if (event->val == KM_PRESS) { + finish(); + return OPERATOR_FINISHED; + } + break; + } + case RIGHTMOUSE: + case EVT_ESCKEY: { + op_data.brush->curves_sculpt_settings->minimum_distance = op_data.initial_minimum_distance; + finish(); + WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, nullptr); + return OPERATOR_CANCELLED; + } + } + + return OPERATOR_RUNNING_MODAL; +} + +} // namespace min_distance_edit + +static void SCULPT_CURVES_OT_min_distance_edit(wmOperatorType *ot) +{ + ot->name = "Edit Minimum Distance"; + ot->idname = __func__; + ot->description = "Change the minimum distance used by the density brush"; + + ot->poll = min_distance_edit::min_distance_edit_poll; + ot->invoke = min_distance_edit::min_distance_edit_invoke; + ot->modal = min_distance_edit::min_distance_edit_modal; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; +} + } // namespace blender::ed::sculpt_paint /* -------------------------------------------------------------------- */ @@ -327,6 +1281,10 @@ void ED_operatortypes_sculpt_curves() using namespace blender::ed::sculpt_paint; WM_operatortype_append(SCULPT_CURVES_OT_brush_stroke); WM_operatortype_append(CURVES_OT_sculptmode_toggle); + WM_operatortype_append(SCULPT_CURVES_OT_select_random); + WM_operatortype_append(SCULPT_CURVES_OT_select_end); + WM_operatortype_append(SCULPT_CURVES_OT_select_grow); + WM_operatortype_append(SCULPT_CURVES_OT_min_distance_edit); } /** \} */ diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc b/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc new file mode 100644 index 00000000000..db890d6a054 --- /dev/null +++ b/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc @@ -0,0 +1,307 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include + +#include "curves_sculpt_intern.hh" + +#include "BLI_float4x4.hh" +#include "BLI_vector.hh" + +#include "PIL_time.h" + +#include "DEG_depsgraph.h" + +#include "BKE_brush.h" +#include "BKE_context.h" +#include "BKE_curves.hh" +#include "BKE_paint.h" + +#include "DNA_brush_enums.h" +#include "DNA_brush_types.h" +#include "DNA_curves_types.h" +#include "DNA_object_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "ED_screen.h" +#include "ED_view3d.h" + +#include "WM_api.h" + +/** + * The code below uses a prefix naming convention to indicate the coordinate space: + * cu: Local space of the curves object that is being edited. + * su: Local space of the surface object. + * wo: World space. + * re: 2D coordinates within the region. + */ + +namespace blender::ed::sculpt_paint { + +class PinchOperation : public CurvesSculptStrokeOperation { + private: + bool invert_pinch_; + Array segment_lengths_cu_; + + /** Only used when a 3D brush is used. */ + CurvesBrush3D brush_3d_; + + friend struct PinchOperationExecutor; + + public: + PinchOperation(const bool invert_pinch) : invert_pinch_(invert_pinch) + { + } + + void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override; +}; + +struct PinchOperationExecutor { + PinchOperation *self_ = nullptr; + CurvesSculptCommonContext ctx_; + + Object *object_ = nullptr; + Curves *curves_id_ = nullptr; + CurvesGeometry *curves_ = nullptr; + + VArray point_factors_; + Vector selected_curve_indices_; + IndexMask curve_selection_; + + CurvesSculptTransforms transforms_; + + const CurvesSculpt *curves_sculpt_ = nullptr; + const Brush *brush_ = nullptr; + float brush_radius_base_re_; + float brush_radius_factor_; + float brush_strength_; + + float invert_factor_; + + float2 brush_pos_re_; + + PinchOperationExecutor(const bContext &C) : ctx_(C) + { + } + + void execute(PinchOperation &self, const bContext &C, const StrokeExtension &stroke_extension) + { + self_ = &self; + + object_ = CTX_data_active_object(&C); + curves_id_ = static_cast(object_->data); + curves_ = &CurvesGeometry::wrap(curves_id_->geometry); + if (curves_->curves_num() == 0) { + return; + } + + curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt; + brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint); + brush_radius_base_re_ = BKE_brush_size_get(ctx_.scene, brush_); + brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension); + brush_strength_ = BKE_brush_alpha_get(ctx_.scene, brush_); + + invert_factor_ = self_->invert_pinch_ ? -1.0f : 1.0f; + + transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface); + + point_factors_ = get_point_selection(*curves_id_); + curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_); + + brush_pos_re_ = stroke_extension.mouse_position; + const eBrushFalloffShape falloff_shape = static_cast( + brush_->falloff_shape); + + if (stroke_extension.is_first) { + this->initialize_segment_lengths(); + + if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { + self_->brush_3d_ = *sample_curves_3d_brush(*ctx_.depsgraph, + *ctx_.region, + *ctx_.v3d, + *ctx_.rv3d, + *object_, + brush_pos_re_, + brush_radius_base_re_); + } + } + + Array changed_curves(curves_->curves_num(), false); + if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { + this->pinch_projected_with_symmetry(changed_curves); + } + else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { + this->pinch_spherical_with_symmetry(changed_curves); + } + else { + BLI_assert_unreachable(); + } + + this->restore_segment_lengths(changed_curves); + curves_->tag_positions_changed(); + DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); + ED_region_tag_redraw(ctx_.region); + } + + void pinch_projected_with_symmetry(MutableSpan r_changed_curves) + { + const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( + eCurvesSymmetryType(curves_id_->symmetry)); + for (const float4x4 &brush_transform : symmetry_brush_transforms) { + this->pinch_projected(brush_transform, r_changed_curves); + } + } + + void pinch_projected(const float4x4 &brush_transform, MutableSpan r_changed_curves) + { + const float4x4 brush_transform_inv = brush_transform.inverted(); + + float4x4 projection; + ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); + MutableSpan positions_cu = curves_->positions_for_write(); + const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_; + const float brush_radius_sq_re = pow2f(brush_radius_re); + + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { + for (const int curve_i : curve_selection_.slice(range)) { + const IndexRange points = curves_->points_for_curve(curve_i); + for (const int point_i : points.drop_front(1)) { + const float3 old_pos_cu = brush_transform_inv * positions_cu[point_i]; + float2 old_pos_re; + ED_view3d_project_float_v2_m4(ctx_.region, old_pos_cu, old_pos_re, projection.values); + + const float dist_to_brush_sq_re = math::distance_squared(old_pos_re, brush_pos_re_); + if (dist_to_brush_sq_re > brush_radius_sq_re) { + continue; + } + + const float dist_to_brush_re = std::sqrt(dist_to_brush_sq_re); + const float t = safe_divide(dist_to_brush_re, brush_radius_base_re_); + const float radius_falloff = t * BKE_brush_curve_strength(brush_, t, 1.0f); + const float weight = invert_factor_ * 0.1f * brush_strength_ * radius_falloff * + point_factors_[point_i]; + + const float2 new_pos_re = math::interpolate(old_pos_re, brush_pos_re_, weight); + + const float3 old_pos_wo = transforms_.curves_to_world * old_pos_cu; + float3 new_pos_wo; + ED_view3d_win_to_3d(ctx_.v3d, ctx_.region, old_pos_wo, new_pos_re, new_pos_wo); + + const float3 new_pos_cu = transforms_.world_to_curves * new_pos_wo; + positions_cu[point_i] = brush_transform * new_pos_cu; + r_changed_curves[curve_i] = true; + } + } + }); + } + + void pinch_spherical_with_symmetry(MutableSpan r_changed_curves) + { + float3 brush_pos_wo; + ED_view3d_win_to_3d(ctx_.v3d, + ctx_.region, + transforms_.curves_to_world * self_->brush_3d_.position_cu, + brush_pos_re_, + brush_pos_wo); + const float3 brush_pos_cu = transforms_.world_to_curves * brush_pos_wo; + const float brush_radius_cu = self_->brush_3d_.radius_cu * brush_radius_factor_; + + const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( + eCurvesSymmetryType(curves_id_->symmetry)); + for (const float4x4 &brush_transform : symmetry_brush_transforms) { + this->pinch_spherical(brush_transform * brush_pos_cu, brush_radius_cu, r_changed_curves); + } + } + + void pinch_spherical(const float3 &brush_pos_cu, + const float brush_radius_cu, + MutableSpan r_changed_curves) + { + MutableSpan positions_cu = curves_->positions_for_write(); + const float brush_radius_sq_cu = pow2f(brush_radius_cu); + + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { + for (const int curve_i : curve_selection_.slice(range)) { + const IndexRange points = curves_->points_for_curve(curve_i); + for (const int point_i : points.drop_front(1)) { + const float3 old_pos_cu = positions_cu[point_i]; + + const float dist_to_brush_sq_cu = math::distance_squared(old_pos_cu, brush_pos_cu); + if (dist_to_brush_sq_cu > brush_radius_sq_cu) { + continue; + } + + const float dist_to_brush_cu = std::sqrt(dist_to_brush_sq_cu); + const float t = safe_divide(dist_to_brush_cu, brush_radius_cu); + const float radius_falloff = t * BKE_brush_curve_strength(brush_, t, 1.0f); + const float weight = invert_factor_ * 0.1f * brush_strength_ * radius_falloff * + point_factors_[point_i]; + + const float3 new_pos_cu = math::interpolate(old_pos_cu, brush_pos_cu, weight); + positions_cu[point_i] = new_pos_cu; + + r_changed_curves[curve_i] = true; + } + } + }); + } + + void initialize_segment_lengths() + { + const Span positions_cu = curves_->positions(); + self_->segment_lengths_cu_.reinitialize(curves_->points_num()); + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { + for (const int curve_i : curve_selection_.slice(range)) { + const IndexRange points = curves_->points_for_curve(curve_i); + for (const int point_i : points.drop_back(1)) { + const float3 &p1_cu = positions_cu[point_i]; + const float3 &p2_cu = positions_cu[point_i + 1]; + const float length_cu = math::distance(p1_cu, p2_cu); + self_->segment_lengths_cu_[point_i] = length_cu; + } + } + }); + } + + void restore_segment_lengths(const Span changed_curves) + { + const Span expected_lengths_cu = self_->segment_lengths_cu_; + MutableSpan positions_cu = curves_->positions_for_write(); + + threading::parallel_for(changed_curves.index_range(), 256, [&](const IndexRange range) { + for (const int curve_i : range) { + if (!changed_curves[curve_i]) { + continue; + } + const IndexRange points = curves_->points_for_curve(curve_i); + for (const int segment_i : IndexRange(points.size() - 1)) { + const float3 &p1_cu = positions_cu[points[segment_i]]; + float3 &p2_cu = positions_cu[points[segment_i] + 1]; + const float3 direction = math::normalize(p2_cu - p1_cu); + const float expected_length_cu = expected_lengths_cu[points[segment_i]]; + p2_cu = p1_cu + direction * expected_length_cu; + } + } + }); + } +}; + +void PinchOperation::on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) +{ + PinchOperationExecutor executor{C}; + executor.execute(*this, C, stroke_extension); +} + +std::unique_ptr new_pinch_operation(const BrushStrokeMode brush_mode, + const bContext &C) +{ + const Scene &scene = *CTX_data_scene(&C); + const Brush &brush = *BKE_paint_brush_for_read(&scene.toolsettings->curves_sculpt->paint); + + const bool invert_pinch = (brush_mode == BRUSH_STROKE_INVERT) != + ((brush.flag & BRUSH_DIR_IN) != 0); + return std::make_unique(invert_pinch); +} + +} // namespace blender::ed::sculpt_paint diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc b/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc new file mode 100644 index 00000000000..dc747fd0bce --- /dev/null +++ b/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc @@ -0,0 +1,393 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_attribute_math.hh" +#include "BKE_brush.h" +#include "BKE_bvhutils.h" +#include "BKE_context.h" +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" + +#include "ED_screen.h" +#include "ED_view3d.h" + +#include "DEG_depsgraph.h" + +#include "DNA_brush_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "WM_api.h" + +#include "BLI_length_parameterize.hh" + +#include "curves_sculpt_intern.hh" + +namespace blender::ed::sculpt_paint { + +class PuffOperation : public CurvesSculptStrokeOperation { + private: + /** Only used when a 3D brush is used. */ + CurvesBrush3D brush_3d_; + + /** Length of each segment indexed by the index of the first point in the segment. */ + Array segment_lengths_cu_; + + friend struct PuffOperationExecutor; + + public: + void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override; +}; + +static float3 compute_surface_point_normal(const MLoopTri &looptri, + const float3 &bary_coord, + const Span corner_normals) +{ + const int l0 = looptri.tri[0]; + const int l1 = looptri.tri[1]; + const int l2 = looptri.tri[2]; + + const float3 &l0_normal = corner_normals[l0]; + const float3 &l1_normal = corner_normals[l1]; + const float3 &l2_normal = corner_normals[l2]; + + const float3 normal = math::normalize( + attribute_math::mix3(bary_coord, l0_normal, l1_normal, l2_normal)); + return normal; +} + +/** + * Utility class that actually executes the update when the stroke is updated. That's useful + * because it avoids passing a very large number of parameters between functions. + */ +struct PuffOperationExecutor { + PuffOperation *self_ = nullptr; + CurvesSculptCommonContext ctx_; + + Object *object_ = nullptr; + Curves *curves_id_ = nullptr; + CurvesGeometry *curves_ = nullptr; + + VArray point_factors_; + Vector selected_curve_indices_; + IndexMask curve_selection_; + + const CurvesSculpt *curves_sculpt_ = nullptr; + const Brush *brush_ = nullptr; + float brush_radius_base_re_; + float brush_radius_factor_; + float brush_strength_; + float2 brush_pos_re_; + + eBrushFalloffShape falloff_shape_; + + CurvesSculptTransforms transforms_; + + Object *surface_ob_ = nullptr; + Mesh *surface_ = nullptr; + Span surface_looptris_; + Span corner_normals_su_; + BVHTreeFromMesh surface_bvh_; + + PuffOperationExecutor(const bContext &C) : ctx_(C) + { + } + + void execute(PuffOperation &self, const bContext &C, const StrokeExtension &stroke_extension) + { + UNUSED_VARS(C, stroke_extension); + self_ = &self; + + object_ = CTX_data_active_object(&C); + curves_id_ = static_cast(object_->data); + curves_ = &CurvesGeometry::wrap(curves_id_->geometry); + if (curves_->curves_num() == 0) { + return; + } + if (curves_id_->surface == nullptr || curves_id_->surface->type != OB_MESH) { + return; + } + + curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt; + brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint); + brush_radius_base_re_ = BKE_brush_size_get(ctx_.scene, brush_); + brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension); + brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension); + brush_pos_re_ = stroke_extension.mouse_position; + + point_factors_ = get_point_selection(*curves_id_); + curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_); + + falloff_shape_ = static_cast(brush_->falloff_shape); + + surface_ob_ = curves_id_->surface; + surface_ = static_cast(surface_ob_->data); + + transforms_ = CurvesSculptTransforms(*object_, surface_ob_); + + if (!CustomData_has_layer(&surface_->ldata, CD_NORMAL)) { + BKE_mesh_calc_normals_split(surface_); + } + corner_normals_su_ = { + reinterpret_cast(CustomData_get_layer(&surface_->ldata, CD_NORMAL)), + surface_->totloop}; + + BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); }); + + surface_looptris_ = {BKE_mesh_runtime_looptri_ensure(surface_), + BKE_mesh_runtime_looptri_len(surface_)}; + + if (stroke_extension.is_first) { + this->initialize_segment_lengths(); + if (falloff_shape_ == PAINT_FALLOFF_SHAPE_SPHERE) { + self.brush_3d_ = *sample_curves_3d_brush(*ctx_.depsgraph, + *ctx_.region, + *ctx_.v3d, + *ctx_.rv3d, + *object_, + brush_pos_re_, + brush_radius_base_re_); + } + } + + Array curve_weights(curve_selection_.size(), 0.0f); + + if (falloff_shape_ == PAINT_FALLOFF_SHAPE_TUBE) { + this->find_curve_weights_projected_with_symmetry(curve_weights); + } + else if (falloff_shape_ == PAINT_FALLOFF_SHAPE_SPHERE) { + this->find_curves_weights_spherical_with_symmetry(curve_weights); + } + else { + BLI_assert_unreachable(); + } + + this->puff(curve_weights); + this->restore_segment_lengths(); + + curves_->tag_positions_changed(); + DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); + ED_region_tag_redraw(ctx_.region); + } + + void find_curve_weights_projected_with_symmetry(MutableSpan r_curve_weights) + { + const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( + eCurvesSymmetryType(curves_id_->symmetry)); + for (const float4x4 &brush_transform : symmetry_brush_transforms) { + this->find_curve_weights_projected(brush_transform, r_curve_weights); + } + } + + void find_curve_weights_projected(const float4x4 &brush_transform, + MutableSpan r_curve_weights) + { + Span positions_cu = curves_->positions(); + + const float4x4 brush_transform_inv = brush_transform.inverted(); + + float4x4 projection; + ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); + + const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_; + const float brush_radius_sq_re = pow2f(brush_radius_re); + + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { + for (const int curve_selection_i : range) { + const int curve_i = curve_selection_[curve_selection_i]; + const IndexRange points = curves_->points_for_curve(curve_i); + const float3 first_pos_cu = brush_transform_inv * positions_cu[points[0]]; + float2 prev_pos_re; + ED_view3d_project_float_v2_m4(ctx_.region, first_pos_cu, prev_pos_re, projection.values); + for (const int point_i : points.drop_front(1)) { + const float3 pos_cu = brush_transform_inv * positions_cu[point_i]; + float2 pos_re; + ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values); + BLI_SCOPED_DEFER([&]() { prev_pos_re = pos_re; }); + + const float dist_to_brush_sq_re = dist_squared_to_line_segment_v2( + brush_pos_re_, prev_pos_re, pos_re); + if (dist_to_brush_sq_re > brush_radius_sq_re) { + continue; + } + + const float dist_to_brush_re = std::sqrt(dist_to_brush_sq_re); + const float radius_falloff = BKE_brush_curve_strength( + brush_, dist_to_brush_re, brush_radius_re); + const float weight = radius_falloff; + math::max_inplace(r_curve_weights[curve_selection_i], weight); + } + } + }); + } + + void find_curves_weights_spherical_with_symmetry(MutableSpan r_curve_weights) + { + float4x4 projection; + ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); + + float3 brush_pos_wo; + ED_view3d_win_to_3d(ctx_.v3d, + ctx_.region, + transforms_.curves_to_world * self_->brush_3d_.position_cu, + brush_pos_re_, + brush_pos_wo); + const float3 brush_pos_cu = transforms_.world_to_curves * brush_pos_wo; + const float brush_radius_cu = self_->brush_3d_.radius_cu * brush_radius_factor_; + + const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( + eCurvesSymmetryType(curves_id_->symmetry)); + for (const float4x4 &brush_transform : symmetry_brush_transforms) { + this->find_curves_weights_spherical( + brush_transform * brush_pos_cu, brush_radius_cu, r_curve_weights); + } + } + + void find_curves_weights_spherical(const float3 &brush_pos_cu, + const float brush_radius_cu, + MutableSpan r_curve_weights) + { + const Span positions_cu = curves_->positions(); + const float brush_radius_sq_cu = pow2f(brush_radius_cu); + + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { + for (const int curve_selection_i : range) { + const int curve_i = curve_selection_[curve_selection_i]; + const IndexRange points = curves_->points_for_curve(curve_i); + for (const int point_i : points.drop_front(1)) { + const float3 &prev_pos_cu = positions_cu[point_i - 1]; + const float3 &pos_cu = positions_cu[point_i]; + const float dist_to_brush_sq_cu = dist_squared_to_line_segment_v3( + brush_pos_cu, prev_pos_cu, pos_cu); + if (dist_to_brush_sq_cu > brush_radius_sq_cu) { + continue; + } + + const float dist_to_brush_cu = std::sqrt(dist_to_brush_sq_cu); + const float radius_falloff = BKE_brush_curve_strength( + brush_, dist_to_brush_cu, brush_radius_cu); + const float weight = radius_falloff; + math::max_inplace(r_curve_weights[curve_selection_i], weight); + } + } + }); + } + + void puff(const Span curve_weights) + { + BLI_assert(curve_weights.size() == curve_selection_.size()); + MutableSpan positions_cu = curves_->positions_for_write(); + + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { + Vector accumulated_lengths_cu; + for (const int curve_selection_i : range) { + const int curve_i = curve_selection_[curve_selection_i]; + const IndexRange points = curves_->points_for_curve(curve_i); + const int first_point_i = points[0]; + const float3 first_pos_cu = positions_cu[first_point_i]; + const float3 first_pos_su = transforms_.curves_to_surface * first_pos_cu; + + /* Find the nearest position on the surface. The curve will be aligned to the normal of + * that point. */ + BVHTreeNearest nearest; + nearest.dist_sq = FLT_MAX; + BLI_bvhtree_find_nearest(surface_bvh_.tree, + first_pos_su, + &nearest, + surface_bvh_.nearest_callback, + &surface_bvh_); + + const MLoopTri &looptri = surface_looptris_[nearest.index]; + const float3 closest_pos_su = nearest.co; + const float3 &v0_su = surface_->mvert[surface_->mloop[looptri.tri[0]].v].co; + const float3 &v1_su = surface_->mvert[surface_->mloop[looptri.tri[1]].v].co; + const float3 &v2_su = surface_->mvert[surface_->mloop[looptri.tri[2]].v].co; + float3 bary_coords; + interp_weights_tri_v3(bary_coords, v0_su, v1_su, v2_su, closest_pos_su); + const float3 normal_su = compute_surface_point_normal( + looptri, bary_coords, corner_normals_su_); + const float3 normal_cu = math::normalize(transforms_.surface_to_curves_normal * normal_su); + + accumulated_lengths_cu.reinitialize(points.size() - 1); + length_parameterize::accumulate_lengths( + positions_cu.slice(points), false, accumulated_lengths_cu); + + /* Align curve to the surface normal while making sure that the curve does not fold up much + * in the process (e.g. when the curve was pointing in the opposite direction before). */ + for (const int i : IndexRange(points.size()).drop_front(1)) { + const int point_i = points[i]; + const float3 old_pos_cu = positions_cu[point_i]; + + /* Compute final position of the point. */ + const float length_param_cu = accumulated_lengths_cu[i - 1]; + const float3 goal_pos_cu = first_pos_cu + length_param_cu * normal_cu; + + const float weight = 0.01f * brush_strength_ * point_factors_[point_i] * + curve_weights[curve_selection_i]; + float3 new_pos_cu = math::interpolate(old_pos_cu, goal_pos_cu, weight); + + /* Make sure the point does not move closer to the root point than it was initially. This + * makes the curve kind of "rotate up". */ + const float old_dist_to_root_cu = math::distance(old_pos_cu, first_pos_cu); + const float new_dist_to_root_cu = math::distance(new_pos_cu, first_pos_cu); + if (new_dist_to_root_cu < old_dist_to_root_cu) { + const float3 offset = math::normalize(new_pos_cu - first_pos_cu); + new_pos_cu += (old_dist_to_root_cu - new_dist_to_root_cu) * offset; + } + + positions_cu[point_i] = new_pos_cu; + } + } + }); + } + + void initialize_segment_lengths() + { + const Span positions_cu = curves_->positions(); + self_->segment_lengths_cu_.reinitialize(curves_->points_num()); + threading::parallel_for(curves_->curves_range(), 128, [&](const IndexRange range) { + for (const int curve_i : range) { + const IndexRange points = curves_->points_for_curve(curve_i); + for (const int point_i : points.drop_back(1)) { + const float3 &p1_cu = positions_cu[point_i]; + const float3 &p2_cu = positions_cu[point_i + 1]; + const float length_cu = math::distance(p1_cu, p2_cu); + self_->segment_lengths_cu_[point_i] = length_cu; + } + } + }); + } + + void restore_segment_lengths() + { + const Span expected_lengths_cu = self_->segment_lengths_cu_; + MutableSpan positions_cu = curves_->positions_for_write(); + + threading::parallel_for(curves_->curves_range(), 256, [&](const IndexRange range) { + for (const int curve_i : range) { + const IndexRange points = curves_->points_for_curve(curve_i); + for (const int segment_i : points.drop_back(1)) { + const float3 &p1_cu = positions_cu[segment_i]; + float3 &p2_cu = positions_cu[segment_i + 1]; + const float3 direction = math::normalize(p2_cu - p1_cu); + const float expected_length_cu = expected_lengths_cu[segment_i]; + p2_cu = p1_cu + direction * expected_length_cu; + } + } + }); + } +}; + +void PuffOperation::on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) +{ + PuffOperationExecutor executor{C}; + executor.execute(*this, C, stroke_extension); +} + +std::unique_ptr new_puff_operation() +{ + return std::make_unique(); +} + +} // namespace blender::ed::sculpt_paint diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc b/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc new file mode 100644 index 00000000000..3e1949cbf34 --- /dev/null +++ b/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc @@ -0,0 +1,310 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include + +#include "curves_sculpt_intern.hh" + +#include "BLI_float4x4.hh" +#include "BLI_vector.hh" + +#include "PIL_time.h" + +#include "DEG_depsgraph.h" + +#include "BKE_attribute_math.hh" +#include "BKE_brush.h" +#include "BKE_bvhutils.h" +#include "BKE_context.h" +#include "BKE_curves.hh" +#include "BKE_geometry_set.hh" +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_mesh_sample.hh" +#include "BKE_paint.h" + +#include "DNA_brush_enums.h" +#include "DNA_brush_types.h" +#include "DNA_curves_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "ED_screen.h" +#include "ED_view3d.h" + +#include "UI_interface.h" + +#include "WM_api.h" + +namespace blender::ed::sculpt_paint { + +struct SlideCurveInfo { + /** Index of the curve to slide. */ + int curve_i; + /** A weight based on the initial distance to the brush. */ + float radius_falloff; +}; + +struct SlideInfo { + /** The transform used for the curves below (e.g. for symmetry). */ + float4x4 brush_transform; + Vector curves_to_slide; +}; + +class SlideOperation : public CurvesSculptStrokeOperation { + private: + /** Last mouse position. */ + float2 brush_pos_last_re_; + /** Information about which curves to slide. This is initialized when the brush starts. */ + Vector slide_info_; + + friend struct SlideOperationExecutor; + + public: + void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override; +}; + +/** + * Utility class that actually executes the update when the stroke is updated. That's useful + * because it avoids passing a very large number of parameters between functions. + */ +struct SlideOperationExecutor { + SlideOperation *self_ = nullptr; + CurvesSculptCommonContext ctx_; + + const CurvesSculpt *curves_sculpt_ = nullptr; + const Brush *brush_ = nullptr; + float brush_radius_base_re_; + float brush_radius_factor_; + float brush_strength_; + + Object *object_ = nullptr; + Curves *curves_id_ = nullptr; + CurvesGeometry *curves_ = nullptr; + + Object *surface_ob_ = nullptr; + Mesh *surface_ = nullptr; + Span surface_looptris_; + VArray_Span surface_uv_map_; + + VArray curve_factors_; + VArray point_factors_; + Vector selected_curve_indices_; + IndexMask curve_selection_; + + float2 brush_pos_prev_re_; + float2 brush_pos_re_; + float2 brush_pos_diff_re_; + + CurvesSculptTransforms transforms_; + + BVHTreeFromMesh surface_bvh_; + + SlideOperationExecutor(const bContext &C) : ctx_(C) + { + } + + void execute(SlideOperation &self, const bContext &C, const StrokeExtension &stroke_extension) + { + UNUSED_VARS(C, stroke_extension); + self_ = &self; + + object_ = CTX_data_active_object(&C); + curves_id_ = static_cast(object_->data); + curves_ = &CurvesGeometry::wrap(curves_id_->geometry); + if (curves_id_->surface == nullptr || curves_id_->surface->type != OB_MESH) { + return; + } + if (curves_->curves_num() == 0) { + return; + } + + curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt; + brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint); + brush_radius_base_re_ = BKE_brush_size_get(ctx_.scene, brush_); + brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension); + brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension); + + curve_factors_ = get_curves_selection(*curves_id_); + point_factors_ = get_point_selection(*curves_id_); + curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_); + + brush_pos_prev_re_ = self_->brush_pos_last_re_; + brush_pos_re_ = stroke_extension.mouse_position; + brush_pos_diff_re_ = brush_pos_re_ - brush_pos_prev_re_; + BLI_SCOPED_DEFER([&]() { self_->brush_pos_last_re_ = brush_pos_re_; }); + + transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface); + + surface_ob_ = curves_id_->surface; + surface_ = static_cast(surface_ob_->data); + + BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); }); + + surface_looptris_ = {BKE_mesh_runtime_looptri_ensure(surface_), + BKE_mesh_runtime_looptri_len(surface_)}; + + if (curves_id_->surface_uv_map != nullptr) { + MeshComponent surface_component; + surface_component.replace(surface_, GeometryOwnershipType::ReadOnly); + surface_uv_map_ = surface_component + .attribute_try_get_for_read(curves_id_->surface_uv_map, + ATTR_DOMAIN_CORNER) + .typed(); + } + + if (stroke_extension.is_first) { + const Vector brush_transforms = get_symmetry_brush_transforms( + eCurvesSymmetryType(curves_id_->symmetry)); + for (const float4x4 &brush_transform : brush_transforms) { + this->detect_curves_to_slide(brush_transform); + } + return; + } + this->slide_projected(); + + curves_->tag_positions_changed(); + DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); + ED_region_tag_redraw(ctx_.region); + } + + void detect_curves_to_slide(const float4x4 &brush_transform) + { + const float4x4 brush_transform_inv = brush_transform.inverted(); + + const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_; + const float brush_radius_sq_re = pow2f(brush_radius_re); + + const Span positions_cu = curves_->positions(); + + float4x4 projection; + ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); + + self_->slide_info_.append({brush_transform}); + Vector &curves_to_slide = self_->slide_info_.last().curves_to_slide; + + /* Find curves in brush radius that should be moved. */ + for (const int curve_i : curve_selection_) { + const int first_point_i = curves_->offsets()[curve_i]; + const float3 &first_pos_cu = brush_transform_inv * positions_cu[first_point_i]; + + float2 first_pos_re; + ED_view3d_project_float_v2_m4(ctx_.region, first_pos_cu, first_pos_re, projection.values); + + const float dist_to_brush_sq_re = math::distance_squared(first_pos_re, brush_pos_re_); + if (dist_to_brush_sq_re > brush_radius_sq_re) { + continue; + } + const float dist_to_brush_re = std::sqrt(dist_to_brush_sq_re); + const float radius_falloff = BKE_brush_curve_strength( + brush_, dist_to_brush_re, brush_radius_re); + curves_to_slide.append({curve_i, radius_falloff}); + } + } + + void slide_projected() + { + MutableSpan positions_cu = curves_->positions_for_write(); + + MutableSpan surface_uv_coords; + if (!surface_uv_map_.is_empty()) { + surface_uv_coords = curves_->surface_uv_coords_for_write(); + } + + float4x4 projection; + ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); + + for (const SlideInfo &slide_info : self_->slide_info_) { + const float4x4 &brush_transform = slide_info.brush_transform; + const float4x4 brush_transform_inv = brush_transform.inverted(); + const Span curves_to_slide = slide_info.curves_to_slide; + + threading::parallel_for(curves_to_slide.index_range(), 256, [&](const IndexRange range) { + for (const SlideCurveInfo &curve_slide_info : curves_to_slide.slice(range)) { + const int curve_i = curve_slide_info.curve_i; + const IndexRange points = curves_->points_for_curve(curve_i); + const int first_point_i = points.first(); + const float3 old_first_pos_cu = brush_transform_inv * positions_cu[first_point_i]; + + float2 old_first_pos_re; + ED_view3d_project_float_v2_m4( + ctx_.region, old_first_pos_cu, old_first_pos_re, projection.values); + const float first_point_weight = brush_strength_ * curve_slide_info.radius_falloff; + + /* Slide root position in region space and then project it back onto the surface. */ + const float2 new_first_pos_re = old_first_pos_re + + first_point_weight * brush_pos_diff_re_; + + float3 ray_start_wo, ray_end_wo; + ED_view3d_win_to_segment_clipped(ctx_.depsgraph, + ctx_.region, + ctx_.v3d, + new_first_pos_re, + ray_start_wo, + ray_end_wo, + true); + const float3 ray_start_su = transforms_.world_to_surface * ray_start_wo; + const float3 ray_end_su = transforms_.world_to_surface * ray_end_wo; + + const float3 ray_direction_su = math::normalize(ray_end_su - ray_start_su); + BVHTreeRayHit hit; + hit.dist = FLT_MAX; + hit.index = -1; + BLI_bvhtree_ray_cast(surface_bvh_.tree, + ray_start_su, + ray_direction_su, + 0.0f, + &hit, + surface_bvh_.raycast_callback, + &surface_bvh_); + if (hit.index == -1) { + continue; + } + + const int looptri_index = hit.index; + const float3 attached_pos_su = hit.co; + + const float3 attached_pos_cu = transforms_.surface_to_curves * attached_pos_su; + const float3 pos_offset_cu = brush_transform * (attached_pos_cu - old_first_pos_cu); + + /* Update positions. The first point doesn't have an additional weight here, because then + * it wouldn't be attached to the surface anymore. */ + positions_cu[first_point_i] += pos_offset_cu; + for (const int point_i : points.drop_front(1)) { + const float weight = point_factors_[point_i]; + positions_cu[point_i] += weight * pos_offset_cu; + } + + /* Update surface attachment information if necessary. */ + if (!surface_uv_map_.is_empty()) { + const MLoopTri &looptri = surface_looptris_[looptri_index]; + const float3 bary_coord = bke::mesh_surface_sample::compute_bary_coord_in_triangle( + *surface_, looptri, attached_pos_su); + const float2 &uv0 = surface_uv_map_[looptri.tri[0]]; + const float2 &uv1 = surface_uv_map_[looptri.tri[1]]; + const float2 &uv2 = surface_uv_map_[looptri.tri[2]]; + const float2 uv = attribute_math::mix3(bary_coord, uv0, uv1, uv2); + surface_uv_coords[curve_i] = uv; + } + } + }); + } + } +}; + +void SlideOperation::on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) +{ + SlideOperationExecutor executor{C}; + executor.execute(*this, C, stroke_extension); +} + +std::unique_ptr new_slide_operation() +{ + return std::make_unique(); +} + +} // namespace blender::ed::sculpt_paint diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_smooth.cc b/source/blender/editors/sculpt_paint/curves_sculpt_smooth.cc new file mode 100644 index 00000000000..e72b17d448b --- /dev/null +++ b/source/blender/editors/sculpt_paint/curves_sculpt_smooth.cc @@ -0,0 +1,259 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_brush.h" +#include "BKE_context.h" + +#include "ED_screen.h" +#include "ED_view3d.h" + +#include "DEG_depsgraph.h" + +#include "DNA_brush_types.h" + +#include "WM_api.h" + +#include "BLI_enumerable_thread_specific.hh" + +#include "curves_sculpt_intern.hh" + +namespace blender::ed::sculpt_paint { + +class SmoothOperation : public CurvesSculptStrokeOperation { + private: + /** Only used when a 3D brush is used. */ + CurvesBrush3D brush_3d_; + + friend struct SmoothOperationExecutor; + + public: + void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override; +}; + +/** + * Utility class that actually executes the update when the stroke is updated. That's useful + * because it avoids passing a very large number of parameters between functions. + */ +struct SmoothOperationExecutor { + SmoothOperation *self_ = nullptr; + CurvesSculptCommonContext ctx_; + + Object *object_ = nullptr; + Curves *curves_id_ = nullptr; + CurvesGeometry *curves_ = nullptr; + + VArray point_factors_; + Vector selected_curve_indices_; + IndexMask curve_selection_; + + const CurvesSculpt *curves_sculpt_ = nullptr; + const Brush *brush_ = nullptr; + float brush_radius_base_re_; + float brush_radius_factor_; + float brush_strength_; + float2 brush_pos_re_; + + CurvesSculptTransforms transforms_; + + SmoothOperationExecutor(const bContext &C) : ctx_(C) + { + } + + void execute(SmoothOperation &self, const bContext &C, const StrokeExtension &stroke_extension) + { + UNUSED_VARS(C, stroke_extension); + self_ = &self; + + object_ = CTX_data_active_object(&C); + curves_id_ = static_cast(object_->data); + curves_ = &CurvesGeometry::wrap(curves_id_->geometry); + if (curves_->curves_num() == 0) { + return; + } + + curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt; + brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint); + brush_radius_base_re_ = BKE_brush_size_get(ctx_.scene, brush_); + brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension); + brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension); + brush_pos_re_ = stroke_extension.mouse_position; + + point_factors_ = get_point_selection(*curves_id_); + curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_); + transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface); + + const eBrushFalloffShape falloff_shape = static_cast( + brush_->falloff_shape); + if (stroke_extension.is_first) { + if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { + self.brush_3d_ = *sample_curves_3d_brush(*ctx_.depsgraph, + *ctx_.region, + *ctx_.v3d, + *ctx_.rv3d, + *object_, + brush_pos_re_, + brush_radius_base_re_); + } + } + + if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { + this->smooth_projected_with_symmetry(); + } + else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { + this->smooth_spherical_with_symmetry(); + } + else { + BLI_assert_unreachable(); + } + + curves_->tag_positions_changed(); + DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); + ED_region_tag_redraw(ctx_.region); + } + + void smooth_projected_with_symmetry() + { + const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( + eCurvesSymmetryType(curves_id_->symmetry)); + for (const float4x4 &brush_transform : symmetry_brush_transforms) { + this->smooth_projected(brush_transform); + } + } + + void smooth_projected(const float4x4 &brush_transform) + { + const float4x4 brush_transform_inv = brush_transform.inverted(); + + MutableSpan positions_cu = curves_->positions_for_write(); + const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_; + const float brush_radius_sq_re = pow2f(brush_radius_re); + + float4x4 projection; + ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); + + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { + Vector old_curve_positions_re; + for (const int curve_i : curve_selection_.slice(range)) { + const IndexRange points = curves_->points_for_curve(curve_i); + + /* Find position of control points in screen space. */ + old_curve_positions_re.clear(); + old_curve_positions_re.reserve(points.size()); + for (const int point_i : points) { + const float3 &pos_cu = brush_transform_inv * positions_cu[point_i]; + float2 pos_re; + ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values); + old_curve_positions_re.append_unchecked(pos_re); + } + for (const int i : IndexRange(points.size()).drop_front(1).drop_back(1)) { + const int point_i = points[i]; + const float2 &old_pos_re = old_curve_positions_re[i]; + const float dist_to_brush_sq_re = math::distance_squared(old_pos_re, brush_pos_re_); + if (dist_to_brush_sq_re > brush_radius_sq_re) { + continue; + } + + const float dist_to_brush_re = std::sqrt(dist_to_brush_sq_re); + const float radius_falloff = BKE_brush_curve_strength( + brush_, dist_to_brush_re, brush_radius_re); + /* Used to make the brush easier to use. Otherwise a strength of 1 would be way too + * large. */ + const float weight_factor = 0.1f; + const float weight = weight_factor * brush_strength_ * radius_falloff * + point_factors_[point_i]; + + /* Move points towards the middle of their neighbors. */ + const float2 &old_pos_prev_re = old_curve_positions_re[i - 1]; + const float2 &old_pos_next_re = old_curve_positions_re[i + 1]; + const float2 goal_pos_re = math::midpoint(old_pos_prev_re, old_pos_next_re); + const float2 new_pos_re = math::interpolate(old_pos_re, goal_pos_re, weight); + const float3 old_pos_cu = brush_transform_inv * positions_cu[point_i]; + float3 new_pos_wo; + ED_view3d_win_to_3d(ctx_.v3d, + ctx_.region, + transforms_.curves_to_world * old_pos_cu, + new_pos_re, + new_pos_wo); + const float3 new_pos_cu = brush_transform * (transforms_.world_to_curves * new_pos_wo); + positions_cu[point_i] = new_pos_cu; + } + } + }); + } + + void smooth_spherical_with_symmetry() + { + float4x4 projection; + ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); + + float3 brush_pos_wo; + ED_view3d_win_to_3d(ctx_.v3d, + ctx_.region, + transforms_.curves_to_world * self_->brush_3d_.position_cu, + brush_pos_re_, + brush_pos_wo); + const float3 brush_pos_cu = transforms_.world_to_curves * brush_pos_wo; + const float brush_radius_cu = self_->brush_3d_.radius_cu * brush_radius_factor_; + + const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( + eCurvesSymmetryType(curves_id_->symmetry)); + for (const float4x4 &brush_transform : symmetry_brush_transforms) { + this->smooth_spherical(brush_transform * brush_pos_cu, brush_radius_cu); + } + } + + void smooth_spherical(const float3 &brush_pos_cu, const float brush_radius_cu) + { + MutableSpan positions_cu = curves_->positions_for_write(); + const float brush_radius_sq_cu = pow2f(brush_radius_cu); + + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { + Vector old_curve_positions_cu; + for (const int curve_i : curve_selection_.slice(range)) { + const IndexRange points = curves_->points_for_curve(curve_i); + /* Remember original positions so that we don't smooth based on already smoothed points + * below. */ + old_curve_positions_cu.clear(); + old_curve_positions_cu.extend(positions_cu.slice(points)); + for (const int i : IndexRange(points.size()).drop_front(1).drop_back(1)) { + const int point_i = points[i]; + const float3 &old_pos_cu = old_curve_positions_cu[i]; + const float dist_to_brush_sq_cu = math::distance_squared(old_pos_cu, brush_pos_cu); + if (dist_to_brush_sq_cu > brush_radius_sq_cu) { + continue; + } + + const float dist_to_brush_cu = std::sqrt(dist_to_brush_sq_cu); + const float radius_falloff = BKE_brush_curve_strength( + brush_, dist_to_brush_cu, brush_radius_cu); + /* Used to make the brush easier to use. Otherwise a strength of 1 would be way too + * large. */ + const float weight_factor = 0.1f; + const float weight = weight_factor * brush_strength_ * radius_falloff * + point_factors_[point_i]; + + /* Move points towards the middle of their neighbors. */ + const float3 &old_pos_prev_cu = old_curve_positions_cu[i - 1]; + const float3 &old_pos_next_cu = old_curve_positions_cu[i + 1]; + const float3 goal_pos_cu = math::midpoint(old_pos_prev_cu, old_pos_next_cu); + const float3 new_pos_cu = math::interpolate(old_pos_cu, goal_pos_cu, weight); + positions_cu[point_i] = new_pos_cu; + } + } + }); + } +}; + +void SmoothOperation::on_stroke_extended(const bContext &C, + const StrokeExtension &stroke_extension) +{ + SmoothOperationExecutor executor{C}; + executor.execute(*this, C, stroke_extension); +} + +std::unique_ptr new_smooth_operation() +{ + return std::make_unique(); +} + +} // namespace blender::ed::sculpt_paint diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c index 63e6dc7e965..1ee26935dc9 100644 --- a/source/blender/editors/sculpt_paint/paint_stroke.c +++ b/source/blender/editors/sculpt_paint/paint_stroke.c @@ -995,7 +995,7 @@ static void stroke_done(bContext *C, wmOperator *op, PaintStroke *stroke) static bool curves_sculpt_brush_uses_spacing(const eBrushCurvesSculptTool tool) { - return ELEM(tool, CURVES_SCULPT_TOOL_ADD); + return ELEM(tool, CURVES_SCULPT_TOOL_ADD, CURVES_SCULPT_TOOL_DENSITY); } bool paint_space_stroke_enabled(Brush *br, ePaintMode mode) diff --git a/source/blender/makesdna/DNA_brush_enums.h b/source/blender/makesdna/DNA_brush_enums.h index f2cd49b6dea..adda23c26f2 100644 --- a/source/blender/makesdna/DNA_brush_enums.h +++ b/source/blender/makesdna/DNA_brush_enums.h @@ -469,6 +469,11 @@ typedef enum eBrushCurvesSculptTool { CURVES_SCULPT_TOOL_ADD = 3, CURVES_SCULPT_TOOL_GROW_SHRINK = 4, CURVES_SCULPT_TOOL_SELECTION_PAINT = 5, + CURVES_SCULPT_TOOL_PINCH = 6, + CURVES_SCULPT_TOOL_SMOOTH = 7, + CURVES_SCULPT_TOOL_PUFF = 8, + CURVES_SCULPT_TOOL_DENSITY = 9, + CURVES_SCULPT_TOOL_SLIDE = 10, } eBrushCurvesSculptTool; /** When #BRUSH_ACCUMULATE is used */ @@ -622,6 +627,12 @@ typedef enum eBrushCurvesSculptFlag { BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_POINT_COUNT = (1 << 4), } eBrushCurvesSculptFlag; +typedef enum eBrushCurvesSculptDensityMode { + BRUSH_CURVES_SCULPT_DENSITY_MODE_AUTO = 0, + BRUSH_CURVES_SCULPT_DENSITY_MODE_ADD = 1, + BRUSH_CURVES_SCULPT_DENSITY_MODE_REMOVE = 2, +} eBrushCurvesSculptDensityMode; + #define MAX_BRUSH_PIXEL_RADIUS 500 #define GP_MAX_BRUSH_PIXEL_RADIUS 1000 diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h index d13496b21f7..b24bb786593 100644 --- a/source/blender/makesdna/DNA_brush_types.h +++ b/source/blender/makesdna/DNA_brush_types.h @@ -148,6 +148,13 @@ typedef struct BrushCurvesSculptSettings { float minimum_length; /** Length of newly added curves when it is not interpolated from other curves. */ float curve_length; + /** Minimum distance between curve root points used by the Density brush. */ + float minimum_distance; + /** How often the Density brush tries to add a new curve. */ + int density_add_attempts; + /** #eBrushCurvesSculptDensityMode. */ + uint8_t density_mode; + char _pad[7]; } BrushCurvesSculptSettings; typedef struct Brush { diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index 80a61543b5a..e0d55050c63 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -251,6 +251,11 @@ const EnumPropertyItem rna_enum_brush_curves_sculpt_tool_items[] = { {CURVES_SCULPT_TOOL_ADD, "ADD", ICON_BRUSH_CURVES_ADD, "Add Curves", ""}, {CURVES_SCULPT_TOOL_GROW_SHRINK, "GROW_SHRINK", ICON_BRUSH_CURVES_GROW_SHRINK, "Grow / Shrink Curves", ""}, {CURVES_SCULPT_TOOL_SELECTION_PAINT, "SELECTION_PAINT", ICON_BRUSH_PAINT_SELECT, "Paint Selection", ""}, + {CURVES_SCULPT_TOOL_PINCH, "PINCH", ICON_BRUSH_CURVES_PINCH, "Pinch Curves", ""}, + {CURVES_SCULPT_TOOL_SMOOTH, "SMOOTH", ICON_BRUSH_CURVES_SMOOTH, "Smooth Curves", ""}, + {CURVES_SCULPT_TOOL_PUFF, "PUFF", ICON_BRUSH_CURVES_PUFF, "Puff Curves", ""}, + {CURVES_SCULPT_TOOL_DENSITY, "DENSITY", ICON_BRUSH_CURVES_DENSITY, "Density Curves", ""}, + {CURVES_SCULPT_TOOL_SLIDE, "SLIDE", ICON_BRUSH_CURVES_SLIDE, "Slide Curves", ""}, {0, NULL, 0, NULL, NULL}, }; /* clang-format on */ @@ -889,6 +894,7 @@ static const EnumPropertyItem *rna_Brush_direction_itemf(bContext *C, switch (me->curves_sculpt_tool) { case CURVES_SCULPT_TOOL_GROW_SHRINK: case CURVES_SCULPT_TOOL_SELECTION_PAINT: + case CURVES_SCULPT_TOOL_PINCH: return prop_direction_items; default: return DummyRNA_DEFAULT_items; @@ -1949,6 +1955,26 @@ static void rna_def_curves_sculpt_options(BlenderRNA *brna) StructRNA *srna; PropertyRNA *prop; + static const EnumPropertyItem density_mode_items[] = { + {BRUSH_CURVES_SCULPT_DENSITY_MODE_AUTO, + "AUTO", + ICON_AUTO, + "Auto", + "Either add or remove curves depending on the minimum distance of the curves under the " + "cursor"}, + {BRUSH_CURVES_SCULPT_DENSITY_MODE_ADD, + "ADD", + ICON_ADD, + "Add", + "Add new curves between existing curves, taking the minimum distance into account"}, + {BRUSH_CURVES_SCULPT_DENSITY_MODE_REMOVE, + "REMOVE", + ICON_REMOVE, + "Remove", + "Remove curves whose root points are too close"}, + {0, NULL, 0, NULL, NULL}, + }; + srna = RNA_def_struct(brna, "BrushCurvesSculptSettings", NULL); RNA_def_struct_sdna(srna, "BrushCurvesSculptSettings"); RNA_def_struct_ui_text(srna, "Curves Sculpt Brush Settings", ""); @@ -1997,6 +2023,22 @@ static void rna_def_curves_sculpt_options(BlenderRNA *brna) prop, "Curve Length", "Length of newly added curves when it is not interpolated from other curves"); + + prop = RNA_def_property(srna, "minimum_distance", PROP_FLOAT, PROP_DISTANCE); + RNA_def_property_range(prop, 0.0f, FLT_MAX); + RNA_def_property_ui_range(prop, 0.0, 1000.0f, 0.001, 2); + RNA_def_property_ui_text( + prop, "Minimum Distance", "Goal distance between curve roots for the Density brush"); + + prop = RNA_def_property(srna, "density_add_attempts", PROP_INT, PROP_NONE); + RNA_def_property_range(prop, 0, INT32_MAX); + RNA_def_property_ui_text( + prop, "Density Add Attempts", "How many times the Density brush tries to add a new curve"); + + prop = RNA_def_property(srna, "density_mode", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, density_mode_items); + RNA_def_property_ui_text( + prop, "Density Mode", "Determines whether the brush adds or removes curves"); } static void rna_def_brush(BlenderRNA *brna) -- cgit v1.2.3 From 92d7f9ac56e0ff1e65c364487542dfb7c32a0a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Am=C3=A9lie=20Fondevilla?= Date: Thu, 30 Jun 2022 15:16:05 +0200 Subject: Animation: Add GP layers in regular Dopesheet Grease Pencil animation channels are now also shown in the Dopesheet mode of the Dopesheet editor and in the Timeline. Grease pencil related events are now listened not only by container `SACTCONT_GPENCIL` (Grease Pencil Dopesheet), but also `SACTCONT_DOPESHEET` (main Dopesheet), and `SACTCONT_TIMELINE` (timeline). A new Animation Filter flag was added: `ANIMFILTER_FCURVESONLY`. For now this only filters out Grease Pencil Layer channels. **Implemented:** - Preview range set: now only considers selected Grease Pencil keyframes when `onlySel` parameter is true. Not only this allows the operator to work with grease pencil keyframes in main dopesheet, but it also fixes the operator in the Grease Pencil dopesheet. - Translation: allocation (and freeing) of specific memory for translation of Grease Pencil keyframes. - Copy/Paste: call to both Fcurve and GPencil operators, to allow for mixed selection. Errors are only reported when both the FCurve and GPencil functions fail to paste anything. - Keyframe Type change and Insert Keyframe: removed some code here to unify Grease Pencil dopesheet and main dopesheet code. - Jump, Snap, Mirror, Select all/box/lasso/circle, Select left/right, Clickselect: account for Grease Pencil channels within the channels loop, no need for `ANIMFILTER_FCURVESONLY` there. **Not Implemented:** - Graph-related operators. The filter `ANIMFILTER_FCURVESONLY` is naively added to all graph-related operators, meaning more-or-less all operators that used `ANIMFILTER_CURVE_VISIBLE`. - Select linked: is for F-curves channel only - Select more/less: not yet implemented for grease pencil layers. - Clean Keys, Sample, Extrapolation, Interpolation, Easing, and Handle type change: work on Fcurve-channels only, so the `ANIMFILTER_FCURVESONLY` filter is activated Graying out these operators (when no fcurve keyframe is selected) can be done with custom poll functions BUT may affect performance. This is NOT done in this patch. **Dopesheet Summary Selection:** The main summary of the dopesheet now also takes into account Grease Pencil keyframes, using some nasty copy/pasting of code, as explained [on devtalk](https://devtalk.blender.org/t/gpencil-layers-integration-in-main-dopesheet-selection-issue/24527). It works, but may be improved, providing some deeper changes. Reviewed By: mendio, pepeland, sybren Maniphest Tasks: T97477 Differential Revision: https://developer.blender.org/D15003 --- .../editors/animation/anim_channels_defines.c | 2 +- .../blender/editors/animation/anim_channels_edit.c | 2 +- source/blender/editors/animation/anim_filter.c | 3 + .../blender/editors/animation/keyframes_general.c | 18 +- .../blender/editors/gpencil/editaction_gpencil.c | 33 +- source/blender/editors/include/ED_anim_api.h | 4 + source/blender/editors/include/ED_gpencil.h | 5 + source/blender/editors/include/ED_keyframes_edit.h | 20 +- source/blender/editors/screen/screen_context.c | 14 +- source/blender/editors/space_action/action_edit.c | 376 +++++++++++---------- .../blender/editors/space_action/action_select.c | 222 ++++++------ source/blender/editors/space_action/space_action.c | 4 +- source/blender/editors/space_graph/graph_draw.c | 5 +- source/blender/editors/space_graph/graph_edit.c | 117 ++++--- source/blender/editors/space_graph/graph_ops.c | 15 +- source/blender/editors/space_graph/graph_select.c | 32 +- .../blender/editors/space_graph/graph_slider_ops.c | 4 +- source/blender/editors/space_graph/graph_utils.c | 8 +- source/blender/editors/space_graph/graph_view.c | 7 +- source/blender/editors/transform/transform.h | 2 + .../editors/transform/transform_convert_action.c | 101 +++--- .../editors/transform/transform_convert_graph.c | 9 +- 22 files changed, 547 insertions(+), 456 deletions(-) diff --git a/source/blender/editors/animation/anim_channels_defines.c b/source/blender/editors/animation/anim_channels_defines.c index 9eeabf2d05e..7dac1286526 100644 --- a/source/blender/editors/animation/anim_channels_defines.c +++ b/source/blender/editors/animation/anim_channels_defines.c @@ -3123,7 +3123,7 @@ static bAnimChannelType ACF_DSSIMULATION = { /* TODO: just get this from RNA? */ static int acf_dsgpencil_icon(bAnimListElem *UNUSED(ale)) { - return ICON_GREASEPENCIL; + return ICON_OUTLINER_DATA_GREASEPENCIL; } /* Get the appropriate flag(s) for the setting when it is valid. */ diff --git a/source/blender/editors/animation/anim_channels_edit.c b/source/blender/editors/animation/anim_channels_edit.c index b223a1493fd..8464f280c29 100644 --- a/source/blender/editors/animation/anim_channels_edit.c +++ b/source/blender/editors/animation/anim_channels_edit.c @@ -2033,7 +2033,7 @@ static void setflag_anim_channels(bAnimContext *ac, if ((ac->spacetype == SPACE_GRAPH) && (ac->regiontype != RGN_TYPE_CHANNELS)) { /* graph editor (case 2) */ filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_CHANNELS | ANIMFILTER_CURVE_VISIBLE | - ANIMFILTER_NODUPLIS); + ANIMFILTER_FCURVESONLY | ANIMFILTER_NODUPLIS); } else { /* standard case */ diff --git a/source/blender/editors/animation/anim_filter.c b/source/blender/editors/animation/anim_filter.c index a75944fa2f2..e20932fa53e 100644 --- a/source/blender/editors/animation/anim_filter.c +++ b/source/blender/editors/animation/anim_filter.c @@ -1925,6 +1925,9 @@ static size_t animdata_filter_ds_gpencil( tmp_items += animfilter_block_data(ac, &tmp_data, ads, &gpd->id, filter_mode); /* add Grease Pencil layers */ + if (!(filter_mode & ANIMFILTER_FCURVESONLY)) { + tmp_items += animdata_filter_gpencil_layers_data(&tmp_data, ads, gpd, filter_mode); + } /* TODO: do these need a separate expander? * XXX: what order should these go in? */ diff --git a/source/blender/editors/animation/keyframes_general.c b/source/blender/editors/animation/keyframes_general.c index 00e2f221117..30f500f9674 100644 --- a/source/blender/editors/animation/keyframes_general.c +++ b/source/blender/editors/animation/keyframes_general.c @@ -1217,11 +1217,11 @@ const EnumPropertyItem rna_enum_keyframe_paste_merge_items[] = { {0, NULL, 0, NULL, NULL}, }; -short paste_animedit_keys(bAnimContext *ac, - ListBase *anim_data, - const eKeyPasteOffset offset_mode, - const eKeyMergeMode merge_mode, - bool flip) +eKeyPasteError paste_animedit_keys(bAnimContext *ac, + ListBase *anim_data, + const eKeyPasteOffset offset_mode, + const eKeyMergeMode merge_mode, + bool flip) { bAnimListElem *ale; @@ -1235,13 +1235,11 @@ short paste_animedit_keys(bAnimContext *ac, /* check if buffer is empty */ if (BLI_listbase_is_empty(&animcopybuf)) { - BKE_report(ac->reports, RPT_ERROR, "No animation data in buffer to paste"); - return -1; + return KEYFRAME_PASTE_NOTHING_TO_PASTE; } if (BLI_listbase_is_empty(anim_data)) { - BKE_report(ac->reports, RPT_ERROR, "No selected F-Curves to paste into"); - return -1; + return KEYFRAME_PASTE_NOWHERE_TO_PASTE; } /* methods of offset */ @@ -1335,7 +1333,7 @@ short paste_animedit_keys(bAnimContext *ac, ANIM_animdata_update(ac, anim_data); - return 0; + return KEYFRAME_PASTE_OK; } /* **************************************************** */ diff --git a/source/blender/editors/gpencil/editaction_gpencil.c b/source/blender/editors/gpencil/editaction_gpencil.c index 949043e4be1..c7d0d9e0e35 100644 --- a/source/blender/editors/gpencil/editaction_gpencil.c +++ b/source/blender/editors/gpencil/editaction_gpencil.c @@ -214,6 +214,18 @@ void ED_gpencil_layer_frames_select_region(KeyframeEditData *ked, } } +void ED_gpencil_set_active_channel(bGPdata *gpd, bGPDlayer *gpl) +{ + gpl->flag |= GP_LAYER_SELECT; + + /* Update other layer status. */ + if (BKE_gpencil_layer_active_get(gpd) != gpl) { + BKE_gpencil_layer_active_set(gpd, gpl); + BKE_gpencil_layer_autolock_set(gpd, false); + WM_main_add_notifier(NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + } +} + /* ***************************************** */ /* Frame Editing Tools */ @@ -316,8 +328,13 @@ bool ED_gpencil_anim_copybuf_copy(bAnimContext *ac) filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); - /* assume that each of these is a GP layer */ for (ale = anim_data.first; ale; ale = ale->next) { + /* This function only deals with grease pencil layer frames. + * This check is needed in the case of a call from the main dopesheet. */ + if (ale->type != ANIMTYPE_GPLAYER) { + continue; + } + ListBase copied_frames = {NULL, NULL}; bGPDlayer *gpl = (bGPDlayer *)ale->data; @@ -359,14 +376,8 @@ bool ED_gpencil_anim_copybuf_copy(bAnimContext *ac) /* clean up */ ANIM_animdata_freelist(&anim_data); - /* check if anything ended up in the buffer */ - if (ELEM(NULL, gpencil_anim_copybuf.first, gpencil_anim_copybuf.last)) { - BKE_report(ac->reports, RPT_ERROR, "No keyframes copied to keyframes copy/paste buffer"); - return false; - } - /* report success */ - return true; + return !BLI_listbase_is_empty(&gpencil_anim_copybuf); } bool ED_gpencil_anim_copybuf_paste(bAnimContext *ac, const short offset_mode) @@ -381,7 +392,6 @@ bool ED_gpencil_anim_copybuf_paste(bAnimContext *ac, const short offset_mode) /* check if buffer is empty */ if (BLI_listbase_is_empty(&gpencil_anim_copybuf)) { - BKE_report(ac->reports, RPT_ERROR, "No data in buffer to paste"); return false; } @@ -414,6 +424,11 @@ bool ED_gpencil_anim_copybuf_paste(bAnimContext *ac, const short offset_mode) /* from selected channels */ for (ale = anim_data.first; ale; ale = ale->next) { + /* only deal with GPlayers (case of calls from general dopesheet) */ + if (ale->type != ANIMTYPE_GPLAYER) { + continue; + } + bGPDlayer *gpld = (bGPDlayer *)ale->data; bGPDlayer *gpls = NULL; bGPDframe *gpfs, *gpf; diff --git a/source/blender/editors/include/ED_anim_api.h b/source/blender/editors/include/ED_anim_api.h index da40eef87fd..ac3b4133007 100644 --- a/source/blender/editors/include/ED_anim_api.h +++ b/source/blender/editors/include/ED_anim_api.h @@ -324,11 +324,15 @@ typedef enum eAnimFilter_Flags { /** duplicate entries for animation data attached to multi-user blocks must not occur */ ANIMFILTER_NODUPLIS = (1 << 11), + /** avoid channel that does not have any F-curve data */ + ANIMFILTER_FCURVESONLY = (1 << 12), + /** for checking if we should keep some collapsed channel around (internal use only!) */ ANIMFILTER_TMP_PEEK = (1 << 30), /** Ignore ONLYSEL flag from #bDopeSheet.filterflag (internal use only!) */ ANIMFILTER_TMP_IGNORE_ONLYSEL = (1u << 31), + } eAnimFilter_Flags; /** \} */ diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h index d844bd59c9d..b6488d6da56 100644 --- a/source/blender/editors/include/ED_gpencil.h +++ b/source/blender/editors/include/ED_gpencil.h @@ -280,6 +280,11 @@ void ED_gpencil_select_frames(struct bGPDlayer *gpl, short select_mode); */ void ED_gpencil_select_frame(struct bGPDlayer *gpl, int selx, short select_mode); +/** + * Set the layer's channel as active + */ +void ED_gpencil_set_active_channel(struct bGPdata *gpd, struct bGPDlayer *gpl); + /** * Delete selected frames. */ diff --git a/source/blender/editors/include/ED_keyframes_edit.h b/source/blender/editors/include/ED_keyframes_edit.h index 163797d395d..9596c3a7fed 100644 --- a/source/blender/editors/include/ED_keyframes_edit.h +++ b/source/blender/editors/include/ED_keyframes_edit.h @@ -229,6 +229,16 @@ typedef enum eKeyMergeMode { KEYFRAME_PASTE_MERGE_OVER_RANGE_ALL, } eKeyMergeMode; +/* Possible errors occurring while pasting keys. */ +typedef enum eKeyPasteError { + /* No errors occurred */ + KEYFRAME_PASTE_OK, + /* Nothing was copied */ + KEYFRAME_PASTE_NOTHING_TO_PASTE, + /* No F-curves was selected to paste into*/ + KEYFRAME_PASTE_NOWHERE_TO_PASTE +} eKeyPasteError; + /** \} */ /* -------------------------------------------------------------------- */ @@ -416,11 +426,11 @@ void sample_fcurve(struct FCurve *fcu); void ANIM_fcurves_copybuf_free(void); short copy_animedit_keys(struct bAnimContext *ac, ListBase *anim_data); -short paste_animedit_keys(struct bAnimContext *ac, - ListBase *anim_data, - eKeyPasteOffset offset_mode, - eKeyMergeMode merge_mode, - bool flip); +eKeyPasteError paste_animedit_keys(struct bAnimContext *ac, + ListBase *anim_data, + eKeyPasteOffset offset_mode, + eKeyMergeMode merge_mode, + bool flip); /* ************************************************ */ diff --git a/source/blender/editors/screen/screen_context.c b/source/blender/editors/screen/screen_context.c index fe69759481b..239113c28a4 100644 --- a/source/blender/editors/screen/screen_context.c +++ b/source/blender/editors/screen/screen_context.c @@ -995,7 +995,7 @@ static eContextResult screen_ctx_sel_actions_impl(const bContext *C, switch (ac.spacetype) { case SPACE_GRAPH: - filter |= ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_SEL; + filter |= ANIMFILTER_FCURVESONLY | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_SEL; break; case SPACE_ACTION: @@ -1059,8 +1059,9 @@ static eContextResult screen_ctx_sel_edit_fcurves_(const bContext *C, ListBase anim_data = {NULL, NULL}; int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_NODUPLIS) | - (ac.spacetype == SPACE_GRAPH ? ANIMFILTER_CURVE_VISIBLE : - ANIMFILTER_LIST_VISIBLE) | + (ac.spacetype == SPACE_GRAPH ? + (ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY) : + ANIMFILTER_LIST_VISIBLE) | extra_filter; ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); @@ -1104,7 +1105,7 @@ static eContextResult screen_ctx_active_editable_fcurve(const bContext *C, ListBase anim_data = {NULL, NULL}; int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_ACTIVE | ANIMFILTER_FOREDIT | - ANIMFILTER_CURVE_VISIBLE); + ANIMFILTER_FCURVESONLY | ANIMFILTER_CURVE_VISIBLE); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); @@ -1130,8 +1131,9 @@ static eContextResult screen_ctx_selected_editable_keyframes(const bContext *C, /* Use keyframes from editable selected FCurves. */ int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_NODUPLIS | ANIMFILTER_FOREDIT | ANIMFILTER_SEL) | - (ac.spacetype == SPACE_GRAPH ? ANIMFILTER_CURVE_VISIBLE : - ANIMFILTER_LIST_VISIBLE); + (ac.spacetype == SPACE_GRAPH ? + (ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY) : + ANIMFILTER_LIST_VISIBLE); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); diff --git a/source/blender/editors/space_action/action_edit.c b/source/blender/editors/space_action/action_edit.c index 87a271543d1..2ac10736eea 100644 --- a/source/blender/editors/space_action/action_edit.c +++ b/source/blender/editors/space_action/action_edit.c @@ -158,8 +158,7 @@ static bool get_keyframe_extents(bAnimContext *ac, float *min, float *max, const /* get data to filter, from Action or Dopesheet */ /* XXX: what is sel doing here?! * Commented it, was breaking things (eg. the "auto preview range" tool). */ - filter = (ANIMFILTER_DATA_VISIBLE | - ANIMFILTER_LIST_VISIBLE /*| ANIMFILTER_SEL */ /*| ANIMFILTER_CURVESONLY*/ | + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE /*| ANIMFILTER_SEL */ | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); @@ -178,10 +177,12 @@ static bool get_keyframe_extents(bAnimContext *ac, float *min, float *max, const /* Find gp-frame which is less than or equal to current-frame. */ for (gpf = gpl->frames.first; gpf; gpf = gpf->next) { - const float framenum = (float)gpf->framenum; - *min = min_ff(*min, framenum); - *max = max_ff(*max, framenum); - found = true; + if (!onlySel || (gpf->flag & GP_FRAME_SELECT)) { + const float framenum = (float)gpf->framenum; + *min = min_ff(*min, framenum); + *max = max_ff(*max, framenum); + found = true; + } } } else if (ale->datatype == ALE_MASKLAY) { @@ -498,7 +499,7 @@ static short copy_action_keys(bAnimContext *ac) ANIM_fcurves_copybuf_free(); /* filter data */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE /*| ANIMFILTER_CURVESONLY*/ | + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FCURVESONLY | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); @@ -511,13 +512,13 @@ static short copy_action_keys(bAnimContext *ac) return ok; } -static short paste_action_keys(bAnimContext *ac, - const eKeyPasteOffset offset_mode, - const eKeyMergeMode merge_mode, - bool flip) +static eKeyPasteError paste_action_keys(bAnimContext *ac, + const eKeyPasteOffset offset_mode, + const eKeyMergeMode merge_mode, + bool flip) { ListBase anim_data = {NULL, NULL}; - int filter, ok = 0; + int filter; /* filter data * - First time we try to filter more strictly, allowing only selected channels @@ -525,15 +526,15 @@ static short paste_action_keys(bAnimContext *ac, * - Second time, we loosen things up if nothing was found the first time, allowing * users to just paste keyframes back into the original curve again T31670. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | - ANIMFILTER_FOREDIT /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY | ANIMFILTER_NODUPLIS); if (ANIM_animdata_filter(ac, &anim_data, filter | ANIMFILTER_SEL, ac->data, ac->datatype) == 0) { ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); } /* paste keyframes */ - ok = paste_animedit_keys(ac, &anim_data, offset_mode, merge_mode, flip); + const eKeyPasteError ok = paste_animedit_keys(ac, &anim_data, offset_mode, merge_mode, flip); /* clean up */ ANIM_animdata_freelist(&anim_data); @@ -555,7 +556,8 @@ static int actkeys_copy_exec(bContext *C, wmOperator *op) /* copy keyframes */ if (ac.datatype == ANIMCONT_GPENCIL) { if (ED_gpencil_anim_copybuf_copy(&ac) == false) { - /* Nothing got copied - An error about this should be been logged already */ + /* check if anything ended up in the buffer */ + BKE_report(op->reports, RPT_ERROR, "No keyframes copied to keyframes copy/paste buffer"); return OPERATOR_CANCELLED; } } @@ -565,7 +567,11 @@ static int actkeys_copy_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } else { - if (copy_action_keys(&ac)) { + /* Both copy function needs to be evaluated to account for mixed selection */ + const short kf_empty = copy_action_keys(&ac); + const bool gpf_ok = ED_gpencil_anim_copybuf_copy(&ac); + + if (kf_empty && !gpf_ok) { BKE_report(op->reports, RPT_ERROR, "No keyframes copied to keyframes copy/paste buffer"); return OPERATOR_CANCELLED; } @@ -597,6 +603,8 @@ static int actkeys_paste_exec(bContext *C, wmOperator *op) const eKeyMergeMode merge_mode = RNA_enum_get(op->ptr, "merge"); const bool flipped = RNA_boolean_get(op->ptr, "flipped"); + bool gpframes_inbuf = false; + /* get editor data */ if (ANIM_animdata_get_context(C, &ac) == 0) { return OPERATOR_CANCELLED; @@ -608,7 +616,7 @@ static int actkeys_paste_exec(bContext *C, wmOperator *op) /* paste keyframes */ if (ac.datatype == ANIMCONT_GPENCIL) { if (ED_gpencil_anim_copybuf_paste(&ac, offset_mode) == false) { - /* An error occurred - Reports should have been fired already */ + BKE_report(op->reports, RPT_ERROR, "No data in buffer to paste"); return OPERATOR_CANCELLED; } } @@ -620,14 +628,31 @@ static int actkeys_paste_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } else { + /* Both paste function needs to be evaluated to account for mixed selection */ + const eKeyPasteError kf_empty = paste_action_keys(&ac, offset_mode, merge_mode, flipped); /* non-zero return means an error occurred while trying to paste */ - if (paste_action_keys(&ac, offset_mode, merge_mode, flipped)) { - return OPERATOR_CANCELLED; + gpframes_inbuf = ED_gpencil_anim_copybuf_paste(&ac, offset_mode); + + /* Only report an error if nothing was pasted, i.e. when both FCurve and GPencil failed. */ + if (!gpframes_inbuf) { + switch (kf_empty) { + case KEYFRAME_PASTE_OK: + /* FCurve paste was ok, so it's all good. */ + break; + + case KEYFRAME_PASTE_NOWHERE_TO_PASTE: + BKE_report(op->reports, RPT_ERROR, "No selected F-Curves to paste into"); + return OPERATOR_CANCELLED; + + case KEYFRAME_PASTE_NOTHING_TO_PASTE: + BKE_report(op->reports, RPT_ERROR, "No data in buffer to paste"); + return OPERATOR_CANCELLED; + } } } /* Grease Pencil needs extra update to refresh the added keyframes. */ - if (ac.datatype == ANIMCONT_GPENCIL) { + if (ac.datatype == ANIMCONT_GPENCIL || gpframes_inbuf) { WM_event_add_notifier(C, NC_GPENCIL | ND_DATA, NULL); } /* set notifier that keyframes have changed */ @@ -697,94 +722,86 @@ static const EnumPropertyItem prop_actkeys_insertkey_types[] = { {0, NULL, 0, NULL, NULL}, }; -/* this function is responsible for inserting new keyframes */ -static void insert_action_keys(bAnimContext *ac, short mode) +static void insert_gpencil_key(bAnimContext *ac, + bAnimListElem *ale, + const eGP_GetFrame_Mode add_frame_mode, + bGPdata **gpd_old) { - ListBase anim_data = {NULL, NULL}; - ListBase nla_cache = {NULL, NULL}; - bAnimListElem *ale; - int filter; + Scene *scene = ac->scene; + bGPdata *gpd = (bGPdata *)ale->id; + bGPDlayer *gpl = (bGPDlayer *)ale->data; + BKE_gpencil_layer_frame_get(gpl, CFRA, add_frame_mode); + /* Check if the gpd changes to tag only once. */ + if (gpd != *gpd_old) { + BKE_gpencil_tag(gpd); + *gpd_old = gpd; + } +} + +static void insert_fcurve_key(bAnimContext *ac, + bAnimListElem *ale, + const AnimationEvalContext anim_eval_context, + eInsertKeyFlags flag, + ListBase *nla_cache) +{ + FCurve *fcu = (FCurve *)ale->key_data; ReportList *reports = ac->reports; Scene *scene = ac->scene; ToolSettings *ts = scene->toolsettings; - eInsertKeyFlags flag; - /* filter data */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | - ANIMFILTER_FOREDIT /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS); - if (mode == 2) { - filter |= ANIMFILTER_SEL; - } - else if (mode == 3) { - filter |= ANIMFILTER_ACTGROUPED; + /* Read value from property the F-Curve represents, or from the curve only? + * - ale->id != NULL: + * Typically, this means that we have enough info to try resolving the path. + * + * - ale->owner != NULL: + * If this is set, then the path may not be resolvable from the ID alone, + * so it's easier for now to just read the F-Curve directly. + * (TODO: add the full-blown PointerRNA relative parsing case here...) + */ + if (ale->id && !ale->owner) { + insert_keyframe(ac->bmain, + reports, + ale->id, + NULL, + ((fcu->grp) ? (fcu->grp->name) : (NULL)), + fcu->rna_path, + fcu->array_index, + &anim_eval_context, + ts->keyframe_type, + nla_cache, + flag); } + else { + AnimData *adt = ANIM_nla_mapping_get(ac, ale); - ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); - - /* Init keyframing flag. */ - flag = ANIM_get_keyframing_flags(scene, true); - - /* insert keyframes */ - const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(ac->depsgraph, - (float)CFRA); - for (ale = anim_data.first; ale; ale = ale->next) { - FCurve *fcu = (FCurve *)ale->key_data; - - /* Read value from property the F-Curve represents, or from the curve only? - * - ale->id != NULL: - * Typically, this means that we have enough info to try resolving the path. - * - * - ale->owner != NULL: - * If this is set, then the path may not be resolvable from the ID alone, - * so it's easier for now to just read the F-Curve directly. - * (TODO: add the full-blown PointerRNA relative parsing case here...) - */ - if (ale->id && !ale->owner) { - insert_keyframe(ac->bmain, - reports, - ale->id, - NULL, - ((fcu->grp) ? (fcu->grp->name) : (NULL)), - fcu->rna_path, - fcu->array_index, - &anim_eval_context, - ts->keyframe_type, - &nla_cache, - flag); - } - else { - AnimData *adt = ANIM_nla_mapping_get(ac, ale); - - /* adjust current frame for NLA-scaling */ - float cfra = anim_eval_context.eval_time; - if (adt) { - cfra = BKE_nla_tweakedit_remap(adt, cfra, NLATIME_CONVERT_UNMAP); - } - - const float curval = evaluate_fcurve(fcu, cfra); - insert_vert_fcurve(fcu, cfra, curval, ts->keyframe_type, 0); + /* adjust current frame for NLA-scaling */ + float cfra = anim_eval_context.eval_time; + if (adt) { + cfra = BKE_nla_tweakedit_remap(adt, cfra, NLATIME_CONVERT_UNMAP); } - ale->update |= ANIM_UPDATE_DEFAULT; + const float curval = evaluate_fcurve(fcu, cfra); + insert_vert_fcurve(fcu, cfra, curval, ts->keyframe_type, 0); } - BKE_animsys_free_nla_keyframing_context_cache(&nla_cache); - - ANIM_animdata_update(ac, &anim_data); - ANIM_animdata_freelist(&anim_data); + ale->update |= ANIM_UPDATE_DEFAULT; } -/* this function is for inserting new grease pencil frames */ -static void insert_gpencil_keys(bAnimContext *ac, short mode) +/* this function is responsible for inserting new keyframes */ +static void insert_action_keys(bAnimContext *ac, short mode) { ListBase anim_data = {NULL, NULL}; + ListBase nla_cache = {NULL, NULL}; bAnimListElem *ale; int filter; Scene *scene = ac->scene; ToolSettings *ts = scene->toolsettings; + eInsertKeyFlags flag; + eGP_GetFrame_Mode add_frame_mode; + bGPdata *gpd_old = NULL; /* filter data */ filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | @@ -792,30 +809,43 @@ static void insert_gpencil_keys(bAnimContext *ac, short mode) if (mode == 2) { filter |= ANIMFILTER_SEL; } + else if (mode == 3) { + filter |= ANIMFILTER_ACTGROUPED; + } ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); - /* add a copy or a blank frame? */ + /* Init keyframing flag. */ + flag = ANIM_get_keyframing_flags(scene, true); + + /* GPLayers specific flags */ if (ts->gpencil_flags & GP_TOOL_FLAG_RETAIN_LAST) { - add_frame_mode = GP_GETFRAME_ADD_COPY; /* XXX: actframe may not be what we want? */ + add_frame_mode = GP_GETFRAME_ADD_COPY; } else { add_frame_mode = GP_GETFRAME_ADD_NEW; } - /* Insert gp frames. */ - bGPdata *gpd_old = NULL; + /* insert keyframes */ + const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(ac->depsgraph, + (float)CFRA); for (ale = anim_data.first; ale; ale = ale->next) { - bGPdata *gpd = (bGPdata *)ale->id; - bGPDlayer *gpl = (bGPDlayer *)ale->data; - BKE_gpencil_layer_frame_get(gpl, CFRA, add_frame_mode); - /* Check if the gpd changes to tag only once. */ - if (gpd != gpd_old) { - BKE_gpencil_tag(gpd); - gpd_old = gpd; + switch (ale->type) { + case ANIMTYPE_GPLAYER: + insert_gpencil_key(ac, ale, add_frame_mode, &gpd_old); + break; + + case ANIMTYPE_FCURVE: + insert_fcurve_key(ac, ale, anim_eval_context, flag, &nla_cache); + break; + + default: + BLI_assert_msg(false, "Keys cannot be inserted into this animation type."); } } + BKE_animsys_free_nla_keyframing_context_cache(&nla_cache); + ANIM_animdata_update(ac, &anim_data); ANIM_animdata_freelist(&anim_data); } @@ -841,12 +871,7 @@ static int actkeys_insertkey_exec(bContext *C, wmOperator *op) mode = RNA_enum_get(op->ptr, "type"); /* insert keyframes */ - if (ac.datatype == ANIMCONT_GPENCIL) { - insert_gpencil_keys(&ac, mode); - } - else { - insert_action_keys(&ac, mode); - } + insert_action_keys(&ac, mode); /* set notifier that keyframes have changed */ if (ac.datatype == ANIMCONT_GPENCIL) { @@ -886,14 +911,8 @@ static bool duplicate_action_keys(bAnimContext *ac) bool changed = false; /* filter data */ - if (ELEM(ac->datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) { - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_NODUPLIS); - } - else { - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | - ANIMFILTER_FOREDIT /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS); - } + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* loop through filtered data and delete selected keys */ @@ -968,14 +987,8 @@ static bool delete_action_keys(bAnimContext *ac) bool changed_final = false; /* filter data */ - if (ELEM(ac->datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) { - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_NODUPLIS); - } - else { - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | - ANIMFILTER_FOREDIT /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS); - } + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* loop through filtered data and delete selected keys */ @@ -1063,7 +1076,7 @@ static void clean_action_keys(bAnimContext *ac, float thresh, bool clean_chan) /* filter data */ filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_SEL /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS); + ANIMFILTER_SEL | ANIMFILTER_FCURVESONLY | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* loop through filtered data and clean curves */ @@ -1139,8 +1152,8 @@ static void sample_action_keys(bAnimContext *ac) int filter; /* filter data */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | - ANIMFILTER_FOREDIT /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* Loop through filtered data and add keys between selected keyframes on every frame. */ @@ -1238,7 +1251,7 @@ static void setexpo_action_keys(bAnimContext *ac, short mode) /* filter data */ filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_SEL /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS); + ANIMFILTER_SEL | ANIMFILTER_FCURVESONLY | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* loop through setting mode per F-Curve */ @@ -1352,7 +1365,8 @@ static int actkeys_ipo_exec(bContext *C, wmOperator *op) /* set handle type */ ANIM_animdata_keyframe_callback(&ac, (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | - ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS), + ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS | + ANIMFILTER_FCURVESONLY), ANIM_editkeyframes_ipo(mode)); /* set notifier that keyframe properties have changed */ @@ -1401,7 +1415,8 @@ static int actkeys_easing_exec(bContext *C, wmOperator *op) /* set handle type */ ANIM_animdata_keyframe_callback(&ac, (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | - ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS), + ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS | + ANIMFILTER_FCURVESONLY), ANIM_editkeyframes_easing(mode)); /* set notifier that keyframe properties have changed */ @@ -1444,8 +1459,8 @@ static void sethandles_action_keys(bAnimContext *ac, short mode) KeyframeEditFunc sel_cb = ANIM_editkeyframes_ok(BEZT_OK_SELECTED); /* filter data */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | - ANIMFILTER_FOREDIT /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* Loop through setting flags for handles @@ -1527,8 +1542,8 @@ static void setkeytype_action_keys(bAnimContext *ac, short mode) KeyframeEditFunc set_cb = ANIM_editkeyframes_keytype(mode); /* filter data */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | - ANIMFILTER_FOREDIT /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* Loop through setting BezTriple interpolation @@ -1536,32 +1551,19 @@ static void setkeytype_action_keys(bAnimContext *ac, short mode) * Currently that's not necessary here. */ for (ale = anim_data.first; ale; ale = ale->next) { - ANIM_fcurve_keyframes_loop(NULL, ale->key_data, NULL, set_cb, NULL); - - ale->update |= ANIM_UPDATE_DEPS | ANIM_UPDATE_HANDLES; - } - - ANIM_animdata_update(ac, &anim_data); - ANIM_animdata_freelist(&anim_data); -} - -/* this function is responsible for setting the keyframe type for Grease Pencil frames */ -static void setkeytype_gpencil_keys(bAnimContext *ac, short mode) -{ - ListBase anim_data = {NULL, NULL}; - bAnimListElem *ale; - int filter; + switch (ale->type) { + case ANIMTYPE_GPLAYER: + ED_gpencil_layer_frames_keytype_set(ale->data, mode); + ale->update |= ANIM_UPDATE_DEPS; + break; - /* filter data */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_NODUPLIS); - ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); + case ANIMTYPE_FCURVE: + ANIM_fcurve_keyframes_loop(NULL, ale->key_data, NULL, set_cb, NULL); + ale->update |= ANIM_UPDATE_DEPS | ANIM_UPDATE_HANDLES; + break; - /* loop through each layer */ - for (ale = anim_data.first; ale; ale = ale->next) { - if (ale->type == ANIMTYPE_GPLAYER) { - ED_gpencil_layer_frames_keytype_set(ale->data, mode); - ale->update |= ANIM_UPDATE_DEPS; + default: + BLI_assert_msg(false, "Keytype cannot be set into this animation type."); } } @@ -1590,12 +1592,7 @@ static int actkeys_keytype_exec(bContext *C, wmOperator *op) mode = RNA_enum_get(op->ptr, "type"); /* set handle type */ - if (ac.datatype == ANIMCONT_GPENCIL) { - setkeytype_gpencil_keys(&ac, mode); - } - else { - setkeytype_action_keys(&ac, mode); - } + setkeytype_action_keys(&ac, mode); /* set notifier that keyframe properties have changed */ WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME_PROP, NULL); @@ -1653,19 +1650,44 @@ static int actkeys_framejump_exec(bContext *C, wmOperator *UNUSED(op)) /* init edit data */ /* loop over action data, averaging values */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE /*| ANIMFILTER_CURVESONLY */ | - ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); for (ale = anim_data.first; ale; ale = ale->next) { - AnimData *adt = ANIM_nla_mapping_get(&ac, ale); - if (adt) { - ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 1); - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, bezt_calc_average, NULL); - ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 1); - } - else { - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, bezt_calc_average, NULL); + switch (ale->datatype) { + case ALE_GPFRAME: { + bGPDlayer *gpl = ale->data; + bGPDframe *gpf; + + for (gpf = gpl->frames.first; gpf; gpf = gpf->next) { + /* only if selected */ + if (!(gpf->flag & GP_FRAME_SELECT)) { + continue; + } + /* store average time in float 1 (only do rounding at last step) */ + ked.f1 += gpf->framenum; + + /* increment number of items */ + ked.i1++; + } + break; + } + + case ALE_FCURVE: { + AnimData *adt = ANIM_nla_mapping_get(&ac, ale); + if (adt) { + ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 1); + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, bezt_calc_average, NULL); + ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 1); + } + else { + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, bezt_calc_average, NULL); + } + break; + } + + default: + BLI_assert_msg(false, "Cannot jump to keyframe into this animation type."); } } @@ -1742,8 +1764,8 @@ static void snap_action_keys(bAnimContext *ac, short mode) filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT); } else { - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | - ANIMFILTER_FOREDIT /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_NODUPLIS); } ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); @@ -1876,14 +1898,8 @@ static void mirror_action_keys(bAnimContext *ac, short mode) } /* filter data */ - if (ELEM(ac->datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) { - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_NODUPLIS); - } - else { - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | - ANIMFILTER_FOREDIT /*| ANIMFILTER_CURVESONLY*/ | ANIMFILTER_NODUPLIS); - } + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* mirror keyframes */ diff --git a/source/blender/editors/space_action/action_select.c b/source/blender/editors/space_action/action_select.c index aff888818e0..f5dc2104d66 100644 --- a/source/blender/editors/space_action/action_select.c +++ b/source/blender/editors/space_action/action_select.c @@ -235,13 +235,7 @@ static void deselect_action_keys(bAnimContext *ac, short test, short sel) KeyframeEditFunc test_cb, sel_cb; /* determine type-based settings */ - if (ELEM(ac->datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) { - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS); - } - else { - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE /*| ANIMFILTER_CURVESONLY*/ | - ANIMFILTER_NODUPLIS); - } + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS); /* filter data */ ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); @@ -416,7 +410,7 @@ static void box_select_elem( break; } - if (ale->type == ANIMTYPE_SUMMARY && ELEM(ac->datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) { + if (ale->type == ANIMTYPE_SUMMARY) { ListBase anim_data = {NULL, NULL}; ANIM_animdata_filter(ac, &anim_data, ANIMFILTER_DATA_VISIBLE, ac->data, ac->datatype); @@ -428,8 +422,10 @@ static void box_select_elem( ANIM_animdata_freelist(&anim_data); } - ANIM_animchannel_keyframes_loop( - &sel_data->ked, ac->ads, ale, sel_data->ok_cb, sel_data->select_cb, NULL); + if (!ELEM(ac->datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) { + ANIM_animchannel_keyframes_loop( + &sel_data->ked, ac->ads, ale, sel_data->ok_cb, sel_data->select_cb, NULL); + } } } } @@ -658,7 +654,7 @@ static void region_select_elem(RegionSelectData *sel_data, bAnimListElem *ale, b break; } - if (ale->type == ANIMTYPE_SUMMARY && ELEM(ac->datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) { + if (ale->type == ANIMTYPE_SUMMARY) { ListBase anim_data = {NULL, NULL}; ANIM_animdata_filter(ac, &anim_data, ANIMFILTER_DATA_VISIBLE, ac->data, ac->datatype); @@ -670,8 +666,10 @@ static void region_select_elem(RegionSelectData *sel_data, bAnimListElem *ale, b ANIM_animdata_freelist(&anim_data); } - ANIM_animchannel_keyframes_loop( - &sel_data->ked, ac->ads, ale, sel_data->ok_cb, sel_data->select_cb, NULL); + if (!ELEM(ac->datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) { + ANIM_animchannel_keyframes_loop( + &sel_data->ked, ac->ads, ale, sel_data->ok_cb, sel_data->select_cb, NULL); + } } } } @@ -946,28 +944,36 @@ static void markers_selectkeys_between(bAnimContext *ac) ked.f2 = max; /* filter data */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE /*| ANIMFILTER_CURVESONLY */ | - ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* select keys in-between */ for (ale = anim_data.first; ale; ale = ale->next) { - AnimData *adt = ANIM_nla_mapping_get(ac, ale); + switch (ale->type) { + case ANIMTYPE_GPLAYER: + ED_gpencil_layer_frames_select_box(ale->data, min, max, SELECT_ADD); + ale->update |= ANIM_UPDATE_DEPS; + break; - if (adt) { - ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 1); - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, ok_cb, select_cb, NULL); - ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 1); - } - else if (ale->type == ANIMTYPE_GPLAYER) { - ED_gpencil_layer_frames_select_box(ale->data, min, max, SELECT_ADD); - ale->update |= ANIM_UPDATE_DEPS; - } - else if (ale->type == ANIMTYPE_MASKLAYER) { - ED_masklayer_frames_select_box(ale->data, min, max, SELECT_ADD); - } - else { - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, ok_cb, select_cb, NULL); + case ANIMTYPE_MASKLAYER: + ED_masklayer_frames_select_box(ale->data, min, max, SELECT_ADD); + break; + + case ANIMTYPE_FCURVE: { + AnimData *adt = ANIM_nla_mapping_get(ac, ale); + if (adt) { + ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 1); + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, ok_cb, select_cb, NULL); + ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 1); + } + else { + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, ok_cb, select_cb, NULL); + } + break; + } + + default: + BLI_assert_msg(false, "Keys cannot be selected into this animation type."); } } @@ -1000,11 +1006,16 @@ static void columnselect_action_keys(bAnimContext *ac, short mode) } } else { - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE /*| ANIMFILTER_CURVESONLY*/); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); for (ale = anim_data.first; ale; ale = ale->next) { - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, bezt_to_cfraelem, NULL); + if (ale->datatype == ALE_GPFRAME) { + ED_gpencil_layer_make_cfra_list(ale->data, &ked.list, 1); + } + else { + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, bezt_to_cfraelem, NULL); + } } } ANIM_animdata_freelist(&anim_data); @@ -1033,12 +1044,7 @@ static void columnselect_action_keys(bAnimContext *ac, short mode) /* loop through all of the keys and select additional keyframes * based on the keys found to be selected above */ - if (ELEM(ac->datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) { - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE); - } - else { - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE /*| ANIMFILTER_CURVESONLY*/); - } + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); for (ale = anim_data.first; ale; ale = ale->next) { @@ -1144,7 +1150,7 @@ static int actkeys_select_linked_exec(bContext *C, wmOperator *UNUSED(op)) } /* loop through all of the keys and select additional keyframes based on these */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE /*| ANIMFILTER_CURVESONLY*/ | + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FCURVESONLY | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); @@ -1200,7 +1206,7 @@ static void select_moreless_action_keys(bAnimContext *ac, short mode) build_cb = ANIM_editkeyframes_buildselmap(mode); /* loop through all of the keys and select additional keyframes based on these */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE /*| ANIMFILTER_CURVESONLY*/ | + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FCURVESONLY | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); @@ -1354,33 +1360,36 @@ static void actkeys_select_leftright(bAnimContext *ac, short leftright, short se } /* filter data */ - if (ELEM(ac->datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) { - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS); - } - else { - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE /*| ANIMFILTER_CURVESONLY*/ | - ANIMFILTER_NODUPLIS); - } + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* select keys */ for (ale = anim_data.first; ale; ale = ale->next) { - AnimData *adt = ANIM_nla_mapping_get(ac, ale); + switch (ale->type) { + case ANIMTYPE_GPLAYER: + ED_gpencil_layer_frames_select_box(ale->data, ked.f1, ked.f2, select_mode); + ale->update |= ANIM_UPDATE_DEPS; + break; - if (adt) { - ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 1); - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, ok_cb, select_cb, NULL); - ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 1); - } - else if (ale->type == ANIMTYPE_GPLAYER) { - ED_gpencil_layer_frames_select_box(ale->data, ked.f1, ked.f2, select_mode); - ale->update |= ANIM_UPDATE_DEPS; - } - else if (ale->type == ANIMTYPE_MASKLAYER) { - ED_masklayer_frames_select_box(ale->data, ked.f1, ked.f2, select_mode); - } - else { - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, ok_cb, select_cb, NULL); + case ANIMTYPE_MASKLAYER: + ED_masklayer_frames_select_box(ale->data, ked.f1, ked.f2, select_mode); + break; + + case ANIMTYPE_FCURVE: { + AnimData *adt = ANIM_nla_mapping_get(ac, ale); + if (adt) { + ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 1); + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, ok_cb, select_cb, NULL); + ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 1); + } + else { + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, ok_cb, select_cb, NULL); + } + break; + } + + default: + BLI_assert_msg(false, "Keys cannot be selected into this animation type."); } } @@ -1539,29 +1548,29 @@ static void actkeys_mselect_single(bAnimContext *ac, ED_mask_select_frame(ale->data, selx, select_mode); } else { - if (ELEM(ac->datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK) && (ale->type == ANIMTYPE_SUMMARY) && - (ale->datatype == ALE_ALL)) { + if (ale->type == ANIMTYPE_SUMMARY && ale->datatype == ALE_ALL) { ListBase anim_data = {NULL, NULL}; int filter; - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE /*| ANIMFILTER_CURVESONLY */ | - ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); - for (ale = anim_data.first; ale; ale = ale->next) { - if (ale->type == ANIMTYPE_GPLAYER) { - ED_gpencil_select_frame(ale->data, selx, select_mode); - ale->update |= ANIM_UPDATE_DEPS; + /* Loop over all keys that are represented by this summary key. */ + LISTBASE_FOREACH (bAnimListElem *, ale2, &anim_data) { + if (ale2->type == ANIMTYPE_GPLAYER) { + ED_gpencil_select_frame(ale2->data, selx, select_mode); + ale2->update |= ANIM_UPDATE_DEPS; } - else if (ale->type == ANIMTYPE_MASKLAYER) { - ED_mask_select_frame(ale->data, selx, select_mode); + else if (ale2->type == ANIMTYPE_MASKLAYER) { + ED_mask_select_frame(ale2->data, selx, select_mode); } } ANIM_animdata_update(ac, &anim_data); ANIM_animdata_freelist(&anim_data); } - else { + + if (!ELEM(ac->datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) { ANIM_animchannel_keyframes_loop(&ked, ac->ads, ale, ok_cb, select_cb, NULL); } } @@ -1588,35 +1597,29 @@ static void actkeys_mselect_column(bAnimContext *ac, short select_mode, float se /* loop through all of the keys and select additional keyframes * based on the keys found to be selected above */ - if (ELEM(ac->datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) { - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE /*| ANIMFILTER_CURVESONLY */ | - ANIMFILTER_NODUPLIS); - } - else { - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS); - } + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); for (ale = anim_data.first; ale; ale = ale->next) { - AnimData *adt = ANIM_nla_mapping_get(ac, ale); - - /* set frame for validation callback to refer to */ - if (adt) { - ked.f1 = BKE_nla_tweakedit_remap(adt, selx, NLATIME_CONVERT_UNMAP); - } - else { - ked.f1 = selx; - } - /* select elements with frame number matching cfra */ if (ale->type == ANIMTYPE_GPLAYER) { - ED_gpencil_select_frame(ale->key_data, selx, select_mode); + ED_gpencil_select_frame(ale->data, selx, select_mode); ale->update |= ANIM_UPDATE_DEPS; } else if (ale->type == ANIMTYPE_MASKLAYER) { - ED_mask_select_frame(ale->key_data, selx, select_mode); + ED_mask_select_frame(ale->data, selx, select_mode); } else { + AnimData *adt = ANIM_nla_mapping_get(ac, ale); + + /* set frame for validation callback to refer to */ + if (adt) { + ked.f1 = BKE_nla_tweakedit_remap(adt, selx, NLATIME_CONVERT_UNMAP); + } + else { + ked.f1 = selx; + } + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, ok_cb, select_cb, NULL); } } @@ -1645,29 +1648,28 @@ static void actkeys_mselect_channel_only(bAnimContext *ac, bAnimListElem *ale, s ED_mask_select_frames(ale->data, select_mode); } else { - if (ELEM(ac->datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK) && (ale->type == ANIMTYPE_SUMMARY) && - (ale->datatype == ALE_ALL)) { + if (ale->type == ANIMTYPE_SUMMARY && ale->datatype == ALE_ALL) { ListBase anim_data = {NULL, NULL}; int filter; - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE /*| ANIMFILTER_CURVESONLY */ | - ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); - for (ale = anim_data.first; ale; ale = ale->next) { - if (ale->type == ANIMTYPE_GPLAYER) { - ED_gpencil_select_frames(ale->data, select_mode); - ale->update |= ANIM_UPDATE_DEPS; + LISTBASE_FOREACH (bAnimListElem *, ale2, &anim_data) { + if (ale2->type == ANIMTYPE_GPLAYER) { + ED_gpencil_select_frames(ale2->data, select_mode); + ale2->update |= ANIM_UPDATE_DEPS; } - else if (ale->type == ANIMTYPE_MASKLAYER) { - ED_mask_select_frames(ale->data, select_mode); + else if (ale2->type == ANIMTYPE_MASKLAYER) { + ED_mask_select_frames(ale2->data, select_mode); } } ANIM_animdata_update(ac, &anim_data); ANIM_animdata_freelist(&anim_data); } - else { + + if (!ELEM(ac->datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) { ANIM_animchannel_keyframes_loop(NULL, ac->ads, ale, NULL, select_cb, NULL); } } @@ -1734,6 +1736,12 @@ static int mouse_action_keys(bAnimContext *ac, fcu->flag |= FCURVE_SELECTED; ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, fcu, ale->type); } + else if (ale->type == ANIMTYPE_GPLAYER) { + bGPdata *gpd = (bGPdata *)ale->id; + bGPDlayer *gpl = ale->data; + + ED_gpencil_set_active_channel(gpd, gpl); + } } } else if (ac->datatype == ANIMCONT_GPENCIL) { @@ -1745,13 +1753,7 @@ static int mouse_action_keys(bAnimContext *ac, bGPdata *gpd = (bGPdata *)ale->id; bGPDlayer *gpl = ale->data; - gpl->flag |= GP_LAYER_SELECT; - /* Update other layer status. */ - if (BKE_gpencil_layer_active_get(gpd) != gpl) { - BKE_gpencil_layer_active_set(gpd, gpl); - BKE_gpencil_layer_autolock_set(gpd, false); - WM_main_add_notifier(NC_GPENCIL | ND_DATA | NA_EDITED, NULL); - } + ED_gpencil_set_active_channel(gpd, gpl); } } else if (ac->datatype == ANIMCONT_MASK) { diff --git a/source/blender/editors/space_action/space_action.c b/source/blender/editors/space_action/space_action.c index 09163842587..3e3905e5263 100644 --- a/source/blender/editors/space_action/space_action.c +++ b/source/blender/editors/space_action/space_action.c @@ -506,8 +506,8 @@ static void action_listener(const wmSpaceTypeListenerParams *params) /* context changes */ switch (wmn->category) { case NC_GPENCIL: - /* only handle these events in GPencil mode for performance considerations */ - if (saction->mode == SACTCONT_GPENCIL) { + /* only handle these events for containers in which GPencil frames are displayed */ + if (ELEM(saction->mode, SACTCONT_GPENCIL, SACTCONT_DOPESHEET, SACTCONT_TIMELINE)) { if (wmn->action == NA_EDITED) { ED_area_tag_redraw(area); } diff --git a/source/blender/editors/space_graph/graph_draw.c b/source/blender/editors/space_graph/graph_draw.c index a946ce22139..608a1f4d73e 100644 --- a/source/blender/editors/space_graph/graph_draw.c +++ b/source/blender/editors/space_graph/graph_draw.c @@ -1348,7 +1348,7 @@ void graph_draw_curves(bAnimContext *ac, SpaceGraph *sipo, ARegion *region, shor int filter; /* build list of curves to draw */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY); filter |= ((sel) ? (ANIMFILTER_SEL) : (ANIMFILTER_UNSEL)); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); @@ -1393,7 +1393,8 @@ void graph_draw_channel_names(bContext *C, bAnimContext *ac, ARegion *region) size_t items; /* build list of channels to draw */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS | + ANIMFILTER_FCURVESONLY); items = ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* Update max-extent of channels here (taking into account scrollers): diff --git a/source/blender/editors/space_graph/graph_edit.c b/source/blender/editors/space_graph/graph_edit.c index cfc4fcf8dad..324da039daa 100644 --- a/source/blender/editors/space_graph/graph_edit.c +++ b/source/blender/editors/space_graph/graph_edit.c @@ -109,8 +109,8 @@ static void insert_graph_keys(bAnimContext *ac, eGraphKeys_InsertKey_Types mode) eInsertKeyFlags flag = 0; /* Filter data. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); if (mode & GRAPHKEYS_INSERTKEY_SEL) { filter |= ANIMFILTER_SEL; } @@ -457,7 +457,8 @@ static short copy_graph_keys(bAnimContext *ac) * - First time we try to filter more strictly, allowing only selected channels * to allow copying animation between channels. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_NODUPLIS); if (ANIM_animdata_filter(ac, &anim_data, filter | ANIMFILTER_SEL, ac->data, ac->datatype) == 0) { ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); @@ -472,13 +473,13 @@ static short copy_graph_keys(bAnimContext *ac) return ok; } -static short paste_graph_keys(bAnimContext *ac, - const eKeyPasteOffset offset_mode, - const eKeyMergeMode merge_mode, - bool flip) +static eKeyPasteError paste_graph_keys(bAnimContext *ac, + const eKeyPasteOffset offset_mode, + const eKeyMergeMode merge_mode, + bool flip) { ListBase anim_data = {NULL, NULL}; - int filter, ok = 0; + int filter; /* Filter data * - First time we try to filter more strictly, allowing only selected channels @@ -486,15 +487,15 @@ static short paste_graph_keys(bAnimContext *ac, * - Second time, we loosen things up if nothing was found the first time, allowing * users to just paste keyframes back into the original curve again T31670. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); if (ANIM_animdata_filter(ac, &anim_data, filter | ANIMFILTER_SEL, ac->data, ac->datatype) == 0) { ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); } /* Paste keyframes. */ - ok = paste_animedit_keys(ac, &anim_data, offset_mode, merge_mode, flip); + const eKeyPasteError ok = paste_animedit_keys(ac, &anim_data, offset_mode, merge_mode, flip); /* Clean up. */ ANIM_animdata_freelist(&anim_data); @@ -554,9 +555,18 @@ static int graphkeys_paste_exec(bContext *C, wmOperator *op) /* Ac.reports by default will be the global reports list, which won't show warnings. */ ac.reports = op->reports; - /* Paste keyframes - non-zero return means an error occurred while trying to paste. */ - if (paste_graph_keys(&ac, offset_mode, merge_mode, flipped)) { - return OPERATOR_CANCELLED; + const eKeyPasteError kf_empty = paste_graph_keys(&ac, offset_mode, merge_mode, flipped); + switch (kf_empty) { + case KEYFRAME_PASTE_OK: + break; + + case KEYFRAME_PASTE_NOWHERE_TO_PASTE: + BKE_report(op->reports, RPT_ERROR, "No selected F-Curves to paste into"); + return OPERATOR_CANCELLED; + + case KEYFRAME_PASTE_NOTHING_TO_PASTE: + BKE_report(op->reports, RPT_ERROR, "No data in buffer to paste"); + return OPERATOR_CANCELLED; } /* Set notifier that keyframes have changed. */ @@ -631,8 +641,8 @@ static bool duplicate_graph_keys(bAnimContext *ac) bool changed = false; /* Filter data. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* Loop through filtered data and delete selected keys. */ @@ -702,8 +712,8 @@ static bool delete_graph_keys(bAnimContext *ac) bool changed_final = false; /* Filter data. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* Loop through filtered data and delete selected keys. */ @@ -785,8 +795,8 @@ static void clean_graph_keys(bAnimContext *ac, float thresh, bool clean_chan) int filter; /* Filter data. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_SEL | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_FOREDIT | ANIMFILTER_SEL | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* Loop through filtered data and clean curves. */ @@ -862,8 +872,8 @@ static void bake_graph_curves(bAnimContext *ac, int start, int end) int filter; /* Filter data. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_SEL | - ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_SEL | ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* Loop through filtered data and add keys between selected keyframes on every frame. */ @@ -949,8 +959,8 @@ static void unbake_graph_curves(bAnimContext *ac, int start, int end) bAnimListElem *ale; /* Filter data. */ - const int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_SEL | - ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); + const int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_SEL | ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* Loop through filtered data and add keys between selected keyframes on every frame. */ @@ -1100,8 +1110,8 @@ static int graphkeys_sound_bake_exec(bContext *C, wmOperator *op) end = CFRA + sbi.length - 1; /* Filter anim channels. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_SEL | - ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_SEL | ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* Loop through all selected F-Curves, replacing its data with the sound samples. */ @@ -1267,8 +1277,8 @@ static void sample_graph_keys(bAnimContext *ac) int filter; /* filter data */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* Loop through filtered data and add keys between selected keyframes on every frame. */ @@ -1364,8 +1374,8 @@ static void setexpo_graph_keys(bAnimContext *ac, short mode) int filter; /* Filter data. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_SEL | - ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_SEL | ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* Loop through setting mode per F-Curve. */ @@ -1469,8 +1479,8 @@ static void setipo_graph_keys(bAnimContext *ac, short mode) KeyframeEditFunc set_cb = ANIM_editkeyframes_ipo(mode); /* Filter data. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* Loop through setting BezTriple interpolation @@ -1547,8 +1557,8 @@ static void seteasing_graph_keys(bAnimContext *ac, short mode) KeyframeEditFunc set_cb = ANIM_editkeyframes_easing(mode); /* Filter data. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* Loop through setting BezTriple easing. @@ -1625,8 +1635,8 @@ static void sethandles_graph_keys(bAnimContext *ac, short mode) KeyframeEditFunc sel_cb = ANIM_editkeyframes_ok(BEZT_OK_SELECTED); /* Filter data. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* Loop through setting flags for handles. @@ -1945,7 +1955,7 @@ static int graphkeys_euler_filter_exec(bContext *C, wmOperator *op) /* Step 1: extract only the rotation f-curves. */ const int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_SEL | ANIMFILTER_CURVE_VISIBLE | - ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); + ANIMFILTER_FCURVESONLY | ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); ListBase anim_data = {NULL, NULL}; ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); @@ -2056,7 +2066,8 @@ static KeyframeEditData sum_selected_keyframes(bAnimContext *ac) memset(&ked, 0, sizeof(KeyframeEditData)); /* Loop over action data, averaging values. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); for (ale = anim_data.first; ale; ale = ale->next) { @@ -2240,8 +2251,8 @@ static void snap_graph_keys(bAnimContext *ac, short mode) float cursor_value = 0.0f; /* Filter data. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* Init custom data for iterating over keyframes. */ @@ -2361,8 +2372,8 @@ static const EnumPropertyItem prop_graphkeys_equalize_handles_sides[] = { static void equalize_graph_keys(bAnimContext *ac, int mode, float handle_length, bool flatten) { /* Filter data. */ - const int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_NODUPLIS); + const int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); ListBase anim_data = {NULL, NULL}; ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); @@ -2523,8 +2534,8 @@ static void mirror_graph_keys(bAnimContext *ac, short mode) edit_cb = ANIM_editkeyframes_mirror(mode); /* Filter data. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* Mirror keyframes. */ @@ -2620,8 +2631,8 @@ static int graphkeys_smooth_exec(bContext *C, wmOperator *UNUSED(op)) } /* Filter data. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* Smooth keyframes. */ @@ -2720,7 +2731,8 @@ static int graph_fmodifier_add_exec(bContext *C, wmOperator *op) type = RNA_enum_get(op->ptr, "type"); /* Filter data. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS | + ANIMFILTER_FCURVESONLY); if (RNA_boolean_get(op->ptr, "only_active")) { /* FIXME: enforce in this case only a single channel to get handled? */ filter |= ANIMFILTER_ACTIVE; @@ -2875,14 +2887,14 @@ static int graph_fmodifier_paste_exec(bContext *C, wmOperator *op) /* Filter data. */ if (RNA_boolean_get(op->ptr, "only_active")) { /* This should be the default (for buttons) - Just paste to the active FCurve. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_ACTIVE | ANIMFILTER_FOREDIT | - ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FCURVESONLY | ANIMFILTER_ACTIVE | + ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); } else { /* This is only if the operator gets called from a hotkey or search - * Paste to all visible curves. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_SEL | - ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_SEL | ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); } ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); @@ -3065,7 +3077,8 @@ static int graph_driver_delete_invalid_exec(bContext *C, wmOperator *op) /* NOTE: We might need a scene update to evaluate the driver flags. */ /* Filter data. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_NODUPLIS); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* Find invalid drivers. */ diff --git a/source/blender/editors/space_graph/graph_ops.c b/source/blender/editors/space_graph/graph_ops.c index 128925d4591..158f2eae88f 100644 --- a/source/blender/editors/space_graph/graph_ops.c +++ b/source/blender/editors/space_graph/graph_ops.c @@ -234,14 +234,16 @@ static int graphview_curves_hide_exec(bContext *C, wmOperator *op) /* get list of all channels that selection may need to be flushed to * - hierarchy must not affect what we have access to here... */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_CHANNELS | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FCURVESONLY | ANIMFILTER_LIST_CHANNELS | + ANIMFILTER_NODUPLIS); ANIM_animdata_filter(&ac, &all_data, filter, ac.data, ac.datatype); /* filter data * - of the remaining visible curves, we want to hide the ones that are * selected/unselected (depending on "unselected" prop) */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FCURVESONLY | ANIMFILTER_CURVE_VISIBLE | + ANIMFILTER_NODUPLIS); if (unselected) { filter |= ANIMFILTER_UNSEL; } @@ -275,7 +277,8 @@ static int graphview_curves_hide_exec(bContext *C, wmOperator *op) /* unhide selected */ if (unselected) { /* turn off requirement for visible */ - filter = ANIMFILTER_SEL | ANIMFILTER_NODUPLIS | ANIMFILTER_LIST_CHANNELS; + filter = ANIMFILTER_SEL | ANIMFILTER_NODUPLIS | ANIMFILTER_LIST_CHANNELS | + ANIMFILTER_FCURVESONLY; /* flushing has been done */ ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); @@ -344,13 +347,15 @@ static int graphview_curves_reveal_exec(bContext *C, wmOperator *op) /* get list of all channels that selection may need to be flushed to * - hierarchy must not affect what we have access to here... */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_CHANNELS | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_CHANNELS | ANIMFILTER_NODUPLIS | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &all_data, filter, ac.data, ac.datatype); /* filter data * - just go through all visible channels, ensuring that everything is set to be curve-visible */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); for (ale = anim_data.first; ale; ale = ale->next) { diff --git a/source/blender/editors/space_graph/graph_select.c b/source/blender/editors/space_graph/graph_select.c index e71c5114b0a..70f9e4a60d9 100644 --- a/source/blender/editors/space_graph/graph_select.c +++ b/source/blender/editors/space_graph/graph_select.c @@ -172,7 +172,8 @@ static void get_nearest_fcurve_verts_list(bAnimContext *ac, const int mval[2], L * - if the option to only show keyframes that belong to selected F-Curves is enabled, * include the 'only selected' flag... */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_NODUPLIS | ANIMFILTER_FCURVESONLY); if (sipo->flag & SIPO_SELCUVERTSONLY) { /* FIXME: this should really be check for by the filtering code... */ filter |= ANIMFILTER_SEL; @@ -342,7 +343,8 @@ void deselect_graph_keys(bAnimContext *ac, bool test, short sel, bool do_channel KeyframeEditFunc test_cb, sel_cb; /* determine type-based settings */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_NODUPLIS); /* filter data */ ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); @@ -498,7 +500,8 @@ static rctf initialize_box_select_coords(const bAnimContext *ac, const rctf *rec static int initialize_animdata_selection_filter(const SpaceGraph *sipo) { - int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS); + int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_NODUPLIS); if (sipo->flag & SIPO_SELCUVERTSONLY) { filter |= ANIMFILTER_FOREDIT | ANIMFILTER_SELEDIT; } @@ -1150,7 +1153,8 @@ static void markers_selectkeys_between(bAnimContext *ac) ked.f2 = max; /* filter data */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* select keys in-between */ @@ -1189,7 +1193,8 @@ static void columnselect_graph_keys(bAnimContext *ac, short mode) /* build list of columns */ switch (mode) { case GRAPHKEYS_COLUMNSEL_KEYS: /* list of selected keys */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); for (ale = anim_data.first; ale; ale = ale->next) { @@ -1222,7 +1227,8 @@ static void columnselect_graph_keys(bAnimContext *ac, short mode) /* loop through all of the keys and select additional keyframes * based on the keys found to be selected above */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); for (ale = anim_data.first; ale; ale = ale->next) { @@ -1314,7 +1320,8 @@ static int graphkeys_select_linked_exec(bContext *C, wmOperator *UNUSED(op)) } /* loop through all of the keys and select additional keyframes based on these */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_NODUPLIS); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); for (ale = anim_data.first; ale; ale = ale->next) { @@ -1372,7 +1379,8 @@ static void select_moreless_graph_keys(bAnimContext *ac, short mode) memset(&ked, 0, sizeof(KeyframeEditData)); /* loop through all of the keys and select additional keyframes based on these */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); for (ale = anim_data.first; ale; ale = ale->next) { @@ -1521,7 +1529,7 @@ static void graphkeys_select_leftright(bAnimContext *ac, short leftright, short } /* filter data */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_NODUPLIS | ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* select keys */ @@ -1797,7 +1805,8 @@ static int mouse_graph_keys(bAnimContext *ac, * otherwise the active flag won't be set T26452. */ if (!run_modal && (nvi->fcu->flag & FCURVE_SELECTED) && something_was_selected) { /* NOTE: Sync the filter flags with findnearest_fcurve_vert. */ - int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS); + int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_NODUPLIS); ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, nvi->fcu, nvi->ctype); } @@ -1873,7 +1882,8 @@ static int graphkeys_mselect_column(bAnimContext *ac, /* loop through all of the keys and select additional keyframes * based on the keys found to be selected above */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); for (ale = anim_data.first; ale; ale = ale->next) { diff --git a/source/blender/editors/space_graph/graph_slider_ops.c b/source/blender/editors/space_graph/graph_slider_ops.c index 313f6ca1561..f3d92911155 100644 --- a/source/blender/editors/space_graph/graph_slider_ops.c +++ b/source/blender/editors/space_graph/graph_slider_ops.c @@ -48,8 +48,8 @@ /* Used to obtain a list of animation channels for the operators to work on. */ #define OPERATOR_DATA_FILTER \ - (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_SEL | \ - ANIMFILTER_NODUPLIS) + (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | \ + ANIMFILTER_FOREDIT | ANIMFILTER_SEL | ANIMFILTER_NODUPLIS) /* This data type is only used for modal operation. */ typedef struct tGraphSliderOp { diff --git a/source/blender/editors/space_graph/graph_utils.c b/source/blender/editors/space_graph/graph_utils.c index d8baa4c643d..a813e6ae245 100644 --- a/source/blender/editors/space_graph/graph_utils.c +++ b/source/blender/editors/space_graph/graph_utils.c @@ -131,7 +131,7 @@ bool graphop_visible_keyframes_poll(bContext *C) /* loop over the visible (selection doesn't matter) F-Curves, and see if they're suitable * stopping on the first successful match */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY); items = ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); if (items == 0) { return found; @@ -183,7 +183,8 @@ bool graphop_editable_keyframes_poll(bContext *C) /* loop over the editable F-Curves, and see if they're suitable * stopping on the first successful match */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_CURVE_VISIBLE); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_CURVE_VISIBLE | + ANIMFILTER_FCURVESONLY); items = ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); if (items == 0) { CTX_wm_operator_poll_msg_set(C, "There is no animation data to operate on"); @@ -286,7 +287,8 @@ bool graphop_selected_fcurve_poll(bContext *C) /* Get the editable + selected F-Curves, and as long as we got some, we can return. * NOTE: curve-visible flag isn't included, * otherwise selecting a curve via list to edit is too cumbersome. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_SEL | ANIMFILTER_FOREDIT); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_SEL | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); items = ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); if (items == 0) { return false; diff --git a/source/blender/editors/space_graph/graph_view.c b/source/blender/editors/space_graph/graph_view.c index 18465018d35..f80c7c17c3a 100644 --- a/source/blender/editors/space_graph/graph_view.c +++ b/source/blender/editors/space_graph/graph_view.c @@ -56,7 +56,8 @@ void get_graph_keyframe_extents(bAnimContext *ac, int filter; /* Get data to filter, from Dopesheet. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_NODUPLIS); if (sipo->flag & SIPO_SELCUVERTSONLY) { filter |= ANIMFILTER_SEL; } @@ -398,8 +399,8 @@ static void create_ghost_curves(bAnimContext *ac, int start, int end) } /* Filter data. */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_SEL | - ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_SEL | ANIMFILTER_NODUPLIS); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* Loop through filtered data and add keys between selected keyframes on every frame. */ diff --git a/source/blender/editors/transform/transform.h b/source/blender/editors/transform/transform.h index 76573c85d52..049d1b6a90e 100644 --- a/source/blender/editors/transform/transform.h +++ b/source/blender/editors/transform/transform.h @@ -463,6 +463,8 @@ typedef struct TransDataContainer { int data_len; /** Total number of transformed data_mirror. */ int data_mirror_len; + /** Total number of transformed gp-frames. */ + int data_gpf_len; struct Object *obedit; diff --git a/source/blender/editors/transform/transform_convert_action.c b/source/blender/editors/transform/transform_convert_action.c index 71c245cd512..ba62f6f1fea 100644 --- a/source/blender/editors/transform/transform_convert_action.c +++ b/source/blender/editors/transform/transform_convert_action.c @@ -311,6 +311,7 @@ void createTransActionData(bContext *C, TransInfo *t) const bool is_prop_edit = (t->flag & T_PROP_EDIT) != 0; int count = 0; + int gpf_count = 0; float cfra; float ypos = 1.0f / ((ysize / xsize) * (xmask / ymask)) * BLI_rctf_cent_y(&t->region->v2d.cur); @@ -320,12 +321,7 @@ void createTransActionData(bContext *C, TransInfo *t) } /* filter data */ - if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) { - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT); - } - else { - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT /*| ANIMFILTER_CURVESONLY*/); - } + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* which side of the current frame should be allowed */ @@ -365,13 +361,16 @@ void createTransActionData(bContext *C, TransInfo *t) } if (adt_count > 0) { + if (ELEM(ale->type, ANIMTYPE_GPLAYER, ANIMTYPE_MASKLAYER)) { + gpf_count += adt_count; + } count += adt_count; ale->tag = true; } } /* stop if trying to build list if nothing selected */ - if (count == 0) { + if (count == 0 && gpf_count == 0) { /* cleanup temp list */ ANIM_animdata_freelist(&anim_data); return; @@ -387,8 +386,9 @@ void createTransActionData(bContext *C, TransInfo *t) td = tc->data; td2d = tc->data_2d; - if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) { - tc->custom.type.data = tfd = MEM_callocN(sizeof(tGPFtransdata) * count, "tGPFtransdata"); + if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK, ANIMCONT_DOPESHEET, ANIMCONT_TIMELINE)) { + tc->data_gpf_len = gpf_count; + tc->custom.type.data = tfd = MEM_callocN(sizeof(tGPFtransdata) * gpf_count, "tGPFtransdata"); tc->custom.type.use_free = true; } @@ -558,8 +558,9 @@ static void flushTransIntFrameActionData(TransInfo *t) TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); tGPFtransdata *tfd = tc->custom.type.data; - /* flush data! */ - for (int i = 0; i < tc->data_len; i++, tfd++) { + /* flush data! + * Expects data_gpf_len to be set in the data container. */ + for (int i = 0; i < tc->data_gpf_len; i++, tfd++) { *(tfd->sdata) = round_fl_to_int(tfd->val); } } @@ -589,7 +590,7 @@ void recalcData_actedit(TransInfo *t) ANIM_animdata_context_getdata(&ac); /* perform flush */ - if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK)) { + if (ELEM(ac.datatype, ANIMCONT_GPENCIL, ANIMCONT_MASK, ANIMCONT_DOPESHEET, ANIMCONT_TIMELINE)) { /* flush transform values back to actual coordinates */ flushTransIntFrameActionData(t); } @@ -735,7 +736,7 @@ static void posttrans_action_clean(bAnimContext *ac, bAction *act) int filter; /* filter data */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT /*| ANIMFILTER_CURVESONLY*/); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(ac, &anim_data, filter, act, ANIMCONT_ACTION); /* loop through relevant data, removing keyframes as appropriate @@ -776,32 +777,44 @@ void special_aftertrans_update__actedit(bContext *C, TransInfo *t) if (ELEM(ac.datatype, ANIMCONT_DOPESHEET, ANIMCONT_SHAPEKEY, ANIMCONT_TIMELINE)) { ListBase anim_data = {NULL, NULL}; bAnimListElem *ale; - short filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT /*| ANIMFILTER_CURVESONLY*/); + short filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT); /* get channels to work on */ ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); - /* these should all be F-Curves */ for (ale = anim_data.first; ale; ale = ale->next) { - AnimData *adt = ANIM_nla_mapping_get(&ac, ale); - FCurve *fcu = (FCurve *)ale->key_data; - - /* 3 cases here for curve cleanups: - * 1) NOTRANSKEYCULL on -> cleanup of duplicates shouldn't be done - * 2) canceled == 0 -> user confirmed the transform, - * so duplicates should be removed - * 3) canceled + duplicate -> user canceled the transform, - * but we made duplicates, so get rid of these - */ - if ((saction->flag & SACTION_NOTRANSKEYCULL) == 0 && ((canceled == 0) || (duplicate))) { - if (adt) { - ANIM_nla_mapping_apply_fcurve(adt, fcu, 0, 0); - posttrans_fcurve_clean(fcu, SELECT, false); /* only use handles in graph editor */ - ANIM_nla_mapping_apply_fcurve(adt, fcu, 1, 0); - } - else { - posttrans_fcurve_clean(fcu, SELECT, false); /* only use handles in graph editor */ + switch (ale->datatype) { + case ALE_GPFRAME: + ale->id->tag &= ~LIB_TAG_DOIT; + posttrans_gpd_clean((bGPdata *)ale->id); + break; + + case ALE_FCURVE: { + AnimData *adt = ANIM_nla_mapping_get(&ac, ale); + FCurve *fcu = (FCurve *)ale->key_data; + + /* 3 cases here for curve cleanups: + * 1) NOTRANSKEYCULL on -> cleanup of duplicates shouldn't be done + * 2) canceled == 0 -> user confirmed the transform, + * so duplicates should be removed + * 3) canceled + duplicate -> user canceled the transform, + * but we made duplicates, so get rid of these + */ + if ((saction->flag & SACTION_NOTRANSKEYCULL) == 0 && ((canceled == 0) || (duplicate))) { + if (adt) { + ANIM_nla_mapping_apply_fcurve(adt, fcu, 0, 0); + posttrans_fcurve_clean(fcu, SELECT, false); /* only use handles in graph editor */ + ANIM_nla_mapping_apply_fcurve(adt, fcu, 1, 0); + } + else { + posttrans_fcurve_clean(fcu, SELECT, false); /* only use handles in graph editor */ + } + } + break; } + + default: + BLI_assert_msg(false, "Keys cannot be transformed into this animation type."); } } @@ -847,15 +860,8 @@ void special_aftertrans_update__actedit(bContext *C, TransInfo *t) LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) { if (ale->datatype == ALE_GPFRAME) { - ale->id->tag |= LIB_TAG_DOIT; - } - } - LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) { - if (ale->datatype == ALE_GPFRAME) { - if (ale->id->tag & LIB_TAG_DOIT) { - ale->id->tag &= ~LIB_TAG_DOIT; - posttrans_gpd_clean((bGPdata *)ale->id); - } + ale->id->tag &= ~LIB_TAG_DOIT; + posttrans_gpd_clean((bGPdata *)ale->id); } } ANIM_animdata_freelist(&anim_data); @@ -878,15 +884,8 @@ void special_aftertrans_update__actedit(bContext *C, TransInfo *t) LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) { if (ale->datatype == ALE_MASKLAY) { - ale->id->tag |= LIB_TAG_DOIT; - } - } - LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) { - if (ale->datatype == ALE_MASKLAY) { - if (ale->id->tag & LIB_TAG_DOIT) { - ale->id->tag &= ~LIB_TAG_DOIT; - posttrans_mask_clean((Mask *)ale->id); - } + ale->id->tag &= ~LIB_TAG_DOIT; + posttrans_mask_clean((Mask *)ale->id); } } ANIM_animdata_freelist(&anim_data); diff --git a/source/blender/editors/transform/transform_convert_graph.c b/source/blender/editors/transform/transform_convert_graph.c index 2039daee386..1444268baf5 100644 --- a/source/blender/editors/transform/transform_convert_graph.c +++ b/source/blender/editors/transform/transform_convert_graph.c @@ -232,7 +232,8 @@ void createTransGraphEditData(bContext *C, TransInfo *t) anim_map_flag |= ANIM_get_normalization_flags(&ac); /* filter data */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_CURVE_VISIBLE); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_CURVE_VISIBLE | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* which side of the current frame should be allowed */ @@ -915,7 +916,8 @@ void recalcData_graphedit(TransInfo *t) flushTransGraphData(t); /* get curves to check if a re-sort is needed */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_CURVE_VISIBLE); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_CURVE_VISIBLE | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* now test if there is a need to re-sort */ @@ -975,7 +977,8 @@ void special_aftertrans_update__graph(bContext *C, TransInfo *t) if (ac.datatype) { ListBase anim_data = {NULL, NULL}; bAnimListElem *ale; - short filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_CURVE_VISIBLE); + short filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_CURVE_VISIBLE | + ANIMFILTER_FCURVESONLY); /* get channels to work on */ ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); -- cgit v1.2.3 From 547efb6b1e3414f1b696fb1975e0c49742a264dc Mon Sep 17 00:00:00 2001 From: Richard Antalik Date: Thu, 30 Jun 2022 15:27:49 +0200 Subject: Fix T99133:animating multiply factor on video strips crashes blender Crash caused by `effect_seq->len` being 0, so frame map was not built. Get length in timeline using handle positions. --- source/blender/sequencer/intern/effects.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/source/blender/sequencer/intern/effects.c b/source/blender/sequencer/intern/effects.c index 368c00534be..4aec4916f7b 100644 --- a/source/blender/sequencer/intern/effects.c +++ b/source/blender/sequencer/intern/effects.c @@ -2600,7 +2600,10 @@ static FCurve *seq_effect_speed_speed_factor_curve_get(Scene *scene, Sequence *s void seq_effect_speed_rebuild_map(Scene *scene, Sequence *seq) { - if ((seq->seq1 == NULL) || (seq->len < 1)) { + const int effect_strip_length = SEQ_time_right_handle_frame_get(scene, seq) - + SEQ_time_left_handle_frame_get(scene, seq); + + if ((seq->seq1 == NULL) || (effect_strip_length < 1)) { return; /* Make coverity happy and check for (CID 598) input strip... */ } @@ -2614,15 +2617,13 @@ void seq_effect_speed_rebuild_map(Scene *scene, Sequence *seq) MEM_freeN(v->frameMap); } - const int effect_strip_length = SEQ_time_right_handle_frame_get(scene, seq) - - SEQ_time_left_handle_frame_get(scene, seq); v->frameMap = MEM_mallocN(sizeof(float) * effect_strip_length, __func__); v->frameMap[0] = 0.0f; float target_frame = 0; for (int frame_index = 1; frame_index < effect_strip_length; frame_index++) { target_frame += evaluate_fcurve(fcu, SEQ_time_left_handle_frame_get(scene, seq) + frame_index); - CLAMP(target_frame, 0, seq->seq1->len); + CLAMP(target_frame, 0, SEQ_time_strip_length_get(seq->seq1)); v->frameMap[frame_index] = target_frame; } } -- cgit v1.2.3 From 90ccb71969f3c1358dae6e9aab2992ded78272f6 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 30 Jun 2022 23:44:13 +1000 Subject: Fix missing argument, avoid instancing function call in macro --- source/blender/sequencer/intern/effects.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/blender/sequencer/intern/effects.c b/source/blender/sequencer/intern/effects.c index 4aec4916f7b..2ab8b170ce9 100644 --- a/source/blender/sequencer/intern/effects.c +++ b/source/blender/sequencer/intern/effects.c @@ -2623,7 +2623,8 @@ void seq_effect_speed_rebuild_map(Scene *scene, Sequence *seq) float target_frame = 0; for (int frame_index = 1; frame_index < effect_strip_length; frame_index++) { target_frame += evaluate_fcurve(fcu, SEQ_time_left_handle_frame_get(scene, seq) + frame_index); - CLAMP(target_frame, 0, SEQ_time_strip_length_get(seq->seq1)); + const int target_frame_max = SEQ_time_strip_length_get(scene, seq->seq1); + CLAMP(target_frame, 0, target_frame_max); v->frameMap[frame_index] = target_frame; } } -- cgit v1.2.3 From e75f3e3febc413a75af0c855c3fce0864d2191e6 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 30 Jun 2022 22:53:06 +1000 Subject: Cleanup: use "use_" prefix for boolean property --- source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c | 2 +- source/blender/makesrna/intern/rna_gpencil_modifier.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c index 1c5485d2640..4f00b997902 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c @@ -448,7 +448,7 @@ static void options_light_reference_draw(const bContext *UNUSED(C), Panel *panel uiItemR(col, ptr, "shadow_camera_near", 0, "Near", ICON_NONE); uiItemR(col, ptr, "shadow_camera_far", 0, "Far", ICON_NONE); - uiItemR(layout, ptr, "shadow_enclosed_shapes", 0, IFACE_("Eclosed Shapes"), ICON_NONE); + uiItemR(layout, ptr, "use_shadow_enclosed_shapes", 0, IFACE_("Eclosed Shapes"), ICON_NONE); } static void options_panel_draw(const bContext *UNUSED(C), Panel *panel) diff --git a/source/blender/makesrna/intern/rna_gpencil_modifier.c b/source/blender/makesrna/intern/rna_gpencil_modifier.c index 0df11c7fc8e..dccf7d7a7a9 100644 --- a/source/blender/makesrna/intern/rna_gpencil_modifier.c +++ b/source/blender/makesrna/intern/rna_gpencil_modifier.c @@ -3464,7 +3464,7 @@ static void rna_def_modifier_gpencillineart(BlenderRNA *brna) "affect cast shadow and light contour since they are at the border"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_dependency_update"); - prop = RNA_def_property(srna, "shadow_enclosed_shapes", PROP_BOOLEAN, PROP_NONE); + prop = RNA_def_property(srna, "use_shadow_enclosed_shapes", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "calculation_flags", LRT_SHADOW_ENCLOSED_SHAPES); RNA_def_property_ui_text(prop, "Shadow Enclosed Shapes", -- cgit v1.2.3 From e190b70946b776cb105f8987c4cf1ec5157e75d0 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 30 Jun 2022 22:53:08 +1000 Subject: Cleanup: declare GHOST/Wayland methods const Needed when called by functions that are const too. --- intern/ghost/intern/GHOST_WindowWayland.cpp | 4 ++-- intern/ghost/intern/GHOST_WindowWayland.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp index a1936d3667c..27c2a09465d 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cpp +++ b/intern/ghost/intern/GHOST_WindowWayland.cpp @@ -591,12 +591,12 @@ bool GHOST_WindowWayland::outputs_leave(output_t *reg_output) return true; } -uint16_t GHOST_WindowWayland::dpi() +uint16_t GHOST_WindowWayland::dpi() const { return w->dpi; } -int GHOST_WindowWayland::scale() +int GHOST_WindowWayland::scale() const { return w->scale; } diff --git a/intern/ghost/intern/GHOST_WindowWayland.h b/intern/ghost/intern/GHOST_WindowWayland.h index 89354c54c0f..b5aeecd6204 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.h +++ b/intern/ghost/intern/GHOST_WindowWayland.h @@ -113,9 +113,9 @@ class GHOST_WindowWayland : public GHOST_Window { bool outputs_leave(output_t *reg_output); bool outputs_changed_update_scale(); - uint16_t dpi(); + uint16_t dpi() const; - int scale(); + int scale() const; private: GHOST_SystemWayland *m_system; -- cgit v1.2.3 From df40e9d0aad0c2a5b649d99c25e991a3664501c5 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 30 Jun 2022 22:53:09 +1000 Subject: Fix memory leak with off-screen buffers under Wayland Each off-screen buffer created a surface and EGL window which was only freed when Blender exited. Resolve by freeing the associated data when disposing the off-screen context. --- intern/ghost/intern/GHOST_Context.h | 19 +++++++++ intern/ghost/intern/GHOST_SystemWayland.cpp | 62 ++++++++++++++++------------- 2 files changed, 54 insertions(+), 27 deletions(-) diff --git a/intern/ghost/intern/GHOST_Context.h b/intern/ghost/intern/GHOST_Context.h index d9c2cdce258..e707f1c3475 100644 --- a/intern/ghost/intern/GHOST_Context.h +++ b/intern/ghost/intern/GHOST_Context.h @@ -92,6 +92,22 @@ class GHOST_Context : public GHOST_IContext { return GHOST_kFailure; } + /** + * Get user data. + */ + void *getUserData() + { + return m_user_data; + } + + /** + * Set user data (intended for the caller to use as needed). + */ + void setUserData(void *user_data) + { + m_user_data = user_data; + } + /** * Stereo visual created. Only necessary for 'real' stereo support, * ie quad buffered stereo. This is not always possible, depends on @@ -124,6 +140,9 @@ class GHOST_Context : public GHOST_IContext { bool m_stereoVisual; + /** Caller specified, not for internal use. */ + void *m_user_data = nullptr; + static void initClearGL(); #ifdef WITH_CXX_GUARDEDALLOC diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 36c9421f596..af629e83b19 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -334,9 +334,6 @@ struct display_t { struct zwp_tablet_manager_v2 *tablet_manager = nullptr; struct zwp_relative_pointer_manager_v1 *relative_pointer_manager = nullptr; struct zwp_pointer_constraints_v1 *pointer_constraints = nullptr; - - std::vector os_surfaces; - std::vector os_egl_windows; }; /** \} */ @@ -472,14 +469,6 @@ static void display_destroy(display_t *d) zwp_pointer_constraints_v1_destroy(d->pointer_constraints); } - for (wl_egl_window *os_egl_window : d->os_egl_windows) { - wl_egl_window_destroy(os_egl_window); - } - - for (wl_surface *os_surface : d->os_surfaces) { - wl_surface_destroy(os_surface); - } - if (d->compositor) { wl_compositor_destroy(d->compositor); } @@ -2745,22 +2734,17 @@ void GHOST_SystemWayland::getAllDisplayDimensions(uint32_t &width, uint32_t &hei height = xy_max[1] - xy_min[1]; } -GHOST_IContext *GHOST_SystemWayland::createOffscreenContext(GHOST_GLSettings /*glSettings*/) +static GHOST_Context *createOffscreenContext_impl(GHOST_SystemWayland *system, + struct wl_display *wl_display, + wl_egl_window *egl_window) { - /* Create new off-screen window. */ - wl_surface *os_surface = wl_compositor_create_surface(compositor()); - wl_egl_window *os_egl_window = wl_egl_window_create(os_surface, int(1), int(1)); - - d->os_surfaces.push_back(os_surface); - d->os_egl_windows.push_back(os_egl_window); - GHOST_Context *context; for (int minor = 6; minor >= 0; --minor) { - context = new GHOST_ContextEGL(this, + context = new GHOST_ContextEGL(system, false, - EGLNativeWindowType(os_egl_window), - EGLNativeDisplayType(d->display), + EGLNativeWindowType(egl_window), + EGLNativeDisplayType(wl_display), EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, 4, minor, @@ -2774,10 +2758,10 @@ GHOST_IContext *GHOST_SystemWayland::createOffscreenContext(GHOST_GLSettings /*g delete context; } - context = new GHOST_ContextEGL(this, + context = new GHOST_ContextEGL(system, false, - EGLNativeWindowType(os_egl_window), - EGLNativeDisplayType(d->display), + EGLNativeWindowType(egl_window), + EGLNativeDisplayType(wl_display), EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, 3, 3, @@ -2789,15 +2773,39 @@ GHOST_IContext *GHOST_SystemWayland::createOffscreenContext(GHOST_GLSettings /*g return context; } delete context; + return nullptr; +} - GHOST_PRINT("Cannot create off-screen EGL context" << std::endl); +GHOST_IContext *GHOST_SystemWayland::createOffscreenContext(GHOST_GLSettings /*glSettings*/) +{ + /* Create new off-screen window. */ + wl_surface *wl_surface = wl_compositor_create_surface(compositor()); + wl_egl_window *egl_window = wl_egl_window_create(wl_surface, int(1), int(1)); - return nullptr; + GHOST_Context *context = createOffscreenContext_impl(this, d->display, egl_window); + + if (!context) { + GHOST_PRINT("Cannot create off-screen EGL context" << std::endl); + wl_surface_destroy(wl_surface); + wl_egl_window_destroy(egl_window); + return nullptr; + } + + wl_surface_set_user_data(wl_surface, egl_window); + context->setUserData(wl_surface); + + return context; } GHOST_TSuccess GHOST_SystemWayland::disposeContext(GHOST_IContext *context) { + struct wl_surface *wl_surface = (struct wl_surface *)((GHOST_Context *)context)->getUserData(); + wl_egl_window *egl_window = (wl_egl_window *)wl_surface_get_user_data(wl_surface); + wl_egl_window_destroy(egl_window); + wl_surface_destroy(wl_surface); + delete context; + return GHOST_kSuccess; } -- cgit v1.2.3 From 6bd2c6789b244a03bccdb254502567691c42b944 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 30 Jun 2022 22:53:20 +1000 Subject: GHOST: get/set cursor position now uses client instead of screen coords Use client (window) relative coordinates for cursor position access, this only moves the conversion from window-manager into GHOST, (no functional changes). This is needed for fix a bug in GHOST/Wayland which doesn't support accessing absolute cursor coordinates & the window is needed to properly access the cursor coordinates. As it happens every caller to GHOST_GetCursorPosition was already making the values window-relative, so there is little benefit in attempting to workaround the problem on the Wayland side. If needed the screen-space versions of functions can be exposed again. --- intern/ghost/GHOST_C-api.h | 19 +++++++------ intern/ghost/GHOST_ISystem.h | 19 +++++++++++++ intern/ghost/intern/GHOST_C-api.cpp | 34 +++++++++++++++++++++-- intern/ghost/intern/GHOST_System.cpp | 23 +++++++++++++++ intern/ghost/intern/GHOST_System.h | 11 ++++++++ source/blender/windowmanager/intern/wm_cursors.c | 6 ++-- source/blender/windowmanager/intern/wm_playanim.c | 11 ++++---- source/blender/windowmanager/intern/wm_window.c | 32 +++++++++++++-------- source/blender/windowmanager/wm_window.h | 7 +++-- 9 files changed, 128 insertions(+), 34 deletions(-) diff --git a/intern/ghost/GHOST_C-api.h b/intern/ghost/GHOST_C-api.h index 0f05e501d17..4cbc0d65b11 100644 --- a/intern/ghost/GHOST_C-api.h +++ b/intern/ghost/GHOST_C-api.h @@ -383,27 +383,28 @@ extern bool GHOST_GetCursorVisibility(GHOST_WindowHandle windowhandle); extern GHOST_TSuccess GHOST_SetCursorVisibility(GHOST_WindowHandle windowhandle, bool visible); /** - * Returns the current location of the cursor (location in screen coordinates) + * Returns the current location of the cursor (location in client relative coordinates) * \param systemhandle: The handle to the system. * \param x: The x-coordinate of the cursor. * \param y: The y-coordinate of the cursor. * \return Indication of success. */ -extern GHOST_TSuccess GHOST_GetCursorPosition(GHOST_SystemHandle systemhandle, - int32_t *x, - int32_t *y); - +GHOST_TSuccess GHOST_GetCursorPosition(const GHOST_SystemHandle systemhandle, + const GHOST_WindowHandle windowhandle, + int32_t *x, + int32_t *y); /** - * Updates the location of the cursor (location in screen coordinates). + * Updates the location of the cursor (location in client relative coordinates). * Not all operating systems allow the cursor to be moved (without the input device being moved). * \param systemhandle: The handle to the system. * \param x: The x-coordinate of the cursor. * \param y: The y-coordinate of the cursor. * \return Indication of success. */ -extern GHOST_TSuccess GHOST_SetCursorPosition(GHOST_SystemHandle systemhandle, - int32_t x, - int32_t y); +GHOST_TSuccess GHOST_SetCursorPosition(GHOST_SystemHandle systemhandle, + GHOST_WindowHandle windowhandle, + int32_t x, + int32_t y); void GHOST_GetCursorGrabState(GHOST_WindowHandle windowhandle, GHOST_TGrabCursorMode *r_mode, diff --git a/intern/ghost/GHOST_ISystem.h b/intern/ghost/GHOST_ISystem.h index d7485975906..91cf1c4c558 100644 --- a/intern/ghost/GHOST_ISystem.h +++ b/intern/ghost/GHOST_ISystem.h @@ -364,6 +364,25 @@ class GHOST_ISystem { * Cursor management functionality ***************************************************************************************/ + /** + * Returns the current location of the cursor (location in window coordinates) + * \param x: The x-coordinate of the cursor. + * \param y: The y-coordinate of the cursor. + * \return Indication of success. + */ + virtual GHOST_TSuccess getCursorPositionClientRelative(const GHOST_IWindow *window, + int32_t &x, + int32_t &y) const = 0; + /** + * Updates the location of the cursor (location in window coordinates). + * \param x: The x-coordinate of the cursor. + * \param y: The y-coordinate of the cursor. + * \return Indication of success. + */ + virtual GHOST_TSuccess setCursorPositionClientRelative(GHOST_IWindow *window, + int32_t x, + int32_t y) = 0; + /** * Returns the current location of the cursor (location in screen coordinates) * \param x: The x-coordinate of the cursor. diff --git a/intern/ghost/intern/GHOST_C-api.cpp b/intern/ghost/intern/GHOST_C-api.cpp index 8c690767810..65e7de707ec 100644 --- a/intern/ghost/intern/GHOST_C-api.cpp +++ b/intern/ghost/intern/GHOST_C-api.cpp @@ -348,19 +348,49 @@ GHOST_TSuccess GHOST_SetCursorVisibility(GHOST_WindowHandle windowhandle, bool v return window->setCursorVisibility(visible); } -GHOST_TSuccess GHOST_GetCursorPosition(GHOST_SystemHandle systemhandle, int32_t *x, int32_t *y) +/* Unused, can expose again if needed although WAYLAND + * can only properly use client relative coordinates, so leave disabled if possible. */ +#if 0 +GHOST_TSuccess GHOST_GetCursorPositionScreenCoords(GHOST_SystemHandle systemhandle, + int32_t *x, + int32_t *y) { GHOST_ISystem *system = (GHOST_ISystem *)systemhandle; return system->getCursorPosition(*x, *y); } -GHOST_TSuccess GHOST_SetCursorPosition(GHOST_SystemHandle systemhandle, int32_t x, int32_t y) +GHOST_TSuccess GHOST_SetCursorPositionScreenCoords(GHOST_SystemHandle systemhandle, + int32_t x, + int32_t y) { GHOST_ISystem *system = (GHOST_ISystem *)systemhandle; return system->setCursorPosition(x, y); } +#endif + +GHOST_TSuccess GHOST_GetCursorPosition(const GHOST_SystemHandle systemhandle, + const GHOST_WindowHandle windowhandle, + int32_t *x, + int32_t *y) +{ + const GHOST_ISystem *system = (const GHOST_ISystem *)systemhandle; + const GHOST_IWindow *window = (const GHOST_IWindow *)windowhandle; + + return system->getCursorPositionClientRelative(window, *x, *y); +} + +GHOST_TSuccess GHOST_SetCursorPosition(GHOST_SystemHandle systemhandle, + GHOST_WindowHandle windowhandle, + int32_t x, + int32_t y) +{ + GHOST_ISystem *system = (GHOST_ISystem *)systemhandle; + GHOST_IWindow *window = (GHOST_IWindow *)windowhandle; + + return system->setCursorPositionClientRelative(window, x, y); +} GHOST_TSuccess GHOST_SetCursorGrab(GHOST_WindowHandle windowhandle, GHOST_TGrabCursorMode mode, diff --git a/intern/ghost/intern/GHOST_System.cpp b/intern/ghost/intern/GHOST_System.cpp index 2f4ab9ee37c..cf04287af9f 100644 --- a/intern/ghost/intern/GHOST_System.cpp +++ b/intern/ghost/intern/GHOST_System.cpp @@ -260,6 +260,29 @@ GHOST_TSuccess GHOST_System::pushEvent(GHOST_IEvent *event) return success; } +GHOST_TSuccess GHOST_System::getCursorPositionClientRelative(const GHOST_IWindow *window, + int32_t &x, + int32_t &y) const +{ + /* Sub-classes that can implement this directly should do so. */ + int32_t screen_x, screen_y; + GHOST_TSuccess success = getCursorPosition(screen_x, screen_y); + if (success == GHOST_kSuccess) { + window->screenToClient(screen_x, screen_y, x, y); + } + return success; +} + +GHOST_TSuccess GHOST_System::setCursorPositionClientRelative(GHOST_IWindow *window, + int32_t x, + int32_t y) +{ + /* Sub-classes that can implement this directly should do so. */ + int32_t screen_x, screen_y; + window->clientToScreen(x, y, screen_x, screen_y); + return setCursorPosition(screen_x, screen_y); +} + GHOST_TSuccess GHOST_System::getModifierKeyState(GHOST_TModifierKey mask, bool &isDown) const { GHOST_ModifierKeys keys; diff --git a/intern/ghost/intern/GHOST_System.h b/intern/ghost/intern/GHOST_System.h index e4a49551222..42f0a773dea 100644 --- a/intern/ghost/intern/GHOST_System.h +++ b/intern/ghost/intern/GHOST_System.h @@ -203,6 +203,17 @@ class GHOST_System : public GHOST_ISystem { * Cursor management functionality ***************************************************************************************/ + /* Client relative functions use a default implementation + * that converts from screen-coordinates to client coordinates. + * Implementations may override. */ + + GHOST_TSuccess getCursorPositionClientRelative(const GHOST_IWindow *window, + int32_t &x, + int32_t &y) const override; + GHOST_TSuccess setCursorPositionClientRelative(GHOST_IWindow *window, + int32_t x, + int32_t y) override; + /** * Inherited from GHOST_ISystem but left pure virtual *
diff --git a/source/blender/windowmanager/intern/wm_cursors.c b/source/blender/windowmanager/intern/wm_cursors.c
index 54636cb57ec..43be87fce39 100644
--- a/source/blender/windowmanager/intern/wm_cursors.c
+++ b/source/blender/windowmanager/intern/wm_cursors.c
@@ -231,8 +231,8 @@ void WM_cursor_grab_enable(wmWindow *win, int wrap, bool hide, int bounds[4])
   GHOST_TAxisFlag mode_axis = GHOST_kAxisX | GHOST_kAxisY;
 
   if (bounds) {
-    wm_cursor_position_to_ghost(win, &bounds[0], &bounds[1]);
-    wm_cursor_position_to_ghost(win, &bounds[2], &bounds[3]);
+    wm_cursor_position_to_ghost_screen_coords(win, &bounds[0], &bounds[1]);
+    wm_cursor_position_to_ghost_screen_coords(win, &bounds[2], &bounds[3]);
   }
 
   if (hide) {
@@ -266,7 +266,7 @@ void WM_cursor_grab_disable(wmWindow *win, const int mouse_ungrab_xy[2])
     if (win && win->ghostwin) {
       if (mouse_ungrab_xy) {
         int mouse_xy[2] = {mouse_ungrab_xy[0], mouse_ungrab_xy[1]};
-        wm_cursor_position_to_ghost(win, &mouse_xy[0], &mouse_xy[1]);
+        wm_cursor_position_to_ghost_screen_coords(win, &mouse_xy[0], &mouse_xy[1]);
         GHOST_SetCursorGrab(win->ghostwin, GHOST_kGrabDisable, GHOST_kAxisNone, NULL, mouse_xy);
       }
       else {
diff --git a/source/blender/windowmanager/intern/wm_playanim.c b/source/blender/windowmanager/intern/wm_playanim.c
index baba64b2230..99f117f267a 100644
--- a/source/blender/windowmanager/intern/wm_playanim.c
+++ b/source/blender/windowmanager/intern/wm_playanim.c
@@ -1217,8 +1217,7 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void)
       GHOST_TEventButtonData *bd = GHOST_GetEventData(evt);
       int cx, cy, sizex, sizey, inside_window;
 
-      GHOST_GetCursorPosition(g_WS.ghost_system, &cx, &cy);
-      GHOST_ScreenToClient(g_WS.ghost_window, cx, cy, &cx, &cy);
+      GHOST_GetCursorPosition(g_WS.ghost_system, g_WS.ghost_window, &cx, &cy);
       playanim_window_get_size(&sizex, &sizey);
 
       inside_window = (cx >= 0 && cx < sizex && cy >= 0 && cy <= sizey);
@@ -1267,15 +1266,15 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void)
          * however the API currently doesn't support this. */
         {
           int x_test, y_test;
-          GHOST_GetCursorPosition(g_WS.ghost_system, &x_test, &y_test);
-          if (x_test != cd->x || y_test != cd->y) {
+          GHOST_GetCursorPosition(g_WS.ghost_system, g_WS.ghost_window, &cx, &cy);
+          GHOST_ScreenToClient(g_WS.ghost_window, cd->x, cd->y, &x_test, &y_test);
+
+          if (cx != x_test || cy != y_test) {
             /* we're not the last event... skipping */
             break;
           }
         }
 
-        GHOST_ScreenToClient(g_WS.ghost_window, cd->x, cd->y, &cx, &cy);
-
         tag_change_frame(ps, cx);
       }
       break;
diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c
index baf137e6665..36f91f8414a 100644
--- a/source/blender/windowmanager/intern/wm_window.c
+++ b/source/blender/windowmanager/intern/wm_window.c
@@ -920,25 +920,33 @@ int wm_window_fullscreen_toggle_exec(bContext *C, wmOperator *UNUSED(op))
 
 /* ************ events *************** */
 
-void wm_cursor_position_from_ghost(wmWindow *win, int *x, int *y)
+void wm_cursor_position_from_ghost_client_coords(wmWindow *win, int *x, int *y)
 {
   float fac = GHOST_GetNativePixelSize(win->ghostwin);
-
-  GHOST_ScreenToClient(win->ghostwin, *x, *y, x, y);
   *x *= fac;
 
   *y = (win->sizey - 1) - *y;
   *y *= fac;
 }
 
-void wm_cursor_position_to_ghost(wmWindow *win, int *x, int *y)
+void wm_cursor_position_to_ghost_client_coords(wmWindow *win, int *x, int *y)
 {
   float fac = GHOST_GetNativePixelSize(win->ghostwin);
 
   *x /= fac;
   *y /= fac;
   *y = win->sizey - *y - 1;
+}
+
+void wm_cursor_position_from_ghost_screen_coords(wmWindow *win, int *x, int *y)
+{
+  GHOST_ScreenToClient(win->ghostwin, *x, *y, x, y);
+  wm_cursor_position_from_ghost_client_coords(win, x, y);
+}
 
+void wm_cursor_position_to_ghost_screen_coords(wmWindow *win, int *x, int *y)
+{
+  wm_cursor_position_to_ghost_client_coords(win, x, y);
   GHOST_ClientToScreen(win->ghostwin, *x, *y, x, y);
 }
 
@@ -949,8 +957,8 @@ void wm_cursor_position_get(wmWindow *win, int *r_x, int *r_y)
     *r_y = win->eventstate->xy[1];
     return;
   }
-  GHOST_GetCursorPosition(g_system, r_x, r_y);
-  wm_cursor_position_from_ghost(win, r_x, r_y);
+  GHOST_GetCursorPosition(g_system, win->ghostwin, r_x, r_y);
+  wm_cursor_position_from_ghost_client_coords(win, r_x, r_y);
 }
 
 typedef enum {
@@ -1418,14 +1426,14 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_pt
       case GHOST_kEventTrackpad: {
         GHOST_TEventTrackpadData *pd = data;
 
-        wm_cursor_position_from_ghost(win, &pd->x, &pd->y);
+        wm_cursor_position_from_ghost_screen_coords(win, &pd->x, &pd->y);
         wm_event_add_ghostevent(wm, win, type, data);
         break;
       }
       case GHOST_kEventCursorMove: {
         GHOST_TEventCursorData *cd = data;
 
-        wm_cursor_position_from_ghost(win, &cd->x, &cd->y);
+        wm_cursor_position_from_ghost_screen_coords(win, &cd->x, &cd->y);
         wm_event_add_ghostevent(wm, win, type, data);
         break;
       }
@@ -1866,7 +1874,7 @@ wmWindow *WM_window_find_under_cursor(wmWindow *win, const int mval[2], int r_mv
 {
   int tmp[2];
   copy_v2_v2_int(tmp, mval);
-  wm_cursor_position_to_ghost(win, &tmp[0], &tmp[1]);
+  wm_cursor_position_to_ghost_screen_coords(win, &tmp[0], &tmp[1]);
 
   GHOST_WindowHandle ghostwin = GHOST_GetWindowUnderCursor(g_system, tmp[0], tmp[1]);
 
@@ -1875,7 +1883,7 @@ wmWindow *WM_window_find_under_cursor(wmWindow *win, const int mval[2], int r_mv
   }
 
   wmWindow *win_other = GHOST_GetWindowUserData(ghostwin);
-  wm_cursor_position_from_ghost(win_other, &tmp[0], &tmp[1]);
+  wm_cursor_position_from_ghost_screen_coords(win_other, &tmp[0], &tmp[1]);
   copy_v2_v2_int(r_mval, tmp);
   return win_other;
 }
@@ -2015,8 +2023,8 @@ void WM_cursor_warp(wmWindow *win, int x, int y)
   if (win && win->ghostwin) {
     int oldx = x, oldy = y;
 
-    wm_cursor_position_to_ghost(win, &x, &y);
-    GHOST_SetCursorPosition(g_system, x, y);
+    wm_cursor_position_to_ghost_client_coords(win, &x, &y);
+    GHOST_SetCursorPosition(g_system, win->ghostwin, x, y);
 
     win->eventstate->prev_xy[0] = oldx;
     win->eventstate->prev_xy[1] = oldy;
diff --git a/source/blender/windowmanager/wm_window.h b/source/blender/windowmanager/wm_window.h
index 67557768413..3644aa085f7 100644
--- a/source/blender/windowmanager/wm_window.h
+++ b/source/blender/windowmanager/wm_window.h
@@ -100,8 +100,11 @@ void wm_window_set_swap_interval(wmWindow *win, int interval);
 bool wm_window_get_swap_interval(wmWindow *win, int *intervalOut);
 
 void wm_cursor_position_get(wmWindow *win, int *r_x, int *r_y);
-void wm_cursor_position_from_ghost(wmWindow *win, int *r_x, int *r_y);
-void wm_cursor_position_to_ghost(wmWindow *win, int *x, int *y);
+void wm_cursor_position_from_ghost_screen_coords(wmWindow *win, int *r_x, int *r_y);
+void wm_cursor_position_to_ghost_screen_coords(wmWindow *win, int *x, int *y);
+
+void wm_cursor_position_from_ghost_client_coords(wmWindow *win, int *x, int *y);
+void wm_cursor_position_to_ghost_client_coords(wmWindow *win, int *x, int *y);
 
 #ifdef WITH_INPUT_IME
 void wm_window_IME_begin(wmWindow *win, int x, int y, int w, int h, bool complete);
-- 
cgit v1.2.3


From 1cf64434ed1aa2a3be65c73c61e030745a597858 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Thu, 30 Jun 2022 22:53:24 +1000
Subject: Fix accessing cursor position for GHOST/Wayland

GHOST_GetCursorPosition wasn't working properly under Wayland because
the last focused window didn't necessarily match the window used to call
wm_cursor_position_get(..).

Now the window passed into wm_cursor_position_get is passed to GHOST
so that window is used to access cursor coordinates.
---
 intern/ghost/intern/GHOST_SystemWayland.cpp | 92 +++++++++++++++++++++--------
 intern/ghost/intern/GHOST_SystemWayland.h   |  8 ++-
 2 files changed, 74 insertions(+), 26 deletions(-)

diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp
index af629e83b19..cec06ed6a50 100644
--- a/intern/ghost/intern/GHOST_SystemWayland.cpp
+++ b/intern/ghost/intern/GHOST_SystemWayland.cpp
@@ -2649,58 +2649,100 @@ uint8_t GHOST_SystemWayland::getNumDisplays() const
   return d ? uint8_t(d->outputs.size()) : 0;
 }
 
-GHOST_TSuccess GHOST_SystemWayland::getCursorPosition(int32_t &x, int32_t &y) const
+static GHOST_TSuccess getCursorPositionClientRelative_impl(
+    const input_state_pointer_t *input_state,
+    const GHOST_WindowWayland *win,
+    int32_t &x,
+    int32_t &y)
 {
-  if (d->inputs.empty()) {
+  const wl_fixed_t scale = win->scale();
+  x = wl_fixed_to_int(scale * input_state->xy[0]);
+  y = wl_fixed_to_int(scale * input_state->xy[1]);
+  return GHOST_kSuccess;
+}
+
+static GHOST_TSuccess setCursorPositionClientRelative_impl(input_t *input,
+                                                           GHOST_WindowWayland *win,
+                                                           const int32_t x,
+                                                           const int32_t y)
+{
+  /* NOTE: WAYLAND doesn't support warping the cursor.
+   * However when grab is enabled, we already simulate a cursor location
+   * so that can be set to a new location. */
+  if (!input->relative_pointer) {
     return GHOST_kFailure;
   }
+  const wl_fixed_t scale = win->scale();
+  const wl_fixed_t xy_next[2] = {
+      wl_fixed_from_int(x) / scale,
+      wl_fixed_from_int(y) / scale,
+  };
 
+  /* As the cursor was "warped" generate an event at the new location. */
+  relative_pointer_handle_relative_motion_impl(input, win, xy_next);
+
+  return GHOST_kSuccess;
+}
+
+GHOST_TSuccess GHOST_SystemWayland::getCursorPositionClientRelative(const GHOST_IWindow *window,
+                                                                    int32_t &x,
+                                                                    int32_t &y) const
+{
+  if (d->inputs.empty()) {
+    return GHOST_kFailure;
+  }
   input_t *input = d->inputs[0];
   input_state_pointer_t *input_state = input_state_pointer_active(input);
-
   if (!input_state || !input_state->wl_surface) {
     return GHOST_kFailure;
   }
+  const GHOST_WindowWayland *win = static_cast(window);
+  return getCursorPositionClientRelative_impl(input_state, win, x, y);
+}
 
-  GHOST_WindowWayland *win = static_cast(
-      wl_surface_get_user_data(input_state->wl_surface));
-  if (!win) {
+GHOST_TSuccess GHOST_SystemWayland::setCursorPositionClientRelative(GHOST_IWindow *window,
+                                                                    const int32_t x,
+                                                                    const int32_t y)
+{
+  if (d->inputs.empty()) {
     return GHOST_kFailure;
   }
+  input_t *input = d->inputs[0];
+  GHOST_WindowWayland *win = static_cast(window);
+  return setCursorPositionClientRelative_impl(input, win, x, y);
+}
 
-  const wl_fixed_t scale = win->scale();
-  x = wl_fixed_to_int(scale * input_state->xy[0]);
-  y = wl_fixed_to_int(scale * input_state->xy[1]);
-  return GHOST_kSuccess;
+GHOST_TSuccess GHOST_SystemWayland::getCursorPosition(int32_t &x, int32_t &y) const
+{
+  if (d->inputs.empty()) {
+    return GHOST_kFailure;
+  }
+  input_t *input = d->inputs[0];
+  input_state_pointer_t *input_state = input_state_pointer_active(input);
+  if (!input_state || !input_state->wl_surface) {
+    return GHOST_kFailure;
+  }
+  GHOST_WindowWayland *win = window_from_surface(input->pointer.wl_surface);
+  if (!win) {
+    return GHOST_kFailure;
+  }
+  return getCursorPositionClientRelative_impl(input_state, win, x, y);
 }
 
 GHOST_TSuccess GHOST_SystemWayland::setCursorPosition(const int32_t x, const int32_t y)
 {
-  /* NOTE: WAYLAND doesn't support warping the cursor.
-   * However when grab is enabled, we already simulate a cursor location
-   * so that can be set to a new location. */
   if (d->inputs.empty()) {
     return GHOST_kFailure;
   }
   input_t *input = d->inputs[0];
-  if (!input->relative_pointer) {
+  if (!input->pointer.wl_surface) {
     return GHOST_kFailure;
   }
-
   GHOST_WindowWayland *win = window_from_surface(input->pointer.wl_surface);
   if (!win) {
     return GHOST_kFailure;
   }
-  const wl_fixed_t scale = win->scale();
-  const wl_fixed_t xy_next[2] = {
-      wl_fixed_from_int(x) / scale,
-      wl_fixed_from_int(y) / scale,
-  };
-
-  /* As the cursor was "warped" generate an event at the new location. */
-  relative_pointer_handle_relative_motion_impl(input, win, xy_next);
-
-  return GHOST_kSuccess;
+  return setCursorPositionClientRelative_impl(input, win, x, y);
 }
 
 void GHOST_SystemWayland::getMainDisplayDimensions(uint32_t &width, uint32_t &height) const
diff --git a/intern/ghost/intern/GHOST_SystemWayland.h b/intern/ghost/intern/GHOST_SystemWayland.h
index 972d16257eb..7f7bd021bda 100644
--- a/intern/ghost/intern/GHOST_SystemWayland.h
+++ b/intern/ghost/intern/GHOST_SystemWayland.h
@@ -79,8 +79,14 @@ class GHOST_SystemWayland : public GHOST_System {
 
   uint8_t getNumDisplays() const override;
 
-  GHOST_TSuccess getCursorPosition(int32_t &x, int32_t &y) const override;
+  GHOST_TSuccess getCursorPositionClientRelative(const GHOST_IWindow *window,
+                                                 int32_t &x,
+                                                 int32_t &y) const override;
+  GHOST_TSuccess setCursorPositionClientRelative(GHOST_IWindow *window,
+                                                 int32_t x,
+                                                 int32_t y) override;
 
+  GHOST_TSuccess getCursorPosition(int32_t &x, int32_t &y) const override;
   GHOST_TSuccess setCursorPosition(int32_t x, int32_t y) override;
 
   void getMainDisplayDimensions(uint32_t &width, uint32_t &height) const override;
-- 
cgit v1.2.3


From cfd087673dfc74c411d5aeadc8b8c61ce1b05b9f Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Thu, 30 Jun 2022 22:53:31 +1000
Subject: Fix key/dnd event handling accessing freed memory under Wayland

Closing a window could leave danging pointers which Wayland
callbacks are responsible for clearing.

However, any calls Blender makes that don't originate from Wayland's
handlers don't have that assurance (key-repeat in this case).

Resolve by using a window lookup on each key-repeat event.
---
 intern/ghost/intern/GHOST_SystemWayland.cpp | 39 ++++++++++++++++-------------
 1 file changed, 22 insertions(+), 17 deletions(-)

diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp
index cec06ed6a50..6665a962e81 100644
--- a/intern/ghost/intern/GHOST_SystemWayland.cpp
+++ b/intern/ghost/intern/GHOST_SystemWayland.cpp
@@ -173,9 +173,15 @@ struct data_source_t {
   char *buffer_out = nullptr;
 };
 
+/**
+ * Data used to implement client-side key-repeat.
+ *
+ * \note it's important not to store the target window here
+ * as it can be closed while the key is repeating,
+ * instead use the focused keyboard from #intput_t which is cleared when windows are closed.
+ * Therefor keyboard events must always check the window has not been cleared.
+ */
 struct key_repeat_payload_t {
-  GHOST_SystemWayland *system = nullptr;
-  GHOST_IWindow *window = nullptr;
   struct input_t *input = nullptr;
 
   xkb_keycode_t key_code;
@@ -803,8 +809,7 @@ static void dnd_events(const input_t *const input, const GHOST_TEventType event)
 {
   /* NOTE: `input->data_offer_dnd_mutex` must already be locked. */
   const uint64_t time = input->system->getMilliSeconds();
-  GHOST_WindowWayland *const win = static_cast(
-      wl_surface_get_user_data(input->focus_dnd));
+  GHOST_WindowWayland *const win = window_from_surface(input->focus_dnd);
   if (!win) {
     return;
   }
@@ -2027,8 +2032,6 @@ static void keyboard_handle_key(void *data,
     if ((input->key_repeat.rate > 0) && (etype == GHOST_kEventKeyDown) &&
         xkb_keymap_key_repeats(xkb_state_get_keymap(input->xkb_state), key_code)) {
       key_repeat_payload = new key_repeat_payload_t({
-          .system = input->system,
-          .window = win,
           .input = input,
           .key_code = key_code,
           .key_data = {.gkey = gkey},
@@ -2042,17 +2045,19 @@ static void keyboard_handle_key(void *data,
           task->getUserData());
 
       input_t *input = payload->input;
-      /* Calculate this value every time in case modifier keys are pressed. */
-      char utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)] = {'\0'};
-      xkb_state_key_get_utf8(input->xkb_state, payload->key_code, utf8_buf, sizeof(utf8_buf));
-
-      payload->system->pushEvent(new GHOST_EventKey(payload->system->getMilliSeconds(),
-                                                    GHOST_kEventKeyDown,
-                                                    payload->window,
-                                                    payload->key_data.gkey,
-                                                    '\0',
-                                                    utf8_buf,
-                                                    true));
+      if (GHOST_IWindow *win = window_from_surface(input->keyboard.wl_surface)) {
+        GHOST_SystemWayland *system = input->system;
+        /* Calculate this value every time in case modifier keys are pressed. */
+        char utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)] = {'\0'};
+        xkb_state_key_get_utf8(input->xkb_state, payload->key_code, utf8_buf, sizeof(utf8_buf));
+        system->pushEvent(new GHOST_EventKey(system->getMilliSeconds(),
+                                             GHOST_kEventKeyDown,
+                                             win,
+                                             payload->key_data.gkey,
+                                             '\0',
+                                             utf8_buf,
+                                             true));
+      }
     };
     input->key_repeat.timer = input->system->installTimer(
         input->key_repeat.delay, 1000 / input->key_repeat.rate, key_repeat_fn, key_repeat_payload);
-- 
cgit v1.2.3


From 7c98632289ead480c3d8d28f47815078a4d4c329 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Thu, 30 Jun 2022 22:53:33 +1000
Subject: GHOST/Wayland: use flush instead of roundtrip

Using flush avoids handling new events which complicates logic here.
---
 intern/ghost/intern/GHOST_WindowWayland.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp
index 27c2a09465d..27bda5ea41e 100644
--- a/intern/ghost/intern/GHOST_WindowWayland.cpp
+++ b/intern/ghost/intern/GHOST_WindowWayland.cpp
@@ -721,7 +721,7 @@ GHOST_WindowWayland::~GHOST_WindowWayland()
 
   /* NOTE(@campbellbarton): This is needed so the appropriate handlers event
    * (#wl_surface_listener.leave in particular) run to prevent access to the freed surfaces.
-   * Without this round-trip, calling #getCursorPosition immediately after closing a window
+   * Without flushing the display, calling #getCursorPosition immediately after closing a window
    * causes dangling #wl_surface pointers to be accessed
    * (since the window is used for scaling the cursor position).
    *
@@ -731,7 +731,7 @@ GHOST_WindowWayland::~GHOST_WindowWayland()
    * Any information requested in this state (such as the cursor position) won't be valid and
    * could cause difficult to reproduce bugs. So perform a round-trip as closing a window isn't
    * an action that runs continuously & isn't likely to cause unnecessary overhead. See: T99078. */
-  wl_display_roundtrip(m_system->display());
+  wl_display_flush(m_system->display());
 
   delete w;
 }
-- 
cgit v1.2.3


From 58ccd8338e53423e223085094af2d35c76285c30 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Thu, 30 Jun 2022 22:53:35 +1000
Subject: GHOST/Wayland: clarify window access from surfaces

It wasn't obvious when direct access or lookups should be used.

Add class methods for direct lookups as well as searching from known
windows when windows are accessed outside Wayland's handlers.

This avoids having to check if the window has been removed in some cases.
---
 intern/ghost/intern/GHOST_SystemWayland.cpp | 160 ++++++++++------------------
 intern/ghost/intern/GHOST_WindowWayland.cpp |  42 ++++++++
 intern/ghost/intern/GHOST_WindowWayland.h   |  12 +++
 3 files changed, 108 insertions(+), 106 deletions(-)

diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp
index 6665a962e81..985310dc919 100644
--- a/intern/ghost/intern/GHOST_SystemWayland.cpp
+++ b/intern/ghost/intern/GHOST_SystemWayland.cpp
@@ -45,8 +45,6 @@
 #include 
 #include 
 
-static GHOST_WindowWayland *window_from_surface(struct wl_surface *surface);
-
 static void keyboard_handle_key_repeat_cancel(struct input_t *input);
 
 static void output_handle_done(void *data, struct wl_output *wl_output);
@@ -152,6 +150,8 @@ struct cursor_t {
 struct tablet_tool_input_t {
   struct input_t *input = nullptr;
   struct wl_surface *cursor_surface = nullptr;
+  /** Used to delay clearing tablet focused surface until the frame is handled. */
+  bool proximity = false;
 
   GHOST_TabletData data = GHOST_TABLET_DATA_NONE;
 };
@@ -783,10 +783,7 @@ static void relative_pointer_handle_relative_motion(
     const wl_fixed_t /*dy_unaccel*/)
 {
   input_t *input = static_cast(data);
-  GHOST_WindowWayland *win = window_from_surface(input->pointer.wl_surface);
-  if (!win) {
-    return;
-  }
+  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(input->pointer.wl_surface);
   const wl_fixed_t scale = win->scale();
   const wl_fixed_t xy_next[2] = {
       input->pointer.xy[0] + (dx / scale),
@@ -809,10 +806,7 @@ static void dnd_events(const input_t *const input, const GHOST_TEventType event)
 {
   /* NOTE: `input->data_offer_dnd_mutex` must already be locked. */
   const uint64_t time = input->system->getMilliSeconds();
-  GHOST_WindowWayland *const win = window_from_surface(input->focus_dnd);
-  if (!win) {
-    return;
-  }
+  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(input->focus_dnd);
   const wl_fixed_t scale = win->scale();
   const int event_xy[2] = {
       wl_fixed_to_int(scale * input->data_offer_dnd->dnd.xy[0]),
@@ -1077,7 +1071,7 @@ static void data_device_handle_drop(void *data, struct wl_data_device * /*wl_dat
       static constexpr const char *file_proto = "file://";
       static constexpr const char *crlf = "\r\n";
 
-      GHOST_WindowWayland *win = window_from_surface(surface);
+      GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(surface);
       GHOST_ASSERT(win != nullptr, "Unable to find window for drop event from surface");
 
       std::vector uris;
@@ -1213,19 +1207,6 @@ const struct wl_buffer_listener cursor_buffer_listener = {
 /** \name Listener (Surface), #wl_surface_listener
  * \{ */
 
-static GHOST_WindowWayland *window_from_surface(struct wl_surface *surface)
-{
-  if (surface) {
-    for (GHOST_IWindow *iwin : window_manager->getWindows()) {
-      GHOST_WindowWayland *win = static_cast(iwin);
-      if (surface == win->surface()) {
-        return win;
-      }
-    }
-  }
-  return nullptr;
-}
-
 static bool update_cursor_scale(cursor_t &cursor, wl_shm *shm)
 {
   int scale = 0;
@@ -1291,10 +1272,7 @@ static void pointer_handle_enter(void *data,
                                  const wl_fixed_t surface_x,
                                  const wl_fixed_t surface_y)
 {
-  GHOST_WindowWayland *win = window_from_surface(surface);
-  if (!win) {
-    return;
-  }
+  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(surface);
 
   win->activate();
 
@@ -1310,7 +1288,7 @@ static void pointer_handle_enter(void *data,
   const wl_fixed_t scale = win->scale();
   input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(),
                                                  GHOST_kEventCursorMove,
-                                                 static_cast(win),
+                                                 win,
                                                  wl_fixed_to_int(scale * input->pointer.xy[0]),
                                                  wl_fixed_to_int(scale * input->pointer.xy[1]),
                                                  GHOST_TABLET_DATA_NONE));
@@ -1323,12 +1301,12 @@ static void pointer_handle_leave(void *data,
 {
   /* First clear the `pointer.wl_surface`, since the window won't exist when closing the window. */
   static_cast(data)->pointer.wl_surface = nullptr;
-
-  GHOST_IWindow *win = window_from_surface(surface);
-  if (!win) {
+  /* Use `from_surface_find_mut` because this callback runs when the window is has been closed. */
+  if (!surface) {
     return;
   }
-  static_cast(win)->deactivate();
+  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(surface);
+  win->deactivate();
 }
 
 static void pointer_handle_motion(void *data,
@@ -1341,10 +1319,7 @@ static void pointer_handle_motion(void *data,
   input->pointer.xy[0] = surface_x;
   input->pointer.xy[1] = surface_y;
 
-  GHOST_WindowWayland *win = window_from_surface(input->pointer.wl_surface);
-  if (!win) {
-    return;
-  }
+  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(input->pointer.wl_surface);
   const wl_fixed_t scale = win->scale();
   input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(),
                                                  GHOST_kEventCursorMove,
@@ -1401,11 +1376,7 @@ static void pointer_handle_button(void *data,
   input->data_source_serial = serial;
   input->pointer.buttons.set(ebutton, state == WL_POINTER_BUTTON_STATE_PRESSED);
 
-  GHOST_IWindow *win = window_from_surface(input->pointer.wl_surface);
-  if (!win) {
-    return;
-  }
-
+  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(input->pointer.wl_surface);
   input->system->pushEvent(new GHOST_EventButton(
       input->system->getMilliSeconds(), etype, win, ebutton, GHOST_TABLET_DATA_NONE));
 }
@@ -1422,10 +1393,7 @@ static void pointer_handle_axis(void *data,
     return;
   }
 
-  GHOST_IWindow *win = window_from_surface(input->pointer.wl_surface);
-  if (!win) {
-    return;
-  }
+  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(input->pointer.wl_surface);
   input->system->pushEvent(
       new GHOST_EventWheel(input->system->getMilliSeconds(), win, std::signbit(value) ? +1 : -1));
 }
@@ -1497,6 +1465,7 @@ static void tablet_tool_handle_proximity_in(void *data,
                                             struct wl_surface *surface)
 {
   tablet_tool_input_t *tool_input = static_cast(data);
+  tool_input->proximity = true;
 
   input_t *input = tool_input->input;
   input->cursor_source_serial = serial;
@@ -1513,10 +1482,8 @@ static void tablet_tool_handle_proximity_in(void *data,
   /* In case pressure isn't supported. */
   td.Pressure = 1.0f;
 
-  GHOST_WindowWayland *win = window_from_surface(input->tablet.wl_surface);
-  if (!win) {
-    return;
-  }
+  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(input->tablet.wl_surface);
+
   win->activate();
 
   win->setCursorShape(win->getCursorShape());
@@ -1525,14 +1492,9 @@ static void tablet_tool_handle_proximity_out(void *data,
                                              struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/)
 {
   tablet_tool_input_t *tool_input = static_cast(data);
-  input_t *input = tool_input->input;
-  input->tablet.wl_surface = nullptr;
-
-  GHOST_WindowWayland *win = window_from_surface(input->tablet.wl_surface);
-  if (!win) {
-    return;
-  }
-  win->setCursorShape(win->getCursorShape());
+  /* Defer clearing the surface until the frame is handled.
+   * Without this, the frame can not access the surface. */
+  tool_input->proximity = false;
 }
 
 static void tablet_tool_handle_down(void *data,
@@ -1547,10 +1509,7 @@ static void tablet_tool_handle_down(void *data,
   input->data_source_serial = serial;
   input->tablet.buttons.set(ebutton, true);
 
-  GHOST_WindowWayland *win = window_from_surface(input->tablet.wl_surface);
-  if (!win) {
-    return;
-  }
+  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(input->tablet.wl_surface);
   input->system->pushEvent(new GHOST_EventButton(
       input->system->getMilliSeconds(), etype, win, ebutton, tool_input->data));
 }
@@ -1564,10 +1523,7 @@ static void tablet_tool_handle_up(void *data, struct zwp_tablet_tool_v2 * /*zwp_
 
   input->tablet.buttons.set(ebutton, false);
 
-  GHOST_WindowWayland *win = window_from_surface(input->tablet.wl_surface);
-  if (!win) {
-    return;
-  }
+  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(input->tablet.wl_surface);
   input->system->pushEvent(new GHOST_EventButton(
       input->system->getMilliSeconds(), etype, win, ebutton, tool_input->data));
 }
@@ -1591,12 +1547,6 @@ static void tablet_tool_handle_pressure(void *data,
                                         const uint32_t pressure)
 {
   tablet_tool_input_t *tool_input = static_cast(data);
-  input_t *input = tool_input->input;
-  GHOST_WindowWayland *win = window_from_surface(input->tablet.wl_surface);
-  if (!win) {
-    return;
-  }
-
   GHOST_TabletData &td = tool_input->data;
   td.Pressure = (float)pressure / 65535;
 }
@@ -1611,12 +1561,6 @@ static void tablet_tool_handle_tilt(void *data,
                                     const wl_fixed_t tilt_y)
 {
   tablet_tool_input_t *tool_input = static_cast(data);
-  input_t *input = tool_input->input;
-  GHOST_WindowWayland *win = window_from_surface(input->tablet.wl_surface);
-  if (!win) {
-    return;
-  }
-
   GHOST_TabletData &td = tool_input->data;
   /* Map degrees to `-1.0..1.0`. */
   td.Xtilt = wl_fixed_to_double(tilt_x) / 90.0f;
@@ -1649,11 +1593,7 @@ static void tablet_tool_handle_wheel(void *data,
 
   tablet_tool_input_t *tool_input = static_cast(data);
   input_t *input = tool_input->input;
-  GHOST_WindowWayland *win = window_from_surface(input->tablet.wl_surface);
-  if (!win) {
-    return;
-  }
-
+  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(input->tablet.wl_surface);
   input->system->pushEvent(new GHOST_EventWheel(input->system->getMilliSeconds(), win, clicks));
 }
 static void tablet_tool_handle_button(void *data,
@@ -1691,10 +1631,7 @@ static void tablet_tool_handle_button(void *data,
   input->data_source_serial = serial;
   input->tablet.buttons.set(ebutton, state == WL_POINTER_BUTTON_STATE_PRESSED);
 
-  GHOST_WindowWayland *win = window_from_surface(input->tablet.wl_surface);
-  if (!win) {
-    return;
-  }
+  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(input->tablet.wl_surface);
   input->system->pushEvent(new GHOST_EventButton(
       input->system->getMilliSeconds(), etype, win, ebutton, tool_input->data));
 }
@@ -1704,18 +1641,26 @@ static void tablet_tool_handle_frame(void *data,
 {
   tablet_tool_input_t *tool_input = static_cast(data);
   input_t *input = tool_input->input;
-  GHOST_WindowWayland *win = window_from_surface(input->tablet.wl_surface);
-  if (!win) {
-    return;
-  }
 
-  const wl_fixed_t scale = win->scale();
-  input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(),
-                                                 GHOST_kEventCursorMove,
-                                                 win,
-                                                 wl_fixed_to_int(scale * input->tablet.xy[0]),
-                                                 wl_fixed_to_int(scale * input->tablet.xy[1]),
-                                                 tool_input->data));
+  /* Use "find", unlike most other handlers because the window
+   * may have been closed before this handler is called. */
+  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_find_mut(input->tablet.wl_surface);
+  if (win) {
+    const wl_fixed_t scale = win->scale();
+    input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(),
+                                                   GHOST_kEventCursorMove,
+                                                   win,
+                                                   wl_fixed_to_int(scale * input->tablet.xy[0]),
+                                                   wl_fixed_to_int(scale * input->tablet.xy[1]),
+                                                   tool_input->data));
+  }
+
+  if (tool_input->proximity == false) {
+    if (win) {
+      win->setCursorShape(win->getCursorShape());
+    }
+    input->tablet.wl_surface = nullptr;
+  }
 }
 
 static const struct zwp_tablet_tool_v2_listener tablet_tool_listner = {
@@ -2021,8 +1966,7 @@ static void keyboard_handle_key(void *data,
 
   input->data_source_serial = serial;
 
-  GHOST_IWindow *win = static_cast(
-      wl_surface_get_user_data(input->keyboard.wl_surface));
+  GHOST_IWindow *win = GHOST_WindowWayland::from_surface_mut(input->keyboard.wl_surface);
   input->system->pushEvent(new GHOST_EventKey(
       input->system->getMilliSeconds(), etype, win, gkey, '\0', utf8_buf, false));
 
@@ -2045,7 +1989,8 @@ static void keyboard_handle_key(void *data,
           task->getUserData());
 
       input_t *input = payload->input;
-      if (GHOST_IWindow *win = window_from_surface(input->keyboard.wl_surface)) {
+      if (GHOST_IWindow *win = GHOST_WindowWayland::from_surface_find_mut(
+              input->keyboard.wl_surface)) {
         GHOST_SystemWayland *system = input->system;
         /* Calculate this value every time in case modifier keys are pressed. */
         char utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)] = {'\0'};
@@ -2727,7 +2672,8 @@ GHOST_TSuccess GHOST_SystemWayland::getCursorPosition(int32_t &x, int32_t &y) co
   if (!input_state || !input_state->wl_surface) {
     return GHOST_kFailure;
   }
-  GHOST_WindowWayland *win = window_from_surface(input->pointer.wl_surface);
+  /* Use 'from_surface_find_mut' because this can be called outside WAYLAND's event loop. */
+  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_find_mut(input->pointer.wl_surface);
   if (!win) {
     return GHOST_kFailure;
   }
@@ -2743,7 +2689,8 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorPosition(const int32_t x, const int
   if (!input->pointer.wl_surface) {
     return GHOST_kFailure;
   }
-  GHOST_WindowWayland *win = window_from_surface(input->pointer.wl_surface);
+  /* Use 'from_surface_find_mut' because this can be called outside WAYLAND's event loop. */
+  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_find_mut(input->pointer.wl_surface);
   if (!win) {
     return GHOST_kFailure;
   }
@@ -3333,7 +3280,8 @@ static bool setCursorGrab_use_software_confine(const GHOST_TGrabCursorMode mode,
   if (mode != GHOST_kGrabNormal) {
     return false;
   }
-  GHOST_WindowWayland *win = window_from_surface(surface);
+  /* Use 'from_surface_find_mut' because this can be called outside WAYLAND's event loop. */
+  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_find_mut(surface);
   if (!win) {
     return false;
   }
@@ -3412,8 +3360,8 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorGrab(const GHOST_TGrabCursorMode mo
     if (input->locked_pointer) {
       /* Request location to restore to. */
       if (mode_current == GHOST_kGrabWrap) {
-        /* The chance this fails is _very_ low. */
-        GHOST_WindowWayland *win = window_from_surface(surface);
+        /* The chance this fails is _very_ low, it may be called outside WAYLAND's event loop. */
+        GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_find_mut(surface);
         if (!win) {
           GHOST_PRINT("could not find window from surface when un-grabbing!" << std::endl);
         }
diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp
index 27bda5ea41e..dd61832e2ee 100644
--- a/intern/ghost/intern/GHOST_WindowWayland.cpp
+++ b/intern/ghost/intern/GHOST_WindowWayland.cpp
@@ -14,6 +14,7 @@
 #include "GHOST_ContextEGL.h"
 #include "GHOST_ContextNone.h"
 
+#include 
 #include 
 
 #include  /* For `std::find`. */
@@ -24,6 +25,8 @@
 
 static constexpr size_t base_dpi = 96;
 
+static GHOST_WindowManager *window_manager = nullptr;
+
 struct window_t {
   GHOST_WindowWayland *w = nullptr;
   struct wl_surface *wl_surface = nullptr;
@@ -380,6 +383,11 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system,
       m_system(system),
       w(new window_t)
 {
+  /* Globally store pointer to window manager. */
+  if (!window_manager) {
+    window_manager = m_system->getWindowManager();
+  }
+
   w->w = this;
 
   w->size[0] = int32_t(width);
@@ -521,6 +529,40 @@ wl_surface *GHOST_WindowWayland::surface() const
   return w->wl_surface;
 }
 
+GHOST_WindowWayland *GHOST_WindowWayland::from_surface_find_mut(const wl_surface *surface)
+{
+  GHOST_ASSERT(surface, "argument must not be NULL");
+  for (GHOST_IWindow *iwin : window_manager->getWindows()) {
+    GHOST_WindowWayland *win = static_cast(iwin);
+    if (surface == win->surface()) {
+      return win;
+    }
+  }
+  return nullptr;
+}
+
+const GHOST_WindowWayland *GHOST_WindowWayland::from_surface_find(const wl_surface *surface)
+{
+  return GHOST_WindowWayland::from_surface_find_mut(surface);
+}
+
+GHOST_WindowWayland *GHOST_WindowWayland::from_surface_mut(wl_surface *surface)
+{
+  GHOST_WindowWayland *win = static_cast(wl_surface_get_user_data(surface));
+  GHOST_ASSERT(win == GHOST_WindowWayland::from_surface_find_mut(surface),
+               "Inconsistent window state, consider using \"from_surface_find_mut\"");
+  return win;
+}
+
+const GHOST_WindowWayland *GHOST_WindowWayland::from_surface(const wl_surface *surface)
+{
+  const GHOST_WindowWayland *win = static_cast(
+      wl_surface_get_user_data(const_cast(surface)));
+  GHOST_ASSERT(win == GHOST_WindowWayland::from_surface_find(surface),
+               "Inconsistent window state, consider using \"from_surface_find\"");
+  return win;
+}
+
 const std::vector &GHOST_WindowWayland::outputs()
 {
   return w->outputs;
diff --git a/intern/ghost/intern/GHOST_WindowWayland.h b/intern/ghost/intern/GHOST_WindowWayland.h
index b5aeecd6204..1d0693ff1f8 100644
--- a/intern/ghost/intern/GHOST_WindowWayland.h
+++ b/intern/ghost/intern/GHOST_WindowWayland.h
@@ -105,6 +105,18 @@ class GHOST_WindowWayland : public GHOST_Window {
 
   struct wl_surface *surface() const;
 
+  /**
+   * Use window find function when the window may have been closed.
+   * Typically this is needed when accessing surfaces outside WAYLAND handlers.
+   */
+  static const GHOST_WindowWayland *from_surface_find(const wl_surface *surface);
+  static GHOST_WindowWayland *from_surface_find_mut(const wl_surface *surface);
+  /**
+   * Use direct access when from WAYLAND handlers.
+   */
+  static const GHOST_WindowWayland *from_surface(const wl_surface *surface);
+  static GHOST_WindowWayland *from_surface_mut(wl_surface *surface);
+
   output_t *output_find_by_wl(struct wl_output *output);
 
   const std::vector &outputs();
-- 
cgit v1.2.3


From 8bf9d482dacf54c72291de187676ff76845e807f Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Thu, 30 Jun 2022 23:00:55 +1000
Subject: Cleanup: colon after params, move text into public doc-strings,
 spelling

---
 source/blender/blenkernel/BKE_camera.h                   |  3 ++-
 source/blender/blenkernel/BKE_constraint.h               | 16 +++++++++-------
 source/blender/blenkernel/BKE_fcurve.h                   |  5 +++--
 source/blender/blenkernel/BKE_lib_override.h             |  4 ++--
 source/blender/blenkernel/intern/constraint.c            |  2 --
 source/blender/editors/sculpt_paint/curves_sculpt_ops.cc |  6 +++---
 .../blender/editors/sculpt_paint/curves_sculpt_pinch.cc  |  8 ++++----
 source/blender/gpu/intern/gpu_buffers.c                  |  6 +++---
 source/blender/makesrna/RNA_access.h                     |  2 +-
 9 files changed, 27 insertions(+), 25 deletions(-)

diff --git a/source/blender/blenkernel/BKE_camera.h b/source/blender/blenkernel/BKE_camera.h
index 40df7acd066..afe0eb35c4d 100644
--- a/source/blender/blenkernel/BKE_camera.h
+++ b/source/blender/blenkernel/BKE_camera.h
@@ -164,10 +164,11 @@ bool BKE_camera_multiview_spherical_stereo(const struct RenderData *rd,
 /* Camera background image API */
 
 struct CameraBGImage *BKE_camera_background_image_new(struct Camera *cam);
+
 /**
  * Duplicate a background image, in a ID management compatible way.
  *
- * \param copy_flag The usual ID copying flags, see `LIB_ID_CREATE_`/`LIB_ID_COPY_` enums in
+ * \param copy_flag: The usual ID copying flags, see `LIB_ID_CREATE_`/`LIB_ID_COPY_` enums in
  * `BKE_lib_id.h`.
  */
 struct CameraBGImage *BKE_camera_background_image_copy(struct CameraBGImage *bgpic_src,
diff --git a/source/blender/blenkernel/BKE_constraint.h b/source/blender/blenkernel/BKE_constraint.h
index 2be299f5b0c..3186be3674d 100644
--- a/source/blender/blenkernel/BKE_constraint.h
+++ b/source/blender/blenkernel/BKE_constraint.h
@@ -265,6 +265,8 @@ void BKE_constraint_panel_expand(struct bConstraint *con);
 /* Constraint Evaluation function prototypes */
 
 /**
+ * Package an object/bone for use in constraint evaluation.
+ *
  * This function MEM_calloc's a #bConstraintOb struct,
  * that will need to be freed after evaluation.
  */
@@ -312,17 +314,17 @@ void BKE_constraint_target_matrix_get(struct Depsgraph *depsgraph,
  * Retrieves the list of all constraint targets, including the custom space target.
  * Must be followed by a call to BKE_constraint_targets_flush to free memory.
  *
- * \param r_targets Pointer to the list to be initialized with target data.
+ * \param r_targets: Pointer to the list to be initialized with target data.
  * \returns the number of targets stored in the list.
  */
 int BKE_constraint_targets_get(struct bConstraint *con, struct ListBase *r_targets);
 
 /**
- * Copies changed data from the list produced by BKE_constraint_targets_get back to the constraint
+ * Copies changed data from the list produced by #BKE_constraint_targets_get back to the constraint
  * data structures and frees memory.
  *
- * \param targets List of targets filled by BKE_constraint_targets_get.
- * \param no_copy Only free memory without copying changes (read-only mode).
+ * \param targets: List of targets filled by BKE_constraint_targets_get.
+ * \param no_copy: Only free memory without copying changes (read-only mode).
  */
 void BKE_constraint_targets_flush(struct bConstraint *con, struct ListBase *targets, bool no_copy);
 
@@ -336,10 +338,10 @@ void BKE_constraint_targets_for_solving_get(struct Depsgraph *depsgraph,
                                             float ctime);
 
 /**
- * Initializes the custom coordinate space data if required by the constraint.
+ * Initialize the Custom Space matrix inside `cob` (if required by the constraint).
  *
- * \param cob Constraint evaluation context (contains the matrix to be initialized).
- * \param con Constraint that is about to be evaluated.
+ * \param cob: Constraint evaluation context (contains the matrix to be initialized).
+ * \param con: Constraint that is about to be evaluated.
  */
 void BKE_constraint_custom_object_space_init(struct bConstraintOb *cob, struct bConstraint *con);
 
diff --git a/source/blender/blenkernel/BKE_fcurve.h b/source/blender/blenkernel/BKE_fcurve.h
index 4489527fcab..10d9ce3364d 100644
--- a/source/blender/blenkernel/BKE_fcurve.h
+++ b/source/blender/blenkernel/BKE_fcurve.h
@@ -323,8 +323,9 @@ struct FCurve *BKE_fcurve_find_by_rna(struct PointerRNA *ptr,
  * Same as above, but takes a context data,
  * temp hack needed for complex paths like texture ones.
  *
- * \param r_special Optional, ignored when NULL. Set to `true` if the given RNA `ptr` is a NLA
- * strip, and the returned F-curve comes from this NLA strip. */
+ * \param r_special: Optional, ignored when NULL. Set to `true` if the given RNA `ptr` is a NLA
+ * strip, and the returned F-curve comes from this NLA strip.
+ */
 struct FCurve *BKE_fcurve_find_by_rna_context_ui(struct bContext *C,
                                                  const struct PointerRNA *ptr,
                                                  struct PropertyRNA *prop,
diff --git a/source/blender/blenkernel/BKE_lib_override.h b/source/blender/blenkernel/BKE_lib_override.h
index 2e28b3c00ee..b2162e651fd 100644
--- a/source/blender/blenkernel/BKE_lib_override.h
+++ b/source/blender/blenkernel/BKE_lib_override.h
@@ -73,9 +73,9 @@ bool BKE_lib_override_library_is_system_defined(const struct Main *bmain, const
  * Check if given Override Property for given ID is animated (through a F-Curve in an Action, or
  * from a driver).
  *
- * \param override_rna_prop if not NULL, the RNA property matching the given path in the
+ * \param override_rna_prop: if not NULL, the RNA property matching the given path in the
  * `override_prop`.
- * \param rnaprop_index Array in the RNA property, 0 if unknown or irrelevant.
+ * \param rnaprop_index: Array in the RNA property, 0 if unknown or irrelevant.
  */
 bool BKE_lib_override_library_property_is_animated(const ID *id,
                                                    const IDOverrideLibraryProperty *override_prop,
diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c
index cee32dc557b..8400d610a32 100644
--- a/source/blender/blenkernel/intern/constraint.c
+++ b/source/blender/blenkernel/intern/constraint.c
@@ -116,7 +116,6 @@ void BKE_constraint_unique_name(bConstraint *con, ListBase *list)
 
 /* ----------------- Evaluation Loop Preparation --------------- */
 
-/* package an object/bone for use in constraint evaluation */
 bConstraintOb *BKE_constraints_make_evalob(
     Depsgraph *depsgraph, Scene *scene, Object *ob, void *subdata, short datatype)
 {
@@ -6323,7 +6322,6 @@ void BKE_constraint_targets_for_solving_get(struct Depsgraph *depsgraph,
   }
 }
 
-/** Initialize the Custom Space matrix inside cob. */
 void BKE_constraint_custom_object_space_init(bConstraintOb *cob, bConstraint *con)
 {
   if (con && con->space_object && is_custom_space_needed(con)) {
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc
index 739c39f6196..5c73c7a37d3 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc
@@ -740,7 +740,7 @@ static void select_grow_invoke_per_curve(Curves &curves_id,
 
   threading::parallel_invoke(
       [&]() {
-        /* Build kd-tree for the selected points. */
+        /* Build KD-tree for the selected points. */
         KDTree_3d *kdtree = BLI_kdtree_3d_new(curve_op_data.selected_point_indices.size());
         BLI_SCOPED_DEFER([&]() { BLI_kdtree_3d_free(kdtree); });
         for (const int point_i : curve_op_data.selected_point_indices) {
@@ -765,7 +765,7 @@ static void select_grow_invoke_per_curve(Curves &curves_id,
                                 });
       },
       [&]() {
-        /* Build kd-tree for the unselected points. */
+        /* Build KD-tree for the unselected points. */
         KDTree_3d *kdtree = BLI_kdtree_3d_new(curve_op_data.unselected_point_indices.size());
         BLI_SCOPED_DEFER([&]() { BLI_kdtree_3d_free(kdtree); });
         for (const int point_i : curve_op_data.unselected_point_indices) {
@@ -1011,7 +1011,7 @@ static int calculate_points_per_side(bContext *C, MinDistanceEditData &op_data)
     }
   }
 
-  /* Limit to a harcoded number since it only adds noise at some point. */
+  /* Limit to a hard-coded number since it only adds noise at some point. */
   return std::min(300, needed_points);
 }
 
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc b/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc
index db890d6a054..5b7359a3905 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc
@@ -30,10 +30,10 @@
 
 /**
  * The code below uses a prefix naming convention to indicate the coordinate space:
- * cu: Local space of the curves object that is being edited.
- * su: Local space of the surface object.
- * wo: World space.
- * re: 2D coordinates within the region.
+ * `cu`: Local space of the curves object that is being edited.
+ * `su`: Local space of the surface object.
+ * `wo`: World space.
+ * `re`: 2D coordinates within the region.
  */
 
 namespace blender::ed::sculpt_paint {
diff --git a/source/blender/gpu/intern/gpu_buffers.c b/source/blender/gpu/intern/gpu_buffers.c
index 8665b49f4aa..14bbd82c282 100644
--- a/source/blender/gpu/intern/gpu_buffers.c
+++ b/source/blender/gpu/intern/gpu_buffers.c
@@ -1182,9 +1182,9 @@ GPU_PBVH_Buffers *GPU_pbvh_bmesh_buffers_build(bool smooth_shading)
  * Builds a list of attributes from a set of domains and a set of
  * customdata types.
  *
- * \param active_only Returns only one item, a GPUAttrRef to active_layer
- * \param active_layer CustomDataLayer to use for the active layer
- * \param active_layer CustomDataLayer to use for the render layer
+ * \param active_only: Returns only one item, a #GPUAttrRef to active_layer.
+ * \param active_layer: #CustomDataLayer to use for the active layer.
+ * \param active_layer: #CustomDataLayer to use for the render layer.
  */
 static int gpu_pbvh_make_attr_offs(eAttrDomainMask domain_mask,
                                    eCustomDataMask type_mask,
diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h
index 67605201a9f..005228bea72 100644
--- a/source/blender/makesrna/RNA_access.h
+++ b/source/blender/makesrna/RNA_access.h
@@ -497,7 +497,7 @@ char *RNA_path_back(const char *path);
  * character in `rna_path` that is part of the array index for the given property. Return NULL if
  * none can be found, e.g. because the property is not an RNA array.
  *
- * \param array_prop if not NULL, the PropertyRNA assumed to be the last one from the RNA path.
+ * \param array_prop: if not NULL, the #PropertyRNA assumed to be the last one from the RNA path.
  * Only used to ensure it is a valid array property.
  */
 const char *RNA_path_array_index_token_find(const char *rna_path, const PropertyRNA *array_prop);
-- 
cgit v1.2.3


From 6bb703a9ee8dc368c17f7432485e2e863597a576 Mon Sep 17 00:00:00 2001
From: Richard Antalik 
Date: Thu, 30 Jun 2022 15:56:08 +0200
Subject: Fix T99266: Crash when dragging file to VSE from file browser

Crash happened because sequencer data was not initialized. Ensure data
is initialized before adding strip.
---
 source/blender/editors/space_sequencer/sequencer_drag_drop.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source/blender/editors/space_sequencer/sequencer_drag_drop.c b/source/blender/editors/space_sequencer/sequencer_drag_drop.c
index 586cdad7e74..94427009939 100644
--- a/source/blender/editors/space_sequencer/sequencer_drag_drop.c
+++ b/source/blender/editors/space_sequencer/sequencer_drag_drop.c
@@ -233,7 +233,7 @@ static void update_overlay_strip_poistion_data(bContext *C, const int mval[2])
     coords->is_intersecting = false;
     Sequence dummy_seq = {
         .machine = coords->channel, .start = coords->start_frame, .len = coords->strip_len};
-    Editing *ed = SEQ_editing_get(scene);
+    Editing *ed = SEQ_editing_ensure(scene);
 
     for (int i = 0; i < coords->channel_len && !coords->is_intersecting; i++) {
       coords->is_intersecting = SEQ_transform_test_overlap(scene, ed->seqbasep, &dummy_seq);
-- 
cgit v1.2.3


From f00d9e80aec70c804747f9340bdf0e2fc0e94942 Mon Sep 17 00:00:00 2001
From: Andrii Symkin 
Date: Thu, 30 Jun 2022 16:22:43 +0200
Subject: Cycles: add more math functions for float4

Add more math functions for float4 to make them on par with float3 ones. It
makes it possible to change the types of float3 variables to float4 without
additional work.

Differential Revision: https://developer.blender.org/D15318
---
 intern/cycles/util/math_float4.h | 140 +++++++++++++++++++++++++--------------
 1 file changed, 90 insertions(+), 50 deletions(-)

diff --git a/intern/cycles/util/math_float4.h b/intern/cycles/util/math_float4.h
index 073c65c2d6a..c2721873037 100644
--- a/intern/cycles/util/math_float4.h
+++ b/intern/cycles/util/math_float4.h
@@ -55,6 +55,7 @@ ccl_device_inline float4 floor(const float4 &a);
 ccl_device_inline float4 mix(const float4 &a, const float4 &b, float t);
 #endif /* !__KERNEL_METAL__*/
 
+ccl_device_inline float4 safe_divide(const float4 a, const float4 b);
 ccl_device_inline float4 safe_divide(const float4 a, const float b);
 
 #ifdef __KERNEL_SSE__
@@ -74,11 +75,14 @@ template<> __forceinline const float4 shuffle<1, 1, 3, 3>(const float4 &b);
 #  endif
 #endif /* __KERNEL_SSE__ */
 
+ccl_device_inline float reduce_min(const float4 a);
+ccl_device_inline float reduce_max(const float4 a);
+ccl_device_inline float reduce_add(const float4 a);
+
+ccl_device_inline bool isequal(const float4 a, const float4 b);
+
 #ifndef __KERNEL_GPU__
 ccl_device_inline float4 select(const int4 &mask, const float4 &a, const float4 &b);
-ccl_device_inline float4 reduce_min(const float4 &a);
-ccl_device_inline float4 reduce_max(const float4 &a);
-ccl_device_inline float4 reduce_add(const float4 &a);
 #endif /* !__KERNEL_GPU__ */
 
 /*******************************************************************************
@@ -303,27 +307,9 @@ ccl_device_inline bool is_zero(const float4 &a)
 #  endif
 }
 
-ccl_device_inline float4 reduce_add(const float4 &a)
-{
-#  if defined(__KERNEL_SSE__)
-#    if defined(__KERNEL_NEON__)
-  return float4(vdupq_n_f32(vaddvq_f32(a)));
-#    elif defined(__KERNEL_SSE3__)
-  float4 h(_mm_hadd_ps(a.m128, a.m128));
-  return float4(_mm_hadd_ps(h.m128, h.m128));
-#    else
-  float4 h(shuffle<1, 0, 3, 2>(a) + a);
-  return shuffle<2, 3, 0, 1>(h) + h;
-#    endif
-#  else
-  float sum = (a.x + a.y) + (a.z + a.w);
-  return make_float4(sum, sum, sum, sum);
-#  endif
-}
-
 ccl_device_inline float average(const float4 &a)
 {
-  return reduce_add(a).x * 0.25f;
+  return reduce_add(a) * 0.25f;
 }
 
 ccl_device_inline float len(const float4 &a)
@@ -392,8 +378,77 @@ ccl_device_inline float4 mix(const float4 &a, const float4 &b, float t)
   return a + t * (b - a);
 }
 
+ccl_device_inline float4 saturate(const float4 &a)
+{
+  return make_float4(saturatef(a.x), saturatef(a.y), saturatef(a.z), saturatef(a.w));
+}
+
+ccl_device_inline float4 exp(float4 v)
+{
+  return make_float4(expf(v.x), expf(v.y), expf(v.z), expf(v.z));
+}
+
+ccl_device_inline float4 log(float4 v)
+{
+  return make_float4(logf(v.x), logf(v.y), logf(v.z), logf(v.z));
+}
+
 #endif /* !__KERNEL_METAL__*/
 
+ccl_device_inline float reduce_add(const float4 a)
+{
+#if defined(__KERNEL_SSE__)
+#  if defined(__KERNEL_NEON__)
+  return vaddvq_f32(a);
+#  elif defined(__KERNEL_SSE3__)
+  float4 h(_mm_hadd_ps(a.m128, a.m128));
+  return _mm_cvtss_f32(_mm_hadd_ps(h.m128, h.m128));
+#  else
+  float4 h(shuffle<1, 0, 3, 2>(a) + a);
+  return _mm_cvtss_f32(shuffle<2, 3, 0, 1>(h) + h);
+#  endif
+#else
+  return a.x + a.y + a.z + a.w;
+#endif
+}
+
+ccl_device_inline float reduce_min(const float4 a)
+{
+#if defined(__KERNEL_SSE__)
+#  if defined(__KERNEL_NEON__)
+  return vminvq_f32(a);
+#  else
+  float4 h = min(shuffle<1, 0, 3, 2>(a), a);
+  return _mm_cvtss_f32(min(shuffle<2, 3, 0, 1>(h), h));
+#  endif
+#else
+  return min(min(a.x, a.y), min(a.z, a.w));
+#endif
+}
+
+ccl_device_inline float reduce_max(const float4 a)
+{
+#if defined(__KERNEL_SSE__)
+#  if defined(__KERNEL_NEON__)
+  return vmaxvq_f32(a);
+#  else
+  float4 h = max(shuffle<1, 0, 3, 2>(a), a);
+  return _mm_cvtss_f32(max(shuffle<2, 3, 0, 1>(h), h));
+#  endif
+#else
+  return max(max(a.x, a.y), max(a.z, a.w));
+#endif
+}
+
+ccl_device_inline bool isequal(const float4 a, const float4 b)
+{
+#if defined(__KERNEL_METAL__)
+  return all(a == b);
+#else
+  return a == b;
+#endif
+}
+
 #ifdef __KERNEL_SSE__
 template
 __forceinline const float4 shuffle(const float4 &b)
@@ -461,34 +516,6 @@ ccl_device_inline float4 mask(const int4 &mask, const float4 &a)
   return select(mask, a, zero_float4());
 }
 
-ccl_device_inline float4 reduce_min(const float4 &a)
-{
-#  if defined(__KERNEL_SSE__)
-#    if defined(__KERNEL_NEON__)
-  return float4(vdupq_n_f32(vminvq_f32(a)));
-#    else
-  float4 h = min(shuffle<1, 0, 3, 2>(a), a);
-  return min(shuffle<2, 3, 0, 1>(h), h);
-#    endif
-#  else
-  return make_float4(min(min(a.x, a.y), min(a.z, a.w)));
-#  endif
-}
-
-ccl_device_inline float4 reduce_max(const float4 &a)
-{
-#  if defined(__KERNEL_SSE__)
-#    if defined(__KERNEL_NEON__)
-  return float4(vdupq_n_f32(vmaxvq_f32(a)));
-#    else
-  float4 h = max(shuffle<1, 0, 3, 2>(a), a);
-  return max(shuffle<2, 3, 0, 1>(h), h);
-#    endif
-#  else
-  return make_float4(max(max(a.x, a.y), max(a.z, a.w)));
-#  endif
-}
-
 ccl_device_inline float4 load_float4(ccl_private const float *v)
 {
 #  ifdef __KERNEL_SSE__
@@ -505,6 +532,14 @@ ccl_device_inline float4 safe_divide(const float4 a, const float b)
   return (b != 0.0f) ? a / b : zero_float4();
 }
 
+ccl_device_inline float4 safe_divide(const float4 a, const float4 b)
+{
+  return make_float4((b.x != 0.0f) ? a.x / b.x : 0.0f,
+                     (b.y != 0.0f) ? a.y / b.y : 0.0f,
+                     (b.z != 0.0f) ? a.z / b.z : 0.0f,
+                     (b.w != 0.0f) ? a.w / b.w : 0.0f);
+}
+
 ccl_device_inline bool isfinite_safe(float4 v)
 {
   return isfinite_safe(v.x) && isfinite_safe(v.y) && isfinite_safe(v.z) && isfinite_safe(v.w);
@@ -523,6 +558,11 @@ ccl_device_inline float4 ensure_finite(float4 v)
   return v;
 }
 
+ccl_device_inline float4 pow(float4 v, float e)
+{
+  return make_float4(powf(v.x, e), powf(v.y, e), powf(v.z, e), powf(v.z, e));
+}
+
 CCL_NAMESPACE_END
 
 #endif /* __UTIL_MATH_FLOAT4_H__ */
-- 
cgit v1.2.3


From ef268c78933079137288e326704431432adf9ad9 Mon Sep 17 00:00:00 2001
From: Patrick Mours 
Date: Thu, 30 Jun 2022 16:44:38 +0200
Subject: Build: Fix build of library dependencies on Linux aarch64

rBb9c37608a9e959a896f5358d4ab3d3d001a70833 moved evaluation of
`versions.cmake` before `options.cmake`, as a result of which
`BLENDER_PLATFORM_ARM` was no longer defined in `versions.cmake`,
causing it to choose the wrong OpenSSL version for aarch64. This
reverts that. Also fixes a compiler crash when building flex with some
glibc versions.

Differential Revision: https://developer.blender.org/D15319
---
 build_files/build_environment/CMakeLists.txt    |  3 ++-
 build_files/build_environment/cmake/flex.cmake  |  2 ++
 build_files/build_environment/cmake/ispc.cmake  |  2 +-
 build_files/build_environment/patches/flex.diff | 15 +++++++++++++++
 4 files changed, 20 insertions(+), 2 deletions(-)
 create mode 100644 build_files/build_environment/patches/flex.diff

diff --git a/build_files/build_environment/CMakeLists.txt b/build_files/build_environment/CMakeLists.txt
index a9ff48b2a9b..e0350901cd0 100644
--- a/build_files/build_environment/CMakeLists.txt
+++ b/build_files/build_environment/CMakeLists.txt
@@ -29,8 +29,9 @@ cmake_minimum_required(VERSION 3.5)
 
 include(ExternalProject)
 include(cmake/check_software.cmake)
-include(cmake/versions.cmake)
 include(cmake/options.cmake)
+# versions.cmake needs to be included after options.cmake due to the BLENDER_PLATFORM_ARM variable being needed.
+include(cmake/versions.cmake)
 include(cmake/boost_build_options.cmake)
 include(cmake/download.cmake)
 include(cmake/macros.cmake)
diff --git a/build_files/build_environment/cmake/flex.cmake b/build_files/build_environment/cmake/flex.cmake
index 2b04c8d5d68..99233adbcdc 100644
--- a/build_files/build_environment/cmake/flex.cmake
+++ b/build_files/build_environment/cmake/flex.cmake
@@ -5,6 +5,8 @@ ExternalProject_Add(external_flex
   URL_HASH ${FLEX_HASH_TYPE}=${FLEX_HASH}
   DOWNLOAD_DIR ${DOWNLOAD_DIR}
   PREFIX ${BUILD_DIR}/flex
+  # This patch fixes build with some versions of glibc (https://github.com/westes/flex/commit/24fd0551333e7eded87b64dd36062da3df2f6380)
+  PATCH_COMMAND ${PATCH_CMD} -d ${BUILD_DIR}/flex/src/external_flex < ${PATCH_DIR}/flex.diff
   CONFIGURE_COMMAND ${CONFIGURE_ENV} && cd ${BUILD_DIR}/flex/src/external_flex/ && ${CONFIGURE_COMMAND} --prefix=${LIBDIR}/flex
   BUILD_COMMAND ${CONFIGURE_ENV} && cd ${BUILD_DIR}/flex/src/external_flex/ && make -j${MAKE_THREADS}
   INSTALL_COMMAND ${CONFIGURE_ENV} && cd ${BUILD_DIR}/flex/src/external_flex/ && make install
diff --git a/build_files/build_environment/cmake/ispc.cmake b/build_files/build_environment/cmake/ispc.cmake
index 86dc1d9efa8..c2dbedca55f 100644
--- a/build_files/build_environment/cmake/ispc.cmake
+++ b/build_files/build_environment/cmake/ispc.cmake
@@ -28,7 +28,7 @@ elseif(UNIX)
   set(ISPC_EXTRA_ARGS_UNIX
     -DCMAKE_C_COMPILER=${LIBDIR}/llvm/bin/clang
     -DCMAKE_CXX_COMPILER=${LIBDIR}/llvm/bin/clang++
-    -DARM_ENABLED=Off
+    -DARM_ENABLED=${BLENDER_PLATFORM_ARM}
     -DFLEX_EXECUTABLE=${LIBDIR}/flex/bin/flex
   )
 endif()
diff --git a/build_files/build_environment/patches/flex.diff b/build_files/build_environment/patches/flex.diff
new file mode 100644
index 00000000000..d3f9e8b0a66
--- /dev/null
+++ b/build_files/build_environment/patches/flex.diff
@@ -0,0 +1,15 @@
+diff --git a/configure.ac b/configure.ac
+index c6f12d644..3c977a4e3 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -25,8 +25,10 @@
+ # autoconf requirements and initialization
+ 
+ AC_INIT([the fast lexical analyser generator],[2.6.4],[flex-help@lists.sourceforge.net],[flex])
++AC_PREREQ([2.60])
+ AC_CONFIG_SRCDIR([src/scan.l])
+ AC_CONFIG_AUX_DIR([build-aux])
++AC_USE_SYSTEM_EXTENSIONS
+ LT_INIT
+ AM_INIT_AUTOMAKE([1.15 -Wno-portability foreign std-options dist-lzip parallel-tests subdir-objects])
+ AC_CONFIG_HEADER([src/config.h])
-- 
cgit v1.2.3


From 34e04ccde21e21246d82e3d5a54860dc85029ea1 Mon Sep 17 00:00:00 2001
From: Brecht Van Lommel 
Date: Thu, 30 Jun 2022 16:52:04 +0200
Subject: Cleanup: fix compiler warnings

"override" should be used either for all methods or none, otherwise Clang gives
warnings. Adding it for all platforms is a bigger change.
---
 intern/ghost/intern/GHOST_System.h | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/intern/ghost/intern/GHOST_System.h b/intern/ghost/intern/GHOST_System.h
index 42f0a773dea..d5558be3444 100644
--- a/intern/ghost/intern/GHOST_System.h
+++ b/intern/ghost/intern/GHOST_System.h
@@ -209,10 +209,8 @@ class GHOST_System : public GHOST_ISystem {
 
   GHOST_TSuccess getCursorPositionClientRelative(const GHOST_IWindow *window,
                                                  int32_t &x,
-                                                 int32_t &y) const override;
-  GHOST_TSuccess setCursorPositionClientRelative(GHOST_IWindow *window,
-                                                 int32_t x,
-                                                 int32_t y) override;
+                                                 int32_t &y) const;
+  GHOST_TSuccess setCursorPositionClientRelative(GHOST_IWindow *window, int32_t x, int32_t y);
 
   /**
    * Inherited from GHOST_ISystem but left pure virtual
-- 
cgit v1.2.3


From 79fe27b976dde09b83e801a0e80f40010617c256 Mon Sep 17 00:00:00 2001
From: Patrick Huang 
Date: Thu, 30 Jun 2022 16:46:25 +0200
Subject: Fix T96429: outliner tooltip inconsistent with behavior when linking
 collections

Previously, it would show "Link inside collection" but move between collections
instead in some cases. Additionally there was no tooltip for the "Link before/
after/between collections" case.

Behavior and tooltip should now be consistent in all cases.

Differential Revision: https://developer.blender.org/D15237
---
 .../editors/space_outliner/outliner_dragdrop.cc    | 69 +++++++++++++---------
 1 file changed, 40 insertions(+), 29 deletions(-)

diff --git a/source/blender/editors/space_outliner/outliner_dragdrop.cc b/source/blender/editors/space_outliner/outliner_dragdrop.cc
index e20958c1b1e..7782da9d762 100644
--- a/source/blender/editors/space_outliner/outliner_dragdrop.cc
+++ b/source/blender/editors/space_outliner/outliner_dragdrop.cc
@@ -1157,9 +1157,6 @@ static bool collection_drop_init(
   /* Get collection to drag out of. */
   ID *parent = drag_id->from_parent;
   Collection *from_collection = collection_parent_from_ID(parent);
-  if (is_link) {
-    from_collection = nullptr;
-  }
 
   /* Currently this should not be allowed, cannot edit items in an override of a Collection. */
   if (from_collection != nullptr && ID_IS_OVERRIDE_LIBRARY(from_collection)) {
@@ -1201,25 +1198,19 @@ static bool collection_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event
       collection_drop_init(C, drag, event->xy, event->modifier & KM_CTRL, &data)) {
     TreeElement *te = data.te;
     TreeStoreElem *tselem = TREESTORE(te);
-    if (!data.from || event->modifier & KM_CTRL) {
-      tselem->flag |= TSE_DRAG_INTO;
-      changed = true;
-    }
-    else {
-      switch (data.insert_type) {
-        case TE_INSERT_BEFORE:
-          tselem->flag |= TSE_DRAG_BEFORE;
-          changed = true;
-          break;
-        case TE_INSERT_AFTER:
-          tselem->flag |= TSE_DRAG_AFTER;
-          changed = true;
-          break;
-        case TE_INSERT_INTO: {
-          tselem->flag |= TSE_DRAG_INTO;
-          changed = true;
-          break;
-        }
+    switch (data.insert_type) {
+      case TE_INSERT_BEFORE:
+        tselem->flag |= TSE_DRAG_BEFORE;
+        changed = true;
+        break;
+      case TE_INSERT_AFTER:
+        tselem->flag |= TSE_DRAG_AFTER;
+        changed = true;
+        break;
+      case TE_INSERT_INTO: {
+        tselem->flag |= TSE_DRAG_INTO;
+        changed = true;
+        break;
       }
     }
     if (changed) {
@@ -1244,28 +1235,48 @@ static char *collection_drop_tooltip(bContext *C,
   CollectionDrop data;
   if (event && ((event->modifier & KM_SHIFT) == 0) &&
       collection_drop_init(C, drag, xy, event->modifier & KM_CTRL, &data)) {
-    TreeElement *te = data.te;
-    if (!data.from || event->modifier & KM_CTRL) {
-      return BLI_strdup(TIP_("Link inside Collection"));
+    const bool is_link = !data.from || (event->modifier & KM_CTRL);
+
+    /* Test if we are moving within same parent collection. */
+    bool same_level = false;
+    LISTBASE_FOREACH (CollectionParent *, parent, &data.to->parents) {
+      if (data.from == parent->collection) {
+        same_level = true;
+      }
     }
+
+    /* Tooltips when not moving directly into another collection i.e. mouse on border of
+     * collections. Later we will decide which tooltip to return. */
+    const bool tooltip_link = (is_link && !same_level);
+    const char *tooltip_before = tooltip_link ? TIP_("Link before collection") :
+                                                TIP_("Move before collection");
+    const char *tooltip_between = tooltip_link ? TIP_("Link between collections") :
+                                                 TIP_("Move between collections");
+    const char *tooltip_after = tooltip_link ? TIP_("Link after collection") :
+                                               TIP_("Move after collection");
+
+    TreeElement *te = data.te;
     switch (data.insert_type) {
       case TE_INSERT_BEFORE:
         if (te->prev && outliner_is_collection_tree_element(te->prev)) {
-          return BLI_strdup(TIP_("Move between collections"));
+          return BLI_strdup(tooltip_between);
         }
         else {
-          return BLI_strdup(TIP_("Move before collection"));
+          return BLI_strdup(tooltip_before);
         }
         break;
       case TE_INSERT_AFTER:
         if (te->next && outliner_is_collection_tree_element(te->next)) {
-          return BLI_strdup(TIP_("Move between collections"));
+          return BLI_strdup(tooltip_between);
         }
         else {
-          return BLI_strdup(TIP_("Move after collection"));
+          return BLI_strdup(tooltip_after);
         }
         break;
       case TE_INSERT_INTO: {
+        if (is_link) {
+          return BLI_strdup(TIP_("Link inside collection"));
+        }
 
         /* Check the type of the drag IDs to avoid the incorrect "Shift to parent"
          * for collections. Checking the type of the first ID works fine here since
-- 
cgit v1.2.3


From 66de653784ab06ccea46413de6b2f086b5a69d30 Mon Sep 17 00:00:00 2001
From: Richard Antalik 
Date: Thu, 30 Jun 2022 18:26:14 +0200
Subject: Fix incorrect strip position if pitch was animated

Commit 302b04a5a3fc introduced new retiming system, that unified sound
pitch animation with strip speed control. Because sound playback is
handled in different way, this did not work as expected and old files
were broken. In addition animation was not copied to new property.

Revert length position and offset handling for sound strips so their
position does not change and remap fcurves to new `speed_factor`
property.
---
 source/blender/blenloader/intern/versioning_300.c | 63 ++++++++++++++++-------
 source/blender/makesrna/intern/rna_sequencer.c    |  1 -
 source/blender/sequencer/intern/strip_time.c      | 25 ++++++---
 3 files changed, 61 insertions(+), 28 deletions(-)

diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c
index 35b1367ca1e..f6cc413d220 100644
--- a/source/blender/blenloader/intern/versioning_300.c
+++ b/source/blender/blenloader/intern/versioning_300.c
@@ -595,6 +595,39 @@ static bNodeTree *add_realize_node_tree(Main *bmain)
   return node_tree;
 }
 
+static void seq_speed_factor_fix_rna_path(Sequence *seq, ListBase *fcurves)
+{
+  char name_esc[(sizeof(seq->name) - 2) * 2];
+  BLI_str_escape(name_esc, seq->name + 2, sizeof(name_esc));
+  char *path = BLI_sprintfN("sequence_editor.sequences_all[\"%s\"].pitch", name_esc);
+  FCurve *fcu = BKE_fcurve_find(&fcurves, path, 0);
+  if (fcu != NULL) {
+    MEM_freeN(fcu->rna_path);
+    fcu->rna_path = BLI_sprintfN("sequence_editor.sequences_all[\"%s\"].speed_factor", name_esc);
+  }
+  MEM_freeN(path);
+}
+
+static bool seq_speed_factor_set(Sequence *seq, void *user_data)
+{
+  const Scene *scene = user_data;
+  if (seq->type == SEQ_TYPE_SOUND_RAM) {
+    /* Move `pitch` animation to `speed_factor` */
+    if (scene->adt && scene->adt->action) {
+      seq_speed_factor_fix_rna_path(seq, &scene->adt->action->curves);
+    }
+    if (scene->adt && !BLI_listbase_is_empty(&scene->adt->drivers)) {
+      seq_speed_factor_fix_rna_path(seq, &scene->adt->drivers);
+    }
+
+    seq->speed_factor = seq->pitch;
+  }
+  else {
+    seq->speed_factor = 1.0f;
+  }
+  return true;
+}
+
 void do_versions_after_linking_300(Main *bmain, ReportList *UNUSED(reports))
 {
   if (MAIN_VERSION_ATLEAST(bmain, 300, 0) && !MAIN_VERSION_ATLEAST(bmain, 300, 1)) {
@@ -819,6 +852,17 @@ void do_versions_after_linking_300(Main *bmain, ReportList *UNUSED(reports))
       }
     }
   }
+
+  if (!MAIN_VERSION_ATLEAST(bmain, 303, 5)) {
+    LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
+      Editing *ed = SEQ_editing_get(scene);
+      if (ed == NULL) {
+        continue;
+      }
+      SEQ_for_each_callback(&ed->seqbase, seq_speed_factor_set, scene);
+    }
+  }
+
   /**
    * Versioning code until next subversion bump goes here.
    *
@@ -1235,17 +1279,6 @@ static bool version_merge_still_offsets(Sequence *seq, void *UNUSED(user_data))
   return true;
 }
 
-static bool seq_speed_factor_set(Sequence *seq, void *UNUSED(user_data))
-{
-  if (seq->type == SEQ_TYPE_SOUND_RAM) {
-    seq->speed_factor = seq->pitch;
-  }
-  else {
-    seq->speed_factor = 1.0f;
-  }
-  return true;
-}
-
 /* Those `version_liboverride_rnacollections_*` functions mimic the old, pre-3.0 code to find
  * anchor and source items in the given list of modifiers, constraints etc., using only the
  * `subitem_local` data of the override property operation.
@@ -3223,14 +3256,6 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
         }
       }
     }
-
-    LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
-      Editing *ed = SEQ_editing_get(scene);
-      if (ed == NULL) {
-        continue;
-      }
-      SEQ_for_each_callback(&ed->seqbase, seq_speed_factor_set, NULL);
-    }
   }
   /**
    * Versioning code until next subversion bump goes here.
diff --git a/source/blender/makesrna/intern/rna_sequencer.c b/source/blender/makesrna/intern/rna_sequencer.c
index f9dfb8544ca..04037a64426 100644
--- a/source/blender/makesrna/intern/rna_sequencer.c
+++ b/source/blender/makesrna/intern/rna_sequencer.c
@@ -2331,7 +2331,6 @@ static void rna_def_speed_factor(StructRNA *srna)
   RNA_def_property_float_default(prop, 1.0f);
   RNA_def_property_range(prop, 0.1f, FLT_MAX);
   RNA_def_property_ui_range(prop, 1.0f, 100.0f, 10.0, 3);
-  RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
   RNA_def_property_ui_text(prop, "Speed Factor", "Multiply playback speed");
   RNA_def_property_float_funcs(
       prop, NULL, "rna_Sequence_speed_factor_set", NULL); /* overlap test */
diff --git a/source/blender/sequencer/intern/strip_time.c b/source/blender/sequencer/intern/strip_time.c
index d4357fe28b6..aa51813c9b8 100644
--- a/source/blender/sequencer/intern/strip_time.c
+++ b/source/blender/sequencer/intern/strip_time.c
@@ -437,15 +437,20 @@ bool SEQ_time_strip_intersects_frame(const Scene *scene,
 
 void SEQ_time_speed_factor_set(const Scene *scene, Sequence *seq, const float speed_factor)
 {
-  const float left_handle_frame = SEQ_time_left_handle_frame_get(scene, seq);
-  const float unity_start_offset = seq->startofs * seq->speed_factor;
-  const float unity_end_offset = seq->endofs * seq->speed_factor;
 
-  /* Left handle is pivot point for content scaling - it must always show same frame. */
-  seq->speed_factor = speed_factor;
-  seq->startofs = unity_start_offset / speed_factor;
-  seq->start = left_handle_frame - seq->startofs;
-  seq->endofs = unity_end_offset / speed_factor;
+  if (seq->type == SEQ_TYPE_SOUND_RAM) {
+    seq->speed_factor = speed_factor;
+  }
+  else {
+    const float left_handle_frame = SEQ_time_left_handle_frame_get(scene, seq);
+    const float unity_start_offset = seq->startofs * seq->speed_factor;
+    const float unity_end_offset = seq->endofs * seq->speed_factor;
+    /* Left handle is pivot point for content scaling - it must always show same frame. */
+    seq->speed_factor = speed_factor;
+    seq->startofs = unity_start_offset / speed_factor;
+    seq->start = left_handle_frame - seq->startofs;
+    seq->endofs = unity_end_offset / speed_factor;
+  }
 
   SEQ_time_update_meta_strip_range(scene, seq_sequence_lookup_meta_by_seq(scene, seq));
   seq_time_update_effects_strip_range(scene, seq_sequence_lookup_effects_by_seq(scene, seq));
@@ -471,6 +476,10 @@ bool SEQ_time_has_still_frames(const Scene *scene, const Sequence *seq)
  * factor */
 int SEQ_time_strip_length_get(const Scene *scene, const Sequence *seq)
 {
+  if (seq->type == SEQ_TYPE_SOUND_RAM) {
+    return seq->len;
+  }
+
   return seq->len / seq_time_playback_rate_factor_get(scene, seq);
 }
 
-- 
cgit v1.2.3


From 65166e145b4d6292abc289b71894c53b25c186ba Mon Sep 17 00:00:00 2001
From: Julian Eisel 
Date: Thu, 30 Jun 2022 18:36:42 +0200
Subject: Cleanup: Remove scene frame macros (`CFRA` et al.)

Removes the following macros for scene/render frame values:
- `CFRA`
- `SUBFRA`
- `SFRA`
- `EFRA`

These macros don't add much, other than saving a few characters when typing.
It's not immediately clear what they refer to, they just hide what they
actually access. Just be explicit and clear about that.
Plus these macros gave read and write access to the variables, so eyesores like
this would be done (eyesore because it looks like assigning to a constant):
```
CFRA = some_frame_nbr;
```

Reviewed By: sergey

Differential Revision: https://developer.blender.org/D15311
---
 source/blender/blenkernel/intern/gpencil_curve.c   |  2 +-
 source/blender/blenkernel/intern/gpencil_geom.cc   |  4 ++--
 source/blender/blenkernel/intern/image.cc          |  2 +-
 source/blender/blenkernel/intern/pointcache.c      | 24 +++++++++----------
 source/blender/blenkernel/intern/sound.c           | 18 +++++++-------
 source/blender/draw/engines/eevee/eevee_engine.c   |  6 ++---
 .../blender/draw/engines/overlay/overlay_extra.c   |  7 +++---
 .../editors/animation/anim_channels_defines.c      | 12 +++++-----
 source/blender/editors/animation/anim_draw.c       | 24 +++++++++----------
 source/blender/editors/animation/anim_markers.c    | 12 +++++-----
 .../blender/editors/animation/anim_motion_paths.c  |  8 +++----
 source/blender/editors/animation/anim_ops.c        | 14 +++++------
 source/blender/editors/animation/keyframes_edit.c  |  4 ++--
 .../blender/editors/animation/keyframes_general.c  |  8 +++----
 source/blender/editors/animation/keyframing.c      | 19 ++++++++-------
 source/blender/editors/armature/pose_lib.c         |  4 ++--
 source/blender/editors/armature/pose_lib_2.c       |  2 +-
 source/blender/editors/armature/pose_slide.c       |  4 ++--
 source/blender/editors/armature/pose_transform.c   |  6 ++---
 source/blender/editors/gpencil/annotate_draw.c     |  8 ++++---
 source/blender/editors/gpencil/annotate_paint.c    |  2 +-
 .../blender/editors/gpencil/editaction_gpencil.c   | 14 +++++------
 source/blender/editors/gpencil/gpencil_add_blank.c |  2 +-
 .../blender/editors/gpencil/gpencil_add_monkey.c   |  4 ++--
 .../blender/editors/gpencil/gpencil_add_stroke.c   |  4 ++--
 .../editors/gpencil/gpencil_bake_animation.cc      |  8 +++----
 source/blender/editors/gpencil/gpencil_convert.c   |  8 +++----
 source/blender/editors/gpencil/gpencil_data.c      |  6 ++---
 source/blender/editors/gpencil/gpencil_edit.c      | 12 +++++-----
 source/blender/editors/gpencil/gpencil_fill.c      |  6 ++---
 .../blender/editors/gpencil/gpencil_interpolate.c  |  8 +++----
 source/blender/editors/gpencil/gpencil_merge.c     |  2 +-
 source/blender/editors/gpencil/gpencil_mesh.cc     |  4 ++--
 source/blender/editors/gpencil/gpencil_paint.c     |  4 ++--
 source/blender/editors/gpencil/gpencil_primitive.c |  4 ++--
 .../blender/editors/gpencil/gpencil_sculpt_paint.c |  4 ++--
 source/blender/editors/gpencil/gpencil_select.c    |  2 +-
 source/blender/editors/io/io_alembic.c             |  8 +++----
 source/blender/editors/io/io_gpencil_export.c      | 12 +++++-----
 source/blender/editors/io/io_gpencil_import.c      |  6 ++---
 source/blender/editors/io/io_obj.c                 |  8 +++----
 source/blender/editors/mask/mask_add.c             |  6 ++---
 source/blender/editors/mask/mask_draw.c            |  2 +-
 source/blender/editors/mask/mask_editaction.c      |  2 +-
 source/blender/editors/mask/mask_ops.c             |  8 +++----
 source/blender/editors/mask/mask_shapekey.c        |  8 +++----
 source/blender/editors/object/object_add.cc        |  2 +-
 source/blender/editors/physics/particle_edit.c     |  6 ++---
 source/blender/editors/physics/physics_fluid.c     |  8 +++----
 source/blender/editors/render/render_opengl.cc     | 15 ++++++------
 source/blender/editors/render/render_preview.cc    |  2 +-
 source/blender/editors/screen/screen_ops.c         | 28 +++++++++++-----------
 source/blender/editors/space_action/action_edit.c  | 10 ++++----
 .../blender/editors/space_action/action_select.c   | 12 +++++-----
 source/blender/editors/space_action/space_action.c |  8 +++----
 source/blender/editors/space_clip/clip_draw.c      | 10 ++++----
 source/blender/editors/space_clip/clip_editor.c    |  6 ++---
 source/blender/editors/space_clip/clip_graph_ops.c |  8 +++----
 source/blender/editors/space_clip/clip_ops.c       | 10 ++++----
 source/blender/editors/space_clip/clip_utils.c     | 12 +++++-----
 source/blender/editors/space_clip/tracking_ops.c   |  9 +++----
 .../editors/space_clip/tracking_ops_track.c        |  4 ++--
 source/blender/editors/space_graph/graph_edit.c    | 18 +++++++-------
 source/blender/editors/space_graph/graph_ops.c     |  8 +++----
 source/blender/editors/space_graph/graph_select.c  |  8 +++----
 source/blender/editors/space_image/image_buttons.c |  2 +-
 source/blender/editors/space_image/image_draw.c    |  3 ++-
 source/blender/editors/space_image/image_ops.c     |  8 +++----
 source/blender/editors/space_nla/nla_edit.c        |  6 ++---
 source/blender/editors/space_nla/nla_select.c      |  6 ++---
 source/blender/editors/space_nla/space_nla.c       |  4 ++--
 source/blender/editors/space_node/drawnode.cc      |  2 +-
 .../editors/space_sequencer/sequencer_add.c        |  4 ++--
 .../editors/space_sequencer/sequencer_edit.c       | 14 +++++------
 .../editors/space_sequencer/sequencer_select.c     | 10 ++++----
 source/blender/editors/space_view3d/view3d_draw.c  |  2 +-
 .../editors/space_view3d/view3d_gizmo_ruler.c      |  4 ++--
 source/blender/editors/space_view3d/view3d_utils.c |  2 +-
 .../editors/transform/transform_convert_action.c   | 12 +++++-----
 .../editors/transform/transform_convert_armature.c |  4 ++--
 .../editors/transform/transform_convert_gpencil.c  |  2 +-
 .../editors/transform/transform_convert_graph.c    | 14 +++++------
 .../editors/transform/transform_convert_mask.c     |  4 ++--
 .../editors/transform/transform_convert_nla.c      | 14 +++++------
 .../editors/transform/transform_convert_object.c   |  4 ++--
 .../transform/transform_convert_sequencer.c        |  4 ++--
 .../transform/transform_convert_sequencer_image.c  | 10 ++++----
 .../editors/transform/transform_mode_timescale.c   |  4 ++--
 .../editors/transform/transform_snap_object.cc     |  2 +-
 .../editors/transform/transform_snap_sequencer.c   |  2 +-
 source/blender/editors/util/ed_util_imbuf.c        |  2 +-
 .../gpencil_modifiers/intern/MOD_gpencil_util.c    |  4 ++--
 .../gpencil_modifiers/intern/MOD_gpencillattice.c  |  4 ++--
 .../gpencil_modifiers/intern/MOD_gpencilmirror.c   |  4 ++--
 .../intern/MOD_gpencilshrinkwrap.c                 |  4 ++--
 .../blender/io/gpencil/intern/gpencil_io_capi.cc   |  4 ++--
 .../io/wavefront_obj/exporter/obj_exporter.cc      |  6 ++---
 source/blender/makesdna/DNA_scene_types.h          |  4 ----
 source/blender/sequencer/intern/strip_relations.c  |  2 +-
 .../gizmo/intern/wm_gizmo_target_props.c           |  2 +-
 100 files changed, 356 insertions(+), 349 deletions(-)

diff --git a/source/blender/blenkernel/intern/gpencil_curve.c b/source/blender/blenkernel/intern/gpencil_curve.c
index 20b8342f090..d68b322e4c5 100644
--- a/source/blender/blenkernel/intern/gpencil_curve.c
+++ b/source/blender/blenkernel/intern/gpencil_curve.c
@@ -501,7 +501,7 @@ void BKE_gpencil_convert_curve(Main *bmain,
   }
 
   /* Check if there is an active frame and add if needed. */
-  bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_ADD_COPY);
+  bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, GP_GETFRAME_ADD_COPY);
 
   /* Read all splines of the curve and create a stroke for each. */
   LISTBASE_FOREACH (Nurb *, nu, &cu->nurb) {
diff --git a/source/blender/blenkernel/intern/gpencil_geom.cc b/source/blender/blenkernel/intern/gpencil_geom.cc
index 0445a1540c7..d0075a7d161 100644
--- a/source/blender/blenkernel/intern/gpencil_geom.cc
+++ b/source/blender/blenkernel/intern/gpencil_geom.cc
@@ -2706,7 +2706,7 @@ bool BKE_gpencil_convert_mesh(Main *bmain,
       gpl_fill = BKE_gpencil_layer_addnew(gpd, element_name, true, false);
     }
     bGPDframe *gpf_fill = BKE_gpencil_layer_frame_get(
-        gpl_fill, CFRA + frame_offset, GP_GETFRAME_ADD_NEW);
+        gpl_fill, scene->r.cfra + frame_offset, GP_GETFRAME_ADD_NEW);
     int i;
     for (i = 0; i < mpoly_len; i++) {
       const MPoly *mp = &mpoly[i];
@@ -2781,7 +2781,7 @@ bool BKE_gpencil_convert_mesh(Main *bmain,
     gpl_stroke = BKE_gpencil_layer_addnew(gpd, element_name, true, false);
   }
   bGPDframe *gpf_stroke = BKE_gpencil_layer_frame_get(
-      gpl_stroke, CFRA + frame_offset, GP_GETFRAME_ADD_NEW);
+      gpl_stroke, scene->r.cfra + frame_offset, GP_GETFRAME_ADD_NEW);
 
   gpencil_generate_edgeloops(ob_eval,
                              gpd,
diff --git a/source/blender/blenkernel/intern/image.cc b/source/blender/blenkernel/intern/image.cc
index afde8893b93..0c4dc3c87f3 100644
--- a/source/blender/blenkernel/intern/image.cc
+++ b/source/blender/blenkernel/intern/image.cc
@@ -1582,7 +1582,7 @@ static void stampdata(
   }
 
   if (use_dynamic && scene->r.stamp & R_STAMP_MARKER) {
-    const char *name = BKE_scene_find_last_marker_name(scene, CFRA);
+    const char *name = BKE_scene_find_last_marker_name(scene, scene->r.cfra);
 
     if (name) {
       STRNCPY(text, name);
diff --git a/source/blender/blenkernel/intern/pointcache.c b/source/blender/blenkernel/intern/pointcache.c
index 21217a7f9bf..5d8025dce8a 100644
--- a/source/blender/blenkernel/intern/pointcache.c
+++ b/source/blender/blenkernel/intern/pointcache.c
@@ -2920,7 +2920,7 @@ int BKE_ptcache_id_reset(Scene *scene, PTCacheID *pid, int mode)
     BKE_ptcache_id_clear(pid, PTCACHE_CLEAR_ALL, 0);
   }
   else if (after) {
-    BKE_ptcache_id_clear(pid, PTCACHE_CLEAR_AFTER, CFRA);
+    BKE_ptcache_id_clear(pid, PTCACHE_CLEAR_AFTER, scene->r.cfra);
   }
 
   return (reset || clear || after);
@@ -3162,8 +3162,8 @@ void BKE_ptcache_bake(PTCacheBaker *baker)
   PTCacheID *pid = &baker->pid;
   PointCache *cache = NULL;
   float frameleno = scene->r.framelen;
-  int cfrao = CFRA;
-  int startframe = MAXFRAME, endframe = baker->anim_init ? scene->r.sfra : CFRA;
+  int cfrao = scene->r.cfra;
+  int startframe = MAXFRAME, endframe = baker->anim_init ? scene->r.sfra : scene->r.cfra;
   int bake = baker->bake;
   int render = baker->render;
 
@@ -3270,7 +3270,7 @@ void BKE_ptcache_bake(PTCacheBaker *baker)
     }
   }
 
-  CFRA = startframe;
+  scene->r.cfra = startframe;
   scene->r.framelen = 1.0;
 
   /* bake */
@@ -3282,21 +3282,21 @@ void BKE_ptcache_bake(PTCacheBaker *baker)
 
   stime = ptime = PIL_check_seconds_timer();
 
-  for (int fr = CFRA; fr <= endframe; fr += baker->quick_step, CFRA = fr) {
+  for (int fr = scene->r.cfra; fr <= endframe; fr += baker->quick_step, scene->r.cfra = fr) {
     BKE_scene_graph_update_for_newframe(depsgraph);
 
     if (baker->update_progress) {
-      float progress = ((float)(CFRA - startframe) / (float)(endframe - startframe));
+      float progress = ((float)(scene->r.cfra - startframe) / (float)(endframe - startframe));
       baker->update_progress(baker->bake_job, progress, &cancel);
     }
 
     if (G.background) {
-      printf("bake: frame %d :: %d\n", CFRA, endframe);
+      printf("bake: frame %d :: %d\n", scene->r.cfra, endframe);
     }
     else {
       ctime = PIL_check_seconds_timer();
 
-      fetd = (ctime - ptime) * (endframe - CFRA) / baker->quick_step;
+      fetd = (ctime - ptime) * (endframe - scene->r.cfra) / baker->quick_step;
 
       if (use_timer || fetd > 60.0) {
         use_timer = true;
@@ -3307,7 +3307,7 @@ void BKE_ptcache_bake(PTCacheBaker *baker)
 
         printf("Baked for %s, current frame: %i/%i (%.3fs), ETC: %s\r",
                run,
-               CFRA - startframe + 1,
+               scene->r.cfra - startframe + 1,
                endframe - startframe + 1,
                ctime - ptime,
                etd);
@@ -3321,7 +3321,7 @@ void BKE_ptcache_bake(PTCacheBaker *baker)
       break;
     }
 
-    CFRA += 1;
+    scene->r.cfra += 1;
   }
 
   if (use_timer) {
@@ -3330,7 +3330,7 @@ void BKE_ptcache_bake(PTCacheBaker *baker)
     printf("\nBake %s %s (%i frames simulated).\n",
            (cancel ? "canceled after" : "finished in"),
            run,
-           CFRA - startframe);
+           scene->r.cfra - startframe);
   }
 
   /* clear baking flag */
@@ -3379,7 +3379,7 @@ void BKE_ptcache_bake(PTCacheBaker *baker)
   }
 
   scene->r.framelen = frameleno;
-  CFRA = cfrao;
+  scene->r.cfra = cfrao;
 
   if (bake) { /* already on cfra unless baking */
     BKE_scene_graph_update_for_newframe(depsgraph);
diff --git a/source/blender/blenkernel/intern/sound.c b/source/blender/blenkernel/intern/sound.c
index 343a829cf76..f459b5a82ac 100644
--- a/source/blender/blenkernel/intern/sound.c
+++ b/source/blender/blenkernel/intern/sound.c
@@ -804,7 +804,7 @@ void BKE_sound_set_scene_volume(Scene *scene, float volume)
   }
   AUD_Sequence_setAnimationData(scene->sound_scene,
                                 AUD_AP_VOLUME,
-                                CFRA,
+                                scene->r.cfra,
                                 &volume,
                                 (scene->audio.flag & AUDIO_VOLUME_ANIMATED) != 0);
 }
@@ -855,7 +855,7 @@ static double get_cur_time(Scene *scene)
   /* We divide by the current framelen to take into account time remapping.
    * Otherwise we will get the wrong starting time which will break A/V sync.
    * See T74111 for further details. */
-  return FRA2TIME((CFRA + SUBFRA) / (double)scene->r.framelen);
+  return FRA2TIME((scene->r.cfra + scene->r.subframe) / (double)scene->r.framelen);
 }
 
 void BKE_sound_play_scene(Scene *scene)
@@ -911,7 +911,7 @@ void BKE_sound_seek_scene(Main *bmain, Scene *scene)
   int animation_playing;
 
   const double one_frame = 1.0 / FPS;
-  const double cur_time = FRA2TIME(CFRA);
+  const double cur_time = FRA2TIME(scene->r.cfra);
 
   AUD_Device_lock(sound_device);
 
@@ -1131,13 +1131,13 @@ static void sound_update_base(Scene *scene, Object *object, void *new_set)
 
         mat4_to_quat(quat, object->obmat);
         AUD_SequenceEntry_setAnimationData(
-            strip->speaker_handle, AUD_AP_LOCATION, CFRA, object->obmat[3], 1);
+            strip->speaker_handle, AUD_AP_LOCATION, scene->r.cfra, object->obmat[3], 1);
         AUD_SequenceEntry_setAnimationData(
-            strip->speaker_handle, AUD_AP_ORIENTATION, CFRA, quat, 1);
+            strip->speaker_handle, AUD_AP_ORIENTATION, scene->r.cfra, quat, 1);
         AUD_SequenceEntry_setAnimationData(
-            strip->speaker_handle, AUD_AP_VOLUME, CFRA, &speaker->volume, 1);
+            strip->speaker_handle, AUD_AP_VOLUME, scene->r.cfra, &speaker->volume, 1);
         AUD_SequenceEntry_setAnimationData(
-            strip->speaker_handle, AUD_AP_PITCH, CFRA, &speaker->pitch, 1);
+            strip->speaker_handle, AUD_AP_PITCH, scene->r.cfra, &speaker->pitch, 1);
         AUD_SequenceEntry_setSound(strip->speaker_handle, speaker->sound->playback_handle);
         AUD_SequenceEntry_setMuted(strip->speaker_handle, mute);
       }
@@ -1172,8 +1172,8 @@ void BKE_sound_update_scene(Depsgraph *depsgraph, Scene *scene)
   if (scene->camera) {
     mat4_to_quat(quat, scene->camera->obmat);
     AUD_Sequence_setAnimationData(
-        scene->sound_scene, AUD_AP_LOCATION, CFRA, scene->camera->obmat[3], 1);
-    AUD_Sequence_setAnimationData(scene->sound_scene, AUD_AP_ORIENTATION, CFRA, quat, 1);
+        scene->sound_scene, AUD_AP_LOCATION, scene->r.cfra, scene->camera->obmat[3], 1);
+    AUD_Sequence_setAnimationData(scene->sound_scene, AUD_AP_ORIENTATION, scene->r.cfra, quat, 1);
   }
 
   AUD_destroySet(scene->speaker_handles);
diff --git a/source/blender/draw/engines/eevee/eevee_engine.c b/source/blender/draw/engines/eevee/eevee_engine.c
index 0cdeeaf314f..ffe0863fb9f 100644
--- a/source/blender/draw/engines/eevee/eevee_engine.c
+++ b/source/blender/draw/engines/eevee/eevee_engine.c
@@ -451,8 +451,8 @@ static void eevee_render_to_image(void *vedata,
   }
   EEVEE_PrivateData *g_data = ved->stl->g_data;
 
-  int initial_frame = CFRA;
-  float initial_subframe = SUBFRA;
+  int initial_frame = scene->r.cfra;
+  float initial_subframe = scene->r.subframe;
   float shuttertime = (do_motion_blur) ? scene->eevee.motion_blur_shutter : 0.0f;
   int time_steps_tot = (do_motion_blur) ? max_ii(1, scene->eevee.motion_blur_steps) : 1;
   g_data->render_timesteps = time_steps_tot;
@@ -588,7 +588,7 @@ static void eevee_render_to_image(void *vedata,
   /* Restore original viewport size. */
   DRW_render_viewport_size_set((int[2]){g_data->size_orig[0], g_data->size_orig[1]});
 
-  if (CFRA != initial_frame || SUBFRA != initial_subframe) {
+  if (scene->r.cfra != initial_frame || scene->r.subframe != initial_subframe) {
     /* Restore original frame number. This is because the render pipeline expects it. */
     RE_engine_frame_set(engine, initial_frame, initial_subframe);
   }
diff --git a/source/blender/draw/engines/overlay/overlay_extra.c b/source/blender/draw/engines/overlay/overlay_extra.c
index f875254a685..9d478310104 100644
--- a/source/blender/draw/engines/overlay/overlay_extra.c
+++ b/source/blender/draw/engines/overlay/overlay_extra.c
@@ -1353,7 +1353,7 @@ static void OVERLAY_volume_extra(OVERLAY_ExtraCallBuffers *cb,
 
   /* Don't show smoke before simulation starts, this could be made an option in the future. */
   const bool draw_velocity = (fds->draw_velocity && fds->fluid &&
-                              CFRA >= fds->point_cache[0]->startframe);
+                              scene->r.cfra >= fds->point_cache[0]->startframe);
 
   /* Show gridlines only for slices with no interpolation. */
   const bool show_gridlines = (fds->show_gridlines && fds->fluid &&
@@ -1546,8 +1546,9 @@ void OVERLAY_extra_cache_populate(OVERLAY_Data *vedata, Object *ob)
                            (md = BKE_modifiers_findby_type(ob, eModifierType_Fluid)) &&
                            (BKE_modifier_is_enabled(scene, md, eModifierMode_Realtime)) &&
                            (((FluidModifierData *)md)->domain != NULL) &&
-                           (CFRA >= (((FluidModifierData *)md)->domain->cache_frame_start)) &&
-                           (CFRA <= (((FluidModifierData *)md)->domain->cache_frame_end));
+                           (scene->r.cfra >=
+                            (((FluidModifierData *)md)->domain->cache_frame_start)) &&
+                           (scene->r.cfra <= (((FluidModifierData *)md)->domain->cache_frame_end));
 
   float *color;
   int theme_id = DRW_object_wire_theme_get(ob, view_layer, &color);
diff --git a/source/blender/editors/animation/anim_channels_defines.c b/source/blender/editors/animation/anim_channels_defines.c
index 7dac1286526..729e8533d50 100644
--- a/source/blender/editors/animation/anim_channels_defines.c
+++ b/source/blender/editors/animation/anim_channels_defines.c
@@ -4747,13 +4747,13 @@ static void achannel_setting_slider_cb(bContext *C, void *id_poin, void *fcu_poi
   RNA_id_pointer_create(id, &id_ptr);
 
   /* Get NLA context for value remapping */
-  const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(depsgraph,
-                                                                                    (float)CFRA);
+  const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(
+      depsgraph, (float)scene->r.cfra);
   NlaKeyframingContext *nla_context = BKE_animsys_get_nla_keyframing_context(
       &nla_cache, &id_ptr, adt, &anim_eval_context);
 
   /* get current frame and apply NLA-mapping to it (if applicable) */
-  cfra = BKE_nla_tweakedit_remap(adt, (float)CFRA, NLATIME_CONVERT_UNMAP);
+  cfra = BKE_nla_tweakedit_remap(adt, (float)scene->r.cfra, NLATIME_CONVERT_UNMAP);
 
   /* Get flags for keyframing. */
   flag = ANIM_get_keyframing_flags(scene, true);
@@ -4803,8 +4803,8 @@ static void achannel_setting_slider_shapekey_cb(bContext *C, void *key_poin, voi
   RNA_id_pointer_create((ID *)key, &id_ptr);
 
   /* Get NLA context for value remapping */
-  const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(depsgraph,
-                                                                                    (float)CFRA);
+  const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(
+      depsgraph, (float)scene->r.cfra);
   NlaKeyframingContext *nla_context = BKE_animsys_get_nla_keyframing_context(
       &nla_cache, &id_ptr, key->adt, &anim_eval_context);
 
@@ -4872,7 +4872,7 @@ static void achannel_setting_slider_nla_curve_cb(bContext *C,
   float cfra;
 
   /* get current frame - *no* NLA mapping should be done */
-  cfra = (float)CFRA;
+  cfra = (float)scene->r.cfra;
 
   /* get flags for keyframing */
   flag = ANIM_get_keyframing_flags(scene, true);
diff --git a/source/blender/editors/animation/anim_draw.c b/source/blender/editors/animation/anim_draw.c
index ee1522c7b76..d9dcbf1d57e 100644
--- a/source/blender/editors/animation/anim_draw.c
+++ b/source/blender/editors/animation/anim_draw.c
@@ -120,9 +120,9 @@ void ANIM_draw_framerange(Scene *scene, View2D *v2d)
   immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
   immUniformThemeColorShadeAlpha(TH_BACK, -25, -100);
 
-  if (SFRA < EFRA) {
-    immRectf(pos, v2d->cur.xmin, v2d->cur.ymin, (float)SFRA, v2d->cur.ymax);
-    immRectf(pos, (float)EFRA, v2d->cur.ymin, v2d->cur.xmax, v2d->cur.ymax);
+  if (scene->r.sfra < scene->r.efra) {
+    immRectf(pos, v2d->cur.xmin, v2d->cur.ymin, (float)scene->r.sfra, v2d->cur.ymax);
+    immRectf(pos, (float)scene->r.efra, v2d->cur.ymin, v2d->cur.xmax, v2d->cur.ymax);
   }
   else {
     immRectf(pos, v2d->cur.xmin, v2d->cur.ymin, v2d->cur.xmax, v2d->cur.ymax);
@@ -135,11 +135,11 @@ void ANIM_draw_framerange(Scene *scene, View2D *v2d)
 
   immBegin(GPU_PRIM_LINES, 4);
 
-  immVertex2f(pos, (float)SFRA, v2d->cur.ymin);
-  immVertex2f(pos, (float)SFRA, v2d->cur.ymax);
+  immVertex2f(pos, (float)scene->r.sfra, v2d->cur.ymin);
+  immVertex2f(pos, (float)scene->r.sfra, v2d->cur.ymax);
 
-  immVertex2f(pos, (float)EFRA, v2d->cur.ymin);
-  immVertex2f(pos, (float)EFRA, v2d->cur.ymax);
+  immVertex2f(pos, (float)scene->r.efra, v2d->cur.ymin);
+  immVertex2f(pos, (float)scene->r.efra, v2d->cur.ymax);
 
   immEnd();
   immUnbindProgram();
@@ -530,7 +530,7 @@ static bool find_prev_next_keyframes(struct bContext *C, int *r_nextfra, int *r_
   bool donenext = false, doneprev = false;
   int nextcount = 0, prevcount = 0;
 
-  cfranext = cfraprev = (float)(CFRA);
+  cfranext = cfraprev = (float)(scene->r.cfra);
 
   /* seed up dummy dopesheet context with flags to perform necessary filtering */
   if ((scene->flag & SCE_KEYS_NO_SELONLY) == 0) {
@@ -559,7 +559,7 @@ static bool find_prev_next_keyframes(struct bContext *C, int *r_nextfra, int *r_
     aknext = ED_keylist_find_next(keylist, cfranext);
 
     if (aknext) {
-      if (CFRA == (int)aknext->cfra) {
+      if (scene->r.cfra == (int)aknext->cfra) {
         /* make this the new starting point for the search and ignore */
         cfranext = aknext->cfra;
       }
@@ -577,7 +577,7 @@ static bool find_prev_next_keyframes(struct bContext *C, int *r_nextfra, int *r_
     akprev = ED_keylist_find_prev(keylist, cfraprev);
 
     if (akprev) {
-      if (CFRA == (int)akprev->cfra) {
+      if (scene->r.cfra == (int)akprev->cfra) {
         /* make this the new starting point for the search */
       }
       else {
@@ -599,14 +599,14 @@ static bool find_prev_next_keyframes(struct bContext *C, int *r_nextfra, int *r_
       *r_prevfra = cfraprev;
     }
     else {
-      *r_prevfra = CFRA - (cfranext - CFRA);
+      *r_prevfra = scene->r.cfra - (cfranext - scene->r.cfra);
     }
 
     if (donenext) {
       *r_nextfra = cfranext;
     }
     else {
-      *r_nextfra = CFRA + (CFRA - cfraprev);
+      *r_nextfra = scene->r.cfra + (scene->r.cfra - cfraprev);
     }
 
     return true;
diff --git a/source/blender/editors/animation/anim_markers.c b/source/blender/editors/animation/anim_markers.c
index ad06b185132..03a2caf4b7d 100644
--- a/source/blender/editors/animation/anim_markers.c
+++ b/source/blender/editors/animation/anim_markers.c
@@ -104,7 +104,7 @@ int ED_markers_post_apply_transform(
     ListBase *markers, Scene *scene, int mode, float value, char side)
 {
   TimeMarker *marker;
-  float cfra = (float)CFRA;
+  float cfra = (float)scene->r.cfra;
   int changed_tot = 0;
 
   /* sanity check - no markers, or locked markers */
@@ -1497,8 +1497,8 @@ static void ED_markers_select_leftright(bAnimContext *ac,
   }
 
   LISTBASE_FOREACH (TimeMarker *, marker, markers) {
-    if ((mode == MARKERS_LRSEL_LEFT && marker->frame <= CFRA) ||
-        (mode == MARKERS_LRSEL_RIGHT && marker->frame >= CFRA)) {
+    if ((mode == MARKERS_LRSEL_LEFT && marker->frame <= scene->r.cfra) ||
+        (mode == MARKERS_LRSEL_RIGHT && marker->frame >= scene->r.cfra)) {
       marker->flag |= SELECT;
     }
   }
@@ -1754,11 +1754,11 @@ static int ed_marker_camera_bind_exec(bContext *C, wmOperator *op)
     return OPERATOR_CANCELLED;
   }
 
-  marker = ED_markers_find_nearest_marker(markers, CFRA);
-  if ((marker == NULL) || (marker->frame != CFRA)) {
+  marker = ED_markers_find_nearest_marker(markers, scene->r.cfra);
+  if ((marker == NULL) || (marker->frame != scene->r.cfra)) {
     marker = MEM_callocN(sizeof(TimeMarker), "Camera TimeMarker");
     marker->flag = SELECT;
-    marker->frame = CFRA;
+    marker->frame = scene->r.cfra;
     BLI_addtail(markers, marker);
 
     /* deselect all others, so that the user can then move it without problems */
diff --git a/source/blender/editors/animation/anim_motion_paths.c b/source/blender/editors/animation/anim_motion_paths.c
index b15bd3db678..23c1d68b4d6 100644
--- a/source/blender/editors/animation/anim_motion_paths.c
+++ b/source/blender/editors/animation/anim_motion_paths.c
@@ -391,7 +391,7 @@ void animviz_calc_motionpaths(Depsgraph *depsgraph,
     return;
   }
 
-  const int cfra = CFRA;
+  const int cfra = scene->r.cfra;
   int sfra = INT_MAX, efra = INT_MIN;
   switch (range) {
     case ANIMVIZ_CALC_RANGE_CURRENT_FRAME:
@@ -485,7 +485,7 @@ void animviz_calc_motionpaths(Depsgraph *depsgraph,
             sfra,
             efra,
             efra - sfra + 1);
-  for (CFRA = sfra; CFRA <= efra; CFRA++) {
+  for (scene->r.cfra = sfra; scene->r.cfra <= efra; scene->r.cfra++) {
     if (range == ANIMVIZ_CALC_RANGE_CURRENT_FRAME) {
       /* For current frame, only update tagged. */
       BKE_scene_graph_update_tagged(depsgraph, bmain);
@@ -496,14 +496,14 @@ void animviz_calc_motionpaths(Depsgraph *depsgraph,
     }
 
     /* perform baking for targets */
-    motionpaths_calc_bake_targets(targets, CFRA);
+    motionpaths_calc_bake_targets(targets, scene->r.cfra);
   }
 
   /* reset original environment */
   /* NOTE: We don't always need to reevaluate the main scene, as the depsgraph
    * may be a temporary one that works on a subset of the data.
    * We always have to restore the current frame though. */
-  CFRA = cfra;
+  scene->r.cfra = cfra;
   if (range != ANIMVIZ_CALC_RANGE_CURRENT_FRAME && restore) {
     motionpaths_calc_update_scene(depsgraph);
   }
diff --git a/source/blender/editors/animation/anim_ops.c b/source/blender/editors/animation/anim_ops.c
index f3972cb45df..c7e755fb6df 100644
--- a/source/blender/editors/animation/anim_ops.c
+++ b/source/blender/editors/animation/anim_ops.c
@@ -142,14 +142,14 @@ static void change_frame_apply(bContext *C, wmOperator *op)
 
   /* set the new frame number */
   if (scene->r.flag & SCER_SHOW_SUBFRAME) {
-    CFRA = (int)frame;
-    SUBFRA = frame - (int)frame;
+    scene->r.cfra = (int)frame;
+    scene->r.subframe = frame - (int)frame;
   }
   else {
-    CFRA = round_fl_to_int(frame);
-    SUBFRA = 0.0f;
+    scene->r.cfra = round_fl_to_int(frame);
+    scene->r.subframe = 0.0f;
   }
-  FRAMENUMBER_MIN_CLAMP(CFRA);
+  FRAMENUMBER_MIN_CLAMP(scene->r.cfra);
 
   /* do updates */
   DEG_id_tag_update(&scene->id, ID_RECALC_FRAME_CHANGE);
@@ -382,7 +382,7 @@ static int anim_set_sfra_exec(bContext *C, wmOperator *op)
     return OPERATOR_CANCELLED;
   }
 
-  frame = CFRA;
+  frame = scene->r.cfra;
 
   /* if Preview Range is defined, set the 'start' frame for that */
   if (PRVRANGEON) {
@@ -437,7 +437,7 @@ static int anim_set_efra_exec(bContext *C, wmOperator *op)
     return OPERATOR_CANCELLED;
   }
 
-  frame = CFRA;
+  frame = scene->r.cfra;
 
   /* if Preview Range is defined, set the 'end' frame for that */
   if (PRVRANGEON) {
diff --git a/source/blender/editors/animation/keyframes_edit.c b/source/blender/editors/animation/keyframes_edit.c
index f8277cf6a85..88207f7d514 100644
--- a/source/blender/editors/animation/keyframes_edit.c
+++ b/source/blender/editors/animation/keyframes_edit.c
@@ -831,7 +831,7 @@ static short snap_bezier_cframe(KeyframeEditData *ked, BezTriple *bezt)
 {
   const Scene *scene = ked->scene;
   if (bezt->f2 & SELECT) {
-    bezt->vec[1][0] = (float)CFRA;
+    bezt->vec[1][0] = (float)scene->r.cfra;
   }
   return 0;
 }
@@ -929,7 +929,7 @@ static short mirror_bezier_cframe(KeyframeEditData *ked, BezTriple *bezt)
   const Scene *scene = ked->scene;
 
   if (bezt->f2 & SELECT) {
-    mirror_bezier_xaxis_ex(bezt, CFRA);
+    mirror_bezier_xaxis_ex(bezt, scene->r.cfra);
   }
 
   return 0;
diff --git a/source/blender/editors/animation/keyframes_general.c b/source/blender/editors/animation/keyframes_general.c
index 30f500f9674..dd88752af14 100644
--- a/source/blender/editors/animation/keyframes_general.c
+++ b/source/blender/editors/animation/keyframes_general.c
@@ -921,7 +921,7 @@ short copy_animedit_keys(bAnimContext *ac, ListBase *anim_data)
   }
 
   /* in case 'relative' paste method is used */
-  animcopy_cfra = CFRA;
+  animcopy_cfra = scene->r.cfra;
 
   /* everything went fine */
   return 0;
@@ -1245,13 +1245,13 @@ eKeyPasteError paste_animedit_keys(bAnimContext *ac,
   /* methods of offset */
   switch (offset_mode) {
     case KEYFRAME_PASTE_OFFSET_CFRA_START:
-      offset = (float)(CFRA - animcopy_firstframe);
+      offset = (float)(scene->r.cfra - animcopy_firstframe);
       break;
     case KEYFRAME_PASTE_OFFSET_CFRA_END:
-      offset = (float)(CFRA - animcopy_lastframe);
+      offset = (float)(scene->r.cfra - animcopy_lastframe);
       break;
     case KEYFRAME_PASTE_OFFSET_CFRA_RELATIVE:
-      offset = (float)(CFRA - animcopy_cfra);
+      offset = (float)(scene->r.cfra - animcopy_cfra);
       break;
     case KEYFRAME_PASTE_OFFSET_NONE:
       offset = 0.0f;
diff --git a/source/blender/editors/animation/keyframing.c b/source/blender/editors/animation/keyframing.c
index aa99a4e50c3..2fa8907de71 100644
--- a/source/blender/editors/animation/keyframing.c
+++ b/source/blender/editors/animation/keyframing.c
@@ -1947,7 +1947,8 @@ static int insert_key_exec(bContext *C, wmOperator *op)
   Object *obedit = CTX_data_edit_object(C);
   bool ob_edit_mode = false;
 
-  float cfra = (float)CFRA; /* XXX for now, don't bother about all the yucky offset crap */
+  float cfra = (float)
+                   scene->r.cfra; /* XXX for now, don't bother about all the yucky offset crap */
   int num_channels;
   const bool confirm = op->flag & OP_IS_INVOKE;
 
@@ -2168,7 +2169,8 @@ static int delete_key_exec(bContext *C, wmOperator *op)
 static int delete_key_using_keying_set(bContext *C, wmOperator *op, KeyingSet *ks)
 {
   Scene *scene = CTX_data_scene(C);
-  float cfra = (float)CFRA; /* XXX for now, don't bother about all the yucky offset crap */
+  float cfra = (float)
+                   scene->r.cfra; /* XXX for now, don't bother about all the yucky offset crap */
   int num_channels;
   const bool confirm = op->flag & OP_IS_INVOKE;
 
@@ -2344,7 +2346,7 @@ void ANIM_OT_keyframe_clear_v3d(wmOperatorType *ot)
 static int delete_key_v3d_without_keying_set(bContext *C, wmOperator *op)
 {
   Scene *scene = CTX_data_scene(C);
-  float cfra = (float)CFRA;
+  float cfra = (float)scene->r.cfra;
 
   int selected_objects_len = 0;
   int selected_objects_success_len = 0;
@@ -2494,7 +2496,7 @@ static int insert_key_button_exec(bContext *C, wmOperator *op)
   char *path;
   uiBut *but;
   const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(
-      CTX_data_depsgraph_pointer(C), (float)CFRA);
+      CTX_data_depsgraph_pointer(C), (float)scene->r.cfra);
   bool changed = false;
   int index;
   const bool all = RNA_boolean_get(op->ptr, "all");
@@ -2663,7 +2665,8 @@ static int delete_key_button_exec(bContext *C, wmOperator *op)
   PropertyRNA *prop = NULL;
   Main *bmain = CTX_data_main(C);
   char *path;
-  float cfra = (float)CFRA; /* XXX for now, don't bother about all the yucky offset crap */
+  float cfra = (float)
+                   scene->r.cfra; /* XXX for now, don't bother about all the yucky offset crap */
   bool changed = false;
   int index;
   const bool all = RNA_boolean_get(op->ptr, "all");
@@ -2835,7 +2838,7 @@ void ANIM_OT_keyframe_clear_button(wmOperatorType *ot)
 
 bool autokeyframe_cfra_can_key(const Scene *scene, ID *id)
 {
-  float cfra = (float)CFRA; /* XXX for now, this will do */
+  float cfra = (float)scene->r.cfra; /* XXX for now, this will do */
 
   /* only filter if auto-key mode requires this */
   if (IS_AUTOKEY_ON(scene) == 0) {
@@ -3065,7 +3068,7 @@ bool ED_autokeyframe_object(bContext *C, Scene *scene, Object *ob, KeyingSet *ks
      * 3) Free the extra info.
      */
     ANIM_relative_keyingset_add_source(&dsources, &ob->id, NULL, NULL);
-    ANIM_apply_keyingset(C, &dsources, NULL, ks, MODIFYKEY_MODE_INSERT, (float)CFRA);
+    ANIM_apply_keyingset(C, &dsources, NULL, ks, MODIFYKEY_MODE_INSERT, (float)scene->r.cfra);
     BLI_freelistN(&dsources);
 
     return true;
@@ -3085,7 +3088,7 @@ bool ED_autokeyframe_pchan(
      * 3) Free the extra info.
      */
     ANIM_relative_keyingset_add_source(&dsources, &ob->id, &RNA_PoseBone, pchan);
-    ANIM_apply_keyingset(C, &dsources, NULL, ks, MODIFYKEY_MODE_INSERT, (float)CFRA);
+    ANIM_apply_keyingset(C, &dsources, NULL, ks, MODIFYKEY_MODE_INSERT, (float)scene->r.cfra);
     BLI_freelistN(&dsources);
 
     return true;
diff --git a/source/blender/editors/armature/pose_lib.c b/source/blender/editors/armature/pose_lib.c
index ea54c3014ca..b8e7c2624fd 100644
--- a/source/blender/editors/armature/pose_lib.c
+++ b/source/blender/editors/armature/pose_lib.c
@@ -448,7 +448,7 @@ static int poselib_add_menu_invoke(bContext *C, wmOperator *op, const wmEvent *U
                ICON_NONE,
                "POSELIB_OT_pose_add",
                "frame",
-               CFRA);
+               scene->r.cfra);
 
     /* Replace existing - sub-menu. */
     uiItemMenuF(
@@ -1113,7 +1113,7 @@ static void poselib_keytag_pose(bContext *C, Scene *scene, tPoseLib_PreviewData
   /* perform actual auto-keying now */
   if (autokey) {
     /* insert keyframes for all relevant bones in one go */
-    ANIM_apply_keyingset(C, &dsources, NULL, ks, MODIFYKEY_MODE_INSERT, (float)CFRA);
+    ANIM_apply_keyingset(C, &dsources, NULL, ks, MODIFYKEY_MODE_INSERT, (float)scene->r.cfra);
     BLI_freelistN(&dsources);
   }
 
diff --git a/source/blender/editors/armature/pose_lib_2.c b/source/blender/editors/armature/pose_lib_2.c
index 9ee289145c4..d866062cec0 100644
--- a/source/blender/editors/armature/pose_lib_2.c
+++ b/source/blender/editors/armature/pose_lib_2.c
@@ -134,7 +134,7 @@ static void poselib_keytag_pose(bContext *C, Scene *scene, PoseBlendData *pbd)
   }
 
   /* Perform actual auto-keying. */
-  ANIM_apply_keyingset(C, &dsources, NULL, ks, MODIFYKEY_MODE_INSERT, (float)CFRA);
+  ANIM_apply_keyingset(C, &dsources, NULL, ks, MODIFYKEY_MODE_INSERT, (float)scene->r.cfra);
   BLI_freelistN(&dsources);
 
   /* send notifiers for this */
diff --git a/source/blender/editors/armature/pose_slide.c b/source/blender/editors/armature/pose_slide.c
index 0efa32ec63a..38c99c2ef60 100644
--- a/source/blender/editors/armature/pose_slide.c
+++ b/source/blender/editors/armature/pose_slide.c
@@ -2061,12 +2061,12 @@ static int pose_propagate_exec(bContext *C, wmOperator *op)
     if (mode == POSE_PROPAGATE_SMART_HOLDS) {
       /* We store in endFrame the end frame of the "long keyframe" (i.e. a held value) starting
        * from the keyframe that occurs after the current frame. */
-      modeData.end_frame = pose_propagate_get_boneHoldEndFrame(pfl, (float)CFRA);
+      modeData.end_frame = pose_propagate_get_boneHoldEndFrame(pfl, (float)scene->r.cfra);
     }
 
     /* Go through propagating pose to keyframes, curve by curve. */
     for (ld = pfl->fcurves.first; ld; ld = ld->next) {
-      pose_propagate_fcurve(op, pfl->ob, (FCurve *)ld->data, (float)CFRA, modeData);
+      pose_propagate_fcurve(op, pfl->ob, (FCurve *)ld->data, (float)scene->r.cfra, modeData);
     }
   }
 
diff --git a/source/blender/editors/armature/pose_transform.c b/source/blender/editors/armature/pose_transform.c
index f0b0218d7e0..cfc6b0b6b6e 100644
--- a/source/blender/editors/armature/pose_transform.c
+++ b/source/blender/editors/armature/pose_transform.c
@@ -1201,7 +1201,7 @@ static int pose_clear_transform_generic_exec(bContext *C,
         KeyingSet *ks = ANIM_get_keyingset_for_autokeying(scene, default_ksName);
 
         /* insert keyframes */
-        ANIM_apply_keyingset(C, &dsources, NULL, ks, MODIFYKEY_MODE_INSERT, (float)CFRA);
+        ANIM_apply_keyingset(C, &dsources, NULL, ks, MODIFYKEY_MODE_INSERT, (float)scene->r.cfra);
 
         /* now recalculate paths */
         if (ob_iter->pose->avs.path_bakeflag & MOTIONPATH_BAKE_HAS_PATHS) {
@@ -1343,8 +1343,8 @@ static int pose_clear_user_transforms_exec(bContext *C, wmOperator *op)
   View3D *v3d = CTX_wm_view3d(C);
   Scene *scene = CTX_data_scene(C);
   Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
-  const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(depsgraph,
-                                                                                    (float)CFRA);
+  const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(
+      depsgraph, (float)scene->r.cfra);
   const bool only_select = RNA_boolean_get(op->ptr, "only_selected");
 
   FOREACH_OBJECT_IN_MODE_BEGIN (view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob) {
diff --git a/source/blender/editors/gpencil/annotate_draw.c b/source/blender/editors/gpencil/annotate_draw.c
index f720f261ad5..ae09aea28d3 100644
--- a/source/blender/editors/gpencil/annotate_draw.c
+++ b/source/blender/editors/gpencil/annotate_draw.c
@@ -841,7 +841,8 @@ void ED_annotation_draw_2dimage(const bContext *C)
   }
 
   /* draw it! */
-  annotation_draw_data_all(scene, gpd, offsx, offsy, sizex, sizey, CFRA, dflag, area->spacetype);
+  annotation_draw_data_all(
+      scene, gpd, offsx, offsy, sizex, sizey, scene->r.cfra, dflag, area->spacetype);
 }
 
 void ED_annotation_draw_view2d(const bContext *C, bool onlyv2d)
@@ -877,7 +878,7 @@ void ED_annotation_draw_view2d(const bContext *C, bool onlyv2d)
   }
 
   annotation_draw_data_all(
-      scene, gpd, 0, 0, region->winx, region->winy, CFRA, dflag, area->spacetype);
+      scene, gpd, 0, 0, region->winx, region->winy, scene->r.cfra, dflag, area->spacetype);
 }
 
 void ED_annotation_draw_view3d(
@@ -928,7 +929,8 @@ void ED_annotation_draw_view3d(
   }
 
   /* draw it! */
-  annotation_draw_data_all(scene, gpd, offsx, offsy, winx, winy, CFRA, dflag, v3d->spacetype);
+  annotation_draw_data_all(
+      scene, gpd, offsx, offsy, winx, winy, scene->r.cfra, dflag, v3d->spacetype);
 }
 
 void ED_annotation_draw_ex(
diff --git a/source/blender/editors/gpencil/annotate_paint.c b/source/blender/editors/gpencil/annotate_paint.c
index 8c393cc4f3f..dd935e4f5f5 100644
--- a/source/blender/editors/gpencil/annotate_paint.c
+++ b/source/blender/editors/gpencil/annotate_paint.c
@@ -1568,7 +1568,7 @@ static void annotation_paint_initstroke(tGPsdata *p,
       add_frame_mode = GP_GETFRAME_ADD_NEW;
     }
 
-    p->gpf = BKE_gpencil_layer_frame_get(p->gpl, CFRA, add_frame_mode);
+    p->gpf = BKE_gpencil_layer_frame_get(p->gpl, scene->r.cfra, add_frame_mode);
 
     if (p->gpf == NULL) {
       p->status = GP_STATUS_ERROR;
diff --git a/source/blender/editors/gpencil/editaction_gpencil.c b/source/blender/editors/gpencil/editaction_gpencil.c
index c7d0d9e0e35..8a98dcb57fc 100644
--- a/source/blender/editors/gpencil/editaction_gpencil.c
+++ b/source/blender/editors/gpencil/editaction_gpencil.c
@@ -371,7 +371,7 @@ bool ED_gpencil_anim_copybuf_copy(bAnimContext *ac)
   }
 
   /* in case 'relative' paste method is used */
-  gpencil_anim_copy_cfra = CFRA;
+  gpencil_anim_copy_cfra = scene->r.cfra;
 
   /* clean up */
   ANIM_animdata_freelist(&anim_data);
@@ -403,13 +403,13 @@ bool ED_gpencil_anim_copybuf_paste(bAnimContext *ac, const short offset_mode)
   /* methods of offset (eKeyPasteOffset) */
   switch (offset_mode) {
     case KEYFRAME_PASTE_OFFSET_CFRA_START:
-      offset = (CFRA - gpencil_anim_copy_firstframe);
+      offset = (scene->r.cfra - gpencil_anim_copy_firstframe);
       break;
     case KEYFRAME_PASTE_OFFSET_CFRA_END:
-      offset = (CFRA - gpencil_anim_copy_lastframe);
+      offset = (scene->r.cfra - gpencil_anim_copy_lastframe);
       break;
     case KEYFRAME_PASTE_OFFSET_CFRA_RELATIVE:
-      offset = (CFRA - gpencil_anim_copy_cfra);
+      offset = (scene->r.cfra - gpencil_anim_copy_cfra);
       break;
     case KEYFRAME_PASTE_OFFSET_NONE:
       offset = 0;
@@ -518,7 +518,7 @@ static bool gpencil_frame_snap_nearestsec(bGPDframe *gpf, Scene *scene)
 static bool gpencil_frame_snap_cframe(bGPDframe *gpf, Scene *scene)
 {
   if (gpf->flag & GP_FRAME_SELECT) {
-    gpf->framenum = (int)CFRA;
+    gpf->framenum = (int)scene->r.cfra;
   }
   return false;
 }
@@ -560,8 +560,8 @@ static bool gpencil_frame_mirror_cframe(bGPDframe *gpf, Scene *scene)
   int diff;
 
   if (gpf->flag & GP_FRAME_SELECT) {
-    diff = CFRA - gpf->framenum;
-    gpf->framenum = CFRA + diff;
+    diff = scene->r.cfra - gpf->framenum;
+    gpf->framenum = scene->r.cfra + diff;
   }
 
   return false;
diff --git a/source/blender/editors/gpencil/gpencil_add_blank.c b/source/blender/editors/gpencil/gpencil_add_blank.c
index 2f22fad53e7..e8e6d328804 100644
--- a/source/blender/editors/gpencil/gpencil_add_blank.c
+++ b/source/blender/editors/gpencil/gpencil_add_blank.c
@@ -76,7 +76,7 @@ void ED_gpencil_create_blank(bContext *C, Object *ob, float UNUSED(mat[4][4]))
   bGPDlayer *layer = BKE_gpencil_layer_addnew(gpd, "GP_Layer", true, false);
 
   /* frames */
-  BKE_gpencil_frame_addnew(layer, CFRA);
+  BKE_gpencil_frame_addnew(layer, scene->r.cfra);
 
   /* update depsgraph */
   DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
diff --git a/source/blender/editors/gpencil/gpencil_add_monkey.c b/source/blender/editors/gpencil/gpencil_add_monkey.c
index 65ee732f6a9..bc046e89d21 100644
--- a/source/blender/editors/gpencil/gpencil_add_monkey.c
+++ b/source/blender/editors/gpencil/gpencil_add_monkey.c
@@ -844,8 +844,8 @@ void ED_gpencil_create_monkey(bContext *C, Object *ob, float mat[4][4])
 
   /* frames */
   /* NOTE: No need to check for existing, as this will take care of it for us */
-  bGPDframe *frameFills = BKE_gpencil_frame_addnew(Fills, CFRA);
-  bGPDframe *frameLines = BKE_gpencil_frame_addnew(Lines, CFRA);
+  bGPDframe *frameFills = BKE_gpencil_frame_addnew(Fills, scene->r.cfra);
+  bGPDframe *frameLines = BKE_gpencil_frame_addnew(Lines, scene->r.cfra);
 
   /* generate strokes */
   gps = BKE_gpencil_stroke_add(frameFills, color_Skin, 270, 75, false);
diff --git a/source/blender/editors/gpencil/gpencil_add_stroke.c b/source/blender/editors/gpencil/gpencil_add_stroke.c
index bc5fe9b5cfb..e24964c4832 100644
--- a/source/blender/editors/gpencil/gpencil_add_stroke.c
+++ b/source/blender/editors/gpencil/gpencil_add_stroke.c
@@ -211,8 +211,8 @@ void ED_gpencil_create_stroke(bContext *C, Object *ob, float mat[4][4])
   bGPDlayer *lines = BKE_gpencil_layer_addnew(gpd, "Lines", true, false);
 
   /* frames */
-  bGPDframe *frame_color = BKE_gpencil_frame_addnew(colors, CFRA);
-  bGPDframe *frame_lines = BKE_gpencil_frame_addnew(lines, CFRA);
+  bGPDframe *frame_color = BKE_gpencil_frame_addnew(colors, scene->r.cfra);
+  bGPDframe *frame_lines = BKE_gpencil_frame_addnew(lines, scene->r.cfra);
   UNUSED_VARS(frame_color);
 
   /* generate stroke */
diff --git a/source/blender/editors/gpencil/gpencil_bake_animation.cc b/source/blender/editors/gpencil/gpencil_bake_animation.cc
index 66f53bea326..e480852a9bb 100644
--- a/source/blender/editors/gpencil/gpencil_bake_animation.cc
+++ b/source/blender/editors/gpencil/gpencil_bake_animation.cc
@@ -265,7 +265,7 @@ static int gpencil_bake_grease_pencil_animation_exec(bContext *C, wmOperator *op
     }
 
     /* Move scene to new frame. */
-    CFRA = i;
+    scene->r.cfra = i;
     BKE_scene_graph_update_for_newframe(depsgraph);
 
     /* Loop all objects in the list. */
@@ -285,7 +285,7 @@ static int gpencil_bake_grease_pencil_animation_exec(bContext *C, wmOperator *op
 
         /* Apply time modifier. */
         int remap_cfra = BKE_gpencil_time_modifier_cfra(
-            depsgraph, scene, elem->ob, gpl_src, CFRA, false);
+            depsgraph, scene, elem->ob, gpl_src, scene->r.cfra, false);
         /* Duplicate frame. */
         bGPDframe *gpf_src = BKE_gpencil_layer_frame_get(
             gpl_src, remap_cfra, GP_GETFRAME_USE_PREV);
@@ -293,7 +293,7 @@ static int gpencil_bake_grease_pencil_animation_exec(bContext *C, wmOperator *op
           continue;
         }
         bGPDframe *gpf_dst = BKE_gpencil_frame_duplicate(gpf_src, true);
-        gpf_dst->framenum = CFRA + frame_offset;
+        gpf_dst->framenum = scene->r.cfra + frame_offset;
         gpf_dst->flag &= ~GP_FRAME_SELECT;
         BLI_addtail(&gpl_dst->frames, gpf_dst);
 
@@ -337,7 +337,7 @@ static int gpencil_bake_grease_pencil_animation_exec(bContext *C, wmOperator *op
     }
   }
   /* Return scene frame state and DB to original state. */
-  CFRA = oldframe;
+  scene->r.cfra = oldframe;
   BKE_scene_graph_update_for_newframe(depsgraph);
 
   /* Free memory. */
diff --git a/source/blender/editors/gpencil/gpencil_convert.c b/source/blender/editors/gpencil/gpencil_convert.c
index 0601d009bf7..25b5466e260 100644
--- a/source/blender/editors/gpencil/gpencil_convert.c
+++ b/source/blender/editors/gpencil/gpencil_convert.c
@@ -1271,7 +1271,7 @@ static void gpencil_layer_to_curve(bContext *C,
   Collection *collection = CTX_data_collection(C);
   Scene *scene = CTX_data_scene(C);
 
-  bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_USE_PREV);
+  bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, GP_GETFRAME_USE_PREV);
   bGPDstroke *prev_gps = NULL;
   Object *ob;
   Curve *cu;
@@ -1414,7 +1414,7 @@ static bool gpencil_convert_check_has_valid_timing(bContext *C, bGPDlayer *gpl,
   int i;
   bool valid = true;
 
-  if (!gpl || !(gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_USE_PREV)) ||
+  if (!gpl || !(gpf = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, GP_GETFRAME_USE_PREV)) ||
       !(gps = gpf->strokes.first)) {
     return false;
   }
@@ -1481,7 +1481,7 @@ static bool gpencil_convert_poll(bContext *C)
    * and if we are not in edit mode!
    */
   return ((area && area->spacetype == SPACE_VIEW3D) && (gpl = BKE_gpencil_layer_active_get(gpd)) &&
-          (gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_USE_PREV)) &&
+          (gpf = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, GP_GETFRAME_USE_PREV)) &&
           (gpf->strokes.first) && (!GPENCIL_ANY_EDIT_MODE(gpd)));
 }
 
@@ -1811,7 +1811,7 @@ static int image_to_gpencil_exec(bContext *C, wmOperator *op)
   /* Add layer and frame. */
   bGPdata *gpd = (bGPdata *)ob->data;
   bGPDlayer *gpl = BKE_gpencil_layer_addnew(gpd, "Image Layer", true, false);
-  bGPDframe *gpf = BKE_gpencil_frame_addnew(gpl, CFRA);
+  bGPDframe *gpf = BKE_gpencil_frame_addnew(gpl, scene->r.cfra);
   done = BKE_gpencil_from_image(sima, gpd, gpf, size, is_mask);
 
   if (done) {
diff --git a/source/blender/editors/gpencil/gpencil_data.c b/source/blender/editors/gpencil/gpencil_data.c
index 6843c42d2d0..b7ac73b9692 100644
--- a/source/blender/editors/gpencil/gpencil_data.c
+++ b/source/blender/editors/gpencil/gpencil_data.c
@@ -226,7 +226,7 @@ static int gpencil_layer_add_exec(bContext *C, wmOperator *op)
       bGPDlayer *gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true, false);
       /* Add a new frame to make it visible in Dopesheet. */
       if (gpl != NULL) {
-        gpl->actframe = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_ADD_NEW);
+        gpl->actframe = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, GP_GETFRAME_ADD_NEW);
       }
     }
   }
@@ -646,12 +646,12 @@ static int gpencil_frame_duplicate_exec(bContext *C, wmOperator *op)
   }
 
   if (mode == 0) {
-    BKE_gpencil_frame_addcopy(gpl_active, CFRA);
+    BKE_gpencil_frame_addcopy(gpl_active, scene->r.cfra);
   }
   else {
     LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
       if ((gpl->flag & GP_LAYER_LOCKED) == 0) {
-        BKE_gpencil_frame_addcopy(gpl, CFRA);
+        BKE_gpencil_frame_addcopy(gpl, scene->r.cfra);
       }
     }
   }
diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c
index 5028baf1589..06f3c169fa9 100644
--- a/source/blender/editors/gpencil/gpencil_edit.c
+++ b/source/blender/editors/gpencil/gpencil_edit.c
@@ -1718,7 +1718,7 @@ static int gpencil_strokes_paste_exec(bContext *C, wmOperator *op)
          *       we are obliged to add a new frame if one
          *       doesn't exist already
          */
-        gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_ADD_NEW);
+        gpf = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, GP_GETFRAME_ADD_NEW);
         if (gpf) {
           /* Create new stroke */
           bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps, true, true);
@@ -1971,7 +1971,7 @@ static int gpencil_blank_frame_add_exec(bContext *C, wmOperator *op)
 {
   bGPdata *gpd = ED_gpencil_data_get_active(C);
   Scene *scene = CTX_data_scene(C);
-  int cfra = CFRA;
+  int cfra = scene->r.cfra;
 
   bGPDlayer *active_gpl = BKE_gpencil_layer_active_get(gpd);
 
@@ -2075,7 +2075,7 @@ static int gpencil_actframe_delete_exec(bContext *C, wmOperator *op)
 
   Scene *scene = CTX_data_scene(C);
 
-  bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_USE_PREV);
+  bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, GP_GETFRAME_USE_PREV);
 
   /* if there's no existing Grease-Pencil data there, add some */
   if (gpd == NULL) {
@@ -2150,7 +2150,7 @@ static int gpencil_actframe_delete_all_exec(bContext *C, wmOperator *op)
 
   CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) {
     /* try to get the "active" frame - but only if it actually occurs on this frame */
-    bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_USE_PREV);
+    bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, GP_GETFRAME_USE_PREV);
 
     if (gpf == NULL) {
       continue;
@@ -3818,7 +3818,7 @@ static int gpencil_strokes_reproject_exec(bContext *C, wmOperator *op)
             /* update frame to get the new location of objects */
             if ((mode == GP_REPROJECT_SURFACE) && (cfra_prv != gpf->framenum)) {
               cfra_prv = gpf->framenum;
-              CFRA = gpf->framenum;
+              scene->r.cfra = gpf->framenum;
               BKE_scene_graph_update_for_newframe(depsgraph);
             }
 
@@ -3846,7 +3846,7 @@ static int gpencil_strokes_reproject_exec(bContext *C, wmOperator *op)
   CTX_DATA_END;
 
   /* return frame state and DB to original state */
-  CFRA = oldframe;
+  scene->r.cfra = oldframe;
   BKE_scene_graph_update_for_newframe(depsgraph);
 
   if (sctx != NULL) {
diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c
index 3f06dbfdbb3..5305c764b3a 100644
--- a/source/blender/editors/gpencil/gpencil_fill.c
+++ b/source/blender/editors/gpencil/gpencil_fill.c
@@ -1748,7 +1748,7 @@ static tGPDfill *gpencil_session_init_fill(bContext *C, wmOperator *op)
   tgpf->v3d = tgpf->area->spacedata.first;
   tgpf->depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
   tgpf->win = CTX_wm_window(C);
-  tgpf->active_cfra = CFRA;
+  tgpf->active_cfra = scene->r.cfra;
   tgpf->reports = op->reports;
 
   /* Setup space conversions. */
@@ -2222,8 +2222,8 @@ static int gpencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event)
             /* Hash of selected frames. */
             GHash *frame_list = BLI_ghash_int_new_ex(__func__, 64);
 
-            /* If not multi-frame and there is no frame in CFRA for the active layer, create
-             * a new frame. */
+            /* If not multi-frame and there is no frame in scene->r.cfra for the active layer,
+             * create a new frame. */
             if (!is_multiedit) {
               tgpf->gpf = BKE_gpencil_layer_frame_get(
                   tgpf->gpl,
diff --git a/source/blender/editors/gpencil/gpencil_interpolate.c b/source/blender/editors/gpencil/gpencil_interpolate.c
index 0039dbae674..e7a4f2fe2dc 100644
--- a/source/blender/editors/gpencil/gpencil_interpolate.c
+++ b/source/blender/editors/gpencil/gpencil_interpolate.c
@@ -483,10 +483,10 @@ static void gpencil_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi)
     tgpil = MEM_callocN(sizeof(tGPDinterpolate_layer), "GPencil Interpolate Layer");
 
     tgpil->gpl = gpl;
-    bGPDframe *gpf = gpencil_get_previous_keyframe(gpl, CFRA);
+    bGPDframe *gpf = gpencil_get_previous_keyframe(gpl, scene->r.cfra);
     tgpil->prevFrame = BKE_gpencil_frame_duplicate(gpf, true);
 
-    gpf = gpencil_get_next_keyframe(gpl, CFRA);
+    gpf = gpencil_get_next_keyframe(gpl, scene->r.cfra);
     tgpil->nextFrame = BKE_gpencil_frame_duplicate(gpf, true);
 
     BLI_addtail(&tgpi->ilayers, tgpil);
@@ -750,7 +750,7 @@ static int gpencil_interpolate_invoke(bContext *C, wmOperator *op, const wmEvent
   tGPDinterpolate *tgpi = NULL;
 
   /* Cannot interpolate if not between 2 frames. */
-  int cfra = CFRA;
+  int cfra = scene->r.cfra;
   bGPDframe *gpf_prv = gpencil_get_previous_keyframe(gpl, cfra);
   bGPDframe *gpf_next = gpencil_get_next_keyframe(gpl, cfra);
   if (ELEM(NULL, gpf_prv, gpf_next)) {
@@ -1221,7 +1221,7 @@ static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op)
   GP_SpaceConversion gsc;
   gpencil_point_conversion_init(C, &gsc);
 
-  int cfra = CFRA;
+  int cfra = scene->r.cfra;
 
   GP_Interpolate_Settings *ipo_settings = &ts->gp_interpolate;
   const int step = RNA_int_get(op->ptr, "step");
diff --git a/source/blender/editors/gpencil/gpencil_merge.c b/source/blender/editors/gpencil/gpencil_merge.c
index 06343dcad43..8ff3f20cef3 100644
--- a/source/blender/editors/gpencil/gpencil_merge.c
+++ b/source/blender/editors/gpencil/gpencil_merge.c
@@ -113,7 +113,7 @@ static bGPDstroke *gpencil_prepare_stroke(bContext *C, wmOperator *op, int totpo
   else {
     add_frame_mode = GP_GETFRAME_ADD_NEW;
   }
-  bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, add_frame_mode);
+  bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, add_frame_mode);
 
   /* stroke */
   bGPDstroke *gps = BKE_gpencil_stroke_new(MAX2(ob->actcol - 1, 0), totpoints, brush->size);
diff --git a/source/blender/editors/gpencil/gpencil_mesh.cc b/source/blender/editors/gpencil/gpencil_mesh.cc
index aee00d4ede3..b27e1c75746 100644
--- a/source/blender/editors/gpencil/gpencil_mesh.cc
+++ b/source/blender/editors/gpencil/gpencil_mesh.cc
@@ -283,7 +283,7 @@ static int gpencil_bake_mesh_animation_exec(bContext *C, wmOperator *op)
     }
 
     /* Move scene to new frame. */
-    CFRA = i;
+    scene->r.cfra = i;
     BKE_scene_graph_update_for_newframe(depsgraph);
 
     /* Loop all objects in the list. */
@@ -325,7 +325,7 @@ static int gpencil_bake_mesh_animation_exec(bContext *C, wmOperator *op)
   }
 
   /* Return scene frame state and DB to original state. */
-  CFRA = oldframe;
+  scene->r.cfra = oldframe;
   BKE_scene_graph_update_for_newframe(depsgraph);
 
   /* Remove unused materials. */
diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c
index 7c7f532f087..70486138556 100644
--- a/source/blender/editors/gpencil/gpencil_paint.c
+++ b/source/blender/editors/gpencil/gpencil_paint.c
@@ -2166,7 +2166,7 @@ static void gpencil_paint_initstroke(tGPsdata *p,
       if (gpl->actframe && gpl->actframe->strokes.first) {
         if (ts->gpencil_flags & GP_TOOL_FLAG_RETAIN_LAST) {
           short frame_mode = IS_AUTOKEY_ON(scene) ? GP_GETFRAME_ADD_COPY : GP_GETFRAME_USE_PREV;
-          gpl->actframe = BKE_gpencil_layer_frame_get(gpl, CFRA, frame_mode);
+          gpl->actframe = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, frame_mode);
         }
         has_layer_to_erase = true;
         break;
@@ -2204,7 +2204,7 @@ static void gpencil_paint_initstroke(tGPsdata *p,
     bool need_tag = p->gpl->actframe == NULL;
     bGPDframe *actframe = p->gpl->actframe;
 
-    p->gpf = BKE_gpencil_layer_frame_get(p->gpl, CFRA, add_frame_mode);
+    p->gpf = BKE_gpencil_layer_frame_get(p->gpl, scene->r.cfra, add_frame_mode);
     /* Only if there wasn't an active frame, need update. */
     if (need_tag) {
       DEG_id_tag_update(&p->gpd->id, ID_RECALC_GEOMETRY);
diff --git a/source/blender/editors/gpencil/gpencil_primitive.c b/source/blender/editors/gpencil/gpencil_primitive.c
index b57b8145749..befff611d58 100644
--- a/source/blender/editors/gpencil/gpencil_primitive.c
+++ b/source/blender/editors/gpencil/gpencil_primitive.c
@@ -292,7 +292,7 @@ static void gpencil_primitive_set_initdata(bContext *C, tGPDprimitive *tgpi)
   Scene *scene = CTX_data_scene(C);
   ToolSettings *ts = scene->toolsettings;
   Brush *brush = tgpi->brush;
-  int cfra = CFRA;
+  int cfra = scene->r.cfra;
 
   bGPDlayer *gpl = CTX_data_active_gpencil_layer(C);
 
@@ -1195,7 +1195,7 @@ static void gpencil_primitive_init(bContext *C, wmOperator *op)
   tgpi->orign_type = RNA_enum_get(op->ptr, "type");
 
   /* set current frame number */
-  tgpi->cframe = CFRA;
+  tgpi->cframe = scene->r.cfra;
 
   /* set GP datablock */
   tgpi->gpd = gpd;
diff --git a/source/blender/editors/gpencil/gpencil_sculpt_paint.c b/source/blender/editors/gpencil/gpencil_sculpt_paint.c
index f25d5937ac4..b68240362c5 100644
--- a/source/blender/editors/gpencil/gpencil_sculpt_paint.c
+++ b/source/blender/editors/gpencil/gpencil_sculpt_paint.c
@@ -1013,7 +1013,7 @@ static void gpencil_brush_clone_add(bContext *C, tGP_BrushEditData *gso)
         gpl = CTX_data_active_gpencil_layer(C);
       }
       bGPDframe *gpf = BKE_gpencil_layer_frame_get(
-          gpl, CFRA, IS_AUTOKEY_ON(scene) ? GP_GETFRAME_ADD_NEW : GP_GETFRAME_USE_PREV);
+          gpl, scene->r.cfra, IS_AUTOKEY_ON(scene) ? GP_GETFRAME_ADD_NEW : GP_GETFRAME_USE_PREV);
       if (gpf == NULL) {
         continue;
       }
@@ -1336,7 +1336,7 @@ static void gpencil_sculpt_brush_init_stroke(bContext *C, tGP_BrushEditData *gso
   bGPdata *gpd = gso->gpd;
 
   Scene *scene = gso->scene;
-  int cfra = CFRA;
+  int cfra = scene->r.cfra;
 
   /* only try to add a new frame if this is the first stroke, or the frame has changed */
   if ((gpd == NULL) || (cfra == gso->cfra)) {
diff --git a/source/blender/editors/gpencil/gpencil_select.c b/source/blender/editors/gpencil/gpencil_select.c
index e903d11a1e0..a19265720e8 100644
--- a/source/blender/editors/gpencil/gpencil_select.c
+++ b/source/blender/editors/gpencil/gpencil_select.c
@@ -759,7 +759,7 @@ static bool gpencil_select_same_layer(bContext *C)
 
   bool changed = false;
   CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) {
-    bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_USE_PREV);
+    bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, GP_GETFRAME_USE_PREV);
     bGPDstroke *gps;
     bool found = false;
 
diff --git a/source/blender/editors/io/io_alembic.c b/source/blender/editors/io/io_alembic.c
index 0e8e0f83597..0068586730f 100644
--- a/source/blender/editors/io/io_alembic.c
+++ b/source/blender/editors/io/io_alembic.c
@@ -144,10 +144,10 @@ static int wm_alembic_export_exec(bContext *C, wmOperator *op)
   /* Take some defaults from the scene, if not specified explicitly. */
   Scene *scene = CTX_data_scene(C);
   if (params.frame_start == INT_MIN) {
-    params.frame_start = SFRA;
+    params.frame_start = scene->r.sfra;
   }
   if (params.frame_end == INT_MIN) {
-    params.frame_end = EFRA;
+    params.frame_end = scene->r.efra;
   }
 
   const bool as_background_job = RNA_boolean_get(op->ptr, "as_background_job");
@@ -248,8 +248,8 @@ static void wm_alembic_export_draw(bContext *C, wmOperator *op)
   Scene *scene = CTX_data_scene(C);
 
   if (scene != NULL && RNA_boolean_get(op->ptr, "init_scene_frame_range")) {
-    RNA_int_set(op->ptr, "start", SFRA);
-    RNA_int_set(op->ptr, "end", EFRA);
+    RNA_int_set(op->ptr, "start", scene->r.sfra);
+    RNA_int_set(op->ptr, "end", scene->r.efra);
 
     RNA_boolean_set(op->ptr, "init_scene_frame_range", false);
   }
diff --git a/source/blender/editors/io/io_gpencil_export.c b/source/blender/editors/io/io_gpencil_export.c
index 6e5ae9f3cba..3f905dd7de0 100644
--- a/source/blender/editors/io/io_gpencil_export.c
+++ b/source/blender/editors/io/io_gpencil_export.c
@@ -153,9 +153,9 @@ static int wm_gpencil_export_svg_exec(bContext *C, wmOperator *op)
                             .v3d = v3d,
                             .ob = ob,
                             .mode = GP_EXPORT_TO_SVG,
-                            .frame_start = CFRA,
-                            .frame_end = CFRA,
-                            .frame_cur = CFRA,
+                            .frame_start = scene->r.cfra,
+                            .frame_end = scene->r.cfra,
+                            .frame_cur = scene->r.cfra,
                             .flag = flag,
                             .scale = 1.0f,
                             .select_mode = select_mode,
@@ -306,9 +306,9 @@ static int wm_gpencil_export_pdf_exec(bContext *C, wmOperator *op)
                             .v3d = v3d,
                             .ob = ob,
                             .mode = GP_EXPORT_TO_PDF,
-                            .frame_start = SFRA,
-                            .frame_end = EFRA,
-                            .frame_cur = CFRA,
+                            .frame_start = scene->r.sfra,
+                            .frame_end = scene->r.efra,
+                            .frame_cur = scene->r.cfra,
                             .flag = flag,
                             .scale = 1.0f,
                             .select_mode = select_mode,
diff --git a/source/blender/editors/io/io_gpencil_import.c b/source/blender/editors/io/io_gpencil_import.c
index 45f5441616f..9ac64407dcf 100644
--- a/source/blender/editors/io/io_gpencil_import.c
+++ b/source/blender/editors/io/io_gpencil_import.c
@@ -90,9 +90,9 @@ static int wm_gpencil_import_svg_exec(bContext *C, wmOperator *op)
       .v3d = v3d,
       .ob = NULL,
       .mode = GP_IMPORT_FROM_SVG,
-      .frame_start = CFRA,
-      .frame_end = CFRA,
-      .frame_cur = CFRA,
+      .frame_start = scene->r.cfra,
+      .frame_end = scene->r.cfra,
+      .frame_cur = scene->r.cfra,
       .flag = flag,
       .scale = scale,
       .select_mode = 0,
diff --git a/source/blender/editors/io/io_obj.c b/source/blender/editors/io/io_obj.c
index 53fa4788d0e..79ec7ebf2a5 100644
--- a/source/blender/editors/io/io_obj.c
+++ b/source/blender/editors/io/io_obj.c
@@ -208,11 +208,11 @@ static bool wm_obj_export_check(bContext *C, wmOperator *op)
     int end = RNA_int_get(op->ptr, "end_frame");
     /* Set the defaults. */
     if (start == INT_MIN) {
-      start = SFRA;
+      start = scene->r.sfra;
       changed = true;
     }
     if (end == INT_MAX) {
-      end = EFRA;
+      end = scene->r.efra;
       changed = true;
     }
     /* Fix user errors. */
@@ -266,7 +266,7 @@ void WM_OT_obj_export(struct wmOperatorType *ot)
                   "Export multiple frames instead of the current frame only");
   RNA_def_int(ot->srna,
               "start_frame",
-              INT_MIN, /* wm_obj_export_check uses this to set SFRA. */
+              INT_MIN, /* wm_obj_export_check uses this to set scene->r.sfra. */
               INT_MIN,
               INT_MAX,
               "Start Frame",
@@ -275,7 +275,7 @@ void WM_OT_obj_export(struct wmOperatorType *ot)
               INT_MAX);
   RNA_def_int(ot->srna,
               "end_frame",
-              INT_MAX, /* wm_obj_export_check uses this to set EFRA. */
+              INT_MAX, /* wm_obj_export_check uses this to set scene->r.efra. */
               INT_MIN,
               INT_MAX,
               "End Frame",
diff --git a/source/blender/editors/mask/mask_add.c b/source/blender/editors/mask/mask_add.c
index 7ac326cb00c..df30870007f 100644
--- a/source/blender/editors/mask/mask_add.c
+++ b/source/blender/editors/mask/mask_add.c
@@ -258,7 +258,7 @@ static bool add_vertex_subdivide(const bContext *C, Mask *mask, const float co[2
                                       &u,
                                       NULL)) {
     Scene *scene = CTX_data_scene(C);
-    const float ctime = CFRA;
+    const float ctime = scene->r.cfra;
 
     MaskSplinePoint *new_point;
     int point_index = point - spline->points;
@@ -295,7 +295,7 @@ static bool add_vertex_extrude(const bContext *C,
                                const float co[2])
 {
   Scene *scene = CTX_data_scene(C);
-  const float ctime = CFRA;
+  const float ctime = scene->r.cfra;
 
   MaskSpline *spline;
   MaskSplinePoint *point;
@@ -394,7 +394,7 @@ static bool add_vertex_extrude(const bContext *C,
 static bool add_vertex_new(const bContext *C, Mask *mask, MaskLayer *mask_layer, const float co[2])
 {
   Scene *scene = CTX_data_scene(C);
-  const float ctime = CFRA;
+  const float ctime = scene->r.cfra;
 
   MaskSpline *spline;
   MaskSplinePoint *new_point = NULL, *ref_point = NULL;
diff --git a/source/blender/editors/mask/mask_draw.c b/source/blender/editors/mask/mask_draw.c
index 4662fe9d1a8..4c01154ba49 100644
--- a/source/blender/editors/mask/mask_draw.c
+++ b/source/blender/editors/mask/mask_draw.c
@@ -802,7 +802,7 @@ void ED_mask_draw_frames(
        mask_layer_shape = mask_layer_shape->next) {
     int frame = mask_layer_shape->frame;
 
-    // draw_keyframe(i, CFRA, sfra, framelen, 1);
+    // draw_keyframe(i, scene->r.cfra, sfra, framelen, 1);
     int height = (frame == cfra) ? 22 : 10;
     int x = (frame - sfra) * framelen;
     immVertex2i(pos, x, region_bottom);
diff --git a/source/blender/editors/mask/mask_editaction.c b/source/blender/editors/mask/mask_editaction.c
index 8a23a53a5d2..9819532224e 100644
--- a/source/blender/editors/mask/mask_editaction.c
+++ b/source/blender/editors/mask/mask_editaction.c
@@ -304,7 +304,7 @@ static bool snap_mask_layer_nearestsec(MaskLayerShape *mask_layer_shape, Scene *
 static bool snap_mask_layer_cframe(MaskLayerShape *mask_layer_shape, Scene *scene)
 {
   if (mask_layer_shape->flag & MASK_SHAPE_SELECT) {
-    mask_layer_shape->frame = (int)CFRA;
+    mask_layer_shape->frame = (int)scene->r.cfra;
   }
   return false;
 }
diff --git a/source/blender/editors/mask/mask_ops.c b/source/blender/editors/mask/mask_ops.c
index 14976d860f6..d34b274c111 100644
--- a/source/blender/editors/mask/mask_ops.c
+++ b/source/blender/editors/mask/mask_ops.c
@@ -856,7 +856,7 @@ static int slide_point_modal(bContext *C, wmOperator *op, const wmEvent *event)
         /* Don't key sliding feather UW's. */
         if ((data->action == SLIDE_ACTION_FEATHER && data->uw) == false) {
           if (IS_AUTOKEY_ON(scene)) {
-            ED_mask_layer_shape_auto_key(data->mask_layer, CFRA);
+            ED_mask_layer_shape_auto_key(data->mask_layer, scene->r.cfra);
           }
         }
 
@@ -1262,7 +1262,7 @@ static int slide_spline_curvature_modal(bContext *C, wmOperator *op, const wmEve
       if (event->type == slide_data->event_invoke_type && event->val == KM_RELEASE) {
         /* Don't key sliding feather UW's. */
         if (IS_AUTOKEY_ON(scene)) {
-          ED_mask_layer_shape_auto_key(slide_data->mask_layer, CFRA);
+          ED_mask_layer_shape_auto_key(slide_data->mask_layer, scene->r.cfra);
         }
 
         WM_event_add_notifier(C, NC_MASK | NA_EDITED, slide_data->mask);
@@ -1525,7 +1525,7 @@ static int mask_switch_direction_exec(bContext *C, wmOperator *UNUSED(op))
 
     if (changed_layer) {
       if (IS_AUTOKEY_ON(scene)) {
-        ED_mask_layer_shape_auto_key(mask_layer, CFRA);
+        ED_mask_layer_shape_auto_key(mask_layer, scene->r.cfra);
       }
     }
   }
@@ -1587,7 +1587,7 @@ static int mask_normals_make_consistent_exec(bContext *C, wmOperator *UNUSED(op)
 
     if (changed_layer) {
       if (IS_AUTOKEY_ON(scene)) {
-        ED_mask_layer_shape_auto_key(mask_layer, CFRA);
+        ED_mask_layer_shape_auto_key(mask_layer, scene->r.cfra);
       }
     }
   }
diff --git a/source/blender/editors/mask/mask_shapekey.c b/source/blender/editors/mask/mask_shapekey.c
index 55145f27012..48944c081a8 100644
--- a/source/blender/editors/mask/mask_shapekey.c
+++ b/source/blender/editors/mask/mask_shapekey.c
@@ -33,7 +33,7 @@
 static int mask_shape_key_insert_exec(bContext *C, wmOperator *UNUSED(op))
 {
   Scene *scene = CTX_data_scene(C);
-  const int frame = CFRA;
+  const int frame = scene->r.cfra;
   Mask *mask = CTX_data_edit_mask(C);
   bool changed = false;
 
@@ -76,7 +76,7 @@ void MASK_OT_shape_key_insert(wmOperatorType *ot)
 static int mask_shape_key_clear_exec(bContext *C, wmOperator *UNUSED(op))
 {
   Scene *scene = CTX_data_scene(C);
-  const int frame = CFRA;
+  const int frame = scene->r.cfra;
   Mask *mask = CTX_data_edit_mask(C);
   bool changed = false;
 
@@ -122,7 +122,7 @@ void MASK_OT_shape_key_clear(wmOperatorType *ot)
 static int mask_shape_key_feather_reset_exec(bContext *C, wmOperator *UNUSED(op))
 {
   Scene *scene = CTX_data_scene(C);
-  const int frame = CFRA;
+  const int frame = scene->r.cfra;
   Mask *mask = CTX_data_edit_mask(C);
   bool changed = false;
 
@@ -214,7 +214,7 @@ void MASK_OT_shape_key_feather_reset(wmOperatorType *ot)
 static int mask_shape_key_rekey_exec(bContext *C, wmOperator *op)
 {
   Scene *scene = CTX_data_scene(C);
-  const int frame = CFRA;
+  const int frame = scene->r.cfra;
   Mask *mask = CTX_data_edit_mask(C);
   bool changed = false;
 
diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc
index 041a1383b28..41aaede8d6f 100644
--- a/source/blender/editors/object/object_add.cc
+++ b/source/blender/editors/object/object_add.cc
@@ -1984,7 +1984,7 @@ static int object_speaker_add_exec(bContext *C, wmOperator *op)
     AnimData *adt = BKE_animdata_ensure_id(&ob->id);
     NlaTrack *nlt = BKE_nlatrack_add(adt, nullptr, is_liboverride);
     NlaStrip *strip = BKE_nla_add_soundstrip(bmain, scene, static_cast(ob->data));
-    strip->start = CFRA;
+    strip->start = scene->r.cfra;
     strip->end += strip->start;
 
     /* hook them up */
diff --git a/source/blender/editors/physics/particle_edit.c b/source/blender/editors/physics/particle_edit.c
index 0e03feba340..64d5ee91215 100644
--- a/source/blender/editors/physics/particle_edit.c
+++ b/source/blender/editors/physics/particle_edit.c
@@ -1512,7 +1512,7 @@ static void PE_update_selection(Depsgraph *depsgraph, Scene *scene, Object *ob,
     }
   }
 
-  psys_cache_edit_paths(depsgraph, scene, ob, edit, CFRA, G.is_rendering);
+  psys_cache_edit_paths(depsgraph, scene, ob, edit, scene->r.cfra, G.is_rendering);
 
   /* disable update flag */
   LOOP_POINTS {
@@ -1647,11 +1647,11 @@ void PE_update_object(Depsgraph *depsgraph, Scene *scene, Object *ob, int usefla
    * and flagging with PEK_HIDE will prevent selection. This might get restored once this is
    * supported in drawing (but doesn't make much sense for hair anyways). */
   if (edit->psys && edit->psys->part->type == PART_EMITTER) {
-    PE_hide_keys_time(scene, edit, CFRA);
+    PE_hide_keys_time(scene, edit, scene->r.cfra);
   }
 
   /* regenerate path caches */
-  psys_cache_edit_paths(depsgraph, scene, ob, edit, CFRA, G.is_rendering);
+  psys_cache_edit_paths(depsgraph, scene, ob, edit, scene->r.cfra, G.is_rendering);
 
   /* disable update flag */
   LOOP_POINTS {
diff --git a/source/blender/editors/physics/physics_fluid.c b/source/blender/editors/physics/physics_fluid.c
index 96195bdcc2e..80de8fae072 100644
--- a/source/blender/editors/physics/physics_fluid.c
+++ b/source/blender/editors/physics/physics_fluid.c
@@ -256,8 +256,8 @@ static void fluid_bake_sequence(FluidJob *job)
   frame = is_first_frame ? fds->cache_frame_start : (*pause_frame);
 
   /* Save orig frame and update scene frame. */
-  orig_frame = CFRA;
-  CFRA = frame;
+  orig_frame = scene->r.cfra;
+  scene->r.cfra = frame;
 
   /* Loop through selected frames. */
   for (; frame <= fds->cache_frame_end; frame++) {
@@ -280,7 +280,7 @@ static void fluid_bake_sequence(FluidJob *job)
       *(job->progress) = progress;
     }
 
-    CFRA = frame;
+    scene->r.cfra = frame;
 
     /* Update animation system. */
     ED_update_for_newframe(job->bmain, job->depsgraph);
@@ -293,7 +293,7 @@ static void fluid_bake_sequence(FluidJob *job)
   }
 
   /* Restore frame position that we were on before bake. */
-  CFRA = orig_frame;
+  scene->r.cfra = orig_frame;
 }
 
 static void fluid_bake_endjob(void *customdata)
diff --git a/source/blender/editors/render/render_opengl.cc b/source/blender/editors/render/render_opengl.cc
index d907a52543c..7bd9812a178 100644
--- a/source/blender/editors/render/render_opengl.cc
+++ b/source/blender/editors/render/render_opengl.cc
@@ -494,7 +494,8 @@ static void screen_opengl_render_apply(const bContext *C, OGLRender *oglrender)
     for (view_id = 0; view_id < oglrender->views_len; view_id++) {
       context.view_id = view_id;
       context.gpu_offscreen = oglrender->ofs;
-      oglrender->seq_data.ibufs_arr[view_id] = SEQ_render_give_ibuf(&context, CFRA, chanshown);
+      oglrender->seq_data.ibufs_arr[view_id] = SEQ_render_give_ibuf(
+          &context, scene->r.cfra, chanshown);
     }
   }
 
@@ -1136,12 +1137,12 @@ static bool screen_opengl_render_anim_step(bContext *C, wmOperator *op)
   RenderResult *rr;
 
   /* go to next frame */
-  if (CFRA < oglrender->nfra) {
-    CFRA++;
+  if (scene->r.cfra < oglrender->nfra) {
+    scene->r.cfra++;
   }
-  while (CFRA < oglrender->nfra) {
+  while (scene->r.cfra < oglrender->nfra) {
     BKE_scene_graph_update_for_newframe(depsgraph);
-    CFRA++;
+    scene->r.cfra++;
   }
 
   is_movie = BKE_imtype_is_movie(scene->r.im_format.imtype);
@@ -1184,7 +1185,7 @@ static bool screen_opengl_render_anim_step(bContext *C, wmOperator *op)
   }
 
   if (oglrender->render_frames == nullptr ||
-      BLI_BITMAP_TEST_BOOL(oglrender->render_frames, CFRA - PSFRA)) {
+      BLI_BITMAP_TEST_BOOL(oglrender->render_frames, scene->r.cfra - PSFRA)) {
     /* render into offscreen buffer */
     screen_opengl_render_apply(C, oglrender);
   }
@@ -1204,7 +1205,7 @@ finally: /* Step the frame and bail early if needed */
   oglrender->nfra += scene->r.frame_step;
 
   /* stop at the end or on error */
-  if (CFRA >= PEFRA || !ok) {
+  if (scene->r.cfra >= PEFRA || !ok) {
     screen_opengl_render_end(C, static_cast(op->customdata));
     return false;
   }
diff --git a/source/blender/editors/render/render_preview.cc b/source/blender/editors/render/render_preview.cc
index addcedc4481..97bbcaa102f 100644
--- a/source/blender/editors/render/render_preview.cc
+++ b/source/blender/editors/render/render_preview.cc
@@ -805,7 +805,7 @@ static Scene *object_preview_scene_create(const struct ObjectPreviewData *previe
   Scene *scene = BKE_scene_add(preview_data->pr_main, "Object preview scene");
   /* Preview need to be in the current frame to get a thumbnail similar of what
    * viewport displays. */
-  CFRA = preview_data->cfra;
+  scene->r.cfra = preview_data->cfra;
 
   ViewLayer *view_layer = static_cast(scene->view_layers.first);
   Depsgraph *depsgraph = DEG_graph_new(
diff --git a/source/blender/editors/screen/screen_ops.c b/source/blender/editors/screen/screen_ops.c
index d35bd5ab2fe..c616ca2b5eb 100644
--- a/source/blender/editors/screen/screen_ops.c
+++ b/source/blender/editors/screen/screen_ops.c
@@ -2951,9 +2951,9 @@ static int frame_offset_exec(bContext *C, wmOperator *op)
 
   int delta = RNA_int_get(op->ptr, "delta");
 
-  CFRA += delta;
-  FRAMENUMBER_MIN_CLAMP(CFRA);
-  SUBFRA = 0.0f;
+  scene->r.cfra += delta;
+  FRAMENUMBER_MIN_CLAMP(scene->r.cfra);
+  scene->r.subframe = 0.0f;
 
   areas_do_frame_follow(C, false);
 
@@ -2992,7 +2992,7 @@ static int frame_jump_exec(bContext *C, wmOperator *op)
   Scene *scene = CTX_data_scene(C);
   wmTimer *animtimer = CTX_wm_screen(C)->animtimer;
 
-  /* Don't change CFRA directly if animtimer is running as this can cause
+  /* Don't change scene->r.cfra directly if animtimer is running as this can cause
    * first/last frame not to be actually shown (bad since for example physics
    * simulations aren't reset properly).
    */
@@ -3010,10 +3010,10 @@ static int frame_jump_exec(bContext *C, wmOperator *op)
   }
   else {
     if (RNA_boolean_get(op->ptr, "end")) {
-      CFRA = PEFRA;
+      scene->r.cfra = PEFRA;
     }
     else {
-      CFRA = PSFRA;
+      scene->r.cfra = PSFRA;
     }
 
     areas_do_frame_follow(C, true);
@@ -3062,7 +3062,7 @@ static int keyframe_jump_exec(bContext *C, wmOperator *op)
     return OPERATOR_CANCELLED;
   }
 
-  float cfra = (float)(CFRA);
+  float cfra = (float)(scene->r.cfra);
 
   /* Initialize binary-tree-list for getting keyframes. */
   struct AnimKeylist *keylist = ED_keylist_create();
@@ -3104,9 +3104,9 @@ static int keyframe_jump_exec(bContext *C, wmOperator *op)
   }
 
   while ((ak != NULL) && (done == false)) {
-    if (CFRA != (int)ak->cfra) {
+    if (scene->r.cfra != (int)ak->cfra) {
       /* this changes the frame, so set the frame and we're done */
-      CFRA = (int)ak->cfra;
+      scene->r.cfra = (int)ak->cfra;
       done = true;
     }
     else {
@@ -3165,20 +3165,20 @@ static void SCREEN_OT_keyframe_jump(wmOperatorType *ot)
 static int marker_jump_exec(bContext *C, wmOperator *op)
 {
   Scene *scene = CTX_data_scene(C);
-  int closest = CFRA;
+  int closest = scene->r.cfra;
   const bool next = RNA_boolean_get(op->ptr, "next");
   bool found = false;
 
   /* find matching marker in the right direction */
   LISTBASE_FOREACH (TimeMarker *, marker, &scene->markers) {
     if (next) {
-      if ((marker->frame > CFRA) && (!found || closest > marker->frame)) {
+      if ((marker->frame > scene->r.cfra) && (!found || closest > marker->frame)) {
         closest = marker->frame;
         found = true;
       }
     }
     else {
-      if ((marker->frame < CFRA) && (!found || closest < marker->frame)) {
+      if ((marker->frame < scene->r.cfra) && (!found || closest < marker->frame)) {
         closest = marker->frame;
         found = true;
       }
@@ -3192,7 +3192,7 @@ static int marker_jump_exec(bContext *C, wmOperator *op)
     return OPERATOR_CANCELLED;
   }
 
-  CFRA = closest;
+  scene->r.cfra = closest;
 
   areas_do_frame_follow(C, true);
 
@@ -4714,7 +4714,7 @@ static int screen_animation_step_invoke(bContext *C, wmOperator *UNUSED(op), con
   if (sad->flag & ANIMPLAY_FLAG_JUMPED) {
     DEG_id_tag_update(&scene->id, ID_RECALC_FRAME_CHANGE);
 #ifdef PROFILE_AUDIO_SYNCH
-    old_frame = CFRA;
+    old_frame = scene->r.cfra;
 #endif
   }
 
diff --git a/source/blender/editors/space_action/action_edit.c b/source/blender/editors/space_action/action_edit.c
index 2ac10736eea..e97b666810c 100644
--- a/source/blender/editors/space_action/action_edit.c
+++ b/source/blender/editors/space_action/action_edit.c
@@ -730,7 +730,7 @@ static void insert_gpencil_key(bAnimContext *ac,
   Scene *scene = ac->scene;
   bGPdata *gpd = (bGPdata *)ale->id;
   bGPDlayer *gpl = (bGPDlayer *)ale->data;
-  BKE_gpencil_layer_frame_get(gpl, CFRA, add_frame_mode);
+  BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, add_frame_mode);
   /* Check if the gpd changes to tag only once. */
   if (gpd != *gpd_old) {
     BKE_gpencil_tag(gpd);
@@ -827,8 +827,8 @@ static void insert_action_keys(bAnimContext *ac, short mode)
   }
 
   /* insert keyframes */
-  const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(ac->depsgraph,
-                                                                                    (float)CFRA);
+  const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(
+      ac->depsgraph, (float)scene->r.cfra);
   for (ale = anim_data.first; ale; ale = ale->next) {
     switch (ale->type) {
       case ANIMTYPE_GPLAYER:
@@ -1696,8 +1696,8 @@ static int actkeys_framejump_exec(bContext *C, wmOperator *UNUSED(op))
   /* set the new current frame value, based on the average time */
   if (ked.i1) {
     Scene *scene = ac.scene;
-    CFRA = round_fl_to_int(ked.f1 / ked.i1);
-    SUBFRA = 0.0f;
+    scene->r.cfra = round_fl_to_int(ked.f1 / ked.i1);
+    scene->r.subframe = 0.0f;
   }
 
   /* set notifier that things have changed */
diff --git a/source/blender/editors/space_action/action_select.c b/source/blender/editors/space_action/action_select.c
index f5dc2104d66..d1a8592ae9d 100644
--- a/source/blender/editors/space_action/action_select.c
+++ b/source/blender/editors/space_action/action_select.c
@@ -1026,7 +1026,7 @@ static void columnselect_action_keys(bAnimContext *ac, short mode)
       ce = MEM_callocN(sizeof(CfraElem), "cfraElem");
       BLI_addtail(&ked.list, ce);
 
-      ce->cfra = (float)CFRA;
+      ce->cfra = (float)scene->r.cfra;
       break;
 
     case ACTKEYS_COLUMNSEL_MARKERS_COLUMN: /* list of selected markers */
@@ -1352,10 +1352,10 @@ static void actkeys_select_leftright(bAnimContext *ac, short leftright, short se
 
   if (leftright == ACTKEYS_LRSEL_LEFT) {
     ked.f1 = MINAFRAMEF;
-    ked.f2 = (float)(CFRA + 0.1f);
+    ked.f2 = (float)(scene->r.cfra + 0.1f);
   }
   else {
-    ked.f1 = (float)(CFRA - 0.1f);
+    ked.f1 = (float)(scene->r.cfra - 0.1f);
     ked.f2 = MAXFRAMEF;
   }
 
@@ -1402,8 +1402,8 @@ static void actkeys_select_leftright(bAnimContext *ac, short leftright, short se
       TimeMarker *marker;
 
       for (marker = markers->first; marker; marker = marker->next) {
-        if (((leftright == ACTKEYS_LRSEL_LEFT) && (marker->frame < CFRA)) ||
-            ((leftright == ACTKEYS_LRSEL_RIGHT) && (marker->frame >= CFRA))) {
+        if (((leftright == ACTKEYS_LRSEL_LEFT) && (marker->frame < scene->r.cfra)) ||
+            ((leftright == ACTKEYS_LRSEL_RIGHT) && (marker->frame >= scene->r.cfra))) {
           marker->flag |= SELECT;
         }
         else {
@@ -1473,7 +1473,7 @@ static int actkeys_select_leftright_invoke(bContext *C, wmOperator *op, const wm
 
     /* determine which side of the current frame mouse is on */
     x = UI_view2d_region_to_view_x(v2d, event->mval[0]);
-    if (x < CFRA) {
+    if (x < scene->r.cfra) {
       RNA_enum_set(op->ptr, "mode", ACTKEYS_LRSEL_LEFT);
     }
     else {
diff --git a/source/blender/editors/space_action/space_action.c b/source/blender/editors/space_action/space_action.c
index 3e3905e5263..3e507f73d1a 100644
--- a/source/blender/editors/space_action/space_action.c
+++ b/source/blender/editors/space_action/space_action.c
@@ -98,9 +98,9 @@ static SpaceLink *action_create(const ScrArea *area, const Scene *scene)
   BLI_addtail(&saction->regionbase, region);
   region->regiontype = RGN_TYPE_WINDOW;
 
-  region->v2d.tot.xmin = (float)(SFRA - 10);
+  region->v2d.tot.xmin = (float)(scene->r.sfra - 10);
   region->v2d.tot.ymin = (float)(-area->winy) / 3.0f;
-  region->v2d.tot.xmax = (float)(EFRA + 10);
+  region->v2d.tot.xmax = (float)(scene->r.efra + 10);
   region->v2d.tot.ymax = 0.0f;
 
   region->v2d.cur = region->v2d.tot;
@@ -561,8 +561,8 @@ static void action_listener(const wmSpaceTypeListenerParams *params)
           LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
             if (region->regiontype == RGN_TYPE_WINDOW) {
               Scene *scene = wmn->reference;
-              region->v2d.tot.xmin = (float)(SFRA - 4);
-              region->v2d.tot.xmax = (float)(EFRA + 4);
+              region->v2d.tot.xmin = (float)(scene->r.sfra - 4);
+              region->v2d.tot.xmax = (float)(scene->r.efra + 4);
               break;
             }
           }
diff --git a/source/blender/editors/space_clip/clip_draw.c b/source/blender/editors/space_clip/clip_draw.c
index 171fb580fb7..78174160eb8 100644
--- a/source/blender/editors/space_clip/clip_draw.c
+++ b/source/blender/editors/space_clip/clip_draw.c
@@ -131,7 +131,7 @@ static void draw_movieclip_cache(SpaceClip *sc, ARegion *region, MovieClip *clip
 {
   float x;
   int *points, totseg, i, a;
-  float sfra = SFRA, efra = EFRA, framelen = region->winx / (efra - sfra + 1);
+  float sfra = scene->r.sfra, efra = scene->r.efra, framelen = region->winx / (efra - sfra + 1);
   MovieTracking *tracking = &clip->tracking;
   MovieTrackingObject *act_object = BKE_tracking_object_get_active(tracking);
   MovieTrackingTrack *act_track = BKE_tracking_track_get_active(&clip->tracking);
@@ -245,14 +245,16 @@ static void draw_movieclip_cache(SpaceClip *sc, ARegion *region, MovieClip *clip
 
   /* solver keyframes */
   immUniformColor4ub(175, 255, 0, 255);
-  draw_keyframe(act_object->keyframe1 + clip->start_frame - 1, CFRA, sfra, framelen, 2, pos);
-  draw_keyframe(act_object->keyframe2 + clip->start_frame - 1, CFRA, sfra, framelen, 2, pos);
+  draw_keyframe(
+      act_object->keyframe1 + clip->start_frame - 1, scene->r.cfra, sfra, framelen, 2, pos);
+  draw_keyframe(
+      act_object->keyframe2 + clip->start_frame - 1, scene->r.cfra, sfra, framelen, 2, pos);
 
   immUnbindProgram();
 
   /* movie clip animation */
   if ((sc->mode == SC_MODE_MASKEDIT) && sc->mask_info.mask) {
-    ED_mask_draw_frames(sc->mask_info.mask, region, CFRA, sfra, efra);
+    ED_mask_draw_frames(sc->mask_info.mask, region, scene->r.cfra, sfra, efra);
   }
 }
 
diff --git a/source/blender/editors/space_clip/clip_editor.c b/source/blender/editors/space_clip/clip_editor.c
index cf7c3b51ae3..9a690f36aab 100644
--- a/source/blender/editors/space_clip/clip_editor.c
+++ b/source/blender/editors/space_clip/clip_editor.c
@@ -1046,7 +1046,7 @@ static int prefetch_get_start_frame(const bContext *C)
 {
   Scene *scene = CTX_data_scene(C);
 
-  return SFRA;
+  return scene->r.sfra;
 }
 
 static int prefetch_get_final_frame(const bContext *C)
@@ -1057,10 +1057,10 @@ static int prefetch_get_final_frame(const bContext *C)
   int end_frame;
 
   /* check whether all the frames from prefetch range are cached */
-  end_frame = EFRA;
+  end_frame = scene->r.efra;
 
   if (clip->len) {
-    end_frame = min_ii(end_frame, SFRA + clip->len - 1);
+    end_frame = min_ii(end_frame, scene->r.sfra + clip->len - 1);
   }
 
   return end_frame;
diff --git a/source/blender/editors/space_clip/clip_graph_ops.c b/source/blender/editors/space_clip/clip_graph_ops.c
index d07cf09ffa6..67565a88bab 100644
--- a/source/blender/editors/space_clip/clip_graph_ops.c
+++ b/source/blender/editors/space_clip/clip_graph_ops.c
@@ -629,8 +629,8 @@ static int view_all_exec(bContext *C, wmOperator *UNUSED(op))
                                      NULL);
 
   /* set extents of view to start/end frames */
-  v2d->cur.xmin = (float)SFRA;
-  v2d->cur.xmax = (float)EFRA;
+  v2d->cur.xmin = (float)scene->r.sfra;
+  v2d->cur.xmax = (float)scene->r.efra;
 
   if (userdata.min < userdata.max) {
     v2d->cur.ymin = userdata.min;
@@ -675,8 +675,8 @@ void ED_clip_graph_center_current_frame(Scene *scene, ARegion *region)
   float extra = BLI_rctf_size_x(&v2d->cur) / 2.0f;
 
   /* set extents of view to start/end frames */
-  v2d->cur.xmin = (float)CFRA - extra;
-  v2d->cur.xmax = (float)CFRA + extra;
+  v2d->cur.xmin = (float)scene->r.cfra - extra;
+  v2d->cur.xmax = (float)scene->r.cfra + extra;
 }
 
 static int center_current_frame_exec(bContext *C, wmOperator *UNUSED(op))
diff --git a/source/blender/editors/space_clip/clip_ops.c b/source/blender/editors/space_clip/clip_ops.c
index f5bf850791a..f276c2acd1a 100644
--- a/source/blender/editors/space_clip/clip_ops.c
+++ b/source/blender/editors/space_clip/clip_ops.c
@@ -1061,9 +1061,9 @@ static void change_frame_apply(bContext *C, wmOperator *op)
   Scene *scene = CTX_data_scene(C);
 
   /* set the new frame number */
-  CFRA = RNA_int_get(op->ptr, "frame");
-  FRAMENUMBER_MIN_CLAMP(CFRA);
-  SUBFRA = 0.0f;
+  scene->r.cfra = RNA_int_get(op->ptr, "frame");
+  FRAMENUMBER_MIN_CLAMP(scene->r.cfra);
+  scene->r.subframe = 0.0f;
 
   /* do updates */
   DEG_id_tag_update(&scene->id, ID_RECALC_FRAME_CHANGE);
@@ -1084,7 +1084,7 @@ static int frame_from_event(bContext *C, const wmEvent *event)
   int framenr = 0;
 
   if (region->regiontype == RGN_TYPE_WINDOW) {
-    float sfra = SFRA, efra = EFRA, framelen = region->winx / (efra - sfra + 1);
+    float sfra = scene->r.sfra, efra = scene->r.efra, framelen = region->winx / (efra - sfra + 1);
 
     framenr = sfra + event->mval[0] / framelen;
   }
@@ -1399,7 +1399,7 @@ static void do_sequence_proxy(void *pjv,
   ProxyJob *pj = pjv;
   MovieClip *clip = pj->clip;
   Scene *scene = pj->scene;
-  int sfra = SFRA, efra = EFRA;
+  int sfra = scene->r.sfra, efra = scene->r.efra;
   ProxyThread *handles;
   int tot_thread = BLI_task_scheduler_num_threads();
   int width, height;
diff --git a/source/blender/editors/space_clip/clip_utils.c b/source/blender/editors/space_clip/clip_utils.c
index 01f2b24c8a4..221b87a8b5a 100644
--- a/source/blender/editors/space_clip/clip_utils.c
+++ b/source/blender/editors/space_clip/clip_utils.c
@@ -617,8 +617,8 @@ void clip_draw_sfra_efra(View2D *v2d, Scene *scene)
   immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
 
   immUniformColor4f(0.0f, 0.0f, 0.0f, 0.4f);
-  immRectf(pos, v2d->cur.xmin, v2d->cur.ymin, (float)SFRA, v2d->cur.ymax);
-  immRectf(pos, (float)EFRA, v2d->cur.ymin, v2d->cur.xmax, v2d->cur.ymax);
+  immRectf(pos, v2d->cur.xmin, v2d->cur.ymin, (float)scene->r.sfra, v2d->cur.ymax);
+  immRectf(pos, (float)scene->r.efra, v2d->cur.ymin, v2d->cur.xmax, v2d->cur.ymax);
 
   GPU_blend(GPU_BLEND_NONE);
 
@@ -628,10 +628,10 @@ void clip_draw_sfra_efra(View2D *v2d, Scene *scene)
   GPU_line_width(1.0f);
 
   immBegin(GPU_PRIM_LINES, 4);
-  immVertex2f(pos, (float)SFRA, v2d->cur.ymin);
-  immVertex2f(pos, (float)SFRA, v2d->cur.ymax);
-  immVertex2f(pos, (float)EFRA, v2d->cur.ymin);
-  immVertex2f(pos, (float)EFRA, v2d->cur.ymax);
+  immVertex2f(pos, (float)scene->r.sfra, v2d->cur.ymin);
+  immVertex2f(pos, (float)scene->r.sfra, v2d->cur.ymax);
+  immVertex2f(pos, (float)scene->r.efra, v2d->cur.ymin);
+  immVertex2f(pos, (float)scene->r.efra, v2d->cur.ymax);
   immEnd();
 
   immUnbindProgram();
diff --git a/source/blender/editors/space_clip/tracking_ops.c b/source/blender/editors/space_clip/tracking_ops.c
index ca224b04da5..7f113108b02 100644
--- a/source/blender/editors/space_clip/tracking_ops.c
+++ b/source/blender/editors/space_clip/tracking_ops.c
@@ -1267,7 +1267,8 @@ static int frame_jump_exec(bContext *C, wmOperator *op)
     }
 
     delta = pos == 1 ? 1 : -1;
-    while (sc->user.framenr + delta >= SFRA && sc->user.framenr + delta <= EFRA) {
+    while (sc->user.framenr + delta >= scene->r.sfra &&
+           sc->user.framenr + delta <= scene->r.efra) {
       int framenr = BKE_movieclip_remap_scene_to_clip_frame(clip, sc->user.framenr + delta);
       MovieTrackingMarker *marker = BKE_tracking_marker_get_exact(track, framenr);
 
@@ -1286,7 +1287,7 @@ static int frame_jump_exec(bContext *C, wmOperator *op)
       delta = pos == 3 ? 1 : -1;
       framenr += delta;
 
-      while (framenr + delta >= SFRA && framenr + delta <= EFRA) {
+      while (framenr + delta >= scene->r.sfra && framenr + delta <= scene->r.efra) {
         MovieReconstructedCamera *cam = BKE_tracking_camera_get_reconstructed(
             tracking, object, framenr);
 
@@ -1300,8 +1301,8 @@ static int frame_jump_exec(bContext *C, wmOperator *op)
     }
   }
 
-  if (CFRA != sc->user.framenr) {
-    CFRA = sc->user.framenr;
+  if (scene->r.cfra != sc->user.framenr) {
+    scene->r.cfra = sc->user.framenr;
     DEG_id_tag_update(&scene->id, ID_RECALC_FRAME_CHANGE);
 
     WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
diff --git a/source/blender/editors/space_clip/tracking_ops_track.c b/source/blender/editors/space_clip/tracking_ops_track.c
index d5223d57490..f6fd2980c19 100644
--- a/source/blender/editors/space_clip/tracking_ops_track.c
+++ b/source/blender/editors/space_clip/tracking_ops_track.c
@@ -131,10 +131,10 @@ static bool track_markers_initjob(bContext *C, TrackMarkersJob *tmj, bool backwa
 
   if (sequence) {
     if (backwards) {
-      tmj->efra = SFRA;
+      tmj->efra = scene->r.sfra;
     }
     else {
-      tmj->efra = EFRA;
+      tmj->efra = scene->r.efra;
     }
     tmj->efra = BKE_movieclip_remap_scene_to_clip_frame(clip, tmj->efra);
   }
diff --git a/source/blender/editors/space_graph/graph_edit.c b/source/blender/editors/space_graph/graph_edit.c
index 324da039daa..7e8bca88744 100644
--- a/source/blender/editors/space_graph/graph_edit.c
+++ b/source/blender/editors/space_graph/graph_edit.c
@@ -156,10 +156,10 @@ static void insert_graph_keys(bAnimContext *ac, eGraphKeys_InsertKey_Types mode)
         x = sipo->cursorTime;
       }
       else if (adt) {
-        x = BKE_nla_tweakedit_remap(adt, (float)CFRA, NLATIME_CONVERT_UNMAP);
+        x = BKE_nla_tweakedit_remap(adt, (float)scene->r.cfra, NLATIME_CONVERT_UNMAP);
       }
       else {
-        x = (float)CFRA;
+        x = (float)scene->r.cfra;
       }
 
       /* Normalize units of cursor's value. */
@@ -178,7 +178,7 @@ static void insert_graph_keys(bAnimContext *ac, eGraphKeys_InsertKey_Types mode)
   }
   else {
     const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(
-        ac->depsgraph, (float)CFRA);
+        ac->depsgraph, (float)scene->r.cfra);
     for (ale = anim_data.first; ale; ale = ale->next) {
       FCurve *fcu = (FCurve *)ale->key_data;
 
@@ -211,12 +211,12 @@ static void insert_graph_keys(bAnimContext *ac, eGraphKeys_InsertKey_Types mode)
         AnimData *adt = ANIM_nla_mapping_get(ac, ale);
 
         /* Adjust current frame for NLA-mapping. */
-        float cfra = (float)CFRA;
+        float cfra = (float)scene->r.cfra;
         if ((sipo) && (sipo->mode == SIPO_MODE_DRIVERS)) {
           cfra = sipo->cursorTime;
         }
         else if (adt) {
-          cfra = BKE_nla_tweakedit_remap(adt, (float)CFRA, NLATIME_CONVERT_UNMAP);
+          cfra = BKE_nla_tweakedit_remap(adt, (float)scene->r.cfra, NLATIME_CONVERT_UNMAP);
         }
 
         const float curval = evaluate_fcurve_only_curve(fcu, cfra);
@@ -1106,8 +1106,8 @@ static int graphkeys_sound_bake_exec(bContext *C, wmOperator *op)
   }
 
   /* Determine extents of the baking. */
-  sbi.cfra = start = CFRA;
-  end = CFRA + sbi.length - 1;
+  sbi.cfra = start = scene->r.cfra;
+  end = scene->r.cfra + sbi.length - 1;
 
   /* Filter anim channels. */
   filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY |
@@ -2130,8 +2130,8 @@ static int graphkeys_framejump_exec(bContext *C, wmOperator *UNUSED(op))
   }
   else {
     /* Animation Mode - Affects current frame (int) */
-    CFRA = round_fl_to_int(sum_time / num_keyframes);
-    SUBFRA = 0.0f;
+    scene->r.cfra = round_fl_to_int(sum_time / num_keyframes);
+    scene->r.subframe = 0.0f;
   }
   sipo->cursorVal = sum_value / (float)num_keyframes;
 
diff --git a/source/blender/editors/space_graph/graph_ops.c b/source/blender/editors/space_graph/graph_ops.c
index 158f2eae88f..8deea21318c 100644
--- a/source/blender/editors/space_graph/graph_ops.c
+++ b/source/blender/editors/space_graph/graph_ops.c
@@ -72,21 +72,21 @@ static void graphview_cursor_apply(bContext *C, wmOperator *op)
      * NOTE: sync this part of the code with ANIM_OT_change_frame
      */
     /* 1) frame is rounded to the nearest int, since frames are ints */
-    CFRA = round_fl_to_int(frame);
+    scene->r.cfra = round_fl_to_int(frame);
 
     if (scene->r.flag & SCER_LOCK_FRAME_SELECTION) {
       /* Clip to preview range
        * NOTE: Preview range won't go into negative values,
        *       so only clamping once should be fine.
        */
-      CLAMP(CFRA, PSFRA, PEFRA);
+      CLAMP(scene->r.cfra, PSFRA, PEFRA);
     }
     else {
       /* Prevent negative frames */
-      FRAMENUMBER_MIN_CLAMP(CFRA);
+      FRAMENUMBER_MIN_CLAMP(scene->r.cfra);
     }
 
-    SUBFRA = 0.0f;
+    scene->r.subframe = 0.0f;
     DEG_id_tag_update(&scene->id, ID_RECALC_FRAME_CHANGE);
   }
 
diff --git a/source/blender/editors/space_graph/graph_select.c b/source/blender/editors/space_graph/graph_select.c
index 70f9e4a60d9..a36bd5c1461 100644
--- a/source/blender/editors/space_graph/graph_select.c
+++ b/source/blender/editors/space_graph/graph_select.c
@@ -1209,7 +1209,7 @@ static void columnselect_graph_keys(bAnimContext *ac, short mode)
       ce = MEM_callocN(sizeof(CfraElem), "cfraElem");
       BLI_addtail(&ked.list, ce);
 
-      ce->cfra = (float)CFRA;
+      ce->cfra = (float)scene->r.cfra;
       break;
 
     case GRAPHKEYS_COLUMNSEL_MARKERS_COLUMN: /* list of selected markers */
@@ -1521,10 +1521,10 @@ static void graphkeys_select_leftright(bAnimContext *ac, short leftright, short
 
   if (leftright == GRAPHKEYS_LRSEL_LEFT) {
     ked.f1 = MINAFRAMEF;
-    ked.f2 = (float)(CFRA + 0.1f);
+    ked.f2 = (float)(scene->r.cfra + 0.1f);
   }
   else {
-    ked.f1 = (float)(CFRA - 0.1f);
+    ked.f1 = (float)(scene->r.cfra - 0.1f);
     ked.f2 = MAXFRAMEF;
   }
 
@@ -1605,7 +1605,7 @@ static int graphkeys_select_leftright_invoke(bContext *C, wmOperator *op, const
 
     /* determine which side of the current frame mouse is on */
     x = UI_view2d_region_to_view_x(v2d, event->mval[0]);
-    if (x < CFRA) {
+    if (x < scene->r.cfra) {
       RNA_enum_set(op->ptr, "mode", GRAPHKEYS_LRSEL_LEFT);
     }
     else {
diff --git a/source/blender/editors/space_image/image_buttons.c b/source/blender/editors/space_image/image_buttons.c
index d0c21f85472..0a774ee679c 100644
--- a/source/blender/editors/space_image/image_buttons.c
+++ b/source/blender/editors/space_image/image_buttons.c
@@ -1218,7 +1218,7 @@ void uiTemplateImageInfo(uiLayout *layout, bContext *C, Image *ima, ImageUser *i
   if (ELEM(ima->source, IMA_SRC_SEQUENCE, IMA_SRC_MOVIE)) {
     /* don't use iuser->framenr directly because it may not be updated if auto-refresh is off */
     Scene *scene = CTX_data_scene(C);
-    const int framenr = BKE_image_user_frame_get(iuser, CFRA, NULL);
+    const int framenr = BKE_image_user_frame_get(iuser, scene->r.cfra, NULL);
     char str[MAX_IMAGE_INFO_LEN];
     int duration = 0;
 
diff --git a/source/blender/editors/space_image/image_draw.c b/source/blender/editors/space_image/image_draw.c
index 048c7345b97..f6f9428213f 100644
--- a/source/blender/editors/space_image/image_draw.c
+++ b/source/blender/editors/space_image/image_draw.c
@@ -515,7 +515,8 @@ void draw_image_cache(const bContext *C, ARegion *region)
   SpaceImage *sima = CTX_wm_space_image(C);
   Scene *scene = CTX_data_scene(C);
   Image *image = ED_space_image(sima);
-  float x, cfra = CFRA, sfra = SFRA, efra = EFRA, framelen = region->winx / (efra - sfra + 1);
+  float x, cfra = scene->r.cfra, sfra = scene->r.sfra, efra = scene->r.efra,
+           framelen = region->winx / (efra - sfra + 1);
   Mask *mask = NULL;
 
   if (!ED_space_image_show_cache(sima)) {
diff --git a/source/blender/editors/space_image/image_ops.c b/source/blender/editors/space_image/image_ops.c
index 537132ac428..b77bdc11ca5 100644
--- a/source/blender/editors/space_image/image_ops.c
+++ b/source/blender/editors/space_image/image_ops.c
@@ -3580,9 +3580,9 @@ static void change_frame_apply(bContext *C, wmOperator *op)
   Scene *scene = CTX_data_scene(C);
 
   /* set the new frame number */
-  CFRA = RNA_int_get(op->ptr, "frame");
-  FRAMENUMBER_MIN_CLAMP(CFRA);
-  SUBFRA = 0.0f;
+  scene->r.cfra = RNA_int_get(op->ptr, "frame");
+  FRAMENUMBER_MIN_CLAMP(scene->r.cfra);
+  scene->r.subframe = 0.0f;
 
   /* do updates */
   DEG_id_tag_update(&scene->id, ID_RECALC_FRAME_CHANGE);
@@ -3603,7 +3603,7 @@ static int frame_from_event(bContext *C, const wmEvent *event)
   int framenr = 0;
 
   if (region->regiontype == RGN_TYPE_WINDOW) {
-    float sfra = SFRA, efra = EFRA, framelen = region->winx / (efra - sfra + 1);
+    float sfra = scene->r.sfra, efra = scene->r.efra, framelen = region->winx / (efra - sfra + 1);
 
     framenr = sfra + event->mval[0] / framelen;
   }
diff --git a/source/blender/editors/space_nla/nla_edit.c b/source/blender/editors/space_nla/nla_edit.c
index 81520445000..50f00aed867 100644
--- a/source/blender/editors/space_nla/nla_edit.c
+++ b/source/blender/editors/space_nla/nla_edit.c
@@ -625,7 +625,7 @@ static int nlaedit_add_actionclip_exec(bContext *C, wmOperator *op)
   }
 
   scene = ac.scene;
-  cfra = (float)CFRA;
+  cfra = (float)scene->r.cfra;
 
   /* get action to use */
   act = BLI_findlink(&bmain->actions, RNA_enum_get(op->ptr, "action"));
@@ -901,7 +901,7 @@ static int nlaedit_add_sound_exec(bContext *C, wmOperator *UNUSED(op))
   }
 
   scene = ac.scene;
-  cfra = CFRA;
+  cfra = scene->r.cfra;
 
   /* get a list of the editable tracks being shown in the NLA */
   filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_SEL |
@@ -2388,7 +2388,7 @@ static int nlaedit_snap_exec(bContext *C, wmOperator *op)
         /* calculate new start position based on snapping mode */
         switch (mode) {
           case NLAEDIT_SNAP_CFRA: /* to current frame */
-            strip->start = (float)CFRA;
+            strip->start = (float)scene->r.cfra;
             break;
           case NLAEDIT_SNAP_NEAREST_FRAME: /* to nearest frame */
             strip->start = floorf(start + 0.5f);
diff --git a/source/blender/editors/space_nla/nla_select.c b/source/blender/editors/space_nla/nla_select.c
index 1efb91bc99f..d51bfce6355 100644
--- a/source/blender/editors/space_nla/nla_select.c
+++ b/source/blender/editors/space_nla/nla_select.c
@@ -455,10 +455,10 @@ static void nlaedit_select_leftright(bContext *C,
   /* get range, and get the right flag-setting mode */
   if (leftright == NLAEDIT_LRSEL_LEFT) {
     xmin = MINAFRAMEF;
-    xmax = (float)(CFRA + 0.1f);
+    xmax = (float)(scene->r.cfra + 0.1f);
   }
   else {
-    xmin = (float)(CFRA - 0.1f);
+    xmin = (float)(scene->r.cfra - 0.1f);
     xmax = MAXFRAMEF;
   }
 
@@ -540,7 +540,7 @@ static int nlaedit_select_leftright_invoke(bContext *C, wmOperator *op, const wm
 
     /* determine which side of the current frame mouse is on */
     x = UI_view2d_region_to_view_x(v2d, event->mval[0]);
-    if (x < CFRA) {
+    if (x < scene->r.cfra) {
       RNA_enum_set(op->ptr, "mode", NLAEDIT_LRSEL_LEFT);
     }
     else {
diff --git a/source/blender/editors/space_nla/space_nla.c b/source/blender/editors/space_nla/space_nla.c
index c50278518de..13035a9d5fd 100644
--- a/source/blender/editors/space_nla/space_nla.c
+++ b/source/blender/editors/space_nla/space_nla.c
@@ -87,9 +87,9 @@ static SpaceLink *nla_create(const ScrArea *area, const Scene *scene)
   BLI_addtail(&snla->regionbase, region);
   region->regiontype = RGN_TYPE_WINDOW;
 
-  region->v2d.tot.xmin = (float)(SFRA - 10);
+  region->v2d.tot.xmin = (float)(scene->r.sfra - 10);
   region->v2d.tot.ymin = (float)(-area->winy) / 3.0f;
-  region->v2d.tot.xmax = (float)(EFRA + 10);
+  region->v2d.tot.xmax = (float)(scene->r.efra + 10);
   region->v2d.tot.ymax = 0.0f;
 
   region->v2d.cur = region->v2d.tot;
diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc
index 6806d715004..66e07c804b6 100644
--- a/source/blender/editors/space_node/drawnode.cc
+++ b/source/blender/editors/space_node/drawnode.cc
@@ -329,7 +329,7 @@ static void node_buts_image_user(uiLayout *layout,
     Scene *scene = CTX_data_scene(C);
 
     char numstr[32];
-    const int framenr = BKE_image_user_frame_get(iuser, CFRA, nullptr);
+    const int framenr = BKE_image_user_frame_get(iuser, scene->r.cfra, nullptr);
     BLI_snprintf(numstr, sizeof(numstr), IFACE_("Frame: %d"), framenr);
     uiItemL(layout, numstr, ICON_NONE);
   }
diff --git a/source/blender/editors/space_sequencer/sequencer_add.c b/source/blender/editors/space_sequencer/sequencer_add.c
index 0f6d6930530..dd6d58ee5a2 100644
--- a/source/blender/editors/space_sequencer/sequencer_add.c
+++ b/source/blender/editors/space_sequencer/sequencer_add.c
@@ -192,7 +192,7 @@ static int sequencer_generic_invoke_xy_guess_channel(bContext *C, int type)
   Sequence *seq;
   Scene *scene = CTX_data_scene(C);
   Editing *ed = SEQ_editing_ensure(scene);
-  int timeline_frame = (int)CFRA;
+  int timeline_frame = (int)scene->r.cfra;
   int proximity = INT_MAX;
 
   if (!ed || !ed->seqbasep) {
@@ -218,7 +218,7 @@ static void sequencer_generic_invoke_xy__internal(bContext *C, wmOperator *op, i
 {
   Scene *scene = CTX_data_scene(C);
 
-  int timeline_frame = (int)CFRA;
+  int timeline_frame = (int)scene->r.cfra;
 
   /* Effect strips don't need a channel initialized from the mouse. */
   if (!(flag & SEQPROP_NOCHAN) && RNA_struct_property_is_set(op->ptr, "channel") == 0) {
diff --git a/source/blender/editors/space_sequencer/sequencer_edit.c b/source/blender/editors/space_sequencer/sequencer_edit.c
index d507c9ae6bc..79eeb3c0374 100644
--- a/source/blender/editors/space_sequencer/sequencer_edit.c
+++ b/source/blender/editors/space_sequencer/sequencer_edit.c
@@ -265,7 +265,7 @@ static int sequencer_gap_remove_exec(bContext *C, wmOperator *op)
   const bool do_all = RNA_boolean_get(op->ptr, "all");
   const Editing *ed = SEQ_editing_get(scene);
 
-  SEQ_edit_remove_gaps(scene, ed->seqbasep, CFRA, do_all);
+  SEQ_edit_remove_gaps(scene, ed->seqbasep, scene->r.cfra, do_all);
 
   WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
   DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
@@ -304,7 +304,7 @@ static int sequencer_gap_insert_exec(bContext *C, wmOperator *op)
   Scene *scene = CTX_data_scene(C);
   const int frames = RNA_int_get(op->ptr, "frames");
   const Editing *ed = SEQ_editing_get(scene);
-  SEQ_transform_offset_after_frame(scene, ed->seqbasep, frames, CFRA);
+  SEQ_transform_offset_after_frame(scene, ed->seqbasep, frames, scene->r.cfra);
 
   WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
 
@@ -423,7 +423,7 @@ static int sequencer_snap_invoke(bContext *C, wmOperator *op, const wmEvent *UNU
 
   int snap_frame;
 
-  snap_frame = CFRA;
+  snap_frame = scene->r.cfra;
 
   RNA_int_set(op->ptr, "frame", snap_frame);
   return sequencer_snap_exec(C, op);
@@ -1465,7 +1465,7 @@ static int sequencer_split_invoke(bContext *C, wmOperator *op, const wmEvent *ev
   View2D *v2d = UI_view2d_fromcontext(C);
 
   int split_side = RNA_enum_get(op->ptr, "side");
-  int split_frame = CFRA;
+  int split_frame = scene->r.cfra;
 
   if (split_side == SEQ_SIDE_MOUSE) {
     if (ED_operator_sequencer_active(C) && v2d) {
@@ -2091,12 +2091,12 @@ static bool strip_jump_internal(Scene *scene,
                                 const bool do_center)
 {
   bool changed = false;
-  int timeline_frame = CFRA;
+  int timeline_frame = scene->r.cfra;
   int next_frame = SEQ_time_find_next_prev_edit(
       scene, timeline_frame, side, do_skip_mute, do_center, false);
 
   if (next_frame != timeline_frame) {
-    CFRA = next_frame;
+    scene->r.cfra = next_frame;
     changed = true;
   }
 
@@ -3408,7 +3408,7 @@ static int sequencer_strip_transform_fit_exec(bContext *C, wmOperator *op)
 
   for (seq = ed->seqbasep->first; seq; seq = seq->next) {
     if (seq->flag & SELECT && seq->type != SEQ_TYPE_SOUND_RAM) {
-      const int timeline_frame = CFRA;
+      const int timeline_frame = scene->r.cfra;
       StripElem *strip_elem = SEQ_render_give_stripelem(scene, seq, timeline_frame);
 
       if (strip_elem == NULL) {
diff --git a/source/blender/editors/space_sequencer/sequencer_select.c b/source/blender/editors/space_sequencer/sequencer_select.c
index cfdbeaf0658..3f48404053d 100644
--- a/source/blender/editors/space_sequencer/sequencer_select.c
+++ b/source/blender/editors/space_sequencer/sequencer_select.c
@@ -589,8 +589,8 @@ static void sequencer_select_side_of_frame(const bContext *C,
 
   const float x = UI_view2d_region_to_view_x(v2d, mval[0]);
   LISTBASE_FOREACH (Sequence *, seq_iter, SEQ_active_seqbase_get(ed)) {
-    if (((x < CFRA) && (SEQ_time_right_handle_frame_get(scene, seq_iter) <= CFRA)) ||
-        ((x >= CFRA) && (SEQ_time_left_handle_frame_get(scene, seq_iter) >= CFRA))) {
+    if (((x < scene->r.cfra) && (SEQ_time_right_handle_frame_get(scene, seq_iter) <= scene->r.cfra)) ||
+        ((x >= scene->r.cfra) && (SEQ_time_left_handle_frame_get(scene, seq_iter) >= scene->r.cfra))) {
       /* Select left or right. */
       seq_iter->flag |= SELECT;
       recurs_sel_seq(seq_iter);
@@ -603,8 +603,8 @@ static void sequencer_select_side_of_frame(const bContext *C,
       TimeMarker *tmarker;
 
       for (tmarker = scene->markers.first; tmarker; tmarker = tmarker->next) {
-        if (((x < CFRA) && (tmarker->frame <= CFRA)) ||
-            ((x >= CFRA) && (tmarker->frame >= CFRA))) {
+        if (((x < scene->r.cfra) && (tmarker->frame <= scene->r.cfra)) ||
+            ((x >= scene->r.cfra) && (tmarker->frame >= scene->r.cfra))) {
           tmarker->flag |= SELECT;
         }
         else {
@@ -1443,7 +1443,7 @@ static int sequencer_select_side_of_frame_exec(bContext *C, wmOperator *op)
   if (extend == false) {
     ED_sequencer_deselect_all(scene);
   }
-  const int timeline_frame = CFRA;
+  const int timeline_frame = scene->r.cfra;
   LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) {
     bool test = false;
     switch (side) {
diff --git a/source/blender/editors/space_view3d/view3d_draw.c b/source/blender/editors/space_view3d/view3d_draw.c
index b9e4c19295d..5405867be56 100644
--- a/source/blender/editors/space_view3d/view3d_draw.c
+++ b/source/blender/editors/space_view3d/view3d_draw.c
@@ -1294,7 +1294,7 @@ static void draw_viewport_name(ARegion *region, View3D *v3d, int xoffset, int *y
 static void draw_selected_name(
     Scene *scene, ViewLayer *view_layer, Object *ob, int xoffset, int *yoffset)
 {
-  const int cfra = CFRA;
+  const int cfra = scene->r.cfra;
   const char *msg_pin = " (Pinned)";
   const char *msg_sep = " : ";
 
diff --git a/source/blender/editors/space_view3d/view3d_gizmo_ruler.c b/source/blender/editors/space_view3d/view3d_gizmo_ruler.c
index 96f242dba9f..62dc461e05c 100644
--- a/source/blender/editors/space_view3d/view3d_gizmo_ruler.c
+++ b/source/blender/editors/space_view3d/view3d_gizmo_ruler.c
@@ -523,7 +523,7 @@ static bool view3d_ruler_to_gpencil(bContext *C, wmGizmoGroup *gzgroup)
     gpl->flag |= GP_LAYER_HIDE | GP_LAYER_IS_RULER;
   }
 
-  gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_ADD_NEW);
+  gpf = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, GP_GETFRAME_ADD_NEW);
   BKE_gpencil_free_strokes(gpf);
 
   for (ruler_item = gzgroup_ruler_item_first_get(gzgroup); ruler_item;
@@ -577,7 +577,7 @@ static bool view3d_ruler_from_gpencil(const bContext *C, wmGizmoGroup *gzgroup)
     gpl = view3d_ruler_layer_get(scene->gpd);
     if (gpl) {
       bGPDframe *gpf;
-      gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_USE_PREV);
+      gpf = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, GP_GETFRAME_USE_PREV);
       if (gpf) {
         bGPDstroke *gps;
         for (gps = gpf->strokes.first; gps; gps = gps->next) {
diff --git a/source/blender/editors/space_view3d/view3d_utils.c b/source/blender/editors/space_view3d/view3d_utils.c
index 306394ce53d..1fabdef8da2 100644
--- a/source/blender/editors/space_view3d/view3d_utils.c
+++ b/source/blender/editors/space_view3d/view3d_utils.c
@@ -633,7 +633,7 @@ bool ED_view3d_camera_autokey(const Scene *scene,
                               const bool do_translate)
 {
   if (autokeyframe_cfra_can_key(scene, id_key)) {
-    const float cfra = (float)CFRA;
+    const float cfra = (float)scene->r.cfra;
     ListBase dsources = {NULL, NULL};
 
     /* add data-source override for the camera object */
diff --git a/source/blender/editors/transform/transform_convert_action.c b/source/blender/editors/transform/transform_convert_action.c
index ba62f6f1fea..71a78321e12 100644
--- a/source/blender/editors/transform/transform_convert_action.c
+++ b/source/blender/editors/transform/transform_convert_action.c
@@ -326,7 +326,7 @@ void createTransActionData(bContext *C, TransInfo *t)
 
   /* which side of the current frame should be allowed */
   if (t->mode == TFM_TIME_EXTEND) {
-    t->frame_side = transform_convert_frame_side_dir_get(t, (float)CFRA);
+    t->frame_side = transform_convert_frame_side_dir_get(t, (float)scene->r.cfra);
   }
   else {
     /* normal transform - both sides of current frame are considered */
@@ -341,10 +341,10 @@ void createTransActionData(bContext *C, TransInfo *t)
      * higher scaling ratios, but is faster than converting all points)
      */
     if (adt) {
-      cfra = BKE_nla_tweakedit_remap(adt, (float)CFRA, NLATIME_CONVERT_UNMAP);
+      cfra = BKE_nla_tweakedit_remap(adt, (float)scene->r.cfra, NLATIME_CONVERT_UNMAP);
     }
     else {
-      cfra = (float)CFRA;
+      cfra = (float)scene->r.cfra;
     }
 
     if (ELEM(ale->type, ANIMTYPE_FCURVE, ANIMTYPE_NLACURVE)) {
@@ -399,7 +399,7 @@ void createTransActionData(bContext *C, TransInfo *t)
       continue;
     }
 
-    cfra = (float)CFRA;
+    cfra = (float)scene->r.cfra;
 
     {
       AnimData *adt;
@@ -447,10 +447,10 @@ void createTransActionData(bContext *C, TransInfo *t)
 
       adt = ANIM_nla_mapping_get(&ac, ale);
       if (adt) {
-        cfra = BKE_nla_tweakedit_remap(adt, (float)CFRA, NLATIME_CONVERT_UNMAP);
+        cfra = BKE_nla_tweakedit_remap(adt, (float)scene->r.cfra, NLATIME_CONVERT_UNMAP);
       }
       else {
-        cfra = (float)CFRA;
+        cfra = (float)scene->r.cfra;
       }
 
       if (ale->type == ANIMTYPE_GPLAYER) {
diff --git a/source/blender/editors/transform/transform_convert_armature.c b/source/blender/editors/transform/transform_convert_armature.c
index a031a6cd45b..1613218ca29 100644
--- a/source/blender/editors/transform/transform_convert_armature.c
+++ b/source/blender/editors/transform/transform_convert_armature.c
@@ -93,8 +93,8 @@ static void autokeyframe_pose(
   KeyingSet *active_ks = ANIM_scene_get_active_keyingset(scene);
   ListBase nla_cache = {NULL, NULL};
   Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
-  const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(depsgraph,
-                                                                                    (float)CFRA);
+  const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(
+      depsgraph, (float)scene->r.cfra);
   eInsertKeyFlags flag = 0;
 
   /* flag is initialized from UserPref keyframing settings
diff --git a/source/blender/editors/transform/transform_convert_gpencil.c b/source/blender/editors/transform/transform_convert_gpencil.c
index a88d42b7f30..4bd02b0a45b 100644
--- a/source/blender/editors/transform/transform_convert_gpencil.c
+++ b/source/blender/editors/transform/transform_convert_gpencil.c
@@ -685,7 +685,7 @@ void createTransGPencil(bContext *C, TransInfo *t)
   bGPdata *gpd = obact->data;
   BLI_assert(gpd != NULL);
 
-  const int cfra_scene = CFRA;
+  const int cfra_scene = scene->r.cfra;
 
   const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
   const bool use_multiframe_falloff = (ts->gp_sculpt.flag & GP_SCULPT_SETT_FLAG_FRAME_FALLOFF) !=
diff --git a/source/blender/editors/transform/transform_convert_graph.c b/source/blender/editors/transform/transform_convert_graph.c
index 1444268baf5..d93fff72de6 100644
--- a/source/blender/editors/transform/transform_convert_graph.c
+++ b/source/blender/editors/transform/transform_convert_graph.c
@@ -239,7 +239,7 @@ void createTransGraphEditData(bContext *C, TransInfo *t)
   /* which side of the current frame should be allowed */
   /* XXX we still want this mode, but how to get this using standard transform too? */
   if (t->mode == TFM_TIME_EXTEND) {
-    t->frame_side = transform_convert_frame_side_dir_get(t, (float)CFRA);
+    t->frame_side = transform_convert_frame_side_dir_get(t, (float)scene->r.cfra);
   }
   else {
     /* normal transform - both sides of current frame are considered */
@@ -264,10 +264,10 @@ void createTransGraphEditData(bContext *C, TransInfo *t)
      * higher scaling ratios, but is faster than converting all points)
      */
     if (adt) {
-      cfra = BKE_nla_tweakedit_remap(adt, (float)CFRA, NLATIME_CONVERT_UNMAP);
+      cfra = BKE_nla_tweakedit_remap(adt, (float)scene->r.cfra, NLATIME_CONVERT_UNMAP);
     }
     else {
-      cfra = (float)CFRA;
+      cfra = (float)scene->r.cfra;
     }
 
     for (i = 0, bezt = fcu->bezt; i < fcu->totvert; i++, bezt++) {
@@ -370,10 +370,10 @@ void createTransGraphEditData(bContext *C, TransInfo *t)
      * higher scaling ratios, but is faster than converting all points)
      */
     if (adt) {
-      cfra = BKE_nla_tweakedit_remap(adt, (float)CFRA, NLATIME_CONVERT_UNMAP);
+      cfra = BKE_nla_tweakedit_remap(adt, (float)scene->r.cfra, NLATIME_CONVERT_UNMAP);
     }
     else {
-      cfra = (float)CFRA;
+      cfra = (float)scene->r.cfra;
     }
 
     unit_scale = ANIM_unit_mapping_get_factor(
@@ -561,10 +561,10 @@ void createTransGraphEditData(bContext *C, TransInfo *t)
        * higher scaling ratios, but is faster than converting all points)
        */
       if (adt) {
-        cfra = BKE_nla_tweakedit_remap(adt, (float)CFRA, NLATIME_CONVERT_UNMAP);
+        cfra = BKE_nla_tweakedit_remap(adt, (float)scene->r.cfra, NLATIME_CONVERT_UNMAP);
       }
       else {
-        cfra = (float)CFRA;
+        cfra = (float)scene->r.cfra;
       }
 
       for (i = 0, bezt = fcu->bezt; i < fcu->totvert; i++, bezt++) {
diff --git a/source/blender/editors/transform/transform_convert_mask.c b/source/blender/editors/transform/transform_convert_mask.c
index b07d2bda0c5..f035cfc7aa9 100644
--- a/source/blender/editors/transform/transform_convert_mask.c
+++ b/source/blender/editors/transform/transform_convert_mask.c
@@ -117,7 +117,7 @@ static void MaskPointToTransData(Scene *scene,
   const bool is_sel_any = MASKPOINT_ISSEL_ANY(point);
   float parent_matrix[3][3], parent_inverse_matrix[3][3];
 
-  BKE_mask_point_parent_matrix_get(point, CFRA, parent_matrix);
+  BKE_mask_point_parent_matrix_get(point, scene->r.cfra, parent_matrix);
   invert_m3_m3(parent_inverse_matrix, parent_matrix);
 
   if (is_prop_edit || is_sel_point) {
@@ -455,7 +455,7 @@ void special_aftertrans_update__mask(bContext *C, TransInfo *t)
   if (IS_AUTOKEY_ON(t->scene)) {
     Scene *scene = t->scene;
 
-    if (ED_mask_layer_shape_auto_key_select(mask, CFRA)) {
+    if (ED_mask_layer_shape_auto_key_select(mask, scene->r.cfra)) {
       WM_event_add_notifier(C, NC_MASK | ND_DATA, &mask->id);
       DEG_id_tag_update(&mask->id, 0);
     }
diff --git a/source/blender/editors/transform/transform_convert_nla.c b/source/blender/editors/transform/transform_convert_nla.c
index 2fa8fcf65ba..b2c0cc553a7 100644
--- a/source/blender/editors/transform/transform_convert_nla.c
+++ b/source/blender/editors/transform/transform_convert_nla.c
@@ -87,7 +87,7 @@ void createTransNlaData(bContext *C, TransInfo *t)
 
   /* which side of the current frame should be allowed */
   if (t->mode == TFM_TIME_EXTEND) {
-    t->frame_side = transform_convert_frame_side_dir_get(t, (float)CFRA);
+    t->frame_side = transform_convert_frame_side_dir_get(t, (float)scene->r.cfra);
   }
   else {
     /* normal transform - both sides of current frame are considered */
@@ -108,10 +108,10 @@ void createTransNlaData(bContext *C, TransInfo *t)
       /* transition strips can't get directly transformed */
       if (strip->type != NLASTRIP_TYPE_TRANSITION) {
         if (strip->flag & NLASTRIP_FLAG_SELECT) {
-          if (FrameOnMouseSide(t->frame_side, strip->start, (float)CFRA)) {
+          if (FrameOnMouseSide(t->frame_side, strip->start, (float)scene->r.cfra)) {
             count++;
           }
-          if (FrameOnMouseSide(t->frame_side, strip->end, (float)CFRA)) {
+          if (FrameOnMouseSide(t->frame_side, strip->end, (float)scene->r.cfra)) {
             count++;
           }
         }
@@ -122,7 +122,7 @@ void createTransNlaData(bContext *C, TransInfo *t)
   /* stop if trying to build list if nothing selected */
   if (count == 0) {
     /* clear temp metas that may have been created but aren't needed now
-     * because they fell on the wrong side of CFRA
+     * because they fell on the wrong side of scene->r.cfra
      */
     for (ale = anim_data.first; ale; ale = ale->next) {
       NlaTrack *nlt = (NlaTrack *)ale->data;
@@ -181,12 +181,12 @@ void createTransNlaData(bContext *C, TransInfo *t)
             tdn->h2[0] = strip->end;
             tdn->h2[1] = yval;
 
-            center[0] = (float)CFRA;
+            center[0] = (float)scene->r.cfra;
             center[1] = yval;
             center[2] = 0.0f;
 
             /* set td's based on which handles are applicable */
-            if (FrameOnMouseSide(t->frame_side, strip->start, (float)CFRA)) {
+            if (FrameOnMouseSide(t->frame_side, strip->start, (float)scene->r.cfra)) {
               /* just set tdn to assume that it only has one handle for now */
               tdn->handle = -1;
 
@@ -206,7 +206,7 @@ void createTransNlaData(bContext *C, TransInfo *t)
               td->extra = tdn;
               td++;
             }
-            if (FrameOnMouseSide(t->frame_side, strip->end, (float)CFRA)) {
+            if (FrameOnMouseSide(t->frame_side, strip->end, (float)scene->r.cfra)) {
               /* if tdn is already holding the start handle,
                * then we're doing both, otherwise, only end */
               tdn->handle = (tdn->handle) ? 2 : 1;
diff --git a/source/blender/editors/transform/transform_convert_object.c b/source/blender/editors/transform/transform_convert_object.c
index 47e78656861..26d57ae1e58 100644
--- a/source/blender/editors/transform/transform_convert_object.c
+++ b/source/blender/editors/transform/transform_convert_object.c
@@ -732,8 +732,8 @@ static void autokeyframe_object(
     KeyingSet *active_ks = ANIM_scene_get_active_keyingset(scene);
     ListBase dsources = {NULL, NULL};
     Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
-    const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(depsgraph,
-                                                                                      (float)CFRA);
+    const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(
+        depsgraph, (float)scene->r.cfra);
     eInsertKeyFlags flag = 0;
 
     /* Get flags used for inserting keyframes. */
diff --git a/source/blender/editors/transform/transform_convert_sequencer.c b/source/blender/editors/transform/transform_convert_sequencer.c
index 849641fd320..dcc1739606f 100644
--- a/source/blender/editors/transform/transform_convert_sequencer.c
+++ b/source/blender/editors/transform/transform_convert_sequencer.c
@@ -90,7 +90,7 @@ static void SeqTransInfo(TransInfo *t, Sequence *seq, int *r_count, int *r_flag)
   if (t->mode == TFM_TIME_EXTEND) {
 
     /* *** Extend Transform *** */
-    int cfra = CFRA;
+    int cfra = scene->r.cfra;
     int left = SEQ_time_left_handle_frame_get(scene, seq);
     int right = SEQ_time_right_handle_frame_get(scene, seq);
 
@@ -489,7 +489,7 @@ void createTransSeqData(TransInfo *t)
   }
 
   tc->custom.type.free_cb = freeSeqData;
-  t->frame_side = transform_convert_frame_side_dir_get(t, (float)CFRA);
+  t->frame_side = transform_convert_frame_side_dir_get(t, (float)scene->r.cfra);
 
   count = SeqTransCount(t, ed->seqbasep);
 
diff --git a/source/blender/editors/transform/transform_convert_sequencer_image.c b/source/blender/editors/transform/transform_convert_sequencer_image.c
index 46606d5814f..8be454b540c 100644
--- a/source/blender/editors/transform/transform_convert_sequencer_image.c
+++ b/source/blender/editors/transform/transform_convert_sequencer_image.c
@@ -173,19 +173,19 @@ static bool autokeyframe_sequencer_image(bContext *C,
   bool changed = false;
   if (do_rot) {
     prop = RNA_struct_find_property(&ptr, "rotation");
-    changed |= ED_autokeyframe_property(C, scene, &ptr, prop, -1, CFRA, false);
+    changed |= ED_autokeyframe_property(C, scene, &ptr, prop, -1, scene->r.cfra, false);
   }
   if (do_loc) {
     prop = RNA_struct_find_property(&ptr, "offset_x");
-    changed |= ED_autokeyframe_property(C, scene, &ptr, prop, -1, CFRA, false);
+    changed |= ED_autokeyframe_property(C, scene, &ptr, prop, -1, scene->r.cfra, false);
     prop = RNA_struct_find_property(&ptr, "offset_y");
-    changed |= ED_autokeyframe_property(C, scene, &ptr, prop, -1, CFRA, false);
+    changed |= ED_autokeyframe_property(C, scene, &ptr, prop, -1, scene->r.cfra, false);
   }
   if (do_scale) {
     prop = RNA_struct_find_property(&ptr, "scale_x");
-    changed |= ED_autokeyframe_property(C, scene, &ptr, prop, -1, CFRA, false);
+    changed |= ED_autokeyframe_property(C, scene, &ptr, prop, -1, scene->r.cfra, false);
     prop = RNA_struct_find_property(&ptr, "scale_y");
-    changed |= ED_autokeyframe_property(C, scene, &ptr, prop, -1, CFRA, false);
+    changed |= ED_autokeyframe_property(C, scene, &ptr, prop, -1, scene->r.cfra, false);
   }
 
   return changed;
diff --git a/source/blender/editors/transform/transform_mode_timescale.c b/source/blender/editors/transform/transform_mode_timescale.c
index 4130f6dc034..1474bc4591a 100644
--- a/source/blender/editors/transform/transform_mode_timescale.c
+++ b/source/blender/editors/transform/transform_mode_timescale.c
@@ -59,7 +59,7 @@ static void applyTimeScaleValue(TransInfo *t, float value)
        * (this is only valid when not in NLA)
        */
       AnimData *adt = (t->spacetype != SPACE_NLA) ? td->extra : NULL;
-      float startx = CFRA;
+      float startx = scene->r.cfra;
       float fac = value;
 
       /* take proportional editing into account */
@@ -107,7 +107,7 @@ void initTimeScale(TransInfo *t)
   t->mode = TFM_TIME_SCALE;
   t->transform = applyTimeScale;
 
-  /* recalculate center2d to use CFRA and mouse Y, since that's
+  /* recalculate center2d to use scene->r.cfra and mouse Y, since that's
    * what is used in time scale */
   if ((t->flag & T_OVERRIDE_CENTER) == 0) {
     t->center_global[0] = t->scene->r.cfra;
diff --git a/source/blender/editors/transform/transform_snap_object.cc b/source/blender/editors/transform/transform_snap_object.cc
index 8a1eca3a6f7..817474f795e 100644
--- a/source/blender/editors/transform/transform_snap_object.cc
+++ b/source/blender/editors/transform/transform_snap_object.cc
@@ -2623,7 +2623,7 @@ static eSnapMode snapCamera(const SnapObjectContext *sctx,
 
       if ((tracking_object->flag & TRACKING_OBJECT_CAMERA) == 0) {
         BKE_tracking_camera_get_reconstructed_interpolate(
-            tracking, tracking_object, CFRA, reconstructed_camera_mat);
+            tracking, tracking_object, scene->r.cfra, reconstructed_camera_mat);
 
         invert_m4_m4(reconstructed_camera_imat, reconstructed_camera_mat);
       }
diff --git a/source/blender/editors/transform/transform_snap_sequencer.c b/source/blender/editors/transform/transform_snap_sequencer.c
index 7972410ad67..5ac526b1e91 100644
--- a/source/blender/editors/transform/transform_snap_sequencer.c
+++ b/source/blender/editors/transform/transform_snap_sequencer.c
@@ -191,7 +191,7 @@ static void seq_snap_target_points_build(Scene *scene,
   int i = 0;
 
   if (snap_mode & SEQ_SNAP_TO_CURRENT_FRAME) {
-    snap_data->target_snap_points[i] = CFRA;
+    snap_data->target_snap_points[i] = scene->r.cfra;
     i++;
   }
 
diff --git a/source/blender/editors/util/ed_util_imbuf.c b/source/blender/editors/util/ed_util_imbuf.c
index cf6f26652a7..fc5fb9f9c28 100644
--- a/source/blender/editors/util/ed_util_imbuf.c
+++ b/source/blender/editors/util/ed_util_imbuf.c
@@ -291,7 +291,7 @@ static void sequencer_sample_apply(bContext *C, wmOperator *op, const wmEvent *e
   Scene *scene = CTX_data_scene(C);
   SpaceSeq *sseq = (SpaceSeq *)CTX_wm_space_data(C);
   ARegion *region = CTX_wm_region(C);
-  ImBuf *ibuf = sequencer_ibuf_get(bmain, region, depsgraph, scene, sseq, CFRA, 0, NULL);
+  ImBuf *ibuf = sequencer_ibuf_get(bmain, region, depsgraph, scene, sseq, scene->r.cfra, 0, NULL);
   ImageSampleInfo *info = op->customdata;
   float fx, fy;
 
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c b/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c
index d59d2cb52fc..bd8fd9f72ad 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c
@@ -182,7 +182,7 @@ void generic_bake_deform_stroke(
   LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
     LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
       if (retime) {
-        CFRA = gpf->framenum;
+        scene->r.cfra = gpf->framenum;
         BKE_scene_graph_update_for_newframe(depsgraph);
       }
       LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
@@ -193,7 +193,7 @@ void generic_bake_deform_stroke(
 
   /* Return frame state and DB to original state. */
   if (retime) {
-    CFRA = oldframe;
+    scene->r.cfra = oldframe;
     BKE_scene_graph_update_for_newframe(depsgraph);
   }
 }
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c
index 96e649bf7a1..30f1a004338 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c
@@ -129,7 +129,7 @@ static void bakeModifier(Main *UNUSED(bmain),
       /* Apply lattice effects on this frame
        * NOTE: this assumes that we don't want lattice animation on non-keyframed frames.
        */
-      CFRA = gpf->framenum;
+      scene->r.cfra = gpf->framenum;
       BKE_scene_graph_update_for_newframe(depsgraph);
 
       /* Recalculate lattice data. */
@@ -153,7 +153,7 @@ static void bakeModifier(Main *UNUSED(bmain),
   }
 
   /* Return frame state and DB to original state. */
-  CFRA = oldframe;
+  scene->r.cfra = oldframe;
   BKE_scene_graph_update_for_newframe(depsgraph);
 }
 
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c
index df66251159c..1d62e930caa 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c
@@ -163,7 +163,7 @@ static void bakeModifier(Main *UNUSED(bmain),
   LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
     LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
       /* apply mirror effects on this frame */
-      CFRA = gpf->framenum;
+      scene->r.cfra = gpf->framenum;
       BKE_scene_graph_update_for_newframe(depsgraph);
 
       /* compute mirror effects on this frame */
@@ -172,7 +172,7 @@ static void bakeModifier(Main *UNUSED(bmain),
   }
 
   /* return frame state and DB to original state */
-  CFRA = oldframe;
+  scene->r.cfra = oldframe;
   BKE_scene_graph_update_for_newframe(depsgraph);
 }
 
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilshrinkwrap.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilshrinkwrap.c
index cd2373dcb26..017287239ea 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpencilshrinkwrap.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilshrinkwrap.c
@@ -135,7 +135,7 @@ static void bakeModifier(Main *UNUSED(bmain),
   LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
     LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
       /* Apply shrinkwrap effects on this frame. */
-      CFRA = gpf->framenum;
+      scene->r.cfra = gpf->framenum;
       BKE_scene_graph_update_for_newframe(depsgraph);
 
       /* Recalculate shrinkwrap data. */
@@ -163,7 +163,7 @@ static void bakeModifier(Main *UNUSED(bmain),
   }
 
   /* Return frame state and DB to original state. */
-  CFRA = oldframe;
+  scene->r.cfra = oldframe;
   BKE_scene_graph_update_for_newframe(depsgraph);
 }
 
diff --git a/source/blender/io/gpencil/intern/gpencil_io_capi.cc b/source/blender/io/gpencil/intern/gpencil_io_capi.cc
index 84b273bc570..ac5f8cf7c8d 100644
--- a/source/blender/io/gpencil/intern/gpencil_io_capi.cc
+++ b/source/blender/io/gpencil/intern/gpencil_io_capi.cc
@@ -112,7 +112,7 @@ static bool gpencil_io_export_pdf(Depsgraph *depsgraph,
           continue;
         }
 
-        CFRA = i;
+        scene->r.cfra = i;
         BKE_scene_graph_update_for_newframe(depsgraph);
         exporter->prepare_camera_params(scene, iparams);
         exporter->frame_number_set(i);
@@ -122,7 +122,7 @@ static bool gpencil_io_export_pdf(Depsgraph *depsgraph,
       result = exporter->write();
       /* Back to original frame. */
       exporter->frame_number_set(iparams->frame_cur);
-      CFRA = iparams->frame_cur;
+      scene->r.cfra = iparams->frame_cur;
       BKE_scene_camera_switch_update(scene);
       BKE_scene_graph_update_for_newframe(depsgraph);
       break;
diff --git a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc
index b0938084efb..77d4f6268bc 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc
+++ b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc
@@ -323,7 +323,7 @@ void exporter_main(bContext *C, const OBJExportParams &export_params)
 
   char filepath_with_frames[FILE_MAX];
   /* Used to reset the Scene to its original state. */
-  const int original_frame = CFRA;
+  const int original_frame = scene->r.cfra;
 
   for (int frame = export_params.start_frame; frame <= export_params.end_frame; frame++) {
     const bool filepath_ok = append_frame_to_filename(filepath, frame, filepath_with_frames);
@@ -332,11 +332,11 @@ void exporter_main(bContext *C, const OBJExportParams &export_params)
       return;
     }
 
-    CFRA = frame;
+    scene->r.cfra = frame;
     obj_depsgraph.update_for_newframe();
     fprintf(stderr, "Writing to %s\n", filepath_with_frames);
     export_frame(obj_depsgraph.get(), export_params, filepath_with_frames);
   }
-  CFRA = original_frame;
+  scene->r.cfra = original_frame;
 }
 }  // namespace blender::io::obj
diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h
index c45b06f8c85..f8fcd78d63b 100644
--- a/source/blender/makesdna/DNA_scene_types.h
+++ b/source/blender/makesdna/DNA_scene_types.h
@@ -2054,10 +2054,6 @@ extern const char *RE_engine_id_CYCLES;
 #define V3D_CAMERA_SCENE(scene, v3d) \
   ((!(v3d)->scenelock && (v3d)->camera) ? (v3d)->camera : (scene)->camera)
 
-#define CFRA (scene->r.cfra)
-#define SUBFRA (scene->r.subframe)
-#define SFRA (scene->r.sfra)
-#define EFRA (scene->r.efra)
 #define PRVRANGEON (scene->r.flag & SCER_PRV_RANGE)
 #define PSFRA ((PRVRANGEON) ? (scene->r.psfra) : (scene->r.sfra))
 #define PEFRA ((PRVRANGEON) ? (scene->r.pefra) : (scene->r.efra))
diff --git a/source/blender/sequencer/intern/strip_relations.c b/source/blender/sequencer/intern/strip_relations.c
index 1132c4dd69d..f89974b8fef 100644
--- a/source/blender/sequencer/intern/strip_relations.c
+++ b/source/blender/sequencer/intern/strip_relations.c
@@ -250,7 +250,7 @@ void SEQ_relations_free_imbuf(Scene *scene, ListBase *seqbase, bool for_render)
   SEQ_prefetch_stop(scene);
 
   for (seq = seqbase->first; seq; seq = seq->next) {
-    if (for_render && SEQ_time_strip_intersects_frame(scene, seq, CFRA)) {
+    if (for_render && SEQ_time_strip_intersects_frame(scene, seq, scene->r.cfra)) {
       continue;
     }
 
diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_target_props.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_target_props.c
index 1c257424297..80876dfd798 100644
--- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_target_props.c
+++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_target_props.c
@@ -343,7 +343,7 @@ void WM_gizmo_target_property_anim_autokey(bContext *C,
 {
   if (gz_prop->prop != NULL) {
     Scene *scene = CTX_data_scene(C);
-    const float cfra = (float)CFRA;
+    const float cfra = (float)scene->r.cfra;
     const int index = gz_prop->index == -1 ? 0 : gz_prop->index;
     ED_autokeyframe_property(C, scene, &gz_prop->ptr, gz_prop->prop, index, cfra, false);
   }
-- 
cgit v1.2.3


From e7c58941b1f1643c8bb997157eaacac1565fce7c Mon Sep 17 00:00:00 2001
From: Julian Eisel 
Date: Thu, 30 Jun 2022 18:42:22 +0200
Subject: Fix pointer to pointer passed where single pointer is expected (VSE
 versioning)

---
 source/blender/blenloader/intern/versioning_300.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c
index f6cc413d220..34b32ebc175 100644
--- a/source/blender/blenloader/intern/versioning_300.c
+++ b/source/blender/blenloader/intern/versioning_300.c
@@ -600,7 +600,7 @@ static void seq_speed_factor_fix_rna_path(Sequence *seq, ListBase *fcurves)
   char name_esc[(sizeof(seq->name) - 2) * 2];
   BLI_str_escape(name_esc, seq->name + 2, sizeof(name_esc));
   char *path = BLI_sprintfN("sequence_editor.sequences_all[\"%s\"].pitch", name_esc);
-  FCurve *fcu = BKE_fcurve_find(&fcurves, path, 0);
+  FCurve *fcu = BKE_fcurve_find(fcurves, path, 0);
   if (fcu != NULL) {
     MEM_freeN(fcu->rna_path);
     fcu->rna_path = BLI_sprintfN("sequence_editor.sequences_all[\"%s\"].speed_factor", name_esc);
-- 
cgit v1.2.3


From c9795102c2df3bb3f88e4dd3944383b6688e8f99 Mon Sep 17 00:00:00 2001
From: Julian Eisel 
Date: Thu, 30 Jun 2022 19:47:21 +0200
Subject: Fix build error with Alembic after 65166e145b4d

---
 source/blender/editors/gpencil/gpencil_trace_ops.c    |  2 +-
 source/blender/io/alembic/exporter/abc_export_capi.cc |  6 +++---
 source/blender/io/alembic/intern/alembic_capi.cc      | 12 ++++++------
 source/blender/io/usd/intern/usd_capi_export.cc       |  6 +++---
 4 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/source/blender/editors/gpencil/gpencil_trace_ops.c b/source/blender/editors/gpencil/gpencil_trace_ops.c
index 24a0dab24c9..f6e88e05d46 100644
--- a/source/blender/editors/gpencil/gpencil_trace_ops.c
+++ b/source/blender/editors/gpencil/gpencil_trace_ops.c
@@ -295,7 +295,7 @@ static int gpencil_trace_image_exec(bContext *C, wmOperator *op)
   job->base_active = CTX_data_active_base(C);
   job->ob_active = job->base_active->object;
   job->image = (Image *)job->ob_active->data;
-  job->frame_target = CFRA;
+  job->frame_target = scene->r.cfra;
   job->use_current_frame = RNA_boolean_get(op->ptr, "use_current_frame");
 
   /* Create a new grease pencil object or reuse selected. */
diff --git a/source/blender/io/alembic/exporter/abc_export_capi.cc b/source/blender/io/alembic/exporter/abc_export_capi.cc
index edaf53b3efa..5554fb505a4 100644
--- a/source/blender/io/alembic/exporter/abc_export_capi.cc
+++ b/source/blender/io/alembic/exporter/abc_export_capi.cc
@@ -85,7 +85,7 @@ static void export_startjob(void *customdata,
 
   /* For restoring the current frame after exporting animation is done. */
   Scene *scene = DEG_get_input_scene(data->depsgraph);
-  const int orig_frame = CFRA;
+  const int orig_frame = scene->r.cfra;
   const bool export_animation = (data->params.frame_start != data->params.frame_end);
 
   /* Create the Alembic archive. */
@@ -154,8 +154,8 @@ static void export_startjob(void *customdata,
   iter.release_writers();
 
   /* Finish up by going back to the keyframe that was current before we started. */
-  if (CFRA != orig_frame) {
-    CFRA = orig_frame;
+  if (scene->r.cfra != orig_frame) {
+    scene->r.cfra = orig_frame;
     BKE_scene_graph_update_for_newframe(data->depsgraph);
   }
 
diff --git a/source/blender/io/alembic/intern/alembic_capi.cc b/source/blender/io/alembic/intern/alembic_capi.cc
index 1fb535a57f2..cd6750341a8 100644
--- a/source/blender/io/alembic/intern/alembic_capi.cc
+++ b/source/blender/io/alembic/intern/alembic_capi.cc
@@ -526,14 +526,14 @@ static void import_startjob(void *user_data, short *stop, short *do_update, floa
     Scene *scene = data->scene;
 
     if (data->settings.is_sequence) {
-      SFRA = data->settings.sequence_offset;
-      EFRA = SFRA + (data->settings.sequence_len - 1);
-      CFRA = SFRA;
+      scene->r.sfra = data->settings.sequence_offset;
+      scene->r.efra = scene->r.sfra + (data->settings.sequence_len - 1);
+      scene->r.cfra = scene->r.sfra;
     }
     else if (min_time < max_time) {
-      SFRA = static_cast(round(min_time * FPS));
-      EFRA = static_cast(round(max_time * FPS));
-      CFRA = SFRA;
+      scene->r.sfra = static_cast(round(min_time * FPS));
+      scene->r.efra = static_cast(round(max_time * FPS));
+      scene->r.cfra = scene->r.sfra;
     }
   }
 
diff --git a/source/blender/io/usd/intern/usd_capi_export.cc b/source/blender/io/usd/intern/usd_capi_export.cc
index 2049c631671..d9995117b70 100644
--- a/source/blender/io/usd/intern/usd_capi_export.cc
+++ b/source/blender/io/usd/intern/usd_capi_export.cc
@@ -72,7 +72,7 @@ static void export_startjob(void *customdata,
   *do_update = true;
 
   /* For restoring the current frame after exporting animation is done. */
-  const int orig_frame = CFRA;
+  const int orig_frame = scene->r.cfra;
 
   pxr::UsdStageRefPtr usd_stage = pxr::UsdStage::CreateNew(data->filepath);
   if (!usd_stage) {
@@ -129,8 +129,8 @@ static void export_startjob(void *customdata,
   usd_stage->GetRootLayer()->Save();
 
   /* Finish up by going back to the keyframe that was current before we started. */
-  if (CFRA != orig_frame) {
-    CFRA = orig_frame;
+  if (scene->r.cfra != orig_frame) {
+    scene->r.cfra = orig_frame;
     BKE_scene_graph_update_for_newframe(data->depsgraph);
   }
 
-- 
cgit v1.2.3


From fbcc00d10df93bb167bf25248ae931edd5a23b87 Mon Sep 17 00:00:00 2001
From: Brecht Van Lommel 
Date: Thu, 30 Jun 2022 19:41:42 +0200
Subject: Fix broken Cycles performance benchmark after recent logging changes

Ensure full render report is printed with default verbosity.
---
 intern/cycles/session/session.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/intern/cycles/session/session.cpp b/intern/cycles/session/session.cpp
index ba30be817cf..e7de82a6e1b 100644
--- a/intern/cycles/session/session.cpp
+++ b/intern/cycles/session/session.cpp
@@ -146,11 +146,11 @@ void Session::run_main_render_loop()
     RenderWork render_work = run_update_for_next_iteration();
 
     if (!render_work) {
-      if (VLOG_WORK_IS_ON) {
+      if (VLOG_INFO_IS_ON) {
         double total_time, render_time;
         progress.get_time(total_time, render_time);
-        VLOG_WORK << "Rendering in main loop is done in " << render_time << " seconds.";
-        VLOG_WORK << path_trace_->full_report();
+        VLOG_INFO << "Rendering in main loop is done in " << render_time << " seconds.";
+        VLOG_INFO << path_trace_->full_report();
       }
 
       if (params.background) {
-- 
cgit v1.2.3


From a9696f04a012ce23ef419afe489378d1ce840638 Mon Sep 17 00:00:00 2001
From: Richard Antalik 
Date: Thu, 30 Jun 2022 20:41:09 +0200
Subject: Fix T90964: Strip can't be deleted if cursor is at bottom of timeline

Caused by hard-coded width of markers region causing operator to pass
through.

Execute operator if there are no markers.
---
 source/blender/editors/space_sequencer/sequencer_edit.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/source/blender/editors/space_sequencer/sequencer_edit.c b/source/blender/editors/space_sequencer/sequencer_edit.c
index 79eeb3c0374..cb95e9a75de 100644
--- a/source/blender/editors/space_sequencer/sequencer_edit.c
+++ b/source/blender/editors/space_sequencer/sequencer_edit.c
@@ -1703,8 +1703,10 @@ static int sequencer_delete_exec(bContext *C, wmOperator *op)
 static int sequencer_delete_invoke(bContext *C, wmOperator *op, const wmEvent *event)
 {
   ARegion *region = CTX_wm_region(C);
+  Scene *scene = CTX_data_scene(C);
+  ListBase *markers = &scene->markers;
 
-  if (region->regiontype == RGN_TYPE_WINDOW) {
+  if (region->regiontype == RGN_TYPE_WINDOW && !BLI_listbase_is_empty(markers)) {
     /* Bounding box of 30 pixels is used for markers shortcuts,
      * prevent conflict with markers shortcuts here.
      */
-- 
cgit v1.2.3


From f18067aa032a35e8a6cc990b5c250567d0f4d78f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= 
Date: Tue, 28 Jun 2022 18:33:25 +0200
Subject: EEVEE-Next: Add Film and RenderBuffers module

This modules handles renderpasses allocation and filling. Also handles
blitting to viewport framebuffer and render result reading.

Changes against the old implementation:
- the filling of the renderpasses happens all at once requiring
  only 1 geometry pass.
- The filtering is optimized with weights precomputed on CPU and
  reuse of neighboor pixels.
- Only one accumulation buffer for renderpasses (no ping-pong).
- Accumulation happens in one pass for every passes using a single
  dispatch or fullscreen triangle pass.

TAA and history reprojection is not yet implemented.
AOVs support is present but with a 16 AOV limit for now.
Cryptomatte is not yet implemented.
---
 release/scripts/startup/bl_ui/properties_render.py |  45 ++
 release/scripts/startup/bl_ui/properties_world.py  |   8 +-
 source/blender/draw/CMakeLists.txt                 |   6 +
 .../draw/engines/eevee_next/eevee_camera.cc        |  11 +-
 .../draw/engines/eevee_next/eevee_camera.hh        |   3 +-
 .../draw/engines/eevee_next/eevee_defines.hh       |   2 +
 .../draw/engines/eevee_next/eevee_engine.cc        |  22 +-
 .../blender/draw/engines/eevee_next/eevee_film.cc  | 579 +++++++++++++++++++++
 .../blender/draw/engines/eevee_next/eevee_film.hh  | 187 +++++++
 .../draw/engines/eevee_next/eevee_instance.cc      |  83 ++-
 .../draw/engines/eevee_next/eevee_instance.hh      |  29 +-
 .../draw/engines/eevee_next/eevee_material.cc      |   2 +-
 .../draw/engines/eevee_next/eevee_pipeline.cc      |  28 +
 .../draw/engines/eevee_next/eevee_renderbuffers.cc |  96 ++++
 .../draw/engines/eevee_next/eevee_renderbuffers.hh |  57 ++
 .../draw/engines/eevee_next/eevee_sampling.cc      | 264 ++++++++++
 .../draw/engines/eevee_next/eevee_sampling.hh      | 172 ++++++
 .../draw/engines/eevee_next/eevee_shader.cc        |   5 +-
 .../draw/engines/eevee_next/eevee_shader.hh        |   5 +-
 .../draw/engines/eevee_next/eevee_shader_shared.hh | 190 ++++++-
 .../blender/draw/engines/eevee_next/eevee_sync.cc  |  16 +-
 .../draw/engines/eevee_next/eevee_velocity.cc      |   4 +-
 .../blender/draw/engines/eevee_next/eevee_view.cc  |  76 ++-
 .../blender/draw/engines/eevee_next/eevee_view.hh  |  19 +-
 .../blender/draw/engines/eevee_next/eevee_world.cc |   2 +-
 .../eevee_next/shaders/eevee_film_comp.glsl        |  13 +
 .../eevee_next/shaders/eevee_film_frag.glsl        |  29 ++
 .../engines/eevee_next/shaders/eevee_film_lib.glsl | 387 ++++++++++++++
 .../eevee_next/shaders/eevee_nodetree_lib.glsl     |  14 +
 .../shaders/eevee_surf_forward_frag.glsl           |  44 +-
 .../eevee_next/shaders/eevee_surf_world_frag.glsl  |   8 +
 .../eevee_next/shaders/infos/eevee_film_info.hh    |  44 ++
 .../shaders/infos/eevee_material_info.hh           |  55 +-
 .../shaders/infos/eevee_velocity_info.hh           |   1 +
 source/blender/gpu/CMakeLists.txt                  |   4 +
 source/blender/gpu/GPU_shader_shared_utils.h       |   1 +
 source/blender/makesdna/DNA_layer_types.h          |   1 +
 37 files changed, 2395 insertions(+), 117 deletions(-)
 create mode 100644 source/blender/draw/engines/eevee_next/eevee_film.cc
 create mode 100644 source/blender/draw/engines/eevee_next/eevee_film.hh
 create mode 100644 source/blender/draw/engines/eevee_next/eevee_renderbuffers.cc
 create mode 100644 source/blender/draw/engines/eevee_next/eevee_renderbuffers.hh
 create mode 100644 source/blender/draw/engines/eevee_next/eevee_sampling.cc
 create mode 100644 source/blender/draw/engines/eevee_next/eevee_sampling.hh
 create mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_film_comp.glsl
 create mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_film_frag.glsl
 create mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl
 create mode 100644 source/blender/draw/engines/eevee_next/shaders/infos/eevee_film_info.hh

diff --git a/release/scripts/startup/bl_ui/properties_render.py b/release/scripts/startup/bl_ui/properties_render.py
index c20992166ad..148dd3ce22d 100644
--- a/release/scripts/startup/bl_ui/properties_render.py
+++ b/release/scripts/startup/bl_ui/properties_render.py
@@ -390,6 +390,27 @@ class RENDER_PT_eevee_sampling(RenderButtonsPanel, Panel):
         col.prop(props, "use_taa_reprojection")
 
 
+class RENDER_PT_eevee_next_sampling(RenderButtonsPanel, Panel):
+    bl_label = "Sampling"
+    COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'}
+
+    @classmethod
+    def poll(cls, context):
+        return (context.engine in cls.COMPAT_ENGINES)
+
+    def draw(self, context):
+        layout = self.layout
+        layout.use_property_split = True
+        layout.use_property_decorate = False  # No animation.
+
+        scene = context.scene
+        props = scene.eevee
+
+        col = layout.column(align=True)
+        col.prop(props, "taa_render_samples", text="Render")
+        col.prop(props, "taa_samples", text="Viewport")
+
+
 class RENDER_PT_eevee_indirect_lighting(RenderButtonsPanel, Panel):
     bl_label = "Indirect Lighting"
     bl_options = {'DEFAULT_CLOSED'}
@@ -482,6 +503,27 @@ class RENDER_PT_eevee_film(RenderButtonsPanel, Panel):
         sub.prop(props, "overscan_size", text="")
 
 
+class RENDER_PT_eevee_next_film(RenderButtonsPanel, Panel):
+    bl_label = "Film"
+    bl_options = {'DEFAULT_CLOSED'}
+    COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'}
+
+    @classmethod
+    def poll(cls, context):
+        return (context.engine in cls.COMPAT_ENGINES)
+
+    def draw(self, context):
+        layout = self.layout
+        layout.use_property_split = True
+
+        scene = context.scene
+        rd = scene.render
+        props = scene.eevee
+
+        col = layout.column()
+        col.prop(rd, "filter_size")
+
+
 def draw_curves_settings(self, context):
     layout = self.layout
     scene = context.scene
@@ -702,6 +744,9 @@ classes = (
     RENDER_PT_eevee_indirect_lighting_display,
     RENDER_PT_eevee_film,
 
+    RENDER_PT_eevee_next_sampling,
+    RENDER_PT_eevee_next_film,
+
     RENDER_PT_gpencil,
     RENDER_PT_opengl_sampling,
     RENDER_PT_opengl_lighting,
diff --git a/release/scripts/startup/bl_ui/properties_world.py b/release/scripts/startup/bl_ui/properties_world.py
index 34186785f2c..b0ea36abd6b 100644
--- a/release/scripts/startup/bl_ui/properties_world.py
+++ b/release/scripts/startup/bl_ui/properties_world.py
@@ -19,7 +19,7 @@ class WorldButtonsPanel:
 class WORLD_PT_context_world(WorldButtonsPanel, Panel):
     bl_label = ""
     bl_options = {'HIDE_HEADER'}
-    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
+    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'}
 
     @classmethod
     def poll(cls, context):
@@ -41,7 +41,7 @@ class WORLD_PT_context_world(WorldButtonsPanel, Panel):
 class EEVEE_WORLD_PT_mist(WorldButtonsPanel, Panel):
     bl_label = "Mist Pass"
     bl_options = {'DEFAULT_CLOSED'}
-    COMPAT_ENGINES = {'BLENDER_EEVEE'}
+    COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT'}
 
     @classmethod
     def poll(cls, context):
@@ -63,14 +63,14 @@ class EEVEE_WORLD_PT_mist(WorldButtonsPanel, Panel):
 
 
 class WORLD_PT_custom_props(WorldButtonsPanel, PropertyPanel, Panel):
-    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
+    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'}
     _context_path = "world"
     _property_type = bpy.types.World
 
 
 class EEVEE_WORLD_PT_surface(WorldButtonsPanel, Panel):
     bl_label = "Surface"
-    COMPAT_ENGINES = {'BLENDER_EEVEE'}
+    COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT'}
 
     @classmethod
     def poll(cls, context):
diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt
index 9cb3743dd02..f4af9e242d3 100644
--- a/source/blender/draw/CMakeLists.txt
+++ b/source/blender/draw/CMakeLists.txt
@@ -136,9 +136,12 @@ set(SRC
   engines/eevee/eevee_volumes.c
   engines/eevee_next/eevee_camera.cc
   engines/eevee_next/eevee_engine.cc
+  engines/eevee_next/eevee_film.cc
   engines/eevee_next/eevee_instance.cc
   engines/eevee_next/eevee_material.cc
   engines/eevee_next/eevee_pipeline.cc
+  engines/eevee_next/eevee_renderbuffers.cc
+  engines/eevee_next/eevee_sampling.cc
   engines/eevee_next/eevee_shader.cc
   engines/eevee_next/eevee_sync.cc
   engines/eevee_next/eevee_velocity.cc
@@ -357,6 +360,9 @@ set(GLSL_SRC
 
   engines/eevee_next/shaders/eevee_attributes_lib.glsl
   engines/eevee_next/shaders/eevee_camera_lib.glsl
+  engines/eevee_next/shaders/eevee_film_comp.glsl
+  engines/eevee_next/shaders/eevee_film_frag.glsl
+  engines/eevee_next/shaders/eevee_film_lib.glsl
   engines/eevee_next/shaders/eevee_geom_curves_vert.glsl
   engines/eevee_next/shaders/eevee_geom_gpencil_vert.glsl
   engines/eevee_next/shaders/eevee_geom_mesh_vert.glsl
diff --git a/source/blender/draw/engines/eevee_next/eevee_camera.cc b/source/blender/draw/engines/eevee_next/eevee_camera.cc
index e6d2e2db764..1f65f887d46 100644
--- a/source/blender/draw/engines/eevee_next/eevee_camera.cc
+++ b/source/blender/draw/engines/eevee_next/eevee_camera.cc
@@ -77,9 +77,10 @@ void Camera::init()
 void Camera::sync()
 {
   const Object *camera_eval = inst_.camera_eval_object;
-  CameraData &data = data_.current();
 
-  data.filter_size = inst_.scene->r.gauss;
+  data_.swap();
+
+  CameraData &data = data_.current();
 
   if (inst_.drw_view) {
     DRW_view_viewmat_get(inst_.drw_view, data.viewmat.ptr(), false);
@@ -127,6 +128,10 @@ void Camera::sync()
     data.equirect_scale *= data.uv_scale;
 
     data.equirect_scale_inv = 1.0f / data.equirect_scale;
+#else
+    data.fisheye_fov = data.fisheye_lens = -1.0f;
+    data.equirect_bias = float2(0.0f);
+    data.equirect_scale = float2(0.0f);
 #endif
   }
   else if (inst_.drw_view) {
@@ -143,7 +148,7 @@ void Camera::sync()
 
   /* Detect changes in parameters. */
   if (data_.current() != data_.previous()) {
-    // inst_.sampling.reset();
+    inst_.sampling.reset();
   }
 }
 
diff --git a/source/blender/draw/engines/eevee_next/eevee_camera.hh b/source/blender/draw/engines/eevee_next/eevee_camera.hh
index dfec738b1f3..3b3586190c4 100644
--- a/source/blender/draw/engines/eevee_next/eevee_camera.hh
+++ b/source/blender/draw/engines/eevee_next/eevee_camera.hh
@@ -61,8 +61,7 @@ inline bool operator==(const CameraData &a, const CameraData &b)
   return compare_m4m4(a.persmat.ptr(), b.persmat.ptr(), FLT_MIN) && (a.uv_scale == b.uv_scale) &&
          (a.uv_bias == b.uv_bias) && (a.equirect_scale == b.equirect_scale) &&
          (a.equirect_bias == b.equirect_bias) && (a.fisheye_fov == b.fisheye_fov) &&
-         (a.fisheye_lens == b.fisheye_lens) && (a.filter_size == b.filter_size) &&
-         (a.type == b.type);
+         (a.fisheye_lens == b.fisheye_lens) && (a.type == b.type);
 }
 
 inline bool operator!=(const CameraData &a, const CameraData &b)
diff --git a/source/blender/draw/engines/eevee_next/eevee_defines.hh b/source/blender/draw/engines/eevee_next/eevee_defines.hh
index f75ebd2bd13..1e7979b594e 100644
--- a/source/blender/draw/engines/eevee_next/eevee_defines.hh
+++ b/source/blender/draw/engines/eevee_next/eevee_defines.hh
@@ -43,3 +43,5 @@
 
 /* Minimum visibility size. */
 #define LIGHTPROBE_FILTER_VIS_GROUP_SIZE 16
+
+#define FILM_GROUP_SIZE 16
diff --git a/source/blender/draw/engines/eevee_next/eevee_engine.cc b/source/blender/draw/engines/eevee_next/eevee_engine.cc
index be0adfad568..37b4bde324e 100644
--- a/source/blender/draw/engines/eevee_next/eevee_engine.cc
+++ b/source/blender/draw/engines/eevee_next/eevee_engine.cc
@@ -12,6 +12,8 @@
 
 #include "DRW_render.h"
 
+#include "RE_pipeline.h"
+
 #include "eevee_engine.h" /* Own include. */
 
 #include "eevee_instance.hh"
@@ -97,6 +99,8 @@ static void eevee_draw_scene(void *vedata)
   DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get();
   ved->instance->draw_viewport(dfbl);
   STRNCPY(ved->info, ved->instance->info.c_str());
+  /* Reset view for other following engines. */
+  DRW_view_set_active(nullptr);
 }
 
 static void eevee_cache_init(void *vedata)
@@ -144,7 +148,23 @@ static void eevee_render_to_image(void *UNUSED(vedata),
   if (!GPU_shader_storage_buffer_objects_support()) {
     return;
   }
-  UNUSED_VARS(engine, layer);
+
+  eevee::Instance *instance = new eevee::Instance();
+
+  Render *render = engine->re;
+  Depsgraph *depsgraph = DRW_context_state_get()->depsgraph;
+  Object *camera_original_ob = RE_GetCamera(engine->re);
+  const char *viewname = RE_GetActiveRenderView(engine->re);
+  int size[2] = {engine->resolution_x, engine->resolution_y};
+
+  rctf view_rect;
+  rcti rect;
+  RE_GetViewPlane(render, &view_rect, &rect);
+
+  instance->init(size, &rect, engine, depsgraph, nullptr, camera_original_ob, layer);
+  instance->render_frame(layer, viewname);
+
+  delete instance;
 }
 
 static void eevee_render_update_passes(RenderEngine *engine, Scene *scene, ViewLayer *view_layer)
diff --git a/source/blender/draw/engines/eevee_next/eevee_film.cc b/source/blender/draw/engines/eevee_next/eevee_film.cc
new file mode 100644
index 00000000000..44737018c62
--- /dev/null
+++ b/source/blender/draw/engines/eevee_next/eevee_film.cc
@@ -0,0 +1,579 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2021 Blender Foundation.
+ */
+
+/** \file
+ * \ingroup eevee
+ *
+ * A film is a fullscreen buffer (usually at output extent)
+ * that will be able to accumulate sample in any distorted camera_type
+ * using a pixel filter.
+ *
+ * Input needs to be jittered so that the filter converges to the right result.
+ */
+
+#include "BLI_hash.h"
+#include "BLI_rect.h"
+
+#include "GPU_framebuffer.h"
+#include "GPU_texture.h"
+
+#include "DRW_render.h"
+#include "RE_pipeline.h"
+
+#include "eevee_film.hh"
+#include "eevee_instance.hh"
+
+namespace blender::eevee {
+
+ENUM_OPERATORS(eViewLayerEEVEEPassType, 1 << EEVEE_RENDER_PASS_MAX_BIT)
+
+/* -------------------------------------------------------------------- */
+/** \name Arbitrary Output Variables
+ * \{ */
+
+void Film::init_aovs()
+{
+  Vector aovs;
+
+  aovs_info.display_id = -1;
+  aovs_info.display_is_value = false;
+  aovs_info.value_len = aovs_info.color_len = 0;
+
+  if (inst_.is_viewport()) {
+    /* Viewport case. */
+    if (inst_.v3d->shading.render_pass == EEVEE_RENDER_PASS_AOV) {
+      /* AOV display, request only a single AOV. */
+      ViewLayerAOV *aov = (ViewLayerAOV *)BLI_findstring(
+          &inst_.view_layer->aovs, inst_.v3d->shading.aov_name, offsetof(ViewLayerAOV, name));
+
+      if (aov == nullptr) {
+        /* AOV not found in view layer. */
+        return;
+      }
+
+      aovs.append(aov);
+      aovs_info.display_id = 0;
+      aovs_info.display_is_value = (aov->type == AOV_TYPE_VALUE);
+    }
+    else {
+      /* TODO(fclem): The realtime compositor could ask for several AOVs. */
+    }
+  }
+  else {
+    /* Render case. */
+    LISTBASE_FOREACH (ViewLayerAOV *, aov, &inst_.view_layer->aovs) {
+      aovs.append(aov);
+    }
+  }
+
+  if (aovs.size() > AOV_MAX) {
+    inst_.info = "Error: Too many AOVs";
+    return;
+  }
+
+  for (ViewLayerAOV *aov : aovs) {
+    bool is_value = (aov->type == AOV_TYPE_VALUE);
+    uint &index = is_value ? aovs_info.value_len : aovs_info.color_len;
+    uint &hash = is_value ? aovs_info.hash_value[index] : aovs_info.hash_color[index];
+    hash = BLI_hash_string(aov->name);
+    index++;
+  }
+}
+
+float *Film::read_aov(ViewLayerAOV *aov)
+{
+  bool is_value = (aov->type == AOV_TYPE_VALUE);
+  Texture &accum_tx = is_value ? value_accum_tx_ : color_accum_tx_;
+
+  Span aovs_hash(is_value ? aovs_info.hash_value : aovs_info.hash_color,
+                       is_value ? aovs_info.value_len : aovs_info.color_len);
+  /* Find AOV index. */
+  uint hash = BLI_hash_string(aov->name);
+  int aov_index = -1;
+  int i = 0;
+  for (uint candidate_hash : aovs_hash) {
+    if (candidate_hash == hash) {
+      aov_index = i;
+      break;
+    }
+    i++;
+  }
+
+  accum_tx.ensure_layer_views();
+
+  int index = aov_index + (is_value ? data_.aov_value_id : data_.aov_color_id);
+  GPUTexture *pass_tx = accum_tx.layer_view(index);
+
+  return (float *)GPU_texture_read(pass_tx, GPU_DATA_FLOAT, 0);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Mist Pass
+ * \{ */
+
+void Film::sync_mist()
+{
+  const CameraData &cam = inst_.camera.data_get();
+  const ::World *world = inst_.scene->world;
+  float mist_start = world ? world->miststa : cam.clip_near;
+  float mist_distance = world ? world->mistdist : fabsf(cam.clip_far - cam.clip_near);
+  int mist_type = world ? world->mistype : WO_MIST_LINEAR;
+
+  switch (mist_type) {
+    case WO_MIST_QUADRATIC:
+      data_.mist_exponent = 2.0f;
+      break;
+    case WO_MIST_LINEAR:
+      data_.mist_exponent = 1.0f;
+      break;
+    case WO_MIST_INVERSE_QUADRATIC:
+      data_.mist_exponent = 0.5f;
+      break;
+  }
+
+  data_.mist_scale = 1.0 / mist_distance;
+  data_.mist_bias = -mist_start / mist_distance;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name FilmData
+ * \{ */
+
+inline bool operator==(const FilmData &a, const FilmData &b)
+{
+  return (a.extent == b.extent) && (a.offset == b.offset) && (a.filter_size == b.filter_size) &&
+         (a.scaling_factor == b.scaling_factor);
+}
+
+inline bool operator!=(const FilmData &a, const FilmData &b)
+{
+  return !(a == b);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Film
+ * \{ */
+
+void Film::init(const int2 &extent, const rcti *output_rect)
+{
+  init_aovs();
+
+  {
+    /* Enable passes that need to be rendered. */
+    eViewLayerEEVEEPassType render_passes;
+
+    if (inst_.is_viewport()) {
+      /* Viewport Case. */
+      render_passes = eViewLayerEEVEEPassType(inst_.v3d->shading.render_pass);
+
+      if (inst_.overlays_enabled() || inst_.gpencil_engine_enabled) {
+        /* Overlays and Grease Pencil needs the depth for correct compositing.
+         * Using the render pass ensure we store the center depth. */
+        render_passes |= EEVEE_RENDER_PASS_Z;
+      }
+    }
+    else {
+      /* Render Case. */
+      render_passes = eViewLayerEEVEEPassType(inst_.view_layer->eevee.render_passes);
+
+      render_passes |= EEVEE_RENDER_PASS_COMBINED;
+
+#define ENABLE_FROM_LEGACY(name_legacy, name_eevee) \
+  SET_FLAG_FROM_TEST(render_passes, \
+                     (inst_.view_layer->passflag & SCE_PASS_##name_legacy) != 0, \
+                     EEVEE_RENDER_PASS_##name_eevee);
+
+      ENABLE_FROM_LEGACY(Z, Z)
+      ENABLE_FROM_LEGACY(MIST, MIST)
+      ENABLE_FROM_LEGACY(NORMAL, NORMAL)
+      ENABLE_FROM_LEGACY(SHADOW, SHADOW)
+      ENABLE_FROM_LEGACY(AO, AO)
+      ENABLE_FROM_LEGACY(EMIT, EMIT)
+      ENABLE_FROM_LEGACY(ENVIRONMENT, ENVIRONMENT)
+      ENABLE_FROM_LEGACY(DIFFUSE_COLOR, DIFFUSE_COLOR)
+      ENABLE_FROM_LEGACY(GLOSSY_COLOR, SPECULAR_COLOR)
+      ENABLE_FROM_LEGACY(DIFFUSE_DIRECT, DIFFUSE_LIGHT)
+      ENABLE_FROM_LEGACY(GLOSSY_DIRECT, SPECULAR_LIGHT)
+      ENABLE_FROM_LEGACY(ENVIRONMENT, ENVIRONMENT)
+
+#undef ENABLE_FROM_LEGACY
+    }
+
+    /* Filter obsolete passes. */
+    render_passes &= ~(EEVEE_RENDER_PASS_UNUSED_8 | EEVEE_RENDER_PASS_BLOOM);
+
+    /* TODO(@fclem): Can't we rely on depsgraph update notification? */
+    if (assign_if_different(enabled_passes_, render_passes)) {
+      inst_.sampling.reset();
+    }
+  }
+  {
+    rcti fallback_rect;
+    if (BLI_rcti_is_empty(output_rect)) {
+      BLI_rcti_init(&fallback_rect, 0, extent[0], 0, extent[1]);
+      output_rect = &fallback_rect;
+    }
+
+    FilmData data = data_;
+    data.extent = int2(BLI_rcti_size_x(output_rect), BLI_rcti_size_y(output_rect));
+    data.offset = int2(output_rect->xmin, output_rect->ymin);
+    data.filter_size = clamp_f(inst_.scene->r.gauss, 0.0f, 100.0f);
+    /* TODO(fclem): parameter hidden in experimental.
+     * We need to figure out LOD bias first in order to preserve texture crispiness. */
+    data.scaling_factor = 1;
+
+    FilmData &data_prev_ = data_;
+    if (assign_if_different(data_prev_, data)) {
+      inst_.sampling.reset();
+    }
+
+    const eViewLayerEEVEEPassType data_passes = EEVEE_RENDER_PASS_Z | EEVEE_RENDER_PASS_NORMAL |
+                                                EEVEE_RENDER_PASS_VECTOR;
+    const eViewLayerEEVEEPassType color_passes_1 = EEVEE_RENDER_PASS_DIFFUSE_LIGHT |
+                                                   EEVEE_RENDER_PASS_SPECULAR_LIGHT |
+                                                   EEVEE_RENDER_PASS_VOLUME_LIGHT |
+                                                   EEVEE_RENDER_PASS_EMIT;
+    const eViewLayerEEVEEPassType color_passes_2 = EEVEE_RENDER_PASS_DIFFUSE_COLOR |
+                                                   EEVEE_RENDER_PASS_SPECULAR_COLOR |
+                                                   EEVEE_RENDER_PASS_ENVIRONMENT |
+                                                   EEVEE_RENDER_PASS_MIST |
+                                                   EEVEE_RENDER_PASS_SHADOW | EEVEE_RENDER_PASS_AO;
+
+    data_.exposure = 1.0f /* TODO */;
+    data_.has_data = (enabled_passes_ & data_passes) != 0;
+    data_.any_render_pass_1 = (enabled_passes_ & color_passes_1) != 0;
+    data_.any_render_pass_2 = (enabled_passes_ & color_passes_2) != 0;
+  }
+  {
+    /* Set pass offsets.  */
+
+    data_.display_id = aovs_info.display_id;
+    data_.display_is_value = aovs_info.display_is_value;
+
+    /* Combined is in a separate buffer. */
+    data_.combined_id = (enabled_passes_ & EEVEE_RENDER_PASS_COMBINED) ? 0 : -1;
+    /* Depth is in a separate buffer. */
+    data_.depth_id = (enabled_passes_ & EEVEE_RENDER_PASS_Z) ? 0 : -1;
+
+    data_.color_len = 0;
+    data_.value_len = 0;
+
+    auto pass_index_get = [&](eViewLayerEEVEEPassType pass_type) {
+      bool is_value = pass_is_value(pass_type);
+      int index = (enabled_passes_ & pass_type) ?
+                      (is_value ? data_.value_len : data_.color_len)++ :
+                      -1;
+      if (inst_.is_viewport() && inst_.v3d->shading.render_pass == pass_type) {
+        data_.display_id = index;
+        data_.display_is_value = is_value;
+      }
+      return index;
+    };
+
+    data_.mist_id = pass_index_get(EEVEE_RENDER_PASS_MIST);
+    data_.normal_id = pass_index_get(EEVEE_RENDER_PASS_NORMAL);
+    data_.vector_id = pass_index_get(EEVEE_RENDER_PASS_VECTOR);
+    data_.diffuse_light_id = pass_index_get(EEVEE_RENDER_PASS_DIFFUSE_LIGHT);
+    data_.diffuse_color_id = pass_index_get(EEVEE_RENDER_PASS_DIFFUSE_COLOR);
+    data_.specular_light_id = pass_index_get(EEVEE_RENDER_PASS_SPECULAR_LIGHT);
+    data_.specular_color_id = pass_index_get(EEVEE_RENDER_PASS_SPECULAR_COLOR);
+    data_.volume_light_id = pass_index_get(EEVEE_RENDER_PASS_VOLUME_LIGHT);
+    data_.emission_id = pass_index_get(EEVEE_RENDER_PASS_EMIT);
+    data_.environment_id = pass_index_get(EEVEE_RENDER_PASS_ENVIRONMENT);
+    data_.shadow_id = pass_index_get(EEVEE_RENDER_PASS_SHADOW);
+    data_.ambient_occlusion_id = pass_index_get(EEVEE_RENDER_PASS_AO);
+
+    data_.aov_color_id = data_.color_len;
+    data_.aov_value_id = data_.value_len;
+
+    data_.aov_color_len = aovs_info.color_len;
+    data_.aov_value_len = aovs_info.value_len;
+
+    data_.color_len += data_.aov_color_len;
+    data_.value_len += data_.aov_value_len;
+  }
+  {
+    /* TODO(fclem): Overscans. */
+
+    render_extent_ = math::divide_ceil(extent, int2(data_.scaling_factor));
+    int2 weight_extent = inst_.camera.is_panoramic() ? data_.extent : int2(data_.scaling_factor);
+
+    eGPUTextureFormat color_format = GPU_RGBA16F;
+    eGPUTextureFormat float_format = GPU_R16F;
+    eGPUTextureFormat weight_format = GPU_R32F;
+    eGPUTextureFormat depth_format = GPU_R32F;
+
+    int reset = 0;
+    reset += depth_tx_.ensure_2d(depth_format, data_.extent);
+    reset += combined_tx_.current().ensure_2d(color_format, data_.extent);
+    reset += combined_tx_.next().ensure_2d(color_format, data_.extent);
+    /* Two layers, one for nearest sample weight and one for weight accumulation. */
+    reset += weight_tx_.current().ensure_2d_array(weight_format, weight_extent, 2);
+    reset += weight_tx_.next().ensure_2d_array(weight_format, weight_extent, 2);
+    reset += color_accum_tx_.ensure_2d_array(color_format,
+                                             (data_.color_len > 0) ? data_.extent : int2(1),
+                                             (data_.color_len > 0) ? data_.color_len : 1);
+    reset += value_accum_tx_.ensure_2d_array(float_format,
+                                             (data_.value_len > 0) ? data_.extent : int2(1),
+                                             (data_.value_len > 0) ? data_.value_len : 1);
+
+    if (reset > 0) {
+      inst_.sampling.reset();
+      data_.use_history = 0;
+      data_.use_reprojection = 0;
+
+      /* Avoid NaN in uninitialized texture memory making history blending dangerous. */
+      color_accum_tx_.clear(float4(0.0f));
+      value_accum_tx_.clear(float4(0.0f));
+      combined_tx_.current().clear(float4(0.0f));
+      weight_tx_.current().clear(float4(0.0f));
+      depth_tx_.clear(float4(0.0f));
+    }
+  }
+}
+
+void Film::sync()
+{
+  /* We use a fragment shader for viewport because we need to output the depth. */
+  bool use_compute = (inst_.is_viewport() == false);
+
+  eShaderType shader = use_compute ? FILM_COMP : FILM_FRAG;
+
+  /* TODO(fclem): Shader variation for panoramic & scaled resolution. */
+
+  RenderBuffers &rbuffers = inst_.render_buffers;
+
+  DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_ALWAYS;
+  accumulate_ps_ = DRW_pass_create("Film.Accumulate", state);
+  GPUShader *sh = inst_.shaders.static_shader_get(shader);
+  DRWShadingGroup *grp = DRW_shgroup_create(sh, accumulate_ps_);
+  DRW_shgroup_uniform_block_ref(grp, "film_buf", &data_);
+  DRW_shgroup_uniform_texture_ref(grp, "depth_tx", &rbuffers.depth_tx);
+  DRW_shgroup_uniform_texture_ref(grp, "combined_tx", &rbuffers.combined_tx);
+  DRW_shgroup_uniform_texture_ref(grp, "normal_tx", &rbuffers.normal_tx);
+  DRW_shgroup_uniform_texture_ref(grp, "vector_tx", &rbuffers.vector_tx);
+  DRW_shgroup_uniform_texture_ref(grp, "diffuse_light_tx", &rbuffers.diffuse_light_tx);
+  DRW_shgroup_uniform_texture_ref(grp, "diffuse_color_tx", &rbuffers.diffuse_color_tx);
+  DRW_shgroup_uniform_texture_ref(grp, "specular_light_tx", &rbuffers.specular_light_tx);
+  DRW_shgroup_uniform_texture_ref(grp, "specular_color_tx", &rbuffers.specular_color_tx);
+  DRW_shgroup_uniform_texture_ref(grp, "volume_light_tx", &rbuffers.volume_light_tx);
+  DRW_shgroup_uniform_texture_ref(grp, "emission_tx", &rbuffers.emission_tx);
+  DRW_shgroup_uniform_texture_ref(grp, "environment_tx", &rbuffers.environment_tx);
+  DRW_shgroup_uniform_texture_ref(grp, "shadow_tx", &rbuffers.shadow_tx);
+  DRW_shgroup_uniform_texture_ref(grp, "ambient_occlusion_tx", &rbuffers.ambient_occlusion_tx);
+  DRW_shgroup_uniform_texture_ref(grp, "aov_color_tx", &rbuffers.aov_color_tx);
+  DRW_shgroup_uniform_texture_ref(grp, "aov_value_tx", &rbuffers.aov_value_tx);
+  /* NOTE(@fclem): 16 is the max number of sampled texture in many implementations.
+   * If we need more, we need to pack more of the similar passes in the same textures as arrays or
+   * use image binding instead. */
+  DRW_shgroup_uniform_image_ref(grp, "in_weight_img", &weight_tx_.current());
+  DRW_shgroup_uniform_image_ref(grp, "out_weight_img", &weight_tx_.next());
+  DRW_shgroup_uniform_image_ref(grp, "in_combined_img", &combined_tx_.current());
+  DRW_shgroup_uniform_image_ref(grp, "out_combined_img", &combined_tx_.next());
+  DRW_shgroup_uniform_image_ref(grp, "depth_img", &depth_tx_);
+  DRW_shgroup_uniform_image_ref(grp, "color_accum_img", &color_accum_tx_);
+  DRW_shgroup_uniform_image_ref(grp, "value_accum_img", &value_accum_tx_);
+  /* Sync with rendering passes. */
+  DRW_shgroup_barrier(grp, GPU_BARRIER_TEXTURE_FETCH);
+  /* Sync with rendering passes. */
+  DRW_shgroup_barrier(grp, GPU_BARRIER_SHADER_IMAGE_ACCESS);
+  if (use_compute) {
+    int2 dispatch_size = math::divide_ceil(data_.extent, int2(FILM_GROUP_SIZE));
+    DRW_shgroup_call_compute(grp, UNPACK2(dispatch_size), 1);
+  }
+  else {
+    DRW_shgroup_call_procedural_triangles(grp, nullptr, 1);
+  }
+}
+
+void Film::end_sync()
+{
+  if (inst_.sampling.is_reset()) {
+    data_.use_history = 0;
+  }
+
+  // if (camera.changed_type) {
+  //   data_.use_reprojection = false;
+  // }
+
+  aovs_info.push_update();
+
+  sync_mist();
+}
+
+float2 Film::pixel_jitter_get() const
+{
+  float2 jitter = inst_.sampling.rng_2d_get(SAMPLING_FILTER_U);
+
+  if (data_.filter_size < M_SQRT1_2 && !inst_.camera.is_panoramic()) {
+    /* For filter size less than a pixel, change sampling strategy and use a uniform disk
+     * distribution covering the filter shape. This avoids putting samples in areas without any
+     * weights. */
+    /* TODO(fclem): Importance sampling could be a better option here. */
+    jitter = Sampling::sample_disk(jitter) * data_.filter_size;
+  }
+  else {
+    /* Jitter the size of a whole pixel. */
+    jitter = jitter * 2.0f - 1.0f;
+  }
+  /* TODO(fclem): Mixed-resolution rendering: We need to offset to each of the target pixel covered
+   * by a render pixel, ideally, by choosing one randomly using another sampling dimension, or by
+   * repeating the same sample RNG sequence for each pixel offset. */
+  return jitter;
+}
+
+void Film::update_sample_table()
+{
+  data_.subpixel_offset = pixel_jitter_get();
+
+  int filter_size_ceil = ceilf(data_.filter_size);
+  float filter_size_sqr = square_f(data_.filter_size);
+
+  data_.samples_len = 0;
+  if (data_.filter_size < 0.01f) {
+    /* Disable filtering. */
+    data_.samples[0].texel = int2(0, 0);
+    data_.samples[0].weight = 1.0f;
+    data_.samples_weight_total = 1.0f;
+    data_.samples_len = 1;
+  }
+  /* NOTE: Threshold determined by hand until we don't hit the assert bellow. */
+  else if (data_.filter_size < 2.20f) {
+    /* Small filter Size. */
+    int closest_index = 0;
+    float closest_distance = FLT_MAX;
+    data_.samples_weight_total = 0.0f;
+    /* TODO(fclem): For optimization, could try Z-tile ordering. */
+    for (int y = -filter_size_ceil; y <= filter_size_ceil; y++) {
+      for (int x = -filter_size_ceil; x <= filter_size_ceil; x++) {
+        float2 pixel_offset = float2(x, y) - data_.subpixel_offset;
+        float distance_sqr = math::length_squared(pixel_offset);
+        if (distance_sqr < filter_size_sqr) {
+          if (data_.samples_len >= FILM_PRECOMP_SAMPLE_MAX) {
+            BLI_assert_msg(0, "Precomputed sample table is too small.");
+            break;
+          }
+          FilmSample &sample = data_.samples[data_.samples_len];
+          sample.texel = int2(x, y);
+          sample.weight = film_filter_weight(data_.filter_size, distance_sqr);
+          data_.samples_weight_total += sample.weight;
+
+          if (distance_sqr < closest_distance) {
+            closest_distance = distance_sqr;
+            closest_index = data_.samples_len;
+          }
+          data_.samples_len++;
+        }
+      }
+    }
+    /* Put the closest one in first position. */
+    if (closest_index != 0) {
+      SWAP(FilmSample, data_.samples[closest_index], data_.samples[0]);
+    }
+  }
+  else {
+    /* Large Filter Size. */
+    MutableSpan sample_table(data_.samples, FILM_PRECOMP_SAMPLE_MAX);
+    /* To avoid hitting driver TDR and slowing rendering too much we use random sampling. */
+    /* TODO(fclem): This case needs more work. We could distribute the samples better to avoid
+     * loading the same pixel twice. */
+    data_.samples_len = sample_table.size();
+    data_.samples_weight_total = 0.0f;
+
+    int i = 0;
+    for (FilmSample &sample : sample_table) {
+      /* TODO(fclem): Own RNG. */
+      float2 random_2d = inst_.sampling.rng_2d_get(SAMPLING_FILTER_U);
+      /* This randomization makes sure we converge to the right result but also makes nearest
+       * neighbor filtering not converging rapidly. */
+      random_2d.x = (random_2d.x + i) / float(FILM_PRECOMP_SAMPLE_MAX);
+
+      float2 pixel_offset = math::floor(Sampling::sample_spiral(random_2d) * data_.filter_size);
+      sample.texel = int2(pixel_offset);
+
+      float distance_sqr = math::length_squared(pixel_offset - data_.subpixel_offset);
+      sample.weight = film_filter_weight(data_.filter_size, distance_sqr);
+      data_.samples_weight_total += sample.weight;
+      i++;
+    }
+  }
+}
+
+void Film::accumulate(const DRWView *view)
+{
+  if (inst_.is_viewport()) {
+    DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get();
+    GPU_framebuffer_bind(dfbl->default_fb);
+    GPU_framebuffer_viewport_set(dfbl->default_fb, UNPACK2(data_.offset), UNPACK2(data_.extent));
+  }
+
+  update_sample_table();
+
+  data_.display_only = false;
+  data_.push_update();
+
+  DRW_view_set_active(view);
+  DRW_draw_pass(accumulate_ps_);
+
+  combined_tx_.swap();
+  weight_tx_.swap();
+
+  /* Use history after first sample. */
+  if (data_.use_history == 0) {
+    data_.use_history = 1;
+    data_.use_reprojection = 1;
+  }
+}
+
+void Film::display()
+{
+  BLI_assert(inst_.is_viewport());
+
+  /* Acquire dummy render buffers for correct binding. They will not be used. */
+  inst_.render_buffers.acquire(int2(1), (void *)this);
+
+  DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get();
+  GPU_framebuffer_bind(dfbl->default_fb);
+  GPU_framebuffer_viewport_set(dfbl->default_fb, UNPACK2(data_.offset), UNPACK2(data_.extent));
+
+  data_.display_only = true;
+  data_.push_update();
+
+  DRW_view_set_active(nullptr);
+  DRW_draw_pass(accumulate_ps_);
+
+  inst_.render_buffers.release();
+
+  /* IMPORTANT: Do not swap! No accumulation has happened. */
+}
+
+float *Film::read_pass(eViewLayerEEVEEPassType pass_type)
+{
+
+  bool is_value = pass_is_value(pass_type);
+  Texture &accum_tx = (pass_type == EEVEE_RENDER_PASS_COMBINED) ?
+                          combined_tx_.current() :
+                      (pass_type == EEVEE_RENDER_PASS_Z) ?
+                          depth_tx_ :
+                          (is_value ? value_accum_tx_ : color_accum_tx_);
+
+  accum_tx.ensure_layer_views();
+
+  int index = pass_id_get(pass_type);
+  GPUTexture *pass_tx = accum_tx.layer_view(index);
+
+  GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
+
+  return (float *)GPU_texture_read(pass_tx, GPU_DATA_FLOAT, 0);
+}
+
+/** \} */
+
+}  // namespace blender::eevee
diff --git a/source/blender/draw/engines/eevee_next/eevee_film.hh b/source/blender/draw/engines/eevee_next/eevee_film.hh
new file mode 100644
index 00000000000..7ffbd4e45c6
--- /dev/null
+++ b/source/blender/draw/engines/eevee_next/eevee_film.hh
@@ -0,0 +1,187 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2021 Blender Foundation.
+ */
+
+/** \file
+ * \ingroup eevee
+ *
+ * The film class handles accumulation of samples with any distorted camera_type
+ * using a pixel filter. Inputs needs to be jittered so that the filter converges to the right
+ * result.
+ */
+
+#pragma once
+
+#include "DRW_render.h"
+
+#include "eevee_shader_shared.hh"
+
+namespace blender::eevee {
+
+class Instance;
+
+/* -------------------------------------------------------------------- */
+/** \name Film
+ * \{ */
+
+class Film {
+ public:
+  /** Stores indirection table of AOVs based on their name hash and their type. */
+  AOVsInfoDataBuf aovs_info;
+
+ private:
+  Instance &inst_;
+
+  /** Main accumulation textures containing every render-pass except depth and combined. */
+  Texture color_accum_tx_;
+  Texture value_accum_tx_;
+  /** Depth accumulation texture. Separated because using a different format. */
+  Texture depth_tx_;
+  /** Combined "Color" buffer. Double buffered to allow re-projection. */
+  SwapChain combined_tx_;
+  /** Weight buffers. Double buffered to allow updating it during accumulation. */
+  SwapChain weight_tx_;
+  /** Extent used by the render buffers when rendering the main views. */
+  int2 render_extent_ = int2(-1);
+
+  DRWPass *accumulate_ps_ = nullptr;
+
+  FilmDataBuf data_;
+
+  eViewLayerEEVEEPassType enabled_passes_ = eViewLayerEEVEEPassType(0);
+
+ public:
+  Film(Instance &inst) : inst_(inst){};
+  ~Film(){};
+
+  void init(const int2 &full_extent, const rcti *output_rect);
+
+  void sync();
+  void end_sync();
+
+  /** Accumulate the newly rendered sample contained in #RenderBuffers and blit to display. */
+  void accumulate(const DRWView *view);
+
+  /** Blit to display. No rendered sample needed. */
+  void display();
+
+  float *read_pass(eViewLayerEEVEEPassType pass_type);
+  float *read_aov(ViewLayerAOV *aov);
+
+  int2 render_extent_get() const
+  {
+    return render_extent_;
+  }
+
+  float2 pixel_jitter_get() const;
+
+  eViewLayerEEVEEPassType enabled_passes_get() const
+  {
+    return enabled_passes_;
+  }
+
+  static bool pass_is_value(eViewLayerEEVEEPassType pass_type)
+  {
+    switch (pass_type) {
+      case EEVEE_RENDER_PASS_Z:
+      case EEVEE_RENDER_PASS_MIST:
+      case EEVEE_RENDER_PASS_SHADOW:
+      case EEVEE_RENDER_PASS_AO:
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  /* Returns layer offset in the accumulation texture. -1 if the pass is not enabled. */
+  int pass_id_get(eViewLayerEEVEEPassType pass_type) const
+  {
+    switch (pass_type) {
+      case EEVEE_RENDER_PASS_COMBINED:
+        return data_.combined_id;
+      case EEVEE_RENDER_PASS_Z:
+        return data_.depth_id;
+      case EEVEE_RENDER_PASS_MIST:
+        return data_.mist_id;
+      case EEVEE_RENDER_PASS_NORMAL:
+        return data_.normal_id;
+      case EEVEE_RENDER_PASS_DIFFUSE_LIGHT:
+        return data_.diffuse_light_id;
+      case EEVEE_RENDER_PASS_DIFFUSE_COLOR:
+        return data_.diffuse_color_id;
+      case EEVEE_RENDER_PASS_SPECULAR_LIGHT:
+        return data_.specular_light_id;
+      case EEVEE_RENDER_PASS_SPECULAR_COLOR:
+        return data_.specular_color_id;
+      case EEVEE_RENDER_PASS_VOLUME_LIGHT:
+        return data_.volume_light_id;
+      case EEVEE_RENDER_PASS_EMIT:
+        return data_.emission_id;
+      case EEVEE_RENDER_PASS_ENVIRONMENT:
+        return data_.environment_id;
+      case EEVEE_RENDER_PASS_SHADOW:
+        return data_.shadow_id;
+      case EEVEE_RENDER_PASS_AO:
+        return data_.ambient_occlusion_id;
+      case EEVEE_RENDER_PASS_CRYPTOMATTE:
+        return -1; /* TODO */
+      case EEVEE_RENDER_PASS_VECTOR:
+        return data_.vector_id;
+      default:
+        return -1;
+    }
+  }
+
+  static const char *pass_to_render_pass_name(eViewLayerEEVEEPassType pass_type)
+  {
+    switch (pass_type) {
+      case EEVEE_RENDER_PASS_COMBINED:
+        return RE_PASSNAME_COMBINED;
+      case EEVEE_RENDER_PASS_Z:
+        return RE_PASSNAME_Z;
+      case EEVEE_RENDER_PASS_MIST:
+        return RE_PASSNAME_MIST;
+      case EEVEE_RENDER_PASS_NORMAL:
+        return RE_PASSNAME_NORMAL;
+      case EEVEE_RENDER_PASS_DIFFUSE_LIGHT:
+        return RE_PASSNAME_DIFFUSE_DIRECT;
+      case EEVEE_RENDER_PASS_DIFFUSE_COLOR:
+        return RE_PASSNAME_DIFFUSE_COLOR;
+      case EEVEE_RENDER_PASS_SPECULAR_LIGHT:
+        return RE_PASSNAME_GLOSSY_DIRECT;
+      case EEVEE_RENDER_PASS_SPECULAR_COLOR:
+        return RE_PASSNAME_GLOSSY_COLOR;
+      case EEVEE_RENDER_PASS_VOLUME_LIGHT:
+        return RE_PASSNAME_VOLUME_LIGHT;
+      case EEVEE_RENDER_PASS_EMIT:
+        return RE_PASSNAME_EMIT;
+      case EEVEE_RENDER_PASS_ENVIRONMENT:
+        return RE_PASSNAME_ENVIRONMENT;
+      case EEVEE_RENDER_PASS_SHADOW:
+        return RE_PASSNAME_SHADOW;
+      case EEVEE_RENDER_PASS_AO:
+        return RE_PASSNAME_AO;
+      case EEVEE_RENDER_PASS_CRYPTOMATTE:
+        BLI_assert_msg(0, "Cryptomatte is not implemented yet.");
+        return ""; /* TODO */
+      case EEVEE_RENDER_PASS_VECTOR:
+        return RE_PASSNAME_VECTOR;
+      default:
+        BLI_assert(0);
+        return "";
+    }
+  }
+
+ private:
+  void init_aovs();
+  void sync_mist();
+
+  /**
+   * Precompute sample weights if they are uniform across the whole film extent.
+   */
+  void update_sample_table();
+};
+
+/** \} */
+
+}  // namespace blender::eevee
diff --git a/source/blender/draw/engines/eevee_next/eevee_instance.cc b/source/blender/draw/engines/eevee_next/eevee_instance.cc
index 606630bcdef..5fa5628515f 100644
--- a/source/blender/draw/engines/eevee_next/eevee_instance.cc
+++ b/source/blender/draw/engines/eevee_next/eevee_instance.cc
@@ -17,6 +17,7 @@
 #include "DNA_ID.h"
 #include "DNA_lightprobe_types.h"
 #include "DNA_modifier_types.h"
+#include "RE_pipeline.h"
 
 #include "eevee_instance.hh"
 
@@ -43,7 +44,7 @@ void Instance::init(const int2 &output_res,
                     const View3D *v3d_,
                     const RegionView3D *rv3d_)
 {
-  UNUSED_VARS(light_probe_, output_rect);
+  UNUSED_VARS(light_probe_);
   render = render_;
   depsgraph = depsgraph_;
   camera_orig_object = camera_object_;
@@ -56,7 +57,10 @@ void Instance::init(const int2 &output_res,
 
   update_eval_members();
 
-  main_view.init(output_res);
+  sampling.init(scene);
+  camera.init();
+  film.init(output_res, output_rect);
+  main_view.init();
 }
 
 void Instance::set_time(float time)
@@ -90,9 +94,14 @@ void Instance::begin_sync()
   materials.begin_sync();
   velocity.begin_sync();
 
+  gpencil_engine_enabled = false;
+
+  render_buffers.sync();
   pipelines.sync();
   main_view.sync();
   world.sync();
+  camera.sync();
+  film.sync();
 }
 
 void Instance::object_sync(Object *ob)
@@ -146,13 +155,36 @@ void Instance::object_sync(Object *ob)
   ob_handle.reset_recalc_flag();
 }
 
+/* Wrapper to use with DRW_render_object_iter. */
+void Instance::object_sync_render(void *instance_,
+                                  Object *ob,
+                                  RenderEngine *engine,
+                                  Depsgraph *depsgraph)
+{
+  UNUSED_VARS(engine, depsgraph);
+  Instance &inst = *reinterpret_cast(instance_);
+  inst.object_sync(ob);
+}
+
 void Instance::end_sync()
 {
   velocity.end_sync();
+  sampling.end_sync();
+  film.end_sync();
 }
 
 void Instance::render_sync()
 {
+  DRW_cache_restart();
+
+  begin_sync();
+  DRW_render_object_iter(this, render, depsgraph, object_sync_render);
+  end_sync();
+
+  DRW_render_instance_buffer_finish();
+  /* Also we weed to have a correct fbo bound for DRW_hair_update */
+  // GPU_framebuffer_bind();
+  // DRW_hair_update();
 }
 
 /** \} */
@@ -167,6 +199,18 @@ void Instance::render_sync()
  **/
 void Instance::render_sample()
 {
+  if (sampling.finished()) {
+    film.display();
+    return;
+  }
+
+  /* Motion blur may need to do re-sync after a certain number of sample. */
+  if (!is_viewport() && sampling.do_render_sync()) {
+    render_sync();
+  }
+
+  sampling.step();
+
   main_view.render();
 }
 
@@ -178,7 +222,36 @@ void Instance::render_sample()
 
 void Instance::render_frame(RenderLayer *render_layer, const char *view_name)
 {
-  UNUSED_VARS(render_layer, view_name);
+  while (!sampling.finished()) {
+    this->render_sample();
+    /* TODO(fclem) print progression. */
+  }
+
+  /* Read Results. */
+  eViewLayerEEVEEPassType pass_bits = film.enabled_passes_get();
+  for (auto i : IndexRange(EEVEE_RENDER_PASS_MAX_BIT)) {
+    eViewLayerEEVEEPassType pass_type = eViewLayerEEVEEPassType(pass_bits & (1 << i));
+    if (pass_type == 0) {
+      continue;
+    }
+
+    const char *pass_name = Film::pass_to_render_pass_name(pass_type);
+    RenderPass *rp = RE_pass_find_by_name(render_layer, pass_name, view_name);
+    if (rp) {
+      float *result = film.read_pass(pass_type);
+      if (result) {
+        std::cout << "read " << pass_name << std::endl;
+        BLI_mutex_lock(&render->update_render_passes_mutex);
+        /* WORKAROUND: We use texture read to avoid using a framebuffer to get the render result.
+         * However, on some implementation, we need a buffer with a few extra bytes for the read to
+         * happen correctly (see GLTexture::read()). So we need a custom memory allocation. */
+        /* Avoid memcpy(), replace the pointer directly. */
+        MEM_SAFE_FREE(rp->rect);
+        rp->rect = result;
+        BLI_mutex_unlock(&render->update_render_passes_mutex);
+      }
+    }
+  }
 }
 
 void Instance::draw_viewport(DefaultFramebufferList *dfbl)
@@ -187,6 +260,10 @@ void Instance::draw_viewport(DefaultFramebufferList *dfbl)
   render_sample();
   velocity.step_swap();
 
+  if (!sampling.finished_viewport()) {
+    DRW_viewport_request_redraw();
+  }
+
   if (materials.queued_shaders_count > 0) {
     std::stringstream ss;
     ss << "Compiling Shaders " << materials.queued_shaders_count;
diff --git a/source/blender/draw/engines/eevee_next/eevee_instance.hh b/source/blender/draw/engines/eevee_next/eevee_instance.hh
index 84be59fc5f0..f47d4f20363 100644
--- a/source/blender/draw/engines/eevee_next/eevee_instance.hh
+++ b/source/blender/draw/engines/eevee_next/eevee_instance.hh
@@ -16,8 +16,11 @@
 #include "DRW_render.h"
 
 #include "eevee_camera.hh"
+#include "eevee_film.hh"
 #include "eevee_material.hh"
 #include "eevee_pipeline.hh"
+#include "eevee_renderbuffers.hh"
+#include "eevee_sampling.hh"
 #include "eevee_shader.hh"
 #include "eevee_sync.hh"
 #include "eevee_view.hh"
@@ -38,7 +41,10 @@ class Instance {
   MaterialModule materials;
   PipelineModule pipelines;
   VelocityModule velocity;
+  Sampling sampling;
   Camera camera;
+  Film film;
+  RenderBuffers render_buffers;
   MainView main_view;
   World world;
 
@@ -57,6 +63,9 @@ class Instance {
   const View3D *v3d;
   const RegionView3D *rv3d;
 
+  /** True if the grease pencil engine might be running. */
+  bool gpencil_engine_enabled;
+
   /* Info string displayed at the top of the render / viewport. */
   std::string info = "";
 
@@ -67,7 +76,10 @@ class Instance {
         materials(*this),
         pipelines(*this),
         velocity(*this),
+        sampling(*this),
         camera(*this),
+        film(*this),
+        render_buffers(*this),
         main_view(*this),
         world(*this){};
   ~Instance(){};
@@ -92,12 +104,17 @@ class Instance {
 
   void draw_viewport(DefaultFramebufferList *dfbl);
 
-  bool is_viewport(void)
+  bool is_viewport() const
+  {
+    return render == nullptr;
+  }
+
+  bool overlays_enabled() const
   {
-    return !DRW_state_is_scene_render();
+    return (!v3d) || ((v3d->flag & V3D_HIDE_OVERLAYS) == 0);
   }
 
-  bool use_scene_lights(void) const
+  bool use_scene_lights() const
   {
     return (!v3d) ||
            ((v3d->shading.type == OB_MATERIAL) &&
@@ -107,7 +124,7 @@ class Instance {
   }
 
   /* Light the scene using the selected HDRI in the viewport shading pop-over. */
-  bool use_studio_light(void) const
+  bool use_studio_light() const
   {
     return (v3d) && (((v3d->shading.type == OB_MATERIAL) &&
                       ((v3d->shading.flag & V3D_SHADING_SCENE_WORLD) == 0)) ||
@@ -116,6 +133,10 @@ class Instance {
   }
 
  private:
+  static void object_sync_render(void *instance_,
+                                 Object *ob,
+                                 RenderEngine *engine,
+                                 Depsgraph *depsgraph);
   void render_sample();
 
   void mesh_sync(Object *ob, ObjectHandle &ob_handle);
diff --git a/source/blender/draw/engines/eevee_next/eevee_material.cc b/source/blender/draw/engines/eevee_next/eevee_material.cc
index 1676c89d679..b3161a67092 100644
--- a/source/blender/draw/engines/eevee_next/eevee_material.cc
+++ b/source/blender/draw/engines/eevee_next/eevee_material.cc
@@ -195,7 +195,7 @@ MaterialPass MaterialModule::material_pass_get(::Material *blender_mat,
   BLI_assert(GPU_material_status(matpass.gpumat) == GPU_MAT_SUCCESS);
 
   if (GPU_material_recalc_flag_get(matpass.gpumat)) {
-    // inst_.sampling.reset();
+    inst_.sampling.reset();
   }
 
   if ((pipeline_type == MAT_PIPE_DEFERRED) &&
diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc
index 33853eba06c..7b3cfbf5899 100644
--- a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc
+++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc
@@ -24,6 +24,8 @@ namespace blender::eevee {
 
 void WorldPipeline::sync(GPUMaterial *gpumat)
 {
+  RenderBuffers &rbufs = inst_.render_buffers;
+
   DRWState state = DRW_STATE_WRITE_COLOR;
   world_ps_ = DRW_pass_create("World", state);
 
@@ -34,6 +36,19 @@ void WorldPipeline::sync(GPUMaterial *gpumat)
   DRWShadingGroup *grp = DRW_shgroup_material_create(gpumat, world_ps_);
   DRW_shgroup_uniform_texture(grp, "utility_tx", inst_.pipelines.utility_tx);
   DRW_shgroup_call_obmat(grp, DRW_cache_fullscreen_quad_get(), camera_mat.ptr());
+  /* AOVs. */
+  DRW_shgroup_uniform_image_ref(grp, "aov_color_img", &rbufs.aov_color_tx);
+  DRW_shgroup_uniform_image_ref(grp, "aov_value_img", &rbufs.aov_value_tx);
+  DRW_shgroup_storage_block_ref(grp, "aov_buf", &inst_.film.aovs_info);
+  /* RenderPasses. Cleared by background (even if bad practice). */
+  DRW_shgroup_uniform_image_ref(grp, "rp_normal_img", &rbufs.normal_tx);
+  DRW_shgroup_uniform_image_ref(grp, "rp_diffuse_light_img", &rbufs.diffuse_light_tx);
+  DRW_shgroup_uniform_image_ref(grp, "rp_diffuse_color_img", &rbufs.diffuse_color_tx);
+  DRW_shgroup_uniform_image_ref(grp, "rp_specular_light_img", &rbufs.specular_light_tx);
+  DRW_shgroup_uniform_image_ref(grp, "rp_specular_color_img", &rbufs.specular_color_tx);
+  DRW_shgroup_uniform_image_ref(grp, "rp_emission_img", &rbufs.emission_tx);
+  /* To allow opaque pass rendering over it. */
+  DRW_shgroup_barrier(grp, GPU_BARRIER_SHADER_IMAGE_ACCESS);
 }
 
 void WorldPipeline::render()
@@ -83,6 +98,7 @@ void ForwardPipeline::sync()
 
 DRWShadingGroup *ForwardPipeline::material_opaque_add(::Material *blender_mat, GPUMaterial *gpumat)
 {
+  RenderBuffers &rbufs = inst_.render_buffers;
   DRWPass *pass = (blender_mat->blend_flag & MA_BL_CULL_BACKFACE) ? opaque_culled_ps_ : opaque_ps_;
   // LightModule &lights = inst_.lights;
   // LightProbeModule &lightprobes = inst_.lightprobes;
@@ -97,6 +113,18 @@ DRWShadingGroup *ForwardPipeline::material_opaque_add(::Material *blender_mat, G
   // DRW_shgroup_uniform_texture_ref(grp, "lightprobe_grid_tx", lightprobes.grid_tx_ref_get());
   // DRW_shgroup_uniform_texture_ref(grp, "lightprobe_cube_tx", lightprobes.cube_tx_ref_get());
   DRW_shgroup_uniform_texture(grp, "utility_tx", inst_.pipelines.utility_tx);
+  /* AOVs. */
+  DRW_shgroup_uniform_image_ref(grp, "aov_color_img", &rbufs.aov_color_tx);
+  DRW_shgroup_uniform_image_ref(grp, "aov_value_img", &rbufs.aov_value_tx);
+  DRW_shgroup_storage_block_ref(grp, "aov_buf", &inst_.film.aovs_info);
+  /* RenderPasses. */
+  DRW_shgroup_uniform_image_ref(grp, "rp_normal_img", &rbufs.normal_tx);
+  DRW_shgroup_uniform_image_ref(grp, "rp_diffuse_light_img", &rbufs.diffuse_light_tx);
+  DRW_shgroup_uniform_image_ref(grp, "rp_diffuse_color_img", &rbufs.diffuse_color_tx);
+  DRW_shgroup_uniform_image_ref(grp, "rp_specular_light_img", &rbufs.specular_light_tx);
+  DRW_shgroup_uniform_image_ref(grp, "rp_specular_color_img", &rbufs.specular_color_tx);
+  DRW_shgroup_uniform_image_ref(grp, "rp_emission_img", &rbufs.emission_tx);
+
   /* TODO(fclem): Make this only needed if material uses it ... somehow. */
   // if (true) {
   //   DRW_shgroup_uniform_texture_ref(
diff --git a/source/blender/draw/engines/eevee_next/eevee_renderbuffers.cc b/source/blender/draw/engines/eevee_next/eevee_renderbuffers.cc
new file mode 100644
index 00000000000..6e30ba989df
--- /dev/null
+++ b/source/blender/draw/engines/eevee_next/eevee_renderbuffers.cc
@@ -0,0 +1,96 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2021 Blender Foundation.
+ */
+
+/** \file
+ * \ingroup eevee
+ *
+ * A film is a fullscreen buffer (usually at output extent)
+ * that will be able to accumulate sample in any distorted camera_type
+ * using a pixel filter.
+ *
+ * Input needs to be jittered so that the filter converges to the right result.
+ */
+
+#include "BLI_rect.h"
+
+#include "GPU_framebuffer.h"
+#include "GPU_texture.h"
+
+#include "DRW_render.h"
+
+#include "eevee_film.hh"
+#include "eevee_instance.hh"
+
+namespace blender::eevee {
+
+void RenderBuffers::sync()
+{
+  depth_tx.sync();
+  combined_tx.sync();
+
+  normal_tx.sync();
+  vector_tx.sync();
+  diffuse_light_tx.sync();
+  diffuse_color_tx.sync();
+  specular_light_tx.sync();
+  specular_color_tx.sync();
+  volume_light_tx.sync();
+  emission_tx.sync();
+  environment_tx.sync();
+  shadow_tx.sync();
+  ambient_occlusion_tx.sync();
+}
+
+void RenderBuffers::acquire(int2 extent, void *owner)
+{
+  auto pass_extent = [&](eViewLayerEEVEEPassType pass_bit) -> int2 {
+    /* Use dummy texture for disabled passes. Allows correct bindings. */
+    return (inst_.film.enabled_passes_get() & pass_bit) ? extent : int2(1);
+  };
+
+  eGPUTextureFormat color_format = GPU_RGBA16F;
+  eGPUTextureFormat float_format = GPU_R16F;
+
+  /* Depth and combined are always needed. */
+  depth_tx.acquire(extent, GPU_DEPTH24_STENCIL8, owner);
+  combined_tx.acquire(extent, color_format, owner);
+
+  normal_tx.acquire(pass_extent(EEVEE_RENDER_PASS_NORMAL), color_format, owner);
+  vector_tx.acquire(pass_extent(EEVEE_RENDER_PASS_VECTOR), color_format, owner);
+  diffuse_light_tx.acquire(pass_extent(EEVEE_RENDER_PASS_DIFFUSE_LIGHT), color_format, owner);
+  diffuse_color_tx.acquire(pass_extent(EEVEE_RENDER_PASS_DIFFUSE_COLOR), color_format, owner);
+  specular_light_tx.acquire(pass_extent(EEVEE_RENDER_PASS_SPECULAR_LIGHT), color_format, owner);
+  specular_color_tx.acquire(pass_extent(EEVEE_RENDER_PASS_SPECULAR_COLOR), color_format, owner);
+  volume_light_tx.acquire(pass_extent(EEVEE_RENDER_PASS_VOLUME_LIGHT), color_format, owner);
+  emission_tx.acquire(pass_extent(EEVEE_RENDER_PASS_EMIT), color_format, owner);
+  environment_tx.acquire(pass_extent(EEVEE_RENDER_PASS_ENVIRONMENT), color_format, owner);
+  shadow_tx.acquire(pass_extent(EEVEE_RENDER_PASS_SHADOW), float_format, owner);
+  ambient_occlusion_tx.acquire(pass_extent(EEVEE_RENDER_PASS_AO), float_format, owner);
+
+  const AOVsInfoData &aovs = inst_.film.aovs_info;
+  aov_color_tx.ensure_2d_array(
+      color_format, (aovs.color_len > 0) ? extent : int2(1), max_ii(1, aovs.color_len));
+  aov_value_tx.ensure_2d_array(
+      float_format, (aovs.value_len > 0) ? extent : int2(1), max_ii(1, aovs.value_len));
+}
+
+void RenderBuffers::release()
+{
+  depth_tx.release();
+  combined_tx.release();
+
+  normal_tx.release();
+  vector_tx.release();
+  diffuse_light_tx.release();
+  diffuse_color_tx.release();
+  specular_light_tx.release();
+  specular_color_tx.release();
+  volume_light_tx.release();
+  emission_tx.release();
+  environment_tx.release();
+  shadow_tx.release();
+  ambient_occlusion_tx.release();
+}
+
+}  // namespace blender::eevee
diff --git a/source/blender/draw/engines/eevee_next/eevee_renderbuffers.hh b/source/blender/draw/engines/eevee_next/eevee_renderbuffers.hh
new file mode 100644
index 00000000000..8c91fed2f0f
--- /dev/null
+++ b/source/blender/draw/engines/eevee_next/eevee_renderbuffers.hh
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2022 Blender Foundation.
+ */
+
+/** \file
+ * \ingroup eevee
+ *
+ * Render buffers are textures that are filled during a view rendering.
+ * Their content is then added to the accumulation buffers of the film class.
+ * They are short lived and can be reused when doing multi view rendering.
+ */
+
+#pragma once
+
+#include "DRW_render.h"
+
+#include "eevee_shader_shared.hh"
+
+namespace blender::eevee {
+
+class Instance;
+
+class RenderBuffers {
+ public:
+  TextureFromPool depth_tx;
+  TextureFromPool combined_tx;
+
+  // TextureFromPool mist_tx; /* Derived from depth_tx during accumulation. */
+  TextureFromPool normal_tx;
+  TextureFromPool vector_tx;
+  TextureFromPool diffuse_light_tx;
+  TextureFromPool diffuse_color_tx;
+  TextureFromPool specular_light_tx;
+  TextureFromPool specular_color_tx;
+  TextureFromPool volume_light_tx;
+  TextureFromPool emission_tx;
+  TextureFromPool environment_tx;
+  TextureFromPool shadow_tx;
+  TextureFromPool ambient_occlusion_tx;
+  // TextureFromPool cryptomatte_tx; /* TODO */
+  /* TODO(fclem): Use texture from pool once they support texture array. */
+  Texture aov_color_tx;
+  Texture aov_value_tx;
+
+ private:
+  Instance &inst_;
+
+ public:
+  RenderBuffers(Instance &inst) : inst_(inst){};
+
+  void sync();
+  /* Acquires (also ensures) the render buffer before rendering to them. */
+  void acquire(int2 extent, void *owner);
+  void release();
+};
+
+}  // namespace blender::eevee
diff --git a/source/blender/draw/engines/eevee_next/eevee_sampling.cc b/source/blender/draw/engines/eevee_next/eevee_sampling.cc
new file mode 100644
index 00000000000..493fff53919
--- /dev/null
+++ b/source/blender/draw/engines/eevee_next/eevee_sampling.cc
@@ -0,0 +1,264 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2021 Blender Foundation.
+ */
+
+/** \file
+ * \ingroup eevee
+ *
+ * Random number generator, contains persistent state and sample count logic.
+ */
+
+#include "BLI_rand.h"
+
+#include "eevee_instance.hh"
+#include "eevee_sampling.hh"
+
+namespace blender::eevee {
+
+/* -------------------------------------------------------------------- */
+/** \name Sampling
+ * \{ */
+
+void Sampling::init(const Scene *scene)
+{
+  sample_count_ = inst_.is_viewport() ? scene->eevee.taa_samples : scene->eevee.taa_render_samples;
+
+  if (sample_count_ == 0) {
+    BLI_assert(inst_.is_viewport());
+    sample_count_ = infinite_sample_count_;
+  }
+
+  motion_blur_steps_ = !inst_.is_viewport() ? scene->eevee.motion_blur_steps : 1;
+  sample_count_ = divide_ceil_u(sample_count_, motion_blur_steps_);
+
+  if (scene->eevee.flag & SCE_EEVEE_DOF_JITTER) {
+    if (sample_count_ == infinite_sample_count_) {
+      /* Special case for viewport continuous rendering. We clamp to a max sample
+       * to avoid the jittered dof never converging. */
+      dof_ring_count_ = 6;
+    }
+    else {
+      dof_ring_count_ = sampling_web_ring_count_get(dof_web_density_, sample_count_);
+    }
+    dof_sample_count_ = sampling_web_sample_count_get(dof_web_density_, dof_ring_count_);
+    /* Change total sample count to fill the web pattern entirely. */
+    sample_count_ = divide_ceil_u(sample_count_, dof_sample_count_) * dof_sample_count_;
+  }
+  else {
+    dof_ring_count_ = 0;
+    dof_sample_count_ = 1;
+  }
+
+  /* Only multiply after to have full the full DoF web pattern for each time steps. */
+  sample_count_ *= motion_blur_steps_;
+}
+
+void Sampling::end_sync()
+{
+  if (reset_) {
+    viewport_sample_ = 0;
+    if (inst_.is_viewport()) {
+      interactive_mode_ = true;
+    }
+  }
+
+  if (interactive_mode_) {
+    int interactive_sample_count = min_ii(interactive_sample_max_, sample_count_);
+
+    if (viewport_sample_ < interactive_sample_count) {
+      /* Loop over the same starting samples. */
+      sample_ = sample_ % interactive_sample_count;
+    }
+    else {
+      /* Break out of the loop and resume normal pattern. */
+      sample_ = interactive_sample_count;
+      interactive_mode_ = false;
+    }
+  }
+}
+
+void Sampling::step()
+{
+  {
+    /* TODO(fclem) we could use some persistent states to speedup the computation. */
+    double2 r, offset = {0, 0};
+    /* Using 2,3 primes as per UE4 Temporal AA presentation.
+     * advances.realtimerendering.com/s2014/epic/TemporalAA.pptx (slide 14) */
+    uint2 primes = {2, 3};
+    BLI_halton_2d(primes, offset, sample_ + 1, r);
+    /* WORKAROUND: We offset the distribution to make the first sample (0,0). This way, we are
+     * assured that at least one of the samples inside the TAA rotation will match the one from the
+     * draw manager. This makes sure overlays are correctly composited in static scene. */
+    data_.dimensions[SAMPLING_FILTER_U] = fractf(r[0] + (1.0 / 2.0));
+    data_.dimensions[SAMPLING_FILTER_V] = fractf(r[1] + (2.0 / 3.0));
+    /* TODO de-correlate. */
+    data_.dimensions[SAMPLING_TIME] = r[0];
+    data_.dimensions[SAMPLING_CLOSURE] = r[1];
+    data_.dimensions[SAMPLING_RAYTRACE_X] = r[0];
+  }
+  {
+    double2 r, offset = {0, 0};
+    uint2 primes = {5, 7};
+    BLI_halton_2d(primes, offset, sample_ + 1, r);
+    data_.dimensions[SAMPLING_LENS_U] = r[0];
+    data_.dimensions[SAMPLING_LENS_V] = r[1];
+    /* TODO de-correlate. */
+    data_.dimensions[SAMPLING_LIGHTPROBE] = r[0];
+    data_.dimensions[SAMPLING_TRANSPARENCY] = r[1];
+  }
+  {
+    /* Using leaped Halton sequence so we can reused the same primes as lens. */
+    double3 r, offset = {0, 0, 0};
+    uint64_t leap = 11;
+    uint3 primes = {5, 4, 7};
+    BLI_halton_3d(primes, offset, sample_ * leap, r);
+    data_.dimensions[SAMPLING_SHADOW_U] = r[0];
+    data_.dimensions[SAMPLING_SHADOW_V] = r[1];
+    data_.dimensions[SAMPLING_SHADOW_W] = r[2];
+    /* TODO de-correlate. */
+    data_.dimensions[SAMPLING_RAYTRACE_U] = r[0];
+    data_.dimensions[SAMPLING_RAYTRACE_V] = r[1];
+    data_.dimensions[SAMPLING_RAYTRACE_W] = r[2];
+  }
+  {
+    /* Using leaped Halton sequence so we can reused the same primes. */
+    double2 r, offset = {0, 0};
+    uint64_t leap = 5;
+    uint2 primes = {2, 3};
+    BLI_halton_2d(primes, offset, sample_ * leap, r);
+    data_.dimensions[SAMPLING_SHADOW_X] = r[0];
+    data_.dimensions[SAMPLING_SHADOW_Y] = r[1];
+    /* TODO de-correlate. */
+    data_.dimensions[SAMPLING_SSS_U] = r[0];
+    data_.dimensions[SAMPLING_SSS_V] = r[1];
+  }
+
+  data_.push_update();
+
+  viewport_sample_++;
+  sample_++;
+
+  std::cout << sample_ << " " << viewport_sample_ << std::endl;
+
+  reset_ = false;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Sampling patterns
+ * \{ */
+
+float3 Sampling::sample_ball(const float3 &rand)
+{
+  float3 sample;
+  sample.z = rand.x * 2.0f - 1.0f; /* cos theta */
+
+  float r = sqrtf(fmaxf(0.0f, 1.0f - square_f(sample.z))); /* sin theta */
+
+  float omega = rand.y * 2.0f * M_PI;
+  sample.x = r * cosf(omega);
+  sample.y = r * sinf(omega);
+
+  sample *= sqrtf(sqrtf(rand.z));
+  return sample;
+}
+
+float2 Sampling::sample_disk(const float2 &rand)
+{
+  float omega = rand.y * 2.0f * M_PI;
+  return sqrtf(rand.x) * float2(cosf(omega), sinf(omega));
+}
+
+float2 Sampling::sample_spiral(const float2 &rand)
+{
+  /* Fibonacci spiral. */
+  float omega = M_PI * (1.0f + sqrtf(5.0f)) * rand.x;
+  float r = sqrtf(rand.x);
+  /* Random rotation. */
+  omega += rand.y * 2.0f * M_PI;
+  return r * float2(cosf(omega), sinf(omega));
+}
+
+void Sampling::dof_disk_sample_get(float *r_radius, float *r_theta) const
+{
+  if (dof_ring_count_ == 0) {
+    *r_radius = *r_theta = 0.0f;
+    return;
+  }
+
+  int s = sample_ - 1;
+  int ring = 0;
+  int ring_sample_count = 1;
+  int ring_sample = 1;
+
+  s = s * (dof_web_density_ - 1);
+  s = s % dof_sample_count_;
+
+  /* Choosing sample to we get faster convergence.
+   * The issue here is that we cannot map a low descripency sequence to this sampling pattern
+   * because the same sample could be choosen twice in relatively short intervals. */
+  /* For now just use an ascending sequence with an offset. This gives us relatively quick
+   * initial coverage and relatively high distance between samples. */
+  /* TODO(fclem) We can try to order samples based on a LDS into a table to avoid duplicates.
+   * The drawback would be some memory consumption and init time. */
+  int samples_passed = 1;
+  while (s >= samples_passed) {
+    ring++;
+    ring_sample_count = ring * dof_web_density_;
+    ring_sample = s - samples_passed;
+    ring_sample = (ring_sample + 1) % ring_sample_count;
+    samples_passed += ring_sample_count;
+  }
+
+  *r_radius = ring / (float)dof_ring_count_;
+  *r_theta = 2.0f * M_PI * ring_sample / (float)ring_sample_count;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Sampling patterns
+ * \{ */
+
+/* Creates a discrete cumulative distribution function table from a given curvemapping.
+ * Output cdf vector is expected to already be sized according to the wanted resolution. */
+void Sampling::cdf_from_curvemapping(const CurveMapping &curve, Vector &cdf)
+{
+  BLI_assert(cdf.size() > 1);
+  cdf[0] = 0.0f;
+  /* Actual CDF evaluation. */
+  for (int u : cdf.index_range()) {
+    float x = (float)(u + 1) / (float)(cdf.size() - 1);
+    cdf[u + 1] = cdf[u] + BKE_curvemapping_evaluateF(&curve, 0, x);
+  }
+  /* Normalize the CDF. */
+  for (int u : cdf.index_range()) {
+    cdf[u] /= cdf.last();
+  }
+  /* Just to make sure. */
+  cdf.last() = 1.0f;
+}
+
+/* Inverts a cumulative distribution function.
+ * Output vector is expected to already be sized according to the wanted resolution. */
+void Sampling::cdf_invert(Vector &cdf, Vector &inverted_cdf)
+{
+  for (int u : inverted_cdf.index_range()) {
+    float x = (float)u / (float)(inverted_cdf.size() - 1);
+    for (int i : cdf.index_range()) {
+      if (i == cdf.size() - 1) {
+        inverted_cdf[u] = 1.0f;
+      }
+      else if (cdf[i] >= x) {
+        float t = (x - cdf[i]) / (cdf[i + 1] - cdf[i]);
+        inverted_cdf[u] = ((float)i + t) / (float)(cdf.size() - 1);
+        break;
+      }
+    }
+  }
+}
+
+/** \} */
+
+}  // namespace blender::eevee
diff --git a/source/blender/draw/engines/eevee_next/eevee_sampling.hh b/source/blender/draw/engines/eevee_next/eevee_sampling.hh
new file mode 100644
index 00000000000..d956c61f2b2
--- /dev/null
+++ b/source/blender/draw/engines/eevee_next/eevee_sampling.hh
@@ -0,0 +1,172 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2021 Blender Foundation.
+ */
+
+/** \file
+ * \ingroup eevee
+ *
+ * Random number generator, contains persistent state and sample count logic.
+ */
+
+#pragma once
+
+#include "BKE_colortools.h"
+#include "BLI_system.h"
+#include "BLI_vector.hh"
+#include "DNA_scene_types.h"
+#include "DRW_render.h"
+
+#include "eevee_shader_shared.hh"
+
+namespace blender::eevee {
+
+class Instance;
+
+class Sampling {
+ private:
+  Instance &inst_;
+
+  /* Number of samples in the first ring of jittered depth of field. */
+  constexpr static uint64_t dof_web_density_ = 6;
+  /* High number of sample for viewport infinite rendering. */
+  constexpr static uint64_t infinite_sample_count_ = 0xFFFFFFu;
+  /* During interactive rendering, loop over the first few samples. */
+  constexpr static uint64_t interactive_sample_max_ = 8;
+
+  /** 0 based current sample. */
+  uint64_t sample_ = 0;
+  /** Target sample count. */
+  uint64_t sample_count_ = 64;
+  /** Number of ring in the web pattern of the jittered Depth of Field. */
+  uint64_t dof_ring_count_ = 0;
+  /** Number of samples in the web pattern of the jittered Depth of Field. */
+  uint64_t dof_sample_count_ = 1;
+  /** Motion blur steps. */
+  uint64_t motion_blur_steps_ = 1;
+  /** Increases if the view and the scene is static. */
+  int64_t viewport_sample_ = 0;
+  /** Tag to reset sampling for the next sample. */
+  bool reset_ = false;
+  /**
+   * Switch between interactive and static accumulation.
+   * In interactive mode, image stability is prioritized over quality.
+   */
+  bool interactive_mode_ = false;
+
+  SamplingDataBuf data_;
+
+ public:
+  Sampling(Instance &inst) : inst_(inst){};
+  ~Sampling(){};
+
+  void init(const Scene *scene);
+  void end_sync();
+  void step();
+
+  /* Viewport Only: Function to call to notify something in the scene changed.
+   * This will reset accumulation. Do not call after end_sync() or during sample rendering. */
+  void reset()
+  {
+    reset_ = true;
+  }
+
+  /* Viewport Only: true if an update happened in the scene and accumulation needs reset. */
+  bool is_reset() const
+  {
+    return reset_;
+  }
+
+  void bind_resources(DRWShadingGroup *grp)
+  {
+    DRW_shgroup_storage_block_ref(grp, "sampling_buf", &data_);
+  }
+
+  /* Returns a pseudo random number in [0..1] range. Each dimension are de-correlated. */
+  float rng_get(eSamplingDimension dimension) const
+  {
+    return data_.dimensions[dimension];
+  }
+
+  /* Returns a pseudo random number in [0..1] range. Each dimension are de-correlated. */
+  float2 rng_2d_get(eSamplingDimension starting_dimension) const
+  {
+    return *reinterpret_cast(&data_.dimensions[starting_dimension]);
+  }
+
+  /* Returns a pseudo random number in [0..1] range. Each dimension are de-correlated. */
+  float3 rng_3d_get(eSamplingDimension starting_dimension) const
+  {
+    return *reinterpret_cast(&data_.dimensions[starting_dimension]);
+  }
+
+  /* Returns true if rendering has finished. */
+  bool finished() const
+  {
+    return (sample_ >= sample_count_ - 1);
+  }
+
+  /* Returns true if viewport smoothing and sampling has finished. */
+  bool finished_viewport() const
+  {
+    return finished() && (viewport_sample_ >= interactive_sample_max_);
+  }
+
+  /* Return true if we are starting a new motion blur step. We need to run sync again since
+   * depsgraph was updated by MotionBlur::step(). */
+  bool do_render_sync() const
+  {
+    return ((sample_ % (sample_count_ / motion_blur_steps_)) == 0);
+  }
+
+  /**
+   * Special ball distribution:
+   * Point are distributed in a way that when they are orthogonally
+   * projected into any plane, the resulting distribution is (close to)
+   * a uniform disc distribution.
+   * \a rand is 3 random float in the [0..1] range.
+   * Returns point in a ball of radius 1 and centered on the origin.
+   */
+  static float3 sample_ball(const float3 &rand);
+
+  /**
+   * Uniform disc distribution.
+   * \a rand is 2 random float in the [0..1] range.
+   * Returns point in a disk of radius 1 and centered on the origin.
+   */
+  static float2 sample_disk(const float2 &rand);
+
+  /**
+   * Uniform disc distribution using fibonacci spiral sampling.
+   * \a rand is 2 random float in the [0..1] range.
+   * Returns point in a disk of radius 1 and centered on the origin.
+   */
+  static float2 sample_spiral(const float2 &rand);
+
+  /**
+   * Special RNG for depth of field.
+   * Returns \a radius and \a theta angle offset to apply to the web sampling pattern.
+   */
+  void dof_disk_sample_get(float *r_radius, float *r_theta) const;
+
+  /**
+   * Returns sample count inside the jittered depth of field web pattern.
+   */
+  uint64_t dof_ring_count_get() const
+  {
+    return dof_ring_count_;
+  }
+
+  /**
+   * Returns sample count inside the jittered depth of field web pattern.
+   */
+  uint64_t dof_sample_count_get() const
+  {
+    return dof_sample_count_;
+  }
+
+  /* Cumulative Distribution Function Utils. */
+  static void cdf_from_curvemapping(const CurveMapping &curve, Vector &cdf);
+  static void cdf_invert(Vector &cdf, Vector &inverted_cdf);
+};
+
+}  // namespace blender::eevee
diff --git a/source/blender/draw/engines/eevee_next/eevee_shader.cc b/source/blender/draw/engines/eevee_next/eevee_shader.cc
index 09aa97e49e9..f5d4af2914e 100644
--- a/source/blender/draw/engines/eevee_next/eevee_shader.cc
+++ b/source/blender/draw/engines/eevee_next/eevee_shader.cc
@@ -78,6 +78,10 @@ ShaderModule::~ShaderModule()
 const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_type)
 {
   switch (shader_type) {
+    case FILM_FRAG:
+      return "eevee_film_frag";
+    case FILM_COMP:
+      return "eevee_film_comp";
     case VELOCITY_RESOLVE:
       return "eevee_velocity_resolve";
     /* To avoid compiler warning about missing case. */
@@ -161,7 +165,6 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
         }
       }
       info.vertex_inputs_.clear();
-      info.additional_info("draw_curves_infos");
       break;
     case MAT_GEOM_WORLD:
       /**
diff --git a/source/blender/draw/engines/eevee_next/eevee_shader.hh b/source/blender/draw/engines/eevee_next/eevee_shader.hh
index 0f42e880a10..7a0867820af 100644
--- a/source/blender/draw/engines/eevee_next/eevee_shader.hh
+++ b/source/blender/draw/engines/eevee_next/eevee_shader.hh
@@ -26,7 +26,10 @@ namespace blender::eevee {
 
 /* Keep alphabetical order and clean prefix. */
 enum eShaderType {
-  VELOCITY_RESOLVE = 0,
+  FILM_FRAG = 0,
+  FILM_COMP,
+
+  VELOCITY_RESOLVE,
 
   MAX_SHADER_TYPE,
 };
diff --git a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh
index eb409f076f3..4168171ab07 100644
--- a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh
+++ b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh
@@ -12,7 +12,7 @@
 #  include "BLI_memory_utils.hh"
 #  include "DRW_gpu_wrapper.hh"
 
-// #  include "eevee_defines.hh"
+#  include "eevee_defines.hh"
 
 #  include "GPU_shader_shared.h"
 
@@ -27,6 +27,63 @@ using draw::TextureFromPool;
 
 #define UBO_MIN_MAX_SUPPORTED_SIZE 1 << 14
 
+/* -------------------------------------------------------------------- */
+/** \name Sampling
+ * \{ */
+
+enum eSamplingDimension : uint32_t {
+  SAMPLING_FILTER_U = 0u,
+  SAMPLING_FILTER_V = 1u,
+  SAMPLING_LENS_U = 2u,
+  SAMPLING_LENS_V = 3u,
+  SAMPLING_TIME = 4u,
+  SAMPLING_SHADOW_U = 5u,
+  SAMPLING_SHADOW_V = 6u,
+  SAMPLING_SHADOW_W = 7u,
+  SAMPLING_SHADOW_X = 8u,
+  SAMPLING_SHADOW_Y = 9u,
+  SAMPLING_CLOSURE = 10u,
+  SAMPLING_LIGHTPROBE = 11u,
+  SAMPLING_TRANSPARENCY = 12u,
+  SAMPLING_SSS_U = 13u,
+  SAMPLING_SSS_V = 14u,
+  SAMPLING_RAYTRACE_U = 15u,
+  SAMPLING_RAYTRACE_V = 16u,
+  SAMPLING_RAYTRACE_W = 17u,
+  SAMPLING_RAYTRACE_X = 18u
+};
+
+/**
+ * IMPORTANT: Make sure the array can contain all sampling dimensions.
+ * Also note that it needs to be multiple of 4.
+ */
+#define SAMPLING_DIMENSION_COUNT 20
+
+/* NOTE(fclem): Needs to be used in StorageBuffer because of arrays of scalar. */
+struct SamplingData {
+  /** Array containing random values from Low Discrepency Sequence in [0..1) range. */
+  float dimensions[SAMPLING_DIMENSION_COUNT];
+};
+BLI_STATIC_ASSERT_ALIGN(SamplingData, 16)
+
+/* Returns total sample count in a web pattern of the given size. */
+static inline int sampling_web_sample_count_get(int web_density, int ring_count)
+{
+  return ((ring_count * ring_count + ring_count) / 2) * web_density + 1;
+}
+
+/* Returns lowest possible ring count that contains at least sample_count samples. */
+static inline int sampling_web_ring_count_get(int web_density, int sample_count)
+{
+  /* Inversion of web_sample_count_get(). */
+  float x = 2.0f * (float(sample_count) - 1.0f) / float(web_density);
+  /* Solving polynomial. We only search positive solution. */
+  float discriminant = 1.0f + 4.0f * x;
+  return int(ceilf(0.5f * (sqrtf(discriminant) - 1.0f)));
+}
+
+/** \} */
+
 /* -------------------------------------------------------------------- */
 /** \name Camera
  * \{ */
@@ -65,14 +122,136 @@ struct CameraData {
   /** Clipping distances. */
   float clip_near;
   float clip_far;
-  /** Film pixel filter radius. */
-  float filter_size;
   eCameraType type;
+
+  int _pad0;
 };
 BLI_STATIC_ASSERT_ALIGN(CameraData, 16)
 
 /** \} */
 
+/* -------------------------------------------------------------------- */
+/** \name Film
+ * \{ */
+
+#define FILM_PRECOMP_SAMPLE_MAX 16
+
+struct FilmSample {
+  int2 texel;
+  float weight;
+  /** Used for accumulation. */
+  float weight_sum_inv;
+};
+BLI_STATIC_ASSERT_ALIGN(FilmSample, 16)
+
+struct FilmData {
+  /** Size of the film in pixels. */
+  int2 extent;
+  /** Offset of the film in the full-res frame, in pixels. */
+  int2 offset;
+  /** Subpixel offset applied to the window matrix.
+   * NOTE: In final film pixel unit.
+   * NOTE: Positive values makes the view translate in the negative axes direction.
+   * NOTE: The origin is the center of the lower left film pixel of the area covered by a render
+   * pixel if using scaled resolution rendering.
+   */
+  float2 subpixel_offset;
+  /** Is true if history is valid and can be sampled. Bypass history to resets accumulation. */
+  bool1 use_history;
+  /** Is true if combined buffer is valid and can be re-projected to reduce variance. */
+  bool1 use_reprojection;
+  /** Is true if accumulation of non-filtered passes is needed. */
+  bool1 has_data;
+  /** Is true if accumulation of filtered passes is needed. */
+  bool1 any_render_pass_1;
+  bool1 any_render_pass_2;
+  int _pad0, _pad1;
+  /** Output counts per type. */
+  int color_len, value_len;
+  /** Index in color_accum_img or value_accum_img of each pass. -1 if pass is not enabled. */
+  int mist_id;
+  int normal_id;
+  int vector_id;
+  int diffuse_light_id;
+  int diffuse_color_id;
+  int specular_light_id;
+  int specular_color_id;
+  int volume_light_id;
+  int emission_id;
+  int environment_id;
+  int shadow_id;
+  int ambient_occlusion_id;
+  /** Not indexed but still not -1 if enabled. */
+  int depth_id;
+  int combined_id;
+  /** Id of the render-pass to be displayed. -1 for combined. */
+  int display_id;
+  /** True if the render-pass to be displayed is from the value accum buffer. */
+  bool1 display_is_value;
+  /** True if we bypass the accumulation and directly output the accumulation buffer. */
+  bool1 display_only;
+  /** Start of AOVs and number of aov. */
+  int aov_color_id, aov_color_len;
+  int aov_value_id, aov_value_len;
+  /** Settings to render mist pass */
+  float mist_scale, mist_bias, mist_exponent;
+  /** Scene exposure used for better noise reduction. */
+  float exposure;
+  /** Scaling factor for scaled resolution rendering. */
+  int scaling_factor;
+  /** Film pixel filter radius. */
+  float filter_size;
+  /** Precomputed samples. First in the table is the closest one. The rest is unordered. */
+  int samples_len;
+  /** Sum of the weights of all samples in the sample table. */
+  float samples_weight_total;
+  FilmSample samples[FILM_PRECOMP_SAMPLE_MAX];
+};
+BLI_STATIC_ASSERT_ALIGN(FilmData, 16)
+
+static inline float film_filter_weight(float filter_size, float sample_distance_sqr)
+{
+#if 1 /* Faster */
+  /* Gaussian fitted to Blackman-Harris. */
+  float r = sample_distance_sqr / (filter_size * filter_size);
+  const float sigma = 0.284;
+  const float fac = -0.5 / (sigma * sigma);
+  float weight = expf(fac * r);
+#else
+  /* Blackman-Harris filter. */
+  float r = M_2PI * saturate(0.5 + sqrtf(sample_distance_sqr) / (2.0 * filter_size));
+  float weight = 0.35875 - 0.48829 * cosf(r) + 0.14128 * cosf(2.0 * r) - 0.01168 * cosf(3.0 * r);
+#endif
+  return weight;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Arbitrary Output Variables
+ * \{ */
+
+/* Theoretical max is 128 as we are using texture array and VRAM usage.
+ * However, the output_aov() function perform a linear search inside all the hashes.
+ * If we find a way to avoid this we could bump this number up. */
+#define AOV_MAX 16
+
+/* NOTE(fclem): Needs to be used in StorageBuffer because of arrays of scalar. */
+struct AOVsInfoData {
+  uint hash_value[AOV_MAX];
+  uint hash_color[AOV_MAX];
+  /* Length of used data. */
+  uint color_len;
+  uint value_len;
+  /** Id of the AOV to be displayed (from the start of the AOV array). -1 for combined. */
+  int display_id;
+  /** True if the AOV to be displayed is from the value accum buffer. */
+  bool1 display_is_value;
+};
+BLI_STATIC_ASSERT_ALIGN(AOVsInfoData, 16)
+
+/** \} */
+
 /* -------------------------------------------------------------------- */
 /** \name VelocityModule
  * \{ */
@@ -178,10 +357,13 @@ float4 utility_tx_sample(sampler2DArray util_tx, float2 uv, float layer)
 
 #ifdef __cplusplus
 
+using AOVsInfoDataBuf = draw::StorageBuffer;
 using CameraDataBuf = draw::UniformBuffer;
+using FilmDataBuf = draw::UniformBuffer;
+using SamplingDataBuf = draw::StorageBuffer;
+using VelocityGeometryBuf = draw::StorageArrayBuffer;
 using VelocityIndexBuf = draw::StorageArrayBuffer;
 using VelocityObjectBuf = draw::StorageArrayBuffer;
-using VelocityGeometryBuf = draw::StorageArrayBuffer;
 
 }  // namespace blender::eevee
 #endif
diff --git a/source/blender/draw/engines/eevee_next/eevee_sync.cc b/source/blender/draw/engines/eevee_next/eevee_sync.cc
index 42af251d770..e2d4b0ac1c2 100644
--- a/source/blender/draw/engines/eevee_next/eevee_sync.cc
+++ b/source/blender/draw/engines/eevee_next/eevee_sync.cc
@@ -47,7 +47,7 @@ ObjectHandle &SyncModule::sync_object(Object *ob)
   const int recalc_flags = ID_RECALC_COPY_ON_WRITE | ID_RECALC_TRANSFORM | ID_RECALC_SHADING |
                            ID_RECALC_GEOMETRY;
   if ((eevee_dd.recalc & recalc_flags) != 0) {
-    // inst_.sampling.reset();
+    inst_.sampling.reset();
     UNUSED_VARS(inst_);
   }
 
@@ -63,7 +63,7 @@ WorldHandle &SyncModule::sync_world(::World *world)
 
   const int recalc_flags = ID_RECALC_ALL;
   if ((eevee_dd.recalc & recalc_flags) != 0) {
-    // inst_.sampling.reset();
+    inst_.sampling.reset();
   }
   return eevee_dd;
 }
@@ -253,7 +253,10 @@ static void gpencil_stroke_sync(bGPDlayer *UNUSED(gpl),
 void SyncModule::sync_gpencil(Object *ob, ObjectHandle &ob_handle)
 {
   /* TODO(fclem): Waiting for a user option to use the render engine instead of gpencil engine. */
-  return;
+  if (true) {
+    inst_.gpencil_engine_enabled = true;
+    return;
+  }
 
   gpIterData iter(inst_, ob, ob_handle);
 
@@ -280,7 +283,12 @@ static void shgroup_curves_call(MaterialPass &matpass,
   if (matpass.shgrp == nullptr) {
     return;
   }
-  DRW_shgroup_hair_create_sub(ob, part_sys, modifier_data, matpass.shgrp, matpass.gpumat);
+  if (part_sys != nullptr) {
+    DRW_shgroup_hair_create_sub(ob, part_sys, modifier_data, matpass.shgrp, matpass.gpumat);
+  }
+  else {
+    DRW_shgroup_curves_create_sub(ob, matpass.shgrp, matpass.gpumat);
+  }
 }
 
 void SyncModule::sync_curves(Object *ob, ObjectHandle &ob_handle, ModifierData *modifier_data)
diff --git a/source/blender/draw/engines/eevee_next/eevee_velocity.cc b/source/blender/draw/engines/eevee_next/eevee_velocity.cc
index ceae9df44d0..4bd0af8204e 100644
--- a/source/blender/draw/engines/eevee_next/eevee_velocity.cc
+++ b/source/blender/draw/engines/eevee_next/eevee_velocity.cc
@@ -162,7 +162,7 @@ bool VelocityModule::step_object_sync(Object *ob,
   }
 
   /* TODO(@fclem): Reset sampling here? Should ultimately be covered by depsgraph update tags. */
-  // inst_.sampling.reset();
+  inst_.sampling.reset();
 
   return true;
 }
@@ -264,7 +264,7 @@ void VelocityModule::end_sync()
   }
 
   if (deleted_obj.size() > 0) {
-    // inst_.sampling.reset();
+    inst_.sampling.reset();
   }
 
   for (auto key : deleted_obj) {
diff --git a/source/blender/draw/engines/eevee_next/eevee_view.cc b/source/blender/draw/engines/eevee_next/eevee_view.cc
index e21342c5ef6..f4dba47721d 100644
--- a/source/blender/draw/engines/eevee_next/eevee_view.cc
+++ b/source/blender/draw/engines/eevee_next/eevee_view.cc
@@ -34,17 +34,19 @@ void ShadingView::init()
   // mb_.init();
 }
 
-void ShadingView::sync(int2 render_extent_)
+void ShadingView::sync()
 {
+  int2 render_extent = inst_.film.render_extent_get();
+
   if (false /* inst_.camera.is_panoramic() */) {
-    int64_t render_pixel_count = render_extent_.x * (int64_t)render_extent_.y;
+    int64_t render_pixel_count = render_extent.x * (int64_t)render_extent.y;
     /* Divide pixel count between the 6 views. Rendering to a square target. */
     extent_[0] = extent_[1] = ceilf(sqrtf(1 + (render_pixel_count / 6)));
     /* TODO(@fclem): Clip unused views here. */
     is_enabled_ = true;
   }
   else {
-    extent_ = render_extent_;
+    extent_ = render_extent;
     /* Only enable -Z view. */
     is_enabled_ = (StringRefNull(name_) == "negZ_view");
   }
@@ -54,31 +56,23 @@ void ShadingView::sync(int2 render_extent_)
   }
 
   /* Create views. */
-  // const CameraData &data = inst_.camera.data_get();
+  const CameraData &cam = inst_.camera.data_get();
 
   float4x4 viewmat, winmat;
   const float(*viewmat_p)[4] = viewmat.ptr(), (*winmat_p)[4] = winmat.ptr();
-#if 0
   if (false /* inst_.camera.is_panoramic() */) {
     /* TODO(@fclem) Over-scans. */
     /* For now a mandatory 5% over-scan for DoF. */
-    float side = data.clip_near * 1.05f;
-    float near = data.clip_near;
-    float far = data.clip_far;
+    float side = cam.clip_near * 1.05f;
+    float near = cam.clip_near;
+    float far = cam.clip_far;
     perspective_m4(winmat.ptr(), -side, side, -side, side, near, far);
-    viewmat = face_matrix_ * data.viewmat;
+    viewmat = face_matrix_ * cam.viewmat;
   }
   else {
-    viewmat_p = data.viewmat.ptr();
-    winmat_p = data.winmat.ptr();
+    viewmat_p = cam.viewmat.ptr();
+    winmat_p = cam.winmat.ptr();
   }
-#else
-  /* TEMP */
-  UNUSED_VARS(face_matrix_);
-  const DRWView *default_view = DRW_view_default_get();
-  DRW_view_winmat_get(default_view, winmat.ptr(), false);
-  DRW_view_viewmat_get(default_view, viewmat.ptr(), false);
-#endif
 
   main_view_ = DRW_view_create(viewmat_p, winmat_p, nullptr, nullptr, nullptr);
   sub_view_ = DRW_view_create_sub(main_view_, viewmat_p, winmat_p);
@@ -93,7 +87,6 @@ void ShadingView::sync(int2 render_extent_)
   // inst_.hiz_front.view_sync(extent_);
   // inst_.gbuffer.view_sync(extent_);
 
-  combined_tx_.sync();
   postfx_tx_.sync();
 }
 
@@ -108,22 +101,18 @@ void ShadingView::render()
    * With this, we can reuse the same texture across views. */
   DrawEngineType *owner = (DrawEngineType *)name_;
 
-  DefaultTextureList *dtxl = DRW_viewport_texture_list_get();
-
-  depth_tx_.ensure_2d(GPU_DEPTH24_STENCIL8, extent_);
-  combined_tx_.acquire(extent_, GPU_RGBA16F, owner);
+  RenderBuffers &rbufs = inst_.render_buffers;
+  rbufs.acquire(extent_, owner);
   velocity_.acquire(extent_);
-  // combined_fb_.ensure(GPU_ATTACHMENT_TEXTURE(depth_tx_), GPU_ATTACHMENT_TEXTURE(combined_tx_));
-  // prepass_fb_.ensure(GPU_ATTACHMENT_TEXTURE(depth_tx_),
-  //                    GPU_ATTACHMENT_TEXTURE(velocity_.view_vectors_get()));
-  combined_fb_.ensure(GPU_ATTACHMENT_TEXTURE(dtxl->depth), GPU_ATTACHMENT_TEXTURE(dtxl->color));
-  prepass_fb_.ensure(GPU_ATTACHMENT_TEXTURE(dtxl->depth),
+  combined_fb_.ensure(GPU_ATTACHMENT_TEXTURE(rbufs.depth_tx),
+                      GPU_ATTACHMENT_TEXTURE(rbufs.combined_tx));
+  prepass_fb_.ensure(GPU_ATTACHMENT_TEXTURE(rbufs.depth_tx),
                      GPU_ATTACHMENT_TEXTURE(velocity_.view_vectors_get()));
 
   update_view();
 
   DRW_stats_group_start(name_);
-  // DRW_view_set_active(render_view_);
+  DRW_view_set_active(render_view_);
 
   float4 clear_velocity(VELOCITY_INVALID);
   GPU_framebuffer_bind(prepass_fb_);
@@ -142,31 +131,22 @@ void ShadingView::render()
 
   // inst_.lookdev.render_overlay(view_fb_);
 
-  inst_.pipelines.forward.render(render_view_, prepass_fb_, combined_fb_, depth_tx_, combined_tx_);
+  inst_.pipelines.forward.render(
+      render_view_, prepass_fb_, combined_fb_, rbufs.depth_tx, rbufs.combined_tx);
 
   // inst_.lights.debug_draw(view_fb_);
   // inst_.shadows.debug_draw(view_fb_);
 
-  // velocity_.resolve(depth_tx_);
-  velocity_.resolve(dtxl->depth);
-
-  // if (inst_.render_passes.vector) {
-  //   inst_.render_passes.vector->accumulate(velocity_.camera_vectors_get(), sub_view_);
-  // }
+  velocity_.resolve(rbufs.depth_tx);
 
   // GPUTexture *final_radiance_tx = render_post(combined_tx_);
 
-  // if (inst_.render_passes.combined) {
-  //   inst_.render_passes.combined->accumulate(final_radiance_tx, sub_view_);
-  // }
+  inst_.film.accumulate(sub_view_);
 
-  // if (inst_.render_passes.depth) {
-  //   inst_.render_passes.depth->accumulate(depth_tx_, sub_view_);
-  // }
+  rbufs.release();
 
   DRW_stats_group_end();
 
-  combined_tx_.release();
   postfx_tx_.release();
   velocity_.release();
 }
@@ -197,11 +177,15 @@ void ShadingView::update_view()
   DRW_view_viewmat_get(main_view_, viewmat.ptr(), false);
   DRW_view_winmat_get(main_view_, winmat.ptr(), false);
 
+  /* TODO(fclem): Mixed-resolution rendering: We need to make sure we render with exactly the same
+   * distances between pixels to line up render samples and target pixels.
+   * So if the target resolution is not a multiple of the resolution divisor, we need to make the
+   * projection window bigger in the +X and +Y directions. */
+
   /* Anti-Aliasing / Super-Sampling jitter. */
-  // float jitter_u = 2.0f * (inst_.sampling.rng_get(SAMPLING_FILTER_U) - 0.5f) / extent_[0];
-  // float jitter_v = 2.0f * (inst_.sampling.rng_get(SAMPLING_FILTER_V) - 0.5f) / extent_[1];
+  float2 jitter = inst_.film.pixel_jitter_get() / float2(extent_);
 
-  // window_translate_m4(winmat.ptr(), winmat.ptr(), jitter_u, jitter_v);
+  window_translate_m4(winmat.ptr(), winmat.ptr(), UNPACK2(jitter));
   DRW_view_update_sub(sub_view_, viewmat.ptr(), winmat.ptr());
 
   /* FIXME(fclem): The offset may be is noticeably large and the culling might make object pop
diff --git a/source/blender/draw/engines/eevee_next/eevee_view.hh b/source/blender/draw/engines/eevee_next/eevee_view.hh
index fb74412f557..30e06df9716 100644
--- a/source/blender/draw/engines/eevee_next/eevee_view.hh
+++ b/source/blender/draw/engines/eevee_next/eevee_view.hh
@@ -52,8 +52,6 @@ class ShadingView {
 
   Framebuffer prepass_fb_;
   Framebuffer combined_fb_;
-  Texture depth_tx_;
-  TextureFromPool combined_tx_;
   TextureFromPool postfx_tx_;
 
   /** Main views is created from the camera (or is from the viewport). It is not jittered. */
@@ -77,7 +75,7 @@ class ShadingView {
 
   void init();
 
-  void sync(int2 render_extent_);
+  void sync();
 
   void render();
 
@@ -94,7 +92,7 @@ class ShadingView {
  *
  * Container for all views needed to render the final image.
  * We might need up to 6 views for panoramic cameras.
- * All views are always available but only enabled for if need.
+ * All views are always available but only enabled for if needed.
  * \{ */
 
 class MainView {
@@ -109,8 +107,6 @@ class MainView {
   ShadingView shading_views_4;
   ShadingView shading_views_5;
 #define shading_views_ (&shading_views_0)
-  /** Internal render size. */
-  int render_extent_[2];
 
  public:
   MainView(Instance &inst)
@@ -123,15 +119,8 @@ class MainView {
   {
   }
 
-  void init(const int2 full_extent_)
+  void init()
   {
-    /* TODO(fclem) parameter hidden in experimental. We need to figure out mipmap bias to preserve
-     * texture crispiness. */
-    float resolution_scale = 1.0f;
-    for (int i = 0; i < 2; i++) {
-      render_extent_[i] = max_ii(1, roundf(full_extent_[i] * resolution_scale));
-    }
-
     for (auto i : IndexRange(6)) {
       shading_views_[i].init();
     }
@@ -140,7 +129,7 @@ class MainView {
   void sync()
   {
     for (auto i : IndexRange(6)) {
-      shading_views_[i].sync(render_extent_);
+      shading_views_[i].sync();
     }
   }
 
diff --git a/source/blender/draw/engines/eevee_next/eevee_world.cc b/source/blender/draw/engines/eevee_next/eevee_world.cc
index b9cb24fe30a..56cb0f127db 100644
--- a/source/blender/draw/engines/eevee_next/eevee_world.cc
+++ b/source/blender/draw/engines/eevee_next/eevee_world.cc
@@ -79,7 +79,7 @@ void World::sync()
   /* TODO(fclem) This should be detected to scene level. */
   ::World *orig_world = (::World *)DEG_get_original_id(&bl_world->id);
   if (assign_if_different(prev_original_world, orig_world)) {
-    // inst_.sampling.reset();
+    inst_.sampling.reset();
   }
 
   bNodeTree *ntree = (bl_world->nodetree && bl_world->use_nodes) ?
diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_film_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_film_comp.glsl
new file mode 100644
index 00000000000..ce1f19edf53
--- /dev/null
+++ b/source/blender/draw/engines/eevee_next/shaders/eevee_film_comp.glsl
@@ -0,0 +1,13 @@
+
+#pragma BLENDER_REQUIRE(common_view_lib.glsl)
+#pragma BLENDER_REQUIRE(eevee_film_lib.glsl)
+
+void main()
+{
+  ivec2 texel_film = ivec2(gl_GlobalInvocationID.xy);
+  /* Not used. */
+  vec4 out_color;
+  float out_depth;
+
+  film_process_data(texel_film, out_color, out_depth);
+}
diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_film_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_film_frag.glsl
new file mode 100644
index 00000000000..6716c0f126e
--- /dev/null
+++ b/source/blender/draw/engines/eevee_next/shaders/eevee_film_frag.glsl
@@ -0,0 +1,29 @@
+
+#pragma BLENDER_REQUIRE(common_view_lib.glsl)
+#pragma BLENDER_REQUIRE(eevee_film_lib.glsl)
+
+void main()
+{
+  ivec2 texel_film = ivec2(gl_FragCoord.xy);
+  float out_depth;
+
+  if (film_buf.display_only) {
+    out_depth = imageLoad(depth_img, texel_film).r;
+
+    if (film_buf.display_id == -1) {
+      out_color = imageLoad(in_combined_img, texel_film);
+    }
+    else if (film_buf.display_is_value) {
+      out_color.rgb = imageLoad(value_accum_img, ivec3(texel_film, film_buf.display_id)).rrr;
+      out_color.a = 1.0;
+    }
+    else {
+      out_color = imageLoad(color_accum_img, ivec3(texel_film, film_buf.display_id));
+    }
+  }
+  else {
+    film_process_data(texel_film, out_color, out_depth);
+  }
+
+  gl_FragDepth = get_depth_from_view_z(-out_depth);
+}
diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl
new file mode 100644
index 00000000000..03af34f27ef
--- /dev/null
+++ b/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl
@@ -0,0 +1,387 @@
+
+/**
+ * Film accumulation utils functions.
+ **/
+
+#pragma BLENDER_REQUIRE(common_view_lib.glsl)
+#pragma BLENDER_REQUIRE(eevee_camera_lib.glsl)
+
+/* Return scene linear Z depth from the camera or radial depth for panoramic cameras. */
+float film_depth_convert_to_scene(float depth)
+{
+  if (false /* Panoramic */) {
+    /* TODO */
+    return 1.0;
+  }
+  return abs(get_view_z_from_depth(depth));
+}
+
+/* -------------------------------------------------------------------- */
+/** \name Filter
+ * \{ */
+
+FilmSample film_sample_get(int sample_n, ivec2 texel_film)
+{
+#ifdef PANORAMIC
+  /* TODO(fclem): Panoramic projection will be more complex. The samples will have to be retrieve
+   * at runtime, maybe by scanning a whole region. Offset and weight will have to be computed by
+   * reprojecting the incoming pixel data into film pixel space. */
+#else
+
+#  ifdef SCALED_RENDERING
+  texel_film /= film_buf.scaling_factor;
+#  endif
+
+  FilmSample film_sample = film_buf.samples[sample_n];
+  film_sample.texel += texel_film;
+  /* Use extend on borders. */
+  film_sample.texel = clamp(film_sample.texel, ivec2(0, 0), film_buf.extent - 1);
+
+  /* TODO(fclem): Panoramic projection will need to compute the sample weight in the shader
+   * instead of precomputing it on CPU. */
+#  ifdef SCALED_RENDERING
+  /* We need to compute the real distance and weight since a sample
+   * can be used by many final pixel. */
+  vec2 offset = film_buf.subpixel_offset - vec2(texel_film % film_buf.scaling_factor);
+  film_sample.weight = film_filter_weight(film_buf.filter_size, len_squared(offset));
+#  endif
+
+#endif /* PANORAMIC */
+
+  /* Always return a weight above 0 to avoid blind spots between samples. */
+  film_sample.weight = max(film_sample.weight, 1e-6);
+
+  return film_sample;
+}
+
+/* Returns the combined weights of all samples affecting this film pixel. */
+float film_weight_accumulation(ivec2 texel_film)
+{
+#if 0 /* TODO(fclem): Reference implementation, also needed for panoramic cameras. */
+  float weight = 0.0;
+  for (int i = 0; i < film_buf.samples_len; i++) {
+    weight += film_sample_get(i, texel_film).weight;
+  }
+  return weight;
+#endif
+  return film_buf.samples_weight_total;
+}
+
+void film_sample_accum(FilmSample samp, int pass_id, sampler2D tex, inout vec4 accum)
+{
+  if (pass_id == -1) {
+    return;
+  }
+  accum += texelFetch(tex, samp.texel, 0) * samp.weight;
+}
+
+void film_sample_accum(FilmSample samp, int pass_id, sampler2D tex, inout float accum)
+{
+  if (pass_id == -1) {
+    return;
+  }
+  accum += texelFetch(tex, samp.texel, 0).x * samp.weight;
+}
+
+void film_sample_accum(FilmSample samp, int pass_id, sampler2DArray tex, inout vec4 accum)
+{
+  if (pass_id == -1) {
+    return;
+  }
+  accum += texelFetch(tex, ivec3(samp.texel, pass_id), 0) * samp.weight;
+}
+
+void film_sample_accum(FilmSample samp, int pass_id, sampler2DArray tex, inout float accum)
+{
+  if (pass_id == -1) {
+    return;
+  }
+  accum += texelFetch(tex, ivec3(samp.texel, pass_id), 0).x * samp.weight;
+}
+
+void film_sample_accum_mist(FilmSample samp, inout float accum)
+{
+  if (film_buf.mist_id == -1) {
+    return;
+  }
+  float depth = texelFetch(depth_tx, samp.texel, 0).x;
+  vec2 uv = (vec2(samp.texel) + 0.5) / textureSize(depth_tx, 0).xy;
+  vec3 vP = get_view_space_from_depth(uv, depth);
+  bool is_persp = ProjectionMatrix[3][3] == 0.0;
+  float mist = (is_persp) ? length(vP) : abs(vP.z);
+  /* Remap to 0..1 range. */
+  mist = saturate(mist * film_buf.mist_scale + film_buf.mist_bias);
+  /* Falloff. */
+  mist = pow(mist, film_buf.mist_exponent);
+  accum += mist * samp.weight;
+}
+
+void film_sample_accum_combined(FilmSample samp, inout vec4 accum)
+{
+  if (film_buf.combined_id == -1) {
+    return;
+  }
+  vec4 color = texelFetch(combined_tx, samp.texel, 0);
+  /* Convert transmittance to opacity. */
+  color.a = saturate(1.0 - color.a);
+  /* TODO(fclem) Pre-expose. */
+  color.rgb = log2(1.0 + color.rgb);
+
+  accum += color * samp.weight;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Load/Store Data
+ * \{ */
+
+#define WEIGHT_lAYER_ACCUMULATION 0
+#define WEIGHT_lAYER_DISTANCE 1
+
+/* Returns the distance used to store nearest interpolation data. */
+float film_distance_load(ivec2 texel)
+{
+  /* Repeat texture coordinates as the weight can be optimized to a small portion of the film. */
+  texel = texel % imageSize(in_weight_img).xy;
+
+  if (film_buf.use_history == false) {
+    return 1.0e16;
+  }
+  return imageLoad(in_weight_img, ivec3(texel, WEIGHT_lAYER_DISTANCE)).x;
+}
+
+float film_weight_load(ivec2 texel)
+{
+  /* Repeat texture coordinates as the weight can be optimized to a small portion of the film. */
+  texel = texel % imageSize(in_weight_img).xy;
+
+  if (film_buf.use_history == false) {
+    return 0.0;
+  }
+  return imageLoad(in_weight_img, ivec3(texel, WEIGHT_lAYER_ACCUMULATION)).x;
+}
+
+/* Return the motion in pixels. */
+void film_motion_load()
+{
+  // ivec2 texel_sample = film_sample_get(0, texel_film, distance_sample);
+  // vec4 vector = texelFetch(vector_tx, texel_sample);
+
+  // vector.xy *= film_buf.extent;
+}
+
+/* Returns resolved final color. */
+void film_store_combined(FilmSample dst, vec4 color, inout vec4 display)
+{
+  if (film_buf.combined_id == -1) {
+    return;
+  }
+
+  /* Could we assume safe color from earlier pass? */
+  color = safe_color(color);
+  if (false) {
+    /* Re-projection using motion vectors. */
+    // ivec2 texel_combined = texel_film + film_motion_load(texel_film);
+    // float weight_combined = film_weight_load(texel_combined);
+  }
+#ifdef USE_NEIGHBORHOOD_CLAMPING
+  /* Only do that for combined pass as it has a non-negligeable performance impact. */
+  // color = clamp_bbox(color, min, max);
+#endif
+
+  vec4 dst_color = imageLoad(in_combined_img, dst.texel);
+
+  color = (dst_color * dst.weight + color) * dst.weight_sum_inv;
+
+  /* TODO(fclem) undo Pre-expose. */
+  // color.rgb = exp2(color.rgb) - 1.0;
+
+  if (film_buf.display_id == -1) {
+    display = color;
+  }
+  imageStore(out_combined_img, dst.texel, color);
+}
+
+void film_store_color(FilmSample dst, int pass_id, vec4 color, inout vec4 display)
+{
+  if (pass_id == -1) {
+    return;
+  }
+
+  vec4 data_film = imageLoad(color_accum_img, ivec3(dst.texel, pass_id));
+
+  color = (data_film * dst.weight + color) * dst.weight_sum_inv;
+
+  if (film_buf.display_id == pass_id) {
+    display = color;
+  }
+  imageStore(color_accum_img, ivec3(dst.texel, pass_id), color);
+}
+
+void film_store_value(FilmSample dst, int pass_id, float value, inout vec4 display)
+{
+  if (pass_id == -1) {
+    return;
+  }
+
+  float data_film = imageLoad(value_accum_img, ivec3(dst.texel, pass_id)).x;
+
+  value = (data_film * dst.weight + value) * dst.weight_sum_inv;
+
+  if (film_buf.display_id == pass_id) {
+    display = vec4(value, value, value, 1.0);
+  }
+  imageStore(value_accum_img, ivec3(dst.texel, pass_id), vec4(value));
+}
+
+/* Nearest sample variant. Always stores the data. */
+void film_store_data(ivec2 texel_film, int pass_id, vec4 data_sample, inout vec4 display)
+{
+  if (pass_id == -1) {
+    return;
+  }
+
+  if (film_buf.display_id == pass_id) {
+    display = data_sample;
+  }
+  imageStore(color_accum_img, ivec3(texel_film, pass_id), data_sample);
+}
+
+void film_store_depth(ivec2 texel_film, float value, out float out_depth)
+{
+  if (film_buf.depth_id == -1) {
+    return;
+  }
+
+  out_depth = film_depth_convert_to_scene(value);
+
+  imageStore(depth_img, texel_film, vec4(out_depth));
+}
+
+void film_store_distance(ivec2 texel, float value)
+{
+  imageStore(out_weight_img, ivec3(texel, WEIGHT_lAYER_DISTANCE), vec4(value));
+}
+
+void film_store_weight(ivec2 texel, float value)
+{
+  imageStore(out_weight_img, ivec3(texel, WEIGHT_lAYER_ACCUMULATION), vec4(value));
+}
+
+/** \} */
+
+/** NOTE: out_depth is scene linear depth from the camera origin. */
+void film_process_data(ivec2 texel_film, out vec4 out_color, out float out_depth)
+{
+  out_color = vec4(0.0);
+  out_depth = 0.0;
+
+  float weight_accum = film_weight_accumulation(texel_film);
+  float film_weight = film_weight_load(texel_film);
+  float weight_sum = film_weight + weight_accum;
+  film_store_weight(texel_film, weight_sum);
+
+  FilmSample dst;
+  dst.texel = texel_film;
+  dst.weight = film_weight;
+  dst.weight_sum_inv = 1.0 / weight_sum;
+
+  /* NOTE: We split the accumulations into separate loops to avoid using too much registers and
+   * maximize occupancy. */
+
+  if (film_buf.has_data) {
+    float film_weight = film_distance_load(texel_film);
+
+    /* Get sample closest to target texel. It is always sample 0. */
+    FilmSample film_sample = film_sample_get(0, texel_film);
+
+    if (film_sample.weight < film_weight) {
+      float depth = texelFetch(depth_tx, film_sample.texel, 0).x;
+      vec4 normal = texelFetch(normal_tx, film_sample.texel, 0);
+      vec4 vector = texelFetch(vector_tx, film_sample.texel, 0);
+
+      film_store_depth(texel_film, depth, out_depth);
+      film_store_data(texel_film, film_buf.normal_id, normal, out_color);
+      film_store_data(texel_film, film_buf.vector_id, vector, out_color);
+      film_store_distance(texel_film, film_sample.weight);
+    }
+    else {
+      out_depth = imageLoad(depth_img, texel_film).r;
+    }
+  }
+
+  if (film_buf.combined_id != -1) {
+    vec4 combined_accum = vec4(0.0);
+
+    for (int i = 0; i < film_buf.samples_len; i++) {
+      FilmSample src = film_sample_get(i, texel_film);
+      film_sample_accum_combined(src, combined_accum);
+    }
+    film_store_combined(dst, combined_accum, out_color);
+  }
+
+  if (film_buf.any_render_pass_1) {
+    vec4 diffuse_light_accum = vec4(0.0);
+    vec4 specular_light_accum = vec4(0.0);
+    vec4 volume_light_accum = vec4(0.0);
+    vec4 emission_accum = vec4(0.0);
+
+    for (int i = 0; i < film_buf.samples_len; i++) {
+      FilmSample src = film_sample_get(i, texel_film);
+      film_sample_accum(src, film_buf.diffuse_light_id, diffuse_light_tx, diffuse_light_accum);
+      film_sample_accum(src, film_buf.specular_light_id, specular_light_tx, specular_light_accum);
+      film_sample_accum(src, film_buf.volume_light_id, volume_light_tx, volume_light_accum);
+      film_sample_accum(src, film_buf.emission_id, emission_tx, emission_accum);
+    }
+    film_store_color(dst, film_buf.diffuse_light_id, diffuse_light_accum, out_color);
+    film_store_color(dst, film_buf.specular_light_id, specular_light_accum, out_color);
+    film_store_color(dst, film_buf.volume_light_id, volume_light_accum, out_color);
+    film_store_color(dst, film_buf.emission_id, emission_accum, out_color);
+  }
+
+  if (film_buf.any_render_pass_2) {
+    vec4 diffuse_color_accum = vec4(0.0);
+    vec4 specular_color_accum = vec4(0.0);
+    vec4 environment_accum = vec4(0.0);
+    float mist_accum = 0.0;
+    float shadow_accum = 0.0;
+    float ao_accum = 0.0;
+
+    for (int i = 0; i < film_buf.samples_len; i++) {
+      FilmSample src = film_sample_get(i, texel_film);
+      film_sample_accum(src, film_buf.diffuse_color_id, diffuse_color_tx, diffuse_color_accum);
+      film_sample_accum(src, film_buf.specular_color_id, specular_color_tx, specular_color_accum);
+      film_sample_accum(src, film_buf.environment_id, environment_tx, environment_accum);
+      film_sample_accum(src, film_buf.shadow_id, shadow_tx, shadow_accum);
+      film_sample_accum(src, film_buf.ambient_occlusion_id, ambient_occlusion_tx, ao_accum);
+      film_sample_accum_mist(src, mist_accum);
+    }
+    film_store_color(dst, film_buf.diffuse_color_id, diffuse_color_accum, out_color);
+    film_store_color(dst, film_buf.specular_color_id, specular_color_accum, out_color);
+    film_store_color(dst, film_buf.environment_id, environment_accum, out_color);
+    film_store_value(dst, film_buf.shadow_id, shadow_accum, out_color);
+    film_store_value(dst, film_buf.ambient_occlusion_id, ao_accum, out_color);
+    film_store_value(dst, film_buf.mist_id, mist_accum, out_color);
+  }
+
+  for (int aov = 0; aov < film_buf.aov_color_len; aov++) {
+    vec4 aov_accum = vec4(0.0);
+
+    for (int i = 0; i < film_buf.samples_len; i++) {
+      FilmSample src = film_sample_get(i, texel_film);
+      film_sample_accum(src, aov, aov_color_tx, aov_accum);
+    }
+    film_store_color(dst, film_buf.aov_color_id + aov, aov_accum, out_color);
+  }
+
+  for (int aov = 0; aov < film_buf.aov_value_len; aov++) {
+    float aov_accum = 0.0;
+
+    for (int i = 0; i < film_buf.samples_len; i++) {
+      FilmSample src = film_sample_get(i, texel_film);
+      film_sample_accum(src, aov, aov_value_tx, aov_accum);
+    }
+    film_store_value(dst, film_buf.aov_value_id + aov, aov_accum, out_color);
+  }
+}
diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl
index 0ccf06a9e14..71921d0477a 100644
--- a/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl
+++ b/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl
@@ -245,6 +245,20 @@ float F_eta(float a, float b)
 }
 void output_aov(vec4 color, float value, uint hash)
 {
+#if defined(MAT_AOV_SUPPORT) && defined(GPU_FRAGMENT_SHADER)
+  for (int i = 0; i < AOV_MAX && i < aov_buf.color_len; i++) {
+    if (aov_buf.hash_color[i] == hash) {
+      imageStore(aov_color_img, ivec3(gl_FragCoord.xy, i), color);
+      return;
+    }
+  }
+  for (int i = 0; i < AOV_MAX && i < aov_buf.value_len; i++) {
+    if (aov_buf.hash_value[i] == hash) {
+      imageStore(aov_value_img, ivec3(gl_FragCoord.xy, i), vec4(value));
+      return;
+    }
+  }
+#endif
 }
 
 #ifdef EEVEE_MATERIAL_STUBS
diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_forward_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_forward_frag.glsl
index 143e88dbe68..48ced4e5374 100644
--- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_forward_frag.glsl
+++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_forward_frag.glsl
@@ -53,21 +53,45 @@ void main()
 
   g_holdout = saturate(g_holdout);
 
+  vec3 diffuse_light = vec3(saturate(g_diffuse_data.N.z * 0.5 + 0.5));
+  vec3 reflection_light = vec3(spec_light(g_reflection_data));
+  vec3 refraction_light = vec3(saturate(g_refraction_data.N.z * 0.5 + 0.5));
+
+  g_diffuse_data.color *= g_diffuse_data.weight;
+  g_reflection_data.color *= g_reflection_data.weight;
+  g_refraction_data.color *= g_refraction_data.weight;
+  diffuse_light *= step(1e-5, g_diffuse_data.weight);
+  reflection_light *= step(1e-5, g_reflection_data.weight);
+  refraction_light *= step(1e-5, g_refraction_data.weight);
+
   out_radiance.rgb = g_emission;
-  out_radiance.rgb += g_diffuse_data.color * g_diffuse_data.weight *
-                      saturate(g_diffuse_data.N.z * 0.5 + 0.5);
-  out_radiance.rgb += g_reflection_data.color * g_reflection_data.weight *
-                      spec_light(g_reflection_data);
-  out_radiance.rgb += g_refraction_data.color * g_refraction_data.weight *
-                      saturate(g_refraction_data.N.z * 0.5 + 0.5);
+  out_radiance.rgb += g_diffuse_data.color * diffuse_light;
+  out_radiance.rgb += g_reflection_data.color * reflection_light;
+  out_radiance.rgb += g_refraction_data.color * refraction_light;
   out_radiance.a = 0.0;
 
+  vec3 specular_light = reflection_light + refraction_light;
+  vec3 specular_color = g_reflection_data.color + g_refraction_data.color;
+
+  /* TODO(fclem): This feels way too complex for what is it. */
+  bool has_any_bsdf_weight = g_diffuse_data.weight != 0.0 || g_reflection_data.weight != 0.0 ||
+                             g_refraction_data.weight != 0.0;
+  vec3 out_normal = has_any_bsdf_weight ? vec3(0.0) : g_data.N;
+  out_normal += g_diffuse_data.N * g_diffuse_data.weight;
+  out_normal += g_reflection_data.N * g_reflection_data.weight;
+  out_normal += g_refraction_data.N * g_refraction_data.weight;
+  out_normal = safe_normalize(out_normal);
+
+  ivec2 out_texel = ivec2(gl_FragCoord.xy);
+  imageStore(rp_normal_img, out_texel, vec4(out_normal, 1.0));
+  imageStore(rp_diffuse_light_img, out_texel, vec4(diffuse_light, 1.0));
+  imageStore(rp_diffuse_color_img, out_texel, vec4(g_diffuse_data.color, 1.0));
+  imageStore(rp_specular_light_img, out_texel, vec4(specular_light, 1.0));
+  imageStore(rp_specular_color_img, out_texel, vec4(specular_color, 1.0));
+  imageStore(rp_emission_img, out_texel, vec4(g_emission, 1.0));
+
   out_radiance.rgb *= 1.0 - g_holdout;
 
   out_transmittance.rgb = g_transmittance;
   out_transmittance.a = saturate(avg(g_transmittance));
-
-  /* Test */
-  out_transmittance.a = 1.0 - out_transmittance.a;
-  out_radiance.a = 1.0 - out_radiance.a;
 }
diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_world_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_world_frag.glsl
index ac657afc922..b32c3c1c4eb 100644
--- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_world_frag.glsl
+++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_world_frag.glsl
@@ -24,6 +24,14 @@ void main()
 
   g_holdout = saturate(g_holdout);
 
+  ivec2 out_texel = ivec2(gl_FragCoord.xy);
+  imageStore(rp_normal_img, out_texel, vec4(0.0, 0.0, 0.0, 1.0));
+  imageStore(rp_diffuse_light_img, out_texel, vec4(0.0, 0.0, 0.0, 1.0));
+  imageStore(rp_diffuse_color_img, out_texel, vec4(0.0, 0.0, 0.0, 1.0));
+  imageStore(rp_specular_light_img, out_texel, vec4(0.0, 0.0, 0.0, 1.0));
+  imageStore(rp_specular_color_img, out_texel, vec4(0.0, 0.0, 0.0, 1.0));
+  imageStore(rp_emission_img, out_texel, vec4(0.0, 0.0, 0.0, 1.0));
+
   out_background.rgb = safe_color(g_emission) * (1.0 - g_holdout);
   out_background.a = saturate(avg(g_transmittance)) * g_holdout;
 }
diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_film_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_film_info.hh
new file mode 100644
index 00000000000..eec7b8ae615
--- /dev/null
+++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_film_info.hh
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "eevee_defines.hh"
+#include "gpu_shader_create_info.hh"
+
+GPU_SHADER_CREATE_INFO(eevee_film)
+    .uniform_buf(1, "FilmData", "film_buf")
+    .sampler(0, ImageType::DEPTH_2D, "depth_tx")
+    .sampler(1, ImageType::FLOAT_2D, "combined_tx")
+    .sampler(2, ImageType::FLOAT_2D, "normal_tx")
+    .sampler(3, ImageType::FLOAT_2D, "vector_tx")
+    .sampler(4, ImageType::FLOAT_2D, "diffuse_light_tx")
+    .sampler(5, ImageType::FLOAT_2D, "diffuse_color_tx")
+    .sampler(6, ImageType::FLOAT_2D, "specular_light_tx")
+    .sampler(7, ImageType::FLOAT_2D, "specular_color_tx")
+    .sampler(8, ImageType::FLOAT_2D, "volume_light_tx")
+    .sampler(9, ImageType::FLOAT_2D, "emission_tx")
+    .sampler(10, ImageType::FLOAT_2D, "environment_tx")
+    .sampler(11, ImageType::FLOAT_2D, "shadow_tx")
+    .sampler(12, ImageType::FLOAT_2D, "ambient_occlusion_tx")
+    .sampler(13, ImageType::FLOAT_2D_ARRAY, "aov_color_tx")
+    .sampler(14, ImageType::FLOAT_2D_ARRAY, "aov_value_tx")
+    // .sampler(15, ImageType::FLOAT_2D, "cryptomatte_tx") /* TODO */
+    .image(0, GPU_R32F, Qualifier::READ, ImageType::FLOAT_2D_ARRAY, "in_weight_img")
+    .image(1, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_2D_ARRAY, "out_weight_img")
+    .image(2, GPU_RGBA16F, Qualifier::READ, ImageType::FLOAT_2D, "in_combined_img")
+    .image(3, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_combined_img")
+    .image(4, GPU_R32F, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "depth_img")
+    .image(5, GPU_RGBA16F, Qualifier::READ_WRITE, ImageType::FLOAT_2D_ARRAY, "color_accum_img")
+    .image(6, GPU_R16F, Qualifier::READ_WRITE, ImageType::FLOAT_2D_ARRAY, "value_accum_img")
+    .additional_info("eevee_shared")
+    .additional_info("draw_view");
+
+GPU_SHADER_CREATE_INFO(eevee_film_frag)
+    .do_static_compilation(true)
+    .fragment_out(0, Type::VEC4, "out_color")
+    .fragment_source("eevee_film_frag.glsl")
+    .additional_info("draw_fullscreen", "eevee_film");
+
+GPU_SHADER_CREATE_INFO(eevee_film_comp)
+    .do_static_compilation(true)
+    .local_group_size(FILM_GROUP_SIZE, FILM_GROUP_SIZE)
+    .compute_source("eevee_film_comp.glsl")
+    .additional_info("eevee_film");
diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh
index d9a6b6efd0c..950164f5b86 100644
--- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh
+++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh
@@ -70,6 +70,14 @@ GPU_SHADER_INTERFACE_INFO(eevee_surf_iface, "interp")
 
 #define image_out(slot, qualifier, format, name) \
   image(slot, format, qualifier, ImageType::FLOAT_2D, name, Frequency::PASS)
+#define image_array_out(slot, qualifier, format, name) \
+  image(slot, format, qualifier, ImageType::FLOAT_2D_ARRAY, name, Frequency::PASS)
+
+GPU_SHADER_CREATE_INFO(eevee_aov_out)
+    .define("MAT_AOV_SUPPORT")
+    .image_array_out(6, Qualifier::WRITE, GPU_RGBA16F, "aov_color_img")
+    .image_array_out(7, Qualifier::WRITE, GPU_R16F, "aov_value_img")
+    .storage_buf(7, Qualifier::READ, "AOVsInfoData", "aov_buf");
 
 GPU_SHADER_CREATE_INFO(eevee_surf_deferred)
     .vertex_out(eevee_surf_iface)
@@ -89,27 +97,34 @@ GPU_SHADER_CREATE_INFO(eevee_surf_deferred)
     // .image_out(6, Qualifier::READ_WRITE, GPU_RGBA16F, "rpass_volume_light")
     /* TODO: AOVs maybe? */
     .fragment_source("eevee_surf_deferred_frag.glsl")
-    // .additional_info("eevee_sampling_data", "eevee_utility_texture")
+    // .additional_info("eevee_aov_out", "eevee_sampling_data", "eevee_utility_texture")
     ;
 
-#undef image_out
-
 GPU_SHADER_CREATE_INFO(eevee_surf_forward)
     .auto_resource_location(true)
     .vertex_out(eevee_surf_iface)
+    /* Early fragment test is needed for render passes support for forward surfaces. */
+    /* NOTE: This removes the possibility of using gl_FragDepth. */
+    .early_fragment_test(true)
     .fragment_out(0, Type::VEC4, "out_radiance", DualBlend::SRC_0)
     .fragment_out(0, Type::VEC4, "out_transmittance", DualBlend::SRC_1)
     .fragment_source("eevee_surf_forward_frag.glsl")
-    // .additional_info("eevee_sampling_data",
-    //  "eevee_lightprobe_data",
-    /* Optionally added depending on the material. */
-    // "eevee_raytrace_data",
-    // "eevee_transmittance_data",
-    //  "eevee_utility_texture",
-    //  "eevee_light_data",
-    //  "eevee_shadow_data"
-    // )
-    ;
+    .image_out(0, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_normal_img")
+    .image_out(1, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_diffuse_light_img")
+    .image_out(2, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_diffuse_color_img")
+    .image_out(3, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_specular_light_img")
+    .image_out(4, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_specular_color_img")
+    .image_out(5, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_emission_img")
+    .additional_info("eevee_aov_out"
+                     //  "eevee_sampling_data",
+                     //  "eevee_lightprobe_data",
+                     /* Optionally added depending on the material. */
+                     // "eevee_raytrace_data",
+                     // "eevee_transmittance_data",
+                     //  "eevee_utility_texture",
+                     //  "eevee_light_data",
+                     //  "eevee_shadow_data"
+    );
 
 GPU_SHADER_CREATE_INFO(eevee_surf_depth)
     .vertex_out(eevee_surf_iface)
@@ -119,10 +134,20 @@ GPU_SHADER_CREATE_INFO(eevee_surf_depth)
 
 GPU_SHADER_CREATE_INFO(eevee_surf_world)
     .vertex_out(eevee_surf_iface)
+    .image_out(0, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_normal_img")
+    .image_out(1, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_diffuse_light_img")
+    .image_out(2, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_diffuse_color_img")
+    .image_out(3, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_specular_light_img")
+    .image_out(4, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_specular_color_img")
+    .image_out(5, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_emission_img")
     .fragment_out(0, Type::VEC4, "out_background")
     .fragment_source("eevee_surf_world_frag.glsl")
-    // .additional_info("eevee_utility_texture")
-    ;
+    .additional_info("eevee_aov_out"
+                     //"eevee_utility_texture"
+    );
+
+#undef image_out
+#undef image_array_out
 
 /** \} */
 
diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_velocity_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_velocity_info.hh
index a5f16363466..c6cbf9b1456 100644
--- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_velocity_info.hh
+++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_velocity_info.hh
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 
 #include "gpu_shader_create_info.hh"
 
diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt
index d8ed74390f4..62d5537772a 100644
--- a/source/blender/gpu/CMakeLists.txt
+++ b/source/blender/gpu/CMakeLists.txt
@@ -25,6 +25,9 @@ set(INC
   # For theme color access.
   ../editors/include
 
+  # For *_info.hh includes.
+  ../draw/engines/eevee_next
+
   # For node muting stuff.
   ../nodes
 
@@ -445,6 +448,7 @@ list(APPEND INC ${CMAKE_CURRENT_BINARY_DIR})
 
 set(SRC_SHADER_CREATE_INFOS
   ../draw/engines/basic/shaders/infos/basic_depth_info.hh
+  ../draw/engines/eevee_next/shaders/infos/eevee_film_info.hh
   ../draw/engines/eevee_next/shaders/infos/eevee_material_info.hh
   ../draw/engines/eevee_next/shaders/infos/eevee_velocity_info.hh
   ../draw/engines/gpencil/shaders/infos/gpencil_info.hh
diff --git a/source/blender/gpu/GPU_shader_shared_utils.h b/source/blender/gpu/GPU_shader_shared_utils.h
index 474549d1f42..88bdad2bf76 100644
--- a/source/blender/gpu/GPU_shader_shared_utils.h
+++ b/source/blender/gpu/GPU_shader_shared_utils.h
@@ -41,6 +41,7 @@
 #  define floorf floor
 #  define ceilf ceil
 #  define sqrtf sqrt
+#  define expf exp
 
 #  define float2 vec2
 #  define float3 vec3
diff --git a/source/blender/makesdna/DNA_layer_types.h b/source/blender/makesdna/DNA_layer_types.h
index 4ee5f34fcde..0af50b2bd4f 100644
--- a/source/blender/makesdna/DNA_layer_types.h
+++ b/source/blender/makesdna/DNA_layer_types.h
@@ -35,6 +35,7 @@ typedef enum eViewLayerEEVEEPassType {
   EEVEE_RENDER_PASS_BLOOM = (1 << 14),
   EEVEE_RENDER_PASS_AOV = (1 << 15),
   EEVEE_RENDER_PASS_CRYPTOMATTE = (1 << 16),
+  EEVEE_RENDER_PASS_VECTOR = (1 << 17),
 } eViewLayerEEVEEPassType;
 #define EEVEE_RENDER_PASS_MAX_BIT 17
 
-- 
cgit v1.2.3


From 106d937a4e1ebafff244c67f208e4c651a11ed3a Mon Sep 17 00:00:00 2001
From: Gaia Clary 
Date: Thu, 30 Jun 2022 22:12:38 +0200
Subject: COLLADA: Support for alpha color in vertex data. Many thanks to the
 original Author of this patch: Christian Aguilera

The COLLADA importer was silently ignoring the alpha component in the
vertex data.

The `stride` variable holds the component count (3 for RGB; 4 for RGBA),
and can be used for honouring the alpha channel in the vertex data.

Test plan:
- Open Blender.
- Clear the scene.
- Add a plane.
- Enter **Vertex Paint** mode.
- Switch to the **Erase Alpha** blending mode.
- Select a tone of gray.
- Turn strength down to less than 1
- Paint [some of] the vertices of the plane.
- Export project as a COLLADA file (`.dae`).
- Clear the scene.
- Re-import the COLLADA file again.
- Export the project again (with different name).

**Without** this patch, the second exported project will have lost the
alpha component in their vertex data:

```lang=xml, counterexample
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
```

**With** the patch, the first and the second exported projects retain
the alpha values painted previously:

```lang=xml
1 1 1 1 1 1 1 0.5490196 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0.5490196
```

Reviewed By: cristian64, SonnyCampbell_Unity
Authored by: Christian Aguilera

Differential Revision: https://developer.blender.org/D14246
---
 source/blender/io/collada/MeshImporter.cpp | 38 +++++++++++++++++++-----------
 1 file changed, 24 insertions(+), 14 deletions(-)

diff --git a/source/blender/io/collada/MeshImporter.cpp b/source/blender/io/collada/MeshImporter.cpp
index 6e109353be8..fa0348fbcf2 100644
--- a/source/blender/io/collada/MeshImporter.cpp
+++ b/source/blender/io/collada/MeshImporter.cpp
@@ -145,6 +145,27 @@ VCOLDataWrapper::VCOLDataWrapper(COLLADAFW::MeshVertexData &vdata) : mVData(&vda
 {
 }
 
+template
+static void colladaAddColor(T values, MLoopCol *mloopcol, int v_index, int stride)
+{
+  if (values->empty() || values->getCount() < (v_index + 1) * stride) {
+    fprintf(stderr,
+            "VCOLDataWrapper.getvcol(): Out of Bounds error: index %d points outside value "
+            "list of length %zd (with stride=%d) \n",
+            v_index,
+            values->getCount(),
+            stride);
+    return;
+  }
+
+  mloopcol->r = unit_float_to_uchar_clamp((*values)[v_index * stride]);
+  mloopcol->g = unit_float_to_uchar_clamp((*values)[v_index * stride + 1]);
+  mloopcol->b = unit_float_to_uchar_clamp((*values)[v_index * stride + 2]);
+  if (stride == 4) {
+    mloopcol->a = unit_float_to_uchar_clamp((*values)[v_index * stride + 3]);
+  }
+}
+
 void VCOLDataWrapper::get_vcol(int v_index, MLoopCol *mloopcol)
 {
   int stride = mVData->getStride(0);
@@ -155,25 +176,14 @@ void VCOLDataWrapper::get_vcol(int v_index, MLoopCol *mloopcol)
   switch (mVData->getType()) {
     case COLLADAFW::MeshVertexData::DATA_TYPE_FLOAT: {
       COLLADAFW::ArrayPrimitiveType *values = mVData->getFloatValues();
-      if (values->empty() || values->getCount() <= (v_index * stride + 2)) {
-        return; /* XXX: need to create an error instead. */
-      }
-
-      mloopcol->r = unit_float_to_uchar_clamp((*values)[v_index * stride]);
-      mloopcol->g = unit_float_to_uchar_clamp((*values)[v_index * stride + 1]);
-      mloopcol->b = unit_float_to_uchar_clamp((*values)[v_index * stride + 2]);
+      colladaAddColor *>(values, mloopcol, v_index, stride);
     } break;
 
     case COLLADAFW::MeshVertexData::DATA_TYPE_DOUBLE: {
       COLLADAFW::ArrayPrimitiveType *values = mVData->getDoubleValues();
-      if (values->empty() || values->getCount() <= (v_index * stride + 2)) {
-        return; /* XXX: need to create an error instead. */
-      }
-
-      mloopcol->r = unit_float_to_uchar_clamp((*values)[v_index * stride]);
-      mloopcol->g = unit_float_to_uchar_clamp((*values)[v_index * stride + 1]);
-      mloopcol->b = unit_float_to_uchar_clamp((*values)[v_index * stride + 2]);
+      colladaAddColor *>(values, mloopcol, v_index, stride);
     } break;
+
     default:
       fprintf(stderr, "VCOLDataWrapper.getvcol(): unknown data type\n");
   }
-- 
cgit v1.2.3


From 95055af668337cbb07dbb2a0bb0b0adafdc39351 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Thu, 30 Jun 2022 19:01:02 -0500
Subject: Fix T99309: Duplicate elements deletes instance attributes

The node had incorrect handling of instance attributes. For the instance
component, instead of using the instance domain over the point domain,
it just looked through instances recursively when retrieving attributes.
We never want to do that, since we only work on one geometry at a time.
Instead, just switch out the instance domain for the point domain like
the set position node.
---
 .../geometry/nodes/node_geo_duplicate_elements.cc  | 81 ++++++++++------------
 1 file changed, 37 insertions(+), 44 deletions(-)

diff --git a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
index 691f341b518..108a9443d58 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
@@ -61,13 +61,11 @@ struct IndexAttributes {
  * \{ */
 
 static Map gather_attributes_without_id(
-    const GeometrySet &geometry_set,
-    const GeometryComponentType component_type,
-    const bool include_instances)
+    const GeometrySet &geometry_set, const GeometryComponentType component_type)
 {
   Map attributes;
   geometry_set.gather_attributes_for_propagation(
-      {component_type}, component_type, include_instances, attributes);
+      {component_type}, component_type, false, attributes);
   attributes.remove("id");
   return attributes;
 };
@@ -186,26 +184,21 @@ static void copy_stable_id_point(const Span offsets,
   dst_attribute.save();
 }
 
-/* The attributes for the point (also instance) duplicated elements are stored sequentially
- * (1,1,1,2,2,2,3,3,3,etc) They can be copied by using a simple offset array. For each domain, if
- * elements are ordered differently a custom function is called to copy the attributes.
- */
-
-static void copy_point_attributes_without_id(GeometrySet &geometry_set,
-                                             const GeometryComponentType component_type,
-                                             const bool include_instances,
-                                             const Span offsets,
-                                             const IndexMask selection,
-                                             const GeometryComponent &src_component,
-                                             GeometryComponent &dst_component)
+static void copy_attributes_without_id(GeometrySet &geometry_set,
+                                       const GeometryComponentType component_type,
+                                       const eAttrDomain domain,
+                                       const Span offsets,
+                                       const IndexMask selection,
+                                       const GeometryComponent &src_component,
+                                       GeometryComponent &dst_component)
 {
-  Map attributes = gather_attributes_without_id(
-      geometry_set, component_type, include_instances);
+  const Map attributes = gather_attributes_without_id(
+      geometry_set, component_type);
 
   for (const Map::Item entry : attributes.items()) {
     const AttributeIDRef attribute_id = entry.key;
     ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read(attribute_id);
-    if (!src_attribute || src_attribute.domain != ATTR_DOMAIN_POINT) {
+    if (!src_attribute || src_attribute.domain != domain) {
       continue;
     }
     eAttrDomain out_domain = src_attribute.domain;
@@ -245,7 +238,7 @@ static void copy_curve_attributes_without_id(const GeometrySet &geometry_set,
                                              CurveComponent &dst_component)
 {
   Map attributes = gather_attributes_without_id(
-      geometry_set, GEO_COMPONENT_TYPE_CURVE, false);
+      geometry_set, GEO_COMPONENT_TYPE_CURVE);
 
   for (const Map::Item entry : attributes.items()) {
     const AttributeIDRef attribute_id = entry.key;
@@ -426,7 +419,7 @@ static void copy_face_attributes_without_id(GeometrySet &geometry_set,
                                             GeometryComponent &dst_component)
 {
   Map attributes = gather_attributes_without_id(
-      geometry_set, GEO_COMPONENT_TYPE_MESH, false);
+      geometry_set, GEO_COMPONENT_TYPE_MESH);
 
   for (const Map::Item entry : attributes.items()) {
     const AttributeIDRef attribute_id = entry.key;
@@ -639,7 +632,7 @@ static void copy_edge_attributes_without_id(GeometrySet &geometry_set,
                                             GeometryComponent &dst_component)
 {
   Map attributes = gather_attributes_without_id(
-      geometry_set, GEO_COMPONENT_TYPE_MESH, false);
+      geometry_set, GEO_COMPONENT_TYPE_MESH);
 
   for (const Map::Item entry : attributes.items()) {
     const AttributeIDRef attribute_id = entry.key;
@@ -840,7 +833,7 @@ static void duplicate_points_curve(GeometrySet &geometry_set,
   dst_component.replace(new_curves_id, GeometryOwnershipType::Editable);
 
   Map attributes = gather_attributes_without_id(
-      geometry_set, GEO_COMPONENT_TYPE_CURVE, false);
+      geometry_set, GEO_COMPONENT_TYPE_CURVE);
 
   for (const Map::Item entry : attributes.items()) {
     const AttributeIDRef attribute_id = entry.key;
@@ -925,13 +918,13 @@ static void duplicate_points_mesh(GeometrySet &geometry_set,
 
   MeshComponent dst_component;
   dst_component.replace(new_mesh, GeometryOwnershipType::Editable);
-  copy_point_attributes_without_id(geometry_set,
-                                   GEO_COMPONENT_TYPE_MESH,
-                                   false,
-                                   offsets,
-                                   selection,
-                                   src_component,
-                                   dst_component);
+  copy_attributes_without_id(geometry_set,
+                             GEO_COMPONENT_TYPE_MESH,
+                             ATTR_DOMAIN_POINT,
+                             offsets,
+                             selection,
+                             src_component,
+                             dst_component);
 
   copy_stable_id_point(offsets, src_component, dst_component);
 
@@ -972,13 +965,13 @@ static void duplicate_points_pointcloud(GeometrySet &geometry_set,
   PointCloudComponent dst_component;
   dst_component.replace(pointcloud, GeometryOwnershipType::Editable);
 
-  copy_point_attributes_without_id(geometry_set,
-                                   GEO_COMPONENT_TYPE_POINT_CLOUD,
-                                   false,
-                                   offsets,
-                                   selection,
-                                   src_points,
-                                   dst_component);
+  copy_attributes_without_id(geometry_set,
+                             GEO_COMPONENT_TYPE_POINT_CLOUD,
+                             ATTR_DOMAIN_POINT,
+                             offsets,
+                             selection,
+                             src_points,
+                             dst_component);
 
   copy_stable_id_point(offsets, src_points, dst_component);
 
@@ -1076,13 +1069,13 @@ static void duplicate_instances(GeometrySet &geometry_set,
     dst_instances.instance_reference_handles().slice(range).fill(new_handle);
   }
 
-  copy_point_attributes_without_id(geometry_set,
-                                   GEO_COMPONENT_TYPE_INSTANCES,
-                                   true,
-                                   offsets,
-                                   selection,
-                                   src_instances,
-                                   dst_instances);
+  copy_attributes_without_id(geometry_set,
+                             GEO_COMPONENT_TYPE_INSTANCES,
+                             ATTR_DOMAIN_INSTANCE,
+                             offsets,
+                             selection,
+                             src_instances,
+                             dst_instances);
 
   if (attribute_outputs.duplicate_index) {
     create_duplicate_index_attribute(
-- 
cgit v1.2.3


From a69e5c234834ede518effb9f8b18a8968e55d297 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Thu, 30 Jun 2022 19:17:32 -0500
Subject: Cleanup: Avoid assigning constructed VArray to reference

This is clearer about what is actually happening (VArray is small
enough to be a by-value type and is constructed on demand, while
only the generic virtual array is stored).
---
 source/blender/nodes/geometry/nodes/node_geo_accumulate_field.cc  | 8 ++++----
 .../blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc  | 4 ++--
 .../nodes/geometry/nodes/node_geo_curve_endpoint_selection.cc     | 4 ++--
 source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc        | 4 ++--
 source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc      | 4 ++--
 source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc    | 2 +-
 .../blender/nodes/geometry/nodes/node_geo_input_curve_handles.cc  | 2 +-
 .../nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc    | 2 +-
 .../blender/nodes/geometry/nodes/node_geo_instances_to_points.cc  | 2 +-
 source/blender/nodes/geometry/nodes/node_geo_rotate_instances.cc  | 6 +++---
 source/blender/nodes/geometry/nodes/node_geo_scale_instances.cc   | 6 +++---
 source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc | 4 ++--
 source/blender/nodes/geometry/nodes/node_geo_set_id.cc            | 2 +-
 source/blender/nodes/geometry/nodes/node_geo_set_position.cc      | 4 ++--
 .../blender/nodes/geometry/nodes/node_geo_translate_instances.cc  | 4 ++--
 15 files changed, 29 insertions(+), 29 deletions(-)

diff --git a/source/blender/nodes/geometry/nodes/node_geo_accumulate_field.cc b/source/blender/nodes/geometry/nodes/node_geo_accumulate_field.cc
index a7404af8564..be7b3446125 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_accumulate_field.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_accumulate_field.cc
@@ -223,8 +223,8 @@ template class AccumulateFieldInput final : public GeometryFieldInpu
     evaluator.add(input_);
     evaluator.add(group_index_);
     evaluator.evaluate();
-    const VArray &values = evaluator.get_evaluated(0);
-    const VArray &group_indices = evaluator.get_evaluated(1);
+    const VArray values = evaluator.get_evaluated(0);
+    const VArray group_indices = evaluator.get_evaluated(1);
 
     Array accumulations_out(domain_num);
 
@@ -309,8 +309,8 @@ template class TotalFieldInput final : public GeometryFieldInput {
     evaluator.add(input_);
     evaluator.add(group_index_);
     evaluator.evaluate();
-    const VArray &values = evaluator.get_evaluated(0);
-    const VArray &group_indices = evaluator.get_evaluated(1);
+    const VArray values = evaluator.get_evaluated(0);
+    const VArray group_indices = evaluator.get_evaluated(1);
 
     if (group_indices.is_single()) {
       T accumulation = T();
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc
index 35404725998..08e72dae8f6 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc
@@ -203,7 +203,7 @@ static void node_geo_exec(GeoNodeExecParams params)
           data_evaluator.add(input_field);
           data_evaluator.set_selection(selection_field);
           data_evaluator.evaluate();
-          const VArray &component_data = data_evaluator.get_evaluated(0);
+          const VArray component_data = data_evaluator.get_evaluated(0);
           const IndexMask selection = data_evaluator.get_evaluated_selection_as_mask();
 
           const int next_data_index = data.size();
@@ -281,7 +281,7 @@ static void node_geo_exec(GeoNodeExecParams params)
           data_evaluator.add(input_field);
           data_evaluator.set_selection(selection_field);
           data_evaluator.evaluate();
-          const VArray &component_data = data_evaluator.get_evaluated(0);
+          const VArray component_data = data_evaluator.get_evaluated(0);
           const IndexMask selection = data_evaluator.get_evaluated_selection_as_mask();
 
           const int next_data_index = data.size();
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_endpoint_selection.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_endpoint_selection.cc
index b52bf2571b5..db3f108aad5 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_endpoint_selection.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_endpoint_selection.cc
@@ -64,8 +64,8 @@ class EndpointFieldInput final : public GeometryFieldInput {
     evaluator.add(start_size_);
     evaluator.add(end_size_);
     evaluator.evaluate();
-    const VArray &start_size = evaluator.get_evaluated(0);
-    const VArray &end_size = evaluator.get_evaluated(1);
+    const VArray start_size = evaluator.get_evaluated(0);
+    const VArray end_size = evaluator.get_evaluated(1);
 
     Array selection(curves.points_num(), false);
     MutableSpan selection_span = selection.as_mutable_span();
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
index c993a3d305d..2b90428acb1 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
@@ -512,8 +512,8 @@ static void geometry_set_curve_trim(GeometrySet &geometry_set,
   evaluator.add(start_field);
   evaluator.add(end_field);
   evaluator.evaluate();
-  const blender::VArray &starts = evaluator.get_evaluated(0);
-  const blender::VArray &ends = evaluator.get_evaluated(1);
+  const VArray starts = evaluator.get_evaluated(0);
+  const VArray ends = evaluator.get_evaluated(1);
 
   std::unique_ptr curve = curves_to_curve_eval(*geometry_set.get_curves_for_read());
   MutableSpan splines = curve->splines();
diff --git a/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc
index 3eca92e37a3..59d7154db6e 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc
@@ -424,7 +424,7 @@ static void extrude_mesh_edges(MeshComponent &component,
   edge_evaluator.add(offset_field);
   edge_evaluator.evaluate();
   const IndexMask edge_selection = edge_evaluator.get_evaluated_selection_as_mask();
-  const VArray &edge_offsets = edge_evaluator.get_evaluated(0);
+  const VArray edge_offsets = edge_evaluator.get_evaluated(0);
   if (edge_selection.is_empty()) {
     return;
   }
@@ -686,7 +686,7 @@ static void extrude_mesh_face_regions(MeshComponent &component,
   poly_evaluator.add(offset_field);
   poly_evaluator.evaluate();
   const IndexMask poly_selection = poly_evaluator.get_evaluated_selection_as_mask();
-  const VArray &poly_offsets = poly_evaluator.get_evaluated(0);
+  const VArray poly_offsets = poly_evaluator.get_evaluated(0);
   if (poly_selection.is_empty()) {
     return;
   }
diff --git a/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc b/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc
index 58281df43d3..7839e148ee3 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc
@@ -100,7 +100,7 @@ class FieldAtIndex final : public GeometryFieldInput {
     FieldEvaluator index_evaluator{index_field_context, &mask};
     index_evaluator.add(index_field_);
     index_evaluator.evaluate();
-    const VArray &indices = index_evaluator.get_evaluated(0);
+    const VArray indices = index_evaluator.get_evaluated(0);
 
     GVArray output_array;
     attribute_math::convert_to_static_type(*type_, [&](auto dummy) {
diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_curve_handles.cc b/source/blender/nodes/geometry/nodes/node_geo_input_curve_handles.cc
index da249278867..e0aaf43235c 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_input_curve_handles.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_input_curve_handles.cc
@@ -37,7 +37,7 @@ class HandlePositionFieldInput final : public GeometryFieldInput {
     fn::FieldEvaluator evaluator(field_context, &mask);
     evaluator.add(relative_);
     evaluator.evaluate();
-    const VArray &relative = evaluator.get_evaluated(0);
+    const VArray relative = evaluator.get_evaluated(0);
 
     VArray positions = component.attribute_get_for_read(
         "position", ATTR_DOMAIN_POINT, {0, 0, 0});
diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc
index 532c3dc81e5..d02f7291704 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc
@@ -51,7 +51,7 @@ class PlanarFieldInput final : public GeometryFieldInput {
     fn::FieldEvaluator evaluator{context, mesh->totpoly};
     evaluator.add(threshold_);
     evaluator.evaluate();
-    const VArray &thresholds = evaluator.get_evaluated(0);
+    const VArray thresholds = evaluator.get_evaluated(0);
 
     Span poly_normals{(float3 *)BKE_mesh_poly_normals_ensure(mesh), mesh->totpoly};
 
diff --git a/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc
index 2126a5cc329..ffc6137cf83 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc
@@ -59,7 +59,7 @@ static void convert_instances_to_points(GeometrySet &geometry_set,
 
   const VArray &positions = evaluator.get_evaluated(0);
   copy_attribute_to_points(positions, selection, {(float3 *)pointcloud->co, pointcloud->totpoint});
-  const VArray &radii = evaluator.get_evaluated(1);
+  const VArray radii = evaluator.get_evaluated(1);
   copy_attribute_to_points(radii, selection, {pointcloud->radius, pointcloud->totpoint});
 
   Map attributes_to_propagate;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_rotate_instances.cc b/source/blender/nodes/geometry/nodes/node_geo_rotate_instances.cc
index 59e203afd08..d414bb1fa1d 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_rotate_instances.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_rotate_instances.cc
@@ -29,9 +29,9 @@ static void rotate_instances(GeoNodeExecParams ¶ms, InstancesComponent &inst
   evaluator.evaluate();
 
   const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
-  const VArray &rotations = evaluator.get_evaluated(0);
-  const VArray &pivots = evaluator.get_evaluated(1);
-  const VArray &local_spaces = evaluator.get_evaluated(2);
+  const VArray rotations = evaluator.get_evaluated(0);
+  const VArray pivots = evaluator.get_evaluated(1);
+  const VArray local_spaces = evaluator.get_evaluated(2);
 
   MutableSpan instance_transforms = instances_component.instance_transforms();
 
diff --git a/source/blender/nodes/geometry/nodes/node_geo_scale_instances.cc b/source/blender/nodes/geometry/nodes/node_geo_scale_instances.cc
index d4716a6b6f0..7156feb37d7 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_scale_instances.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_scale_instances.cc
@@ -31,9 +31,9 @@ static void scale_instances(GeoNodeExecParams ¶ms, InstancesComponent &insta
   evaluator.evaluate();
 
   const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
-  const VArray &scales = evaluator.get_evaluated(0);
-  const VArray &pivots = evaluator.get_evaluated(1);
-  const VArray &local_spaces = evaluator.get_evaluated(2);
+  const VArray scales = evaluator.get_evaluated(0);
+  const VArray pivots = evaluator.get_evaluated(1);
+  const VArray local_spaces = evaluator.get_evaluated(2);
 
   MutableSpan instance_transforms = instances_component.instance_transforms();
 
diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc b/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc
index d2082924fa7..37533a7b99a 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc
@@ -86,8 +86,8 @@ static void set_position_in_component(CurveComponent &component,
   evaluator.add(offset_field);
   evaluator.evaluate();
   const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
-  const VArray &new_positions = evaluator.get_evaluated(0);
-  const VArray &new_offsets = evaluator.get_evaluated(1);
+  const VArray new_positions = evaluator.get_evaluated(0);
+  const VArray new_offsets = evaluator.get_evaluated(1);
 
   Curves &curves_id = *component.get_for_write();
   bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_id.cc b/source/blender/nodes/geometry/nodes/node_geo_set_id.cc
index 87d48daddea..a7f17c02ce8 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_set_id.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_set_id.cc
@@ -42,7 +42,7 @@ static void set_id_in_component(GeometryComponent &component,
     evaluator.add(id_field);
     evaluator.evaluate();
     const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
-    const VArray &result_ids = evaluator.get_evaluated(0);
+    const VArray result_ids = evaluator.get_evaluated(0);
     OutputAttribute_Typed id_attribute = component.attribute_try_get_for_output_only(
         "id", domain);
     result_ids.materialize(selection, id_attribute.as_span());
diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_position.cc b/source/blender/nodes/geometry/nodes/node_geo_set_position.cc
index e9ed87e552f..1935409b3e5 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_set_position.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_set_position.cc
@@ -154,8 +154,8 @@ static void set_position_in_component(GeometryComponent &component,
   evaluator.evaluate();
 
   const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
-  const VArray &positions_input = evaluator.get_evaluated(0);
-  const VArray &offsets_input = evaluator.get_evaluated(1);
+  const VArray positions_input = evaluator.get_evaluated(0);
+  const VArray offsets_input = evaluator.get_evaluated(1);
   set_computed_position_and_offset(component, positions_input, offsets_input, domain, selection);
 }
 
diff --git a/source/blender/nodes/geometry/nodes/node_geo_translate_instances.cc b/source/blender/nodes/geometry/nodes/node_geo_translate_instances.cc
index 258c1ac3fba..ae538072e65 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_translate_instances.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_translate_instances.cc
@@ -26,8 +26,8 @@ static void translate_instances(GeoNodeExecParams ¶ms, InstancesComponent &i
   evaluator.evaluate();
 
   const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
-  const VArray &translations = evaluator.get_evaluated(0);
-  const VArray &local_spaces = evaluator.get_evaluated(1);
+  const VArray translations = evaluator.get_evaluated(0);
+  const VArray local_spaces = evaluator.get_evaluated(1);
 
   MutableSpan instance_transforms = instances_component.instance_transforms();
 
-- 
cgit v1.2.3


From 982d6589a8c705226b7f2653c2f84ab4dd260830 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Thu, 30 Jun 2022 19:25:44 -0500
Subject: Cleanup: Remove duplicate include

---
 source/blender/editors/mask/mask_select.c | 2 --
 1 file changed, 2 deletions(-)

diff --git a/source/blender/editors/mask/mask_select.c b/source/blender/editors/mask/mask_select.c
index 4bd80208b9e..95cad3f54ca 100644
--- a/source/blender/editors/mask/mask_select.c
+++ b/source/blender/editors/mask/mask_select.c
@@ -31,8 +31,6 @@
 #include "RNA_access.h"
 #include "RNA_define.h"
 
-#include "DEG_depsgraph.h"
-
 #include "mask_intern.h" /* own include */
 
 /* -------------------------------------------------------------------- */
-- 
cgit v1.2.3


From 6161ce6e5d5eb3b278ca3c9960e68a0bb3ba87d0 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Thu, 30 Jun 2022 19:26:51 -0500
Subject: Cleanup: Add assert for unsupported legacy curve type

---
 source/blender/blenkernel/intern/curve_legacy_convert.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source/blender/blenkernel/intern/curve_legacy_convert.cc b/source/blender/blenkernel/intern/curve_legacy_convert.cc
index 61299f165be..299a0fbb44c 100644
--- a/source/blender/blenkernel/intern/curve_legacy_convert.cc
+++ b/source/blender/blenkernel/intern/curve_legacy_convert.cc
@@ -196,7 +196,7 @@ Curves *curve_legacy_to_curves(const Curve &curve_legacy, const ListBase &nurbs_
       curves.curve_types(),
       curves.curve_type_counts(),
       curves.curves_range(),
-      [&](IndexMask /* selection */) {},
+      [&](IndexMask /*selection*/) { BLI_assert_unreachable(); },
       create_poly,
       create_bezier,
       create_nurbs);
-- 
cgit v1.2.3


From 32e9c9802eb3fb3f85c3dbf791e49ef74a9e2b0a Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Thu, 30 Jun 2022 19:27:41 -0500
Subject: Cleanup: Add assert for customdata realloc size

This gives a more clear error than finding the error with the signed
to unsigned conversion for size_t.
---
 source/blender/blenkernel/intern/customdata.cc | 1 +
 1 file changed, 1 insertion(+)

diff --git a/source/blender/blenkernel/intern/customdata.cc b/source/blender/blenkernel/intern/customdata.cc
index bb5b2ee0836..2cd829a7f0b 100644
--- a/source/blender/blenkernel/intern/customdata.cc
+++ b/source/blender/blenkernel/intern/customdata.cc
@@ -2329,6 +2329,7 @@ bool CustomData_merge(const CustomData *source,
 
 void CustomData_realloc(CustomData *data, int totelem)
 {
+  BLI_assert(totelem > 0);
   for (int i = 0; i < data->totlayer; i++) {
     CustomDataLayer *layer = &data->layers[i];
     const LayerTypeInfo *typeInfo;
-- 
cgit v1.2.3


From 4206b302754797ea07684283fa9b677e4ea531d5 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Thu, 30 Jun 2022 19:29:32 -0500
Subject: Curves: Adjust "for each curve by type" utility

The first change is reusing the same vector for all types. While we don't
generally optimize for the multi-type case, it doesn't hurt here. The
second change is avoiding calling the corresponding function if there
are no curves of a certain type. This avoids creating attributes for
types that aren't used, for example.
---
 source/blender/blenkernel/intern/curves_utils.cc | 20 ++++++++++++--------
 1 file changed, 12 insertions(+), 8 deletions(-)

diff --git a/source/blender/blenkernel/intern/curves_utils.cc b/source/blender/blenkernel/intern/curves_utils.cc
index 802469399ab..f7db60ee588 100644
--- a/source/blender/blenkernel/intern/curves_utils.cc
+++ b/source/blender/blenkernel/intern/curves_utils.cc
@@ -109,14 +109,18 @@ void foreach_curve_by_type(const VArray &types,
                            FunctionRef bezier_fn,
                            FunctionRef nurbs_fn)
 {
-  Vector catmull_rom;
-  Vector poly;
-  Vector bezier;
-  Vector nurbs;
-  catmull_rom_fn(indices_for_type(types, counts, CURVE_TYPE_CATMULL_ROM, selection, catmull_rom));
-  poly_fn(indices_for_type(types, counts, CURVE_TYPE_POLY, selection, poly));
-  bezier_fn(indices_for_type(types, counts, CURVE_TYPE_BEZIER, selection, bezier));
-  nurbs_fn(indices_for_type(types, counts, CURVE_TYPE_NURBS, selection, nurbs));
+  Vector indices;
+  auto call_if_not_empty = [&](const CurveType type, FunctionRef fn) {
+    indices.clear();
+    const IndexMask mask = indices_for_type(types, counts, type, selection, indices);
+    if (!mask.is_empty()) {
+      fn(mask);
+    }
+  };
+  call_if_not_empty(CURVE_TYPE_CATMULL_ROM, catmull_rom_fn);
+  call_if_not_empty(CURVE_TYPE_POLY, poly_fn);
+  call_if_not_empty(CURVE_TYPE_BEZIER, bezier_fn);
+  call_if_not_empty(CURVE_TYPE_NURBS, nurbs_fn);
 }
 
 }  // namespace blender::bke::curves
-- 
cgit v1.2.3


From c15ae2e87b26cfeb3f299dedbd566825a70b2611 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Thu, 30 Jun 2022 19:30:06 -0500
Subject: Fix: Correct attribute names in resample curves code

---
 source/blender/geometry/intern/resample_curves.cc | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/source/blender/geometry/intern/resample_curves.cc b/source/blender/geometry/intern/resample_curves.cc
index 36525e1bdf0..c18c776fead 100644
--- a/source/blender/geometry/intern/resample_curves.cc
+++ b/source/blender/geometry/intern/resample_curves.cc
@@ -77,8 +77,8 @@ static bool interpolate_attribute_to_poly_curve(const bke::AttributeIDRef &attri
   static const Set no_interpolation{{
       "handle_type_left",
       "handle_type_right",
-      "handle_position_right",
-      "handle_position_left",
+      "handle_right",
+      "handle_left",
       "nurbs_weight",
   }};
   return !(attribute_id.is_named() && no_interpolation.contains(attribute_id.name()));
-- 
cgit v1.2.3


From 90f4b35bc22d5c4fa1b4579514e1596ae9d131b1 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Fri, 1 Jul 2022 11:18:56 +1000
Subject: Cleanup: remove unused argument

---
 source/blender/draw/engines/eevee_next/eevee_film.cc       |  2 +-
 source/blender/editors/space_outliner/outliner_dragdrop.cc | 11 ++++-------
 2 files changed, 5 insertions(+), 8 deletions(-)

diff --git a/source/blender/draw/engines/eevee_next/eevee_film.cc b/source/blender/draw/engines/eevee_next/eevee_film.cc
index 44737018c62..84a84e21a2a 100644
--- a/source/blender/draw/engines/eevee_next/eevee_film.cc
+++ b/source/blender/draw/engines/eevee_next/eevee_film.cc
@@ -120,7 +120,7 @@ void Film::sync_mist()
   const ::World *world = inst_.scene->world;
   float mist_start = world ? world->miststa : cam.clip_near;
   float mist_distance = world ? world->mistdist : fabsf(cam.clip_far - cam.clip_near);
-  int mist_type = world ? world->mistype : WO_MIST_LINEAR;
+  int mist_type = world ? world->mistype : (int)WO_MIST_LINEAR;
 
   switch (mist_type) {
     case WO_MIST_QUADRATIC:
diff --git a/source/blender/editors/space_outliner/outliner_dragdrop.cc b/source/blender/editors/space_outliner/outliner_dragdrop.cc
index 7782da9d762..c72080be811 100644
--- a/source/blender/editors/space_outliner/outliner_dragdrop.cc
+++ b/source/blender/editors/space_outliner/outliner_dragdrop.cc
@@ -1124,8 +1124,7 @@ static Collection *collection_parent_from_ID(ID *id)
   return nullptr;
 }
 
-static bool collection_drop_init(
-    bContext *C, wmDrag *drag, const int xy[2], const bool is_link, CollectionDrop *data)
+static bool collection_drop_init(bContext *C, wmDrag *drag, const int xy[2], CollectionDrop *data)
 {
   /* Get collection to drop into. */
   TreeElementInsertType insert_type;
@@ -1194,8 +1193,7 @@ static bool collection_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event
   bool changed = outliner_flag_set(*space_outliner, TSE_HIGHLIGHTED_ANY | TSE_DRAG_ANY, false);
 
   CollectionDrop data;
-  if (((event->modifier & KM_SHIFT) == 0) &&
-      collection_drop_init(C, drag, event->xy, event->modifier & KM_CTRL, &data)) {
+  if (((event->modifier & KM_SHIFT) == 0) && collection_drop_init(C, drag, event->xy, &data)) {
     TreeElement *te = data.te;
     TreeStoreElem *tselem = TREESTORE(te);
     switch (data.insert_type) {
@@ -1233,8 +1231,7 @@ static char *collection_drop_tooltip(bContext *C,
   const wmEvent *event = win ? win->eventstate : nullptr;
 
   CollectionDrop data;
-  if (event && ((event->modifier & KM_SHIFT) == 0) &&
-      collection_drop_init(C, drag, xy, event->modifier & KM_CTRL, &data)) {
+  if (event && ((event->modifier & KM_SHIFT) == 0) && collection_drop_init(C, drag, xy, &data)) {
     const bool is_link = !data.from || (event->modifier & KM_CTRL);
 
     /* Test if we are moving within same parent collection. */
@@ -1307,7 +1304,7 @@ static int collection_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmE
   wmDrag *drag = reinterpret_cast(lb->first);
 
   CollectionDrop data;
-  if (!collection_drop_init(C, drag, event->xy, event->modifier & KM_CTRL, &data)) {
+  if (!collection_drop_init(C, drag, event->xy, &data)) {
     return OPERATOR_CANCELLED;
   }
 
-- 
cgit v1.2.3


From cf64a1d73e0e0378049b4e24407e9a4406270e8a Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Fri, 1 Jul 2022 11:18:58 +1000
Subject: Cleanup: spelling in comments

---
 source/blender/draw/engines/eevee_next/eevee_film.cc          |  2 +-
 source/blender/draw/engines/eevee_next/eevee_instance.cc      |  2 +-
 source/blender/draw/engines/eevee_next/eevee_sampling.cc      | 10 +++++-----
 source/blender/draw/engines/eevee_next/eevee_sampling.hh      |  2 +-
 source/blender/draw/engines/eevee_next/eevee_shader_shared.hh |  8 ++++----
 5 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/source/blender/draw/engines/eevee_next/eevee_film.cc b/source/blender/draw/engines/eevee_next/eevee_film.cc
index 84a84e21a2a..1fd4c278c88 100644
--- a/source/blender/draw/engines/eevee_next/eevee_film.cc
+++ b/source/blender/draw/engines/eevee_next/eevee_film.cc
@@ -300,7 +300,7 @@ void Film::init(const int2 &extent, const rcti *output_rect)
     data_.value_len += data_.aov_value_len;
   }
   {
-    /* TODO(fclem): Overscans. */
+    /* TODO(@fclem): Over-scans. */
 
     render_extent_ = math::divide_ceil(extent, int2(data_.scaling_factor));
     int2 weight_extent = inst_.camera.is_panoramic() ? data_.extent : int2(data_.scaling_factor);
diff --git a/source/blender/draw/engines/eevee_next/eevee_instance.cc b/source/blender/draw/engines/eevee_next/eevee_instance.cc
index 5fa5628515f..6098d78b81c 100644
--- a/source/blender/draw/engines/eevee_next/eevee_instance.cc
+++ b/source/blender/draw/engines/eevee_next/eevee_instance.cc
@@ -182,7 +182,7 @@ void Instance::render_sync()
   end_sync();
 
   DRW_render_instance_buffer_finish();
-  /* Also we weed to have a correct fbo bound for DRW_hair_update */
+  /* Also we weed to have a correct FBO bound for #DRW_hair_update */
   // GPU_framebuffer_bind();
   // DRW_hair_update();
 }
diff --git a/source/blender/draw/engines/eevee_next/eevee_sampling.cc b/source/blender/draw/engines/eevee_next/eevee_sampling.cc
index 493fff53919..2f180e58a0b 100644
--- a/source/blender/draw/engines/eevee_next/eevee_sampling.cc
+++ b/source/blender/draw/engines/eevee_next/eevee_sampling.cc
@@ -83,7 +83,7 @@ void Sampling::step()
     /* TODO(fclem) we could use some persistent states to speedup the computation. */
     double2 r, offset = {0, 0};
     /* Using 2,3 primes as per UE4 Temporal AA presentation.
-     * advances.realtimerendering.com/s2014/epic/TemporalAA.pptx (slide 14) */
+     * http://advances.realtimerendering.com/s2014/epic/TemporalAA.pptx (slide 14) */
     uint2 primes = {2, 3};
     BLI_halton_2d(primes, offset, sample_ + 1, r);
     /* WORKAROUND: We offset the distribution to make the first sample (0,0). This way, we are
@@ -196,12 +196,12 @@ void Sampling::dof_disk_sample_get(float *r_radius, float *r_theta) const
   s = s % dof_sample_count_;
 
   /* Choosing sample to we get faster convergence.
-   * The issue here is that we cannot map a low descripency sequence to this sampling pattern
-   * because the same sample could be choosen twice in relatively short intervals. */
+   * The issue here is that we cannot map a low discrepancy sequence to this sampling pattern
+   * because the same sample could be chosen twice in relatively short intervals. */
   /* For now just use an ascending sequence with an offset. This gives us relatively quick
    * initial coverage and relatively high distance between samples. */
-  /* TODO(fclem) We can try to order samples based on a LDS into a table to avoid duplicates.
-   * The drawback would be some memory consumption and init time. */
+  /* TODO(@fclem) We can try to order samples based on a LDS into a table to avoid duplicates.
+   * The drawback would be some memory consumption and initialize time. */
   int samples_passed = 1;
   while (s >= samples_passed) {
     ring++;
diff --git a/source/blender/draw/engines/eevee_next/eevee_sampling.hh b/source/blender/draw/engines/eevee_next/eevee_sampling.hh
index d956c61f2b2..11daa21629a 100644
--- a/source/blender/draw/engines/eevee_next/eevee_sampling.hh
+++ b/source/blender/draw/engines/eevee_next/eevee_sampling.hh
@@ -136,7 +136,7 @@ class Sampling {
   static float2 sample_disk(const float2 &rand);
 
   /**
-   * Uniform disc distribution using fibonacci spiral sampling.
+   * Uniform disc distribution using Fibonacci spiral sampling.
    * \a rand is 2 random float in the [0..1] range.
    * Returns point in a disk of radius 1 and centered on the origin.
    */
diff --git a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh
index 4168171ab07..62eb5a2b965 100644
--- a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh
+++ b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh
@@ -59,9 +59,9 @@ enum eSamplingDimension : uint32_t {
  */
 #define SAMPLING_DIMENSION_COUNT 20
 
-/* NOTE(fclem): Needs to be used in StorageBuffer because of arrays of scalar. */
+/* NOTE(@fclem): Needs to be used in #StorageBuffer because of arrays of scalar. */
 struct SamplingData {
-  /** Array containing random values from Low Discrepency Sequence in [0..1) range. */
+  /** Array containing random values from Low Discrepancy Sequence in [0..1) range. */
   float dimensions[SAMPLING_DIMENSION_COUNT];
 };
 BLI_STATIC_ASSERT_ALIGN(SamplingData, 16)
@@ -149,7 +149,7 @@ struct FilmData {
   int2 extent;
   /** Offset of the film in the full-res frame, in pixels. */
   int2 offset;
-  /** Subpixel offset applied to the window matrix.
+  /** Sub-pixel offset applied to the window matrix.
    * NOTE: In final film pixel unit.
    * NOTE: Positive values makes the view translate in the negative axes direction.
    * NOTE: The origin is the center of the lower left film pixel of the area covered by a render
@@ -236,7 +236,7 @@ static inline float film_filter_weight(float filter_size, float sample_distance_
  * If we find a way to avoid this we could bump this number up. */
 #define AOV_MAX 16
 
-/* NOTE(fclem): Needs to be used in StorageBuffer because of arrays of scalar. */
+/* NOTE(@fclem): Needs to be used in #StorageBuffer because of arrays of scalar. */
 struct AOVsInfoData {
   uint hash_value[AOV_MAX];
   uint hash_color[AOV_MAX];
-- 
cgit v1.2.3


From 3d3ba9ca8e52cb0d8ed18e401821357754d5ce5e Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Fri, 1 Jul 2022 11:19:01 +1000
Subject: Cleanup: group public utility functions for Wayland System/Window

GHOST methods were mixed in with Wayland specific utility functions,
making it difficult to navigate or know where to add new functions.
---
 intern/ghost/intern/GHOST_SystemWayland.cpp | 130 ++++++----
 intern/ghost/intern/GHOST_SystemWayland.h   |  42 ++--
 intern/ghost/intern/GHOST_WindowWayland.cpp | 362 +++++++++++++++-------------
 intern/ghost/intern/GHOST_WindowWayland.h   |  31 +--
 4 files changed, 317 insertions(+), 248 deletions(-)

diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp
index 985310dc919..6562387358d 100644
--- a/intern/ghost/intern/GHOST_SystemWayland.cpp
+++ b/intern/ghost/intern/GHOST_SystemWayland.cpp
@@ -1162,7 +1162,7 @@ static void data_device_handle_selection(void *data,
 
     {
       std::lock_guard lock{system_selection_mutex};
-      system->setSelection(data);
+      system->selection_set(data);
     }
   };
 
@@ -2424,9 +2424,9 @@ static const struct wl_registry_listener registry_listener = {
 /** \} */
 
 /* -------------------------------------------------------------------- */
-/** \name Ghost Implementation
+/** \name GHOST Implementation
  *
- * Wayland specific implementation of the GHOST_System interface.
+ * WAYLAND specific implementation of the #GHOST_System interface.
  * \{ */
 
 GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), d(new display_t)
@@ -2849,52 +2849,6 @@ GHOST_IWindow *GHOST_SystemWayland::createWindow(const char *title,
   return window;
 }
 
-wl_display *GHOST_SystemWayland::display()
-{
-  return d->display;
-}
-
-wl_compositor *GHOST_SystemWayland::compositor()
-{
-  return d->compositor;
-}
-
-#ifdef WITH_GHOST_WAYLAND_LIBDECOR
-
-libdecor *GHOST_SystemWayland::decor_context()
-{
-  return d->decor_context;
-}
-
-#else /* WITH_GHOST_WAYLAND_LIBDECOR */
-
-xdg_wm_base *GHOST_SystemWayland::xdg_shell()
-{
-  return d->xdg_shell;
-}
-
-zxdg_decoration_manager_v1 *GHOST_SystemWayland::xdg_decoration_manager()
-{
-  return d->xdg_decoration_manager;
-}
-
-#endif /* !WITH_GHOST_WAYLAND_LIBDECOR */
-
-const std::vector &GHOST_SystemWayland::outputs() const
-{
-  return d->outputs;
-}
-
-wl_shm *GHOST_SystemWayland::shm() const
-{
-  return d->shm;
-}
-
-void GHOST_SystemWayland::setSelection(const std::string &selection)
-{
-  this->selection = selection;
-}
-
 /**
  * Show the buffer defined by #cursor_buffer_set without changing anything else,
  * so #cursor_buffer_hide can be used to display it again.
@@ -3464,3 +3418,81 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorGrab(const GHOST_TGrabCursorMode mo
 }
 
 /** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Public WAYLAND Direct Data Access
+ *
+ * Expose some members via methods.
+ * \{ */
+
+wl_display *GHOST_SystemWayland::display()
+{
+  return d->display;
+}
+
+wl_compositor *GHOST_SystemWayland::compositor()
+{
+  return d->compositor;
+}
+
+#ifdef WITH_GHOST_WAYLAND_LIBDECOR
+
+libdecor *GHOST_SystemWayland::decor_context()
+{
+  return d->decor_context;
+}
+
+#else /* WITH_GHOST_WAYLAND_LIBDECOR */
+
+xdg_wm_base *GHOST_SystemWayland::xdg_shell()
+{
+  return d->xdg_shell;
+}
+
+zxdg_decoration_manager_v1 *GHOST_SystemWayland::xdg_decoration_manager()
+{
+  return d->xdg_decoration_manager;
+}
+
+#endif /* !WITH_GHOST_WAYLAND_LIBDECOR */
+
+const std::vector &GHOST_SystemWayland::outputs() const
+{
+  return d->outputs;
+}
+
+wl_shm *GHOST_SystemWayland::shm() const
+{
+  return d->shm;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Public WAYLAND Query Access
+ * \{ */
+
+output_t *GHOST_SystemWayland::output_find_by_wl(const struct wl_output *output) const
+{
+  for (output_t *reg_output : this->outputs()) {
+    if (reg_output->wl_output == output) {
+      return reg_output;
+    }
+  }
+  return nullptr;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Public WAYLAND Utility Functions
+ *
+ * Functionality only used for the WAYLAND implementation.
+ * \{ */
+
+void GHOST_SystemWayland::selection_set(const std::string &selection)
+{
+  this->selection = selection;
+}
+
+/** \} */
diff --git a/intern/ghost/intern/GHOST_SystemWayland.h b/intern/ghost/intern/GHOST_SystemWayland.h
index 7f7bd021bda..51753a85668 100644
--- a/intern/ghost/intern/GHOST_SystemWayland.h
+++ b/intern/ghost/intern/GHOST_SystemWayland.h
@@ -109,23 +109,6 @@ class GHOST_SystemWayland : public GHOST_System {
                               const bool is_dialog,
                               const GHOST_IWindow *parentWindow) override;
 
-  wl_display *display();
-
-  wl_compositor *compositor();
-
-#ifdef WITH_GHOST_WAYLAND_LIBDECOR
-  libdecor *decor_context();
-#else
-  xdg_wm_base *xdg_shell();
-  zxdg_decoration_manager_v1 *xdg_decoration_manager();
-#endif
-
-  const std::vector &outputs() const;
-
-  wl_shm *shm() const;
-
-  void setSelection(const std::string &selection);
-
   GHOST_TSuccess setCursorShape(GHOST_TStandardCursor shape);
 
   GHOST_TSuccess hasCursorShape(GHOST_TStandardCursor cursorShape);
@@ -151,6 +134,31 @@ class GHOST_SystemWayland : public GHOST_System {
                                const GHOST_TGrabCursorMode mode_current,
                                wl_surface *surface);
 
+  /* WAYLAND direct-data access. */
+
+  wl_display *display();
+
+  wl_compositor *compositor();
+
+#ifdef WITH_GHOST_WAYLAND_LIBDECOR
+  libdecor *decor_context();
+#else
+  xdg_wm_base *xdg_shell();
+  zxdg_decoration_manager_v1 *xdg_decoration_manager();
+#endif
+
+  const std::vector &outputs() const;
+
+  wl_shm *shm() const;
+
+  /* WAYLAND query access. */
+
+  output_t *output_find_by_wl(const struct wl_output *output) const;
+
+  /* WAYLAND utility functions. */
+
+  void selection_set(const std::string &selection);
+
  private:
   struct display_t *d;
   std::string selection;
diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp
index dd61832e2ee..c11dfb612b2 100644
--- a/intern/ghost/intern/GHOST_WindowWayland.cpp
+++ b/intern/ghost/intern/GHOST_WindowWayland.cpp
@@ -323,14 +323,9 @@ static void surface_handle_enter(void *data,
                                  struct wl_surface * /*wl_surface*/,
                                  struct wl_output *output)
 {
-  GHOST_WindowWayland *w = static_cast(data);
-  output_t *reg_output = w->output_find_by_wl(output);
-  if (reg_output == nullptr) {
-    return;
-  }
-
-  if (w->outputs_enter(reg_output)) {
-    w->outputs_changed_update_scale();
+  GHOST_WindowWayland *win = static_cast(data);
+  if (win->outputs_enter_wl(output)) {
+    win->outputs_changed_update_scale();
   }
 }
 
@@ -339,12 +334,7 @@ static void surface_handle_leave(void *data,
                                  struct wl_output *output)
 {
   GHOST_WindowWayland *w = static_cast(data);
-  output_t *reg_output = w->output_find_by_wl(output);
-  if (reg_output == nullptr) {
-    return;
-  }
-
-  if (w->outputs_leave(reg_output)) {
+  if (w->outputs_leave_wl(output)) {
     w->outputs_changed_update_scale();
   }
 }
@@ -357,9 +347,9 @@ struct wl_surface_listener wl_surface_listener = {
 /** \} */
 
 /* -------------------------------------------------------------------- */
-/** \name Ghost Implementation
+/** \name GHOST Implementation
  *
- * Wayland specific implementation of the GHOST_Window interface.
+ * WAYLAND specific implementation of the #GHOST_Window interface.
  * \{ */
 
 GHOST_TSuccess GHOST_WindowWayland::hasCursorShape(GHOST_TStandardCursor cursorShape)
@@ -492,157 +482,6 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system,
   setSwapInterval(0);
 }
 
-GHOST_TSuccess GHOST_WindowWayland::close()
-{
-  return m_system->pushEvent(
-      new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowClose, this));
-}
-
-GHOST_TSuccess GHOST_WindowWayland::activate()
-{
-  if (m_system->getWindowManager()->setActiveWindow(this) == GHOST_kFailure) {
-    return GHOST_kFailure;
-  }
-  return m_system->pushEvent(
-      new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowActivate, this));
-}
-
-GHOST_TSuccess GHOST_WindowWayland::deactivate()
-{
-  m_system->getWindowManager()->setWindowInactive(this);
-  return m_system->pushEvent(
-      new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowDeactivate, this));
-}
-
-GHOST_TSuccess GHOST_WindowWayland::notify_size()
-{
-#ifdef GHOST_OPENGL_ALPHA
-  setOpaque();
-#endif
-
-  return m_system->pushEvent(
-      new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowSize, this));
-}
-
-wl_surface *GHOST_WindowWayland::surface() const
-{
-  return w->wl_surface;
-}
-
-GHOST_WindowWayland *GHOST_WindowWayland::from_surface_find_mut(const wl_surface *surface)
-{
-  GHOST_ASSERT(surface, "argument must not be NULL");
-  for (GHOST_IWindow *iwin : window_manager->getWindows()) {
-    GHOST_WindowWayland *win = static_cast(iwin);
-    if (surface == win->surface()) {
-      return win;
-    }
-  }
-  return nullptr;
-}
-
-const GHOST_WindowWayland *GHOST_WindowWayland::from_surface_find(const wl_surface *surface)
-{
-  return GHOST_WindowWayland::from_surface_find_mut(surface);
-}
-
-GHOST_WindowWayland *GHOST_WindowWayland::from_surface_mut(wl_surface *surface)
-{
-  GHOST_WindowWayland *win = static_cast(wl_surface_get_user_data(surface));
-  GHOST_ASSERT(win == GHOST_WindowWayland::from_surface_find_mut(surface),
-               "Inconsistent window state, consider using \"from_surface_find_mut\"");
-  return win;
-}
-
-const GHOST_WindowWayland *GHOST_WindowWayland::from_surface(const wl_surface *surface)
-{
-  const GHOST_WindowWayland *win = static_cast(
-      wl_surface_get_user_data(const_cast(surface)));
-  GHOST_ASSERT(win == GHOST_WindowWayland::from_surface_find(surface),
-               "Inconsistent window state, consider using \"from_surface_find\"");
-  return win;
-}
-
-const std::vector &GHOST_WindowWayland::outputs()
-{
-  return w->outputs;
-}
-
-output_t *GHOST_WindowWayland::output_find_by_wl(struct wl_output *output)
-{
-  for (output_t *reg_output : this->m_system->outputs()) {
-    if (reg_output->wl_output == output) {
-      return reg_output;
-    }
-  }
-  return nullptr;
-}
-
-bool GHOST_WindowWayland::outputs_changed_update_scale()
-{
-  uint32_t dpi_next;
-  const int scale_next = outputs_max_scale_or_default(this->outputs(), 0, &dpi_next);
-  if (scale_next == 0) {
-    return false;
-  }
-
-  window_t *win = this->w;
-  const uint32_t dpi_curr = win->dpi;
-  const int scale_curr = win->scale;
-  bool changed = false;
-
-  if (scale_next != scale_curr) {
-    /* Unlikely but possible there is a pending size change is set. */
-    win->size_pending[0] = (win->size_pending[0] / scale_curr) * scale_next;
-    win->size_pending[1] = (win->size_pending[1] / scale_curr) * scale_next;
-
-    win->scale = scale_next;
-    wl_surface_set_buffer_scale(this->surface(), scale_next);
-    changed = true;
-  }
-
-  if (dpi_next != dpi_curr) {
-    /* Using the real DPI will cause wrong scaling of the UI
-     * use a multiplier for the default DPI as workaround. */
-    win->dpi = dpi_next;
-    changed = true;
-  }
-
-  return changed;
-}
-
-bool GHOST_WindowWayland::outputs_enter(output_t *reg_output)
-{
-  std::vector &outputs = w->outputs;
-  auto it = std::find(outputs.begin(), outputs.end(), reg_output);
-  if (it != outputs.end()) {
-    return false;
-  }
-  outputs.push_back(reg_output);
-  return true;
-}
-
-bool GHOST_WindowWayland::outputs_leave(output_t *reg_output)
-{
-  std::vector &outputs = w->outputs;
-  auto it = std::find(outputs.begin(), outputs.end(), reg_output);
-  if (it == outputs.end()) {
-    return false;
-  }
-  outputs.erase(it);
-  return true;
-}
-
-uint16_t GHOST_WindowWayland::dpi() const
-{
-  return w->dpi;
-}
-
-int GHOST_WindowWayland::scale() const
-{
-  return w->scale;
-}
-
 GHOST_TSuccess GHOST_WindowWayland::setWindowCursorGrab(GHOST_TGrabCursorMode mode)
 {
   return m_system->setCursorGrab(mode, m_cursorGrab, w->wl_surface);
@@ -943,3 +782,192 @@ GHOST_Context *GHOST_WindowWayland::newDrawingContext(GHOST_TDrawingContextType
 }
 
 /** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Public WAYLAND Direct Data Access
+ *
+ * Expose some members via methods.
+ * \{ */
+
+uint16_t GHOST_WindowWayland::dpi() const
+{
+  return w->dpi;
+}
+
+int GHOST_WindowWayland::scale() const
+{
+  return w->scale;
+}
+
+wl_surface *GHOST_WindowWayland::surface() const
+{
+  return w->wl_surface;
+}
+
+const std::vector &GHOST_WindowWayland::outputs()
+{
+  return w->outputs;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Public WAYLAND Query Access
+ * \{ */
+
+GHOST_WindowWayland *GHOST_WindowWayland::from_surface_find_mut(const wl_surface *surface)
+{
+  GHOST_ASSERT(surface, "argument must not be NULL");
+  for (GHOST_IWindow *iwin : window_manager->getWindows()) {
+    GHOST_WindowWayland *win = static_cast(iwin);
+    if (surface == win->surface()) {
+      return win;
+    }
+  }
+  return nullptr;
+}
+
+const GHOST_WindowWayland *GHOST_WindowWayland::from_surface_find(const wl_surface *surface)
+{
+  return GHOST_WindowWayland::from_surface_find_mut(surface);
+}
+
+GHOST_WindowWayland *GHOST_WindowWayland::from_surface_mut(wl_surface *surface)
+{
+  GHOST_WindowWayland *win = static_cast(wl_surface_get_user_data(surface));
+  GHOST_ASSERT(win == GHOST_WindowWayland::from_surface_find_mut(surface),
+               "Inconsistent window state, consider using \"from_surface_find_mut\"");
+  return win;
+}
+
+const GHOST_WindowWayland *GHOST_WindowWayland::from_surface(const wl_surface *surface)
+{
+  const GHOST_WindowWayland *win = static_cast(
+      wl_surface_get_user_data(const_cast(surface)));
+  GHOST_ASSERT(win == GHOST_WindowWayland::from_surface_find(surface),
+               "Inconsistent window state, consider using \"from_surface_find\"");
+  return win;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Public WAYLAND Window Level Functions
+ *
+ * High Level Windowing Utilities.
+ * \{ */
+
+GHOST_TSuccess GHOST_WindowWayland::close()
+{
+  return m_system->pushEvent(
+      new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowClose, this));
+}
+
+GHOST_TSuccess GHOST_WindowWayland::activate()
+{
+  if (m_system->getWindowManager()->setActiveWindow(this) == GHOST_kFailure) {
+    return GHOST_kFailure;
+  }
+  return m_system->pushEvent(
+      new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowActivate, this));
+}
+
+GHOST_TSuccess GHOST_WindowWayland::deactivate()
+{
+  m_system->getWindowManager()->setWindowInactive(this);
+  return m_system->pushEvent(
+      new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowDeactivate, this));
+}
+
+GHOST_TSuccess GHOST_WindowWayland::notify_size()
+{
+#ifdef GHOST_OPENGL_ALPHA
+  setOpaque();
+#endif
+
+  return m_system->pushEvent(
+      new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowSize, this));
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Public WAYLAND Utility Functions
+ *
+ * Functionality only used for the WAYLAND implementation.
+ * \{ */
+
+bool GHOST_WindowWayland::outputs_changed_update_scale()
+{
+  uint32_t dpi_next;
+  const int scale_next = outputs_max_scale_or_default(this->outputs(), 0, &dpi_next);
+  if (scale_next == 0) {
+    return false;
+  }
+
+  window_t *win = this->w;
+  const uint32_t dpi_curr = win->dpi;
+  const int scale_curr = win->scale;
+  bool changed = false;
+
+  if (scale_next != scale_curr) {
+    /* Unlikely but possible there is a pending size change is set. */
+    win->size_pending[0] = (win->size_pending[0] / scale_curr) * scale_next;
+    win->size_pending[1] = (win->size_pending[1] / scale_curr) * scale_next;
+
+    win->scale = scale_next;
+    wl_surface_set_buffer_scale(this->surface(), scale_next);
+    changed = true;
+  }
+
+  if (dpi_next != dpi_curr) {
+    /* Using the real DPI will cause wrong scaling of the UI
+     * use a multiplier for the default DPI as workaround. */
+    win->dpi = dpi_next;
+    changed = true;
+  }
+
+  return changed;
+}
+
+bool GHOST_WindowWayland::outputs_enter(output_t *reg_output)
+{
+  std::vector &outputs = w->outputs;
+  auto it = std::find(outputs.begin(), outputs.end(), reg_output);
+  if (it != outputs.end()) {
+    return false;
+  }
+  outputs.push_back(reg_output);
+  return true;
+}
+
+bool GHOST_WindowWayland::outputs_leave(output_t *reg_output)
+{
+  std::vector &outputs = w->outputs;
+  auto it = std::find(outputs.begin(), outputs.end(), reg_output);
+  if (it == outputs.end()) {
+    return false;
+  }
+  outputs.erase(it);
+  return true;
+}
+
+bool GHOST_WindowWayland::outputs_enter_wl(const wl_output *output)
+{
+  output_t *reg_output = m_system->output_find_by_wl(output);
+  if (!reg_output) {
+    return false;
+  }
+  return outputs_enter(reg_output);
+}
+
+bool GHOST_WindowWayland::outputs_leave_wl(const wl_output *output)
+{
+  output_t *reg_output = m_system->output_find_by_wl(output);
+  if (!reg_output) {
+    return false;
+  }
+  return outputs_leave(reg_output);
+}
+
+/** \} */
diff --git a/intern/ghost/intern/GHOST_WindowWayland.h b/intern/ghost/intern/GHOST_WindowWayland.h
index 1d0693ff1f8..be8ff45553d 100644
--- a/intern/ghost/intern/GHOST_WindowWayland.h
+++ b/intern/ghost/intern/GHOST_WindowWayland.h
@@ -93,17 +93,14 @@ class GHOST_WindowWayland : public GHOST_Window {
   void setOpaque() const;
 #endif
 
-  /* WAYLAND utility functions. */
-
-  GHOST_TSuccess close();
-
-  GHOST_TSuccess activate();
-
-  GHOST_TSuccess deactivate();
-
-  GHOST_TSuccess notify_size();
+  /* WAYLAND direct-data access. */
 
+  uint16_t dpi() const;
+  int scale() const;
   struct wl_surface *surface() const;
+  const std::vector &outputs();
+
+  /* WAYLAND query access. */
 
   /**
    * Use window find function when the window may have been closed.
@@ -117,17 +114,21 @@ class GHOST_WindowWayland : public GHOST_Window {
   static const GHOST_WindowWayland *from_surface(const wl_surface *surface);
   static GHOST_WindowWayland *from_surface_mut(wl_surface *surface);
 
-  output_t *output_find_by_wl(struct wl_output *output);
+  /* WAYLAND window-level functions. */
 
-  const std::vector &outputs();
+  GHOST_TSuccess close();
+  GHOST_TSuccess activate();
+  GHOST_TSuccess deactivate();
+  GHOST_TSuccess notify_size();
+
+  /* WAYLAND utility functions. */
 
   bool outputs_enter(output_t *reg_output);
   bool outputs_leave(output_t *reg_output);
-  bool outputs_changed_update_scale();
-
-  uint16_t dpi() const;
+  bool outputs_enter_wl(const struct wl_output *output);
+  bool outputs_leave_wl(const struct wl_output *output);
 
-  int scale() const;
+  bool outputs_changed_update_scale();
 
  private:
   GHOST_SystemWayland *m_system;
-- 
cgit v1.2.3


From 7e55ff15b094079d8052c353fcc8aa7e1f6422c5 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Thu, 30 Jun 2022 21:31:11 -0500
Subject: Fix: Incorrectly sized curves created in set conversion node

The number of points in the source curve was needed, but the offset
(just zero) was passed instead. It's unclear how this worked before.
A mistake in the recent commit 9e393fc2f12583d32dd.

Also use a common utility for retrieving the sizes of curves
in ranges instead of reimplementing it for this file.
---
 .../geometry/nodes/node_geo_curve_spline_type.cc   | 34 +++++++++-------------
 1 file changed, 14 insertions(+), 20 deletions(-)

diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc
index 8479d61a890..f846a6be53b 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc
@@ -303,15 +303,6 @@ static int to_nurbs_size(const CurveType src_type, const int src_size)
   }
 }
 
-static void retrieve_curve_sizes(const bke::CurvesGeometry &curves, MutableSpan sizes)
-{
-  threading::parallel_for(curves.curves_range(), 4096, [&](IndexRange range) {
-    for (const int i : range) {
-      sizes[i] = curves.points_for_curve(i).size();
-    }
-  });
-}
-
 struct GenericAttributes : NonCopyable, NonMovable {
   Vector src;
   Vector dst;
@@ -354,17 +345,22 @@ static void convert_to_bezier(const CurveComponent &src_component,
                               CurveComponent &dst_component,
                               bke::CurvesGeometry &dst_curves)
 {
+  const Vector unselected_ranges = selection.extract_ranges_invert(
+      src_curves.curves_range());
+
   const VArray src_knot_modes = src_curves.nurbs_knots_modes();
   const VArray src_types = src_curves.curve_types();
   const VArray src_cyclic = src_curves.cyclic();
   const Span src_positions = src_curves.positions();
 
   MutableSpan dst_offsets = dst_curves.offsets_for_write();
-  retrieve_curve_sizes(src_curves, dst_curves.offsets_for_write());
+  bke::curves::fill_curve_counts(src_curves, unselected_ranges, dst_curves.offsets_for_write());
   threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) {
     for (const int i : selection.slice(range)) {
-      dst_offsets[i] = to_bezier_size(
-          CurveType(src_types[i]), src_cyclic[i], KnotsMode(src_knot_modes[i]), dst_offsets[i]);
+      const CurveType type = CurveType(src_types[i]);
+      const KnotsMode knots_mode = KnotsMode(src_knot_modes[i]);
+      const IndexRange points = src_curves.points_for_curve(i);
+      dst_offsets[i] = to_bezier_size(type, src_cyclic[i], knots_mode, points.size());
     }
   });
   bke::curves::accumulate_counts_to_offsets(dst_offsets);
@@ -489,9 +485,6 @@ static void convert_to_bezier(const CurveComponent &src_component,
                                      bezier_to_bezier,
                                      nurbs_to_bezier);
 
-  const Vector unselected_ranges = selection.extract_ranges_invert(
-      src_curves.curves_range());
-
   for (const int i : attributes.src.index_range()) {
     bke::curves::copy_point_data(
         src_curves, dst_curves, unselected_ranges, attributes.src[i], attributes.dst[i]);
@@ -508,15 +501,19 @@ static void convert_to_nurbs(const CurveComponent &src_component,
                              CurveComponent &dst_component,
                              bke::CurvesGeometry &dst_curves)
 {
+  const Vector unselected_ranges = selection.extract_ranges_invert(
+      src_curves.curves_range());
+
   const VArray src_types = src_curves.curve_types();
   const VArray src_cyclic = src_curves.cyclic();
   const Span src_positions = src_curves.positions();
 
   MutableSpan dst_offsets = dst_curves.offsets_for_write();
-  retrieve_curve_sizes(src_curves, dst_curves.offsets_for_write());
+  bke::curves::fill_curve_counts(src_curves, unselected_ranges, dst_curves.offsets_for_write());
   threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) {
     for (const int i : selection.slice(range)) {
-      dst_offsets[i] = to_nurbs_size(CurveType(src_types[i]), dst_offsets[i]);
+      const IndexRange points = src_curves.points_for_curve(i);
+      dst_offsets[i] = to_nurbs_size(CurveType(src_types[i]), points.size());
     }
   });
   bke::curves::accumulate_counts_to_offsets(dst_offsets);
@@ -649,9 +646,6 @@ static void convert_to_nurbs(const CurveComponent &src_component,
                                      bezier_to_nurbs,
                                      nurbs_to_nurbs);
 
-  const Vector unselected_ranges = selection.extract_ranges_invert(
-      src_curves.curves_range());
-
   for (const int i : attributes.src.index_range()) {
     bke::curves::copy_point_data(
         src_curves, dst_curves, unselected_ranges, attributes.src[i], attributes.dst[i]);
-- 
cgit v1.2.3


From 276e419671b31dde4f73c269e94c9e0d7d29708f Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Thu, 30 Jun 2022 21:42:09 -0500
Subject: Curves: Avoid initializing offsets when first allocated

The offsets array that encodes the sizes of each curve must be filled
anyway, or the curves will be in an invalid state. Calloc is unnecessary
here. To make that situation clearer, fill the offsets with -1 in debug
builds. Always set the first offset to zero though, since that can save
some boilerplate in other areas.
---
 source/blender/blenkernel/intern/curves_geometry.cc | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc
index b58781ce806..0326afca202 100644
--- a/source/blender/blenkernel/intern/curves_geometry.cc
+++ b/source/blender/blenkernel/intern/curves_geometry.cc
@@ -62,7 +62,11 @@ CurvesGeometry::CurvesGeometry(const int point_num, const int curve_num)
                              this->point_num,
                              ATTR_POSITION.c_str());
 
-  this->curve_offsets = (int *)MEM_calloc_arrayN(this->curve_num + 1, sizeof(int), __func__);
+  this->curve_offsets = (int *)MEM_malloc_arrayN(this->curve_num + 1, sizeof(int), __func__);
+#ifdef DEBUG
+  this->offsets_for_write().fill(-1);
+#endif
+  this->offsets_for_write().first() = 0;
 
   this->update_customdata_pointers();
 
@@ -84,7 +88,7 @@ static void copy_curves_geometry(CurvesGeometry &dst, const CurvesGeometry &src)
   CustomData_copy(&src.curve_data, &dst.curve_data, CD_MASK_ALL, CD_DUPLICATE, dst.curve_num);
 
   MEM_SAFE_FREE(dst.curve_offsets);
-  dst.curve_offsets = (int *)MEM_calloc_arrayN(dst.point_num + 1, sizeof(int), __func__);
+  dst.curve_offsets = (int *)MEM_malloc_arrayN(dst.point_num + 1, sizeof(int), __func__);
   dst.offsets_for_write().copy_from(src.offsets());
 
   dst.tag_topology_changed();
-- 
cgit v1.2.3


From df8d96ab66adf46603b36145b999f43deb4dba2e Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Thu, 30 Jun 2022 21:51:13 -0500
Subject: Cleanup: Remove unnecessary includes from geometry nodes header

---
 source/blender/nodes/NOD_geometry_exec.hh                   | 3 ---
 source/blender/nodes/geometry/nodes/node_geo_boolean.cc     | 1 +
 source/blender/nodes/geometry/nodes/node_geo_object_info.cc | 2 ++
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh
index b82c05f33be..c2dd1cd3ab5 100644
--- a/source/blender/nodes/NOD_geometry_exec.hh
+++ b/source/blender/nodes/NOD_geometry_exec.hh
@@ -8,15 +8,12 @@
 #include "BKE_attribute_access.hh"
 #include "BKE_geometry_fields.hh"
 #include "BKE_geometry_set.hh"
-#include "BKE_geometry_set_instances.hh"
 
 #include "DNA_node_types.h"
 
 #include "NOD_derived_node_tree.hh"
 #include "NOD_geometry_nodes_eval_log.hh"
 
-#include "GEO_realize_instances.hh"
-
 struct Depsgraph;
 struct ModifierData;
 
diff --git a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc
index daeca311e08..81cce1fc5da 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc
@@ -2,6 +2,7 @@
 
 #include "DNA_mesh_types.h"
 
+#include "BKE_geometry_set_instances.hh"
 #include "BKE_mesh_boolean_convert.hh"
 
 #include "UI_interface.h"
diff --git a/source/blender/nodes/geometry/nodes/node_geo_object_info.cc b/source/blender/nodes/geometry/nodes/node_geo_object_info.cc
index f4d91d7496b..0b2159364f1 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_object_info.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_object_info.cc
@@ -2,6 +2,8 @@
 
 #include "BLI_math_matrix.h"
 
+#include "BKE_geometry_set_instances.hh"
+
 #include "UI_interface.h"
 #include "UI_resources.h"
 
-- 
cgit v1.2.3


From bd7b181e1057db7b68ab0f7947529f3cef962e8d Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Thu, 30 Jun 2022 21:52:04 -0500
Subject: Cleanup: Remove outdated comments

Point clouds always use geometry_set_eval, and so do volumes.
---
 source/blender/blenkernel/intern/geometry_set_instances.cc | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/source/blender/blenkernel/intern/geometry_set_instances.cc b/source/blender/blenkernel/intern/geometry_set_instances.cc
index 2d6e0e05a97..6b260207995 100644
--- a/source/blender/blenkernel/intern/geometry_set_instances.cc
+++ b/source/blender/blenkernel/intern/geometry_set_instances.cc
@@ -71,11 +71,6 @@ GeometrySet object_get_evaluated_geometry_set(const Object &object)
     return geometry_set;
   }
 
-  /* TODO: Cover the case of point clouds without modifiers-- they may not be covered by the
-   * #geometry_set_eval case above. */
-
-  /* TODO: Add volume support. */
-
   /* Return by value since there is not always an existing geometry set owned elsewhere to use. */
   return {};
 }
-- 
cgit v1.2.3


From 650a15fb9b9cd32506454759a0c834d7535b8ee9 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Fri, 1 Jul 2022 15:09:59 +1000
Subject: Fix accessing windows from surfaces in Wayland

This is a follow up to [0], where it was assumed flushing the output
would run the appropriate leave handlers & clear the keyboard & pointer
surfaces. While that's mostly true it's not guaranteed.

Resolve this by clearing the pointers when closing windows and add NULL
checks before accessing the windows.

Tested with Gnome, KDE & River compositors.

[0]: 58ccd8338e53423e223085094af2d35c76285c30
---
 intern/ghost/intern/GHOST_SystemWayland.cpp | 254 ++++++++++++++++------------
 intern/ghost/intern/GHOST_SystemWayland.h   |   3 +
 intern/ghost/intern/GHOST_WindowWayland.cpp |  31 ++--
 intern/ghost/intern/GHOST_WindowWayland.h   |   7 +-
 4 files changed, 159 insertions(+), 136 deletions(-)

diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp
index 6562387358d..fb34a72235c 100644
--- a/intern/ghost/intern/GHOST_SystemWayland.cpp
+++ b/intern/ghost/intern/GHOST_SystemWayland.cpp
@@ -783,13 +783,15 @@ static void relative_pointer_handle_relative_motion(
     const wl_fixed_t /*dy_unaccel*/)
 {
   input_t *input = static_cast(data);
-  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(input->pointer.wl_surface);
-  const wl_fixed_t scale = win->scale();
-  const wl_fixed_t xy_next[2] = {
-      input->pointer.xy[0] + (dx / scale),
-      input->pointer.xy[1] + (dy / scale),
-  };
-  relative_pointer_handle_relative_motion_impl(input, win, xy_next);
+  if (wl_surface *focus_surface = input->pointer.wl_surface) {
+    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    const wl_fixed_t scale = win->scale();
+    const wl_fixed_t xy_next[2] = {
+        input->pointer.xy[0] + (dx / scale),
+        input->pointer.xy[1] + (dy / scale),
+    };
+    relative_pointer_handle_relative_motion_impl(input, win, xy_next);
+  }
 }
 
 static const zwp_relative_pointer_v1_listener relative_pointer_listener = {
@@ -805,17 +807,19 @@ static const zwp_relative_pointer_v1_listener relative_pointer_listener = {
 static void dnd_events(const input_t *const input, const GHOST_TEventType event)
 {
   /* NOTE: `input->data_offer_dnd_mutex` must already be locked. */
-  const uint64_t time = input->system->getMilliSeconds();
-  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(input->focus_dnd);
-  const wl_fixed_t scale = win->scale();
-  const int event_xy[2] = {
-      wl_fixed_to_int(scale * input->data_offer_dnd->dnd.xy[0]),
-      wl_fixed_to_int(scale * input->data_offer_dnd->dnd.xy[1]),
-  };
+  if (wl_surface *focus_surface = input->focus_dnd) {
+    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    const wl_fixed_t scale = win->scale();
+    const int event_xy[2] = {
+        wl_fixed_to_int(scale * input->data_offer_dnd->dnd.xy[0]),
+        wl_fixed_to_int(scale * input->data_offer_dnd->dnd.xy[1]),
+    };
 
-  for (const std::string &type : mime_preference_order) {
-    input->system->pushEvent(new GHOST_EventDragnDrop(
-        time, event, mime_dnd.at(type), win, event_xy[0], event_xy[1], nullptr));
+    const uint64_t time = input->system->getMilliSeconds();
+    for (const std::string &type : mime_preference_order) {
+      input->system->pushEvent(new GHOST_EventDragnDrop(
+          time, event, mime_dnd.at(type), win, event_xy[0], event_xy[1], nullptr));
+    }
   }
 }
 
@@ -1071,7 +1075,7 @@ static void data_device_handle_drop(void *data, struct wl_data_device * /*wl_dat
       static constexpr const char *file_proto = "file://";
       static constexpr const char *crlf = "\r\n";
 
-      GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(surface);
+      GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_find(surface);
       GHOST_ASSERT(win != nullptr, "Unable to find window for drop event from surface");
 
       std::vector uris;
@@ -1301,12 +1305,10 @@ static void pointer_handle_leave(void *data,
 {
   /* First clear the `pointer.wl_surface`, since the window won't exist when closing the window. */
   static_cast(data)->pointer.wl_surface = nullptr;
-  /* Use `from_surface_find_mut` because this callback runs when the window is has been closed. */
-  if (!surface) {
-    return;
+  if (surface) {
+    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(surface);
+    win->deactivate();
   }
-  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(surface);
-  win->deactivate();
 }
 
 static void pointer_handle_motion(void *data,
@@ -1319,14 +1321,16 @@ static void pointer_handle_motion(void *data,
   input->pointer.xy[0] = surface_x;
   input->pointer.xy[1] = surface_y;
 
-  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(input->pointer.wl_surface);
-  const wl_fixed_t scale = win->scale();
-  input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(),
-                                                 GHOST_kEventCursorMove,
-                                                 win,
-                                                 wl_fixed_to_int(scale * input->pointer.xy[0]),
-                                                 wl_fixed_to_int(scale * input->pointer.xy[1]),
-                                                 GHOST_TABLET_DATA_NONE));
+  if (wl_surface *focus_surface = input->pointer.wl_surface) {
+    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    const wl_fixed_t scale = win->scale();
+    input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(),
+                                                   GHOST_kEventCursorMove,
+                                                   win,
+                                                   wl_fixed_to_int(scale * input->pointer.xy[0]),
+                                                   wl_fixed_to_int(scale * input->pointer.xy[1]),
+                                                   GHOST_TABLET_DATA_NONE));
+  }
 }
 
 static void pointer_handle_button(void *data,
@@ -1376,9 +1380,11 @@ static void pointer_handle_button(void *data,
   input->data_source_serial = serial;
   input->pointer.buttons.set(ebutton, state == WL_POINTER_BUTTON_STATE_PRESSED);
 
-  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(input->pointer.wl_surface);
-  input->system->pushEvent(new GHOST_EventButton(
-      input->system->getMilliSeconds(), etype, win, ebutton, GHOST_TABLET_DATA_NONE));
+  if (wl_surface *focus_surface = input->pointer.wl_surface) {
+    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    input->system->pushEvent(new GHOST_EventButton(
+        input->system->getMilliSeconds(), etype, win, ebutton, GHOST_TABLET_DATA_NONE));
+  }
 }
 
 static void pointer_handle_axis(void *data,
@@ -1393,9 +1399,11 @@ static void pointer_handle_axis(void *data,
     return;
   }
 
-  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(input->pointer.wl_surface);
-  input->system->pushEvent(
-      new GHOST_EventWheel(input->system->getMilliSeconds(), win, std::signbit(value) ? +1 : -1));
+  if (wl_surface *focus_surface = input->pointer.wl_surface) {
+    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    input->system->pushEvent(new GHOST_EventWheel(
+        input->system->getMilliSeconds(), win, std::signbit(value) ? +1 : -1));
+  }
 }
 
 static const struct wl_pointer_listener pointer_listener = {
@@ -1509,9 +1517,11 @@ static void tablet_tool_handle_down(void *data,
   input->data_source_serial = serial;
   input->tablet.buttons.set(ebutton, true);
 
-  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(input->tablet.wl_surface);
-  input->system->pushEvent(new GHOST_EventButton(
-      input->system->getMilliSeconds(), etype, win, ebutton, tool_input->data));
+  if (wl_surface *focus_surface = input->tablet.wl_surface) {
+    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    input->system->pushEvent(new GHOST_EventButton(
+        input->system->getMilliSeconds(), etype, win, ebutton, tool_input->data));
+  }
 }
 
 static void tablet_tool_handle_up(void *data, struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/)
@@ -1523,9 +1533,11 @@ static void tablet_tool_handle_up(void *data, struct zwp_tablet_tool_v2 * /*zwp_
 
   input->tablet.buttons.set(ebutton, false);
 
-  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(input->tablet.wl_surface);
-  input->system->pushEvent(new GHOST_EventButton(
-      input->system->getMilliSeconds(), etype, win, ebutton, tool_input->data));
+  if (wl_surface *focus_surface = input->tablet.wl_surface) {
+    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    input->system->pushEvent(new GHOST_EventButton(
+        input->system->getMilliSeconds(), etype, win, ebutton, tool_input->data));
+  }
 }
 
 static void tablet_tool_handle_motion(void *data,
@@ -1593,8 +1605,10 @@ static void tablet_tool_handle_wheel(void *data,
 
   tablet_tool_input_t *tool_input = static_cast(data);
   input_t *input = tool_input->input;
-  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(input->tablet.wl_surface);
-  input->system->pushEvent(new GHOST_EventWheel(input->system->getMilliSeconds(), win, clicks));
+  if (wl_surface *focus_surface = input->tablet.wl_surface) {
+    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    input->system->pushEvent(new GHOST_EventWheel(input->system->getMilliSeconds(), win, clicks));
+  }
 }
 static void tablet_tool_handle_button(void *data,
                                       struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
@@ -1631,9 +1645,11 @@ static void tablet_tool_handle_button(void *data,
   input->data_source_serial = serial;
   input->tablet.buttons.set(ebutton, state == WL_POINTER_BUTTON_STATE_PRESSED);
 
-  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(input->tablet.wl_surface);
-  input->system->pushEvent(new GHOST_EventButton(
-      input->system->getMilliSeconds(), etype, win, ebutton, tool_input->data));
+  if (wl_surface *focus_surface = input->tablet.wl_surface) {
+    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    input->system->pushEvent(new GHOST_EventButton(
+        input->system->getMilliSeconds(), etype, win, ebutton, tool_input->data));
+  }
 }
 static void tablet_tool_handle_frame(void *data,
                                      struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
@@ -1642,10 +1658,8 @@ static void tablet_tool_handle_frame(void *data,
   tablet_tool_input_t *tool_input = static_cast(data);
   input_t *input = tool_input->input;
 
-  /* Use "find", unlike most other handlers because the window
-   * may have been closed before this handler is called. */
-  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_find_mut(input->tablet.wl_surface);
-  if (win) {
+  if (wl_surface *focus_surface = input->tablet.wl_surface) {
+    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
     const wl_fixed_t scale = win->scale();
     input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(),
                                                    GHOST_kEventCursorMove,
@@ -1653,12 +1667,12 @@ static void tablet_tool_handle_frame(void *data,
                                                    wl_fixed_to_int(scale * input->tablet.xy[0]),
                                                    wl_fixed_to_int(scale * input->tablet.xy[1]),
                                                    tool_input->data));
+    if (tool_input->proximity == false) {
+      win->setCursorShape(win->getCursorShape());
+    }
   }
 
   if (tool_input->proximity == false) {
-    if (win) {
-      win->setCursorShape(win->getCursorShape());
-    }
     input->tablet.wl_surface = nullptr;
   }
 }
@@ -1966,9 +1980,11 @@ static void keyboard_handle_key(void *data,
 
   input->data_source_serial = serial;
 
-  GHOST_IWindow *win = GHOST_WindowWayland::from_surface_mut(input->keyboard.wl_surface);
-  input->system->pushEvent(new GHOST_EventKey(
-      input->system->getMilliSeconds(), etype, win, gkey, '\0', utf8_buf, false));
+  if (wl_surface *focus_surface = input->keyboard.wl_surface) {
+    GHOST_IWindow *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    input->system->pushEvent(new GHOST_EventKey(
+        input->system->getMilliSeconds(), etype, win, gkey, '\0', utf8_buf, false));
+  }
 
   /* An existing payload means the key repeat timer is reset and will be added again. */
   if (key_repeat_payload == nullptr) {
@@ -1989,8 +2005,8 @@ static void keyboard_handle_key(void *data,
           task->getUserData());
 
       input_t *input = payload->input;
-      if (GHOST_IWindow *win = GHOST_WindowWayland::from_surface_find_mut(
-              input->keyboard.wl_surface)) {
+      if (wl_surface *focus_surface = input->keyboard.wl_surface) {
+        GHOST_IWindow *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
         GHOST_SystemWayland *system = input->system;
         /* Calculate this value every time in case modifier keys are pressed. */
         char utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)] = {'\0'};
@@ -2669,15 +2685,15 @@ GHOST_TSuccess GHOST_SystemWayland::getCursorPosition(int32_t &x, int32_t &y) co
   }
   input_t *input = d->inputs[0];
   input_state_pointer_t *input_state = input_state_pointer_active(input);
-  if (!input_state || !input_state->wl_surface) {
+  if (!input_state) {
     return GHOST_kFailure;
   }
-  /* Use 'from_surface_find_mut' because this can be called outside WAYLAND's event loop. */
-  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_find_mut(input->pointer.wl_surface);
-  if (!win) {
-    return GHOST_kFailure;
+
+  if (wl_surface *focus_surface = input_state->wl_surface) {
+    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    return getCursorPositionClientRelative_impl(input_state, win, x, y);
   }
-  return getCursorPositionClientRelative_impl(input_state, win, x, y);
+  return GHOST_kFailure;
 }
 
 GHOST_TSuccess GHOST_SystemWayland::setCursorPosition(const int32_t x, const int32_t y)
@@ -2686,15 +2702,14 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorPosition(const int32_t x, const int
     return GHOST_kFailure;
   }
   input_t *input = d->inputs[0];
-  if (!input->pointer.wl_surface) {
-    return GHOST_kFailure;
-  }
-  /* Use 'from_surface_find_mut' because this can be called outside WAYLAND's event loop. */
-  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_find_mut(input->pointer.wl_surface);
-  if (!win) {
-    return GHOST_kFailure;
+
+  /* Intentionally different from `getCursorPosition` which supports both tablet & pointer.
+   * In the case of setting the cursor location, tablets don't support this. */
+  if (wl_surface *focus_surface = input->pointer.wl_surface) {
+    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    return setCursorPositionClientRelative_impl(input, win, x, y);
   }
-  return setCursorPositionClientRelative_impl(input, win, x, y);
+  return GHOST_kFailure;
 }
 
 void GHOST_SystemWayland::getMainDisplayDimensions(uint32_t &width, uint32_t &height) const
@@ -3234,8 +3249,7 @@ static bool setCursorGrab_use_software_confine(const GHOST_TGrabCursorMode mode,
   if (mode != GHOST_kGrabNormal) {
     return false;
   }
-  /* Use 'from_surface_find_mut' because this can be called outside WAYLAND's event loop. */
-  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_find_mut(surface);
+  const GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface(surface);
   if (!win) {
     return false;
   }
@@ -3314,45 +3328,43 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorGrab(const GHOST_TGrabCursorMode mo
     if (input->locked_pointer) {
       /* Request location to restore to. */
       if (mode_current == GHOST_kGrabWrap) {
-        /* The chance this fails is _very_ low, it may be called outside WAYLAND's event loop. */
-        GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_find_mut(surface);
-        if (!win) {
-          GHOST_PRINT("could not find window from surface when un-grabbing!" << std::endl);
+        /* Since this call is initiated by Blender, we can be sure the window wasn't closed
+         * by logic outside this function - as the window was needed to make this call. */
+        GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(surface);
+        GHOST_ASSERT(win, "could not find window from surface when un-grabbing!");
+
+        GHOST_Rect bounds;
+        int32_t xy_new[2] = {input->pointer.xy[0], input->pointer.xy[1]};
+
+        /* Fallback to window bounds. */
+        if (win->getCursorGrabBounds(bounds) == GHOST_kFailure) {
+          win->getClientBounds(bounds);
         }
-        else {
-          GHOST_Rect bounds;
-          int32_t xy_new[2] = {input->pointer.xy[0], input->pointer.xy[1]};
-
-          /* Fallback to window bounds. */
-          if (win->getCursorGrabBounds(bounds) == GHOST_kFailure) {
-            win->getClientBounds(bounds);
-          }
-
-          const int scale = win->scale();
-
-          bounds.m_l = wl_fixed_from_int(bounds.m_l) / scale;
-          bounds.m_t = wl_fixed_from_int(bounds.m_t) / scale;
-          bounds.m_r = wl_fixed_from_int(bounds.m_r) / scale;
-          bounds.m_b = wl_fixed_from_int(bounds.m_b) / scale;
-
-          bounds.wrapPoint(xy_new[0], xy_new[1], 0, win->getCursorGrabAxis());
-
-          /* Push an event so the new location is registered. */
-          if ((xy_new[0] != input->pointer.xy[0]) || (xy_new[1] != input->pointer.xy[1])) {
-            input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(),
-                                                           GHOST_kEventCursorMove,
-                                                           win,
-                                                           wl_fixed_to_int(scale * xy_new[0]),
-                                                           wl_fixed_to_int(scale * xy_new[1]),
-                                                           GHOST_TABLET_DATA_NONE));
-          }
-          input->pointer.xy[0] = xy_new[0];
-          input->pointer.xy[1] = xy_new[1];
 
-          zwp_locked_pointer_v1_set_cursor_position_hint(
-              input->locked_pointer, xy_new[0], xy_new[1]);
-          wl_surface_commit(surface);
+        const int scale = win->scale();
+
+        bounds.m_l = wl_fixed_from_int(bounds.m_l) / scale;
+        bounds.m_t = wl_fixed_from_int(bounds.m_t) / scale;
+        bounds.m_r = wl_fixed_from_int(bounds.m_r) / scale;
+        bounds.m_b = wl_fixed_from_int(bounds.m_b) / scale;
+
+        bounds.wrapPoint(xy_new[0], xy_new[1], 0, win->getCursorGrabAxis());
+
+        /* Push an event so the new location is registered. */
+        if ((xy_new[0] != input->pointer.xy[0]) || (xy_new[1] != input->pointer.xy[1])) {
+          input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(),
+                                                         GHOST_kEventCursorMove,
+                                                         win,
+                                                         wl_fixed_to_int(scale * xy_new[0]),
+                                                         wl_fixed_to_int(scale * xy_new[1]),
+                                                         GHOST_TABLET_DATA_NONE));
         }
+        input->pointer.xy[0] = xy_new[0];
+        input->pointer.xy[1] = xy_new[1];
+
+        zwp_locked_pointer_v1_set_cursor_position_hint(
+            input->locked_pointer, xy_new[0], xy_new[1]);
+        wl_surface_commit(surface);
       }
 #ifdef USE_GNOME_CONFINE_HACK
       else if (mode_current == GHOST_kGrabNormal) {
@@ -3495,4 +3507,22 @@ void GHOST_SystemWayland::selection_set(const std::string &selection)
   this->selection = selection;
 }
 
+void GHOST_SystemWayland::window_surface_unref(const wl_surface *surface)
+{
+#define SURFACE_CLEAR_PTR(surface_test) \
+  if (surface_test == surface) { \
+    surface_test = nullptr; \
+  } \
+  ((void)0);
+
+  /* Only clear window surfaces (not cursors, off-screen surfaces etc). */
+  for (input_t *input : d->inputs) {
+    SURFACE_CLEAR_PTR(input->pointer.wl_surface);
+    SURFACE_CLEAR_PTR(input->tablet.wl_surface);
+    SURFACE_CLEAR_PTR(input->keyboard.wl_surface);
+    SURFACE_CLEAR_PTR(input->focus_dnd);
+  }
+#undef SURFACE_CLEAR_PTR
+}
+
 /** \} */
diff --git a/intern/ghost/intern/GHOST_SystemWayland.h b/intern/ghost/intern/GHOST_SystemWayland.h
index 51753a85668..7fe35146be0 100644
--- a/intern/ghost/intern/GHOST_SystemWayland.h
+++ b/intern/ghost/intern/GHOST_SystemWayland.h
@@ -159,6 +159,9 @@ class GHOST_SystemWayland : public GHOST_System {
 
   void selection_set(const std::string &selection);
 
+  /** Clear all references to this surface to prevent accessing NULL pointers. */
+  void window_surface_unref(const wl_surface *surface);
+
  private:
   struct display_t *d;
   std::string selection;
diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp
index c11dfb612b2..159cd808c0c 100644
--- a/intern/ghost/intern/GHOST_WindowWayland.cpp
+++ b/intern/ghost/intern/GHOST_WindowWayland.cpp
@@ -598,20 +598,15 @@ GHOST_WindowWayland::~GHOST_WindowWayland()
   xdg_surface_destroy(w->xdg_surface);
 #endif
 
+  /* Clear any pointers to this window. This is needed because there are no guarantees
+   * that flushing the display will the "leave" handlers before handling events. */
+  m_system->window_surface_unref(w->wl_surface);
+
   wl_surface_destroy(w->wl_surface);
 
-  /* NOTE(@campbellbarton): This is needed so the appropriate handlers event
-   * (#wl_surface_listener.leave in particular) run to prevent access to the freed surfaces.
-   * Without flushing the display, calling #getCursorPosition immediately after closing a window
-   * causes dangling #wl_surface pointers to be accessed
-   * (since the window is used for scaling the cursor position).
-   *
-   * An alternative solution would be to clear all internal pointers that reference this window.
-   * Even though this is reasonable it introduces a 3rd state that needs to be accounted for,
-   * where values are cleared before they have been set to their new values.
-   * Any information requested in this state (such as the cursor position) won't be valid and
-   * could cause difficult to reproduce bugs. So perform a round-trip as closing a window isn't
-   * an action that runs continuously & isn't likely to cause unnecessary overhead. See: T99078. */
+  /* NOTE(@campbellbarton): Flushing will often run the appropriate handlers event
+   * (#wl_surface_listener.leave in particular) to avoid attempted access to the freed surfaces.
+   * This is not fool-proof though, hence the call to #window_surface_unref, see: T99078. */
   wl_display_flush(m_system->display());
 
   delete w;
@@ -815,7 +810,7 @@ const std::vector &GHOST_WindowWayland::outputs()
 /** \name Public WAYLAND Query Access
  * \{ */
 
-GHOST_WindowWayland *GHOST_WindowWayland::from_surface_find_mut(const wl_surface *surface)
+GHOST_WindowWayland *GHOST_WindowWayland::from_surface_find(const wl_surface *surface)
 {
   GHOST_ASSERT(surface, "argument must not be NULL");
   for (GHOST_IWindow *iwin : window_manager->getWindows()) {
@@ -827,16 +822,12 @@ GHOST_WindowWayland *GHOST_WindowWayland::from_surface_find_mut(const wl_surface
   return nullptr;
 }
 
-const GHOST_WindowWayland *GHOST_WindowWayland::from_surface_find(const wl_surface *surface)
-{
-  return GHOST_WindowWayland::from_surface_find_mut(surface);
-}
-
 GHOST_WindowWayland *GHOST_WindowWayland::from_surface_mut(wl_surface *surface)
 {
+  GHOST_ASSERT(surface, "argument must not be NULL");
   GHOST_WindowWayland *win = static_cast(wl_surface_get_user_data(surface));
-  GHOST_ASSERT(win == GHOST_WindowWayland::from_surface_find_mut(surface),
-               "Inconsistent window state, consider using \"from_surface_find_mut\"");
+  GHOST_ASSERT(win == GHOST_WindowWayland::from_surface_find(surface),
+               "Inconsistent window state, consider using \"from_surface_find\"");
   return win;
 }
 
diff --git a/intern/ghost/intern/GHOST_WindowWayland.h b/intern/ghost/intern/GHOST_WindowWayland.h
index be8ff45553d..1afad3b47b3 100644
--- a/intern/ghost/intern/GHOST_WindowWayland.h
+++ b/intern/ghost/intern/GHOST_WindowWayland.h
@@ -36,10 +36,10 @@ class GHOST_WindowWayland : public GHOST_Window {
 
   ~GHOST_WindowWayland() override;
 
-  uint16_t getDPIHint() override;
-
   /* Ghost API */
 
+  uint16_t getDPIHint() override;
+
   GHOST_TSuccess setWindowCursorGrab(GHOST_TGrabCursorMode mode) override;
 
   GHOST_TSuccess setWindowCursorShape(GHOST_TStandardCursor shape) override;
@@ -106,8 +106,7 @@ class GHOST_WindowWayland : public GHOST_Window {
    * Use window find function when the window may have been closed.
    * Typically this is needed when accessing surfaces outside WAYLAND handlers.
    */
-  static const GHOST_WindowWayland *from_surface_find(const wl_surface *surface);
-  static GHOST_WindowWayland *from_surface_find_mut(const wl_surface *surface);
+  static GHOST_WindowWayland *from_surface_find(const wl_surface *surface);
   /**
    * Use direct access when from WAYLAND handlers.
    */
-- 
cgit v1.2.3


From b1d3b14711b2d113742b1693169a0997c737afcc Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Fri, 1 Jul 2022 15:33:18 +1000
Subject: Fix crash when creating an off-screen context fails in Wayland

This is very unlikely to happen but better not to crash.
---
 intern/ghost/intern/GHOST_SystemWayland.cpp | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp
index fb34a72235c..3a5e524782f 100644
--- a/intern/ghost/intern/GHOST_SystemWayland.cpp
+++ b/intern/ghost/intern/GHOST_SystemWayland.cpp
@@ -2789,14 +2789,18 @@ GHOST_IContext *GHOST_SystemWayland::createOffscreenContext(GHOST_GLSettings /*g
 {
   /* Create new off-screen window. */
   wl_surface *wl_surface = wl_compositor_create_surface(compositor());
-  wl_egl_window *egl_window = wl_egl_window_create(wl_surface, int(1), int(1));
+  wl_egl_window *egl_window = wl_surface ? wl_egl_window_create(wl_surface, 1, 1) : nullptr;
 
   GHOST_Context *context = createOffscreenContext_impl(this, d->display, egl_window);
 
   if (!context) {
     GHOST_PRINT("Cannot create off-screen EGL context" << std::endl);
-    wl_surface_destroy(wl_surface);
-    wl_egl_window_destroy(egl_window);
+    if (wl_surface) {
+      wl_surface_destroy(wl_surface);
+    }
+    if (egl_window) {
+      wl_egl_window_destroy(egl_window);
+    }
     return nullptr;
   }
 
-- 
cgit v1.2.3


From 16264aebe6e022f9fbb95851d8806681ae5726a3 Mon Sep 17 00:00:00 2001
From: Richard Antalik 
Date: Fri, 1 Jul 2022 08:59:03 +0200
Subject: Fix sequencer transform test failing.

Issue was caused by using function `SEQ_render_give_stripelem` to obtain
first `StripElem`, but this function now takes retiming into account.

Since first element was meant to be obtained, point to it directly by
using `seq->strip->stripdata`.
---
 source/blender/blenloader/intern/versioning_290.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/source/blender/blenloader/intern/versioning_290.c b/source/blender/blenloader/intern/versioning_290.c
index 47786f55871..de652b40590 100644
--- a/source/blender/blenloader/intern/versioning_290.c
+++ b/source/blender/blenloader/intern/versioning_290.c
@@ -160,7 +160,7 @@ static void seq_convert_transform_crop(const Scene *scene,
   const uint32_t use_transform_flag = (1 << 16);
   const uint32_t use_crop_flag = (1 << 17);
 
-  const StripElem *s_elem = SEQ_render_give_stripelem(scene, seq, seq->start);
+  const StripElem *s_elem = seq->strip->stripdata;
   if (s_elem != NULL) {
     image_size_x = s_elem->orig_width;
     image_size_y = s_elem->orig_height;
@@ -285,7 +285,7 @@ static void seq_convert_transform_crop_2(const Scene *scene,
                                          Sequence *seq,
                                          const eSpaceSeq_Proxy_RenderSize render_size)
 {
-  const StripElem *s_elem = SEQ_render_give_stripelem(scene, seq, seq->start);
+  const StripElem *s_elem = seq->strip->stripdata;
   if (s_elem == NULL) {
     return;
   }
-- 
cgit v1.2.3


From b872ad037aaaffb6783cee9339ce4d876fc1ffca Mon Sep 17 00:00:00 2001
From: Sergey Sharybin 
Date: Fri, 1 Jul 2022 09:40:52 +0200
Subject: Fix T99315: Unit plane track deform compositor node leads to
 unnecessary blur

---
 .../blender/compositor/operations/COM_PlaneDistortCommonOperation.cc | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.cc b/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.cc
index ddda02c5e80..9c19f55e04f 100644
--- a/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.cc
+++ b/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.cc
@@ -43,6 +43,11 @@ BLI_INLINE void warp_coord(float x, float y, float matrix[3][3], float uv[2], fl
   uv[0] = vec[0] / vec[2];
   uv[1] = vec[1] / vec[2];
 
+  /* Offset so that pixel center corresponds to a (0.5, 0.5), which helps keeping transformed
+   * image sharp. */
+  uv[0] += 0.5f;
+  uv[1] += 0.5f;
+
   deriv[0][0] = (matrix[0][0] - matrix[0][2] * uv[0]) / vec[2];
   deriv[1][0] = (matrix[0][1] - matrix[0][2] * uv[1]) / vec[2];
   deriv[0][1] = (matrix[1][0] - matrix[1][2] * uv[0]) / vec[2];
-- 
cgit v1.2.3


From c922b9e2c145b0c945fdb9d99f1209df3bf8bad0 Mon Sep 17 00:00:00 2001
From: Sergey Sharybin 
Date: Thu, 30 Jun 2022 15:17:30 +0200
Subject: Fix image-from-imbuf resulting in invalid image configuration

The image which source is set to file is not expected to have empty
file path. If it happens it becomes very tricky to save the image on
exit using the standard quit dialog.

This change makes it so if the image buffer does not have file path
then the new image is set to the "generated" source and it behaves
as if the image was created like so and was fully painted on.

Additionally, mark image as dirty, so that quitting Blender after
such image was added will warn about possible data loss.
---
 source/blender/blenkernel/intern/image.cc | 27 ++++++++++++++++++++++-----
 1 file changed, 22 insertions(+), 5 deletions(-)

diff --git a/source/blender/blenkernel/intern/image.cc b/source/blender/blenkernel/intern/image.cc
index 0c4dc3c87f3..ed8652ca470 100644
--- a/source/blender/blenkernel/intern/image.cc
+++ b/source/blender/blenkernel/intern/image.cc
@@ -1174,18 +1174,35 @@ Image *BKE_image_add_generated(Main *bmain,
 
 Image *BKE_image_add_from_imbuf(Main *bmain, ImBuf *ibuf, const char *name)
 {
-  Image *ima;
-
   if (name == nullptr) {
     name = BLI_path_basename(ibuf->name);
   }
 
-  ima = image_alloc(bmain, name, IMA_SRC_FILE, IMA_TYPE_IMAGE);
+  /* When the image buffer has valid path create a new image with "file" source and copy the path
+   * from the image buffer.
+   * Otherwise create "generated" image, avoiding invalid configuration with an empty file path. */
+  const eImageSource source = ibuf->name[0] != '\0' ? IMA_SRC_FILE : IMA_SRC_GENERATED;
 
-  if (ima) {
+  Image *ima = image_alloc(bmain, name, source, IMA_TYPE_IMAGE);
+
+  if (!ima) {
+    return nullptr;
+  }
+
+  if (source == IMA_SRC_FILE) {
     STRNCPY(ima->filepath, ibuf->name);
-    image_assign_ibuf(ima, ibuf, IMA_NO_INDEX, 0);
   }
+  else if (ibuf->rect_float) {
+    /* For the consistency with manual image creation: when the image buffer is float reflect it in
+     * the generated flags. */
+    ima->gen_flag |= IMA_GEN_FLOAT;
+  }
+
+  image_assign_ibuf(ima, ibuf, IMA_NO_INDEX, 0);
+
+  /* Consider image dirty since its content can not be re-created unless the image is explicitly
+   * saved. */
+  BKE_image_mark_dirty(ima, ibuf);
 
   return ima;
 }
-- 
cgit v1.2.3


From dfa5bd689e470ad9b8fc7328927afdbe0159e0c1 Mon Sep 17 00:00:00 2001
From: Sergey Sharybin 
Date: Thu, 30 Jun 2022 15:31:12 +0200
Subject: Fix possible wrong image color space when it is created from image
 buffer

From quick look it doesn't seem to be leading to real issues yet as the
image buffers are created with the default roles, but valid color space
is needed to be ensured for an upcoming development.
---
 source/blender/blenkernel/intern/image.cc     | 28 +++++++++++++++++++++++++++
 source/blender/imbuf/IMB_colormanagement.h    |  1 +
 source/blender/imbuf/intern/colormanagement.c |  5 +++++
 3 files changed, 34 insertions(+)

diff --git a/source/blender/blenkernel/intern/image.cc b/source/blender/blenkernel/intern/image.cc
index ed8652ca470..b6294eb058c 100644
--- a/source/blender/blenkernel/intern/image.cc
+++ b/source/blender/blenkernel/intern/image.cc
@@ -1172,6 +1172,33 @@ Image *BKE_image_add_generated(Main *bmain,
   return ima;
 }
 
+static void image_colorspace_from_imbuf(Image *image, const ImBuf *ibuf)
+{
+  const char *colorspace_name = NULL;
+
+  if (ibuf->rect_float) {
+    if (ibuf->float_colorspace) {
+      colorspace_name = IMB_colormanagement_colorspace_get_name(ibuf->float_colorspace);
+    }
+    else {
+      colorspace_name = IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_DEFAULT_FLOAT);
+    }
+  }
+
+  if (ibuf->rect && !colorspace_name) {
+    if (ibuf->rect_colorspace) {
+      colorspace_name = IMB_colormanagement_colorspace_get_name(ibuf->rect_colorspace);
+    }
+    else {
+      colorspace_name = IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_DEFAULT_BYTE);
+    }
+  }
+
+  if (colorspace_name) {
+    STRNCPY(image->colorspace_settings.name, colorspace_name);
+  }
+}
+
 Image *BKE_image_add_from_imbuf(Main *bmain, ImBuf *ibuf, const char *name)
 {
   if (name == nullptr) {
@@ -1199,6 +1226,7 @@ Image *BKE_image_add_from_imbuf(Main *bmain, ImBuf *ibuf, const char *name)
   }
 
   image_assign_ibuf(ima, ibuf, IMA_NO_INDEX, 0);
+  image_colorspace_from_imbuf(ima, ibuf);
 
   /* Consider image dirty since its content can not be re-created unless the image is explicitly
    * saved. */
diff --git a/source/blender/imbuf/IMB_colormanagement.h b/source/blender/imbuf/IMB_colormanagement.h
index 2f0d2f9b449..0818dd653a1 100644
--- a/source/blender/imbuf/IMB_colormanagement.h
+++ b/source/blender/imbuf/IMB_colormanagement.h
@@ -341,6 +341,7 @@ const char *IMB_colormanagement_look_get_indexed_name(int index);
 
 int IMB_colormanagement_colorspace_get_named_index(const char *name);
 const char *IMB_colormanagement_colorspace_get_indexed_name(int index);
+const char *IMB_colormanagement_colorspace_get_name(const struct ColorSpace *colorspace);
 const char *IMB_colormanagement_view_get_default_name(const char *display_name);
 
 void IMB_colormanagement_colorspace_from_ibuf_ftype(
diff --git a/source/blender/imbuf/intern/colormanagement.c b/source/blender/imbuf/intern/colormanagement.c
index a58c2ba4c44..b95ec1cd92b 100644
--- a/source/blender/imbuf/intern/colormanagement.c
+++ b/source/blender/imbuf/intern/colormanagement.c
@@ -3174,6 +3174,11 @@ const char *IMB_colormanagement_colorspace_get_indexed_name(int index)
   return "";
 }
 
+const char *IMB_colormanagement_colorspace_get_name(const ColorSpace *colorspace)
+{
+  return colorspace->name;
+}
+
 void IMB_colormanagement_colorspace_from_ibuf_ftype(
     ColorManagedColorspaceSettings *colorspace_settings, ImBuf *ibuf)
 {
-- 
cgit v1.2.3


From 72b9e07cf26ddeb26ea8773004b951a7f1bff7c5 Mon Sep 17 00:00:00 2001
From: Sergey Sharybin 
Date: Thu, 30 Jun 2022 15:37:32 +0200
Subject: Add helper function to replace buffer of a single-frame image

Very similar to BKE_image_add_from_imbuf with the exception that no
new image data-block is created, but instead the given one is modified.
---
 source/blender/blenkernel/BKE_image.h     |  8 +++++++
 source/blender/blenkernel/intern/image.cc | 40 +++++++++++++++++++++----------
 2 files changed, 35 insertions(+), 13 deletions(-)

diff --git a/source/blender/blenkernel/BKE_image.h b/source/blender/blenkernel/BKE_image.h
index 1f131568900..e54932fbc4e 100644
--- a/source/blender/blenkernel/BKE_image.h
+++ b/source/blender/blenkernel/BKE_image.h
@@ -194,6 +194,14 @@ struct Image *BKE_image_add_generated(struct Main *bmain,
  */
 struct Image *BKE_image_add_from_imbuf(struct Main *bmain, struct ImBuf *ibuf, const char *name);
 
+/**
+ * For a non-viewer single-buffer image (single frame file, or generated image) replace its image
+ * buffer with the given one.
+ * If an unsupported image type (multi-layer, image sequence, ...) the function will assert in the
+ * debug mode and will have an underfined behavior in the release mode.
+ */
+void BKE_image_replace_imbuf(struct Image *image, struct ImBuf *ibuf);
+
 /**
  * For reload, refresh, pack.
  */
diff --git a/source/blender/blenkernel/intern/image.cc b/source/blender/blenkernel/intern/image.cc
index b6294eb058c..92e98935e83 100644
--- a/source/blender/blenkernel/intern/image.cc
+++ b/source/blender/blenkernel/intern/image.cc
@@ -1216,23 +1216,37 @@ Image *BKE_image_add_from_imbuf(Main *bmain, ImBuf *ibuf, const char *name)
     return nullptr;
   }
 
-  if (source == IMA_SRC_FILE) {
-    STRNCPY(ima->filepath, ibuf->name);
-  }
-  else if (ibuf->rect_float) {
-    /* For the consistency with manual image creation: when the image buffer is float reflect it in
-     * the generated flags. */
-    ima->gen_flag |= IMA_GEN_FLOAT;
-  }
+  BKE_image_replace_imbuf(ima, ibuf);
+
+  return ima;
+}
+
+void BKE_image_replace_imbuf(Image *image, ImBuf *ibuf)
+{
+  BLI_assert(image->type == IMA_TYPE_IMAGE &&
+             ELEM(image->source, IMA_SRC_FILE, IMA_SRC_GENERATED));
+
+  BKE_image_free_buffers(image);
 
-  image_assign_ibuf(ima, ibuf, IMA_NO_INDEX, 0);
-  image_colorspace_from_imbuf(ima, ibuf);
+  image_assign_ibuf(image, ibuf, IMA_NO_INDEX, 0);
+  image_colorspace_from_imbuf(image, ibuf);
+
+  /* Keep generated image type flags consistent with the image buffer. */
+  if (image->source == IMA_SRC_GENERATED) {
+    if (ibuf->rect_float) {
+      image->gen_flag |= IMA_GEN_FLOAT;
+    }
+    else {
+      image->gen_flag &= ~IMA_GEN_FLOAT;
+    }
+
+    image->gen_x = ibuf->x;
+    image->gen_y = ibuf->y;
+  }
 
   /* Consider image dirty since its content can not be re-created unless the image is explicitly
    * saved. */
-  BKE_image_mark_dirty(ima, ibuf);
-
-  return ima;
+  BKE_image_mark_dirty(image, ibuf);
 }
 
 /** Pack image buffer to memory as PNG or EXR. */
-- 
cgit v1.2.3


From e4bf58e2852856e5c9d81ce04e6e9bb967795054 Mon Sep 17 00:00:00 2001
From: Sergey Sharybin 
Date: Tue, 28 Jun 2022 16:03:35 +0200
Subject: Tracking: Image from Plane Marker operators

There are two operators added, which are available via a special
content menu next to the plane track image selector:

- New Image from Plane Marker
- Update Image from Plane Marker

The former one creates an image from pixels which the active plane
track marker "sees" at the current frame and sets it as the plane
track's image.

The latter one instead of creating the new image data-block updates
the image in-place.

This allows to create unwarped texture from a billboard from footage.
The intent is to allow this image to be touched up and re-projected
back to the footage with an updated content.

Available from a plane track image context menu, as well as from the
Track menu.

{F13243219}

The demo of the feature from Sebastian Koenig: https://www.youtube.com/watch?v=PDphO-w2SsA

Differential Revision: https://developer.blender.org/D15312
---
 release/scripts/startup/bl_ui/space_clip.py      |  19 ++-
 source/blender/blenkernel/BKE_tracking.h         |   5 +
 source/blender/blenkernel/intern/tracking.c      |  90 ++++++++++++++
 source/blender/editors/space_clip/clip_intern.h  |   3 +
 source/blender/editors/space_clip/space_clip.c   |   3 +
 source/blender/editors/space_clip/tracking_ops.c | 145 +++++++++++++++++++++++
 6 files changed, 264 insertions(+), 1 deletion(-)

diff --git a/release/scripts/startup/bl_ui/space_clip.py b/release/scripts/startup/bl_ui/space_clip.py
index a806e9e0c33..c337e8018e6 100644
--- a/release/scripts/startup/bl_ui/space_clip.py
+++ b/release/scripts/startup/bl_ui/space_clip.py
@@ -771,8 +771,10 @@ class CLIP_PT_plane_track(CLIP_PT_tracking_panel, Panel):
 
         layout.prop(active_track, "name")
         layout.prop(active_track, "use_auto_keying")
-        layout.template_ID(
+        row = layout.row()
+        row.template_ID(
             active_track, "image", new="image.new", open="image.open")
+        row.menu("CLIP_MT_plane_track_image_context_menu", icon='DOWNARROW_HLT', text="")
 
         row = layout.row()
         row.active = active_track.image is not None
@@ -1482,6 +1484,10 @@ class CLIP_MT_track(Menu):
         layout.operator("clip.detect_features")
         layout.operator("clip.create_plane_track")
 
+        layout.separator()
+        layout.operator("clip.new_image_from_plane_marker")
+        layout.operator("clip.update_image_from_plane_marker")
+
         layout.separator()
 
         layout.operator(
@@ -1634,6 +1640,16 @@ class CLIP_MT_tracking_context_menu(Menu):
             draw_mask_context_menu(layout, context)
 
 
+class CLIP_MT_plane_track_image_context_menu(Menu):
+    bl_label = "Plane Track Image Specials"
+
+    def draw(self, _context):
+        layout = self.layout
+
+        layout.operator("clip.new_image_from_plane_marker")
+        layout.operator("clip.update_image_from_plane_marker")
+
+
 class CLIP_PT_camera_presets(PresetPanel, Panel):
     """Predefined tracking camera intrinsics"""
     bl_label = "Camera Presets"
@@ -1935,6 +1951,7 @@ classes = (
     CLIP_MT_select,
     CLIP_MT_select_grouped,
     CLIP_MT_tracking_context_menu,
+    CLIP_MT_plane_track_image_context_menu,
     CLIP_PT_camera_presets,
     CLIP_PT_track_color_presets,
     CLIP_PT_tracking_settings_presets,
diff --git a/source/blender/blenkernel/BKE_tracking.h b/source/blender/blenkernel/BKE_tracking.h
index 3f6d32e2f70..23b1f7c09bb 100644
--- a/source/blender/blenkernel/BKE_tracking.h
+++ b/source/blender/blenkernel/BKE_tracking.h
@@ -560,6 +560,11 @@ struct ImBuf *BKE_tracking_get_search_imbuf(struct ImBuf *ibuf,
                                             bool anchored,
                                             bool disable_channels);
 
+/* Create a new image buffer which consists of pixels which the plane marker "sees".
+ * The function will choose best image resolution based on the plane marker size. */
+struct ImBuf *BKE_tracking_get_plane_imbuf(const struct ImBuf *frame_ibuf,
+                                           const struct MovieTrackingPlaneMarker *plane_marker);
+
 /**
  * Zap channels from the imbuf that are disabled by the user. this can lead to
  * better tracks sometimes. however, instead of simply zeroing the channels
diff --git a/source/blender/blenkernel/intern/tracking.c b/source/blender/blenkernel/intern/tracking.c
index 8b462cba7ed..6abc40e89bc 100644
--- a/source/blender/blenkernel/intern/tracking.c
+++ b/source/blender/blenkernel/intern/tracking.c
@@ -2798,6 +2798,96 @@ ImBuf *BKE_tracking_get_search_imbuf(ImBuf *ibuf,
   return searchibuf;
 }
 
+BLI_INLINE int plane_marker_size_len_in_pixels(const float a[2],
+                                               const float b[2],
+                                               const int frame_width,
+                                               const int frame_height)
+{
+  const float a_px[2] = {a[0] * frame_width, a[1] * frame_height};
+  const float b_px[2] = {b[0] * frame_width, b[1] * frame_height};
+
+  return ceilf(len_v2v2(a_px, b_px));
+}
+
+ImBuf *BKE_tracking_get_plane_imbuf(const ImBuf *frame_ibuf,
+                                    const MovieTrackingPlaneMarker *plane_marker)
+{
+  /* Alias for corners, allowing shorter access to coordinates. */
+  const float(*corners)[2] = plane_marker->corners;
+
+  /* Dimensions of the frame image in pixels. */
+  const int frame_width = frame_ibuf->x;
+  const int frame_height = frame_ibuf->y;
+
+  /* Lengths of left and right edges of the plane marker, in pixels. */
+  const int left_side_len_px = plane_marker_size_len_in_pixels(
+      corners[0], corners[3], frame_width, frame_height);
+  const int right_side_len_px = plane_marker_size_len_in_pixels(
+      corners[1], corners[2], frame_width, frame_height);
+
+  /* Lengths of top and bottom edges of the plane marker, in pixels. */
+  const int top_side_len_px = plane_marker_size_len_in_pixels(
+      corners[3], corners[2], frame_width, frame_height);
+  const int bottom_side_len_px = plane_marker_size_len_in_pixels(
+      corners[0], corners[1], frame_width, frame_height);
+
+  /* Choose the number of samples as a maximum of the corresponding sides in pixels. */
+  const int num_samples_x = max_ii(top_side_len_px, bottom_side_len_px);
+  const int num_samples_y = max_ii(left_side_len_px, right_side_len_px);
+
+  /* Create new result image with the same type of content as the original. */
+  ImBuf *plane_ibuf = IMB_allocImBuf(
+      num_samples_x, num_samples_y, 32, frame_ibuf->rect_float ? IB_rectfloat : IB_rect);
+
+  /* Calculate corner coordinates in pixel space, as spearate X/Y arrays. */
+  const double src_pixel_x[4] = {corners[0][0] * frame_width,
+                                 corners[1][0] * frame_width,
+                                 corners[2][0] * frame_width,
+                                 corners[3][0] * frame_width};
+  const double src_pixel_y[4] = {corners[0][1] * frame_height,
+                                 corners[1][1] * frame_height,
+                                 corners[2][1] * frame_height,
+                                 corners[3][1] * frame_height};
+
+  /* Warped Position is unused but is expected to be provided by the API. */
+  double warped_position_x, warped_position_y;
+
+  /* Actual sampling. */
+  if (frame_ibuf->rect_float != NULL) {
+    libmv_samplePlanarPatchFloat(frame_ibuf->rect_float,
+                                 frame_ibuf->x,
+                                 frame_ibuf->y,
+                                 4,
+                                 src_pixel_x,
+                                 src_pixel_y,
+                                 num_samples_x,
+                                 num_samples_y,
+                                 NULL,
+                                 plane_ibuf->rect_float,
+                                 &warped_position_x,
+                                 &warped_position_y);
+  }
+  else {
+    libmv_samplePlanarPatchByte((unsigned char *)frame_ibuf->rect,
+                                frame_ibuf->x,
+                                frame_ibuf->y,
+                                4,
+                                src_pixel_x,
+                                src_pixel_y,
+                                num_samples_x,
+                                num_samples_y,
+                                NULL,
+                                (unsigned char *)plane_ibuf->rect,
+                                &warped_position_x,
+                                &warped_position_y);
+  }
+
+  plane_ibuf->rect_colorspace = frame_ibuf->rect_colorspace;
+  plane_ibuf->float_colorspace = frame_ibuf->float_colorspace;
+
+  return plane_ibuf;
+}
+
 void BKE_tracking_disable_channels(
     ImBuf *ibuf, bool disable_red, bool disable_green, bool disable_blue, bool grayscale)
 {
diff --git a/source/blender/editors/space_clip/clip_intern.h b/source/blender/editors/space_clip/clip_intern.h
index 7f9cf61b748..2efd6b6b473 100644
--- a/source/blender/editors/space_clip/clip_intern.h
+++ b/source/blender/editors/space_clip/clip_intern.h
@@ -251,6 +251,9 @@ void CLIP_OT_slide_plane_marker(struct wmOperatorType *ot);
 void CLIP_OT_keyframe_insert(struct wmOperatorType *ot);
 void CLIP_OT_keyframe_delete(struct wmOperatorType *ot);
 
+void CLIP_OT_new_image_from_plane_marker(struct wmOperatorType *ot);
+void CLIP_OT_update_image_from_plane_marker(struct wmOperatorType *ot);
+
 /* tracking_select.c */
 
 void CLIP_OT_select(struct wmOperatorType *ot);
diff --git a/source/blender/editors/space_clip/space_clip.c b/source/blender/editors/space_clip/space_clip.c
index a73883e7624..ce6409a7784 100644
--- a/source/blender/editors/space_clip/space_clip.c
+++ b/source/blender/editors/space_clip/space_clip.c
@@ -515,6 +515,9 @@ static void clip_operatortypes(void)
   WM_operatortype_append(CLIP_OT_keyframe_insert);
   WM_operatortype_append(CLIP_OT_keyframe_delete);
 
+  WM_operatortype_append(CLIP_OT_new_image_from_plane_marker);
+  WM_operatortype_append(CLIP_OT_update_image_from_plane_marker);
+
   /* ** clip_graph_ops.c  ** */
 
   /* graph editing */
diff --git a/source/blender/editors/space_clip/tracking_ops.c b/source/blender/editors/space_clip/tracking_ops.c
index 7f113108b02..cba4157d044 100644
--- a/source/blender/editors/space_clip/tracking_ops.c
+++ b/source/blender/editors/space_clip/tracking_ops.c
@@ -16,6 +16,7 @@
 #include "BLI_utildefines.h"
 
 #include "BKE_context.h"
+#include "BKE_image.h"
 #include "BKE_movieclip.h"
 #include "BKE_report.h"
 #include "BKE_tracking.h"
@@ -33,6 +34,9 @@
 
 #include "BLT_translation.h"
 
+#include "IMB_imbuf.h"
+#include "IMB_imbuf_types.h"
+
 #include "clip_intern.h"
 #include "tracking_ops_intern.h"
 
@@ -2213,3 +2217,144 @@ void CLIP_OT_keyframe_delete(wmOperatorType *ot)
 }
 
 /** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Image from plane track marker
+ * \{ */
+
+static ImBuf *sample_plane_marker_image_for_operator(bContext *C)
+{
+  SpaceClip *space_clip = CTX_wm_space_clip(C);
+  const int clip_frame_number = ED_space_clip_get_clip_frame_number(space_clip);
+
+  MovieClip *clip = ED_space_clip_get_clip(space_clip);
+
+  MovieTracking *tracking = &clip->tracking;
+  MovieTrackingPlaneTrack *plane_track = tracking->act_plane_track;
+  const MovieTrackingPlaneMarker *plane_marker = BKE_tracking_plane_marker_get(plane_track,
+                                                                               clip_frame_number);
+
+  ImBuf *frame_ibuf = ED_space_clip_get_buffer(space_clip);
+  if (frame_ibuf == NULL) {
+    return NULL;
+  }
+
+  ImBuf *plane_ibuf = BKE_tracking_get_plane_imbuf(frame_ibuf, plane_marker);
+
+  IMB_freeImBuf(frame_ibuf);
+
+  return plane_ibuf;
+}
+
+static bool new_image_from_plane_marker_poll(bContext *C)
+{
+  if (!ED_space_clip_tracking_poll(C)) {
+    return false;
+  }
+
+  SpaceClip *space_clip = CTX_wm_space_clip(C);
+  MovieClip *clip = ED_space_clip_get_clip(space_clip);
+  const MovieTracking *tracking = &clip->tracking;
+
+  if (tracking->act_plane_track == NULL) {
+    return false;
+  }
+
+  return true;
+}
+
+static int new_image_from_plane_marker_exec(bContext *C, wmOperator *UNUSED(op))
+{
+  SpaceClip *space_clip = CTX_wm_space_clip(C);
+  MovieClip *clip = ED_space_clip_get_clip(space_clip);
+  MovieTracking *tracking = &clip->tracking;
+  MovieTrackingPlaneTrack *plane_track = tracking->act_plane_track;
+
+  ImBuf *plane_ibuf = sample_plane_marker_image_for_operator(C);
+  if (plane_ibuf == NULL) {
+    return OPERATOR_CANCELLED;
+  }
+
+  plane_track->image = BKE_image_add_from_imbuf(CTX_data_main(C), plane_ibuf, plane_track->name);
+
+  IMB_freeImBuf(plane_ibuf);
+
+  WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, clip);
+
+  return OPERATOR_FINISHED;
+}
+
+void CLIP_OT_new_image_from_plane_marker(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "New Image from Plane Marker";
+  ot->description = "Create new image from the content of the plane marker";
+  ot->idname = "CLIP_OT_new_image_from_plane_marker";
+
+  /* api callbacks */
+  ot->poll = new_image_from_plane_marker_poll;
+  ot->exec = new_image_from_plane_marker_exec;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+static bool update_image_from_plane_marker_poll(bContext *C)
+{
+  if (!ED_space_clip_tracking_poll(C)) {
+    return false;
+  }
+
+  SpaceClip *space_clip = CTX_wm_space_clip(C);
+  MovieClip *clip = ED_space_clip_get_clip(space_clip);
+  const MovieTracking *tracking = &clip->tracking;
+
+  if (tracking->act_plane_track == NULL || tracking->act_plane_track->image == NULL) {
+    return false;
+  }
+
+  const Image *image = tracking->act_plane_track->image;
+  return image->type == IMA_TYPE_IMAGE && ELEM(image->source, IMA_SRC_FILE, IMA_SRC_GENERATED);
+}
+
+static int update_image_from_plane_marker_exec(bContext *C, wmOperator *UNUSED(op))
+{
+  SpaceClip *space_clip = CTX_wm_space_clip(C);
+  MovieClip *clip = ED_space_clip_get_clip(space_clip);
+  MovieTracking *tracking = &clip->tracking;
+  MovieTrackingPlaneTrack *plane_track = tracking->act_plane_track;
+
+  ImBuf *plane_ibuf = sample_plane_marker_image_for_operator(C);
+  if (plane_ibuf == NULL) {
+    return OPERATOR_CANCELLED;
+  }
+
+  BKE_image_replace_imbuf(plane_track->image, plane_ibuf);
+
+  IMB_freeImBuf(plane_ibuf);
+
+  WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, clip);
+  WM_event_add_notifier(C, NC_IMAGE | NA_EDITED, plane_track->image);
+
+  BKE_image_partial_update_mark_full_update(plane_track->image);
+
+  return OPERATOR_FINISHED;
+}
+
+void CLIP_OT_update_image_from_plane_marker(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Update Image from Plane Marker";
+  ot->description =
+      "Update current image used by plane marker from the content of the plane marker";
+  ot->idname = "CLIP_OT_update_image_from_plane_marker";
+
+  /* api callbacks */
+  ot->poll = update_image_from_plane_marker_poll;
+  ot->exec = update_image_from_plane_marker_exec;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+/** \} */
-- 
cgit v1.2.3


From 5d57d9f899617fb04dd4f722599aa4b626ec70fe Mon Sep 17 00:00:00 2001
From: Jacques Lucke 
Date: Fri, 1 Jul 2022 09:55:00 +0200
Subject: Cleanup: remove unused variable

---
 source/blender/editors/sculpt_paint/curves_sculpt_density.cc | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc
index 536c44ddd44..120a7802580 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc
@@ -416,9 +416,6 @@ void DensityAddOperation::on_stroke_extended(const bContext &C,
 
 class DensitySubtractOperation : public CurvesSculptStrokeOperation {
  private:
-  /** Only used when a 3D brush is used. */
-  CurvesBrush3D brush_3d_;
-
   friend struct DensitySubtractOperationExecutor;
 
  public:
-- 
cgit v1.2.3


From eff62ea8abaccbaa8dab9c00e3780aaa873148d2 Mon Sep 17 00:00:00 2001
From: Jacques Lucke 
Date: Fri, 1 Jul 2022 10:04:35 +0200
Subject: Geometry Nodes: remove warning in Points node

Generating no points in some frames is a perfectly valid use case.
---
 source/blender/nodes/geometry/nodes/node_geo_points.cc | 1 -
 1 file changed, 1 deletion(-)

diff --git a/source/blender/nodes/geometry/nodes/node_geo_points.cc b/source/blender/nodes/geometry/nodes/node_geo_points.cc
index ced41c6c85c..da414960e1d 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_points.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_points.cc
@@ -62,7 +62,6 @@ static void node_geo_exec(GeoNodeExecParams params)
 {
   const int count = params.extract_input("Count");
   if (count <= 0) {
-    params.error_message_add(NodeWarningType::Warning, TIP_("Point count should be at least 1"));
     params.set_default_remaining_outputs();
     return;
   }
-- 
cgit v1.2.3


From ccbf9ee48262c6078bcd10817f07c6e3f4bb9a25 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Fri, 1 Jul 2022 18:00:30 +1000
Subject: Fix un-grab cursor positioning failing for Wayland

UI elements such as sliders & color picker set an un-grab location
which GHOST/Wayland didn't implement.
---
 intern/ghost/intern/GHOST_SystemWayland.cpp | 27 +++++++++++++++++++++++++++
 intern/ghost/intern/GHOST_SystemWayland.h   |  1 +
 intern/ghost/intern/GHOST_WindowWayland.cpp |  2 +-
 3 files changed, 29 insertions(+), 1 deletion(-)

diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp
index 3a5e524782f..d00c8b34ed7 100644
--- a/intern/ghost/intern/GHOST_SystemWayland.cpp
+++ b/intern/ghost/intern/GHOST_SystemWayland.cpp
@@ -271,6 +271,8 @@ struct input_t {
 #ifdef USE_GNOME_CONFINE_HACK
   bool use_pointer_software_confine = false;
 #endif
+  /** The cursor location (in pixel-space) when hidden grab started (#GHOST_kGrabHide). */
+  wl_fixed_t grab_lock_xy[2] = {0, 0};
 
   struct cursor_t cursor;
 
@@ -3281,6 +3283,7 @@ static input_grab_state_t input_grab_state_from_mode(const GHOST_TGrabCursorMode
 
 GHOST_TSuccess GHOST_SystemWayland::setCursorGrab(const GHOST_TGrabCursorMode mode,
                                                   const GHOST_TGrabCursorMode mode_current,
+                                                  int32_t init_grab_xy[2],
                                                   wl_surface *surface)
 {
   /* Ignore, if the required protocols are not supported. */
@@ -3370,6 +3373,20 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorGrab(const GHOST_TGrabCursorMode mo
             input->locked_pointer, xy_new[0], xy_new[1]);
         wl_surface_commit(surface);
       }
+      else if (mode_current == GHOST_kGrabHide) {
+        if ((init_grab_xy[0] != input->grab_lock_xy[0]) ||
+            (init_grab_xy[1] != input->grab_lock_xy[1])) {
+          GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(surface);
+          const int scale = win->scale();
+          const wl_fixed_t xy_next[2] = {
+              wl_fixed_from_int(init_grab_xy[0]) / scale,
+              wl_fixed_from_int(init_grab_xy[1]) / scale,
+          };
+          zwp_locked_pointer_v1_set_cursor_position_hint(
+              input->locked_pointer, xy_next[0], xy_next[1]);
+          wl_surface_commit(surface);
+        }
+      }
 #ifdef USE_GNOME_CONFINE_HACK
       else if (mode_current == GHOST_kGrabNormal) {
         if (was_software_confine) {
@@ -3410,6 +3427,16 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorGrab(const GHOST_TGrabCursorMode mo
             nullptr,
             ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
       }
+      if (mode == GHOST_kGrabHide) {
+        /* Set the initial position to detect any changes when un-grabbing,
+         * otherwise the unlocked cursor defaults to un-locking in-place. */
+        GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(surface);
+        const int scale = win->scale();
+        init_grab_xy[0] = wl_fixed_to_int(scale * input->pointer.xy[0]);
+        init_grab_xy[1] = wl_fixed_to_int(scale * input->pointer.xy[1]);
+        input->grab_lock_xy[0] = init_grab_xy[0];
+        input->grab_lock_xy[1] = init_grab_xy[1];
+      }
     }
     else if (grab_state_next.use_confine) {
       if (!grab_state_prev.use_confine) {
diff --git a/intern/ghost/intern/GHOST_SystemWayland.h b/intern/ghost/intern/GHOST_SystemWayland.h
index 7fe35146be0..4286aa9d183 100644
--- a/intern/ghost/intern/GHOST_SystemWayland.h
+++ b/intern/ghost/intern/GHOST_SystemWayland.h
@@ -132,6 +132,7 @@ class GHOST_SystemWayland : public GHOST_System {
 
   GHOST_TSuccess setCursorGrab(const GHOST_TGrabCursorMode mode,
                                const GHOST_TGrabCursorMode mode_current,
+                               int32_t init_grab_xy[2],
                                wl_surface *surface);
 
   /* WAYLAND direct-data access. */
diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp
index 159cd808c0c..00d75d62c9f 100644
--- a/intern/ghost/intern/GHOST_WindowWayland.cpp
+++ b/intern/ghost/intern/GHOST_WindowWayland.cpp
@@ -484,7 +484,7 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system,
 
 GHOST_TSuccess GHOST_WindowWayland::setWindowCursorGrab(GHOST_TGrabCursorMode mode)
 {
-  return m_system->setCursorGrab(mode, m_cursorGrab, w->wl_surface);
+  return m_system->setCursorGrab(mode, m_cursorGrab, m_cursorGrabInitPos, w->wl_surface);
 }
 
 GHOST_TSuccess GHOST_WindowWayland::setWindowCursorShape(GHOST_TStandardCursor shape)
-- 
cgit v1.2.3


From 4cba209edd398c244de7a85b4a11eb0901a050ce Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cle=CC=81ment=20Foucault?= 
Date: Fri, 1 Jul 2022 10:10:19 +0200
Subject: GPUMaterial: Remove the max attribute check

This is needed to make the GPU_attribute used as generic input mechanism.
---
 source/blender/gpu/intern/gpu_node_graph.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source/blender/gpu/intern/gpu_node_graph.c b/source/blender/gpu/intern/gpu_node_graph.c
index 3c6a03c56d3..1338c5312c2 100644
--- a/source/blender/gpu/intern/gpu_node_graph.c
+++ b/source/blender/gpu/intern/gpu_node_graph.c
@@ -378,7 +378,7 @@ static GPUMaterialAttribute *gpu_node_graph_add_attribute(GPUNodeGraph *graph,
   }
 
   /* Add new requested attribute if it's within GPU limits. */
-  if (attr == NULL && num_attributes < GPU_MAX_ATTR) {
+  if (attr == NULL) {
     attr = MEM_callocN(sizeof(*attr), __func__);
     attr->type = type;
     STRNCPY(attr->name, name);
-- 
cgit v1.2.3


From 5b7e7d67a5a4734d691962bf9db6ace7634d0741 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cle=CC=81ment=20Foucault?= 
Date: Fri, 1 Jul 2022 10:10:36 +0200
Subject: Cleanup: GPUCodegen: Remove unused variables

---
 source/blender/gpu/intern/gpu_codegen.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source/blender/gpu/intern/gpu_codegen.cc b/source/blender/gpu/intern/gpu_codegen.cc
index 453428cb648..82441c3c89c 100644
--- a/source/blender/gpu/intern/gpu_codegen.cc
+++ b/source/blender/gpu/intern/gpu_codegen.cc
@@ -302,7 +302,7 @@ void GPUCodegen::generate_attribs()
   info.vertex_out(iface);
 
   /* Input declaration, loading / assignment to interface and geometry shader passthrough. */
-  std::stringstream decl_ss, iface_ss, load_ss;
+  std::stringstream load_ss;
 
   int slot = 15;
   LISTBASE_FOREACH (GPUMaterialAttribute *, attr, &graph.attributes) {
-- 
cgit v1.2.3


From bb8953ab49817a6ddaaef21ac0da3a2cdc81e23f Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Fri, 1 Jul 2022 18:09:47 +1000
Subject: GHOST/Wayland: map additional cursors from GHOST_TStandardCursor

---
 intern/ghost/intern/GHOST_SystemWayland.cpp | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp
index d00c8b34ed7..09784c67fda 100644
--- a/intern/ghost/intern/GHOST_SystemWayland.cpp
+++ b/intern/ghost/intern/GHOST_SystemWayland.cpp
@@ -669,7 +669,7 @@ static const std::unordered_map cursors = {
     {GHOST_kStandardCursorRightArrow, "right_ptr"},
     {GHOST_kStandardCursorLeftArrow, "left_ptr"},
     {GHOST_kStandardCursorInfo, ""},
-    {GHOST_kStandardCursorDestroy, ""},
+    {GHOST_kStandardCursorDestroy, "pirate"},
     {GHOST_kStandardCursorHelp, "question_arrow"},
     {GHOST_kStandardCursorWait, "watch"},
     {GHOST_kStandardCursorText, "xterm"},
@@ -677,21 +677,21 @@ static const std::unordered_map cursors = {
     {GHOST_kStandardCursorCrosshairA, ""},
     {GHOST_kStandardCursorCrosshairB, ""},
     {GHOST_kStandardCursorCrosshairC, ""},
-    {GHOST_kStandardCursorPencil, ""},
+    {GHOST_kStandardCursorPencil, "pencil"},
     {GHOST_kStandardCursorUpArrow, "sb_up_arrow"},
     {GHOST_kStandardCursorDownArrow, "sb_down_arrow"},
-    {GHOST_kStandardCursorVerticalSplit, ""},
-    {GHOST_kStandardCursorHorizontalSplit, ""},
+    {GHOST_kStandardCursorVerticalSplit, "split_v"},
+    {GHOST_kStandardCursorHorizontalSplit, "split_h"},
     {GHOST_kStandardCursorEraser, ""},
     {GHOST_kStandardCursorKnife, ""},
-    {GHOST_kStandardCursorEyedropper, ""},
-    {GHOST_kStandardCursorZoomIn, ""},
-    {GHOST_kStandardCursorZoomOut, ""},
+    {GHOST_kStandardCursorEyedropper, "color-picker"},
+    {GHOST_kStandardCursorZoomIn, "zoom-in"},
+    {GHOST_kStandardCursorZoomOut, "zoom-out"},
     {GHOST_kStandardCursorMove, "move"},
-    {GHOST_kStandardCursorNSEWScroll, ""},
-    {GHOST_kStandardCursorNSScroll, ""},
-    {GHOST_kStandardCursorEWScroll, ""},
-    {GHOST_kStandardCursorStop, ""},
+    {GHOST_kStandardCursorNSEWScroll, "size_all"}, /* Not an exact match. */
+    {GHOST_kStandardCursorNSScroll, "size_ver"},   /* Not an exact match. */
+    {GHOST_kStandardCursorEWScroll, "size_hor"},   /* Not an exact match. */
+    {GHOST_kStandardCursorStop, "not-allowed"},
     {GHOST_kStandardCursorUpDown, "sb_v_double_arrow"},
     {GHOST_kStandardCursorLeftRight, "sb_h_double_arrow"},
     {GHOST_kStandardCursorTopSide, "top_side"},
-- 
cgit v1.2.3


From 0554537c3cb4d040cf9a01695ec3089ad0a8426b Mon Sep 17 00:00:00 2001
From: Xavier Hallade 
Date: Fri, 1 Jul 2022 10:12:42 +0200
Subject: Cleanup: add missing license headers in Cycles oneAPI implementation

---
 intern/cycles/kernel/device/oneapi/dll_interface_template.h | 3 +++
 intern/cycles/kernel/device/oneapi/kernel_templates.h       | 3 +++
 2 files changed, 6 insertions(+)

diff --git a/intern/cycles/kernel/device/oneapi/dll_interface_template.h b/intern/cycles/kernel/device/oneapi/dll_interface_template.h
index 22804490a70..662068c0fed 100644
--- a/intern/cycles/kernel/device/oneapi/dll_interface_template.h
+++ b/intern/cycles/kernel/device/oneapi/dll_interface_template.h
@@ -1,3 +1,6 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 Intel Corporation */
+
 /* device_capabilities() returns a C string that must be free'd with oneapi_free(). */
 DLL_INTERFACE_CALL(oneapi_device_capabilities, char *)
 DLL_INTERFACE_CALL(oneapi_free, void, void *)
diff --git a/intern/cycles/kernel/device/oneapi/kernel_templates.h b/intern/cycles/kernel/device/oneapi/kernel_templates.h
index 41f9a9ba583..d8964d9b672 100644
--- a/intern/cycles/kernel/device/oneapi/kernel_templates.h
+++ b/intern/cycles/kernel/device/oneapi/kernel_templates.h
@@ -1,3 +1,6 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2021-2022 Intel Corporation */
+
 #pragma once
 
 /* Some macro magic to generate templates for kernel arguments.
-- 
cgit v1.2.3


From 3ffc5583411ac6e1286586881bdbe1207a34b386 Mon Sep 17 00:00:00 2001
From: Dalai Felinto 
Date: Fri, 1 Jul 2022 10:20:07 +0200
Subject: Sculpt Curves: UI tweaks and shortcut

* Minimum Distance -> Distance Mix
* Max Count -> Count Max
* Shift + A for selection grow

This follows better the names we have in geometry nodes in the Distribute Points
node when using the Poisson Disk method (Distance Min, Distance Max).

The shortcut for the selection grow is the same we use in mesh sculpt
for the Expand Mask operator (which behaves a bit similar).
---
 release/scripts/presets/keyconfig/keymap_data/blender_default.py | 1 +
 release/scripts/startup/bl_ui/space_view3d.py                    | 4 ++--
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
index d12d68ee5a0..c0db6c5f523 100644
--- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py
+++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
@@ -5625,6 +5625,7 @@ def km_sculpt_curves(params):
         *_template_paint_radial_control("curves_sculpt"),
         *_template_items_select_actions(params, "sculpt_curves.select_all"),
         ("sculpt_curves.min_distance_edit", {"type": 'R', "value": 'PRESS', "shift": True}, {}),
+        ("sculpt_curves.select_grow", {"type": 'A', "value": 'PRESS', "shift": True}, {}),
     ])
 
     return keymap
diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py
index c2342e8949a..5a38c4175a8 100644
--- a/release/scripts/startup/bl_ui/space_view3d.py
+++ b/release/scripts/startup/bl_ui/space_view3d.py
@@ -550,12 +550,12 @@ class _draw_tool_settings_context_mode:
             row = layout.row(align=True)
             row.prop(brush.curves_sculpt_settings, "density_mode", text="", expand=True)
             row = layout.row(align=True)
-            row.prop(brush.curves_sculpt_settings, "minimum_distance")
+            row.prop(brush.curves_sculpt_settings, "minimum_distance", text="Distance Min")
             row.operator_context = 'INVOKE_REGION_WIN'
             row.operator("sculpt_curves.min_distance_edit", text="", icon='DRIVER_DISTANCE')
             row = layout.row(align=True)
             row.enabled = brush.curves_sculpt_settings.density_mode != 'REMOVE'
-            row.prop(brush.curves_sculpt_settings, "density_add_attempts", text="Max Count")
+            row.prop(brush.curves_sculpt_settings, "density_add_attempts", text="Count Max")
             layout.popover("VIEW3D_PT_tools_brush_falloff")
             layout.popover("VIEW3D_PT_curves_sculpt_add_shape", text="Curve Shape")
         elif curves_tool == "SLIDE":
-- 
cgit v1.2.3


From 4527dd1ce4784292cd3b8dd3764b9cd843020f9a Mon Sep 17 00:00:00 2001
From: Jason Fielder 
Date: Fri, 1 Jul 2022 10:30:16 +0200
Subject: Metal: MTLMemoryManager implementation includes functions which
 manage allocation of MTLBuffer resources.

The memory manager includes both a GPUContext-local manager which allocates per-context resources such as Circular Scratch Buffers for temporary data such as uniform updates and resource staging, and a GPUContext-global memory manager which features a pooled memory allocator for efficient re-use of resources, to reduce CPU-overhead of frequent memory allocations.

These Memory Managers act as a simple interface for use by other Metal backend modules and to coordinate the lifetime of buffers, to ensure that GPU-resident resources are correctly tracked and freed when no longer in use.

Note: This also contains dependent DIFF changes from D15027, though these will be removed once D15027 lands.

Authored by Apple: Michael Parkin-White

Ref T96261

Reviewed By: fclem

Maniphest Tasks: T96261

Differential Revision: https://developer.blender.org/D15277
---
 source/blender/blenlib/BLI_math_base.h             |  13 +
 source/blender/blenlib/intern/math_base_inline.c   |  10 +
 source/blender/draw/engines/eevee/eevee_render.c   |   5 +
 .../draw/engines/workbench/workbench_render.c      |   5 +
 source/blender/gpu/CMakeLists.txt                  |   2 +
 source/blender/gpu/intern/gpu_immediate_util.c     |   8 +-
 source/blender/gpu/metal/mtl_backend.mm            |  16 +-
 source/blender/gpu/metal/mtl_command_buffer.mm     |  60 +-
 source/blender/gpu/metal/mtl_common.hh             |   5 +
 source/blender/gpu/metal/mtl_context.hh            |  57 +-
 source/blender/gpu/metal/mtl_context.mm            |  50 +-
 source/blender/gpu/metal/mtl_framebuffer.mm        |   2 +-
 source/blender/gpu/metal/mtl_memory.hh             | 476 +++++++++++
 source/blender/gpu/metal/mtl_memory.mm             | 880 +++++++++++++++++++++
 source/blender/gpu/metal/mtl_state.hh              |   8 +-
 source/blender/gpu/metal/mtl_state.mm              |  12 +-
 source/blender/gpu/metal/mtl_texture.hh            |   6 +-
 source/blender/gpu/metal/mtl_texture.mm            |  33 +-
 18 files changed, 1524 insertions(+), 124 deletions(-)
 create mode 100644 source/blender/gpu/metal/mtl_memory.hh
 create mode 100644 source/blender/gpu/metal/mtl_memory.mm

diff --git a/source/blender/blenlib/BLI_math_base.h b/source/blender/blenlib/BLI_math_base.h
index f072a17f384..c0c4594ddc0 100644
--- a/source/blender/blenlib/BLI_math_base.h
+++ b/source/blender/blenlib/BLI_math_base.h
@@ -221,6 +221,19 @@ MINLINE unsigned int power_of_2_min_u(unsigned int x);
  * with integers, to avoid gradual darkening when rounding down.
  */
 MINLINE int divide_round_i(int a, int b);
+
+/**
+ * Integer division that returns the ceiling, instead of flooring like normal C division.
+ */
+MINLINE uint divide_ceil_u(uint a, uint b);
+MINLINE uint64_t divide_ceil_ul(uint64_t a, uint64_t b);
+
+/**
+ * Returns \a a if it is a multiple of \a b or the next multiple or \a b after \b a .
+ */
+MINLINE uint ceil_to_multiple_u(uint a, uint b);
+MINLINE uint64_t ceil_to_multiple_ul(uint64_t a, uint64_t b);
+
 /**
  * modulo that handles negative numbers, works the same as Python's.
  */
diff --git a/source/blender/blenlib/intern/math_base_inline.c b/source/blender/blenlib/intern/math_base_inline.c
index cb7659a7059..fb71e84c23e 100644
--- a/source/blender/blenlib/intern/math_base_inline.c
+++ b/source/blender/blenlib/intern/math_base_inline.c
@@ -370,6 +370,11 @@ MINLINE uint divide_ceil_u(uint a, uint b)
   return (a + b - 1) / b;
 }
 
+MINLINE uint64_t divide_ceil_ul(uint64_t a, uint64_t b)
+{
+  return (a + b - 1) / b;
+}
+
 /**
  * Returns \a a if it is a multiple of \a b or the next multiple or \a b after \b a .
  */
@@ -378,6 +383,11 @@ MINLINE uint ceil_to_multiple_u(uint a, uint b)
   return divide_ceil_u(a, b) * b;
 }
 
+MINLINE uint64_t ceil_to_multiple_ul(uint64_t a, uint64_t b)
+{
+  return divide_ceil_ul(a, b) * b;
+}
+
 MINLINE int mod_i(int i, int n)
 {
   return (i % n + n) % n;
diff --git a/source/blender/draw/engines/eevee/eevee_render.c b/source/blender/draw/engines/eevee/eevee_render.c
index bef19c589c2..82944f237ea 100644
--- a/source/blender/draw/engines/eevee/eevee_render.c
+++ b/source/blender/draw/engines/eevee/eevee_render.c
@@ -24,6 +24,7 @@
 #include "DEG_depsgraph_query.h"
 
 #include "GPU_capabilities.h"
+#include "GPU_context.h"
 #include "GPU_framebuffer.h"
 #include "GPU_state.h"
 
@@ -646,6 +647,10 @@ void EEVEE_render_draw(EEVEE_Data *vedata, RenderEngine *engine, RenderLayer *rl
     /* XXX Seems to fix TDR issue with NVidia drivers on linux. */
     GPU_finish();
 
+    /* Perform render step between samples to allow
+     * flushing of freed GPUBackend resources. */
+    GPU_render_step();
+
     RE_engine_update_progress(engine, (float)(render_samples++) / (float)tot_sample);
   }
 }
diff --git a/source/blender/draw/engines/workbench/workbench_render.c b/source/blender/draw/engines/workbench/workbench_render.c
index e5dcf6c5624..931f6a2dc92 100644
--- a/source/blender/draw/engines/workbench/workbench_render.c
+++ b/source/blender/draw/engines/workbench/workbench_render.c
@@ -17,6 +17,7 @@
 
 #include "ED_view3d.h"
 
+#include "GPU_context.h"
 #include "GPU_shader.h"
 
 #include "DEG_depsgraph.h"
@@ -188,6 +189,10 @@ void workbench_render(void *ved, RenderEngine *engine, RenderLayer *render_layer
 
   workbench_draw_finish(data);
 
+  /* Perform render step between samples to allow
+   * flushing of freed GPUBackend resources. */
+  GPU_render_step();
+
   /* Write render output. */
   const char *viewname = RE_GetActiveRenderView(engine->re);
   RenderPass *rp = RE_pass_find_by_name(render_layer, RE_PASSNAME_COMBINED, viewname);
diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt
index 62d5537772a..9b5ce6e147e 100644
--- a/source/blender/gpu/CMakeLists.txt
+++ b/source/blender/gpu/CMakeLists.txt
@@ -194,6 +194,7 @@ set(METAL_SRC
   metal/mtl_command_buffer.mm
   metal/mtl_debug.mm
   metal/mtl_framebuffer.mm
+  metal/mtl_memory.mm
   metal/mtl_state.mm
   metal/mtl_texture.mm
   metal/mtl_texture_util.mm
@@ -204,6 +205,7 @@ set(METAL_SRC
   metal/mtl_context.hh
   metal/mtl_debug.hh
   metal/mtl_framebuffer.hh
+  metal/mtl_memory.hh
   metal/mtl_state.hh
   metal/mtl_texture.hh
 )
diff --git a/source/blender/gpu/intern/gpu_immediate_util.c b/source/blender/gpu/intern/gpu_immediate_util.c
index a275fd8fc6c..5233ff2dbf6 100644
--- a/source/blender/gpu/intern/gpu_immediate_util.c
+++ b/source/blender/gpu/intern/gpu_immediate_util.c
@@ -142,7 +142,7 @@ static void imm_draw_circle(GPUPrimType prim_type,
                             int nsegments)
 {
   if (prim_type == GPU_PRIM_LINE_LOOP) {
-    /* Note(Metal/AMD): For small primitives, line list more efficient than line strip.. */
+    /* NOTE(Metal/AMD): For small primitives, line list more efficient than line strip.. */
     immBegin(GPU_PRIM_LINES, nsegments * 2);
 
     immVertex2f(shdr_pos, x + (radius_x * cosf(0.0f)), y + (radius_y * sinf(0.0f)));
@@ -333,7 +333,7 @@ static void imm_draw_circle_3D(
     GPUPrimType prim_type, uint pos, float x, float y, float radius, int nsegments)
 {
   if (prim_type == GPU_PRIM_LINE_LOOP) {
-    /* Note(Metal/AMD): For small primitives, line list more efficient than line strip. */
+    /* NOTE(Metal/AMD): For small primitives, line list more efficient than line strip. */
     immBegin(GPU_PRIM_LINES, nsegments * 2);
 
     const float angle = (float)(2 * M_PI) / (float)nsegments;
@@ -386,7 +386,7 @@ void imm_draw_circle_fill_3d(uint pos, float x, float y, float radius, int nsegm
 
 void imm_draw_box_wire_2d(uint pos, float x1, float y1, float x2, float y2)
 {
-  /* Note(Metal/AMD): For small primitives, line list more efficient than line-strip. */
+  /* NOTE(Metal/AMD): For small primitives, line list more efficient than line-strip. */
   immBegin(GPU_PRIM_LINES, 8);
   immVertex2f(pos, x1, y1);
   immVertex2f(pos, x1, y2);
@@ -405,7 +405,7 @@ void imm_draw_box_wire_2d(uint pos, float x1, float y1, float x2, float y2)
 void imm_draw_box_wire_3d(uint pos, float x1, float y1, float x2, float y2)
 {
   /* use this version when GPUVertFormat has a vec3 position */
-  /* Note(Metal/AMD): For small primitives, line list more efficient than line-strip. */
+  /* NOTE(Metal/AMD): For small primitives, line list more efficient than line-strip. */
   immBegin(GPU_PRIM_LINES, 8);
   immVertex3f(pos, x1, y1, 0.0f);
   immVertex3f(pos, x1, y2, 0.0f);
diff --git a/source/blender/gpu/metal/mtl_backend.mm b/source/blender/gpu/metal/mtl_backend.mm
index 81f8f279759..117b8352a0a 100644
--- a/source/blender/gpu/metal/mtl_backend.mm
+++ b/source/blender/gpu/metal/mtl_backend.mm
@@ -127,7 +127,21 @@ void MTLBackend::render_end()
 
 void MTLBackend::render_step()
 {
-  /* Placeholder */
+  /* NOTE(Metal): Primarily called from main thread, but below datastructures
+   * and operations are thread-safe, and GPUContext rendering coordination
+   * is also thread-safe. */
+
+  /* Flush any MTLSafeFreeLists which have previously been released by any MTLContext. */
+  MTLContext::get_global_memory_manager().update_memory_pools();
+
+  /* End existing MTLSafeFreeList and begin new list --
+   * Buffers wont `free` until all associated in-flight command buffers have completed.
+   * Decrement final reference count for ensuring the previous list is certainly
+   * released. */
+  MTLSafeFreeList *cmd_free_buffer_list =
+      MTLContext::get_global_memory_manager().get_current_safe_list();
+  MTLContext::get_global_memory_manager().begin_new_safe_list();
+  cmd_free_buffer_list->decrement_reference();
 }
 
 bool MTLBackend::is_inside_render_boundary()
diff --git a/source/blender/gpu/metal/mtl_command_buffer.mm b/source/blender/gpu/metal/mtl_command_buffer.mm
index 4f6077e8159..f9edd87a73c 100644
--- a/source/blender/gpu/metal/mtl_command_buffer.mm
+++ b/source/blender/gpu/metal/mtl_command_buffer.mm
@@ -19,7 +19,7 @@ namespace blender::gpu {
  * dependencies not being honored for work submitted between
  * different GPUContext's. */
 id MTLCommandBufferManager::sync_event = nil;
-unsigned long long MTLCommandBufferManager::event_signal_val = 0;
+uint64_t MTLCommandBufferManager::event_signal_val = 0;
 
 /* Counter for active command buffers. */
 int MTLCommandBufferManager::num_active_cmd_bufs = 0;
@@ -28,10 +28,9 @@ int MTLCommandBufferManager::num_active_cmd_bufs = 0;
 /** \name MTLCommandBuffer initialization and render coordination.
  * \{ */
 
-void MTLCommandBufferManager::prepare(MTLContext *ctx, bool supports_render)
+void MTLCommandBufferManager::prepare(bool supports_render)
 {
-  context_ = ctx;
-  render_pass_state_.prepare(this, ctx);
+  render_pass_state_.reset_state();
 }
 
 void MTLCommandBufferManager::register_encoder_counters()
@@ -54,10 +53,10 @@ id MTLCommandBufferManager::ensure_begin()
       MTLCommandBufferDescriptor *desc = [[MTLCommandBufferDescriptor alloc] init];
       desc.errorOptions = MTLCommandBufferErrorOptionEncoderExecutionStatus;
       desc.retainedReferences = YES;
-      active_command_buffer_ = [context_->queue commandBufferWithDescriptor:desc];
+      active_command_buffer_ = [context_.queue commandBufferWithDescriptor:desc];
     }
     else {
-      active_command_buffer_ = [context_->queue commandBuffer];
+      active_command_buffer_ = [context_.queue commandBuffer];
     }
     [active_command_buffer_ retain];
     MTLCommandBufferManager::num_active_cmd_bufs++;
@@ -67,6 +66,10 @@ id MTLCommandBufferManager::ensure_begin()
       [active_command_buffer_ encodeWaitForEvent:this->sync_event value:this->event_signal_val];
     }
 
+    /* Ensure we begin new Scratch Buffer if we are on a new frame. */
+    MTLScratchBufferManager &mem = context_.memory_manager;
+    mem.ensure_increment_scratch_buffer();
+
     /* Reset Command buffer heuristics. */
     this->reset_counters();
   }
@@ -86,12 +89,15 @@ bool MTLCommandBufferManager::submit(bool wait)
   this->end_active_command_encoder();
   BLI_assert(active_command_encoder_type_ == MTL_NO_COMMAND_ENCODER);
 
+  /* Flush active ScratchBuffer associated with parent MTLContext. */
+  context_.memory_manager.flush_active_scratch_buffer();
+
   /*** Submit Command Buffer. ***/
   /* Strict ordering ensures command buffers are guaranteed to execute after a previous
    * one has completed. Resolves flickering when command buffers are submitted from
    * different MTLContext's. */
   if (MTLCommandBufferManager::sync_event == nil) {
-    MTLCommandBufferManager::sync_event = [context_->device newEvent];
+    MTLCommandBufferManager::sync_event = [context_.device newEvent];
     BLI_assert(MTLCommandBufferManager::sync_event);
     [MTLCommandBufferManager::sync_event retain];
   }
@@ -102,14 +108,27 @@ bool MTLCommandBufferManager::submit(bool wait)
                                       value:MTLCommandBufferManager::event_signal_val];
 
   /* Command buffer lifetime tracking. */
-  /* TODO(Metal): This routine will later be used to track released memory allocations within the
-   * lifetime of a command buffer such that memory is only released once no longer in use. */
-  id cmd_buffer_ref = [active_command_buffer_ retain];
+  /* Increment current MTLSafeFreeList reference counter to flag MTLBuffers freed within
+   * the current command buffer lifetime as used.
+   * This ensures that in-use resources are not prematurely de-referenced and returned to the
+   * available buffer pool while they are in-use by the GPU. */
+  MTLSafeFreeList *cmd_free_buffer_list =
+      MTLContext::get_global_memory_manager().get_current_safe_list();
+  BLI_assert(cmd_free_buffer_list);
+  cmd_free_buffer_list->increment_reference();
+
+  id cmd_buffer_ref = active_command_buffer_;
+  [cmd_buffer_ref retain];
+
   [cmd_buffer_ref addCompletedHandler:^(id cb) {
+    /* Upon command buffer completion, decrement MTLSafeFreeList reference count
+     * to allow buffers no longer in use by this CommandBuffer to be freed. */
+    cmd_free_buffer_list->decrement_reference();
+
     /* Release command buffer after completion callback handled. */
     [cmd_buffer_ref release];
 
-    /* Decrement active cmd buffer count. */
+    /* Decrement count. */
     MTLCommandBufferManager::num_active_cmd_bufs--;
   }];
 
@@ -516,15 +535,6 @@ bool MTLCommandBufferManager::insert_memory_barrier(eGPUBarrier barrier_bits,
 /* -------------------------------------------------------------------- */
 /** \name Render Pass State for active RenderCommandEncoder
  * \{ */
-
-/* Metal Render Pass State. */
-void MTLRenderPassState::prepare(MTLCommandBufferManager *cmd, MTLContext *mtl_ctx)
-{
-  this->cmd = cmd;
-  this->ctx = mtl_ctx;
-  this->reset_state();
-}
-
 /* Reset binding state when a new RenderCommandEncoder is bound, to ensure
  * pipeline resources are re-applied to the new Encoder.
  * NOTE: In Metal, state is only persistent within an MTLCommandEncoder,
@@ -539,12 +549,12 @@ void MTLRenderPassState::reset_state()
   this->last_bound_shader_state.set(nullptr, 0);
 
   /* Other states. */
-  MTLFrameBuffer *fb = this->cmd->get_active_framebuffer();
+  MTLFrameBuffer *fb = this->cmd.get_active_framebuffer();
   this->last_used_stencil_ref_value = 0;
   this->last_scissor_rect = {0,
                              0,
-                             (unsigned long)((fb != nullptr) ? fb->get_width() : 0),
-                             (unsigned long)((fb != nullptr) ? fb->get_height() : 0)};
+                             (uint)((fb != nullptr) ? fb->get_width() : 0),
+                             (uint)((fb != nullptr) ? fb->get_height() : 0)};
 
   /* Reset cached resource binding state */
   for (int ubo = 0; ubo < MTL_MAX_UNIFORM_BUFFER_BINDINGS; ubo++) {
@@ -573,7 +583,7 @@ void MTLRenderPassState::reset_state()
 void MTLRenderPassState::bind_vertex_texture(id tex, uint slot)
 {
   if (this->cached_vertex_texture_bindings[slot].metal_texture != tex) {
-    id rec = this->cmd->get_active_render_command_encoder();
+    id rec = this->cmd.get_active_render_command_encoder();
     BLI_assert(rec != nil);
     [rec setVertexTexture:tex atIndex:slot];
     this->cached_vertex_texture_bindings[slot].metal_texture = tex;
@@ -583,7 +593,7 @@ void MTLRenderPassState::bind_vertex_texture(id tex, uint slot)
 void MTLRenderPassState::bind_fragment_texture(id tex, uint slot)
 {
   if (this->cached_fragment_texture_bindings[slot].metal_texture != tex) {
-    id rec = this->cmd->get_active_render_command_encoder();
+    id rec = this->cmd.get_active_render_command_encoder();
     BLI_assert(rec != nil);
     [rec setFragmentTexture:tex atIndex:slot];
     this->cached_fragment_texture_bindings[slot].metal_texture = tex;
diff --git a/source/blender/gpu/metal/mtl_common.hh b/source/blender/gpu/metal/mtl_common.hh
index 8dda2c43585..28404d91b4b 100644
--- a/source/blender/gpu/metal/mtl_common.hh
+++ b/source/blender/gpu/metal/mtl_common.hh
@@ -4,8 +4,13 @@
 #define __MTL_COMMON
 
 // -- Renderer Options --
+#define MTL_MAX_DRAWABLES 3
 #define MTL_MAX_SET_BYTES_SIZE 4096
 #define MTL_FORCE_WAIT_IDLE 0
 #define MTL_MAX_COMMAND_BUFFERS 64
 
+/* Number of frames for which we retain in-flight resources such as scratch buffers.
+ * Set as number of GPU frames in flight, plus an additioanl value for extra possible CPU frame. */
+#define MTL_NUM_SAFE_FRAMES (MTL_MAX_DRAWABLES + 1)
+
 #endif
diff --git a/source/blender/gpu/metal/mtl_context.hh b/source/blender/gpu/metal/mtl_context.hh
index 1b2af6a584b..4b87b994a3d 100644
--- a/source/blender/gpu/metal/mtl_context.hh
+++ b/source/blender/gpu/metal/mtl_context.hh
@@ -12,7 +12,9 @@
 
 #include "mtl_backend.hh"
 #include "mtl_capabilities.hh"
+#include "mtl_common.hh"
 #include "mtl_framebuffer.hh"
+#include "mtl_memory.hh"
 #include "mtl_texture.hh"
 
 #include 
@@ -30,7 +32,6 @@ class MTLContext;
 class MTLCommandBufferManager;
 class MTLShader;
 class MTLUniformBuf;
-class MTLBuffer;
 
 /* Structs containing information on current binding state for textures and samplers. */
 struct MTLTextureBinding {
@@ -56,10 +57,13 @@ struct MTLSamplerBinding {
 struct MTLRenderPassState {
   friend class MTLContext;
 
+  MTLRenderPassState(MTLContext &context, MTLCommandBufferManager &command_buffer_manager)
+      : ctx(context), cmd(command_buffer_manager){};
+
   /* Given a RenderPassState is associated with a live RenderCommandEncoder,
    * this state sits within the MTLCommandBufferManager. */
-  MTLCommandBufferManager *cmd;
-  MTLContext *ctx;
+  MTLContext &ctx;
+  MTLCommandBufferManager &cmd;
 
   /* Caching of resource bindings for active MTLRenderCommandEncoder.
    * In Metal, resource bindings are local to the MTLCommandEncoder,
@@ -110,9 +114,6 @@ struct MTLRenderPassState {
   SamplerStateBindingCached cached_vertex_sampler_state_bindings[MTL_MAX_TEXTURE_SLOTS];
   SamplerStateBindingCached cached_fragment_sampler_state_bindings[MTL_MAX_TEXTURE_SLOTS];
 
-  /* Prepare. */
-  void prepare(MTLCommandBufferManager *cmd, MTLContext *ctx);
-
   /* Reset RenderCommandEncoder binding state. */
   void reset_state();
 
@@ -446,18 +447,6 @@ struct MTLContextGlobalShaderPipelineState {
   float line_width = 1.0f;
 };
 
-/* Metal Buffer */
-struct MTLTemporaryBufferRange {
-  id metal_buffer;
-  void *host_ptr;
-  unsigned long long buffer_offset;
-  unsigned long long size;
-  MTLResourceOptions options;
-
-  void flush();
-  bool requires_flush();
-};
-
 /* Command Buffer Manager - Owned by MTLContext.
  * The MTLCommandBufferManager represents all work associated with
  * a command buffer of a given identity. This manager is a fixed-state
@@ -477,14 +466,14 @@ class MTLCommandBufferManager {
  public:
   /* Event to coordinate sequential execution across all "main" command buffers. */
   static id sync_event;
-  static unsigned long long event_signal_val;
+  static uint64_t event_signal_val;
 
   /* Counter for active command buffers. */
   static int num_active_cmd_bufs;
 
  private:
   /* Associated Context and properties. */
-  MTLContext *context_ = nullptr;
+  MTLContext &context_;
   bool supports_render_ = false;
 
   /* CommandBuffer tracking. */
@@ -516,7 +505,9 @@ class MTLCommandBufferManager {
   bool empty_ = true;
 
  public:
-  void prepare(MTLContext *ctx, bool supports_render = true);
+  MTLCommandBufferManager(MTLContext &context)
+      : context_(context), render_pass_state_(context, *this){};
+  void prepare(bool supports_render = true);
 
   /* If wait is true, CPU will stall until GPU work has completed. */
   bool submit(bool wait);
@@ -582,7 +573,7 @@ class MTLContext : public Context {
 
   /* Texture Samplers. */
   /* Cache of generated MTLSamplerState objects based on permutations of `eGPUSamplerState`. */
-  id sampler_state_cache_[GPU_SAMPLER_MAX] = {0};
+  id sampler_state_cache_[GPU_SAMPLER_MAX];
   id default_sampler_state_ = nil;
 
   /* When texture sampler count exceeds the resource bind limit, an
@@ -595,6 +586,7 @@ class MTLContext : public Context {
 
   /* Frame. */
   bool is_inside_frame_ = false;
+  uint current_frame_index_;
 
  public:
   /* Shaders and Pipeline state. */
@@ -604,6 +596,10 @@ class MTLContext : public Context {
   id queue = nil;
   id device = nil;
 
+  /* Memory Management */
+  MTLScratchBufferManager memory_manager;
+  static MTLBufferPool global_memory_manager;
+
   /* CommandBuffer managers. */
   MTLCommandBufferManager main_command_buffer;
 
@@ -624,7 +620,7 @@ class MTLContext : public Context {
   void memory_statistics_get(int *total_mem, int *free_mem) override;
 
   void debug_group_begin(const char *name, int index) override;
-  void debug_group_end(void) override;
+  void debug_group_end() override;
 
   /*** MTLContext Utility functions. */
   /*
@@ -679,6 +675,21 @@ class MTLContext : public Context {
   {
     return is_inside_frame_;
   }
+
+  uint get_current_frame_index()
+  {
+    return current_frame_index_;
+  }
+
+  MTLScratchBufferManager &get_scratchbuffer_manager()
+  {
+    return this->memory_manager;
+  }
+
+  static MTLBufferPool &get_global_memory_manager()
+  {
+    return MTLContext::global_memory_manager;
+  }
 };
 
 }  // namespace blender::gpu
diff --git a/source/blender/gpu/metal/mtl_context.mm b/source/blender/gpu/metal/mtl_context.mm
index 6ecdb3f48b3..2cf70718b76 100644
--- a/source/blender/gpu/metal/mtl_context.mm
+++ b/source/blender/gpu/metal/mtl_context.mm
@@ -16,44 +16,25 @@ using namespace blender::gpu;
 
 namespace blender::gpu {
 
-/* -------------------------------------------------------------------- */
-/** \name Memory Management
- * \{ */
-
-bool MTLTemporaryBufferRange::requires_flush()
-{
-  /* We do not need to flush shared memory. */
-  return this->options & MTLResourceStorageModeManaged;
-}
-
-void MTLTemporaryBufferRange::flush()
-{
-  if (this->requires_flush()) {
-    BLI_assert(this->metal_buffer);
-    BLI_assert((this->buffer_offset + this->size) <= [this->metal_buffer length]);
-    BLI_assert(this->buffer_offset >= 0);
-    [this->metal_buffer
-        didModifyRange:NSMakeRange(this->buffer_offset, this->size - this->buffer_offset)];
-  }
-}
-
-/** \} */
+/* Global memory mamnager */
+MTLBufferPool MTLContext::global_memory_manager;
 
 /* -------------------------------------------------------------------- */
 /** \name MTLContext
  * \{ */
 
 /* Placeholder functions */
-MTLContext::MTLContext(void *ghost_window)
+MTLContext::MTLContext(void *ghost_window) : memory_manager(*this), main_command_buffer(*this)
 {
   /* Init debug. */
   debug::mtl_debug_init();
 
   /* Initialize command buffer state. */
-  this->main_command_buffer.prepare(this);
+  this->main_command_buffer.prepare();
 
   /* Frame management. */
   is_inside_frame_ = false;
+  current_frame_index_ = 0;
 
   /* Create FrameBuffer handles. */
   MTLFrameBuffer *mtl_front_left = new MTLFrameBuffer(this, "front_left");
@@ -65,9 +46,14 @@ MTLContext::MTLContext(void *ghost_window)
    * initialization). */
   MTLBackend::platform_init(this);
   MTLBackend::capabilities_init(this);
+
   /* Initialize Metal modules. */
+  this->memory_manager.init();
   this->state_manager = new MTLStateManager(this);
 
+  /* Ensure global memory manager is initialied */
+  MTLContext::global_memory_manager.init(this->device);
+
   /* Initialize texture read/update structures. */
   this->get_texture_utils().init();
 
@@ -93,7 +79,7 @@ MTLContext::~MTLContext()
     this->finish();
 
     /* End frame. */
-    if (is_inside_frame_) {
+    if (this->get_inside_frame()) {
       this->end_frame();
     }
   }
@@ -112,7 +98,7 @@ MTLContext::~MTLContext()
 void MTLContext::begin_frame()
 {
   BLI_assert(MTLBackend::get()->is_inside_render_boundary());
-  if (is_inside_frame_) {
+  if (this->get_inside_frame()) {
     return;
   }
 
@@ -122,7 +108,7 @@ void MTLContext::begin_frame()
 
 void MTLContext::end_frame()
 {
-  BLI_assert(is_inside_frame_);
+  BLI_assert(this->get_inside_frame());
 
   /* Ensure pre-present work is committed. */
   this->flush();
@@ -136,20 +122,20 @@ void MTLContext::check_error(const char *info)
   /* TODO(Metal): Implement. */
 }
 
-void MTLContext::activate(void)
+void MTLContext::activate()
 {
   /* TODO(Metal): Implement. */
 }
-void MTLContext::deactivate(void)
+void MTLContext::deactivate()
 {
   /* TODO(Metal): Implement. */
 }
 
-void MTLContext::flush(void)
+void MTLContext::flush()
 {
   /* TODO(Metal): Implement. */
 }
-void MTLContext::finish(void)
+void MTLContext::finish()
 {
   /* TODO(Metal): Implement. */
 }
@@ -180,7 +166,7 @@ id MTLContext::ensure_begin_render_pass()
   BLI_assert(this);
 
   /* Ensure the rendering frame has started. */
-  if (!is_inside_frame_) {
+  if (!this->get_inside_frame()) {
     this->begin_frame();
   }
 
diff --git a/source/blender/gpu/metal/mtl_framebuffer.mm b/source/blender/gpu/metal/mtl_framebuffer.mm
index 22de255bf63..b0a90829c0a 100644
--- a/source/blender/gpu/metal/mtl_framebuffer.mm
+++ b/source/blender/gpu/metal/mtl_framebuffer.mm
@@ -756,7 +756,7 @@ void MTLFrameBuffer::update_attachments(bool update_viewport)
   dirty_attachments_ = false;
 }
 
-void MTLFrameBuffer::apply_state(void)
+void MTLFrameBuffer::apply_state()
 {
   MTLContext *mtl_ctx = static_cast(unwrap(GPU_context_active_get()));
   BLI_assert(mtl_ctx);
diff --git a/source/blender/gpu/metal/mtl_memory.hh b/source/blender/gpu/metal/mtl_memory.hh
new file mode 100644
index 00000000000..81793b0647c
--- /dev/null
+++ b/source/blender/gpu/metal/mtl_memory.hh
@@ -0,0 +1,476 @@
+
+#pragma once
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "mtl_common.hh"
+
+#include 
+#include 
+#include 
+
+@class CAMetalLayer;
+@class MTLCommandQueue;
+@class MTLRenderPipelineState;
+
+/* Metal Memory Manager Overview. */
+/*
+ * The Metal Backend Memory manager is designed to provide an interface
+ * for all other MTL_* modules where memory allocation is required.
+ *
+ * Different allocation strategies and datastructures are used depending
+ * on how the data is used by the backend. These aim to optimally handle
+ * system memory and abstract away any complexity from the MTL_* modules
+ * themselves.
+ *
+ * There are two primary allocation modes which can be used:
+ *
+ * ** MTLScratchBufferManager **
+ *
+ *    Each MTLContext owns a ScratchBufferManager which is implemented
+ *    as a pool of circular buffers, designed to handle temporary
+ *    memory allocations which occur on a per-frame basis. The scratch
+ *    buffers allow flushing of host memory to the GPU to be batched.
+ *
+ *    Each frame, the next scratch buffer is reset, then later flushed upon
+ *    command buffer submission.
+ *
+ *    Note: This is allocated per-context due to allocations being tied
+ *    to workload submissions and context-specific submissions.
+ *
+ *    Examples of scratch buffer usage are:
+ *      - Immediate-mode temporary vertex buffers.
+ *      - Shader uniform data updates
+ *      - Staging of data for resource copies, or, data reads/writes.
+ *
+ *  Usage:
+ *
+ *    MTLContext::get_scratchbuffer_manager() - to fetch active manager.
+ *
+ *    MTLTemporaryBuffer scratch_buffer_allocate_range(size)
+ *    MTLTemporaryBuffer scratch_buffer_allocate_range_aligned(size, align)
+ *
+ * ---------------------------------------------------------------------------------
+ *  ** MTLBufferPool **
+ *
+ *    For static and longer-lasting memory allocations, such as those for UBOs,
+ *    Vertex buffers, index buffers, etc; We want an optimal abstraction for
+ *    fetching a MTLBuffer of the desired size and resource options.
+ *
+ *    Memory allocations can be expensive so the MTLBufferPool provides
+ *    functionality to track usage of these buffers and once a buffer
+ *    is no longer in use, it is returned to the buffer pool for use
+ *    by another backend resource.
+ *
+ *    The MTLBufferPool provides functionality for safe tracking of resources,
+ *    as buffers freed on the host side must have their usage by the GPU tracked,
+ *    to ensure they are not prematurely re-used before they have finished being
+ *    used by the GPU.
+ *
+ *    Note: The MTLBufferPool is a global construct which can be fetched from anywhere.
+ *
+ *  Usage:
+ *    MTLContext::get_global_memory_manager();  - static routine to fetch global memory manager.
+ *
+ *    gpu::MTLBuffer *allocate_buffer(size, is_cpu_visibile, bytes=nullptr)
+ *    gpu::MTLBuffer *allocate_buffer_aligned(size, alignment, is_cpu_visibile, bytes=nullptr)
+ */
+
+/* Debug memory statistics: Disabled by Macro rather than guarded for
+ * performance considerations. */
+#define MTL_DEBUG_MEMORY_STATISTICS 0
+
+/* Allows a scratch buffer to temporarily grow beyond its maximum, which allows submission
+ * of one-time-use data packets which are too large. */
+#define MTL_SCRATCH_BUFFER_ALLOW_TEMPORARY_EXPANSION 1
+
+namespace blender::gpu {
+
+/* Forward Declarations. */
+class MTLContext;
+class MTLCommandBufferManager;
+class MTLUniformBuf;
+
+/* -------------------------------------------------------------------- */
+/** \name Memory Management.
+ * \{ */
+
+/* MTLBuffer allocation wrapper. */
+class MTLBuffer {
+
+ private:
+  /* Metal resource. */
+  id metal_buffer_;
+
+  /* Host-visible mapped-memory pointer. Behaviour depends on buffer type:
+   * - Shared buffers: pointer represents base address of MTLBuffer whose data
+   *                   access has shared access by both the CPU and GPU on
+   *                   Unified Memory Architectures (UMA).
+   * - Managed buffer: Host-side mapped buffer region for CPU (Host) access. Managed buffers
+   *                   must be manually flushed to transfer data to GPU-resident buffer.
+   * - Private buffer: Host access is invalid, `data` will be nullptr. */
+  void *data_;
+
+  /* Whether buffer is allocated from an external source. */
+  bool is_external_ = false;
+
+  /* Allocation info. */
+  MTLResourceOptions options_;
+  id device_;
+  uint64_t alignment_;
+  uint64_t size_;
+
+  /* Allocated size may be larger than actual size. */
+  uint64_t usage_size_;
+
+  /* Lifetime info - whether the current buffer is actively in use. A buffer
+   * should be in use after it has been allocated. De-allocating the buffer, and
+   * returning it to the free buffer pool will set in_use to false. Using a buffer
+   * while it is not in-use should not be allowed and result in an error. */
+  std::atomic in_use_;
+
+ public:
+  MTLBuffer(id device, uint64_t size, MTLResourceOptions options, uint alignment = 1);
+  MTLBuffer(id external_buffer);
+  ~MTLBuffer();
+
+  /* Fetch information about backing MTLBuffer. */
+  id get_metal_buffer() const;
+  void *get_host_ptr() const;
+  uint64_t get_size_used() const;
+  uint64_t get_size() const;
+
+  /* Flush data to GPU. */
+  void flush();
+  void flush_range(uint64_t offset, uint64_t length);
+  bool requires_flush();
+
+  /* Buffer usage tracking. */
+  void flag_in_use(bool used);
+  bool get_in_use();
+  void set_usage_size(uint64_t size_used);
+
+  /* Debug. */
+  void set_label(NSString *str);
+
+  /* Read properties. */
+  MTLResourceOptions get_resource_options();
+  uint64_t get_alignment();
+
+  /* Resource-local free: For buffers allocated via memory manager,
+   * this will call the context `free_buffer` method to return the buffer to the context memory
+   * pool.
+   *
+   * Otherwise, free will release the associated metal resource.
+   * As a note, calling the destructor will also destroy the buffer and associated metal
+   * resource. */
+  void free();
+
+  /* Safety check to ensure buffers are not used after free. */
+  void debug_ensure_used();
+};
+
+/* View into part of an MTLBuffer. */
+struct MTLBufferRange {
+  id metal_buffer;
+  void *data;
+  uint64_t buffer_offset;
+  uint64_t size;
+  MTLResourceOptions options;
+
+  void flush();
+  bool requires_flush();
+};
+
+/* Circular scratch buffer allocations should be seen as temporary and only used within the
+ * lifetime of the frame. */
+using MTLTemporaryBuffer = MTLBufferRange;
+
+/* Round-Robin Circular-buffer. */
+class MTLCircularBuffer {
+  friend class MTLScratchBufferManager;
+
+ private:
+  MTLContext &own_context_;
+
+  /* Wrapped MTLBuffer allocation handled. */
+  gpu::MTLBuffer *cbuffer_;
+
+  /* Current offset where next allocation will begin. */
+  uint64_t current_offset_;
+
+  /* Whether the Circular Buffer can grow during re-allocation if
+   * the size is exceeded. */
+  bool can_resize_;
+
+  /* Usage information. */
+  uint64_t used_frame_index_;
+  uint64_t last_flush_base_offset_;
+
+ public:
+  MTLCircularBuffer(MTLContext &ctx, uint64_t initial_size, bool allow_grow);
+  ~MTLCircularBuffer();
+  MTLTemporaryBuffer allocate_range(uint64_t alloc_size);
+  MTLTemporaryBuffer allocate_range_aligned(uint64_t alloc_size, uint alignment);
+  void flush();
+
+  /* Reset pointer back to start of circular buffer. */
+  void reset();
+};
+
+/* Wrapper struct used by Memory Manager to sort and compare gpu::MTLBuffer resources inside the
+ * memory pools. */
+struct MTLBufferHandle {
+  gpu::MTLBuffer *buffer;
+  uint64_t buffer_size;
+
+  inline MTLBufferHandle(gpu::MTLBuffer *buf)
+  {
+    this->buffer = buf;
+    this->buffer_size = this->buffer->get_size();
+  }
+
+  inline MTLBufferHandle(uint64_t compare_size)
+  {
+    this->buffer = nullptr;
+    this->buffer_size = compare_size;
+  }
+};
+
+struct CompareMTLBuffer {
+  bool operator()(const MTLBufferHandle &lhs, const MTLBufferHandle &rhs) const
+  {
+    return lhs.buffer_size < rhs.buffer_size;
+  }
+};
+
+/* An MTLSafeFreeList is a temporary list of gpu::MTLBuffers which have
+ * been freed by the high level backend, but are pending GPU work execution before
+ * the gpu::MTLBuffers can be returned to the Memory manager pools.
+ * This list is implemented as a chunked linked-list.
+ *
+ * Only a single MTLSafeFreeList is active at one time and is associated with current command
+ * buffer submissions. If an MTLBuffer is freed during the lifetime of a command buffer, it could
+ * still possibly be in-use and as such, the MTLSafeFreeList will increment its reference count for
+ * each command buffer submitted while the current pool is active.
+ *
+ * -- Reference count is incremented upon MTLCommandBuffer commit.
+ * -- Reference count is decremented in the MTLCommandBuffer completion callback handler.
+ *
+ * A new MTLSafeFreeList will begin each render step (frame). This pooling of buffers, rather than
+ * individual buffer resource tracking reduces performance overhead.
+ *
+ *  * The reference count starts at 1 to ensure that the reference count cannot prematurely reach
+ *  zero until any command buffers have been submitted. This additional decrement happens
+ *  when the next MTLSafeFreeList is created, to allow the existing pool to be released once
+ *  the reference count hits zero after submitted command buffers complete.
+ *
+ * Note: the Metal API independently tracks resources used by command buffers for the purpose of
+ * keeping resources alive while in-use by the driver and CPU, however, this differs from the
+ * MTLSafeFreeList mechanism in the Metal backend, which exists for the purpose of allowing
+ * previously allocated MTLBuffer resources to be re-used. This allows us to save on the expensive
+ * cost of memory allocation.
+ */
+class MTLSafeFreeList {
+  friend class MTLBufferPool;
+
+ private:
+  std::atomic reference_count_;
+  std::atomic in_free_queue_;
+  std::recursive_mutex lock_;
+
+  /* Linked list of next MTLSafeFreeList chunk if current chunk is full. */
+  std::atomic has_next_pool_;
+  std::atomic next_;
+
+  /* Lockless list. MAX_NUM_BUFFERS_ within a chunk based on considerations
+   * for performance and memory. */
+  static const int MAX_NUM_BUFFERS_ = 1024;
+  std::atomic current_list_index_;
+  gpu::MTLBuffer *safe_free_pool_[MAX_NUM_BUFFERS_];
+
+ public:
+  MTLSafeFreeList();
+
+  /* Add buffer to Safe Free List, can be called from secondary threads.
+   * Performs a lockless list insert. */
+  void insert_buffer(gpu::MTLBuffer *buffer);
+
+  /* Increments command buffer reference count. */
+  void increment_reference();
+
+  /* Decrement and return of buffers to pool occur on MTLCommandBuffer completion callback thread.
+   */
+  void decrement_reference();
+
+  void flag_in_queue()
+  {
+    in_free_queue_ = true;
+    if (has_next_pool_) {
+      MTLSafeFreeList *next_pool = next_.load();
+      BLI_assert(next_pool != nullptr);
+      next_pool->flag_in_queue();
+    }
+  }
+};
+
+/* MTLBuffer pools. */
+/* Allocating Metal buffers is expensive, so we cache all allocated buffers,
+ * and when requesting a new buffer, find one which fits the required dimensions
+ * from an existing pool of buffers.
+ *
+ * When freeing MTLBuffers, we insert them into the current MTLSafeFreeList, which defers
+ * release of the buffer until the associated command buffers have finished executing.
+ * This prevents a buffer from being re-used while it is still in-use by the GPU.
+ *
+ * * Once command buffers complete, MTLSafeFreeList's associated with the current
+ *   command buffer submission are added to the `completed_safelist_queue_`.
+ *
+ * * At a set point in time, all MTLSafeFreeList's in `completed_safelist_queue_` have their
+ *   MTLBuffers re-inserted into the Memory Manager's pools. */
+class MTLBufferPool {
+
+ private:
+  /* Memory statistics. */
+  long long int total_allocation_bytes_ = 0;
+
+#if MTL_DEBUG_MEMORY_STATISTICS == 1
+  /* Debug statistics. */
+  std::atomic per_frame_allocation_count_;
+  std::atomic allocations_in_pool_;
+  std::atomic buffers_in_pool_;
+#endif
+
+  /* Metal resources. */
+  bool ensure_initialised_ = false;
+  id device_ = nil;
+
+  /* The buffer selection aims to pick a buffer which meets the minimum size requierments.
+   * To do this, we keep an ordered set of all available buffers. If the buffer is larger than the
+   * desired allocation size, we check it aginst `mtl_buffer_size_threshold_factor_`, which defines
+   * what % larger than the original allocation the buffer can be.
+   * - A higher value results in greater re-use of previously allocated buffers of similar sizes.
+   * - A lower value may result in more dynamic allocations, but minimised memory usage for a given
+   *   scenario.
+   * The current value of 1.26 is calibrated for optimal performance and memory utilisation. */
+  static constexpr float mtl_buffer_size_threshold_factor_ = 1.26;
+
+  /* Buffer pools using MTLResourceOptions as key for allocation type.
+   * Aliased as 'uint64_t' for map type compatibility.
+   * - A size-ordered list (MultiSet) of allocated buffers is kept per MTLResourceOptions
+   *   permutation. This allows efficient lookup for buffers of a given requested size.
+   * - MTLBufferHandle wraps a gpu::MTLBuffer pointer to achieve easy size-based sorting
+   *   via CompareMTLBuffer. */
+  using MTLBufferPoolOrderedList = std::multiset;
+  using MTLBufferResourceOptions = uint64_t;
+
+  blender::Map buffer_pools_;
+  blender::Vector allocations_;
+
+  /* Maintain a queue of all MTLSafeFreeList's that have been released
+   * by the GPU and are ready to have their buffers re-inserted into the
+   * MemoryManager pools.
+   * Access to this queue is made thread-safe through safelist_lock_. */
+  std::mutex safelist_lock_;
+  blender::Vector completed_safelist_queue_;
+
+  /* Current free list, associated with active MTLCommandBuffer submission. */
+  /* MTLBuffer::free() can be called from separate threads, due to usage within animation
+   * system/worker threads. */
+  std::atomic current_free_list_;
+
+ public:
+  void init(id device);
+  ~MTLBufferPool();
+
+  gpu::MTLBuffer *allocate_buffer(uint64_t size, bool cpu_visible, const void *bytes = nullptr);
+  gpu::MTLBuffer *allocate_buffer_aligned(uint64_t size,
+                                          uint alignment,
+                                          bool cpu_visible,
+                                          const void *bytes = nullptr);
+  bool free_buffer(gpu::MTLBuffer *buffer);
+
+  /* Flush MTLSafeFreeList buffers, for completed lists in `completed_safelist_queue_`,
+   * back to memory pools. */
+  void update_memory_pools();
+
+  /* Access and control over active MTLSafeFreeList. */
+  MTLSafeFreeList *get_current_safe_list();
+  void begin_new_safe_list();
+
+  /* Add a completed MTLSafeFreeList to completed_safelist_queue_. */
+  void push_completed_safe_list(MTLSafeFreeList *list);
+
+ private:
+  void ensure_buffer_pool(MTLResourceOptions options);
+  void insert_buffer_into_pool(MTLResourceOptions options, gpu::MTLBuffer *buffer);
+  void free();
+};
+
+/* Scratch buffers are circular-buffers used for temporary data within the current frame.
+ * In order to preserve integrity of contents when having multiple-frames-in-flight,
+ * we cycle through a collection of scratch buffers which are reset upon next use.
+ *
+ * Below are a series of properties, declared to manage scratch buffers. If a scratch buffer
+ * overflows, then the original buffer will be flushed and submitted, with retained references
+ * by usage within the command buffer, and a new buffer will be created.
+ * - The new buffer will grow in size to account for increased demand in temporary memory.
+ */
+class MTLScratchBufferManager {
+
+ private:
+  /* Maximum number of scratch buffers to allocate. This should be the maximum number of
+   * simultaneous frames in flight. */
+  static constexpr uint mtl_max_scratch_buffers_ = MTL_NUM_SAFE_FRAMES;
+
+ public:
+  /* Maximum size of single scratch buffer allocation. When re-sizing, this is the maximum size the
+   * newly allocated buffers will grow to. Larger allocations are possible if
+   * `MTL_SCRATCH_BUFFER_ALLOW_TEMPORARY_EXPANSION` is enabled, but these will instead allocate new
+   * buffers from the memory pools on the fly. */
+  static constexpr uint mtl_scratch_buffer_max_size_ = 128 * 1024 * 1024;
+
+  /* Initial size of circular scratch buffers prior to growth. */
+  static constexpr uint mtl_scratch_buffer_initial_size_ = 16 * 1024 * 1024;
+
+ private:
+  /* Parent MTLContext. */
+  MTLContext &context_;
+  bool initialised_ = false;
+
+  /* Scratch buffer currently in-use. */
+  uint current_scratch_buffer_ = 0;
+
+  /* Scratch buffer pool. */
+  MTLCircularBuffer *scratch_buffers_[mtl_max_scratch_buffers_];
+
+ public:
+  MTLScratchBufferManager(MTLContext &context) : context_(context){};
+  ~MTLScratchBufferManager();
+
+  /* Explicit initialisation and freeing of resources. Init must occur after device creation. */
+  void init();
+  void free();
+
+  /* Allocation functions for creating temporary allocations from active circular buffer. */
+  MTLTemporaryBuffer scratch_buffer_allocate_range(uint64_t alloc_size);
+  MTLTemporaryBuffer scratch_buffer_allocate_range_aligned(uint64_t alloc_size, uint alignment);
+
+  /* Ensure a new scratch buffer is started if we move onto a new frame.
+   * Called when a new command buffer begins. */
+  void ensure_increment_scratch_buffer();
+
+  /* Flush memory for active scratch buffer to GPU.
+   * This call will perform a partial flush of the buffer starting from
+   * the last offset the data was flushed from, to the current offset. */
+  void flush_active_scratch_buffer();
+};
+
+/** \} */
+
+}  // namespace blender::gpu
diff --git a/source/blender/gpu/metal/mtl_memory.mm b/source/blender/gpu/metal/mtl_memory.mm
new file mode 100644
index 00000000000..5c5938997e6
--- /dev/null
+++ b/source/blender/gpu/metal/mtl_memory.mm
@@ -0,0 +1,880 @@
+
+#include "BKE_global.h"
+
+#include "DNA_userdef_types.h"
+
+#include "mtl_context.hh"
+#include "mtl_debug.hh"
+#include "mtl_memory.hh"
+
+using namespace blender;
+using namespace blender::gpu;
+
+namespace blender::gpu {
+
+/* -------------------------------------------------------------------- */
+/** \name Memory Management - MTLBufferPool and MTLSafeFreeList implementations. */
+
+void MTLBufferPool::init(id mtl_device)
+{
+  if (!ensure_initialised_) {
+    BLI_assert(mtl_device);
+    ensure_initialised_ = true;
+    device_ = mtl_device;
+
+#if MTL_DEBUG_MEMORY_STATISTICS == 1
+    /* Debug statistics. */
+    per_frame_allocation_count_ = 0;
+    allocations_in_pool_ = 0;
+    buffers_in_pool_ = 0;
+#endif
+
+    /* Free pools -- Create initial safe free pool */
+    BLI_assert(current_free_list_ == nullptr);
+    this->begin_new_safe_list();
+  }
+}
+
+MTLBufferPool::~MTLBufferPool()
+{
+  this->free();
+}
+
+void MTLBufferPool::free()
+{
+
+  for (auto buffer : allocations_) {
+    BLI_assert(buffer);
+    delete buffer;
+  }
+  allocations_.clear();
+
+  for (std::multiset *buffer_pool :
+       buffer_pools_.values()) {
+    delete buffer_pool;
+  }
+  buffer_pools_.clear();
+}
+
+gpu::MTLBuffer *MTLBufferPool::allocate_buffer(uint64_t size, bool cpu_visible, const void *bytes)
+{
+  /* Allocate buffer with default HW-compatible alignemnt of 256 bytes.
+   * See https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf for more. */
+  return this->allocate_buffer_aligned(size, 256, cpu_visible, bytes);
+}
+
+gpu::MTLBuffer *MTLBufferPool::allocate_buffer_aligned(uint64_t size,
+                                                       uint alignment,
+                                                       bool cpu_visible,
+                                                       const void *bytes)
+{
+  /* Check not required. Main GPU module usage considered thread-safe. */
+  // BLI_assert(BLI_thread_is_main());
+
+  /* Calculate aligned size */
+  BLI_assert(alignment > 0);
+  uint64_t aligned_alloc_size = ceil_to_multiple_ul(size, alignment);
+
+  /* Allocate new MTL Buffer */
+  MTLResourceOptions options;
+  if (cpu_visible) {
+    options = ([device_ hasUnifiedMemory]) ? MTLResourceStorageModeShared :
+                                             MTLResourceStorageModeManaged;
+  }
+  else {
+    options = MTLResourceStorageModePrivate;
+  }
+
+  /* Check if we have a suitable buffer */
+  gpu::MTLBuffer *new_buffer = nullptr;
+  std::multiset **pool_search = buffer_pools_.lookup_ptr(
+      (uint64_t)options);
+
+  if (pool_search != nullptr) {
+    std::multiset *pool = *pool_search;
+    MTLBufferHandle size_compare(aligned_alloc_size);
+    auto result = pool->lower_bound(size_compare);
+    if (result != pool->end()) {
+      /* Potential buffer found, check if within size threshold requirements. */
+      gpu::MTLBuffer *found_buffer = result->buffer;
+      BLI_assert(found_buffer);
+      BLI_assert(found_buffer->get_metal_buffer());
+
+      uint64_t found_size = found_buffer->get_size();
+
+      if (found_size >= aligned_alloc_size &&
+          found_size <= (aligned_alloc_size * mtl_buffer_size_threshold_factor_)) {
+        MTL_LOG_INFO(
+            "[MemoryAllocator] Suitable Buffer of size %lld found, for requested size: %lld\n",
+            found_size,
+            aligned_alloc_size);
+
+        new_buffer = found_buffer;
+        BLI_assert(!new_buffer->get_in_use());
+
+        /* Remove buffer from free set. */
+        pool->erase(result);
+      }
+      else {
+        MTL_LOG_INFO(
+            "[MemoryAllocator] Buffer of size %lld found, but was incompatible with requested "
+            "size: "
+            "%lld\n",
+            found_size,
+            aligned_alloc_size);
+        new_buffer = nullptr;
+      }
+    }
+  }
+
+  /* Allocate new buffer. */
+  if (new_buffer == nullptr) {
+    new_buffer = new gpu::MTLBuffer(device_, size, options, alignment);
+
+    /* Track allocation in context. */
+    allocations_.append(new_buffer);
+    total_allocation_bytes_ += aligned_alloc_size;
+  }
+  else {
+    /* Re-use suitable buffer. */
+    new_buffer->set_usage_size(aligned_alloc_size);
+
+#if MTL_DEBUG_MEMORY_STATISTICS == 1
+    /* Debug. */
+    allocations_in_pool_ -= new_buffer->get_size();
+    buffers_in_pool_--;
+    BLI_assert(allocations_in_pool_ >= 0);
+#endif
+
+    /* Ensure buffer memory is correctly backed. */
+    BLI_assert(new_buffer->get_metal_buffer());
+  }
+  /* Flag buffer as actively in-use. */
+  new_buffer->flag_in_use(true);
+
+  /* Upload initial data if provided -- Size based on original size param, not aligned size*/
+  if (bytes) {
+    BLI_assert(!(options & MTLResourceStorageModePrivate));
+    BLI_assert(size <= aligned_alloc_size);
+    BLI_assert(size <= [new_buffer->get_metal_buffer() length]);
+    memcpy(new_buffer->get_host_ptr(), bytes, size);
+    new_buffer->flush_range(0, size);
+  }
+
+#if MTL_DEBUG_MEMORY_STATISTICS == 1
+  this->per_frame_allocation_count++;
+#endif
+
+  return new_buffer;
+}
+
+bool MTLBufferPool::free_buffer(gpu::MTLBuffer *buffer)
+{
+  /* Ensure buffer is flagged as in-use. I.e. has not already been returned to memory pools. */
+  bool buffer_in_use = buffer->get_in_use();
+  BLI_assert(buffer_in_use);
+  if (buffer_in_use) {
+
+    /* Fetch active safe pool from atomic ptr. */
+    MTLSafeFreeList *current_pool = this->get_current_safe_list();
+
+    /* Place buffer in safe_free_pool before returning to MemoryManager buffer pools. */
+    BLI_assert(current_pool);
+    current_pool->insert_buffer(buffer);
+    buffer->flag_in_use(false);
+
+    return true;
+  }
+  return false;
+}
+
+void MTLBufferPool::update_memory_pools()
+{
+  /* Ensure thread-safe access to `completed_safelist_queue_`, which contains
+   * the list of MTLSafeFreeList's whose buffers are ready to be
+   * re-inserted into the Memory Manager pools. */
+  safelist_lock_.lock();
+
+#if MTL_DEBUG_MEMORY_STATISTICS == 1
+  int num_buffers_added = 0;
+#endif
+
+  /* Always free oldest MTLSafeFreeList first. */
+  for (int safe_pool_free_index = 0; safe_pool_free_index < completed_safelist_queue_.size();
+       safe_pool_free_index++) {
+    MTLSafeFreeList *current_pool = completed_safelist_queue_[safe_pool_free_index];
+
+    /* Iterate through all MTLSafeFreeList linked-chunks. */
+    while (current_pool != nullptr) {
+      current_pool->lock_.lock();
+      BLI_assert(current_pool);
+      BLI_assert(current_pool->in_free_queue_);
+      int counter = 0;
+      int size = min_ii(current_pool->current_list_index_, MTLSafeFreeList::MAX_NUM_BUFFERS_);
+
+      /* Re-add all buffers within frame index to MemoryManager pools. */
+      while (counter < size) {
+
+        gpu::MTLBuffer *buf = current_pool->safe_free_pool_[counter];
+
+        /* Insert buffer back into open pools. */
+        BLI_assert(buf->get_in_use() == false);
+        this->insert_buffer_into_pool(buf->get_resource_options(), buf);
+        counter++;
+
+#if MTL_DEBUG_MEMORY_STATISTICS == 1
+        num_buffers_added++;
+#endif
+      }
+
+      /* Fetch next MTLSafeFreeList chunk, if any. */
+      MTLSafeFreeList *next_list = nullptr;
+      if (current_pool->has_next_pool_ > 0) {
+        next_list = current_pool->next_.load();
+      }
+
+      /* Delete current MTLSafeFreeList */
+      current_pool->lock_.unlock();
+      delete current_pool;
+      current_pool = nullptr;
+
+      /* Move onto next chunk. */
+      if (next_list != nullptr) {
+        current_pool = next_list;
+      }
+    }
+  }
+
+#if MTL_DEBUG_MEMORY_STATISTICS == 1
+  printf("--- Allocation Stats ---\n");
+  printf("  Num buffers processed in pool (this frame): %u\n", num_buffers_added);
+
+  uint framealloc = (uint)this->per_frame_allocation_count;
+  printf("  Allocations in frame: %u\n", framealloc);
+  printf("  Total Buffers allocated: %u\n", (uint)allocations_.size());
+  printf("  Total Memory allocated: %u MB\n", (uint)total_allocation_bytes_ / (1024 * 1024));
+
+  uint allocs = (uint)(allocations_in_pool_) / 1024 / 2024;
+  printf("  Free memory in pools: %u MB\n", allocs);
+
+  uint buffs = (uint)buffers_in_pool_;
+  printf("  Buffers in pools: %u\n", buffs);
+
+  printf("  Pools %u:\n", (uint)buffer_pools_.size());
+  auto key_iterator = buffer_pools_.keys().begin();
+  auto value_iterator = buffer_pools_.values().begin();
+  while (key_iterator != buffer_pools_.keys().end()) {
+    uint64_t mem_in_pool = 0;
+    uint64_t iters = 0;
+    for (auto it = (*value_iterator)->begin(); it != (*value_iterator)->end(); it++) {
+      mem_in_pool += it->buffer_size;
+      iters++;
+    }
+
+    printf("    Buffers in pool (%u)(%llu): %u (%u MB)\n",
+           (uint)*key_iterator,
+           iters,
+           (uint)((*value_iterator)->size()),
+           (uint)mem_in_pool / 1024 / 1024);
+    ++key_iterator;
+    ++value_iterator;
+  }
+
+  this->per_frame_allocation_count = 0;
+#endif
+
+  /* Clear safe pools list */
+  completed_safelist_queue_.clear();
+  safelist_lock_.unlock();
+}
+
+void MTLBufferPool::push_completed_safe_list(MTLSafeFreeList *safe_list)
+{
+  /* When an MTLSafeFreeList has been released by the GPU, and buffers are ready to
+   * be re-inserted into the MemoryManager pools for future use, add the MTLSafeFreeList
+   * to the `completed_safelist_queue_` for flushing at a controlled point in time. */
+  safe_list->lock_.lock();
+  BLI_assert(safe_list);
+  BLI_assert(safe_list->reference_count_ == 0 &&
+             "Pool must be fully dereferenced by all in-use cmd buffers before returning.\n");
+  BLI_assert(safe_list->in_free_queue_ == false && "Pool must not already be in queue");
+
+  /* Flag MTLSafeFreeList as having been added, and insert into SafeFreePool queue. */
+  safe_list->flag_in_queue();
+  safelist_lock_.lock();
+  completed_safelist_queue_.append(safe_list);
+  safelist_lock_.unlock();
+  safe_list->lock_.unlock();
+}
+
+MTLSafeFreeList *MTLBufferPool::get_current_safe_list()
+{
+  /* Thread-safe access via atomic ptr. */
+  return current_free_list_;
+}
+
+void MTLBufferPool::begin_new_safe_list()
+{
+  safelist_lock_.lock();
+  current_free_list_ = new MTLSafeFreeList();
+  safelist_lock_.unlock();
+}
+
+void MTLBufferPool::ensure_buffer_pool(MTLResourceOptions options)
+{
+  std::multiset **pool_search = buffer_pools_.lookup_ptr(
+      (uint64_t)options);
+  if (pool_search == nullptr) {
+    std::multiset *pool =
+        new std::multiset();
+    buffer_pools_.add_new((uint64_t)options, pool);
+  }
+}
+
+void MTLBufferPool::insert_buffer_into_pool(MTLResourceOptions options, gpu::MTLBuffer *buffer)
+{
+  /* Ensure `safelist_lock_` is locked in calling code before modifying. */
+  BLI_assert(buffer);
+
+  /* Reset usage size to actual size of allocation. */
+  buffer->set_usage_size(buffer->get_size());
+
+  /* Ensure pool exists. */
+  this->ensure_buffer_pool(options);
+
+  /* TODO(Metal): Support purgability - Allow buffer in pool to have its memory taken back by the
+   * OS if needed. As we keep allocations around, they may not actually be in use, but we can
+   * ensure they do not block other apps from using memory. Upon a buffer being needed again, we
+   * can reset this state.
+   *  TODO(Metal): Purgeability state does not update instantly, so this requires a deferral. */
+  BLI_assert(buffer->get_metal_buffer());
+  /* buffer->metal_buffer); [buffer->metal_buffer setPurgeableState:MTLPurgeableStateVolatile]; */
+
+  std::multiset *pool = buffer_pools_.lookup(options);
+  pool->insert(MTLBufferHandle(buffer));
+
+#if MTL_DEBUG_MEMORY_STATISTICS == 1
+  /* Debug statistics. */
+  allocations_in_pool_ += buffer->size;
+  buffers_in_pool_++;
+#endif
+}
+
+MTLSafeFreeList::MTLSafeFreeList()
+{
+  reference_count_ = 1;
+  in_free_queue_ = false;
+  current_list_index_ = 0;
+  next_ = nullptr;
+  has_next_pool_ = 0;
+}
+
+void MTLSafeFreeList::insert_buffer(gpu::MTLBuffer *buffer)
+{
+  BLI_assert(in_free_queue_ == false);
+
+  /* Lockless list insert. */
+  uint insert_index = current_list_index_++;
+
+  /* If the current MTLSafeFreeList size is exceeded, we ripple down the linked-list chain and
+   * insert the buffer into the next available chunk. */
+  if (insert_index >= MTLSafeFreeList::MAX_NUM_BUFFERS_) {
+
+    /* Check if first caller to generate next pool. */
+    int has_next = has_next_pool_++;
+    if (has_next == 0) {
+      next_ = new MTLSafeFreeList();
+    }
+    MTLSafeFreeList *next_list = next_.load();
+    BLI_assert(next_list);
+    next_list->insert_buffer(buffer);
+
+    /* Clamp index to chunk limit if overflowing. */
+    current_list_index_ = MTLSafeFreeList::MAX_NUM_BUFFERS_;
+    return;
+  }
+
+  safe_free_pool_[insert_index] = buffer;
+}
+
+/* Increments from active GPUContext thread. */
+void MTLSafeFreeList::increment_reference()
+{
+  lock_.lock();
+  BLI_assert(in_free_queue_ == false);
+  reference_count_++;
+  lock_.unlock();
+}
+
+/* Reference decrements and addition to completed list queue can occur from MTLCommandBuffer
+ * completion callback thread. */
+void MTLSafeFreeList::decrement_reference()
+{
+  lock_.lock();
+  BLI_assert(in_free_queue_ == false);
+  int ref_count = reference_count_--;
+
+  if (ref_count == 0) {
+    MTLContext::get_global_memory_manager().push_completed_safe_list(this);
+  }
+  lock_.unlock();
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name MTLBuffer wrapper class implementation.
+ * \{ */
+
+/* Construct a gpu::MTLBuffer wrapper around a newly created metal::MTLBuffer. */
+MTLBuffer::MTLBuffer(id mtl_device,
+                     uint64_t size,
+                     MTLResourceOptions options,
+                     uint alignment)
+{
+  /* Calculate aligned allocation size. */
+  BLI_assert(alignment > 0);
+  uint64_t aligned_alloc_size = ceil_to_multiple_ul(size, alignment);
+
+  alignment_ = alignment;
+  device_ = mtl_device;
+  is_external_ = false;
+
+  options_ = options;
+  this->flag_in_use(false);
+
+  metal_buffer_ = [device_ newBufferWithLength:aligned_alloc_size options:options];
+  BLI_assert(metal_buffer_);
+  [metal_buffer_ retain];
+
+  size_ = aligned_alloc_size;
+  this->set_usage_size(size_);
+  if (!(options_ & MTLResourceStorageModePrivate)) {
+    data_ = [metal_buffer_ contents];
+  }
+  else {
+    data_ = nullptr;
+  }
+}
+
+MTLBuffer::MTLBuffer(id external_buffer)
+{
+  BLI_assert(external_buffer != nil);
+
+  /* Ensure external_buffer remains referenced while in-use. */
+  metal_buffer_ = external_buffer;
+  [metal_buffer_ retain];
+
+  /* Extract properties. */
+  is_external_ = true;
+  device_ = nil;
+  alignment_ = 1;
+  options_ = [metal_buffer_ resourceOptions];
+  size_ = [metal_buffer_ allocatedSize];
+  this->set_usage_size(size_);
+  data_ = [metal_buffer_ contents];
+  in_use_ = true;
+}
+
+gpu::MTLBuffer::~MTLBuffer()
+{
+  if (metal_buffer_ != nil) {
+    [metal_buffer_ release];
+    metal_buffer_ = nil;
+  }
+}
+
+void gpu::MTLBuffer::free()
+{
+  if (!is_external_) {
+    MTLContext::get_global_memory_manager().free_buffer(this);
+  }
+  else {
+    if (metal_buffer_ != nil) {
+      [metal_buffer_ release];
+      metal_buffer_ = nil;
+    }
+  }
+}
+
+id gpu::MTLBuffer::get_metal_buffer() const
+{
+  return metal_buffer_;
+}
+
+void *gpu::MTLBuffer::get_host_ptr() const
+{
+  BLI_assert(!(options_ & MTLResourceStorageModePrivate));
+  BLI_assert(data_);
+  return data_;
+}
+
+uint64_t gpu::MTLBuffer::get_size() const
+{
+  return size_;
+}
+
+uint64_t gpu::MTLBuffer::get_size_used() const
+{
+  return usage_size_;
+}
+
+bool gpu::MTLBuffer::requires_flush()
+{
+  /* We do not need to flush shared memory, as addressable buffer is shared. */
+  return options_ & MTLResourceStorageModeManaged;
+}
+
+void gpu::MTLBuffer::set_label(NSString *str)
+{
+  metal_buffer_.label = str;
+}
+
+void gpu::MTLBuffer::debug_ensure_used()
+{
+  /* Debug: If buffer is not flagged as in-use, this is a problem. */
+  BLI_assert(in_use_ &&
+             "Buffer should be marked as 'in-use' if being actively used by an instance. Buffer "
+             "has likely already been freed.");
+}
+
+void gpu::MTLBuffer::flush()
+{
+  this->debug_ensure_used();
+  if (this->requires_flush()) {
+    [metal_buffer_ didModifyRange:NSMakeRange(0, size_)];
+  }
+}
+
+void gpu::MTLBuffer::flush_range(uint64_t offset, uint64_t length)
+{
+  this->debug_ensure_used();
+  if (this->requires_flush()) {
+    BLI_assert((offset + length) <= size_);
+    [metal_buffer_ didModifyRange:NSMakeRange(offset, length)];
+  }
+}
+
+void gpu::MTLBuffer::flag_in_use(bool used)
+{
+  in_use_ = used;
+}
+
+bool gpu::MTLBuffer::get_in_use()
+{
+  return in_use_;
+}
+
+void gpu::MTLBuffer::set_usage_size(uint64_t size_used)
+{
+  BLI_assert(size_used > 0 && size_used <= size_);
+  usage_size_ = size_used;
+}
+
+MTLResourceOptions gpu::MTLBuffer::get_resource_options()
+{
+  return options_;
+}
+
+uint64_t gpu::MTLBuffer::get_alignment()
+{
+  return alignment_;
+}
+
+bool MTLBufferRange::requires_flush()
+{
+  /* We do not need to flush shared memory. */
+  return this->options & MTLResourceStorageModeManaged;
+}
+
+void MTLBufferRange::flush()
+{
+  if (this->requires_flush()) {
+    BLI_assert(this->metal_buffer);
+    BLI_assert((this->buffer_offset + this->size) <= [this->metal_buffer length]);
+    BLI_assert(this->buffer_offset >= 0);
+    [this->metal_buffer
+        didModifyRange:NSMakeRange(this->buffer_offset, this->size - this->buffer_offset)];
+  }
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name MTLScratchBufferManager and MTLCircularBuffer implementation.
+ * \{ */
+
+MTLScratchBufferManager::~MTLScratchBufferManager()
+{
+  this->free();
+}
+
+void MTLScratchBufferManager::init()
+{
+
+  if (!this->initialised_) {
+    BLI_assert(context_.device);
+
+    /* Initialise Scratch buffers */
+    for (int sb = 0; sb < mtl_max_scratch_buffers_; sb++) {
+      scratch_buffers_[sb] = new MTLCircularBuffer(
+          context_, mtl_scratch_buffer_initial_size_, true);
+      BLI_assert(scratch_buffers_[sb]);
+      BLI_assert(&(scratch_buffers_[sb]->own_context_) == &context_);
+    }
+    current_scratch_buffer_ = 0;
+    initialised_ = true;
+  }
+}
+
+void MTLScratchBufferManager::free()
+{
+  initialised_ = false;
+
+  /* Release Scratch buffers */
+  for (int sb = 0; sb < mtl_max_scratch_buffers_; sb++) {
+    delete scratch_buffers_[sb];
+    scratch_buffers_[sb] = nullptr;
+  }
+  current_scratch_buffer_ = 0;
+}
+
+MTLTemporaryBuffer MTLScratchBufferManager::scratch_buffer_allocate_range(uint64_t alloc_size)
+{
+  return this->scratch_buffer_allocate_range_aligned(alloc_size, 1);
+}
+
+MTLTemporaryBuffer MTLScratchBufferManager::scratch_buffer_allocate_range_aligned(
+    uint64_t alloc_size, uint alignment)
+{
+  /* Ensure scratch buffer allocation alignment adheres to offset alignment requirements. */
+  alignment = max_uu(alignment, 256);
+
+  BLI_assert(current_scratch_buffer_ >= 0 && "Scratch Buffer index not set");
+  MTLCircularBuffer *current_scratch_buff = this->scratch_buffers_[current_scratch_buffer_];
+  BLI_assert(current_scratch_buff != nullptr && "Scratch Buffer does not exist");
+  MTLTemporaryBuffer allocated_range = current_scratch_buff->allocate_range_aligned(alloc_size,
+                                                                                    alignment);
+  BLI_assert(allocated_range.size >= alloc_size && allocated_range.size <= alloc_size + alignment);
+  BLI_assert(allocated_range.metal_buffer != nil);
+  return allocated_range;
+}
+
+void MTLScratchBufferManager::ensure_increment_scratch_buffer()
+{
+  /* Fetch active scratch buffer. */
+  MTLCircularBuffer *active_scratch_buf = scratch_buffers_[current_scratch_buffer_];
+  BLI_assert(&active_scratch_buf->own_context_ == &context_);
+
+  /* Ensure existing scratch buffer is no longer in use. MTL_MAX_SCRATCH_BUFFERS specifies
+   * the number of allocated scratch buffers. This value should be equal to the number of
+   * simultaneous frames in-flight. I.e. the maximal number of scratch buffers which are
+   * simultaneously in-use. */
+  if (active_scratch_buf->used_frame_index_ < context_.get_current_frame_index()) {
+    current_scratch_buffer_ = (current_scratch_buffer_ + 1) % mtl_max_scratch_buffers_;
+    active_scratch_buf = scratch_buffers_[current_scratch_buffer_];
+    active_scratch_buf->reset();
+    BLI_assert(&active_scratch_buf->own_context_ == &context_);
+    MTL_LOG_INFO("Scratch buffer %d reset - (ctx %p)(Frame index: %d)\n",
+                 current_scratch_buffer_,
+                 &context_,
+                 context_.get_current_frame_index());
+  }
+}
+
+void MTLScratchBufferManager::flush_active_scratch_buffer()
+{
+  /* Fetch active scratch buffer and verify context. */
+  MTLCircularBuffer *active_scratch_buf = scratch_buffers_[current_scratch_buffer_];
+  BLI_assert(&active_scratch_buf->own_context_ == &context_);
+  active_scratch_buf->flush();
+}
+
+/* MTLCircularBuffer implementation. */
+MTLCircularBuffer::MTLCircularBuffer(MTLContext &ctx, uint64_t initial_size, bool allow_grow)
+    : own_context_(ctx)
+{
+  BLI_assert(this);
+  MTLResourceOptions options = ([own_context_.device hasUnifiedMemory]) ?
+                                   MTLResourceStorageModeShared :
+                                   MTLResourceStorageModeManaged;
+  cbuffer_ = new gpu::MTLBuffer(own_context_.device, initial_size, options, 256);
+  current_offset_ = 0;
+  can_resize_ = allow_grow;
+  cbuffer_->flag_in_use(true);
+
+  used_frame_index_ = ctx.get_current_frame_index();
+  last_flush_base_offset_ = 0;
+
+  /* Debug label. */
+  if (G.debug & G_DEBUG_GPU) {
+    cbuffer_->set_label(@"Circular Scratch Buffer");
+  }
+}
+
+MTLCircularBuffer::~MTLCircularBuffer()
+{
+  delete cbuffer_;
+}
+
+MTLTemporaryBuffer MTLCircularBuffer::allocate_range(uint64_t alloc_size)
+{
+  return this->allocate_range_aligned(alloc_size, 1);
+}
+
+MTLTemporaryBuffer MTLCircularBuffer::allocate_range_aligned(uint64_t alloc_size, uint alignment)
+{
+  BLI_assert(this);
+
+  /* Ensure alignment of an allocation is aligned to compatible offset boundaries. */
+  BLI_assert(alignment > 0);
+  alignment = max_ulul(alignment, 256);
+
+  /* Align current offset and allocation size to desired alignment */
+  uint64_t aligned_current_offset = ceil_to_multiple_ul(current_offset_, alignment);
+  uint64_t aligned_alloc_size = ceil_to_multiple_ul(alloc_size, alignment);
+  bool can_allocate = (aligned_current_offset + aligned_alloc_size) < cbuffer_->get_size();
+
+  BLI_assert(aligned_current_offset >= current_offset_);
+  BLI_assert(aligned_alloc_size >= alloc_size);
+
+  BLI_assert(aligned_current_offset % alignment == 0);
+  BLI_assert(aligned_alloc_size % alignment == 0);
+
+  /* Recreate Buffer */
+  if (!can_allocate) {
+    uint64_t new_size = cbuffer_->get_size();
+    if (can_resize_) {
+      /* Resize to the maximum of basic resize heuristic OR the size of the current offset +
+       * requested allocation -- we want the buffer to grow to a large enough size such that it
+       * does not need to resize mid-frame. */
+      new_size = max_ulul(
+          min_ulul(MTLScratchBufferManager::mtl_scratch_buffer_max_size_, new_size * 1.2),
+          aligned_current_offset + aligned_alloc_size);
+
+#if MTL_SCRATCH_BUFFER_ALLOW_TEMPORARY_EXPANSION == 1
+      /* IF a requested allocation EXCEEDS the maximum supported size, temporarily allocate up to
+       * this, but shrink down ASAP. */
+      if (new_size > MTLScratchBufferManager::mtl_scratch_buffer_max_size_) {
+
+        /* If new requested allocation is bigger than maximum allowed size, temporarily resize to
+         * maximum allocation size -- Otherwise, clamp the buffer size back down to the defined
+         * maximum */
+        if (aligned_alloc_size > MTLScratchBufferManager::mtl_scratch_buffer_max_size_) {
+          new_size = aligned_alloc_size;
+          MTL_LOG_INFO("Temporarily growing Scratch buffer to %d MB\n",
+                       (int)new_size / 1024 / 1024);
+        }
+        else {
+          new_size = MTLScratchBufferManager::mtl_scratch_buffer_max_size_;
+          MTL_LOG_INFO("Shrinking Scratch buffer back to %d MB\n", (int)new_size / 1024 / 1024);
+        }
+      }
+      BLI_assert(aligned_alloc_size <= new_size);
+#else
+      new_size = min_ulul(MTLScratchBufferManager::mtl_scratch_buffer_max_size_, new_size);
+
+      if (aligned_alloc_size > new_size) {
+        BLI_assert(false);
+
+        /* Cannot allocate */
+        MTLTemporaryBuffer alloc_range;
+        alloc_range.metal_buffer = nil;
+        alloc_range.data = nullptr;
+        alloc_range.buffer_offset = 0;
+        alloc_range.size = 0;
+        alloc_range.options = cbuffer_->options;
+      }
+#endif
+    }
+    else {
+      MTL_LOG_WARNING(
+          "Performance Warning: Reached the end of circular buffer of size: %llu, but cannot "
+          "resize. Starting new buffer\n",
+          cbuffer_->get_size());
+      BLI_assert(aligned_alloc_size <= new_size);
+
+      /* Cannot allocate. */
+      MTLTemporaryBuffer alloc_range;
+      alloc_range.metal_buffer = nil;
+      alloc_range.data = nullptr;
+      alloc_range.buffer_offset = 0;
+      alloc_range.size = 0;
+      alloc_range.options = cbuffer_->get_resource_options();
+    }
+
+    /* Flush current buffer to ensure changes are visible on the GPU. */
+    this->flush();
+
+    /* Discard old buffer and create a new one - Relying on Metal reference counting to track
+     * in-use buffers */
+    MTLResourceOptions prev_options = cbuffer_->get_resource_options();
+    uint prev_alignment = cbuffer_->get_alignment();
+    delete cbuffer_;
+    cbuffer_ = new gpu::MTLBuffer(own_context_.device, new_size, prev_options, prev_alignment);
+    cbuffer_->flag_in_use(true);
+    current_offset_ = 0;
+    last_flush_base_offset_ = 0;
+
+    /* Debug label. */
+    if (G.debug & G_DEBUG_GPU) {
+      cbuffer_->set_label(@"Circular Scratch Buffer");
+    }
+    MTL_LOG_INFO("Resized Metal circular buffer to %llu bytes\n", new_size);
+
+    /* Reset allocation Status. */
+    aligned_current_offset = 0;
+    BLI_assert((aligned_current_offset + aligned_alloc_size) <= cbuffer_->get_size());
+  }
+
+  /* Allocate chunk. */
+  MTLTemporaryBuffer alloc_range;
+  alloc_range.metal_buffer = cbuffer_->get_metal_buffer();
+  alloc_range.data = (void *)((uint8_t *)([alloc_range.metal_buffer contents]) +
+                              aligned_current_offset);
+  alloc_range.buffer_offset = aligned_current_offset;
+  alloc_range.size = aligned_alloc_size;
+  alloc_range.options = cbuffer_->get_resource_options();
+  BLI_assert(alloc_range.data);
+
+  /* Shift offset to match alignment. */
+  current_offset_ = aligned_current_offset + aligned_alloc_size;
+  BLI_assert(current_offset_ <= cbuffer_->get_size());
+  return alloc_range;
+}
+
+void MTLCircularBuffer::flush()
+{
+  BLI_assert(this);
+
+  uint64_t len = current_offset_ - last_flush_base_offset_;
+  if (len > 0) {
+    cbuffer_->flush_range(last_flush_base_offset_, len);
+    last_flush_base_offset_ = current_offset_;
+  }
+}
+
+void MTLCircularBuffer::reset()
+{
+  BLI_assert(this);
+
+  /* If circular buffer has data written to it, offset will be greater than zero. */
+  if (current_offset_ > 0) {
+
+    /* Ensure the circular buffer is no longer being used by an in-flight frame. */
+    BLI_assert((own_context_.get_current_frame_index() >=
+                (used_frame_index_ + MTL_NUM_SAFE_FRAMES - 1)) &&
+               "Trying to reset Circular scratch buffer's while its data is still being used by "
+               "an in-flight frame");
+
+    current_offset_ = 0;
+    last_flush_base_offset_ = 0;
+  }
+
+  /* Update used frame index to current. */
+  used_frame_index_ = own_context_.get_current_frame_index();
+}
+
+/** \} */
+
+}  // blender::gpu
diff --git a/source/blender/gpu/metal/mtl_state.hh b/source/blender/gpu/metal/mtl_state.hh
index 23bf8600ddd..ddb27a444d4 100644
--- a/source/blender/gpu/metal/mtl_state.hh
+++ b/source/blender/gpu/metal/mtl_state.hh
@@ -30,18 +30,18 @@ class MTLStateManager : public StateManager {
  public:
   MTLStateManager(MTLContext *ctx);
 
-  void apply_state(void) override;
-  void force_state(void) override;
+  void apply_state() override;
+  void force_state() override;
 
   void issue_barrier(eGPUBarrier barrier_bits) override;
 
   void texture_bind(Texture *tex, eGPUSamplerState sampler, int unit) override;
   void texture_unbind(Texture *tex) override;
-  void texture_unbind_all(void) override;
+  void texture_unbind_all() override;
 
   void image_bind(Texture *tex, int unit) override;
   void image_unbind(Texture *tex) override;
-  void image_unbind_all(void) override;
+  void image_unbind_all() override;
 
   void texture_unpack_row_length_set(uint len) override;
 
diff --git a/source/blender/gpu/metal/mtl_state.mm b/source/blender/gpu/metal/mtl_state.mm
index cf7fbdba6b9..59c258a0d12 100644
--- a/source/blender/gpu/metal/mtl_state.mm
+++ b/source/blender/gpu/metal/mtl_state.mm
@@ -17,7 +17,7 @@ namespace blender::gpu {
 /** \name MTLStateManager
  * \{ */
 
-void MTLStateManager::mtl_state_init(void)
+void MTLStateManager::mtl_state_init()
 {
   BLI_assert(context_);
   context_->pipeline_state_init();
@@ -36,7 +36,7 @@ MTLStateManager::MTLStateManager(MTLContext *ctx) : StateManager()
   set_mutable_state(mutable_state);
 }
 
-void MTLStateManager::apply_state(void)
+void MTLStateManager::apply_state()
 {
   this->set_state(this->state);
   this->set_mutable_state(this->mutable_state);
@@ -45,7 +45,7 @@ void MTLStateManager::apply_state(void)
   static_cast(context_->active_fb)->apply_state();
 };
 
-void MTLStateManager::force_state(void)
+void MTLStateManager::force_state()
 {
   /* Little exception for clip distances since they need to keep the old count correct. */
   uint32_t clip_distances = current_.clip_distances;
@@ -548,7 +548,7 @@ void MTLStateManager::issue_barrier(eGPUBarrier barrier_bits)
 
   /* Apple Silicon does not support memory barriers.
    * We do not currently need these due to implicit API guarantees.
-   * Note(Metal): MTLFence/MTLEvent may be required to synchronize work if
+   * NOTE(Metal): MTLFence/MTLEvent may be required to synchronize work if
    * untracked resources are ever used. */
   if ([ctx->device hasUnifiedMemory]) {
     return;
@@ -600,7 +600,7 @@ void MTLStateManager::texture_unbind(Texture *tex_)
   ctx->texture_unbind(mtl_tex);
 }
 
-void MTLStateManager::texture_unbind_all(void)
+void MTLStateManager::texture_unbind_all()
 {
   MTLContext *ctx = static_cast(unwrap(GPU_context_active_get()));
   BLI_assert(ctx);
@@ -623,7 +623,7 @@ void MTLStateManager::image_unbind(Texture *tex_)
   this->texture_unbind(tex_);
 }
 
-void MTLStateManager::image_unbind_all(void)
+void MTLStateManager::image_unbind_all()
 {
   this->texture_unbind_all();
 }
diff --git a/source/blender/gpu/metal/mtl_texture.hh b/source/blender/gpu/metal/mtl_texture.hh
index 0f908995a93..9387d5af814 100644
--- a/source/blender/gpu/metal/mtl_texture.hh
+++ b/source/blender/gpu/metal/mtl_texture.hh
@@ -237,7 +237,7 @@ class MTLTexture : public Texture {
   void update_sub(
       int mip, int offset[3], int extent[3], eGPUDataFormat type, const void *data) override;
 
-  void generate_mipmap(void) override;
+  void generate_mipmap() override;
   void copy_to(Texture *dst) override;
   void clear(eGPUDataFormat format, const void *data) override;
   void swizzle_set(const char swizzle_mask[4]) override;
@@ -248,7 +248,7 @@ class MTLTexture : public Texture {
   void *read(int mip, eGPUDataFormat type) override;
 
   /* Remove once no longer required -- will just return 0 for now in MTL path*/
-  uint gl_bindcode_get(void) const override;
+  uint gl_bindcode_get() const override;
 
   bool texture_is_baked();
   const char *get_name()
@@ -257,7 +257,7 @@ class MTLTexture : public Texture {
   }
 
  protected:
-  bool init_internal(void) override;
+  bool init_internal() override;
   bool init_internal(GPUVertBuf *vbo) override;
   bool init_internal(const GPUTexture *src,
                      int mip_offset,
diff --git a/source/blender/gpu/metal/mtl_texture.mm b/source/blender/gpu/metal/mtl_texture.mm
index ff2c2fce235..0cb38a3a2b7 100644
--- a/source/blender/gpu/metal/mtl_texture.mm
+++ b/source/blender/gpu/metal/mtl_texture.mm
@@ -478,23 +478,6 @@ void gpu::MTLTexture::update_sub(
     MTLPixelFormat destination_format = gpu_texture_format_to_metal(format_);
     int expected_dst_bytes_per_pixel = get_mtl_format_bytesize(destination_format);
     int destination_num_channels = get_mtl_format_num_components(destination_format);
-    int destination_totalsize = 0;
-    switch (this->dimensions_count()) {
-      case 1:
-        destination_totalsize = expected_dst_bytes_per_pixel * max_ii(expected_update_w, 1);
-        break;
-      case 2:
-        destination_totalsize = expected_dst_bytes_per_pixel * max_ii(expected_update_w, 1) *
-                                max_ii(extent[1], 1);
-        break;
-      case 3:
-        destination_totalsize = expected_dst_bytes_per_pixel * max_ii(expected_update_w, 1) *
-                                max_ii(extent[1], 1) * max_ii(extent[2], 1);
-        break;
-      default:
-        BLI_assert(false);
-        break;
-    }
 
     /* Prepare specialisation struct (For texture update routine). */
     TextureUpdateRoutineSpecialisation compute_specialisation_kernel = {
@@ -568,12 +551,12 @@ void gpu::MTLTexture::update_sub(
 
     /* Prepare staging buffer for data. */
     id staging_buffer = nil;
-    unsigned long long staging_buffer_offset = 0;
+    uint64_t staging_buffer_offset = 0;
 
     /* Fetch allocation from scratch buffer. */
-    MTLTemporaryBufferRange allocation; /* TODO(Metal): Metal Memory manager. */
-    /* = ctx->get_memory_manager().scratch_buffer_allocate_range_aligned(totalsize, 256);*/
-    memcpy(allocation.host_ptr, data, totalsize);
+    MTLTemporaryBuffer allocation =
+        ctx->get_scratchbuffer_manager().scratch_buffer_allocate_range_aligned(totalsize, 256);
+    memcpy(allocation.data, data, totalsize);
     staging_buffer = allocation.metal_buffer;
     staging_buffer_offset = allocation.buffer_offset;
 
@@ -915,7 +898,7 @@ void gpu::MTLTexture::ensure_mipmaps(int miplvl)
   this->mip_range_set(0, mipmaps_);
 }
 
-void gpu::MTLTexture::generate_mipmap(void)
+void gpu::MTLTexture::generate_mipmap()
 {
   /* Fetch Active Context. */
   MTLContext *ctx = reinterpret_cast(GPU_context_active_get());
@@ -1230,7 +1213,7 @@ void gpu::MTLTexture::read_internal(int mip,
   destination_buffer = [ctx->device newBufferWithLength:max_ii(total_bytes, 256)
                                                 options:bufferOptions];
   destination_offset = 0;
-  destination_buffer_host_ptr = (void *)((unsigned char *)([destination_buffer contents]) +
+  destination_buffer_host_ptr = (void *)((uint8_t *)([destination_buffer contents]) +
                                          destination_offset);
 
   /* Prepare specialisation struct (For non-trivial texture read routine). */
@@ -1444,12 +1427,12 @@ void gpu::MTLTexture::read_internal(int mip,
 }
 
 /* Remove once no longer required -- will just return 0 for now in MTL path. */
-uint gpu::MTLTexture::gl_bindcode_get(void) const
+uint gpu::MTLTexture::gl_bindcode_get() const
 {
   return 0;
 }
 
-bool gpu::MTLTexture::init_internal(void)
+bool gpu::MTLTexture::init_internal()
 {
   if (format_ == GPU_DEPTH24_STENCIL8) {
     /* Apple Silicon requires GPU_DEPTH32F_STENCIL8 instead of GPU_DEPTH24_STENCIL8. */
-- 
cgit v1.2.3


From e7a21275c0f1bfb0e8bbeda384e5b46131e48b93 Mon Sep 17 00:00:00 2001
From: Aras Pranckevicius 
Date: Fri, 1 Jul 2022 12:17:50 +0300
Subject: IO: print import & export times of Alembic & USD
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Many existing importers/exporters do log the time it takes to system
console (some others log more information too). In particular, OBJ
(C++ & python), STL (C++ & python), PLY, glTF2 all log the time it
takes. However, neither USD nor Alembic do. And also it's harder to
know the time it takes there from a profiler, since all the work
normally is done on a background job and is split between several
threads (so you can't just find some top-level function and see how
much time it took).

This change:

- Adds import/export time logging to USD & Alembic importer/exporter,
- In the time utility class (also used by OBJ & STL), improve the
  output formatting: 1) print only one decimal digit, 2) for long
  times, print seconds and also produce a hours:minutes:seconds form.

Reviewed By: Michael Kowalski, Kévin Dietrich
Differential Revision: https://developer.blender.org/D15170
---
 source/blender/blenlib/intern/timeit.cc               | 18 ++++++++++++++----
 source/blender/io/alembic/exporter/abc_export_capi.cc | 12 ++++++++++++
 source/blender/io/alembic/intern/alembic_capi.cc      | 12 ++++++++++++
 source/blender/io/usd/intern/usd_capi_export.cc       | 12 ++++++++++++
 source/blender/io/usd/intern/usd_capi_import.cc       | 12 ++++++++++++
 5 files changed, 62 insertions(+), 4 deletions(-)

diff --git a/source/blender/blenlib/intern/timeit.cc b/source/blender/blenlib/intern/timeit.cc
index f11f9c4ad94..7a8cf8da038 100644
--- a/source/blender/blenlib/intern/timeit.cc
+++ b/source/blender/blenlib/intern/timeit.cc
@@ -3,19 +3,29 @@
 #include "BLI_timeit.hh"
 
 #include 
+#include 
 
 namespace blender::timeit {
 
 void print_duration(Nanoseconds duration)
 {
-  if (duration < std::chrono::microseconds(100)) {
+  using namespace std::chrono;
+  if (duration < microseconds(100)) {
     std::cout << duration.count() << " ns";
   }
-  else if (duration < std::chrono::seconds(5)) {
-    std::cout << duration.count() / 1.0e6 << " ms";
+  else if (duration < seconds(5)) {
+    std::cout << std::fixed << std::setprecision(1) << duration.count() / 1.0e6 << " ms";
+  }
+  else if (duration > seconds(90)) {
+    /* Long durations: print seconds, and also H:m:s */
+    const auto dur_hours = duration_cast(duration);
+    const auto dur_mins = duration_cast(duration - dur_hours);
+    const auto dur_sec = duration_cast(duration - dur_hours - dur_mins);
+    std::cout << std::fixed << std::setprecision(1) << duration.count() / 1.0e9 << " s ("
+              << dur_hours.count() << "H:" << dur_mins.count() << "m:" << dur_sec.count() << "s)";
   }
   else {
-    std::cout << duration.count() / 1.0e9 << " s";
+    std::cout << std::fixed << std::setprecision(1) << duration.count() / 1.0e9 << " s";
   }
 }
 
diff --git a/source/blender/io/alembic/exporter/abc_export_capi.cc b/source/blender/io/alembic/exporter/abc_export_capi.cc
index 5554fb505a4..dfca89e2c6d 100644
--- a/source/blender/io/alembic/exporter/abc_export_capi.cc
+++ b/source/blender/io/alembic/exporter/abc_export_capi.cc
@@ -24,6 +24,7 @@
 #include "BLI_fileops.h"
 #include "BLI_path_util.h"
 #include "BLI_string.h"
+#include "BLI_timeit.hh"
 
 #include "WM_api.h"
 #include "WM_types.h"
@@ -44,6 +45,7 @@ struct ExportJobData {
 
   bool was_canceled;
   bool export_ok;
+  blender::timeit::TimePoint start_time;
 };
 
 namespace blender::io::alembic {
@@ -59,6 +61,14 @@ static void build_depsgraph(Depsgraph *depsgraph, const bool visible_objects_onl
   }
 }
 
+static void report_job_duration(const ExportJobData *data)
+{
+  blender::timeit::Nanoseconds duration = blender::timeit::Clock::now() - data->start_time;
+  std::cout << "Alembic export of '" << data->filename << "' took ";
+  blender::timeit::print_duration(duration);
+  std::cout << '\n';
+}
+
 static void export_startjob(void *customdata,
                             /* Cannot be const, this function implements wm_jobs_start_callback.
                              * NOLINTNEXTLINE: readability-non-const-parameter. */
@@ -68,6 +78,7 @@ static void export_startjob(void *customdata,
 {
   ExportJobData *data = static_cast(customdata);
   data->was_canceled = false;
+  data->start_time = blender::timeit::Clock::now();
 
   G.is_rendering = true;
   WM_set_locked_interface(data->wm, true);
@@ -177,6 +188,7 @@ static void export_endjob(void *customdata)
 
   G.is_rendering = false;
   WM_set_locked_interface(data->wm, false);
+  report_job_duration(data);
 }
 
 }  // namespace blender::io::alembic
diff --git a/source/blender/io/alembic/intern/alembic_capi.cc b/source/blender/io/alembic/intern/alembic_capi.cc
index cd6750341a8..27df23b38c6 100644
--- a/source/blender/io/alembic/intern/alembic_capi.cc
+++ b/source/blender/io/alembic/intern/alembic_capi.cc
@@ -50,6 +50,7 @@
 #include "BLI_math.h"
 #include "BLI_path_util.h"
 #include "BLI_string.h"
+#include "BLI_timeit.hh"
 
 #include "WM_api.h"
 #include "WM_types.h"
@@ -434,8 +435,17 @@ struct ImportJobData {
   bool was_cancelled;
   bool import_ok;
   bool is_background_job;
+  blender::timeit::TimePoint start_time;
 };
 
+static void report_job_duration(const ImportJobData *data)
+{
+  blender::timeit::Nanoseconds duration = blender::timeit::Clock::now() - data->start_time;
+  std::cout << "Alembic import of '" << data->filename << "' took ";
+  blender::timeit::print_duration(duration);
+  std::cout << '\n';
+}
+
 static void import_startjob(void *user_data, short *stop, short *do_update, float *progress)
 {
   SCOPE_TIMER("Alembic import, objects reading and creation");
@@ -445,6 +455,7 @@ static void import_startjob(void *user_data, short *stop, short *do_update, floa
   data->stop = stop;
   data->do_update = do_update;
   data->progress = progress;
+  data->start_time = blender::timeit::Clock::now();
 
   WM_set_locked_interface(data->wm, true);
 
@@ -649,6 +660,7 @@ static void import_endjob(void *user_data)
   }
 
   WM_main_add_notifier(NC_SCENE | ND_FRAME, data->scene);
+  report_job_duration(data);
 }
 
 static void import_freejob(void *user_data)
diff --git a/source/blender/io/usd/intern/usd_capi_export.cc b/source/blender/io/usd/intern/usd_capi_export.cc
index d9995117b70..1033f85181c 100644
--- a/source/blender/io/usd/intern/usd_capi_export.cc
+++ b/source/blender/io/usd/intern/usd_capi_export.cc
@@ -27,6 +27,7 @@
 #include "BLI_fileops.h"
 #include "BLI_path_util.h"
 #include "BLI_string.h"
+#include "BLI_timeit.hh"
 
 #include "WM_api.h"
 #include "WM_types.h"
@@ -42,8 +43,17 @@ struct ExportJobData {
   USDExportParams params;
 
   bool export_ok;
+  timeit::TimePoint start_time;
 };
 
+static void report_job_duration(const ExportJobData *data)
+{
+  timeit::Nanoseconds duration = timeit::Clock::now() - data->start_time;
+  std::cout << "USD export of '" << data->filepath << "' took ";
+  timeit::print_duration(duration);
+  std::cout << '\n';
+}
+
 static void export_startjob(void *customdata,
                             /* Cannot be const, this function implements wm_jobs_start_callback.
                              * NOLINTNEXTLINE: readability-non-const-parameter. */
@@ -53,6 +63,7 @@ static void export_startjob(void *customdata,
 {
   ExportJobData *data = static_cast(customdata);
   data->export_ok = false;
+  data->start_time = timeit::Clock::now();
 
   G.is_rendering = true;
   WM_set_locked_interface(data->wm, true);
@@ -151,6 +162,7 @@ static void export_endjob(void *customdata)
 
   G.is_rendering = false;
   WM_set_locked_interface(data->wm, false);
+  report_job_duration(data);
 }
 
 }  // namespace blender::io::usd
diff --git a/source/blender/io/usd/intern/usd_capi_import.cc b/source/blender/io/usd/intern/usd_capi_import.cc
index 4118205d87f..13ae6f4d4c0 100644
--- a/source/blender/io/usd/intern/usd_capi_import.cc
+++ b/source/blender/io/usd/intern/usd_capi_import.cc
@@ -30,6 +30,7 @@
 #include "BLI_math_rotation.h"
 #include "BLI_path_util.h"
 #include "BLI_string.h"
+#include "BLI_timeit.hh"
 
 #include "DEG_depsgraph.h"
 #include "DEG_depsgraph_build.h"
@@ -132,8 +133,17 @@ struct ImportJobData {
   char error_code;
   bool was_canceled;
   bool import_ok;
+  timeit::TimePoint start_time;
 };
 
+static void report_job_duration(const ImportJobData *data)
+{
+  timeit::Nanoseconds duration = timeit::Clock::now() - data->start_time;
+  std::cout << "USD import of '" << data->filepath << "' took ";
+  timeit::print_duration(duration);
+  std::cout << '\n';
+}
+
 static void import_startjob(void *customdata, short *stop, short *do_update, float *progress)
 {
   ImportJobData *data = static_cast(customdata);
@@ -143,6 +153,7 @@ static void import_startjob(void *customdata, short *stop, short *do_update, flo
   data->progress = progress;
   data->was_canceled = false;
   data->archive = nullptr;
+  data->start_time = timeit::Clock::now();
 
   WM_set_locked_interface(data->wm, true);
   G.is_break = false;
@@ -337,6 +348,7 @@ static void import_endjob(void *customdata)
   }
 
   WM_main_add_notifier(NC_SCENE | ND_FRAME, data->scene);
+  report_job_duration(data);
 }
 
 static void import_freejob(void *user_data)
-- 
cgit v1.2.3


From 41c10ac84ae420274dd79227dc698c8be14d13dd Mon Sep 17 00:00:00 2001
From: Xavier Hallade 
Date: Fri, 1 Jul 2022 10:36:00 +0200
Subject: Cycles: fix support for multiple Intel GPUs

Identical Intel GPUs ended up with the same id.
Added PCI BDF to the id to make it unique.
---
 intern/cycles/device/oneapi/device.cpp        | 4 ++++
 intern/cycles/kernel/device/oneapi/kernel.cpp | 3 +++
 2 files changed, 7 insertions(+)

diff --git a/intern/cycles/device/oneapi/device.cpp b/intern/cycles/device/oneapi/device.cpp
index f70425b32cf..8056c204188 100644
--- a/intern/cycles/device/oneapi/device.cpp
+++ b/intern/cycles/device/oneapi/device.cpp
@@ -89,6 +89,9 @@ bool device_oneapi_init()
   if (getenv("SYCL_DEVICE_FILTER") == nullptr) {
     _putenv_s("SYCL_DEVICE_FILTER", "host,level_zero");
   }
+  if (getenv("SYCL_ENABLE_PCI") == nullptr) {
+    _putenv_s("SYCL_ENABLE_PCI", "1");
+  }
   if (getenv("SYCL_PI_LEVEL_ZERO_USE_COPY_ENGINE_FOR_IN_ORDER_QUEUE") == nullptr) {
     _putenv_s("SYCL_PI_LEVEL_ZERO_USE_COPY_ENGINE_FOR_IN_ORDER_QUEUE", "0");
   }
@@ -96,6 +99,7 @@ bool device_oneapi_init()
   setenv("SYCL_CACHE_PERSISTENT", "1", false);
   setenv("SYCL_CACHE_THRESHOLD", "0", false);
   setenv("SYCL_DEVICE_FILTER", "host,level_zero", false);
+  setenv("SYCL_ENABLE_PCI", "1", false);
   setenv("SYCL_PI_LEVEL_ZERO_USE_COPY_ENGINE_FOR_IN_ORDER_QUEUE", "0", false);
 #  endif
 
diff --git a/intern/cycles/kernel/device/oneapi/kernel.cpp b/intern/cycles/kernel/device/oneapi/kernel.cpp
index 2b915027bda..11a551e822e 100644
--- a/intern/cycles/kernel/device/oneapi/kernel.cpp
+++ b/intern/cycles/kernel/device/oneapi/kernel.cpp
@@ -862,6 +862,9 @@ void oneapi_iterate_devices(OneAPIDeviceIteratorCallback cb, void *user_ptr)
         device.get_platform().get_info();
     std::string name = device.get_info();
     std::string id = "ONEAPI_" + platform_name + "_" + name;
+    if (device.has(sycl::aspect::ext_intel_pci_address)) {
+      id.append("_" + device.get_info());
+    }
     (cb)(id.c_str(), name.c_str(), num, user_ptr);
     num++;
   }
-- 
cgit v1.2.3


From 5e5fe217caaed35d6a39744d4f22b38793beae8f Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Fri, 1 Jul 2022 20:35:11 +1000
Subject: Cleanup: rename internal cursor grabbing function

This function was named as if it was part of GHOST's API but was
in fact an internal utility.
---
 intern/ghost/intern/GHOST_SystemWayland.cpp | 200 ++++++++++++++--------------
 intern/ghost/intern/GHOST_SystemWayland.h   |  10 +-
 intern/ghost/intern/GHOST_WindowWayland.cpp |   5 +-
 3 files changed, 109 insertions(+), 106 deletions(-)

diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp
index 09784c67fda..6a419fc718d 100644
--- a/intern/ghost/intern/GHOST_SystemWayland.cpp
+++ b/intern/ghost/intern/GHOST_SystemWayland.cpp
@@ -3281,10 +3281,106 @@ static input_grab_state_t input_grab_state_from_mode(const GHOST_TGrabCursorMode
   return grab_state;
 }
 
-GHOST_TSuccess GHOST_SystemWayland::setCursorGrab(const GHOST_TGrabCursorMode mode,
-                                                  const GHOST_TGrabCursorMode mode_current,
-                                                  int32_t init_grab_xy[2],
-                                                  wl_surface *surface)
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Public WAYLAND Direct Data Access
+ *
+ * Expose some members via methods.
+ * \{ */
+
+wl_display *GHOST_SystemWayland::display()
+{
+  return d->display;
+}
+
+wl_compositor *GHOST_SystemWayland::compositor()
+{
+  return d->compositor;
+}
+
+#ifdef WITH_GHOST_WAYLAND_LIBDECOR
+
+libdecor *GHOST_SystemWayland::decor_context()
+{
+  return d->decor_context;
+}
+
+#else /* WITH_GHOST_WAYLAND_LIBDECOR */
+
+xdg_wm_base *GHOST_SystemWayland::xdg_shell()
+{
+  return d->xdg_shell;
+}
+
+zxdg_decoration_manager_v1 *GHOST_SystemWayland::xdg_decoration_manager()
+{
+  return d->xdg_decoration_manager;
+}
+
+#endif /* !WITH_GHOST_WAYLAND_LIBDECOR */
+
+const std::vector &GHOST_SystemWayland::outputs() const
+{
+  return d->outputs;
+}
+
+wl_shm *GHOST_SystemWayland::shm() const
+{
+  return d->shm;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Public WAYLAND Query Access
+ * \{ */
+
+output_t *GHOST_SystemWayland::output_find_by_wl(const struct wl_output *output) const
+{
+  for (output_t *reg_output : this->outputs()) {
+    if (reg_output->wl_output == output) {
+      return reg_output;
+    }
+  }
+  return nullptr;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Public WAYLAND Utility Functions
+ *
+ * Functionality only used for the WAYLAND implementation.
+ * \{ */
+
+void GHOST_SystemWayland::selection_set(const std::string &selection)
+{
+  this->selection = selection;
+}
+
+void GHOST_SystemWayland::window_surface_unref(const wl_surface *surface)
+{
+#define SURFACE_CLEAR_PTR(surface_test) \
+  if (surface_test == surface) { \
+    surface_test = nullptr; \
+  } \
+  ((void)0);
+
+  /* Only clear window surfaces (not cursors, off-screen surfaces etc). */
+  for (input_t *input : d->inputs) {
+    SURFACE_CLEAR_PTR(input->pointer.wl_surface);
+    SURFACE_CLEAR_PTR(input->tablet.wl_surface);
+    SURFACE_CLEAR_PTR(input->keyboard.wl_surface);
+    SURFACE_CLEAR_PTR(input->focus_dnd);
+  }
+#undef SURFACE_CLEAR_PTR
+}
+
+bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mode,
+                                                 const GHOST_TGrabCursorMode mode_current,
+                                                 int32_t init_grab_xy[2],
+                                                 wl_surface *surface)
 {
   /* Ignore, if the required protocols are not supported. */
   if (!d->relative_pointer_manager || !d->pointer_constraints) {
@@ -3461,99 +3557,3 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorGrab(const GHOST_TGrabCursorMode mo
 }
 
 /** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Public WAYLAND Direct Data Access
- *
- * Expose some members via methods.
- * \{ */
-
-wl_display *GHOST_SystemWayland::display()
-{
-  return d->display;
-}
-
-wl_compositor *GHOST_SystemWayland::compositor()
-{
-  return d->compositor;
-}
-
-#ifdef WITH_GHOST_WAYLAND_LIBDECOR
-
-libdecor *GHOST_SystemWayland::decor_context()
-{
-  return d->decor_context;
-}
-
-#else /* WITH_GHOST_WAYLAND_LIBDECOR */
-
-xdg_wm_base *GHOST_SystemWayland::xdg_shell()
-{
-  return d->xdg_shell;
-}
-
-zxdg_decoration_manager_v1 *GHOST_SystemWayland::xdg_decoration_manager()
-{
-  return d->xdg_decoration_manager;
-}
-
-#endif /* !WITH_GHOST_WAYLAND_LIBDECOR */
-
-const std::vector &GHOST_SystemWayland::outputs() const
-{
-  return d->outputs;
-}
-
-wl_shm *GHOST_SystemWayland::shm() const
-{
-  return d->shm;
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Public WAYLAND Query Access
- * \{ */
-
-output_t *GHOST_SystemWayland::output_find_by_wl(const struct wl_output *output) const
-{
-  for (output_t *reg_output : this->outputs()) {
-    if (reg_output->wl_output == output) {
-      return reg_output;
-    }
-  }
-  return nullptr;
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Public WAYLAND Utility Functions
- *
- * Functionality only used for the WAYLAND implementation.
- * \{ */
-
-void GHOST_SystemWayland::selection_set(const std::string &selection)
-{
-  this->selection = selection;
-}
-
-void GHOST_SystemWayland::window_surface_unref(const wl_surface *surface)
-{
-#define SURFACE_CLEAR_PTR(surface_test) \
-  if (surface_test == surface) { \
-    surface_test = nullptr; \
-  } \
-  ((void)0);
-
-  /* Only clear window surfaces (not cursors, off-screen surfaces etc). */
-  for (input_t *input : d->inputs) {
-    SURFACE_CLEAR_PTR(input->pointer.wl_surface);
-    SURFACE_CLEAR_PTR(input->tablet.wl_surface);
-    SURFACE_CLEAR_PTR(input->keyboard.wl_surface);
-    SURFACE_CLEAR_PTR(input->focus_dnd);
-  }
-#undef SURFACE_CLEAR_PTR
-}
-
-/** \} */
diff --git a/intern/ghost/intern/GHOST_SystemWayland.h b/intern/ghost/intern/GHOST_SystemWayland.h
index 4286aa9d183..cf9ec5d0757 100644
--- a/intern/ghost/intern/GHOST_SystemWayland.h
+++ b/intern/ghost/intern/GHOST_SystemWayland.h
@@ -130,11 +130,6 @@ class GHOST_SystemWayland : public GHOST_System {
 
   bool getCursorGrabUseSoftwareDisplay(const GHOST_TGrabCursorMode mode);
 
-  GHOST_TSuccess setCursorGrab(const GHOST_TGrabCursorMode mode,
-                               const GHOST_TGrabCursorMode mode_current,
-                               int32_t init_grab_xy[2],
-                               wl_surface *surface);
-
   /* WAYLAND direct-data access. */
 
   wl_display *display();
@@ -163,6 +158,11 @@ class GHOST_SystemWayland : public GHOST_System {
   /** Clear all references to this surface to prevent accessing NULL pointers. */
   void window_surface_unref(const wl_surface *surface);
 
+  bool window_cursor_grab_set(const GHOST_TGrabCursorMode mode,
+                              const GHOST_TGrabCursorMode mode_current,
+                              int32_t init_grab_xy[2],
+                              wl_surface *surface);
+
  private:
   struct display_t *d;
   std::string selection;
diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp
index 00d75d62c9f..6cae60fc4b5 100644
--- a/intern/ghost/intern/GHOST_WindowWayland.cpp
+++ b/intern/ghost/intern/GHOST_WindowWayland.cpp
@@ -484,7 +484,10 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system,
 
 GHOST_TSuccess GHOST_WindowWayland::setWindowCursorGrab(GHOST_TGrabCursorMode mode)
 {
-  return m_system->setCursorGrab(mode, m_cursorGrab, m_cursorGrabInitPos, w->wl_surface);
+  if (m_system->window_cursor_grab_set(mode, m_cursorGrab, m_cursorGrabInitPos, w->wl_surface)) {
+    return GHOST_kSuccess;
+  }
+  return GHOST_kFailure;
 }
 
 GHOST_TSuccess GHOST_WindowWayland::setWindowCursorShape(GHOST_TStandardCursor shape)
-- 
cgit v1.2.3


From b683a37824aa2fb22efe22a1dbce667b8155698f Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Fri, 1 Jul 2022 20:52:00 +1000
Subject: Fix T99301: RNA_boolean_get warning when saving a file for the first
 time

Caused by [0], RNA_struct_property_is_set also functioned to check if
the property existed.

[0]: 6a2c42a0d58e0f36cca1cf4ca0c5c98ec3612f6f
---
 source/blender/windowmanager/intern/wm_files.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source/blender/windowmanager/intern/wm_files.c b/source/blender/windowmanager/intern/wm_files.c
index 5f4c39e33f7..a4d5bed21da 100644
--- a/source/blender/windowmanager/intern/wm_files.c
+++ b/source/blender/windowmanager/intern/wm_files.c
@@ -3063,7 +3063,7 @@ static int wm_save_as_mainfile_exec(bContext *C, wmOperator *op)
   Main *bmain = CTX_data_main(C);
   char path[FILE_MAX];
   const bool is_save_as = (op->type->invoke == wm_save_as_mainfile_invoke);
-  const bool use_save_as_copy = RNA_boolean_get(op->ptr, "copy");
+  const bool use_save_as_copy = is_save_as && RNA_boolean_get(op->ptr, "copy");
 
   /* We could expose all options to the users however in most cases remapping
    * existing relative paths is a good default.
-- 
cgit v1.2.3


From 56b218296c228acbe00940d50336a3b6f0df2915 Mon Sep 17 00:00:00 2001
From: Yiming Wu 
Date: Fri, 1 Jul 2022 16:55:15 +0800
Subject: Fix T99268: LineArt better handling for dense overlappings.

Two main fixes:
- Split tiles only when we are more sure that it will improve distribution.
- Discard edges and chains that are not gonna be used afterwards before chaining.

This speeds up the whole process and also eliminates unnecessary tile splitting.

Reviewed By: Sebastian Parborg (zeddb)

Differential Revision: https://developer.blender.org/D15335
---
 .../gpencil_modifiers/intern/lineart/MOD_lineart.h |   4 +-
 .../intern/lineart/lineart_chain.c                 |  11 ++-
 .../gpencil_modifiers/intern/lineart/lineart_cpu.c | 104 ++++++++++++++++-----
 3 files changed, 92 insertions(+), 27 deletions(-)

diff --git a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h
index d7005a4bc61..5dd833fb12b 100644
--- a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h
+++ b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h
@@ -549,7 +549,7 @@ typedef struct LineartBoundingArea {
   uint32_t max_triangle_count;
   uint32_t line_count;
   uint32_t max_line_count;
-  uint32_t user_count;
+  uint32_t insider_triangle_count;
 
   /* Use array for speeding up multiple accesses. */
   struct LineartTriangle **linked_triangles;
@@ -845,7 +845,7 @@ void MOD_lineart_chain_split_for_fixed_occlusion(LineartData *ld);
  * implemented yet.
  */
 void MOD_lineart_chain_connect(LineartData *ld);
-void MOD_lineart_chain_discard_short(LineartData *ld, float threshold);
+void MOD_lineart_chain_discard_unused(LineartData *ld, float threshold, uint8_t max_occlusion);
 void MOD_lineart_chain_clip_at_border(LineartData *ld);
 /**
  * This should always be the last stage!, see the end of
diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c
index 988698771bf..7c8e0c5a6f5 100644
--- a/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c
+++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c
@@ -725,8 +725,9 @@ void MOD_lineart_chain_split_for_fixed_occlusion(LineartData *ld)
       }
     }
   }
-  /* Get rid of those very short "zig-zag" lines that jumps around visibility. */
-  MOD_lineart_chain_discard_short(ld, DBL_EDGE_LIM);
+
+  MOD_lineart_chain_discard_unused(ld, DBL_EDGE_LIM, ld->conf.max_occlusion_level);
+
   LISTBASE_FOREACH (LineartEdgeChain *, iec, &ld->chains) {
     lineart_bounding_area_link_chain(ld, iec);
   }
@@ -1018,12 +1019,14 @@ float MOD_lineart_chain_compute_length(LineartEdgeChain *ec)
   return offset_accum;
 }
 
-void MOD_lineart_chain_discard_short(LineartData *ld, const float threshold)
+void MOD_lineart_chain_discard_unused(LineartData *ld,
+                                      const float threshold,
+                                      uint8_t max_occlusion)
 {
   LineartEdgeChain *ec, *next_ec;
   for (ec = ld->chains.first; ec; ec = next_ec) {
     next_ec = ec->next;
-    if (MOD_lineart_chain_compute_length(ec) < threshold) {
+    if (ec->level > max_occlusion || MOD_lineart_chain_compute_length(ec) < threshold) {
       BLI_remlink(&ld->chains, ec);
     }
   }
diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c
index 236e2df1537..874da3b88c9 100644
--- a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c
+++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c
@@ -494,10 +494,10 @@ static bool lineart_point_inside_triangle(const double v[2],
                                           const double v1[2],
                                           const double v2[2])
 {
-  double cl, c;
+  double cl, c, cl0;
 
   cl = (v0[0] - v[0]) * (v1[1] - v[1]) - (v0[1] - v[1]) * (v1[0] - v[0]);
-  c = cl;
+  c = cl0 = cl;
 
   cl = (v1[0] - v[0]) * (v2[1] - v[1]) - (v1[1] - v[1]) * (v2[0] - v[0]);
   if (c * cl <= 0) {
@@ -513,8 +513,7 @@ static bool lineart_point_inside_triangle(const double v[2],
 
   c = cl;
 
-  cl = (v0[0] - v[0]) * (v1[1] - v[1]) - (v0[1] - v[1]) * (v1[0] - v[0]);
-  if (c * cl <= 0) {
+  if (c * cl0 <= 0) {
     return false;
   }
 
@@ -3972,7 +3971,7 @@ static void lineart_bounding_area_split(LineartData *ld,
     BLI_spin_init(&ba[i].lock);
   }
 
-  for (int i = 0; i < root->triangle_count; i++) {
+  for (uint32_t i = 0; i < root->triangle_count; i++) {
     LineartTriangle *tri = root->linked_triangles[i];
 
     double b[4];
@@ -4011,10 +4010,8 @@ static bool lineart_bounding_area_edge_intersect(LineartData *UNUSED(fb),
   double converted[4];
   double c1, c;
 
-  if (((converted[0] = (double)ba->l) > MAX2(l[0], r[0])) ||
-      ((converted[1] = (double)ba->r) < MIN2(l[0], r[0])) ||
-      ((converted[2] = (double)ba->b) > MAX2(l[1], r[1])) ||
-      ((converted[3] = (double)ba->u) < MIN2(l[1], r[1]))) {
+  if (((converted[0] = ba->l) > MAX2(l[0], r[0])) || ((converted[1] = ba->r) < MIN2(l[0], r[0])) ||
+      ((converted[2] = ba->b) > MAX2(l[1], r[1])) || ((converted[3] = ba->u) < MIN2(l[1], r[1]))) {
     return false;
   }
 
@@ -4047,22 +4044,26 @@ static bool lineart_bounding_area_edge_intersect(LineartData *UNUSED(fb),
 
 static bool lineart_bounding_area_triangle_intersect(LineartData *fb,
                                                      LineartTriangle *tri,
-                                                     LineartBoundingArea *ba)
+                                                     LineartBoundingArea *ba,
+                                                     bool *r_triangle_vert_inside)
 {
   double p1[2], p2[2], p3[2], p4[2];
   double *FBC1 = tri->v[0]->fbcoord, *FBC2 = tri->v[1]->fbcoord, *FBC3 = tri->v[2]->fbcoord;
 
-  p3[0] = p1[0] = (double)ba->l;
-  p2[1] = p1[1] = (double)ba->b;
-  p2[0] = p4[0] = (double)ba->r;
-  p3[1] = p4[1] = (double)ba->u;
+  p3[0] = p1[0] = ba->l;
+  p2[1] = p1[1] = ba->b;
+  p2[0] = p4[0] = ba->r;
+  p3[1] = p4[1] = ba->u;
 
   if ((FBC1[0] >= p1[0] && FBC1[0] <= p2[0] && FBC1[1] >= p1[1] && FBC1[1] <= p3[1]) ||
       (FBC2[0] >= p1[0] && FBC2[0] <= p2[0] && FBC2[1] >= p1[1] && FBC2[1] <= p3[1]) ||
       (FBC3[0] >= p1[0] && FBC3[0] <= p2[0] && FBC3[1] >= p1[1] && FBC3[1] <= p3[1])) {
+    *r_triangle_vert_inside = true;
     return true;
   }
 
+  *r_triangle_vert_inside = false;
+
   if (lineart_point_inside_triangle(p1, FBC1, FBC2, FBC3) ||
       lineart_point_inside_triangle(p2, FBC1, FBC2, FBC3) ||
       lineart_point_inside_triangle(p3, FBC1, FBC2, FBC3) ||
@@ -4101,7 +4102,8 @@ static void lineart_bounding_area_link_triangle(LineartData *ld,
                                                 bool do_intersection,
                                                 struct LineartIsecThread *th)
 {
-  if (!lineart_bounding_area_triangle_intersect(ld, tri, root_ba)) {
+  bool triangle_vert_inside;
+  if (!lineart_bounding_area_triangle_intersect(ld, tri, root_ba, &triangle_vert_inside)) {
     return;
   }
 
@@ -4138,7 +4140,12 @@ static void lineart_bounding_area_link_triangle(LineartData *ld,
   if (old_ba->triangle_count < old_ba->max_triangle_count) {
     const uint32_t old_tri_count = old_ba->triangle_count;
 
-    old_ba->linked_triangles[old_ba->triangle_count++] = tri;
+    old_ba->linked_triangles[old_tri_count] = tri;
+
+    if (triangle_vert_inside) {
+      old_ba->insider_triangle_count++;
+    }
+    old_ba->triangle_count++;
 
     /* Do intersections in place. */
     if (do_intersection && ld->conf.use_intersections) {
@@ -4151,7 +4158,8 @@ static void lineart_bounding_area_link_triangle(LineartData *ld,
   }
   else { /* We need to wait for either splitting or array extension to be done. */
 
-    if (recursive_level < ld->qtree.recursive_level) {
+    if (recursive_level < ld->qtree.recursive_level &&
+        old_ba->insider_triangle_count >= LRT_TILE_SPLITTING_TRIANGLE_LIMIT) {
       if (!old_ba->child) {
         /* old_ba->child==NULL, means we are the thread that's doing the splitting. */
         lineart_bounding_area_split(ld, old_ba, recursive_level);
@@ -4271,6 +4279,62 @@ void lineart_main_link_lines(LineartData *ld)
   LRT_ITER_ALL_LINES_END
 }
 
+static void lineart_main_remove_unused_lines_recursive(LineartBoundingArea *ba,
+                                                       uint8_t max_occlusion)
+{
+  if (ba->child) {
+    for (int i = 0; i < 4; i++) {
+      lineart_main_remove_unused_lines_recursive(&ba->child[i], max_occlusion);
+    }
+    return;
+  }
+
+  if (!ba->line_count) {
+    return;
+  }
+
+  int usable_count = 0;
+  for (int i = 0; i < ba->line_count; i++) {
+    LineartEdge *e = ba->linked_lines[i];
+    if (e->min_occ > max_occlusion) {
+      continue;
+    }
+    usable_count++;
+  }
+
+  if (!usable_count) {
+    ba->line_count = 0;
+    return;
+  }
+
+  LineartEdge **new_array = MEM_callocN(sizeof(LineartEdge *) * usable_count,
+                                        "cleaned lineart edge array");
+
+  int new_i = 0;
+  for (int i = 0; i < ba->line_count; i++) {
+    LineartEdge *e = ba->linked_lines[i];
+    if (e->min_occ > max_occlusion) {
+      continue;
+    }
+    new_array[new_i] = e;
+    new_i++;
+  }
+
+  MEM_freeN(ba->linked_lines);
+  ba->linked_lines = new_array;
+  ba->max_line_count = ba->line_count = usable_count;
+}
+
+static void lineart_main_remove_unused_lines_from_tiles(LineartData *ld)
+{
+  for (int row = 0; row < ld->qtree.count_y; row++) {
+    for (int col = 0; col < ld->qtree.count_x; col++) {
+      lineart_main_remove_unused_lines_recursive(
+          &ld->qtree.initials[row * ld->qtree.count_x + col], ld->conf.max_occlusion_level);
+    }
+  }
+}
+
 static bool lineart_get_triangle_bounding_areas(
     LineartData *ld, LineartTriangle *tri, int *rowbegin, int *rowend, int *colbegin, int *colend)
 {
@@ -4997,6 +5061,8 @@ bool MOD_lineart_compute_feature_lines(Depsgraph *depsgraph,
 
     lineart_main_make_enclosed_shapes(ld, shadow_rb);
 
+    lineart_main_remove_unused_lines_from_tiles(ld);
+
     /* Chaining is all single threaded. See lineart_chain.c
      * In this particular call, only lines that are geometrically connected (share the _exact_
      * same end point) will be chained together. */
@@ -5010,10 +5076,6 @@ bool MOD_lineart_compute_feature_lines(Depsgraph *depsgraph,
      * the place threshold value gets involved. */
     MOD_lineart_chain_connect(ld);
 
-    float *t_image = &lmd->chaining_image_threshold;
-    /* This configuration ensures there won't be accidental lost of short unchained segments. */
-    MOD_lineart_chain_discard_short(ld, MIN2(*t_image, 0.001f) - FLT_EPSILON);
-
     if (ld->conf.chain_smooth_tolerance > FLT_EPSILON) {
       /* Keeping UI range of 0-1 for ease of read while scaling down the actual value for best
        * effective range in image-space (Coordinate only goes from -1 to 1). This value is
-- 
cgit v1.2.3


From da00d62c4913a71e116f74f874d7a17cfee9c14e Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Fri, 1 Jul 2022 22:45:10 +1000
Subject: Fix crash with window decorations (libdecor) in Wayland

Surfaces from window decorations were passed into GHOST's listeners
since libdecor & GHOST share a connection.

This error introduced by recent changes that assumed surfaces passed to
GHOST's handler functions were owned by GHOST.

Tag GHOST surfaces & outputs to ensure GHOST only attempts to access
data it created.
---
 intern/ghost/intern/GHOST_SystemWayland.cpp | 144 +++++++++++++++++++---------
 intern/ghost/intern/GHOST_SystemWayland.h   |  12 ++-
 intern/ghost/intern/GHOST_WindowWayland.cpp |  76 ++++-----------
 intern/ghost/intern/GHOST_WindowWayland.h   |  15 ---
 4 files changed, 125 insertions(+), 122 deletions(-)

diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp
index 6a419fc718d..c360423c256 100644
--- a/intern/ghost/intern/GHOST_SystemWayland.cpp
+++ b/intern/ghost/intern/GHOST_SystemWayland.cpp
@@ -786,7 +786,7 @@ static void relative_pointer_handle_relative_motion(
 {
   input_t *input = static_cast(data);
   if (wl_surface *focus_surface = input->pointer.wl_surface) {
-    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
     const wl_fixed_t scale = win->scale();
     const wl_fixed_t xy_next[2] = {
         input->pointer.xy[0] + (dx / scale),
@@ -810,7 +810,7 @@ static void dnd_events(const input_t *const input, const GHOST_TEventType event)
 {
   /* NOTE: `input->data_offer_dnd_mutex` must already be locked. */
   if (wl_surface *focus_surface = input->focus_dnd) {
-    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
     const wl_fixed_t scale = win->scale();
     const int event_xy[2] = {
         wl_fixed_to_int(scale * input->data_offer_dnd->dnd.xy[0]),
@@ -992,6 +992,10 @@ static void data_device_handle_enter(void *data,
                                      const wl_fixed_t y,
                                      struct wl_data_offer *id)
 {
+  if (!ghost_wl_surface_own(surface)) {
+    return;
+  }
+
   input_t *input = static_cast(data);
   std::lock_guard lock{input->data_offer_dnd_mutex};
 
@@ -1077,9 +1081,7 @@ static void data_device_handle_drop(void *data, struct wl_data_device * /*wl_dat
       static constexpr const char *file_proto = "file://";
       static constexpr const char *crlf = "\r\n";
 
-      GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_find(surface);
-      GHOST_ASSERT(win != nullptr, "Unable to find window for drop event from surface");
-
+      GHOST_WindowWayland *win = ghost_wl_surface_user_data(surface);
       std::vector uris;
 
       size_t pos = 0;
@@ -1238,12 +1240,13 @@ static void cursor_surface_handle_enter(void *data,
                                         struct wl_surface * /*wl_surface*/,
                                         struct wl_output *output)
 {
-  input_t *input = static_cast(data);
-  for (const output_t *reg_output : input->system->outputs()) {
-    if (reg_output->wl_output == output) {
-      input->cursor.outputs.insert(reg_output);
-    }
+  if (!ghost_wl_output_own(output)) {
+    return;
   }
+
+  input_t *input = static_cast(data);
+  const output_t *reg_output = ghost_wl_output_user_data(output);
+  input->cursor.outputs.insert(reg_output);
   update_cursor_scale(input->cursor, input->system->shm());
 }
 
@@ -1251,12 +1254,13 @@ static void cursor_surface_handle_leave(void *data,
                                         struct wl_surface * /*wl_surface*/,
                                         struct wl_output *output)
 {
-  input_t *input = static_cast(data);
-  for (const output_t *reg_output : input->system->outputs()) {
-    if (reg_output->wl_output == output) {
-      input->cursor.outputs.erase(reg_output);
-    }
+  if (!(output && ghost_wl_output_own(output))) {
+    return;
   }
+
+  input_t *input = static_cast(data);
+  const output_t *reg_output = ghost_wl_output_user_data(output);
+  input->cursor.outputs.erase(reg_output);
   update_cursor_scale(input->cursor, input->system->shm());
 }
 
@@ -1278,7 +1282,11 @@ static void pointer_handle_enter(void *data,
                                  const wl_fixed_t surface_x,
                                  const wl_fixed_t surface_y)
 {
-  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(surface);
+  if (!ghost_wl_surface_own(surface)) {
+    return;
+  }
+
+  GHOST_WindowWayland *win = ghost_wl_surface_user_data(surface);
 
   win->activate();
 
@@ -1307,8 +1315,8 @@ static void pointer_handle_leave(void *data,
 {
   /* First clear the `pointer.wl_surface`, since the window won't exist when closing the window. */
   static_cast(data)->pointer.wl_surface = nullptr;
-  if (surface) {
-    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(surface);
+  if (surface && ghost_wl_surface_own(surface)) {
+    GHOST_WindowWayland *win = ghost_wl_surface_user_data(surface);
     win->deactivate();
   }
 }
@@ -1324,7 +1332,7 @@ static void pointer_handle_motion(void *data,
   input->pointer.xy[1] = surface_y;
 
   if (wl_surface *focus_surface = input->pointer.wl_surface) {
-    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
     const wl_fixed_t scale = win->scale();
     input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(),
                                                    GHOST_kEventCursorMove,
@@ -1383,7 +1391,7 @@ static void pointer_handle_button(void *data,
   input->pointer.buttons.set(ebutton, state == WL_POINTER_BUTTON_STATE_PRESSED);
 
   if (wl_surface *focus_surface = input->pointer.wl_surface) {
-    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
     input->system->pushEvent(new GHOST_EventButton(
         input->system->getMilliSeconds(), etype, win, ebutton, GHOST_TABLET_DATA_NONE));
   }
@@ -1402,7 +1410,7 @@ static void pointer_handle_axis(void *data,
   }
 
   if (wl_surface *focus_surface = input->pointer.wl_surface) {
-    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
     input->system->pushEvent(new GHOST_EventWheel(
         input->system->getMilliSeconds(), win, std::signbit(value) ? +1 : -1));
   }
@@ -1474,6 +1482,10 @@ static void tablet_tool_handle_proximity_in(void *data,
                                             struct zwp_tablet_v2 * /*tablet*/,
                                             struct wl_surface *surface)
 {
+  if (!ghost_wl_surface_own(surface)) {
+    return;
+  }
+
   tablet_tool_input_t *tool_input = static_cast(data);
   tool_input->proximity = true;
 
@@ -1492,7 +1504,7 @@ static void tablet_tool_handle_proximity_in(void *data,
   /* In case pressure isn't supported. */
   td.Pressure = 1.0f;
 
-  GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(input->tablet.wl_surface);
+  GHOST_WindowWayland *win = ghost_wl_surface_user_data(input->tablet.wl_surface);
 
   win->activate();
 
@@ -1520,7 +1532,7 @@ static void tablet_tool_handle_down(void *data,
   input->tablet.buttons.set(ebutton, true);
 
   if (wl_surface *focus_surface = input->tablet.wl_surface) {
-    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
     input->system->pushEvent(new GHOST_EventButton(
         input->system->getMilliSeconds(), etype, win, ebutton, tool_input->data));
   }
@@ -1536,7 +1548,7 @@ static void tablet_tool_handle_up(void *data, struct zwp_tablet_tool_v2 * /*zwp_
   input->tablet.buttons.set(ebutton, false);
 
   if (wl_surface *focus_surface = input->tablet.wl_surface) {
-    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
     input->system->pushEvent(new GHOST_EventButton(
         input->system->getMilliSeconds(), etype, win, ebutton, tool_input->data));
   }
@@ -1608,7 +1620,7 @@ static void tablet_tool_handle_wheel(void *data,
   tablet_tool_input_t *tool_input = static_cast(data);
   input_t *input = tool_input->input;
   if (wl_surface *focus_surface = input->tablet.wl_surface) {
-    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
     input->system->pushEvent(new GHOST_EventWheel(input->system->getMilliSeconds(), win, clicks));
   }
 }
@@ -1648,7 +1660,7 @@ static void tablet_tool_handle_button(void *data,
   input->tablet.buttons.set(ebutton, state == WL_POINTER_BUTTON_STATE_PRESSED);
 
   if (wl_surface *focus_surface = input->tablet.wl_surface) {
-    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
     input->system->pushEvent(new GHOST_EventButton(
         input->system->getMilliSeconds(), etype, win, ebutton, tool_input->data));
   }
@@ -1661,7 +1673,7 @@ static void tablet_tool_handle_frame(void *data,
   input_t *input = tool_input->input;
 
   if (wl_surface *focus_surface = input->tablet.wl_surface) {
-    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
     const wl_fixed_t scale = win->scale();
     input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(),
                                                    GHOST_kEventCursorMove,
@@ -1813,6 +1825,10 @@ static void keyboard_handle_enter(void *data,
                                   struct wl_surface *surface,
                                   struct wl_array * /*keys*/)
 {
+  if (!ghost_wl_surface_own(surface)) {
+    return;
+  }
+
   input_t *input = static_cast(data);
   input->keyboard.serial = serial;
   input->keyboard.wl_surface = surface;
@@ -1827,8 +1843,12 @@ static void keyboard_handle_enter(void *data,
 static void keyboard_handle_leave(void *data,
                                   struct wl_keyboard * /*wl_keyboard*/,
                                   const uint32_t /*serial*/,
-                                  struct wl_surface * /*surface*/)
+                                  struct wl_surface *surface)
 {
+  if (!(surface && ghost_wl_surface_own(surface))) {
+    return;
+  }
+
   input_t *input = static_cast(data);
   input->keyboard.wl_surface = nullptr;
 
@@ -1983,7 +2003,7 @@ static void keyboard_handle_key(void *data,
   input->data_source_serial = serial;
 
   if (wl_surface *focus_surface = input->keyboard.wl_surface) {
-    GHOST_IWindow *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    GHOST_IWindow *win = ghost_wl_surface_user_data(focus_surface);
     input->system->pushEvent(new GHOST_EventKey(
         input->system->getMilliSeconds(), etype, win, gkey, '\0', utf8_buf, false));
   }
@@ -2008,7 +2028,7 @@ static void keyboard_handle_key(void *data,
 
       input_t *input = payload->input;
       if (wl_surface *focus_surface = input->keyboard.wl_surface) {
-        GHOST_IWindow *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+        GHOST_IWindow *win = ghost_wl_surface_user_data(focus_surface);
         GHOST_SystemWayland *system = input->system;
         /* Calculate this value every time in case modifier keys are pressed. */
         char utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)] = {'\0'};
@@ -2378,6 +2398,9 @@ static void global_handle_add(void *data,
     output_t *output = new output_t;
     output->wl_output = static_cast(
         wl_registry_bind(wl_registry, name, &wl_output_interface, 2));
+    ghost_wl_output_tag(output->wl_output);
+    wl_output_set_user_data(output->wl_output, output);
+
     display->outputs.push_back(output);
     wl_output_add_listener(output->wl_output, &output_listener, output);
 
@@ -2692,7 +2715,7 @@ GHOST_TSuccess GHOST_SystemWayland::getCursorPosition(int32_t &x, int32_t &y) co
   }
 
   if (wl_surface *focus_surface = input_state->wl_surface) {
-    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
     return getCursorPositionClientRelative_impl(input_state, win, x, y);
   }
   return GHOST_kFailure;
@@ -2708,7 +2731,7 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorPosition(const int32_t x, const int
   /* Intentionally different from `getCursorPosition` which supports both tablet & pointer.
    * In the case of setting the cursor location, tablets don't support this. */
   if (wl_surface *focus_surface = input->pointer.wl_surface) {
-    GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(focus_surface);
+    GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface);
     return setCursorPositionClientRelative_impl(input, win, x, y);
   }
   return GHOST_kFailure;
@@ -3255,7 +3278,7 @@ static bool setCursorGrab_use_software_confine(const GHOST_TGrabCursorMode mode,
   if (mode != GHOST_kGrabNormal) {
     return false;
   }
-  const GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface(surface);
+  const GHOST_WindowWayland *win = ghost_wl_surface_user_data(surface);
   if (!win) {
     return false;
   }
@@ -3283,6 +3306,35 @@ static input_grab_state_t input_grab_state_from_mode(const GHOST_TGrabCursorMode
 
 /** \} */
 
+/* -------------------------------------------------------------------- */
+/** \name Public WAYLAND Proxy Ownership API
+ * \{ */
+
+static const char *ghost_wl_output_tag_id = "GHOST-output";
+static const char *ghost_wl_surface_tag_id = "GHOST-window";
+
+bool ghost_wl_output_own(const struct wl_output *output)
+{
+  return wl_proxy_get_tag((struct wl_proxy *)output) == &ghost_wl_output_tag_id;
+}
+
+bool ghost_wl_surface_own(const struct wl_surface *surface)
+{
+  return wl_proxy_get_tag((struct wl_proxy *)surface) == &ghost_wl_surface_tag_id;
+}
+
+void ghost_wl_output_tag(struct wl_output *output)
+{
+  wl_proxy_set_tag((struct wl_proxy *)output, &ghost_wl_output_tag_id);
+}
+
+void ghost_wl_surface_tag(struct wl_surface *surface)
+{
+  wl_proxy_set_tag((struct wl_proxy *)surface, &ghost_wl_surface_tag_id);
+}
+
+/** \} */
+
 /* -------------------------------------------------------------------- */
 /** \name Public WAYLAND Direct Data Access
  *
@@ -3336,14 +3388,20 @@ wl_shm *GHOST_SystemWayland::shm() const
 /** \name Public WAYLAND Query Access
  * \{ */
 
-output_t *GHOST_SystemWayland::output_find_by_wl(const struct wl_output *output) const
+struct output_t *ghost_wl_output_user_data(struct wl_output *wl_output)
 {
-  for (output_t *reg_output : this->outputs()) {
-    if (reg_output->wl_output == output) {
-      return reg_output;
-    }
-  }
-  return nullptr;
+  GHOST_ASSERT(wl_output, "output must not be NULL");
+  GHOST_ASSERT(ghost_wl_output_own(wl_output), "output is not owned by GHOST");
+  output_t *output = static_cast(wl_output_get_user_data(wl_output));
+  return output;
+}
+
+GHOST_WindowWayland *ghost_wl_surface_user_data(struct wl_surface *surface)
+{
+  GHOST_ASSERT(surface, "surface must not be NULL");
+  GHOST_ASSERT(ghost_wl_surface_own(surface), "surface is not owned by GHOST");
+  GHOST_WindowWayland *win = static_cast(wl_surface_get_user_data(surface));
+  return win;
 }
 
 /** \} */
@@ -3433,7 +3491,7 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod
       if (mode_current == GHOST_kGrabWrap) {
         /* Since this call is initiated by Blender, we can be sure the window wasn't closed
          * by logic outside this function - as the window was needed to make this call. */
-        GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(surface);
+        GHOST_WindowWayland *win = ghost_wl_surface_user_data(surface);
         GHOST_ASSERT(win, "could not find window from surface when un-grabbing!");
 
         GHOST_Rect bounds;
@@ -3472,7 +3530,7 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod
       else if (mode_current == GHOST_kGrabHide) {
         if ((init_grab_xy[0] != input->grab_lock_xy[0]) ||
             (init_grab_xy[1] != input->grab_lock_xy[1])) {
-          GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(surface);
+          GHOST_WindowWayland *win = ghost_wl_surface_user_data(surface);
           const int scale = win->scale();
           const wl_fixed_t xy_next[2] = {
               wl_fixed_from_int(init_grab_xy[0]) / scale,
@@ -3526,7 +3584,7 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod
       if (mode == GHOST_kGrabHide) {
         /* Set the initial position to detect any changes when un-grabbing,
          * otherwise the unlocked cursor defaults to un-locking in-place. */
-        GHOST_WindowWayland *win = GHOST_WindowWayland::from_surface_mut(surface);
+        GHOST_WindowWayland *win = ghost_wl_surface_user_data(surface);
         const int scale = win->scale();
         init_grab_xy[0] = wl_fixed_to_int(scale * input->pointer.xy[0]);
         init_grab_xy[1] = wl_fixed_to_int(scale * input->pointer.xy[1]);
diff --git a/intern/ghost/intern/GHOST_SystemWayland.h b/intern/ghost/intern/GHOST_SystemWayland.h
index cf9ec5d0757..e336b02ec9e 100644
--- a/intern/ghost/intern/GHOST_SystemWayland.h
+++ b/intern/ghost/intern/GHOST_SystemWayland.h
@@ -27,6 +27,14 @@ class GHOST_WindowWayland;
 
 struct display_t;
 
+bool ghost_wl_output_own(const struct wl_output *output);
+void ghost_wl_output_tag(struct wl_output *output);
+struct output_t *ghost_wl_output_user_data(struct wl_output *output);
+
+bool ghost_wl_surface_own(const struct wl_surface *surface);
+void ghost_wl_surface_tag(struct wl_surface *surface);
+GHOST_WindowWayland *ghost_wl_surface_user_data(struct wl_surface *surface);
+
 struct output_t {
   struct wl_output *wl_output = nullptr;
   struct zxdg_output_v1 *xdg_output = nullptr;
@@ -147,10 +155,6 @@ class GHOST_SystemWayland : public GHOST_System {
 
   wl_shm *shm() const;
 
-  /* WAYLAND query access. */
-
-  output_t *output_find_by_wl(const struct wl_output *output) const;
-
   /* WAYLAND utility functions. */
 
   void selection_set(const std::string &selection);
diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp
index 6cae60fc4b5..d26dfe0e7e6 100644
--- a/intern/ghost/intern/GHOST_WindowWayland.cpp
+++ b/intern/ghost/intern/GHOST_WindowWayland.cpp
@@ -323,8 +323,12 @@ static void surface_handle_enter(void *data,
                                  struct wl_surface * /*wl_surface*/,
                                  struct wl_output *output)
 {
+  if (!ghost_wl_output_own(output)) {
+    return;
+  }
+  output_t *reg_output = ghost_wl_output_user_data(output);
   GHOST_WindowWayland *win = static_cast(data);
-  if (win->outputs_enter_wl(output)) {
+  if (win->outputs_enter(reg_output)) {
     win->outputs_changed_update_scale();
   }
 }
@@ -333,9 +337,13 @@ static void surface_handle_leave(void *data,
                                  struct wl_surface * /*wl_surface*/,
                                  struct wl_output *output)
 {
-  GHOST_WindowWayland *w = static_cast(data);
-  if (w->outputs_leave_wl(output)) {
-    w->outputs_changed_update_scale();
+  if (!ghost_wl_output_own(output)) {
+    return;
+  }
+  output_t *reg_output = ghost_wl_output_user_data(output);
+  GHOST_WindowWayland *win = static_cast(data);
+  if (win->outputs_leave(reg_output)) {
+    win->outputs_changed_update_scale();
   }
 }
 
@@ -398,7 +406,9 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system,
 
   /* Window surfaces. */
   w->wl_surface = wl_compositor_create_surface(m_system->compositor());
-  wl_surface_set_buffer_scale(this->surface(), w->scale);
+  ghost_wl_surface_tag(w->wl_surface);
+
+  wl_surface_set_buffer_scale(w->wl_surface, w->scale);
 
   wl_surface_add_listener(w->wl_surface, &wl_surface_listener, this);
 
@@ -809,42 +819,6 @@ const std::vector &GHOST_WindowWayland::outputs()
 
 /** \} */
 
-/* -------------------------------------------------------------------- */
-/** \name Public WAYLAND Query Access
- * \{ */
-
-GHOST_WindowWayland *GHOST_WindowWayland::from_surface_find(const wl_surface *surface)
-{
-  GHOST_ASSERT(surface, "argument must not be NULL");
-  for (GHOST_IWindow *iwin : window_manager->getWindows()) {
-    GHOST_WindowWayland *win = static_cast(iwin);
-    if (surface == win->surface()) {
-      return win;
-    }
-  }
-  return nullptr;
-}
-
-GHOST_WindowWayland *GHOST_WindowWayland::from_surface_mut(wl_surface *surface)
-{
-  GHOST_ASSERT(surface, "argument must not be NULL");
-  GHOST_WindowWayland *win = static_cast(wl_surface_get_user_data(surface));
-  GHOST_ASSERT(win == GHOST_WindowWayland::from_surface_find(surface),
-               "Inconsistent window state, consider using \"from_surface_find\"");
-  return win;
-}
-
-const GHOST_WindowWayland *GHOST_WindowWayland::from_surface(const wl_surface *surface)
-{
-  const GHOST_WindowWayland *win = static_cast(
-      wl_surface_get_user_data(const_cast(surface)));
-  GHOST_ASSERT(win == GHOST_WindowWayland::from_surface_find(surface),
-               "Inconsistent window state, consider using \"from_surface_find\"");
-  return win;
-}
-
-/** \} */
-
 /* -------------------------------------------------------------------- */
 /** \name Public WAYLAND Window Level Functions
  *
@@ -910,7 +884,7 @@ bool GHOST_WindowWayland::outputs_changed_update_scale()
     win->size_pending[1] = (win->size_pending[1] / scale_curr) * scale_next;
 
     win->scale = scale_next;
-    wl_surface_set_buffer_scale(this->surface(), scale_next);
+    wl_surface_set_buffer_scale(w->wl_surface, scale_next);
     changed = true;
   }
 
@@ -946,22 +920,4 @@ bool GHOST_WindowWayland::outputs_leave(output_t *reg_output)
   return true;
 }
 
-bool GHOST_WindowWayland::outputs_enter_wl(const wl_output *output)
-{
-  output_t *reg_output = m_system->output_find_by_wl(output);
-  if (!reg_output) {
-    return false;
-  }
-  return outputs_enter(reg_output);
-}
-
-bool GHOST_WindowWayland::outputs_leave_wl(const wl_output *output)
-{
-  output_t *reg_output = m_system->output_find_by_wl(output);
-  if (!reg_output) {
-    return false;
-  }
-  return outputs_leave(reg_output);
-}
-
 /** \} */
diff --git a/intern/ghost/intern/GHOST_WindowWayland.h b/intern/ghost/intern/GHOST_WindowWayland.h
index 1afad3b47b3..c754e4cd9e7 100644
--- a/intern/ghost/intern/GHOST_WindowWayland.h
+++ b/intern/ghost/intern/GHOST_WindowWayland.h
@@ -100,19 +100,6 @@ class GHOST_WindowWayland : public GHOST_Window {
   struct wl_surface *surface() const;
   const std::vector &outputs();
 
-  /* WAYLAND query access. */
-
-  /**
-   * Use window find function when the window may have been closed.
-   * Typically this is needed when accessing surfaces outside WAYLAND handlers.
-   */
-  static GHOST_WindowWayland *from_surface_find(const wl_surface *surface);
-  /**
-   * Use direct access when from WAYLAND handlers.
-   */
-  static const GHOST_WindowWayland *from_surface(const wl_surface *surface);
-  static GHOST_WindowWayland *from_surface_mut(wl_surface *surface);
-
   /* WAYLAND window-level functions. */
 
   GHOST_TSuccess close();
@@ -124,8 +111,6 @@ class GHOST_WindowWayland : public GHOST_Window {
 
   bool outputs_enter(output_t *reg_output);
   bool outputs_leave(output_t *reg_output);
-  bool outputs_enter_wl(const struct wl_output *output);
-  bool outputs_leave_wl(const struct wl_output *output);
 
   bool outputs_changed_update_scale();
 
-- 
cgit v1.2.3


From d0e3388848a66eb4ca2f7e16278fabd5832e568a Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Fri, 1 Jul 2022 09:46:27 -0500
Subject: Cleanup: Simplify logic building in length parameterization

We can construct an IndexRange directly rather than retrieving it.
---
 source/blender/blenlib/intern/length_parameterize.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source/blender/blenlib/intern/length_parameterize.cc b/source/blender/blenlib/intern/length_parameterize.cc
index 7c0fc860b53..e18b048e96d 100644
--- a/source/blender/blenlib/intern/length_parameterize.cc
+++ b/source/blender/blenlib/intern/length_parameterize.cc
@@ -47,7 +47,7 @@ void create_uniform_samples(const Span lengths,
     const int segment_samples_num = std::ceil(next_length * step_length_inv - i_dst);
     indices.slice(i_dst, segment_samples_num).fill(i_src);
 
-    for (const int i : factors.index_range().slice(i_dst, segment_samples_num)) {
+    for (const int i : IndexRange(i_dst, segment_samples_num)) {
       const float length_in_segment = step_length * i - prev_length;
       factors[i] = length_in_segment * segment_length_inv;
     }
-- 
cgit v1.2.3


From 3c60d62dba1214378c3348563167385385f6a539 Mon Sep 17 00:00:00 2001
From: Jacques Lucke 
Date: Sat, 2 Jul 2022 11:40:36 +0200
Subject: BKE: fix wrong recently added assert

---
 source/blender/blenkernel/intern/customdata.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source/blender/blenkernel/intern/customdata.cc b/source/blender/blenkernel/intern/customdata.cc
index 2cd829a7f0b..74903e72f91 100644
--- a/source/blender/blenkernel/intern/customdata.cc
+++ b/source/blender/blenkernel/intern/customdata.cc
@@ -2329,7 +2329,7 @@ bool CustomData_merge(const CustomData *source,
 
 void CustomData_realloc(CustomData *data, int totelem)
 {
-  BLI_assert(totelem > 0);
+  BLI_assert(totelem >= 0);
   for (int i = 0; i < data->totlayer; i++) {
     CustomDataLayer *layer = &data->layers[i];
     const LayerTypeInfo *typeInfo;
-- 
cgit v1.2.3


From 5d9ade27de54b6910ed32f92d20d8f692959603c Mon Sep 17 00:00:00 2001
From: Jacques Lucke 
Date: Sat, 2 Jul 2022 11:45:57 +0200
Subject: BLI: improve span access to virtual arrays

* Make the class names more consistent.
* Implement missing move-constructors and assignment-operators.
---
 source/blender/blenkernel/BKE_attribute_access.hh  |  4 +-
 .../blender/blenkernel/intern/attribute_access.cc  |  5 +-
 source/blender/blenkernel/intern/curve_eval.cc     | 16 ++---
 .../blenkernel/intern/curve_to_mesh_convert.cc     |  8 +--
 .../blender/blenkernel/intern/curves_geometry.cc   |  8 +--
 .../blenkernel/intern/geometry_component_curve.cc  |  8 +--
 .../blender/blenlib/BLI_generic_virtual_array.hh   | 22 ++++---
 source/blender/blenlib/BLI_virtual_array.hh        | 60 +++++++++++++----
 .../blenlib/intern/generic_virtual_array.cc        | 77 ++++++++++++++++++----
 .../blenlib/tests/BLI_virtual_array_test.cc        |  2 +-
 source/blender/editors/curves/intern/curves_ops.cc |  2 +-
 .../editors/sculpt_paint/curves_sculpt_add.cc      |  2 +-
 .../editors/sculpt_paint/curves_sculpt_density.cc  |  4 +-
 .../editors/sculpt_paint/curves_sculpt_slide.cc    |  2 +-
 .../editors/sculpt_paint/paint_vertex_color_ops.cc |  2 +-
 .../blender/geometry/intern/realize_instances.cc   |  8 +--
 .../nodes/geometry/nodes/node_geo_curve_sample.cc  |  2 +-
 .../geometry/nodes/node_geo_curve_spline_type.cc   |  6 +-
 .../geometry/nodes/node_geo_delete_geometry.cc     |  8 +--
 .../nodes/geometry/nodes/node_geo_dual_mesh.cc     |  2 +-
 .../geometry/nodes/node_geo_duplicate_elements.cc  | 18 ++---
 .../nodes/geometry/nodes/node_geo_join_geometry.cc |  2 +-
 .../geometry/nodes/node_geo_points_to_vertices.cc  |  2 +-
 23 files changed, 180 insertions(+), 90 deletions(-)

diff --git a/source/blender/blenkernel/BKE_attribute_access.hh b/source/blender/blenkernel/BKE_attribute_access.hh
index fef91d6f75d..9648b5b7cde 100644
--- a/source/blender/blenkernel/BKE_attribute_access.hh
+++ b/source/blender/blenkernel/BKE_attribute_access.hh
@@ -218,7 +218,7 @@ struct WriteAttributeLookup {
  * Supported convenience features:
  * - Implicit type conversion when writing to builtin attributes.
  * - Supports simple access to a span containing the attribute values (that avoids the use of
- *   VMutableArray_Span in many cases).
+ *   MutableVArraySpan in many cases).
  * - An output attribute can live side by side with an existing attribute with a different domain
  *   or data type. The old attribute will only be overwritten when the #save function is called.
  *
@@ -234,7 +234,7 @@ class OutputAttribute {
   GVMutableArray varray_;
   eAttrDomain domain_ = ATTR_DOMAIN_AUTO;
   SaveFn save_;
-  std::unique_ptr optional_span_varray_;
+  std::unique_ptr optional_span_varray_;
   bool ignore_old_values_ = false;
   bool save_has_been_called_ = false;
 
diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc
index 409941623d2..ee9358eb97e 100644
--- a/source/blender/blenkernel/intern/attribute_access.cc
+++ b/source/blender/blenkernel/intern/attribute_access.cc
@@ -155,10 +155,9 @@ GMutableSpan OutputAttribute::as_span()
 {
   if (!optional_span_varray_) {
     const bool materialize_old_values = !ignore_old_values_;
-    optional_span_varray_ = std::make_unique(varray_,
-                                                                   materialize_old_values);
+    optional_span_varray_ = std::make_unique(varray_, materialize_old_values);
   }
-  GVMutableArray_GSpan &span_varray = *optional_span_varray_;
+  GMutableVArraySpan &span_varray = *optional_span_varray_;
   return span_varray;
 }
 
diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc
index dd2bd982506..c01a184c6f9 100644
--- a/source/blender/blenkernel/intern/curve_eval.cc
+++ b/source/blender/blenkernel/intern/curve_eval.cc
@@ -21,14 +21,14 @@ using blender::Array;
 using blender::float3;
 using blender::float4x4;
 using blender::GVArray;
-using blender::GVArray_GSpan;
+using blender::GVArraySpan;
 using blender::IndexRange;
 using blender::Map;
 using blender::MutableSpan;
 using blender::Span;
 using blender::StringRefNull;
 using blender::VArray;
-using blender::VArray_Span;
+using blender::VArraySpan;
 using blender::Vector;
 using blender::bke::AttributeIDRef;
 using blender::bke::OutputAttribute;
@@ -360,7 +360,7 @@ static void copy_attributes_between_components(const GeometryComponent &src_comp
         if (!src_attribute) {
           return true;
         }
-        GVArray_GSpan src_attribute_data{src_attribute};
+        GVArraySpan src_attribute_data{src_attribute};
 
         OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
             id, meta_data.domain, meta_data.data_type);
@@ -383,16 +383,16 @@ std::unique_ptr curves_to_curve_eval(const Curves &curves_id)
   VArray resolution = curves.resolution();
   VArray normal_mode = curves.normal_mode();
 
-  VArray_Span nurbs_weights{
+  VArraySpan nurbs_weights{
       src_component.attribute_get_for_read("nurbs_weight", ATTR_DOMAIN_POINT, 0.0f)};
-  VArray_Span nurbs_orders{
+  VArraySpan nurbs_orders{
       src_component.attribute_get_for_read("nurbs_order", ATTR_DOMAIN_CURVE, 4)};
-  VArray_Span nurbs_knots_modes{
+  VArraySpan nurbs_knots_modes{
       src_component.attribute_get_for_read("knots_mode", ATTR_DOMAIN_CURVE, 0)};
 
-  VArray_Span handle_types_right{
+  VArraySpan handle_types_right{
       src_component.attribute_get_for_read("handle_type_right", ATTR_DOMAIN_POINT, 0)};
-  VArray_Span handle_types_left{
+  VArraySpan handle_types_left{
       src_component.attribute_get_for_read("handle_type_left", ATTR_DOMAIN_POINT, 0)};
 
   /* Create splines with the correct size and type. */
diff --git a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc
index 58380a1a35f..78e5d07da96 100644
--- a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc
+++ b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc
@@ -228,8 +228,8 @@ struct CurvesInfo {
   const CurvesGeometry &profile;
 
   /* Make sure these are spans because they are potentially accessed many times. */
-  VArray_Span main_cyclic;
-  VArray_Span profile_cyclic;
+  VArraySpan main_cyclic;
+  VArraySpan profile_cyclic;
 };
 static CurvesInfo get_curves_info(const CurvesGeometry &main, const CurvesGeometry &profile)
 {
@@ -700,8 +700,8 @@ Mesh *curve_to_mesh_sweep(const CurvesGeometry &main,
 
   if (profile.curve_type_counts()[CURVE_TYPE_BEZIER] > 0) {
     const VArray curve_types = profile.curve_types();
-    const VArray_Span handle_types_left{profile.handle_types_left()};
-    const VArray_Span handle_types_right{profile.handle_types_right()};
+    const VArraySpan handle_types_left{profile.handle_types_left()};
+    const VArraySpan handle_types_right{profile.handle_types_right()};
 
     foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
       if (curve_types[info.i_profile] == CURVE_TYPE_BEZIER) {
diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc
index 0326afca202..8d955a46275 100644
--- a/source/blender/blenkernel/intern/curves_geometry.cc
+++ b/source/blender/blenkernel/intern/curves_geometry.cc
@@ -477,8 +477,8 @@ static void calculate_evaluated_offsets(const CurvesGeometry &curves,
   VArray resolution = curves.resolution();
   VArray cyclic = curves.cyclic();
 
-  VArray_Span handle_types_left{curves.handle_types_left()};
-  VArray_Span handle_types_right{curves.handle_types_right()};
+  VArraySpan handle_types_left{curves.handle_types_left()};
+  VArraySpan handle_types_right{curves.handle_types_right()};
 
   VArray nurbs_orders = curves.nurbs_orders();
   VArray nurbs_knots_modes = curves.nurbs_knots_modes();
@@ -1013,8 +1013,8 @@ void CurvesGeometry::calculate_bezier_auto_handles()
     return;
   }
   const VArray cyclic = this->cyclic();
-  const VArray_Span types_left{this->handle_types_left()};
-  const VArray_Span types_right{this->handle_types_right()};
+  const VArraySpan types_left{this->handle_types_left()};
+  const VArraySpan types_right{this->handle_types_right()};
   const Span positions = this->positions();
   MutableSpan positions_left = this->handle_positions_left_for_write();
   MutableSpan positions_right = this->handle_positions_right_for_write();
diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc
index 898869c3c44..5725c6043b2 100644
--- a/source/blender/blenkernel/intern/geometry_component_curve.cc
+++ b/source/blender/blenkernel/intern/geometry_component_curve.cc
@@ -17,7 +17,7 @@
 using blender::GMutableSpan;
 using blender::GSpan;
 using blender::GVArray;
-using blender::GVArray_GSpan;
+using blender::GVArraySpan;
 
 /* -------------------------------------------------------------------- */
 /** \name Geometry Component Implementation
@@ -231,7 +231,7 @@ template class VArray_For_SplineToPoint final : public VArrayImpl
   GVArray original_varray_;
   /* Store existing data materialized if it was not already a span. This is expected
    * to be worth it because a single spline's value will likely be accessed many times. */
-  VArray_Span original_data_;
+  VArraySpan original_data_;
   Array offsets_;
 
  public:
@@ -645,8 +645,8 @@ static bool create_point_attribute(GeometryComponent &component,
   GVArray source_varray = varray_from_initializer(initializer, data_type, splines);
   /* TODO: When we can call a variant of #set_all with a virtual array argument,
    * this theoretically unnecessary materialize step could be removed. */
-  GVArray_GSpan source_varray_span{source_varray};
-  write_attribute.varray.set_all(source_varray_span.data());
+  GVArraySpan source_VArraySpan{source_varray};
+  write_attribute.varray.set_all(source_VArraySpan.data());
 
   if (initializer.type == AttributeInit::Type::MoveArray) {
     MEM_freeN(static_cast(initializer).data);
diff --git a/source/blender/blenlib/BLI_generic_virtual_array.hh b/source/blender/blenlib/BLI_generic_virtual_array.hh
index 95305a0561d..3526925c2e2 100644
--- a/source/blender/blenlib/BLI_generic_virtual_array.hh
+++ b/source/blender/blenlib/BLI_generic_virtual_array.hh
@@ -257,22 +257,24 @@ class GVMutableArray : public GVArrayCommon {
 /** \} */
 
 /* -------------------------------------------------------------------- */
-/** \name #GVArray_GSpan and #GVMutableArray_GSpan.
+/** \name #GVArraySpan and #GMutableVArraySpan.
  * \{ */
 
-/* A generic version of VArray_Span. */
-class GVArray_GSpan : public GSpan {
+/* A generic version of VArraySpan. */
+class GVArraySpan : public GSpan {
  private:
   GVArray varray_;
   void *owned_data_ = nullptr;
 
  public:
-  GVArray_GSpan(GVArray varray);
-  ~GVArray_GSpan();
+  GVArraySpan(GVArray varray);
+  GVArraySpan(GVArraySpan &&other);
+  ~GVArraySpan();
+  GVArraySpan &operator=(GVArraySpan &&other);
 };
 
-/* A generic version of VMutableArray_Span. */
-class GVMutableArray_GSpan : public GMutableSpan, NonCopyable, NonMovable {
+/* A generic version of MutableVArraySpan. */
+class GMutableVArraySpan : public GMutableSpan, NonCopyable, NonMovable {
  private:
   GVMutableArray varray_;
   void *owned_data_ = nullptr;
@@ -280,8 +282,10 @@ class GVMutableArray_GSpan : public GMutableSpan, NonCopyable, NonMovable {
   bool show_not_saved_warning_ = true;
 
  public:
-  GVMutableArray_GSpan(GVMutableArray varray, bool copy_values_to_span = true);
-  ~GVMutableArray_GSpan();
+  GMutableVArraySpan(GVMutableArray varray, bool copy_values_to_span = true);
+  GMutableVArraySpan(GMutableVArraySpan &&other);
+  ~GMutableVArraySpan();
+  GMutableVArraySpan &operator=(GMutableVArraySpan &&other);
 
   void save();
   void disable_not_applied_warning();
diff --git a/source/blender/blenlib/BLI_virtual_array.hh b/source/blender/blenlib/BLI_virtual_array.hh
index d0436bdc213..84f6223580a 100644
--- a/source/blender/blenlib/BLI_virtual_array.hh
+++ b/source/blender/blenlib/BLI_virtual_array.hh
@@ -51,7 +51,10 @@ struct CommonVArrayInfo {
   /** True when the #data becomes a dangling pointer when the virtual array is destructed. */
   bool may_have_ownership = true;
 
-  /** Points either to nothing, a single value or array of values, depending on #type. */
+  /**
+   * Points either to nothing, a single value or array of values, depending on #type.
+   * If this is a span of a mutable virtual array, it is safe to cast away const.
+   */
   const void *data;
 
   CommonVArrayInfo() = default;
@@ -1117,15 +1120,15 @@ template static constexpr bool is_VMutableArray_v>
  *    from faster access.
  *  - An API is called, that does not accept virtual arrays, but only spans.
  */
-template class VArray_Span final : public Span {
+template class VArraySpan final : public Span {
  private:
   VArray varray_;
   Array owned_data_;
 
  public:
-  VArray_Span() = default;
+  VArraySpan() = default;
 
-  VArray_Span(VArray varray) : Span(), varray_(std::move(varray))
+  VArraySpan(VArray varray) : Span(), varray_(std::move(varray))
   {
     this->size_ = varray_.size();
     const CommonVArrayInfo info = varray_.common_info();
@@ -1140,7 +1143,7 @@ template class VArray_Span final : public Span {
     }
   }
 
-  VArray_Span(VArray_Span &&other)
+  VArraySpan(VArraySpan &&other)
       : varray_(std::move(other.varray_)), owned_data_(std::move(other.owned_data_))
   {
     this->size_ = varray_.size();
@@ -1155,25 +1158,25 @@ template class VArray_Span final : public Span {
     other.size_ = 0;
   }
 
-  VArray_Span &operator=(VArray_Span &&other)
+  VArraySpan &operator=(VArraySpan &&other)
   {
     if (this == &other) {
       return *this;
     }
     std::destroy_at(this);
-    new (this) VArray_Span(std::move(other));
+    new (this) VArraySpan(std::move(other));
     return *this;
   }
 };
 
 /**
- * Same as #VArray_Span, but for a mutable span.
+ * Same as #VArraySpan, but for a mutable span.
  * The important thing to note is that when changing this span, the results might not be
  * immediately reflected in the underlying virtual array (only when the virtual array is a span
  * internally). The #save method can be used to write all changes to the underlying virtual array,
  * if necessary.
  */
-template class VMutableArray_Span final : public MutableSpan {
+template class MutableVArraySpan final : public MutableSpan {
  private:
   VMutableArray varray_;
   Array owned_data_;
@@ -1183,7 +1186,7 @@ template class VMutableArray_Span final : public MutableSpan {
  public:
   /* Create a span for any virtual array. This is cheap when the virtual array is a span itself. If
    * not, a new array has to be allocated as a wrapper for the underlying virtual array. */
-  VMutableArray_Span(VMutableArray varray, const bool copy_values_to_span = true)
+  MutableVArraySpan(VMutableArray varray, const bool copy_values_to_span = true)
       : MutableSpan(), varray_(std::move(varray))
   {
     this->size_ = varray_.size();
@@ -1204,15 +1207,44 @@ template class VMutableArray_Span final : public MutableSpan {
     }
   }
 
-  ~VMutableArray_Span()
+  MutableVArraySpan(MutableVArraySpan &&other)
+      : varray_(std::move(other.varray_)),
+        owned_data_(std::move(owned_data_)),
+        show_not_saved_warning_(other.show_not_saved_warning_)
+  {
+    this->size_ = varray_.size();
+    const CommonVArrayInfo info = varray_.common_info();
+    if (info.type == CommonVArrayInfo::Type::Span) {
+      this->data_ = reinterpret_cast(info.data);
+    }
+    else {
+      this->data_ = owned_data_.data();
+    }
+    other.data_ = nullptr;
+    other.size_ = 0;
+  }
+
+  ~MutableVArraySpan()
   {
-    if (show_not_saved_warning_) {
-      if (!save_has_been_called_) {
-        std::cout << "Warning: Call `save()` to make sure that changes persist in all cases.\n";
+    if (varray_) {
+      if (show_not_saved_warning_) {
+        if (!save_has_been_called_) {
+          std::cout << "Warning: Call `save()` to make sure that changes persist in all cases.\n";
+        }
       }
     }
   }
 
+  MutableVArraySpan &operator=(MutableVArraySpan &&other)
+  {
+    if (this == &other) {
+      return *this;
+    }
+    std::destroy_at(this);
+    new (this) MutableVArraySpan(std::move(other));
+    return *this;
+  }
+
   /* Write back all values from a temporary allocated array to the underlying virtual array. */
   void save()
   {
diff --git a/source/blender/blenlib/intern/generic_virtual_array.cc b/source/blender/blenlib/intern/generic_virtual_array.cc
index 624af3c6ed1..1f6e6cd6e18 100644
--- a/source/blender/blenlib/intern/generic_virtual_array.cc
+++ b/source/blender/blenlib/intern/generic_virtual_array.cc
@@ -284,10 +284,10 @@ template class GVArrayImpl_For_SmallTrivialSingleValue : public
 /** \} */
 
 /* -------------------------------------------------------------------- */
-/** \name #GVArray_GSpan
+/** \name #GVArraySpan
  * \{ */
 
-GVArray_GSpan::GVArray_GSpan(GVArray varray) : GSpan(varray.type()), varray_(std::move(varray))
+GVArraySpan::GVArraySpan(GVArray varray) : GSpan(varray.type()), varray_(std::move(varray))
 {
   size_ = varray_.size();
   const CommonVArrayInfo info = varray_.common_info();
@@ -301,7 +301,22 @@ GVArray_GSpan::GVArray_GSpan(GVArray varray) : GSpan(varray.type()), varray_(std
   }
 }
 
-GVArray_GSpan::~GVArray_GSpan()
+GVArraySpan::GVArraySpan(GVArraySpan &&other)
+    : GSpan(other.type()), varray_(std::move(other.varray_)), owned_data_(other.owned_data_)
+{
+  size_ = varray_.size();
+  const CommonVArrayInfo info = varray_.common_info();
+  if (info.type == CommonVArrayInfo::Type::Span) {
+    data_ = info.data;
+  }
+  else {
+    data_ = owned_data_;
+  }
+  other.data_ = nullptr;
+  other.size_ = 0;
+}
+
+GVArraySpan::~GVArraySpan()
 {
   if (owned_data_ != nullptr) {
     type_->destruct_n(owned_data_, size_);
@@ -309,13 +324,23 @@ GVArray_GSpan::~GVArray_GSpan()
   }
 }
 
+GVArraySpan &GVArraySpan::operator=(GVArraySpan &&other)
+{
+  if (this == &other) {
+    return *this;
+  }
+  std::destroy_at(this);
+  new (this) GVArraySpan(std::move(other));
+  return *this;
+}
+
 /** \} */
 
 /* -------------------------------------------------------------------- */
-/** \name #GVMutableArray_GSpan
+/** \name #GMutableVArraySpan
  * \{ */
 
-GVMutableArray_GSpan::GVMutableArray_GSpan(GVMutableArray varray, const bool copy_values_to_span)
+GMutableVArraySpan::GMutableVArraySpan(GVMutableArray varray, const bool copy_values_to_span)
     : GMutableSpan(varray.type()), varray_(std::move(varray))
 {
   size_ = varray_.size();
@@ -335,11 +360,31 @@ GVMutableArray_GSpan::GVMutableArray_GSpan(GVMutableArray varray, const bool cop
   }
 }
 
-GVMutableArray_GSpan::~GVMutableArray_GSpan()
+GMutableVArraySpan::GMutableVArraySpan(GMutableVArraySpan &&other)
+    : GMutableSpan(other.type()),
+      varray_(std::move(other.varray_)),
+      owned_data_(other.owned_data_),
+      show_not_saved_warning_(other.show_not_saved_warning_)
 {
-  if (show_not_saved_warning_) {
-    if (!save_has_been_called_) {
-      std::cout << "Warning: Call `apply()` to make sure that changes persist in all cases.\n";
+  size_ = varray_.size();
+  const CommonVArrayInfo info = varray_.common_info();
+  if (info.type == CommonVArrayInfo::Type::Span) {
+    data_ = const_cast(info.data);
+  }
+  else {
+    data_ = owned_data_;
+  }
+  other.data_ = nullptr;
+  other.size_ = 0;
+}
+
+GMutableVArraySpan::~GMutableVArraySpan()
+{
+  if (varray_) {
+    if (show_not_saved_warning_) {
+      if (!save_has_been_called_) {
+        std::cout << "Warning: Call `apply()` to make sure that changes persist in all cases.\n";
+      }
     }
   }
   if (owned_data_ != nullptr) {
@@ -348,7 +393,17 @@ GVMutableArray_GSpan::~GVMutableArray_GSpan()
   }
 }
 
-void GVMutableArray_GSpan::save()
+GMutableVArraySpan &GMutableVArraySpan::operator=(GMutableVArraySpan &&other)
+{
+  if (this == &other) {
+    return *this;
+  }
+  std::destroy_at(this);
+  new (this) GMutableVArraySpan(std::move(other));
+  return *this;
+}
+
+void GMutableVArraySpan::save()
 {
   save_has_been_called_ = true;
   if (data_ != owned_data_) {
@@ -357,7 +412,7 @@ void GVMutableArray_GSpan::save()
   varray_.set_all(owned_data_);
 }
 
-void GVMutableArray_GSpan::disable_not_applied_warning()
+void GMutableVArraySpan::disable_not_applied_warning()
 {
   show_not_saved_warning_ = false;
 }
diff --git a/source/blender/blenlib/tests/BLI_virtual_array_test.cc b/source/blender/blenlib/tests/BLI_virtual_array_test.cc
index 90c7f1078a5..c3c6d84ea42 100644
--- a/source/blender/blenlib/tests/BLI_virtual_array_test.cc
+++ b/source/blender/blenlib/tests/BLI_virtual_array_test.cc
@@ -109,7 +109,7 @@ TEST(virtual_array, AsSpan)
 {
   auto func = [](int64_t index) { return (int)(10 * index); };
   VArray func_varray = VArray::ForFunc(10, func);
-  VArray_Span span_varray{func_varray};
+  VArraySpan span_varray{func_varray};
   EXPECT_EQ(span_varray.size(), 10);
   Span span = span_varray;
   EXPECT_EQ(span.size(), 10);
diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc
index dd7edf66920..2ac1a576286 100644
--- a/source/blender/editors/curves/intern/curves_ops.cc
+++ b/source/blender/editors/curves/intern/curves_ops.cc
@@ -544,7 +544,7 @@ static int snap_curves_to_surface_exec(bContext *C, wmOperator *op)
     MeshComponent surface_mesh_component;
     surface_mesh_component.replace(&surface_mesh, GeometryOwnershipType::ReadOnly);
 
-    VArray_Span surface_uv_map;
+    VArraySpan surface_uv_map;
     if (curves_id.surface_uv_map != nullptr) {
       surface_uv_map = surface_mesh_component
                            .attribute_try_get_for_read(
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc
index bac03de77e7..b7f496889c0 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc
@@ -174,7 +174,7 @@ struct AddOperationExecutor {
     }
 
     /* Find UV map. */
-    VArray_Span surface_uv_map;
+    VArraySpan surface_uv_map;
     if (curves_id_->surface_uv_map != nullptr) {
       MeshComponent surface_component;
       surface_component.replace(surface_, GeometryOwnershipType::ReadOnly);
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc
index 120a7802580..be936b4cbda 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc
@@ -64,7 +64,7 @@ struct DensityAddOperationExecutor {
   Mesh *surface_ = nullptr;
   Span surface_looptris_;
   Span corner_normals_su_;
-  VArray_Span surface_uv_map_;
+  VArraySpan surface_uv_map_;
 
   const CurvesSculpt *curves_sculpt_ = nullptr;
   const Brush *brush_ = nullptr;
@@ -226,7 +226,7 @@ struct DensityAddOperationExecutor {
     }
 
     /* Find UV map. */
-    VArray_Span surface_uv_map;
+    VArraySpan surface_uv_map;
     if (curves_id_->surface_uv_map != nullptr) {
       MeshComponent surface_component;
       surface_component.replace(surface_, GeometryOwnershipType::ReadOnly);
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc b/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc
index 3e1949cbf34..2739b5869d5 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc
@@ -87,7 +87,7 @@ struct SlideOperationExecutor {
   Object *surface_ob_ = nullptr;
   Mesh *surface_ = nullptr;
   Span surface_looptris_;
-  VArray_Span surface_uv_map_;
+  VArraySpan surface_uv_map_;
 
   VArray curve_factors_;
   VArray point_factors_;
diff --git a/source/blender/editors/sculpt_paint/paint_vertex_color_ops.cc b/source/blender/editors/sculpt_paint/paint_vertex_color_ops.cc
index 9a3f2b9b708..50f8854d90f 100644
--- a/source/blender/editors/sculpt_paint/paint_vertex_color_ops.cc
+++ b/source/blender/editors/sculpt_paint/paint_vertex_color_ops.cc
@@ -113,7 +113,7 @@ static bool vertex_paint_from_weight(Object *ob)
     return false;
   }
 
-  GVArray_GSpan interpolated{component.attribute_try_adapt_domain(
+  GVArraySpan interpolated{component.attribute_try_adapt_domain(
       vertex_group, ATTR_DOMAIN_POINT, color_attribute.domain)};
 
   color_attribute.varray.set_all(interpolated.data());
diff --git a/source/blender/geometry/intern/realize_instances.cc b/source/blender/geometry/intern/realize_instances.cc
index bd4099d37f9..060a1aa7603 100644
--- a/source/blender/geometry/intern/realize_instances.cc
+++ b/source/blender/geometry/intern/realize_instances.cc
@@ -66,7 +66,7 @@ struct AttributeFallbacksArray {
 struct PointCloudRealizeInfo {
   const PointCloud *pointcloud = nullptr;
   /** Matches the order stored in #AllPointCloudsInfo.attributes. */
-  Array> attributes;
+  Array> attributes;
   /** Id attribute on the point cloud. If there are no ids, this #Span is empty. */
   Span stored_ids;
 };
@@ -96,7 +96,7 @@ struct MeshRealizeInfo {
   /** Maps old material indices to new material indices. */
   Array material_index_map;
   /** Matches the order in #AllMeshesInfo.attributes. */
-  Array> attributes;
+  Array> attributes;
   /** Vertex ids stored on the mesh. If there are no ids, this #Span is empty. */
   Span stored_vertex_ids;
 };
@@ -116,7 +116,7 @@ struct RealizeCurveInfo {
   /**
    * Matches the order in #AllCurvesInfo.attributes.
    */
-  Array> attributes;
+  Array> attributes;
 
   /** ID attribute on the curves. If there are no ids, this #Span is empty. */
   Span stored_ids;
@@ -283,7 +283,7 @@ static void threaded_fill(const GPointer value, GMutableSpan dst)
 }
 
 static void copy_generic_attributes_to_result(
-    const Span> src_attributes,
+    const Span> src_attributes,
     const AttributeFallbacksArray &attribute_fallbacks,
     const OrderedAttributes &ordered_attributes,
     const FunctionRef &range_fn,
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc
index 9077b59254c..01cf1d8db52 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc
@@ -135,7 +135,7 @@ class SampleCurveFunction : public fn::MultiFunction {
     }
 
     const VArray &lengths_varray = params.readonly_single_input(0, "Length");
-    const VArray_Span lengths{lengths_varray};
+    const VArraySpan lengths{lengths_varray};
 #ifdef DEBUG
     for (const float length : lengths) {
       /* Lengths must be in range of the curve's total length. This is ensured in
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc
index f846a6be53b..5c836391abe 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc
@@ -410,8 +410,8 @@ static void convert_to_bezier(const CurveComponent &src_component,
   };
 
   auto bezier_to_bezier = [&](IndexMask selection) {
-    const VArray_Span src_types_l = src_curves.handle_types_left();
-    const VArray_Span src_types_r = src_curves.handle_types_right();
+    const VArraySpan src_types_l = src_curves.handle_types_left();
+    const VArraySpan src_types_r = src_curves.handle_types_right();
     const Span src_handles_l = src_curves.handle_positions_left();
     const Span src_handles_r = src_curves.handle_positions_right();
 
@@ -569,7 +569,7 @@ static void convert_to_nurbs(const CurveComponent &src_component,
           src_cyclic.get_internal_single() ? NURBS_KNOT_MODE_NORMAL : NURBS_KNOT_MODE_ENDPOINT);
     }
     else {
-      VArray_Span cyclic{src_cyclic};
+      VArraySpan cyclic{src_cyclic};
       MutableSpan knots_modes = dst_curves.nurbs_knots_modes_for_write();
       threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) {
         for (const int i : selection.slice(range)) {
diff --git a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc
index 352411dd8f5..68489d1b9a6 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc
@@ -69,7 +69,7 @@ static void copy_attributes(const Map &attributes
 
     attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
       using T = decltype(dummy);
-      VArray_Span span{attribute.varray.typed()};
+      VArraySpan span{attribute.varray.typed()};
       MutableSpan out_span = result_attribute.as_span();
       out_span.copy_from(span);
     });
@@ -109,7 +109,7 @@ static void copy_attributes_based_on_mask(const Map span{attribute.varray.typed()};
+      VArraySpan span{attribute.varray.typed()};
       MutableSpan out_span = result_attribute.as_span();
       copy_data_based_on_mask(span, out_span, mask);
     });
@@ -145,7 +145,7 @@ static void copy_attributes_based_on_map(const Map span{attribute.varray.typed()};
+      VArraySpan span{attribute.varray.typed()};
       MutableSpan out_span = result_attribute.as_span();
       copy_data_based_on_map(span, out_span, index_map);
     });
@@ -1067,7 +1067,7 @@ static void separate_mesh_selection(GeometrySet &geometry_set,
     return;
   }
 
-  const VArray_Span selection_span{selection};
+  const VArraySpan selection_span{selection};
 
   do_mesh_separation(geometry_set, src_component, selection_span, selection_domain, mode);
 }
diff --git a/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc
index 52156b59c51..8a256a9b91b 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc
@@ -175,7 +175,7 @@ static void transfer_attributes(
 
     attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
       using T = decltype(dummy);
-      VArray_Span span{src_attribute.varray.typed()};
+      VArraySpan span{src_attribute.varray.typed()};
       MutableSpan dst_span = dst_attribute.as_span();
       if (src_attribute.domain == ATTR_DOMAIN_FACE) {
         dst_span.take_front(span.size()).copy_from(span);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
index 108a9443d58..5fe300e0a08 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
@@ -178,7 +178,7 @@ static void copy_stable_id_point(const Span offsets,
     return;
   }
 
-  VArray_Span src{src_attribute.varray.typed()};
+  VArraySpan src{src_attribute.varray.typed()};
   MutableSpan dst = dst_attribute.as_span();
   threaded_id_offset_copy(offsets, src, dst);
   dst_attribute.save();
@@ -211,7 +211,7 @@ static void copy_attributes_without_id(GeometrySet &geometry_set,
     }
     attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
       using T = decltype(dummy);
-      VArray_Span src = src_attribute.varray.typed();
+      VArraySpan src = src_attribute.varray.typed();
       MutableSpan dst = dst_attribute.as_span();
       threaded_slice_fill(offsets, selection, src, dst);
     });
@@ -258,7 +258,7 @@ static void copy_curve_attributes_without_id(const GeometrySet &geometry_set,
 
     attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
       using T = decltype(dummy);
-      VArray_Span src{src_attribute.varray.typed()};
+      VArraySpan src{src_attribute.varray.typed()};
       MutableSpan dst = dst_attribute.as_span();
 
       switch (out_domain) {
@@ -307,7 +307,7 @@ static void copy_stable_id_curves(const bke::CurvesGeometry &src_curves,
     return;
   }
 
-  VArray_Span src{src_attribute.varray.typed()};
+  VArraySpan src{src_attribute.varray.typed()};
   MutableSpan dst = dst_attribute.as_span();
 
   threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) {
@@ -439,7 +439,7 @@ static void copy_face_attributes_without_id(GeometrySet &geometry_set,
 
     attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
       using T = decltype(dummy);
-      VArray_Span src{src_attribute.varray.typed()};
+      VArraySpan src{src_attribute.varray.typed()};
       MutableSpan dst = dst_attribute.as_span();
 
       switch (out_domain) {
@@ -487,7 +487,7 @@ static void copy_stable_id_faces(const Mesh &mesh,
     return;
   }
 
-  VArray_Span src{src_attribute.varray.typed()};
+  VArraySpan src{src_attribute.varray.typed()};
   MutableSpan dst = dst_attribute.as_span();
 
   Span polys(mesh.mpoly, mesh.totpoly);
@@ -651,7 +651,7 @@ static void copy_edge_attributes_without_id(GeometrySet &geometry_set,
     }
     attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
       using T = decltype(dummy);
-      VArray_Span src{src_attribute.varray.typed()};
+      VArraySpan src{src_attribute.varray.typed()};
       MutableSpan dst = dst_attribute.as_span();
 
       switch (out_domain) {
@@ -691,7 +691,7 @@ static void copy_stable_id_edges(const Mesh &mesh,
 
   Span edges(mesh.medge, mesh.totedge);
 
-  VArray_Span src{src_attribute.varray.typed()};
+  VArraySpan src{src_attribute.varray.typed()};
   MutableSpan dst = dst_attribute.as_span();
   threading::parallel_for(IndexRange(selection.size()), 1024, [&](IndexRange range) {
     for (const int i_selection : range) {
@@ -853,7 +853,7 @@ static void duplicate_points_curve(GeometrySet &geometry_set,
 
     attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
       using T = decltype(dummy);
-      VArray_Span src{src_attribute.varray.typed()};
+      VArraySpan src{src_attribute.varray.typed()};
       MutableSpan dst = dst_attribute.as_span();
 
       switch (domain) {
diff --git a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc
index 0c56b0e9804..62cd1d8ac3a 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc
@@ -63,7 +63,7 @@ static void fill_new_attribute(Span src_components,
     GVArray read_attribute = component->attribute_get_for_read(
         attribute_id, domain, data_type, nullptr);
 
-    GVArray_GSpan src_span{read_attribute};
+    GVArraySpan src_span{read_attribute};
     const void *src_buffer = src_span.data();
     void *dst_buffer = dst_span[offset];
     cpp_type->copy_assign_n(src_buffer, dst_buffer, domain_num);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc
index 00b3d167755..95ff53b7146 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc
@@ -68,7 +68,7 @@ static void geometry_set_points_to_vertices(GeometrySet &geometry_set,
       attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
         using T = decltype(dummy);
         VArray src_typed = src.typed();
-        VArray_Span src_typed_span{src_typed};
+        VArraySpan src_typed_span{src_typed};
         copy_attribute_to_vertices(src_typed_span, selection, dst.as_span().typed());
       });
       dst.save();
-- 
cgit v1.2.3


From 9bb2afb55e50f9353cfc76cf2d8df7521b0b5feb Mon Sep 17 00:00:00 2001
From: Howard Trickey 
Date: Sat, 2 Jul 2022 10:09:18 -0400
Subject: Start of Bevel V2, as being worked on with task T98674.

This is the start of a geometry node to do edge, vertex, and face
bevels.

It doesn't yet do anything but analyze the "Vertex cap" around
selected vertices for vertex bevel.
---
 release/scripts/startup/nodeitems_builtins.py      |   1 +
 source/blender/blenkernel/BKE_node.h               |   1 +
 source/blender/blenkernel/intern/node.cc           |   1 +
 source/blender/makesdna/DNA_node_types.h           |  11 +
 source/blender/makesrna/intern/rna_nodetree.c      |  21 +
 source/blender/nodes/NOD_geometry.h                |   1 +
 source/blender/nodes/NOD_static_types.h            |   1 +
 source/blender/nodes/geometry/CMakeLists.txt       |   1 +
 .../nodes/geometry/nodes/node_geo_bevel_mesh.cc    | 461 +++++++++++++++++++++
 9 files changed, 499 insertions(+)
 create mode 100644 source/blender/nodes/geometry/nodes/node_geo_bevel_mesh.cc

diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py
index 21bb3d01616..e6c9d62905e 100644
--- a/release/scripts/startup/nodeitems_builtins.py
+++ b/release/scripts/startup/nodeitems_builtins.py
@@ -107,6 +107,7 @@ def mesh_node_items(context):
     space = context.space_data
     if not space:
         return
+    yield NodeItem("GeometryNodeBevelMesh")
     yield NodeItem("GeometryNodeDualMesh")
     yield NodeItem("GeometryNodeExtrudeMesh")
     yield NodeItem("GeometryNodeFlipFaces")
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index e13ac3180ec..dc86adc9046 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -1340,6 +1340,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i
 
 struct TexResult;
 
+#define GEO_NODE_BEVEL_MESH 1400
 #define TEX_NODE_OUTPUT 401
 #define TEX_NODE_CHECKER 402
 #define TEX_NODE_TEXTURE 403
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index 5be912ffb2b..ba9b28b4a29 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -4722,6 +4722,7 @@ static void registerGeometryNodes()
   register_node_type_geo_attribute_capture();
   register_node_type_geo_attribute_domain_size();
   register_node_type_geo_attribute_statistic();
+  register_node_type_geo_bevel_mesh();
   register_node_type_geo_boolean();
   register_node_type_geo_bounding_box();
   register_node_type_geo_collection_info();
diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h
index 76d8207eead..79e1c9c2c14 100644
--- a/source/blender/makesdna/DNA_node_types.h
+++ b/source/blender/makesdna/DNA_node_types.h
@@ -1221,6 +1221,11 @@ typedef struct NodeGeometryExtrudeMesh {
   uint8_t mode;
 } NodeGeometryExtrudeMesh;
 
+typedef struct NodeGeometryBevelMesh {
+  /* GeometryNodeBevelMode */
+  uint8_t mode;
+} NodeGeometryBevelMesh;
+
 typedef struct NodeGeometryObjectInfo {
   /* GeometryNodeTransformSpace. */
   uint8_t transform_space;
@@ -1966,6 +1971,12 @@ typedef enum GeometryNodeExtrudeMeshMode {
   GEO_NODE_EXTRUDE_MESH_FACES = 2,
 } GeometryNodeExtrudeMeshMode;
 
+typedef enum GeometryNodeBevelMeshMode {
+  GEO_NODE_BEVEL_MESH_VERTICES = 0,
+  GEO_NODE_BEVEL_MESH_EDGES = 1,
+  GEO_NODE_BEVEL_MESH_FACES = 2,
+} GeometryNodeBevelMeshMode;
+
 typedef enum FunctionNodeRotateEulerType {
   FN_NODE_ROTATE_EULER_TYPE_EULER = 0,
   FN_NODE_ROTATE_EULER_TYPE_AXIS_ANGLE = 1,
diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c
index 386ef3f74a3..2966dc97519 100644
--- a/source/blender/makesrna/intern/rna_nodetree.c
+++ b/source/blender/makesrna/intern/rna_nodetree.c
@@ -9551,6 +9551,27 @@ static void def_geo_extrude_mesh(StructRNA *srna)
   RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
 }
 
+static void def_geo_bevel_mesh(StructRNA *srna)
+{
+  PropertyRNA *prop;
+
+  static const EnumPropertyItem mode_items[] = {
+      {GEO_NODE_BEVEL_MESH_VERTICES, "VERTICES", 0, "Vertices", ""},
+      {GEO_NODE_BEVEL_MESH_EDGES, "EDGES", 0, "Edges", ""},
+      {GEO_NODE_BEVEL_MESH_FACES, "FACES", 0, "Faces", ""},
+      {0, NULL, 0, NULL, NULL},
+  };
+
+  RNA_def_struct_sdna_from(srna, "NodeGeometryBevelMesh", "storage");
+
+  prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
+  RNA_def_property_enum_sdna(prop, NULL, "mode");
+  RNA_def_property_enum_items(prop, mode_items);
+  RNA_def_property_enum_default(prop, GEO_NODE_BEVEL_MESH_FACES);
+  RNA_def_property_ui_text(prop, "Mode", "");
+  RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
+}
+
 static void def_geo_distribute_points_on_faces(StructRNA *srna)
 {
   PropertyRNA *prop;
diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h
index 8f15add33fd..7e813aef85e 100644
--- a/source/blender/nodes/NOD_geometry.h
+++ b/source/blender/nodes/NOD_geometry.h
@@ -20,6 +20,7 @@ void register_node_type_geo_attribute_capture(void);
 void register_node_type_geo_attribute_domain_size(void);
 void register_node_type_geo_attribute_separate_xyz(void);
 void register_node_type_geo_attribute_statistic(void);
+void register_node_type_geo_bevel_mesh(void);
 void register_node_type_geo_boolean(void);
 void register_node_type_geo_bounding_box(void);
 void register_node_type_geo_collection_info(void);
diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h
index 609791ad091..da324c98a26 100644
--- a/source/blender/nodes/NOD_static_types.h
+++ b/source/blender/nodes/NOD_static_types.h
@@ -281,6 +281,7 @@ DefNode(FunctionNode, FN_NODE_VALUE_TO_STRING, 0, "VALUE_TO_STRING", ValueToStri
 
 DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_DOMAIN_SIZE, def_geo_attribute_domain_size, "ATTRIBUTE_DOMAIN_SIZE", AttributeDomainSize, "Domain Size", "")
 DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_STATISTIC, def_geo_attribute_statistic, "ATTRIBUTE_STATISTIC", AttributeStatistic, "Attribute Statistic", "")
+DefNode(GeometryNode, GEO_NODE_BEVEL_MESH, def_geo_bevel_mesh, "BEVEL_MESH", BevelMesh, "Bevel Mesh", "")
 DefNode(GeometryNode, GEO_NODE_BOUNDING_BOX, 0, "BOUNDING_BOX", BoundBox, "Bounding Box", "")
 DefNode(GeometryNode, GEO_NODE_CAPTURE_ATTRIBUTE, def_geo_attribute_capture, "CAPTURE_ATTRIBUTE", CaptureAttribute, "Capture Attribute", "")
 DefNode(GeometryNode, GEO_NODE_COLLECTION_INFO, def_geo_collection_info, "COLLECTION_INFO", CollectionInfo, "Collection Info", "")
diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt
index 950124f75d0..1f83b375a84 100644
--- a/source/blender/nodes/geometry/CMakeLists.txt
+++ b/source/blender/nodes/geometry/CMakeLists.txt
@@ -29,6 +29,7 @@ set(SRC
   nodes/node_geo_attribute_capture.cc
   nodes/node_geo_attribute_domain_size.cc
   nodes/node_geo_attribute_statistic.cc
+  nodes/node_geo_bevel_mesh.cc
   nodes/node_geo_boolean.cc
   nodes/node_geo_bounding_box.cc
   nodes/node_geo_collection_info.cc
diff --git a/source/blender/nodes/geometry/nodes/node_geo_bevel_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_bevel_mesh.cc
new file mode 100644
index 00000000000..f14535a3c65
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_bevel_mesh.cc
@@ -0,0 +1,461 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "BKE_mesh.h"
+#include "BKE_mesh_mapping.h"
+#include "BKE_mesh_runtime.h"
+
+#include "BLI_array.hh"
+#include "BLI_set.hh"
+#include "BLI_sort.hh"
+#include "BLI_task.hh"
+#include "BLI_timeit.hh"
+#include "BLI_vector.hh"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "node_geometry_util.hh"
+
+#include 
+
+namespace blender::nodes::node_geo_bevel_mesh_cc {
+
+NODE_STORAGE_FUNCS(NodeGeometryBevelMesh)
+
+static void node_declare(NodeDeclarationBuilder &b)
+{
+  b.add_input("Mesh").supported_type(GEO_COMPONENT_TYPE_MESH);
+  b.add_input(N_("Selection")).default_value(true).supports_field().hide_value();
+  b.add_input(N_("Amount")).default_value(1.0f).supports_field();
+  b.add_output("Mesh");
+}
+
+static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
+{
+  uiLayoutSetPropSep(layout, true);
+  uiLayoutSetPropDecorate(layout, false);
+  uiItemR(layout, ptr, "mode", 0, "", ICON_NONE);
+}
+
+static void node_init(bNodeTree *UNUSED(tree), bNode *node)
+{
+  NodeGeometryBevelMesh *data = MEM_cnew(__func__);
+  data->mode = GEO_NODE_BEVEL_MESH_EDGES;
+  node->storage = data;
+}
+
+static void node_update(bNodeTree *UNUSED(ntree), bNode *UNUSED(node))
+{
+}
+
+/* While Mesh uses the term 'poly' for polygon, most of Blender uses the term 'face',
+ * so we'll go with 'face' in this code except in the final to/from mesh routines.
+ */
+class MeshTopology {
+  MeshElemMap *vert_edge_map_;
+  int *vert_edge_map_mem_;
+  MeshElemMap *edge_poly_map_;
+  int *edge_poly_map_mem_;
+  const Mesh &mesh_;
+
+ public:
+  MeshTopology(const Mesh &mesh);
+  ~MeshTopology();
+
+  /* Edges adjacent to vertex v. */
+  Span vert_edges(int v) const
+  {
+    const MeshElemMap &m = vert_edge_map_[v];
+    return Span{m.indices, m.count};
+  }
+
+  /* Faces adjacent to edge e. */
+  Span edge_faces(int e) const
+  {
+    const MeshElemMap &m = edge_poly_map_[e];
+    return Span{m.indices, m.count};
+  }
+
+  /* Does edge e have exactly two adjacent faces? */
+  bool edge_is_manifold(int e) const
+  {
+    return edge_poly_map_[e].count == 2;
+  }
+
+  /* What is the other manifold face (i.e., not f) attached to edge e?
+   * Edge e must be manifold and f must be one of the incident faces. */
+  int edge_other_manifold_face(int e, int f) const;
+
+  /* What is the other edge of f (i.e., not e) attached to vertex v.
+   * Face f must contain e, and e must have v as one of its vertices. */
+  int face_other_edge_at_vert(int f, int v, int e) const;
+
+  /* Is edge e1 the successor of e0 when going around face f? */
+  bool edge_is_successor_in_face(int e0, int e1, int f) const;
+
+  int num_verts() const
+  {
+    return mesh_.totvert;
+  }
+  int num_edges() const
+  {
+    return mesh_.totedge;
+  }
+  int num_faces() const
+  {
+    return mesh_.totpoly;
+  }
+};
+
+MeshTopology::MeshTopology(const Mesh &mesh) : mesh_(mesh)
+{
+  timeit::ScopedTimer t("MeshTopology construction");
+  BKE_mesh_vert_edge_map_create(
+      &vert_edge_map_, &vert_edge_map_mem_, mesh.medge, mesh.totvert, mesh.totedge);
+  BKE_mesh_edge_poly_map_create(&edge_poly_map_,
+                                &edge_poly_map_mem_,
+                                mesh.medge,
+                                mesh.totedge,
+                                mesh.mpoly,
+                                mesh.totpoly,
+                                mesh.mloop,
+                                mesh.totloop);
+}
+
+MeshTopology::~MeshTopology()
+{
+  MEM_freeN(vert_edge_map_);
+  MEM_freeN(vert_edge_map_mem_);
+  MEM_freeN(edge_poly_map_);
+  MEM_freeN(edge_poly_map_mem_);
+}
+
+int MeshTopology::edge_other_manifold_face(int e, int f) const
+{
+  const MeshElemMap &m = edge_poly_map_[e];
+  BLI_assert(m.count == 2);
+  if (m.indices[0] == f) {
+    return m.indices[1];
+  }
+  BLI_assert(m.indices[1] == f);
+  return m.indices[0];
+}
+
+int MeshTopology::face_other_edge_at_vert(int f, int v, int e) const
+{
+  const MPoly &mpoly = mesh_.mpoly[f];
+  const int loopstart = mpoly.loopstart;
+  const int loopend = mpoly.loopstart + mpoly.totloop - 1;
+  for (int l = loopstart; l <= loopend; l++) {
+    const MLoop &mloop = mesh_.mloop[l];
+    if (mloop.e == e) {
+      if (mloop.v == v) {
+        /* The other edge with vertex v is the preceding (incoming) edge. */
+        MLoop &mloop_prev = l == loopstart ? mesh_.mloop[loopend] : mesh_.mloop[l - 1];
+        return mloop_prev.e;
+      }
+      else {
+        /* The other edge with vertex v is the next (outgoing) edge, which should have vertex v. */
+        MLoop &mloop_next = l == loopend ? mesh_.mloop[loopstart] : mesh_.mloop[l + 1];
+        BLI_assert(mloop_next.v == v);
+        return mloop_next.e;
+      }
+    }
+  }
+  /* If didn't return in the loop, then there is no edge e with vertex v in face f. */
+  BLI_assert_unreachable();
+  return -1;
+}
+
+bool MeshTopology::edge_is_successor_in_face(const int e0, const int e1, const int f) const
+{
+  const MPoly &mpoly = mesh_.mpoly[f];
+  const int loopstart = mpoly.loopstart;
+  const int loopend = mpoly.loopstart + mpoly.totloop - 1;
+  for (int l = loopstart; l <= loopend; l++) {
+    const MLoop &mloop = mesh_.mloop[l];
+    if (mloop.e == e0) {
+      const MLoop &mloop_next = l == loopend ? mesh_.mloop[loopstart] : mesh_.mloop[l + 1];
+      return mloop_next.e == e1;
+    }
+  }
+  return false;
+}
+
+/* A Vertex Cap consists of a vertex in a mesh and an CCW ordering of
+ * alternating edges and faces around it, as viewed from the face's
+ * normal side. Some faces may be missing (i.e., gaps).
+ * (If there are other edges and faces attached to the vertex that
+ * don't fit into this pattern, they need to go into other Vertex Caps
+ * or ignored, for the sake of beveling.)
+ */
+class VertexCap {
+  Array edges_;
+  Array faces_;  // face_[i] is between edges i and i+1
+
+ public:
+  /* The vertex (as index into a mesh) that the cap is around. */
+  int vert;
+
+  VertexCap() : vert(-1)
+  {
+  }
+  VertexCap(int vert, Span edges, Span faces) : edges_(edges), faces_(faces), vert(vert)
+  {
+  }
+
+  /* The number of edges around the cap. */
+  int size() const
+  {
+    return edges_.size();
+  }
+
+  /* Edges in CCW order (viewed from top) around the cap. */
+  Span edges() const
+  {
+    return edges_.as_span();
+  }
+
+  /* Faces in CCW order (viewed from top) around the cap. -1 means a gap. */
+  Span faces() const
+  {
+    return faces_.as_span();
+  }
+
+  /* The ith edge. */
+  int edge(int i) const
+  {
+    return edges_[i];
+  }
+  /* The edge after the ith edge (with wraparound). */
+  int next_edge(int i) const
+  {
+    return i < edges_.size() - 1 ? edges_[i + 1] : edges_[0];
+  }
+  /* The edge before the ith edge (with wraparound). */
+  int prev_edge(int i) const
+  {
+    return i > 1 ? edges_[i - 1] : edges_.last();
+  }
+
+  /* The face returned may be -1, meaning "gap". */
+  /* The face betwen edge(i) and next_edge(i). */
+  int face(int i) const
+  {
+    return faces_[i];
+  }
+  /* The face between edge(i) and prev_edge(i). */
+  int prev_face(int i) const
+  {
+    return i > 1 ? faces_[i - 1] : faces_.last();
+  }
+  /* True if there is a gap between edges i and next_edge(i). */
+  bool is_gap(int i) const
+  {
+    return face(i) == -1;
+  }
+
+  /* Debug printing on std::cout. */
+  void print() const;
+};
+
+class BevelData {
+  Array bevel_vert_caps_;
+
+ public:
+  MeshTopology topo;
+
+  BevelData(const Mesh &mesh) : topo(mesh)
+  {
+  }
+  ~BevelData()
+  {
+  }
+
+  void init_caps_from_vertex_selection(const IndexMask selection);
+};
+
+/* Construct and return the VertexCap for vertex vert. */
+static VertexCap construct_cap(const int vert, const MeshTopology &topo)
+{
+  Span incident_edges = topo.vert_edges(vert);
+  const int num_edges = incident_edges.size();
+  if (num_edges == 0) {
+    return VertexCap(vert, Span(), Span());
+  }
+
+  /* First check for the most common case: a complete manifold cap:
+   * That is, each edge is incident on exactly two faces and the
+   * edge--face--edge--...--face chain forms a single cycle.
+   */
+  bool all_edges_manifold = true;
+  for (const int e : incident_edges) {
+    if (!topo.edge_is_manifold(e)) {
+      all_edges_manifold = false;
+      break;
+    }
+  }
+  if (all_edges_manifold) {
+    bool is_manifold_cap = true;
+    Array ordered_edges(num_edges, -1);
+    Array ordered_faces(num_edges, -1);
+    Set used_edges;
+    Set used_faces;
+
+    int next_edge = incident_edges[0];
+    for (int slot = 0; slot < num_edges; slot++) {
+      /* Invariant: ordered_edges and ordered_faces are filled
+       * up to slot-1 with a valid sequence for the cap, and
+       * next_edge is a valid continuation edge but we don't
+       * yet know if it has already been used.
+       */
+      ordered_edges[slot] = next_edge;
+      used_edges.add_new(next_edge);
+      /* Find a face attached to next_edge that is not yet used. */
+      int next_face;
+      if (slot == 0) {
+        next_face = topo.edge_faces(next_edge)[0];
+      }
+      else {
+        const int prev_face = ordered_faces[slot - 1];
+        next_face = topo.edge_other_manifold_face(next_edge, prev_face);
+      }
+      if (used_faces.contains(next_face)) {
+        is_manifold_cap = false;
+        break;
+      }
+      ordered_faces[slot] = next_face;
+      next_edge = topo.face_other_edge_at_vert(next_face, vert, next_edge);
+      if (slot < num_edges - 1 && used_edges.contains(next_edge)) {
+        is_manifold_cap = false;
+        break;
+      }
+    }
+    is_manifold_cap = is_manifold_cap && next_edge == ordered_edges[0];
+    if (is_manifold_cap) {
+      /* Check if cap is oriented properly, and fix it if not.
+       * A pair of successive edges in ordered_edges should be going CW
+       * in the face in between. For now, just check the first pair.
+       */
+      if (num_edges > 1) {
+        if (topo.edge_is_successor_in_face(ordered_edges[0], ordered_edges[1], ordered_faces[0])) {
+          /* They are in the wrong orientation, so we need to reverse.
+           * To make interleaving of edges and faces work out, reverse only 1..end of edges
+           * and reverse all of faces.
+           */
+          std::reverse(ordered_edges.begin() + 1, ordered_edges.end());
+          std::reverse(ordered_faces.begin(), ordered_faces.end());
+        }
+      }
+      return VertexCap(vert, ordered_edges.as_span(), ordered_faces.as_span());
+    }
+  }
+  std::cout << "to implement: VertexCap for non-manifold edges\n";
+  BLI_assert(false);
+  return VertexCap();
+}
+
+void VertexCap::print() const
+{
+  std::cout << "cap at v" << vert << ": ";
+  for (const int i : edges_.index_range()) {
+    std::cout << "e" << edges_[i] << " ";
+    if (faces_[i] == -1) {
+      std::cout << " ";
+    }
+    else {
+      std::cout << "f" << faces_[i] << " ";
+    }
+  }
+  std::cout << "\n";
+}
+
+void BevelData::init_caps_from_vertex_selection(const IndexMask selection)
+{
+  bevel_vert_caps_.reinitialize(selection.size());
+  threading::parallel_for(selection.index_range(), 1024, [&](const IndexRange range) {
+    for (const int i : range) {
+      bevel_vert_caps_[i] = construct_cap(selection[i], topo);
+    }
+  });
+}
+
+static void bevel_mesh_vertices(MeshComponent &component,
+                                const Field &selection_field,
+                                const Field &amount_field)
+{
+  Mesh &mesh = *component.get_for_write();
+  int orig_vert_size = mesh.totvert;
+  GeometryComponentFieldContext context(component, ATTR_DOMAIN_POINT);
+  FieldEvaluator evaluator{context, orig_vert_size};
+  evaluator.set_selection(selection_field);
+  evaluator.add(amount_field);
+  evaluator.evaluate();
+  VArray amounts = evaluator.get_evaluated(0);
+  const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
+
+  BevelData bdata(mesh);
+  bdata.init_caps_from_vertex_selection(selection);
+}
+
+static void bevel_mesh_edges(MeshComponent &UNUSED(component),
+                             const Field &UNUSED(selection_field),
+                             const Field &UNUSED(amount_field))
+{
+}
+
+static void bevel_mesh_faces(MeshComponent &UNUSED(component),
+                             const Field &UNUSED(selection_field),
+                             const Field &UNUSED(amount_field))
+{
+}
+
+static void node_geo_exec(GeoNodeExecParams params)
+{
+  GeometrySet geometry_set = params.extract_input("Mesh");
+  Field selection_field = params.extract_input>("Selection");
+  Field amount_field = params.extract_input>("Amount");
+  const NodeGeometryBevelMesh &storage = node_storage(params.node());
+  GeometryNodeBevelMeshMode mode = static_cast(storage.mode);
+
+  geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
+    if (geometry_set.has_mesh()) {
+      MeshComponent &component = geometry_set.get_component_for_write();
+      switch (mode) {
+        case GEO_NODE_BEVEL_MESH_VERTICES:
+          bevel_mesh_vertices(component, selection_field, amount_field);
+          break;
+        case GEO_NODE_BEVEL_MESH_EDGES:
+          bevel_mesh_edges(component, selection_field, amount_field);
+          break;
+        case GEO_NODE_BEVEL_MESH_FACES:
+          bevel_mesh_faces(component, selection_field, amount_field);
+          break;
+      }
+      BLI_assert(BKE_mesh_is_valid(component.get_for_write()));
+    }
+  });
+
+  params.set_output("Mesh", std::move(geometry_set));
+}
+
+}  // namespace blender::nodes::node_geo_bevel_mesh_cc
+
+void register_node_type_geo_bevel_mesh()
+{
+  namespace file_ns = blender::nodes::node_geo_bevel_mesh_cc;
+
+  static bNodeType ntype;
+  geo_node_type_base(&ntype, GEO_NODE_BEVEL_MESH, "Bevel Mesh", NODE_CLASS_GEOMETRY);
+  ntype.declare = file_ns::node_declare;
+  node_type_init(&ntype, file_ns::node_init);
+  node_type_update(&ntype, file_ns::node_update);
+  ntype.geometry_node_execute = file_ns::node_geo_exec;
+  node_type_storage(
+      &ntype, "NodeGeometryBevelMesh", node_free_standard_storage, node_copy_standard_storage);
+  ntype.draw_buttons = file_ns::node_layout;
+  nodeRegisterType(&ntype);
+}
-- 
cgit v1.2.3


From 01d7dedd745e2dce6f0f7857367ad8507ed60178 Mon Sep 17 00:00:00 2001
From: Howard Trickey 
Date: Sat, 2 Jul 2022 10:14:26 -0400
Subject: Revert "Start of Bevel V2, as being worked on with task T98674."

This reverts commit 9bb2afb55e50f9353cfc76cf2d8df7521b0b5feb.
Oops, did not intend to commit this to master.
---
 release/scripts/startup/nodeitems_builtins.py      |   1 -
 source/blender/blenkernel/BKE_node.h               |   1 -
 source/blender/blenkernel/intern/node.cc           |   1 -
 source/blender/makesdna/DNA_node_types.h           |  11 -
 source/blender/makesrna/intern/rna_nodetree.c      |  21 -
 source/blender/nodes/NOD_geometry.h                |   1 -
 source/blender/nodes/NOD_static_types.h            |   1 -
 source/blender/nodes/geometry/CMakeLists.txt       |   1 -
 .../nodes/geometry/nodes/node_geo_bevel_mesh.cc    | 461 ---------------------
 9 files changed, 499 deletions(-)
 delete mode 100644 source/blender/nodes/geometry/nodes/node_geo_bevel_mesh.cc

diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py
index e6c9d62905e..21bb3d01616 100644
--- a/release/scripts/startup/nodeitems_builtins.py
+++ b/release/scripts/startup/nodeitems_builtins.py
@@ -107,7 +107,6 @@ def mesh_node_items(context):
     space = context.space_data
     if not space:
         return
-    yield NodeItem("GeometryNodeBevelMesh")
     yield NodeItem("GeometryNodeDualMesh")
     yield NodeItem("GeometryNodeExtrudeMesh")
     yield NodeItem("GeometryNodeFlipFaces")
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index dc86adc9046..e13ac3180ec 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -1340,7 +1340,6 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i
 
 struct TexResult;
 
-#define GEO_NODE_BEVEL_MESH 1400
 #define TEX_NODE_OUTPUT 401
 #define TEX_NODE_CHECKER 402
 #define TEX_NODE_TEXTURE 403
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index ba9b28b4a29..5be912ffb2b 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -4722,7 +4722,6 @@ static void registerGeometryNodes()
   register_node_type_geo_attribute_capture();
   register_node_type_geo_attribute_domain_size();
   register_node_type_geo_attribute_statistic();
-  register_node_type_geo_bevel_mesh();
   register_node_type_geo_boolean();
   register_node_type_geo_bounding_box();
   register_node_type_geo_collection_info();
diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h
index 79e1c9c2c14..76d8207eead 100644
--- a/source/blender/makesdna/DNA_node_types.h
+++ b/source/blender/makesdna/DNA_node_types.h
@@ -1221,11 +1221,6 @@ typedef struct NodeGeometryExtrudeMesh {
   uint8_t mode;
 } NodeGeometryExtrudeMesh;
 
-typedef struct NodeGeometryBevelMesh {
-  /* GeometryNodeBevelMode */
-  uint8_t mode;
-} NodeGeometryBevelMesh;
-
 typedef struct NodeGeometryObjectInfo {
   /* GeometryNodeTransformSpace. */
   uint8_t transform_space;
@@ -1971,12 +1966,6 @@ typedef enum GeometryNodeExtrudeMeshMode {
   GEO_NODE_EXTRUDE_MESH_FACES = 2,
 } GeometryNodeExtrudeMeshMode;
 
-typedef enum GeometryNodeBevelMeshMode {
-  GEO_NODE_BEVEL_MESH_VERTICES = 0,
-  GEO_NODE_BEVEL_MESH_EDGES = 1,
-  GEO_NODE_BEVEL_MESH_FACES = 2,
-} GeometryNodeBevelMeshMode;
-
 typedef enum FunctionNodeRotateEulerType {
   FN_NODE_ROTATE_EULER_TYPE_EULER = 0,
   FN_NODE_ROTATE_EULER_TYPE_AXIS_ANGLE = 1,
diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c
index 2966dc97519..386ef3f74a3 100644
--- a/source/blender/makesrna/intern/rna_nodetree.c
+++ b/source/blender/makesrna/intern/rna_nodetree.c
@@ -9551,27 +9551,6 @@ static void def_geo_extrude_mesh(StructRNA *srna)
   RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
 }
 
-static void def_geo_bevel_mesh(StructRNA *srna)
-{
-  PropertyRNA *prop;
-
-  static const EnumPropertyItem mode_items[] = {
-      {GEO_NODE_BEVEL_MESH_VERTICES, "VERTICES", 0, "Vertices", ""},
-      {GEO_NODE_BEVEL_MESH_EDGES, "EDGES", 0, "Edges", ""},
-      {GEO_NODE_BEVEL_MESH_FACES, "FACES", 0, "Faces", ""},
-      {0, NULL, 0, NULL, NULL},
-  };
-
-  RNA_def_struct_sdna_from(srna, "NodeGeometryBevelMesh", "storage");
-
-  prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
-  RNA_def_property_enum_sdna(prop, NULL, "mode");
-  RNA_def_property_enum_items(prop, mode_items);
-  RNA_def_property_enum_default(prop, GEO_NODE_BEVEL_MESH_FACES);
-  RNA_def_property_ui_text(prop, "Mode", "");
-  RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
-}
-
 static void def_geo_distribute_points_on_faces(StructRNA *srna)
 {
   PropertyRNA *prop;
diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h
index 7e813aef85e..8f15add33fd 100644
--- a/source/blender/nodes/NOD_geometry.h
+++ b/source/blender/nodes/NOD_geometry.h
@@ -20,7 +20,6 @@ void register_node_type_geo_attribute_capture(void);
 void register_node_type_geo_attribute_domain_size(void);
 void register_node_type_geo_attribute_separate_xyz(void);
 void register_node_type_geo_attribute_statistic(void);
-void register_node_type_geo_bevel_mesh(void);
 void register_node_type_geo_boolean(void);
 void register_node_type_geo_bounding_box(void);
 void register_node_type_geo_collection_info(void);
diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h
index da324c98a26..609791ad091 100644
--- a/source/blender/nodes/NOD_static_types.h
+++ b/source/blender/nodes/NOD_static_types.h
@@ -281,7 +281,6 @@ DefNode(FunctionNode, FN_NODE_VALUE_TO_STRING, 0, "VALUE_TO_STRING", ValueToStri
 
 DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_DOMAIN_SIZE, def_geo_attribute_domain_size, "ATTRIBUTE_DOMAIN_SIZE", AttributeDomainSize, "Domain Size", "")
 DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_STATISTIC, def_geo_attribute_statistic, "ATTRIBUTE_STATISTIC", AttributeStatistic, "Attribute Statistic", "")
-DefNode(GeometryNode, GEO_NODE_BEVEL_MESH, def_geo_bevel_mesh, "BEVEL_MESH", BevelMesh, "Bevel Mesh", "")
 DefNode(GeometryNode, GEO_NODE_BOUNDING_BOX, 0, "BOUNDING_BOX", BoundBox, "Bounding Box", "")
 DefNode(GeometryNode, GEO_NODE_CAPTURE_ATTRIBUTE, def_geo_attribute_capture, "CAPTURE_ATTRIBUTE", CaptureAttribute, "Capture Attribute", "")
 DefNode(GeometryNode, GEO_NODE_COLLECTION_INFO, def_geo_collection_info, "COLLECTION_INFO", CollectionInfo, "Collection Info", "")
diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt
index 1f83b375a84..950124f75d0 100644
--- a/source/blender/nodes/geometry/CMakeLists.txt
+++ b/source/blender/nodes/geometry/CMakeLists.txt
@@ -29,7 +29,6 @@ set(SRC
   nodes/node_geo_attribute_capture.cc
   nodes/node_geo_attribute_domain_size.cc
   nodes/node_geo_attribute_statistic.cc
-  nodes/node_geo_bevel_mesh.cc
   nodes/node_geo_boolean.cc
   nodes/node_geo_bounding_box.cc
   nodes/node_geo_collection_info.cc
diff --git a/source/blender/nodes/geometry/nodes/node_geo_bevel_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_bevel_mesh.cc
deleted file mode 100644
index f14535a3c65..00000000000
--- a/source/blender/nodes/geometry/nodes/node_geo_bevel_mesh.cc
+++ /dev/null
@@ -1,461 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-
-#include "DNA_mesh_types.h"
-#include "DNA_meshdata_types.h"
-
-#include "BKE_mesh.h"
-#include "BKE_mesh_mapping.h"
-#include "BKE_mesh_runtime.h"
-
-#include "BLI_array.hh"
-#include "BLI_set.hh"
-#include "BLI_sort.hh"
-#include "BLI_task.hh"
-#include "BLI_timeit.hh"
-#include "BLI_vector.hh"
-
-#include "UI_interface.h"
-#include "UI_resources.h"
-
-#include "node_geometry_util.hh"
-
-#include 
-
-namespace blender::nodes::node_geo_bevel_mesh_cc {
-
-NODE_STORAGE_FUNCS(NodeGeometryBevelMesh)
-
-static void node_declare(NodeDeclarationBuilder &b)
-{
-  b.add_input("Mesh").supported_type(GEO_COMPONENT_TYPE_MESH);
-  b.add_input(N_("Selection")).default_value(true).supports_field().hide_value();
-  b.add_input(N_("Amount")).default_value(1.0f).supports_field();
-  b.add_output("Mesh");
-}
-
-static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
-{
-  uiLayoutSetPropSep(layout, true);
-  uiLayoutSetPropDecorate(layout, false);
-  uiItemR(layout, ptr, "mode", 0, "", ICON_NONE);
-}
-
-static void node_init(bNodeTree *UNUSED(tree), bNode *node)
-{
-  NodeGeometryBevelMesh *data = MEM_cnew(__func__);
-  data->mode = GEO_NODE_BEVEL_MESH_EDGES;
-  node->storage = data;
-}
-
-static void node_update(bNodeTree *UNUSED(ntree), bNode *UNUSED(node))
-{
-}
-
-/* While Mesh uses the term 'poly' for polygon, most of Blender uses the term 'face',
- * so we'll go with 'face' in this code except in the final to/from mesh routines.
- */
-class MeshTopology {
-  MeshElemMap *vert_edge_map_;
-  int *vert_edge_map_mem_;
-  MeshElemMap *edge_poly_map_;
-  int *edge_poly_map_mem_;
-  const Mesh &mesh_;
-
- public:
-  MeshTopology(const Mesh &mesh);
-  ~MeshTopology();
-
-  /* Edges adjacent to vertex v. */
-  Span vert_edges(int v) const
-  {
-    const MeshElemMap &m = vert_edge_map_[v];
-    return Span{m.indices, m.count};
-  }
-
-  /* Faces adjacent to edge e. */
-  Span edge_faces(int e) const
-  {
-    const MeshElemMap &m = edge_poly_map_[e];
-    return Span{m.indices, m.count};
-  }
-
-  /* Does edge e have exactly two adjacent faces? */
-  bool edge_is_manifold(int e) const
-  {
-    return edge_poly_map_[e].count == 2;
-  }
-
-  /* What is the other manifold face (i.e., not f) attached to edge e?
-   * Edge e must be manifold and f must be one of the incident faces. */
-  int edge_other_manifold_face(int e, int f) const;
-
-  /* What is the other edge of f (i.e., not e) attached to vertex v.
-   * Face f must contain e, and e must have v as one of its vertices. */
-  int face_other_edge_at_vert(int f, int v, int e) const;
-
-  /* Is edge e1 the successor of e0 when going around face f? */
-  bool edge_is_successor_in_face(int e0, int e1, int f) const;
-
-  int num_verts() const
-  {
-    return mesh_.totvert;
-  }
-  int num_edges() const
-  {
-    return mesh_.totedge;
-  }
-  int num_faces() const
-  {
-    return mesh_.totpoly;
-  }
-};
-
-MeshTopology::MeshTopology(const Mesh &mesh) : mesh_(mesh)
-{
-  timeit::ScopedTimer t("MeshTopology construction");
-  BKE_mesh_vert_edge_map_create(
-      &vert_edge_map_, &vert_edge_map_mem_, mesh.medge, mesh.totvert, mesh.totedge);
-  BKE_mesh_edge_poly_map_create(&edge_poly_map_,
-                                &edge_poly_map_mem_,
-                                mesh.medge,
-                                mesh.totedge,
-                                mesh.mpoly,
-                                mesh.totpoly,
-                                mesh.mloop,
-                                mesh.totloop);
-}
-
-MeshTopology::~MeshTopology()
-{
-  MEM_freeN(vert_edge_map_);
-  MEM_freeN(vert_edge_map_mem_);
-  MEM_freeN(edge_poly_map_);
-  MEM_freeN(edge_poly_map_mem_);
-}
-
-int MeshTopology::edge_other_manifold_face(int e, int f) const
-{
-  const MeshElemMap &m = edge_poly_map_[e];
-  BLI_assert(m.count == 2);
-  if (m.indices[0] == f) {
-    return m.indices[1];
-  }
-  BLI_assert(m.indices[1] == f);
-  return m.indices[0];
-}
-
-int MeshTopology::face_other_edge_at_vert(int f, int v, int e) const
-{
-  const MPoly &mpoly = mesh_.mpoly[f];
-  const int loopstart = mpoly.loopstart;
-  const int loopend = mpoly.loopstart + mpoly.totloop - 1;
-  for (int l = loopstart; l <= loopend; l++) {
-    const MLoop &mloop = mesh_.mloop[l];
-    if (mloop.e == e) {
-      if (mloop.v == v) {
-        /* The other edge with vertex v is the preceding (incoming) edge. */
-        MLoop &mloop_prev = l == loopstart ? mesh_.mloop[loopend] : mesh_.mloop[l - 1];
-        return mloop_prev.e;
-      }
-      else {
-        /* The other edge with vertex v is the next (outgoing) edge, which should have vertex v. */
-        MLoop &mloop_next = l == loopend ? mesh_.mloop[loopstart] : mesh_.mloop[l + 1];
-        BLI_assert(mloop_next.v == v);
-        return mloop_next.e;
-      }
-    }
-  }
-  /* If didn't return in the loop, then there is no edge e with vertex v in face f. */
-  BLI_assert_unreachable();
-  return -1;
-}
-
-bool MeshTopology::edge_is_successor_in_face(const int e0, const int e1, const int f) const
-{
-  const MPoly &mpoly = mesh_.mpoly[f];
-  const int loopstart = mpoly.loopstart;
-  const int loopend = mpoly.loopstart + mpoly.totloop - 1;
-  for (int l = loopstart; l <= loopend; l++) {
-    const MLoop &mloop = mesh_.mloop[l];
-    if (mloop.e == e0) {
-      const MLoop &mloop_next = l == loopend ? mesh_.mloop[loopstart] : mesh_.mloop[l + 1];
-      return mloop_next.e == e1;
-    }
-  }
-  return false;
-}
-
-/* A Vertex Cap consists of a vertex in a mesh and an CCW ordering of
- * alternating edges and faces around it, as viewed from the face's
- * normal side. Some faces may be missing (i.e., gaps).
- * (If there are other edges and faces attached to the vertex that
- * don't fit into this pattern, they need to go into other Vertex Caps
- * or ignored, for the sake of beveling.)
- */
-class VertexCap {
-  Array edges_;
-  Array faces_;  // face_[i] is between edges i and i+1
-
- public:
-  /* The vertex (as index into a mesh) that the cap is around. */
-  int vert;
-
-  VertexCap() : vert(-1)
-  {
-  }
-  VertexCap(int vert, Span edges, Span faces) : edges_(edges), faces_(faces), vert(vert)
-  {
-  }
-
-  /* The number of edges around the cap. */
-  int size() const
-  {
-    return edges_.size();
-  }
-
-  /* Edges in CCW order (viewed from top) around the cap. */
-  Span edges() const
-  {
-    return edges_.as_span();
-  }
-
-  /* Faces in CCW order (viewed from top) around the cap. -1 means a gap. */
-  Span faces() const
-  {
-    return faces_.as_span();
-  }
-
-  /* The ith edge. */
-  int edge(int i) const
-  {
-    return edges_[i];
-  }
-  /* The edge after the ith edge (with wraparound). */
-  int next_edge(int i) const
-  {
-    return i < edges_.size() - 1 ? edges_[i + 1] : edges_[0];
-  }
-  /* The edge before the ith edge (with wraparound). */
-  int prev_edge(int i) const
-  {
-    return i > 1 ? edges_[i - 1] : edges_.last();
-  }
-
-  /* The face returned may be -1, meaning "gap". */
-  /* The face betwen edge(i) and next_edge(i). */
-  int face(int i) const
-  {
-    return faces_[i];
-  }
-  /* The face between edge(i) and prev_edge(i). */
-  int prev_face(int i) const
-  {
-    return i > 1 ? faces_[i - 1] : faces_.last();
-  }
-  /* True if there is a gap between edges i and next_edge(i). */
-  bool is_gap(int i) const
-  {
-    return face(i) == -1;
-  }
-
-  /* Debug printing on std::cout. */
-  void print() const;
-};
-
-class BevelData {
-  Array bevel_vert_caps_;
-
- public:
-  MeshTopology topo;
-
-  BevelData(const Mesh &mesh) : topo(mesh)
-  {
-  }
-  ~BevelData()
-  {
-  }
-
-  void init_caps_from_vertex_selection(const IndexMask selection);
-};
-
-/* Construct and return the VertexCap for vertex vert. */
-static VertexCap construct_cap(const int vert, const MeshTopology &topo)
-{
-  Span incident_edges = topo.vert_edges(vert);
-  const int num_edges = incident_edges.size();
-  if (num_edges == 0) {
-    return VertexCap(vert, Span(), Span());
-  }
-
-  /* First check for the most common case: a complete manifold cap:
-   * That is, each edge is incident on exactly two faces and the
-   * edge--face--edge--...--face chain forms a single cycle.
-   */
-  bool all_edges_manifold = true;
-  for (const int e : incident_edges) {
-    if (!topo.edge_is_manifold(e)) {
-      all_edges_manifold = false;
-      break;
-    }
-  }
-  if (all_edges_manifold) {
-    bool is_manifold_cap = true;
-    Array ordered_edges(num_edges, -1);
-    Array ordered_faces(num_edges, -1);
-    Set used_edges;
-    Set used_faces;
-
-    int next_edge = incident_edges[0];
-    for (int slot = 0; slot < num_edges; slot++) {
-      /* Invariant: ordered_edges and ordered_faces are filled
-       * up to slot-1 with a valid sequence for the cap, and
-       * next_edge is a valid continuation edge but we don't
-       * yet know if it has already been used.
-       */
-      ordered_edges[slot] = next_edge;
-      used_edges.add_new(next_edge);
-      /* Find a face attached to next_edge that is not yet used. */
-      int next_face;
-      if (slot == 0) {
-        next_face = topo.edge_faces(next_edge)[0];
-      }
-      else {
-        const int prev_face = ordered_faces[slot - 1];
-        next_face = topo.edge_other_manifold_face(next_edge, prev_face);
-      }
-      if (used_faces.contains(next_face)) {
-        is_manifold_cap = false;
-        break;
-      }
-      ordered_faces[slot] = next_face;
-      next_edge = topo.face_other_edge_at_vert(next_face, vert, next_edge);
-      if (slot < num_edges - 1 && used_edges.contains(next_edge)) {
-        is_manifold_cap = false;
-        break;
-      }
-    }
-    is_manifold_cap = is_manifold_cap && next_edge == ordered_edges[0];
-    if (is_manifold_cap) {
-      /* Check if cap is oriented properly, and fix it if not.
-       * A pair of successive edges in ordered_edges should be going CW
-       * in the face in between. For now, just check the first pair.
-       */
-      if (num_edges > 1) {
-        if (topo.edge_is_successor_in_face(ordered_edges[0], ordered_edges[1], ordered_faces[0])) {
-          /* They are in the wrong orientation, so we need to reverse.
-           * To make interleaving of edges and faces work out, reverse only 1..end of edges
-           * and reverse all of faces.
-           */
-          std::reverse(ordered_edges.begin() + 1, ordered_edges.end());
-          std::reverse(ordered_faces.begin(), ordered_faces.end());
-        }
-      }
-      return VertexCap(vert, ordered_edges.as_span(), ordered_faces.as_span());
-    }
-  }
-  std::cout << "to implement: VertexCap for non-manifold edges\n";
-  BLI_assert(false);
-  return VertexCap();
-}
-
-void VertexCap::print() const
-{
-  std::cout << "cap at v" << vert << ": ";
-  for (const int i : edges_.index_range()) {
-    std::cout << "e" << edges_[i] << " ";
-    if (faces_[i] == -1) {
-      std::cout << " ";
-    }
-    else {
-      std::cout << "f" << faces_[i] << " ";
-    }
-  }
-  std::cout << "\n";
-}
-
-void BevelData::init_caps_from_vertex_selection(const IndexMask selection)
-{
-  bevel_vert_caps_.reinitialize(selection.size());
-  threading::parallel_for(selection.index_range(), 1024, [&](const IndexRange range) {
-    for (const int i : range) {
-      bevel_vert_caps_[i] = construct_cap(selection[i], topo);
-    }
-  });
-}
-
-static void bevel_mesh_vertices(MeshComponent &component,
-                                const Field &selection_field,
-                                const Field &amount_field)
-{
-  Mesh &mesh = *component.get_for_write();
-  int orig_vert_size = mesh.totvert;
-  GeometryComponentFieldContext context(component, ATTR_DOMAIN_POINT);
-  FieldEvaluator evaluator{context, orig_vert_size};
-  evaluator.set_selection(selection_field);
-  evaluator.add(amount_field);
-  evaluator.evaluate();
-  VArray amounts = evaluator.get_evaluated(0);
-  const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
-
-  BevelData bdata(mesh);
-  bdata.init_caps_from_vertex_selection(selection);
-}
-
-static void bevel_mesh_edges(MeshComponent &UNUSED(component),
-                             const Field &UNUSED(selection_field),
-                             const Field &UNUSED(amount_field))
-{
-}
-
-static void bevel_mesh_faces(MeshComponent &UNUSED(component),
-                             const Field &UNUSED(selection_field),
-                             const Field &UNUSED(amount_field))
-{
-}
-
-static void node_geo_exec(GeoNodeExecParams params)
-{
-  GeometrySet geometry_set = params.extract_input("Mesh");
-  Field selection_field = params.extract_input>("Selection");
-  Field amount_field = params.extract_input>("Amount");
-  const NodeGeometryBevelMesh &storage = node_storage(params.node());
-  GeometryNodeBevelMeshMode mode = static_cast(storage.mode);
-
-  geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
-    if (geometry_set.has_mesh()) {
-      MeshComponent &component = geometry_set.get_component_for_write();
-      switch (mode) {
-        case GEO_NODE_BEVEL_MESH_VERTICES:
-          bevel_mesh_vertices(component, selection_field, amount_field);
-          break;
-        case GEO_NODE_BEVEL_MESH_EDGES:
-          bevel_mesh_edges(component, selection_field, amount_field);
-          break;
-        case GEO_NODE_BEVEL_MESH_FACES:
-          bevel_mesh_faces(component, selection_field, amount_field);
-          break;
-      }
-      BLI_assert(BKE_mesh_is_valid(component.get_for_write()));
-    }
-  });
-
-  params.set_output("Mesh", std::move(geometry_set));
-}
-
-}  // namespace blender::nodes::node_geo_bevel_mesh_cc
-
-void register_node_type_geo_bevel_mesh()
-{
-  namespace file_ns = blender::nodes::node_geo_bevel_mesh_cc;
-
-  static bNodeType ntype;
-  geo_node_type_base(&ntype, GEO_NODE_BEVEL_MESH, "Bevel Mesh", NODE_CLASS_GEOMETRY);
-  ntype.declare = file_ns::node_declare;
-  node_type_init(&ntype, file_ns::node_init);
-  node_type_update(&ntype, file_ns::node_update);
-  ntype.geometry_node_execute = file_ns::node_geo_exec;
-  node_type_storage(
-      &ntype, "NodeGeometryBevelMesh", node_free_standard_storage, node_copy_standard_storage);
-  ntype.draw_buttons = file_ns::node_layout;
-  nodeRegisterType(&ntype);
-}
-- 
cgit v1.2.3


From 69ee9ca90e36aa6a74317262fccb1e3e2e9210bc Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Sat, 2 Jul 2022 13:04:55 -0500
Subject: Fix: Build error with unity builds off after recent cleanup

Mistake in df8d96ab66adf46603b36
---
 source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc | 2 --
 1 file changed, 2 deletions(-)

diff --git a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc
index 31f706c497c..045206d04cd 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc
@@ -22,8 +22,6 @@ static void node_declare(NodeDeclarationBuilder &b)
   b.add_output(N_("Convex Hull"));
 }
 
-using bke::GeometryInstanceGroup;
-
 #ifdef WITH_BULLET
 
 static Mesh *hull_from_bullet(const Mesh *mesh, Span coords)
-- 
cgit v1.2.3


From ab444a80a280e125b3e4d002941504d56f340ced Mon Sep 17 00:00:00 2001
From: Jacques Lucke 
Date: Sat, 2 Jul 2022 21:51:45 +0200
Subject: BLI: refactor length parameterization

This refactor had two main goals:
* Simplify the sampling code by using an algorithm with fewer special cases.
* Generalize the sampling to support non-sorted samples.

The `SampleSegmentHint` optimization was inspired by `ValueAccessor` from
OpenVDB and improves performance 2x in my test cases.

Differential Revision: https://developer.blender.org/D15348
---
 source/blender/blenlib/BLI_length_parameterize.hh  | 133 +++++++++++++-----
 .../blender/blenlib/intern/length_parameterize.cc  | 150 +++++----------------
 .../blenlib/tests/BLI_length_parameterize_test.cc  |  20 +--
 .../editors/sculpt_paint/curves_sculpt_brush.cc    |   5 +-
 source/blender/geometry/intern/resample_curves.cc  |   9 +-
 5 files changed, 147 insertions(+), 170 deletions(-)

diff --git a/source/blender/blenlib/BLI_length_parameterize.hh b/source/blender/blenlib/BLI_length_parameterize.hh
index ec0fabb75b4..ff957d92263 100644
--- a/source/blender/blenlib/BLI_length_parameterize.hh
+++ b/source/blender/blenlib/BLI_length_parameterize.hh
@@ -19,7 +19,7 @@ namespace blender::length_parameterize {
  *
  * \note This is the same as #bke::curves::curve_segment_num.
  */
-inline int lengths_num(const int points_num, const bool cyclic)
+inline int segments_num(const int points_num, const bool cyclic)
 {
   return cyclic ? points_num : points_num - 1;
 }
@@ -30,7 +30,7 @@ inline int lengths_num(const int points_num, const bool cyclic)
 template
 void accumulate_lengths(const Span values, const bool cyclic, MutableSpan lengths)
 {
-  BLI_assert(lengths.size() == lengths_num(values.size(), cyclic));
+  BLI_assert(lengths.size() == segments_num(values.size(), cyclic));
   float length = 0.0f;
   for (const int i : IndexRange(values.size() - 1)) {
     length += math::distance(values[i], values[i + 1]);
@@ -42,57 +42,122 @@ void accumulate_lengths(const Span values, const bool cyclic, MutableSpan
-void linear_interpolation(const Span src,
-                          const Span indices,
-                          const Span factors,
-                          MutableSpan dst)
+inline void linear_interpolation(const Span src,
+                                 const Span indices,
+                                 const Span factors,
+                                 MutableSpan dst)
 {
   BLI_assert(indices.size() == factors.size());
   BLI_assert(indices.size() == dst.size());
-  const int last_src_index = src.index_range().last();
+  const int last_src_index = src.size() - 1;
 
-  int cyclic_sample_count = 0;
-  for (int i = indices.index_range().last(); i > 0; i--) {
-    if (indices[i] != last_src_index) {
-      break;
+  for (const int i : dst.index_range()) {
+    const int prev_index = indices[i];
+    const float factor = factors[i];
+    const bool is_cyclic_case = prev_index == last_src_index;
+    if (is_cyclic_case) {
+      dst[i] = math::interpolate(src.last(), src.first(), factor);
+    }
+    else {
+      dst[i] = math::interpolate(src[prev_index], src[prev_index + 1], factor);
+    }
+  }
+}
+
+/**
+ * Passing this to consecutive calls of #sample_at_length can increase performance.
+ */
+struct SampleSegmentHint {
+  int segment_index = -1;
+  float segment_start;
+  float segment_length_inv;
+};
+
+/**
+ * \param accumulated_segment_lengths: Lengths of individual segments added up.
+ * \param sample_length: The position to sample at.
+ * \param r_segment_index: Returns the index of the segment that #sample_length is in.
+ * \param r_factor: Returns the position within the segment.
+ *
+ * \note #sample_length must not be outside of any segment.
+ */
+inline void sample_at_length(const Span accumulated_segment_lengths,
+                             float sample_length,
+                             int &r_segment_index,
+                             float &r_factor,
+                             SampleSegmentHint *hint = nullptr)
+{
+  /* Use a shorter variable name. */
+  const Span lengths = accumulated_segment_lengths;
+
+  BLI_assert(lengths.size() > 0);
+  BLI_assert(sample_length >= 0.0f);
+  BLI_assert(sample_length <= lengths.last());
+
+  if (hint != nullptr && hint->segment_index >= 0) {
+    const float length_in_segment = sample_length - hint->segment_start;
+    const float factor = length_in_segment * hint->segment_length_inv;
+    if (factor >= 0.0f && factor < 1.0f) {
+      r_segment_index = hint->segment_index;
+      r_factor = factor;
+      return;
     }
-    dst[i] = math::interpolate(src.last(), src.first(), factors[i]);
-    cyclic_sample_count++;
   }
 
-  for (const int i : dst.index_range().drop_back(cyclic_sample_count)) {
-    dst[i] = math::interpolate(src[indices[i]], src[indices[i] + 1], factors[i]);
+  const float total_length = lengths.last();
+  if (sample_length >= total_length) {
+    /* Return the last position on the last segment. */
+    r_segment_index = lengths.size() - 1;
+    r_factor = 1.0f;
+    return;
+  }
+
+  const int prev_point_index = std::upper_bound(lengths.begin(), lengths.end(), sample_length) -
+                               lengths.begin();
+  const float segment_start = prev_point_index == 0 ? 0.0f : lengths[prev_point_index - 1];
+  const float segment_end = lengths[prev_point_index];
+  const float segment_length = segment_end - segment_start;
+  const float segment_length_inv = safe_divide(1.0f, segment_length);
+  const float length_in_segment = sample_length - segment_start;
+  const float factor = length_in_segment * segment_length_inv;
+
+  r_segment_index = prev_point_index;
+  r_factor = factor;
+
+  if (hint != nullptr) {
+    hint->segment_index = r_segment_index;
+    hint->segment_start = segment_start;
+    hint->segment_length_inv = segment_length_inv;
   }
 }
 
 /**
- * Find the given number of points, evenly spaced along the provided length. For non-cyclic
- * sequences, the first point will always be included, and last point will always be included if
- * the #count is greater than zero. For cyclic sequences, the first point will always be included.
+ * Find evenly spaced samples along the lengths.
  *
- * \warning The #count argument must be greater than zero.
+ * \param accumulated_segment_lengths: The accumulated lengths of the original elements being
+ * sampled. Could be calculated by #accumulate_lengths.
+ * \param include_last_point: Generally false for cyclic sequences and true otherwise.
+ * \param r_segment_indices: The index of the previous point at each sample.
+ * \param r_factors: The portion of the length in each segment at each sample.
  */
-void create_uniform_samples(Span lengths,
-                            bool cyclic,
-                            MutableSpan indices,
-                            MutableSpan factors);
+void sample_uniform(Span accumulated_segment_lengths,
+                    bool include_last_point,
+                    MutableSpan r_segment_indices,
+                    MutableSpan r_factors);
 
 /**
  * For each provided sample length, find the segment index and interpolation factor.
  *
- * \param lengths: The accumulated lengths of the original elements being sampled.
- * Could be calculated by #accumulate_lengths.
+ * \param accumulated_segment_lengths: The accumulated lengths of the original elements being
+ * sampled. Could be calculated by #accumulate_lengths.
  * \param sample_lengths: Sampled locations in the #lengths array. Must be sorted and is expected
  * to be within the range of the #lengths values.
- * \param cyclic: Whether the points described by the #lengths input is cyclic. This is likely
- * redundant information theoretically.
- * \param indices: The index of the previous point at each sample.
- * \param factors: The portion of the length in each segment at each sample.
+ * \param r_segment_indices: The index of the previous point at each sample.
+ * \param r_factors: The portion of the length in each segment at each sample.
  */
-void create_samples_from_sorted_lengths(Span lengths,
-                                        Span sample_lengths,
-                                        bool cyclic,
-                                        MutableSpan indices,
-                                        MutableSpan factors);
+void sample_at_lengths(Span accumulated_segment_lengths,
+                       Span sample_lengths,
+                       MutableSpan r_segment_indices,
+                       MutableSpan r_factors);
 
 }  // namespace blender::length_parameterize
diff --git a/source/blender/blenlib/intern/length_parameterize.cc b/source/blender/blenlib/intern/length_parameterize.cc
index e18b048e96d..06cca281510 100644
--- a/source/blender/blenlib/intern/length_parameterize.cc
+++ b/source/blender/blenlib/intern/length_parameterize.cc
@@ -1,144 +1,58 @@
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 
 #include "BLI_length_parameterize.hh"
+#include "BLI_task.hh"
 
 namespace blender::length_parameterize {
 
-void create_uniform_samples(const Span lengths,
-                            const bool cyclic,
-                            MutableSpan indices,
-                            MutableSpan factors)
+void sample_uniform(const Span lengths,
+                    const bool include_last_point,
+                    MutableSpan r_segment_indices,
+                    MutableSpan r_factors)
 {
-  const int count = indices.size();
+  const int count = r_segment_indices.size();
   BLI_assert(count > 0);
   BLI_assert(lengths.size() >= 1);
   BLI_assert(std::is_sorted(lengths.begin(), lengths.end()));
-  const int segments_num = lengths.size();
-  const int points_num = cyclic ? segments_num : segments_num + 1;
 
-  indices.first() = 0;
-  factors.first() = 0.0f;
   if (count == 1) {
+    r_segment_indices[0] = 0;
+    r_factors[0] = 0.0f;
     return;
   }
-
   const float total_length = lengths.last();
-  if (total_length == 0.0f) {
-    indices.fill(0);
-    factors.fill(0.0f);
-    return;
-  }
-
-  const float step_length = total_length / (count - (cyclic ? 0 : 1));
-  const float step_length_inv = 1.0f / step_length;
-
-  int i_dst = 1;
-  /* Store the length at the previous point in a variable so it can start out at zero
-   * (the lengths array doesn't contain 0 for the first point). */
-  float prev_length = 0.0f;
-  for (const int i_src : IndexRange(points_num - 1)) {
-    const float next_length = lengths[i_src];
-    const float segment_length = next_length - prev_length;
-    if (segment_length == 0.0f) {
-      continue;
-    }
-    /* Add every sample that fits in this segment. */
-    const float segment_length_inv = 1.0f / segment_length;
-    const int segment_samples_num = std::ceil(next_length * step_length_inv - i_dst);
-    indices.slice(i_dst, segment_samples_num).fill(i_src);
-
-    for (const int i : IndexRange(i_dst, segment_samples_num)) {
-      const float length_in_segment = step_length * i - prev_length;
-      factors[i] = length_in_segment * segment_length_inv;
-    }
-
-    i_dst += segment_samples_num;
-
-    prev_length = next_length;
-  }
-
-  /* Add the samples on the last cyclic segment if necessary, and also the samples
-   * that weren't created in the previous loop due to floating point inaccuracy. */
-  if (cyclic && lengths.size() > 1) {
-    indices.drop_front(i_dst).fill(points_num - 1);
-    const float segment_length = lengths.last() - lengths.last(1);
-    if (segment_length == 0.0f) {
-      return;
-    }
-    const float segment_length_inv = 1.0f / segment_length;
-    for (const int i : indices.index_range().drop_front(i_dst)) {
-      const float length_in_segment = step_length * i - prev_length;
-      factors[i] = length_in_segment * segment_length_inv;
+  const float step_length = total_length / (count - include_last_point);
+  threading::parallel_for(IndexRange(count), 512, [&](const IndexRange range) {
+    SampleSegmentHint hint;
+    for (const int i : range) {
+      /* Use minimum to avoid issues with floating point accuracy. */
+      const float sample_length = std::min(total_length, i * step_length);
+      sample_at_length(lengths, sample_length, r_segment_indices[i], r_factors[i], &hint);
     }
-  }
-  else {
-    indices.drop_front(i_dst).fill(points_num - 2);
-    factors.drop_front(i_dst).fill(1.0f);
-  }
+  });
 }
 
-void create_samples_from_sorted_lengths(const Span lengths,
-                                        const Span sample_lengths,
-                                        const bool cyclic,
-                                        MutableSpan indices,
-                                        MutableSpan factors)
+void sample_at_lengths(const Span accumulated_segment_lengths,
+                       const Span sample_lengths,
+                       MutableSpan r_segment_indices,
+                       MutableSpan r_factors)
 {
-  BLI_assert(std::is_sorted(lengths.begin(), lengths.end()));
+  BLI_assert(
+      std::is_sorted(accumulated_segment_lengths.begin(), accumulated_segment_lengths.end()));
   BLI_assert(std::is_sorted(sample_lengths.begin(), sample_lengths.end()));
-  BLI_assert(indices.size() == sample_lengths.size());
-  BLI_assert(indices.size() == factors.size());
-  const int segments_num = lengths.size();
-  const int points_num = cyclic ? segments_num : segments_num + 1;
 
-  const float total_length = lengths.last();
-  if (total_length == 0.0f) {
-    indices.fill(0);
-    factors.fill(0.0f);
-    return;
-  }
+  const int count = sample_lengths.size();
+  BLI_assert(count == r_segment_indices.size());
+  BLI_assert(count == r_factors.size());
 
-  int i_dst = 0;
-  /* Store the length at the previous point in a variable so it can start out at zero
-   * (the lengths array doesn't contain 0 for the first point). */
-  float prev_length = 0.0f;
-  for (const int i_src : IndexRange(points_num - 1)) {
-    const float next_length = lengths[i_src];
-    const float segment_length = next_length - prev_length;
-    if (segment_length == 0.0f) {
-      continue;
-    }
-    /* Add every sample that fits in this segment. It's also necessary to check if the last sample
-     * has been reached, since there is no upper bound on the number of samples in each segment. */
-    const float segment_length_inv = 1.0f / segment_length;
-    while (i_dst < sample_lengths.size() && sample_lengths[i_dst] < next_length) {
-      const float length_in_segment = sample_lengths[i_dst] - prev_length;
-      const float factor = length_in_segment * segment_length_inv;
-      indices[i_dst] = i_src;
-      factors[i_dst] = factor;
-      i_dst++;
+  threading::parallel_for(IndexRange(count), 512, [&](const IndexRange range) {
+    SampleSegmentHint hint;
+    for (const int i : range) {
+      const float sample_length = sample_lengths[i];
+      sample_at_length(
+          accumulated_segment_lengths, sample_length, r_segment_indices[i], r_factors[i], &hint);
     }
-
-    prev_length = next_length;
-  }
-
-  /* Add the samples on the last cyclic segment if necessary, and also the samples
-   * that weren't created in the previous loop due to floating point inaccuracy. */
-  if (cyclic && lengths.size() > 1) {
-    const float segment_length = lengths.last() - lengths.last(1);
-    while (sample_lengths[i_dst] < total_length) {
-      const float length_in_segment = sample_lengths[i_dst] - prev_length;
-      const float factor = length_in_segment / segment_length;
-      indices[i_dst] = points_num - 1;
-      factors[i_dst] = factor;
-      i_dst++;
-    }
-    indices.drop_front(i_dst).fill(points_num - 1);
-    factors.drop_front(i_dst).fill(1.0f);
-  }
-  else {
-    indices.drop_front(i_dst).fill(points_num - 2);
-    factors.drop_front(i_dst).fill(1.0f);
-  }
+  });
 }
 
 }  // namespace blender::length_parameterize
diff --git a/source/blender/blenlib/tests/BLI_length_parameterize_test.cc b/source/blender/blenlib/tests/BLI_length_parameterize_test.cc
index b63e3a0ec86..11f4997f563 100644
--- a/source/blender/blenlib/tests/BLI_length_parameterize_test.cc
+++ b/source/blender/blenlib/tests/BLI_length_parameterize_test.cc
@@ -10,7 +10,7 @@ namespace blender::length_parameterize::tests {
 
 template Array calculate_lengths(const Span values, const bool cyclic)
 {
-  Array lengths(lengths_num(values.size(), cyclic));
+  Array lengths(segments_num(values.size(), cyclic));
   accumulate_lengths(values, cyclic, lengths);
   return lengths;
 }
@@ -30,7 +30,7 @@ TEST(length_parameterize, FloatSimple)
 
   Array indices(4);
   Array factors(4);
-  create_uniform_samples(lengths, false, indices, factors);
+  sample_uniform(lengths, true, indices, factors);
   Array results(4);
   linear_interpolation(values, indices, factors, results);
   Array expected({
@@ -52,7 +52,7 @@ TEST(length_parameterize, Float)
 
   Array indices(20);
   Array factors(20);
-  create_uniform_samples(lengths, false, indices, factors);
+  sample_uniform(lengths, true, indices, factors);
   Array results(20);
   linear_interpolation(values, indices, factors, results);
   Array expected({
@@ -73,7 +73,7 @@ TEST(length_parameterize, Float2)
 
   Array indices(12);
   Array factors(12);
-  create_uniform_samples(lengths, false, indices, factors);
+  sample_uniform(lengths, true, indices, factors);
   Array results(12);
   linear_interpolation(values, indices, factors, results);
   Array expected({
@@ -103,7 +103,7 @@ TEST(length_parameterize, Float2Cyclic)
 
   Array indices(12);
   Array factors(12);
-  create_uniform_samples(lengths, true, indices, factors);
+  sample_uniform(lengths, false, indices, factors);
   Array results(12);
   linear_interpolation(values, indices, factors, results);
   Array expected({
@@ -133,7 +133,7 @@ TEST(length_parameterize, LineMany)
 
   Array indices(5007);
   Array factors(5007);
-  create_uniform_samples(lengths, false, indices, factors);
+  sample_uniform(lengths, true, indices, factors);
   Array results(5007);
   linear_interpolation(values, indices, factors, results);
   Array expected({
@@ -152,7 +152,7 @@ TEST(length_parameterize, CyclicMany)
 
   Array indices(5007);
   Array factors(5007);
-  create_uniform_samples(lengths, true, indices, factors);
+  sample_uniform(lengths, false, indices, factors);
   Array results(5007);
   linear_interpolation(values, indices, factors, results);
   Array expected({
@@ -176,7 +176,7 @@ TEST(length_parameterize, InterpolateColor)
 
   Array indices(10);
   Array factors(10);
-  create_uniform_samples(lengths, true, indices, factors);
+  sample_uniform(lengths, false, indices, factors);
   Array results(10);
   linear_interpolation(colors, indices, factors, results);
   Array expected({
@@ -207,7 +207,7 @@ TEST(length_parameterize, ArbitraryFloatSimple)
   Array sample_lengths{{0.5f, 1.5f, 2.0f, 4.0f}};
   Array indices(4);
   Array factors(4);
-  create_samples_from_sorted_lengths(lengths, sample_lengths, false, indices, factors);
+  sample_at_lengths(lengths, sample_lengths, indices, factors);
   Array results(4);
   linear_interpolation(values, indices, factors, results);
   results.as_span().print_as_lines("results");
@@ -231,7 +231,7 @@ TEST(length_parameterize, ArbitraryFloat2)
       {0.5f, 1.5f, 2.0f, 2.0f, 2.1f, 2.5f, 3.5f, 3.6f, 3.8f, 3.85f, 3.90f, 4.0f}};
   Array indices(12);
   Array factors(12);
-  create_samples_from_sorted_lengths(lengths, sample_lengths, true, indices, factors);
+  sample_at_lengths(lengths, sample_lengths, indices, factors);
   Array results(12);
   linear_interpolation(values, indices, factors, results);
   results.as_span().print_as_lines("results");
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc b/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc
index 1c785fa6452..7d17db515fb 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc
@@ -345,7 +345,7 @@ void move_last_point_and_resample(MutableSpan positions, const float3 &n
 {
   /* Find the accumulated length of each point in the original curve,
    * treating it as a poly curve for performance reasons and simplicity. */
-  Array orig_lengths(length_parameterize::lengths_num(positions.size(), false));
+  Array orig_lengths(length_parameterize::segments_num(positions.size(), false));
   length_parameterize::accumulate_lengths(positions, false, orig_lengths);
   const float orig_total_length = orig_lengths.last();
 
@@ -363,8 +363,7 @@ void move_last_point_and_resample(MutableSpan positions, const float3 &n
 
   Array indices(positions.size() - 1);
   Array factors(positions.size() - 1);
-  length_parameterize::create_samples_from_sorted_lengths(
-      orig_lengths, new_lengths, false, indices, factors);
+  length_parameterize::sample_at_lengths(orig_lengths, new_lengths, indices, factors);
 
   Array new_positions(positions.size() - 1);
   length_parameterize::linear_interpolation(positions, indices, factors, new_positions);
diff --git a/source/blender/geometry/intern/resample_curves.cc b/source/blender/geometry/intern/resample_curves.cc
index c18c776fead..dd1da62408c 100644
--- a/source/blender/geometry/intern/resample_curves.cc
+++ b/source/blender/geometry/intern/resample_curves.cc
@@ -234,11 +234,10 @@ static Curves *resample_to_uniform(const CurveComponent &src_component,
     for (const int i_curve : sliced_selection) {
       const bool cyclic = curves_cyclic[i_curve];
       const IndexRange dst_points = dst_curves.points_for_curve(i_curve);
-      length_parameterize::create_uniform_samples(
-          src_curves.evaluated_lengths_for_curve(i_curve, cyclic),
-          curves_cyclic[i_curve],
-          sample_indices.as_mutable_span().slice(dst_points),
-          sample_factors.as_mutable_span().slice(dst_points));
+      length_parameterize::sample_uniform(src_curves.evaluated_lengths_for_curve(i_curve, cyclic),
+                                          !curves_cyclic[i_curve],
+                                          sample_indices.as_mutable_span().slice(dst_points),
+                                          sample_factors.as_mutable_span().slice(dst_points));
     }
 
     /* For every attribute, evaluate attributes from every curve in the range in the original
-- 
cgit v1.2.3


From 4ffee9a48d1bc01442e554d44a1f55dfc459a221 Mon Sep 17 00:00:00 2001
From: Iliay Katueshenock 
Date: Sat, 2 Jul 2022 18:37:32 -0500
Subject: Fix T99316: Crash with no font in String to Curves node

If you remove the default font from the project, the node will not
have the selected font. In this case, there is no check that the font
does not exist. This suggestion adds an error message if the font
is not specified.

Differential Revision: https://developer.blender.org/D15337
---
 .../geometry/nodes/node_geo_string_to_curves.cc    | 27 ++++++++++++++--------
 1 file changed, 18 insertions(+), 9 deletions(-)

diff --git a/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc b/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc
index efcc8809c9c..94d5d7f946f 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc
@@ -157,12 +157,18 @@ struct TextLayout {
   float final_font_size;
 };
 
-static TextLayout get_text_layout(GeoNodeExecParams ¶ms)
+static std::optional get_text_layout(GeoNodeExecParams ¶ms)
 {
+  VFont *vfont = reinterpret_cast(params.node().id);
+  if (!vfont) {
+    params.error_message_add(NodeWarningType::Error, TIP_("Font not specified"));
+    return std::nullopt;
+  }
+
   TextLayout layout;
   layout.text = params.extract_input("String");
   if (layout.text.empty()) {
-    return {};
+    return std::nullopt;
   }
 
   const NodeGeometryStringToCurves &storage = node_storage(params.node());
@@ -181,7 +187,6 @@ static TextLayout get_text_layout(GeoNodeExecParams ¶ms)
   const float textbox_h = overflow == GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW ?
                               0.0f :
                               params.extract_input("Text Box Height");
-  VFont *vfont = (VFont *)params.node().id;
 
   Curve cu = dna::shallow_zero_initialize();
   cu.type = OB_FONT;
@@ -361,15 +366,19 @@ static void create_attributes(GeoNodeExecParams ¶ms,
 
 static void node_geo_exec(GeoNodeExecParams params)
 {
-  TextLayout layout = get_text_layout(params);
+  std::optional layout = get_text_layout(params);
+  if (!layout) {
+    params.set_default_remaining_outputs();
+    return;
+  }
 
   const NodeGeometryStringToCurves &storage =
       *(const NodeGeometryStringToCurves *)params.node().storage;
   if (storage.overflow == GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE) {
-    params.set_output("Remainder", std::move(layout.truncated_text));
+    params.set_output("Remainder", std::move(layout->truncated_text));
   }
 
-  if (layout.positions.size() == 0) {
+  if (layout->positions.size() == 0) {
     params.set_output("Curve Instances", GeometrySet());
     params.set_default_remaining_outputs();
     return;
@@ -378,9 +387,9 @@ static void node_geo_exec(GeoNodeExecParams params)
   /* Create and add instances. */
   GeometrySet geometry_set_out;
   InstancesComponent &instances = geometry_set_out.get_component_for_write();
-  Map char_handles = create_curve_instances(params, layout, instances);
-  add_instances_from_handles(instances, char_handles, layout);
-  create_attributes(params, layout, instances);
+  Map char_handles = create_curve_instances(params, *layout, instances);
+  add_instances_from_handles(instances, char_handles, *layout);
+  create_attributes(params, *layout, instances);
 
   params.set_output("Curve Instances", std::move(geometry_set_out));
 }
-- 
cgit v1.2.3


From c355be6faeacef6a65afbce97f9776d2a2c7f54c Mon Sep 17 00:00:00 2001
From: Julian Eisel 
Date: Sat, 2 Jul 2022 21:49:21 +0200
Subject: UI: Add AbstractView base class for views, unify reconstruction in
 there

No user visible changes expected.

There's plenty of duplicated code in the grid and the tree view, and I expect
this to become more. This starts the process of unifying these parts, which
should also make it easier to add new views. Complexity in the view classes is
reduced, and some type shenanigans for C compatibility and general view
management can be removed, since there is now a common base type.

For the start this ports some of the view reconstruction, where the view and
its items are compared to the version of itself in the previous redraw, so that
state (highlighted, active, renaming, collapsed, ...) can be preserved.
Notifier listening is also ported.
---
 source/blender/editors/include/UI_abstract_view.hh | 47 +++++++++++++
 source/blender/editors/include/UI_grid_view.hh     | 20 +-----
 source/blender/editors/include/UI_interface.h      | 15 +---
 source/blender/editors/include/UI_tree_view.hh     | 21 +-----
 source/blender/editors/interface/CMakeLists.txt    |  1 +
 source/blender/editors/interface/abstract_view.cc  | 52 ++++++++++++++
 source/blender/editors/interface/grid_view.cc      | 37 ++--------
 .../blender/editors/interface/interface_intern.h   |  7 +-
 source/blender/editors/interface/interface_view.cc | 81 ++++++----------------
 source/blender/editors/interface/tree_view.cc      | 48 +++----------
 source/blender/editors/util/CMakeLists.txt         |  1 +
 11 files changed, 146 insertions(+), 184 deletions(-)
 create mode 100644 source/blender/editors/include/UI_abstract_view.hh
 create mode 100644 source/blender/editors/interface/abstract_view.cc

diff --git a/source/blender/editors/include/UI_abstract_view.hh b/source/blender/editors/include/UI_abstract_view.hh
new file mode 100644
index 00000000000..477f68ca03f
--- /dev/null
+++ b/source/blender/editors/include/UI_abstract_view.hh
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup editorui
+ *
+ * Base for all views (UIs to display data sets), supporting common features.
+ * https://wiki.blender.org/wiki/Source/Interface/Views
+ *
+ * The base class manages reconstruction, most importantly keeping state over reconstructions.
+ */
+
+#pragma once
+
+struct wmNotifier;
+
+namespace blender::ui {
+
+class AbstractView {
+  bool is_reconstructed_ = false;
+
+ public:
+  virtual ~AbstractView() = default;
+
+  /** Listen to a notifier, returning true if a redraw is needed. */
+  virtual bool listen(const wmNotifier &) const;
+
+ protected:
+  AbstractView() = default;
+
+  virtual void update_children_from_old(const AbstractView &old_view) = 0;
+
+  /**
+   * Match the view and its items against an earlier version of itself (if any) and copy the old UI
+   * state (e.g. collapsed, active, selected, renaming, etc.) to the new one. See
+   * #AbstractViewItem.update_from_old().
+   * After this, reconstruction is complete (see #is_reconstructed()).
+   */
+  void update_from_old(uiBlock &new_block);
+
+  /**
+   * Check if the view is fully (re-)constructed. That means, both the build function and
+   * #update_from_old() have finished.
+   */
+  bool is_reconstructed() const;
+};
+
+}  // namespace blender::ui
diff --git a/source/blender/editors/include/UI_grid_view.hh b/source/blender/editors/include/UI_grid_view.hh
index 6f553f4fad1..cabc49e411c 100644
--- a/source/blender/editors/include/UI_grid_view.hh
+++ b/source/blender/editors/include/UI_grid_view.hh
@@ -13,6 +13,7 @@
 #include "BLI_map.hh"
 #include "BLI_vector.hh"
 
+#include "UI_abstract_view.hh"
 #include "UI_resources.h"
 
 struct bContext;
@@ -111,7 +112,7 @@ struct GridViewStyle {
   int tile_height = 0;
 };
 
-class AbstractGridView {
+class AbstractGridView : public AbstractView {
   friend class AbstractGridViewItem;
   friend class GridViewBuilder;
   friend class GridViewLayoutBuilder;
@@ -122,7 +123,6 @@ class AbstractGridView {
    * #update_from_old(). */
   Map item_map_;
   GridViewStyle style_;
-  bool is_reconstructed_ = false;
 
  public:
   AbstractGridView();
@@ -131,9 +131,6 @@ class AbstractGridView {
   using ItemIterFn = FunctionRef;
   void foreach_item(ItemIterFn iter_fn) const;
 
-  /** Listen to a notifier, returning true if a redraw is needed. */
-  virtual bool listen(const wmNotifier &) const;
-
   /**
    * Convenience wrapper constructing the item by forwarding given arguments to the constructor of
    * the type (\a ItemT).
@@ -154,19 +151,8 @@ class AbstractGridView {
  protected:
   virtual void build_items() = 0;
 
-  /**
-   * Check if the view is fully (re-)constructed. That means, both #build_items() and
-   * #update_from_old() have finished.
-   */
-  bool is_reconstructed() const;
-
  private:
-  /**
-   * Match the grid-view against an earlier version of itself (if any) and copy the old UI state
-   * (e.g. active, selected, renaming, etc.) to the new one. See
-   * #AbstractGridViewItem.update_from_old().
-   */
-  void update_from_old(uiBlock &new_block);
+  void update_children_from_old(const AbstractView &old_view) override;
   AbstractGridViewItem *find_matching_item(const AbstractGridViewItem &item_to_match,
                                            const AbstractGridView &view_to_search_in) const;
   /**
diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h
index b2ec2102ddd..f39c62d8e2b 100644
--- a/source/blender/editors/include/UI_interface.h
+++ b/source/blender/editors/include/UI_interface.h
@@ -72,12 +72,10 @@ typedef struct uiBut uiBut;
 typedef struct uiButExtraOpIcon uiButExtraOpIcon;
 typedef struct uiLayout uiLayout;
 typedef struct uiPopupBlockHandle uiPopupBlockHandle;
-/* C handle for C++ #ui::AbstractTreeView type. */
-typedef struct uiTreeViewHandle uiTreeViewHandle;
+/* C handle for C++ #ui::AbstractView type. */
+typedef struct uiViewHandle uiViewHandle;
 /* C handle for C++ #ui::AbstractTreeViewItem type. */
 typedef struct uiTreeViewItemHandle uiTreeViewItemHandle;
-/* C handle for C++ #ui::AbstractGridView type. */
-typedef struct uiGridViewHandle uiGridViewHandle;
 /* C handle for C++ #ui::AbstractGridViewItem type. */
 typedef struct uiGridViewItemHandle uiGridViewItemHandle;
 
@@ -3243,15 +3241,6 @@ uiTreeViewItemHandle *UI_block_tree_view_find_item_at(const struct ARegion *regi
                                                       const int xy[2]) ATTR_NONNULL(1, 2);
 uiTreeViewItemHandle *UI_block_tree_view_find_active_item(const struct ARegion *region);
 
-/**
- * Listen to \a notifier, returning true if the region should redraw.
- */
-bool UI_tree_view_listen_should_redraw(const uiTreeViewHandle *view, const wmNotifier *notifier);
-/**
- * Listen to \a notifier, returning true if the region should redraw.
- */
-bool UI_grid_view_listen_should_redraw(const uiGridViewHandle *view, const wmNotifier *notifier);
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/source/blender/editors/include/UI_tree_view.hh b/source/blender/editors/include/UI_tree_view.hh
index 1aeb13ca5cc..e9abe4c1d1f 100644
--- a/source/blender/editors/include/UI_tree_view.hh
+++ b/source/blender/editors/include/UI_tree_view.hh
@@ -19,6 +19,7 @@
 #include "BLI_function_ref.hh"
 #include "BLI_vector.hh"
 
+#include "UI_abstract_view.hh"
 #include "UI_resources.h"
 
 struct bContext;
@@ -112,7 +113,7 @@ using TreeViewOrItem = TreeViewItemContainer;
 /** \name Tree-View Base Class
  * \{ */
 
-class AbstractTreeView : public TreeViewItemContainer {
+class AbstractTreeView : public AbstractView, public TreeViewItemContainer {
   friend class AbstractTreeViewItem;
   friend class TreeViewBuilder;
 
@@ -122,35 +123,19 @@ class AbstractTreeView : public TreeViewItemContainer {
    */
   std::unique_ptr> rename_buffer_;
 
-  bool is_reconstructed_ = false;
-
  public:
   virtual ~AbstractTreeView() = default;
 
   void foreach_item(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const;
 
-  /** Listen to a notifier, returning true if a redraw is needed. */
-  virtual bool listen(const wmNotifier &) const;
-
   /** Only one item can be renamed at a time. */
   bool is_renaming() const;
 
  protected:
   virtual void build_tree() = 0;
 
-  /**
-   * Check if the tree is fully (re-)constructed. That means, both #build_tree() and
-   * #update_from_old() have finished.
-   */
-  bool is_reconstructed() const;
-
  private:
-  /**
-   * Match the tree-view against an earlier version of itself (if any) and copy the old UI state
-   * (e.g. collapsed, active, selected, renaming, etc.) to the new one. See
-   * #AbstractTreeViewItem.update_from_old().
-   */
-  void update_from_old(uiBlock &new_block);
+  void update_children_from_old(const AbstractView &old_view) override;
   static void update_children_from_old_recursive(const TreeViewOrItem &new_items,
                                                  const TreeViewOrItem &old_items);
   static AbstractTreeViewItem *find_matching_child(const AbstractTreeViewItem &lookup_item,
diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt
index 2a1852bd6e7..1bdec57ac59 100644
--- a/source/blender/editors/interface/CMakeLists.txt
+++ b/source/blender/editors/interface/CMakeLists.txt
@@ -25,6 +25,7 @@ set(INC
 )
 
 set(SRC
+  abstract_view.cc
   grid_view.cc
   interface.cc
   interface_align.c
diff --git a/source/blender/editors/interface/abstract_view.cc b/source/blender/editors/interface/abstract_view.cc
new file mode 100644
index 00000000000..542d82a56a3
--- /dev/null
+++ b/source/blender/editors/interface/abstract_view.cc
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup edinterface
+ */
+
+#include "interface_intern.h"
+
+#include "UI_abstract_view.hh"
+
+namespace blender::ui {
+
+/* ---------------------------------------------------------------------- */
+/** \name View Reconstruction
+ * \{ */
+
+bool AbstractView::is_reconstructed() const
+{
+  return is_reconstructed_;
+}
+
+void AbstractView::update_from_old(uiBlock &new_block)
+{
+  uiBlock *old_block = new_block.oldblock;
+  if (!old_block) {
+    is_reconstructed_ = true;
+    return;
+  }
+
+  const uiViewHandle *old_view_handle = ui_block_view_find_matching_in_old_block(
+      &new_block, reinterpret_cast(this));
+  if (old_view_handle == nullptr) {
+    /* Initial construction, nothing to update. */
+    is_reconstructed_ = true;
+    return;
+  }
+
+  update_children_from_old(reinterpret_cast(*old_view_handle));
+
+  /* Finished (re-)constructing the tree. */
+  is_reconstructed_ = true;
+}
+
+/** \} */
+
+bool AbstractView::listen(const wmNotifier & /*notifier*/) const
+{
+  /* Nothing by default. */
+  return false;
+}
+
+}  // namespace blender::ui
diff --git a/source/blender/editors/interface/grid_view.cc b/source/blender/editors/interface/grid_view.cc
index 194052862cf..19a2326fba1 100644
--- a/source/blender/editors/interface/grid_view.cc
+++ b/source/blender/editors/interface/grid_view.cc
@@ -43,12 +43,6 @@ void AbstractGridView::foreach_item(ItemIterFn iter_fn) const
   }
 }
 
-bool AbstractGridView::listen(const wmNotifier & /*notifier*/) const
-{
-  /* Nothing by default. */
-  return false;
-}
-
 AbstractGridViewItem *AbstractGridView::find_matching_item(
     const AbstractGridViewItem &item_to_match, const AbstractGridView &view_to_search_in) const
 {
@@ -67,34 +61,18 @@ void AbstractGridView::change_state_delayed()
   foreach_item([](AbstractGridViewItem &item) { item.change_state_delayed(); });
 }
 
-void AbstractGridView::update_from_old(uiBlock &new_block)
+void AbstractGridView::update_children_from_old(const AbstractView &old_view)
 {
-  uiGridViewHandle *old_view_handle = ui_block_grid_view_find_matching_in_old_block(
-      &new_block, reinterpret_cast(this));
-  if (!old_view_handle) {
-    /* Initial construction, nothing to update. */
-    is_reconstructed_ = true;
-    return;
-  }
-
-  AbstractGridView &old_view = reinterpret_cast(*old_view_handle);
+  const AbstractGridView &old_grid_view = dynamic_cast(old_view);
 
-  foreach_item([this, &old_view](AbstractGridViewItem &new_item) {
-    const AbstractGridViewItem *matching_old_item = find_matching_item(new_item, old_view);
+  foreach_item([this, &old_grid_view](AbstractGridViewItem &new_item) {
+    const AbstractGridViewItem *matching_old_item = find_matching_item(new_item, old_grid_view);
     if (!matching_old_item) {
       return;
     }
 
     new_item.update_from_old(*matching_old_item);
   });
-
-  /* Finished (re-)constructing the tree. */
-  is_reconstructed_ = true;
-}
-
-bool AbstractGridView::is_reconstructed() const
-{
-  return is_reconstructed_;
 }
 
 const GridViewStyle &AbstractGridView::get_style() const
@@ -509,13 +487,6 @@ bool UI_grid_view_item_is_active(const uiGridViewItemHandle *item_handle)
   return item.is_active();
 }
 
-bool UI_grid_view_listen_should_redraw(const uiGridViewHandle *view_handle,
-                                       const wmNotifier *notifier)
-{
-  const AbstractGridView &view = *reinterpret_cast(view_handle);
-  return view.listen(*notifier);
-}
-
 bool UI_grid_view_item_matches(const uiGridViewItemHandle *a_handle,
                                const uiGridViewItemHandle *b_handle)
 {
diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h
index 791e51b81a6..5e0382f73a9 100644
--- a/source/blender/editors/interface/interface_intern.h
+++ b/source/blender/editors/interface/interface_intern.h
@@ -1543,10 +1543,9 @@ void ui_interface_tag_script_reload_queries(void);
 /* interface_view.cc */
 
 void ui_block_free_views(struct uiBlock *block);
-uiTreeViewHandle *ui_block_tree_view_find_matching_in_old_block(const uiBlock *new_block,
-                                                                const uiTreeViewHandle *new_view);
-uiGridViewHandle *ui_block_grid_view_find_matching_in_old_block(
-    const uiBlock *new_block, const uiGridViewHandle *new_view_handle);
+uiViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block,
+                                                       const uiViewHandle *new_view);
+
 uiButTreeRow *ui_block_view_find_treerow_in_old_block(const uiBlock *new_block,
                                                       const uiTreeViewItemHandle *new_item_handle);
 
diff --git a/source/blender/editors/interface/interface_view.cc b/source/blender/editors/interface/interface_view.cc
index 699ac0c2b53..72a6e3ba5e3 100644
--- a/source/blender/editors/interface/interface_view.cc
+++ b/source/blender/editors/interface/interface_view.cc
@@ -25,6 +25,7 @@
 
 #include "UI_interface.hh"
 
+#include "UI_abstract_view.hh"
 #include "UI_grid_view.hh"
 #include "UI_tree_view.hh"
 
@@ -36,43 +37,27 @@ using namespace blender::ui;
  * #std::variant.
  */
 struct ViewLink : public Link {
-  using TreeViewPtr = std::unique_ptr;
-  using GridViewPtr = std::unique_ptr;
-
   std::string idname;
-  /* NOTE: Can't use std::get() on this until minimum macOS deployment target is 10.14. */
-  std::variant view;
+  std::unique_ptr view;
 };
 
-template constexpr void check_if_valid_view_type()
-{
-  static_assert(std::is_same_v || std::is_same_v,
-                "Unsupported view type");
-}
-
-template T *get_view_from_link(ViewLink &link)
-{
-  auto *t_uptr = std::get_if>(&link.view);
-  return t_uptr ? t_uptr->get() : nullptr;
-}
-
 template
-static T *ui_block_add_view_impl(uiBlock &block, StringRef idname, std::unique_ptr view)
+static T *ui_block_add_view_impl(uiBlock &block,
+                                 StringRef idname,
+                                 std::unique_ptr view)
 {
-  check_if_valid_view_type();
-
   ViewLink *view_link = MEM_new(__func__);
   BLI_addtail(&block.views, view_link);
 
   view_link->view = std::move(view);
   view_link->idname = idname;
 
-  return get_view_from_link(*view_link);
+  return dynamic_cast(view_link->view.get());
 }
 
 AbstractGridView *UI_block_add_view(uiBlock &block,
                                     StringRef idname,
-                                    std::unique_ptr tree_view)
+                                    std::unique_ptr tree_view)
 {
   return ui_block_add_view_impl(block, idname, std::move(tree_view));
 }
@@ -96,17 +81,8 @@ void UI_block_views_listen(const uiBlock *block, const wmRegionListenerParams *l
   ARegion *region = listener_params->region;
 
   LISTBASE_FOREACH (ViewLink *, view_link, &block->views) {
-    if (AbstractGridView *grid_view = get_view_from_link(*view_link)) {
-      if (UI_grid_view_listen_should_redraw(reinterpret_cast(grid_view),
-                                            listener_params->notifier)) {
-        ED_region_tag_redraw(region);
-      }
-    }
-    else if (AbstractTreeView *tree_view = get_view_from_link(*view_link)) {
-      if (UI_tree_view_listen_should_redraw(reinterpret_cast(tree_view),
-                                            listener_params->notifier)) {
-        ED_region_tag_redraw(region);
-      }
+    if (view_link->view->listen(*listener_params->notifier)) {
+      ED_region_tag_redraw(region);
     }
   }
 }
@@ -131,13 +107,11 @@ uiTreeViewItemHandle *UI_block_tree_view_find_active_item(const ARegion *region)
   return tree_row_but->tree_item;
 }
 
-template static StringRef ui_block_view_find_idname(const uiBlock &block, const T &view)
+static StringRef ui_block_view_find_idname(const uiBlock &block, const AbstractView &view)
 {
-  check_if_valid_view_type();
-
   /* First get the idname the of the view we're looking for. */
   LISTBASE_FOREACH (ViewLink *, view_link, &block.views) {
-    if (get_view_from_link(*view_link) == &view) {
+    if (view_link->view.get() == &view) {
       return view_link->idname;
     }
   }
@@ -146,10 +120,9 @@ template static StringRef ui_block_view_find_idname(const uiBlock &bloc
 }
 
 template
-static T *ui_block_view_find_matching_in_old_block(const uiBlock &new_block, const T &new_view)
+static T *ui_block_view_find_matching_in_old_block_impl(const uiBlock &new_block,
+                                                        const T &new_view)
 {
-  check_if_valid_view_type();
-
   uiBlock *old_block = new_block.oldblock;
   if (!old_block) {
     return nullptr;
@@ -162,31 +135,21 @@ static T *ui_block_view_find_matching_in_old_block(const uiBlock &new_block, con
 
   LISTBASE_FOREACH (ViewLink *, old_view_link, &old_block->views) {
     if (old_view_link->idname == idname) {
-      return get_view_from_link(*old_view_link);
+      return dynamic_cast(old_view_link->view.get());
     }
   }
 
   return nullptr;
 }
 
-uiTreeViewHandle *ui_block_tree_view_find_matching_in_old_block(
-    const uiBlock *new_block, const uiTreeViewHandle *new_view_handle)
-{
-  BLI_assert(new_block && new_view_handle);
-  const AbstractTreeView &new_view = reinterpret_cast(*new_view_handle);
-
-  AbstractTreeView *old_view = ui_block_view_find_matching_in_old_block(*new_block, new_view);
-  return reinterpret_cast(old_view);
-}
-
-uiGridViewHandle *ui_block_grid_view_find_matching_in_old_block(
-    const uiBlock *new_block, const uiGridViewHandle *new_view_handle)
+uiViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block,
+                                                       const uiViewHandle *new_view_handle)
 {
   BLI_assert(new_block && new_view_handle);
-  const AbstractGridView &new_view = reinterpret_cast(*new_view_handle);
+  const AbstractView &new_view = reinterpret_cast(*new_view_handle);
 
-  AbstractGridView *old_view = ui_block_view_find_matching_in_old_block(*new_block, new_view);
-  return reinterpret_cast(old_view);
+  AbstractView *old_view = ui_block_view_find_matching_in_old_block_impl(*new_block, new_view);
+  return reinterpret_cast(old_view);
 }
 
 uiButTreeRow *ui_block_view_find_treerow_in_old_block(const uiBlock *new_block,
@@ -199,9 +162,9 @@ uiButTreeRow *ui_block_view_find_treerow_in_old_block(const uiBlock *new_block,
 
   const AbstractTreeViewItem &new_item = *reinterpret_cast(
       new_item_handle);
-  const AbstractTreeView *old_tree_view = ui_block_view_find_matching_in_old_block(
+  const AbstractView *old_view = ui_block_view_find_matching_in_old_block_impl(
       *new_block, new_item.get_tree_view());
-  if (!old_tree_view) {
+  if (!old_view) {
     return nullptr;
   }
 
@@ -216,7 +179,7 @@ uiButTreeRow *ui_block_view_find_treerow_in_old_block(const uiBlock *new_block,
     AbstractTreeViewItem &old_item = *reinterpret_cast(
         old_treerow_but->tree_item);
     /* Check if the row is from the expected tree-view. */
-    if (&old_item.get_tree_view() != old_tree_view) {
+    if (&old_item.get_tree_view() != old_view) {
       continue;
     }
 
diff --git a/source/blender/editors/interface/tree_view.cc b/source/blender/editors/interface/tree_view.cc
index 96158ee48f6..e892e7c272c 100644
--- a/source/blender/editors/interface/tree_view.cc
+++ b/source/blender/editors/interface/tree_view.cc
@@ -68,45 +68,25 @@ void AbstractTreeView::foreach_item(ItemIterFn iter_fn, IterOptions options) con
   foreach_item_recursive(iter_fn, options);
 }
 
-bool AbstractTreeView::listen(const wmNotifier & /*notifier*/) const
-{
-  /* Nothing by default. */
-  return false;
-}
-
 bool AbstractTreeView::is_renaming() const
 {
   return rename_buffer_ != nullptr;
 }
 
-void AbstractTreeView::update_from_old(uiBlock &new_block)
+void AbstractTreeView::update_children_from_old(const AbstractView &old_view)
 {
-  uiBlock *old_block = new_block.oldblock;
-  if (!old_block) {
-    /* Initial construction, nothing to update. */
-    is_reconstructed_ = true;
-    return;
-  }
-
-  uiTreeViewHandle *old_view_handle = ui_block_tree_view_find_matching_in_old_block(
-      &new_block, reinterpret_cast(this));
-  if (old_view_handle == nullptr) {
-    is_reconstructed_ = true;
-    return;
-  }
-
-  AbstractTreeView &old_view = reinterpret_cast(*old_view_handle);
+  /* TODO: Get rid of const cast. */
+  AbstractTreeView &old_tree_view = const_cast(
+      dynamic_cast(old_view));
 
+  /* TODO: Move to AbstractView. */
   /* Update own persistent data. */
   /* Keep the rename buffer persistent while renaming! The rename button uses the buffer's
    * pointer to identify itself over redraws. */
-  rename_buffer_ = std::move(old_view.rename_buffer_);
-  old_view.rename_buffer_ = nullptr;
-
-  update_children_from_old_recursive(*this, old_view);
+  rename_buffer_ = std::move(old_tree_view.rename_buffer_);
+  old_tree_view.rename_buffer_ = nullptr;
 
-  /* Finished (re-)constructing the tree. */
-  is_reconstructed_ = true;
+  update_children_from_old_recursive(*this, old_tree_view);
 }
 
 void AbstractTreeView::update_children_from_old_recursive(const TreeViewOrItem &new_items,
@@ -138,11 +118,6 @@ AbstractTreeViewItem *AbstractTreeView::find_matching_child(
   return nullptr;
 }
 
-bool AbstractTreeView::is_reconstructed() const
-{
-  return is_reconstructed_;
-}
-
 void AbstractTreeView::change_state_delayed()
 {
   BLI_assert_msg(
@@ -811,13 +786,6 @@ class TreeViewItemAPIWrapper {
 
 using namespace blender::ui;
 
-bool UI_tree_view_listen_should_redraw(const uiTreeViewHandle *view_handle,
-                                       const wmNotifier *notifier)
-{
-  const AbstractTreeView &view = *reinterpret_cast(view_handle);
-  return view.listen(*notifier);
-}
-
 bool UI_tree_view_item_is_active(const uiTreeViewItemHandle *item_handle)
 {
   const AbstractTreeViewItem &item = reinterpret_cast(*item_handle);
diff --git a/source/blender/editors/util/CMakeLists.txt b/source/blender/editors/util/CMakeLists.txt
index 5c2a3374aa1..cdfe40c7d35 100644
--- a/source/blender/editors/util/CMakeLists.txt
+++ b/source/blender/editors/util/CMakeLists.txt
@@ -91,6 +91,7 @@ set(SRC
   ../include/ED_uvedit.h
   ../include/ED_view3d.h
   ../include/ED_view3d_offscreen.h
+  ../include/UI_abstract_view.hh
   ../include/UI_grid_view.hh
   ../include/UI_icons.h
   ../include/UI_interface.h
-- 
cgit v1.2.3


From e86c2f7288724bd6fec33ff43e89816d7520a2b3 Mon Sep 17 00:00:00 2001
From: Julian Eisel 
Date: Sat, 2 Jul 2022 22:36:50 +0200
Subject: UI: Move rename buffer management to new view base class

Renaming is a nice example of a feature that shouldn't need a specific
implementation for a specific view type (e.g. grid or tree view). So it's
something that can be supported in the general view code. Individual views can
use it "for free" then. This ports the view level part of the renaming code,
the view item level part of it can be ported once we have a common base class
for the view items.
---
 source/blender/editors/include/UI_abstract_view.hh | 24 +++++++++-
 source/blender/editors/include/UI_tree_view.hh     |  9 ----
 source/blender/editors/interface/abstract_view.cc  | 54 +++++++++++++++++++++-
 source/blender/editors/interface/tree_view.cc      | 33 ++++---------
 4 files changed, 85 insertions(+), 35 deletions(-)

diff --git a/source/blender/editors/include/UI_abstract_view.hh b/source/blender/editors/include/UI_abstract_view.hh
index 477f68ca03f..82f81f1702b 100644
--- a/source/blender/editors/include/UI_abstract_view.hh
+++ b/source/blender/editors/include/UI_abstract_view.hh
@@ -6,17 +6,31 @@
  * Base for all views (UIs to display data sets), supporting common features.
  * https://wiki.blender.org/wiki/Source/Interface/Views
  *
- * The base class manages reconstruction, most importantly keeping state over reconstructions.
+ * One of the most important responsibilities of the base class is managing reconstruction,
+ * enabling state that is persistent over reconstructions/redraws.
  */
 
 #pragma once
 
+#include 
+#include 
+
+#include "BLI_span.hh"
+
 struct wmNotifier;
 
 namespace blender::ui {
 
 class AbstractView {
   bool is_reconstructed_ = false;
+  /**
+   * Only one item can be renamed at a time. So rather than giving each item an own rename buffer
+   * (which just adds unused memory in most cases), have one here that is managed by the view.
+   *
+   * This fixed-size buffer is needed because that's what the rename button requires. In future we
+   * may be able to bind the button to a `std::string` or similar.
+   */
+  std::unique_ptr> rename_buffer_;
 
  public:
   virtual ~AbstractView() = default;
@@ -24,6 +38,14 @@ class AbstractView {
   /** Listen to a notifier, returning true if a redraw is needed. */
   virtual bool listen(const wmNotifier &) const;
 
+  /** Only one item can be renamed at a time. */
+  bool is_renaming() const;
+  /** \return If renaming was started successfully. */
+  bool begin_renaming();
+  void end_renaming();
+  Span get_rename_buffer() const;
+  MutableSpan get_rename_buffer();
+
  protected:
   AbstractView() = default;
 
diff --git a/source/blender/editors/include/UI_tree_view.hh b/source/blender/editors/include/UI_tree_view.hh
index e9abe4c1d1f..9527df590b7 100644
--- a/source/blender/editors/include/UI_tree_view.hh
+++ b/source/blender/editors/include/UI_tree_view.hh
@@ -117,20 +117,11 @@ class AbstractTreeView : public AbstractView, public TreeViewItemContainer {
   friend class AbstractTreeViewItem;
   friend class TreeViewBuilder;
 
-  /**
-   * Only one item can be renamed at a time. So the tree is informed about the renaming state to
-   * enforce that.
-   */
-  std::unique_ptr> rename_buffer_;
-
  public:
   virtual ~AbstractTreeView() = default;
 
   void foreach_item(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const;
 
-  /** Only one item can be renamed at a time. */
-  bool is_renaming() const;
-
  protected:
   virtual void build_tree() = 0;
 
diff --git a/source/blender/editors/interface/abstract_view.cc b/source/blender/editors/interface/abstract_view.cc
index 542d82a56a3..dea9600fde4 100644
--- a/source/blender/editors/interface/abstract_view.cc
+++ b/source/blender/editors/interface/abstract_view.cc
@@ -27,7 +27,7 @@ void AbstractView::update_from_old(uiBlock &new_block)
     return;
   }
 
-  const uiViewHandle *old_view_handle = ui_block_view_find_matching_in_old_block(
+  uiViewHandle *old_view_handle = ui_block_view_find_matching_in_old_block(
       &new_block, reinterpret_cast(this));
   if (old_view_handle == nullptr) {
     /* Initial construction, nothing to update. */
@@ -35,7 +35,15 @@ void AbstractView::update_from_old(uiBlock &new_block)
     return;
   }
 
-  update_children_from_old(reinterpret_cast(*old_view_handle));
+  AbstractView &old_view = reinterpret_cast(*old_view_handle);
+
+  /* Update own persistent data. */
+  /* Keep the rename buffer persistent while renaming! The rename button uses the buffer's
+   * pointer to identify itself over redraws. */
+  rename_buffer_ = std::move(old_view.rename_buffer_);
+  old_view.rename_buffer_ = nullptr;
+
+  update_children_from_old(old_view);
 
   /* Finished (re-)constructing the tree. */
   is_reconstructed_ = true;
@@ -43,10 +51,52 @@ void AbstractView::update_from_old(uiBlock &new_block)
 
 /** \} */
 
+/* ---------------------------------------------------------------------- */
+/** \name Default implementations of virtual functions
+ * \{ */
+
 bool AbstractView::listen(const wmNotifier & /*notifier*/) const
 {
   /* Nothing by default. */
   return false;
 }
 
+/** \} */
+
+/* ---------------------------------------------------------------------- */
+/** \name Renaming
+ * \{ */
+
+bool AbstractView::is_renaming() const
+{
+  return rename_buffer_ != nullptr;
+}
+
+bool AbstractView::begin_renaming()
+{
+  if (is_renaming()) {
+    return false;
+  }
+
+  rename_buffer_ = std::make_unique();
+  return true;
+}
+
+void AbstractView::end_renaming()
+{
+  BLI_assert(is_renaming());
+  rename_buffer_ = nullptr;
+}
+
+Span AbstractView::get_rename_buffer() const
+{
+  return *rename_buffer_;
+}
+MutableSpan AbstractView::get_rename_buffer()
+{
+  return *rename_buffer_;
+}
+
+/** \} */
+
 }  // namespace blender::ui
diff --git a/source/blender/editors/interface/tree_view.cc b/source/blender/editors/interface/tree_view.cc
index e892e7c272c..d29cf367e59 100644
--- a/source/blender/editors/interface/tree_view.cc
+++ b/source/blender/editors/interface/tree_view.cc
@@ -68,23 +68,9 @@ void AbstractTreeView::foreach_item(ItemIterFn iter_fn, IterOptions options) con
   foreach_item_recursive(iter_fn, options);
 }
 
-bool AbstractTreeView::is_renaming() const
-{
-  return rename_buffer_ != nullptr;
-}
-
 void AbstractTreeView::update_children_from_old(const AbstractView &old_view)
 {
-  /* TODO: Get rid of const cast. */
-  AbstractTreeView &old_tree_view = const_cast(
-      dynamic_cast(old_view));
-
-  /* TODO: Move to AbstractView. */
-  /* Update own persistent data. */
-  /* Keep the rename buffer persistent while renaming! The rename button uses the buffer's
-   * pointer to identify itself over redraws. */
-  rename_buffer_ = std::move(old_tree_view.rename_buffer_);
-  old_tree_view.rename_buffer_ = nullptr;
+  const AbstractTreeView &old_tree_view = dynamic_cast(old_view);
 
   update_children_from_old_recursive(*this, old_tree_view);
 }
@@ -233,7 +219,7 @@ AbstractTreeViewItem *AbstractTreeViewItem::find_tree_item_from_rename_button(
     AbstractTreeViewItem *item = reinterpret_cast(tree_row_but->tree_item);
     const AbstractTreeView &tree_view = item->get_tree_view();
 
-    if (item->is_renaming() && (tree_view.rename_buffer_->data() == rename_but.poin)) {
+    if (item->is_renaming() && (tree_view.get_rename_buffer().data() == rename_but.poin)) {
       return item;
     }
   }
@@ -248,7 +234,7 @@ void AbstractTreeViewItem::rename_button_fn(bContext *UNUSED(C), void *arg, char
   BLI_assert(item);
 
   const AbstractTreeView &tree_view = item->get_tree_view();
-  item->rename(tree_view.rename_buffer_->data());
+  item->rename(tree_view.get_rename_buffer().data());
   item->end_renaming();
 }
 
@@ -270,9 +256,9 @@ void AbstractTreeViewItem::add_rename_button(uiLayout &row)
                                0,
                                UI_UNIT_X * 10,
                                UI_UNIT_Y,
-                               tree_view.rename_buffer_->data(),
+                               tree_view.get_rename_buffer().data(),
                                1.0f,
-                               tree_view.rename_buffer_->max_size(),
+                               tree_view.get_rename_buffer().size(),
                                0,
                                0,
                                "");
@@ -372,10 +358,11 @@ void AbstractTreeViewItem::begin_renaming()
     return;
   }
 
-  is_renaming_ = true;
+  if (tree_view.begin_renaming()) {
+    is_renaming_ = true;
+  }
 
-  tree_view.rename_buffer_ = std::make_unique();
-  std::copy(std::begin(label_), std::end(label_), std::begin(*tree_view.rename_buffer_));
+  std::copy(std::begin(label_), std::end(label_), std::begin(tree_view.get_rename_buffer()));
 }
 
 void AbstractTreeViewItem::end_renaming()
@@ -387,7 +374,7 @@ void AbstractTreeViewItem::end_renaming()
   is_renaming_ = false;
 
   AbstractTreeView &tree_view = get_tree_view();
-  tree_view.rename_buffer_ = nullptr;
+  tree_view.end_renaming();
 }
 
 AbstractTreeView &AbstractTreeViewItem::get_tree_view() const
-- 
cgit v1.2.3


From f4a9a3767ee2f5c34c8f8fa6e4dc89232bb3dcd6 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Sun, 3 Jul 2022 20:44:56 -0500
Subject: Cleanup: Rename curve segment count function

`curve_segment_num` -> `segments_num`.
The "curve" prefix is reduntant for a function in the curve namespace.
---
 source/blender/blenkernel/BKE_curves.hh                   |  4 ++--
 source/blender/blenkernel/intern/curve_catmull_rom.cc     |  2 +-
 source/blender/blenkernel/intern/curve_nurbs.cc           |  4 ++--
 source/blender/blenkernel/intern/curve_to_mesh_convert.cc | 12 ++++++------
 source/blender/blenlib/BLI_length_parameterize.hh         |  2 +-
 source/blender/draw/intern/draw_cache_impl_curve.cc       |  2 +-
 6 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh
index 2bebd3ff97d..b1f096b1c39 100644
--- a/source/blender/blenkernel/BKE_curves.hh
+++ b/source/blender/blenkernel/BKE_curves.hh
@@ -423,7 +423,7 @@ namespace curves {
  * The number of segments between control points, accounting for the last segment of cyclic
  * curves. The logic is simple, but this function should be used to make intentions clearer.
  */
-inline int curve_segment_num(const int points_num, const bool cyclic)
+inline int segments_num(const int points_num, const bool cyclic)
 {
   BLI_assert(points_num > 0);
   return (cyclic && points_num > 1) ? points_num : points_num - 1;
@@ -782,7 +782,7 @@ inline IndexRange CurvesGeometry::lengths_range_for_curve(const int curve_index,
   BLI_assert(cyclic == this->cyclic()[curve_index]);
   const IndexRange points = this->evaluated_points_for_curve(curve_index);
   const int start = points.start() + curve_index;
-  return {start, curves::curve_segment_num(points.size(), cyclic)};
+  return {start, curves::segments_num(points.size(), cyclic)};
 }
 
 inline Span CurvesGeometry::evaluated_lengths_for_curve(const int curve_index,
diff --git a/source/blender/blenkernel/intern/curve_catmull_rom.cc b/source/blender/blenkernel/intern/curve_catmull_rom.cc
index 4b2174c912c..1875c7b366a 100644
--- a/source/blender/blenkernel/intern/curve_catmull_rom.cc
+++ b/source/blender/blenkernel/intern/curve_catmull_rom.cc
@@ -11,7 +11,7 @@ namespace blender::bke::curves::catmull_rom {
 
 int calculate_evaluated_num(const int points_num, const bool cyclic, const int resolution)
 {
-  const int eval_num = resolution * curve_segment_num(points_num, cyclic);
+  const int eval_num = resolution * segments_num(points_num, cyclic);
   /* If the curve isn't cyclic, one last point is added to the final point. */
   return cyclic ? eval_num : eval_num + 1;
 }
diff --git a/source/blender/blenkernel/intern/curve_nurbs.cc b/source/blender/blenkernel/intern/curve_nurbs.cc
index cd6b64e9a03..3ab6fb01ea5 100644
--- a/source/blender/blenkernel/intern/curve_nurbs.cc
+++ b/source/blender/blenkernel/intern/curve_nurbs.cc
@@ -38,7 +38,7 @@ int calculate_evaluated_num(const int points_num,
   if (!check_valid_num_and_order(points_num, order, cyclic, knots_mode)) {
     return points_num;
   }
-  return resolution * curve_segment_num(points_num, cyclic);
+  return resolution * segments_num(points_num, cyclic);
 }
 
 int knots_num(const int points_num, const int8_t order, const bool cyclic)
@@ -168,7 +168,7 @@ void calculate_basis_cache(const int points_num,
   MutableSpan basis_start_indices(basis_cache.start_indices);
 
   const int last_control_point_index = cyclic ? points_num + degree : points_num;
-  const int evaluated_segment_num = curve_segment_num(evaluated_num, cyclic);
+  const int evaluated_segment_num = segments_num(evaluated_num, cyclic);
 
   const float start = knots[degree];
   const float end = knots[last_control_point_index];
diff --git a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc
index 78e5d07da96..baf56c0c350 100644
--- a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc
+++ b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc
@@ -39,8 +39,8 @@ static void fill_mesh_topology(const int vert_offset,
                                MutableSpan loops,
                                MutableSpan polys)
 {
-  const int main_segment_num = curves::curve_segment_num(main_point_num, main_cyclic);
-  const int profile_segment_num = curves::curve_segment_num(profile_point_num, profile_cyclic);
+  const int main_segment_num = curves::segments_num(main_point_num, main_cyclic);
+  const int profile_segment_num = curves::segments_num(profile_point_num, profile_cyclic);
 
   if (profile_point_num == 1) {
     for (const int i : IndexRange(main_point_num - 1)) {
@@ -273,7 +273,7 @@ static ResultOffsets calculate_result_offsets(const CurvesInfo &info, const bool
   for (const int i_main : info.main.curves_range()) {
     const bool main_cyclic = info.main_cyclic[i_main];
     const int main_point_num = info.main.evaluated_points_for_curve(i_main).size();
-    const int main_segment_num = curves::curve_segment_num(main_point_num, main_cyclic);
+    const int main_segment_num = curves::segments_num(main_point_num, main_cyclic);
     for (const int i_profile : info.profile.curves_range()) {
       result.vert[mesh_index] = vert_offset;
       result.edge[mesh_index] = edge_offset;
@@ -285,7 +285,7 @@ static ResultOffsets calculate_result_offsets(const CurvesInfo &info, const bool
 
       const bool profile_cyclic = info.profile_cyclic[i_profile];
       const int profile_point_num = info.profile.evaluated_points_for_curve(i_profile).size();
-      const int profile_segment_num = curves::curve_segment_num(profile_point_num, profile_cyclic);
+      const int profile_segment_num = curves::segments_num(profile_point_num, profile_cyclic);
 
       const bool has_caps = fill_caps && !main_cyclic && profile_cyclic;
       const int tube_face_num = main_segment_num * profile_segment_num;
@@ -407,8 +407,8 @@ static void foreach_curve_combination(const CurvesInfo &info,
                          profile_points,
                          main_cyclic,
                          profile_cyclic,
-                         curves::curve_segment_num(main_points.size(), main_cyclic),
-                         curves::curve_segment_num(profile_points.size(), profile_cyclic),
+                         curves::segments_num(main_points.size(), main_cyclic),
+                         curves::segments_num(profile_points.size(), profile_cyclic),
                          offsets_to_range(offsets.vert.as_span(), i),
                          offsets_to_range(offsets.edge.as_span(), i),
                          offsets_to_range(offsets.poly.as_span(), i),
diff --git a/source/blender/blenlib/BLI_length_parameterize.hh b/source/blender/blenlib/BLI_length_parameterize.hh
index ff957d92263..c44bb94f65d 100644
--- a/source/blender/blenlib/BLI_length_parameterize.hh
+++ b/source/blender/blenlib/BLI_length_parameterize.hh
@@ -17,7 +17,7 @@ namespace blender::length_parameterize {
  * Return the size of the necessary lengths array for a group of points, taking into account the
  * possible last cyclic segment.
  *
- * \note This is the same as #bke::curves::curve_segment_num.
+ * \note This is the same as #bke::curves::segments_num.
  */
 inline int segments_num(const int points_num, const bool cyclic)
 {
diff --git a/source/blender/draw/intern/draw_cache_impl_curve.cc b/source/blender/draw/intern/draw_cache_impl_curve.cc
index ebcdabe4942..695c348d8e2 100644
--- a/source/blender/draw/intern/draw_cache_impl_curve.cc
+++ b/source/blender/draw/intern/draw_cache_impl_curve.cc
@@ -108,7 +108,7 @@ static void curve_eval_render_wire_verts_edges_len_get(const blender::bke::Curve
   const blender::VArray cyclic = curves.cyclic();
   for (const int i : curves.curves_range()) {
     const IndexRange points = curves.evaluated_points_for_curve(i);
-    *r_edge_len += blender::bke::curves::curve_segment_num(points.size(), cyclic[i]);
+    *r_edge_len += blender::bke::curves::segments_num(points.size(), cyclic[i]);
   }
 }
 
-- 
cgit v1.2.3


From 929811df63d0555a5e7a8403b7d15d25a04eb89c Mon Sep 17 00:00:00 2001
From: Chris Blackbourn 
Date: Mon, 27 Jun 2022 14:40:49 +1200
Subject: Cleanup(UV): Refactor UV Align and UV Straighten (No user visible
 changes)

Move functionality into uvedit_uv_align_weld and uvedit_uv_straighten.

Prep for D15121
---
 source/blender/editors/uvedit/uvedit_ops.c | 376 +++++++++++++++--------------
 1 file changed, 191 insertions(+), 185 deletions(-)

diff --git a/source/blender/editors/uvedit/uvedit_ops.c b/source/blender/editors/uvedit/uvedit_ops.c
index 0b5d6592426..d872906e63e 100644
--- a/source/blender/editors/uvedit/uvedit_ops.c
+++ b/source/blender/editors/uvedit/uvedit_ops.c
@@ -372,6 +372,194 @@ typedef enum eUVWeldAlign {
   UV_WELD,
 } eUVWeldAlign;
 
+static bool uvedit_uv_align_weld(Scene *scene,
+                                 BMesh *bm,
+                                 const eUVWeldAlign tool,
+                                 const float cent[2])
+{
+  bool changed = false;
+  const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV);
+
+  BMIter iter;
+  BMFace *efa;
+  BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
+    if (!uvedit_face_visible_test(scene, efa)) {
+      continue;
+    }
+
+    BMIter liter;
+    BMLoop *l;
+    BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
+      if (!uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) {
+        continue;
+      }
+      MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
+      if (ELEM(tool, UV_ALIGN_X, UV_WELD)) {
+        if (luv->uv[0] != cent[0]) {
+          luv->uv[0] = cent[0];
+          changed = true;
+        }
+      }
+      if (ELEM(tool, UV_ALIGN_Y, UV_WELD)) {
+        if (luv->uv[1] != cent[1]) {
+          luv->uv[1] = cent[1];
+          changed = true;
+        }
+      }
+    }
+  }
+  return changed;
+}
+
+static bool uvedit_uv_straighten(Scene *scene, BMesh *bm, eUVWeldAlign tool)
+{
+  bool changed = false;
+  BMEdge *eed;
+  BMLoop *l;
+  BMVert *eve;
+  BMVert *eve_start;
+  BMIter iter, liter, eiter;
+
+  const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV);
+
+  /* clear tag */
+  BM_mesh_elem_hflag_disable_all(bm, BM_VERT, BM_ELEM_TAG, false);
+
+  /* tag verts with a selected UV */
+  BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
+    BM_ITER_ELEM (l, &liter, eve, BM_LOOPS_OF_VERT) {
+      if (!uvedit_face_visible_test(scene, l->f)) {
+        continue;
+      }
+
+      if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) {
+        BM_elem_flag_enable(eve, BM_ELEM_TAG);
+        break;
+      }
+    }
+  }
+
+  /* flush vertex tags to edges */
+  BM_ITER_MESH (eed, &iter, bm, BM_EDGES_OF_MESH) {
+    BM_elem_flag_set(
+        eed,
+        BM_ELEM_TAG,
+        (BM_elem_flag_test(eed->v1, BM_ELEM_TAG) && BM_elem_flag_test(eed->v2, BM_ELEM_TAG)));
+  }
+
+  /* find a vertex with only one tagged edge */
+  eve_start = NULL;
+  BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
+    int tot_eed_tag = 0;
+    BM_ITER_ELEM (eed, &eiter, eve, BM_EDGES_OF_VERT) {
+      if (BM_elem_flag_test(eed, BM_ELEM_TAG)) {
+        tot_eed_tag++;
+      }
+    }
+
+    if (tot_eed_tag == 1) {
+      eve_start = eve;
+      break;
+    }
+  }
+
+  if (!eve_start) {
+    return false;
+  }
+  BMVert **eve_line = NULL;
+  BMVert *eve_next = NULL;
+  BLI_array_declare(eve_line);
+  int i;
+
+  eve = eve_start;
+
+  /* walk over edges, building an array of verts in a line */
+  while (eve) {
+    BLI_array_append(eve_line, eve);
+    /* don't touch again */
+    BM_elem_flag_disable(eve, BM_ELEM_TAG);
+
+    eve_next = NULL;
+
+    /* find next eve */
+    BM_ITER_ELEM (eed, &eiter, eve, BM_EDGES_OF_VERT) {
+      if (BM_elem_flag_test(eed, BM_ELEM_TAG)) {
+        BMVert *eve_other = BM_edge_other_vert(eed, eve);
+        if (BM_elem_flag_test(eve_other, BM_ELEM_TAG)) {
+          /* this is a tagged vert we didn't walk over yet, step onto it */
+          eve_next = eve_other;
+          break;
+        }
+      }
+    }
+
+    eve = eve_next;
+  }
+
+  /* now we have all verts, make into a line */
+  if (BLI_array_len(eve_line) > 2) {
+
+    /* we know the returns from these must be valid */
+    const float *uv_start = uvedit_first_selected_uv_from_vertex(
+        scene, eve_line[0], cd_loop_uv_offset);
+    const float *uv_end = uvedit_first_selected_uv_from_vertex(
+        scene, eve_line[BLI_array_len(eve_line) - 1], cd_loop_uv_offset);
+    /* For UV_STRAIGHTEN_X & UV_STRAIGHTEN_Y modes */
+    float a = 0.0f;
+    eUVWeldAlign tool_local = tool;
+
+    if (tool_local == UV_STRAIGHTEN_X) {
+      if (uv_start[1] == uv_end[1]) {
+        tool_local = UV_STRAIGHTEN;
+      }
+      else {
+        a = (uv_end[0] - uv_start[0]) / (uv_end[1] - uv_start[1]);
+      }
+    }
+    else if (tool_local == UV_STRAIGHTEN_Y) {
+      if (uv_start[0] == uv_end[0]) {
+        tool_local = UV_STRAIGHTEN;
+      }
+      else {
+        a = (uv_end[1] - uv_start[1]) / (uv_end[0] - uv_start[0]);
+      }
+    }
+
+    /* go over all verts except for endpoints */
+    for (i = 0; i < BLI_array_len(eve_line); i++) {
+      BM_ITER_ELEM (l, &liter, eve_line[i], BM_LOOPS_OF_VERT) {
+        if (!uvedit_face_visible_test(scene, l->f)) {
+          continue;
+        }
+
+        if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) {
+          MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
+          /* Projection of point (x, y) over line (x1, y1, x2, y2) along X axis:
+           * new_y = (y2 - y1) / (x2 - x1) * (x - x1) + y1
+           * Maybe this should be a BLI func? Or is it already existing?
+           * Could use interp_v2_v2v2, but not sure it's worth it here. */
+          if (tool_local == UV_STRAIGHTEN_X) {
+            luv->uv[0] = a * (luv->uv[1] - uv_start[1]) + uv_start[0];
+          }
+          else if (tool_local == UV_STRAIGHTEN_Y) {
+            luv->uv[1] = a * (luv->uv[0] - uv_start[0]) + uv_start[1];
+          }
+          else {
+            closest_to_line_segment_v2(luv->uv, luv->uv, uv_start, uv_end);
+          }
+          changed = true;
+        }
+      }
+    }
+  }
+  else {
+    /* error - not a line, needs 3+ points. */
+  }
+
+  MEM_SAFE_FREE(eve_line);
+  return changed;
+}
+
 static void uv_weld_align(bContext *C, eUVWeldAlign tool)
 {
   Scene *scene = CTX_data_scene(C);
@@ -429,194 +617,12 @@ static void uv_weld_align(bContext *C, eUVWeldAlign tool)
       continue;
     }
 
-    const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV);
-
-    if (ELEM(tool, UV_ALIGN_X, UV_WELD)) {
-      BMIter iter, liter;
-      BMFace *efa;
-      BMLoop *l;
-
-      BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
-        if (!uvedit_face_visible_test(scene, efa)) {
-          continue;
-        }
-
-        BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
-          if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) {
-            MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
-            luv->uv[0] = cent[0];
-            changed = true;
-          }
-        }
-      }
-    }
-
-    if (ELEM(tool, UV_ALIGN_Y, UV_WELD)) {
-      BMIter iter, liter;
-      BMFace *efa;
-      BMLoop *l;
-
-      BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
-        if (!uvedit_face_visible_test(scene, efa)) {
-          continue;
-        }
-
-        BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
-          if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) {
-            MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
-            luv->uv[1] = cent[1];
-            changed = true;
-          }
-        }
-      }
+    if (ELEM(tool, UV_ALIGN_AUTO, UV_ALIGN_X, UV_ALIGN_Y, UV_WELD)) {
+      changed |= uvedit_uv_align_weld(scene, em->bm, tool, cent);
     }
 
     if (ELEM(tool, UV_STRAIGHTEN, UV_STRAIGHTEN_X, UV_STRAIGHTEN_Y)) {
-      BMEdge *eed;
-      BMLoop *l;
-      BMVert *eve;
-      BMVert *eve_start;
-      BMIter iter, liter, eiter;
-
-      /* clear tag */
-      BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT, BM_ELEM_TAG, false);
-
-      /* tag verts with a selected UV */
-      BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) {
-        BM_ITER_ELEM (l, &liter, eve, BM_LOOPS_OF_VERT) {
-          if (!uvedit_face_visible_test(scene, l->f)) {
-            continue;
-          }
-
-          if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) {
-            BM_elem_flag_enable(eve, BM_ELEM_TAG);
-            break;
-          }
-        }
-      }
-
-      /* flush vertex tags to edges */
-      BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) {
-        BM_elem_flag_set(
-            eed,
-            BM_ELEM_TAG,
-            (BM_elem_flag_test(eed->v1, BM_ELEM_TAG) && BM_elem_flag_test(eed->v2, BM_ELEM_TAG)));
-      }
-
-      /* find a vertex with only one tagged edge */
-      eve_start = NULL;
-      BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) {
-        int tot_eed_tag = 0;
-        BM_ITER_ELEM (eed, &eiter, eve, BM_EDGES_OF_VERT) {
-          if (BM_elem_flag_test(eed, BM_ELEM_TAG)) {
-            tot_eed_tag++;
-          }
-        }
-
-        if (tot_eed_tag == 1) {
-          eve_start = eve;
-          break;
-        }
-      }
-
-      if (eve_start) {
-        BMVert **eve_line = NULL;
-        BMVert *eve_next = NULL;
-        BLI_array_declare(eve_line);
-        int i;
-
-        eve = eve_start;
-
-        /* walk over edges, building an array of verts in a line */
-        while (eve) {
-          BLI_array_append(eve_line, eve);
-          /* don't touch again */
-          BM_elem_flag_disable(eve, BM_ELEM_TAG);
-
-          eve_next = NULL;
-
-          /* find next eve */
-          BM_ITER_ELEM (eed, &eiter, eve, BM_EDGES_OF_VERT) {
-            if (BM_elem_flag_test(eed, BM_ELEM_TAG)) {
-              BMVert *eve_other = BM_edge_other_vert(eed, eve);
-              if (BM_elem_flag_test(eve_other, BM_ELEM_TAG)) {
-                /* this is a tagged vert we didn't walk over yet, step onto it */
-                eve_next = eve_other;
-                break;
-              }
-            }
-          }
-
-          eve = eve_next;
-        }
-
-        /* now we have all verts, make into a line */
-        if (BLI_array_len(eve_line) > 2) {
-
-          /* we know the returns from these must be valid */
-          const float *uv_start = uvedit_first_selected_uv_from_vertex(
-              scene, eve_line[0], cd_loop_uv_offset);
-          const float *uv_end = uvedit_first_selected_uv_from_vertex(
-              scene, eve_line[BLI_array_len(eve_line) - 1], cd_loop_uv_offset);
-          /* For UV_STRAIGHTEN_X & UV_STRAIGHTEN_Y modes */
-          float a = 0.0f;
-          eUVWeldAlign tool_local = tool;
-
-          if (tool_local == UV_STRAIGHTEN_X) {
-            if (uv_start[1] == uv_end[1]) {
-              tool_local = UV_STRAIGHTEN;
-            }
-            else {
-              a = (uv_end[0] - uv_start[0]) / (uv_end[1] - uv_start[1]);
-            }
-          }
-          else if (tool_local == UV_STRAIGHTEN_Y) {
-            if (uv_start[0] == uv_end[0]) {
-              tool_local = UV_STRAIGHTEN;
-            }
-            else {
-              a = (uv_end[1] - uv_start[1]) / (uv_end[0] - uv_start[0]);
-            }
-          }
-
-          /* go over all verts except for endpoints */
-          for (i = 0; i < BLI_array_len(eve_line); i++) {
-            BM_ITER_ELEM (l, &liter, eve_line[i], BM_LOOPS_OF_VERT) {
-              if (!uvedit_face_visible_test(scene, l->f)) {
-                continue;
-              }
-
-              if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) {
-                MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
-                /* Projection of point (x, y) over line (x1, y1, x2, y2) along X axis:
-                 * new_y = (y2 - y1) / (x2 - x1) * (x - x1) + y1
-                 * Maybe this should be a BLI func? Or is it already existing?
-                 * Could use interp_v2_v2v2, but not sure it's worth it here. */
-                if (tool_local == UV_STRAIGHTEN_X) {
-                  luv->uv[0] = a * (luv->uv[1] - uv_start[1]) + uv_start[0];
-                }
-                else if (tool_local == UV_STRAIGHTEN_Y) {
-                  luv->uv[1] = a * (luv->uv[0] - uv_start[0]) + uv_start[1];
-                }
-                else {
-                  closest_to_line_segment_v2(luv->uv, luv->uv, uv_start, uv_end);
-                }
-                changed = true;
-              }
-            }
-          }
-        }
-        else {
-          /* error - not a line, needs 3+ points. */
-        }
-
-        if (eve_line) {
-          MEM_freeN(eve_line);
-        }
-      }
-      else {
-        /* error - can't find an endpoint. */
-      }
+      changed |= uvedit_uv_straighten(scene, em->bm, tool);
     }
 
     if (changed) {
-- 
cgit v1.2.3


From 9dd27a2c87f426fc9aa30f865cbb00bc8027521d Mon Sep 17 00:00:00 2001
From: Chris Blackbourn 
Date: Fri, 1 Jul 2022 14:21:48 +1200
Subject: UV: Improve UV Straighten operator

Improves UV Straighten in several ways:
- Operate on entire selection.
- One straighten for each selected island.
- Prefers pins to anchor the endpoints of the resulting line.

Differential Revision: D15121
Resolves: T78553
---
 source/blender/editors/uvedit/uvedit_ops.c | 234 ++++++++++++++---------------
 1 file changed, 117 insertions(+), 117 deletions(-)

diff --git a/source/blender/editors/uvedit/uvedit_ops.c b/source/blender/editors/uvedit/uvedit_ops.c
index d872906e63e..70c0333fb27 100644
--- a/source/blender/editors/uvedit/uvedit_ops.c
+++ b/source/blender/editors/uvedit/uvedit_ops.c
@@ -411,152 +411,152 @@ static bool uvedit_uv_align_weld(Scene *scene,
   return changed;
 }
 
-static bool uvedit_uv_straighten(Scene *scene, BMesh *bm, eUVWeldAlign tool)
+/* Bitwise-or together, then choose MLoopUV with highest value. */
+typedef enum eUVEndPointPrecedence {
+  UVEP_INVALID = 0,
+  UVEP_SELECTED = (1 << 0),
+  UVEP_PINNED = (1 << 1), /* i.e. Pinned verts are preferred to selected. */
+} eUVEndPointPrecedence;
+
+static eUVEndPointPrecedence uvedit_line_update_get_precedence(const MLoopUV *luv)
 {
-  bool changed = false;
-  BMEdge *eed;
-  BMLoop *l;
-  BMVert *eve;
-  BMVert *eve_start;
-  BMIter iter, liter, eiter;
+  eUVEndPointPrecedence precedence = UVEP_SELECTED;
+  if (luv->flag & MLOOPUV_PINNED) {
+    precedence |= UVEP_PINNED;
+  }
+  return precedence;
+}
 
-  const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV);
+/* Helper to find two endpoints (`a` and `b`) which have higher precedence, and are far apart.
+ * Note that is only a heuristic and won't always find the best two endpoints.
+ */
+static bool uvedit_line_update_endpoint(const MLoopUV *luv,
+                                        float uv_a[2],
+                                        eUVEndPointPrecedence *prec_a,
+                                        float uv_b[2],
+                                        eUVEndPointPrecedence *prec_b)
+{
+  eUVEndPointPrecedence flags = uvedit_line_update_get_precedence(luv);
 
-  /* clear tag */
-  BM_mesh_elem_hflag_disable_all(bm, BM_VERT, BM_ELEM_TAG, false);
+  float len_sq_a = len_squared_v2v2(uv_a, luv->uv);
+  float len_sq_b = len_squared_v2v2(uv_b, luv->uv);
 
-  /* tag verts with a selected UV */
-  BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
-    BM_ITER_ELEM (l, &liter, eve, BM_LOOPS_OF_VERT) {
-      if (!uvedit_face_visible_test(scene, l->f)) {
-        continue;
-      }
+  /* Caching the value of `len_sq_ab` is unlikely to be faster than recalculating.
+   * Profile before optmizing. */
+  float len_sq_ab = len_squared_v2v2(uv_a, uv_b);
 
-      if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) {
-        BM_elem_flag_enable(eve, BM_ELEM_TAG);
-        break;
-      }
-    }
+  if ((*prec_a < flags && 0.0f < len_sq_b) || (*prec_a == flags && len_sq_ab < len_sq_b)) {
+    *prec_a = flags;
+    copy_v2_v2(uv_a, luv->uv);
+    return true;
   }
 
-  /* flush vertex tags to edges */
-  BM_ITER_MESH (eed, &iter, bm, BM_EDGES_OF_MESH) {
-    BM_elem_flag_set(
-        eed,
-        BM_ELEM_TAG,
-        (BM_elem_flag_test(eed->v1, BM_ELEM_TAG) && BM_elem_flag_test(eed->v2, BM_ELEM_TAG)));
+  if ((*prec_b < flags && 0.0f < len_sq_a) || (*prec_b == flags && len_sq_ab < len_sq_a)) {
+    *prec_b = flags;
+    copy_v2_v2(uv_b, luv->uv);
+    return true;
   }
 
-  /* find a vertex with only one tagged edge */
-  eve_start = NULL;
-  BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
-    int tot_eed_tag = 0;
-    BM_ITER_ELEM (eed, &eiter, eve, BM_EDGES_OF_VERT) {
-      if (BM_elem_flag_test(eed, BM_ELEM_TAG)) {
-        tot_eed_tag++;
-      }
-    }
+  return false;
+}
 
-    if (tot_eed_tag == 1) {
-      eve_start = eve;
+/* Find two end extreme points to specify a line, then straighten `len` elements
+ * by moving UVs on the X-axis, Y-axis, or the closest point on the line segment.
+ */
+static bool uvedit_uv_straighten_elements(const UvElement *element,
+                                          const int len,
+                                          const int cd_loop_uv_offset,
+                                          const eUVWeldAlign tool)
+{
+  float uv_start[2];
+  float uv_end[2];
+  eUVEndPointPrecedence prec_start = UVEP_INVALID;
+  eUVEndPointPrecedence prec_end = UVEP_INVALID;
+
+  /* Find start and end of line. */
+  for (int i = 0; i < 10; i++) { /* Heuristic to prevent infinite loop. */
+    bool update = false;
+    for (int j = 0; j < len; j++) {
+      MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(element[j].l, cd_loop_uv_offset);
+      update |= uvedit_line_update_endpoint(luv, uv_start, &prec_start, uv_end, &prec_end);
+    }
+    if (!update) {
       break;
     }
   }
 
-  if (!eve_start) {
-    return false;
+  if (prec_start == UVEP_INVALID || prec_end == UVEP_INVALID) {
+    return false; /* Unable to find two endpoints. */
   }
-  BMVert **eve_line = NULL;
-  BMVert *eve_next = NULL;
-  BLI_array_declare(eve_line);
-  int i;
-
-  eve = eve_start;
 
-  /* walk over edges, building an array of verts in a line */
-  while (eve) {
-    BLI_array_append(eve_line, eve);
-    /* don't touch again */
-    BM_elem_flag_disable(eve, BM_ELEM_TAG);
+  float a = 0.0f; /* Similar to "slope". */
+  eUVWeldAlign tool_local = tool;
 
-    eve_next = NULL;
-
-    /* find next eve */
-    BM_ITER_ELEM (eed, &eiter, eve, BM_EDGES_OF_VERT) {
-      if (BM_elem_flag_test(eed, BM_ELEM_TAG)) {
-        BMVert *eve_other = BM_edge_other_vert(eed, eve);
-        if (BM_elem_flag_test(eve_other, BM_ELEM_TAG)) {
-          /* this is a tagged vert we didn't walk over yet, step onto it */
-          eve_next = eve_other;
-          break;
-        }
-      }
+  if (tool_local == UV_STRAIGHTEN_X) {
+    if (uv_start[1] == uv_end[1]) {
+      /* Caution, different behavior outside line segment. */
+      tool_local = UV_STRAIGHTEN;
+    }
+    else {
+      a = (uv_end[0] - uv_start[0]) / (uv_end[1] - uv_start[1]);
+    }
+  }
+  else if (tool_local == UV_STRAIGHTEN_Y) {
+    if (uv_start[0] == uv_end[0]) {
+      /* Caution, different behavior outside line segment. */
+      tool_local = UV_STRAIGHTEN;
+    }
+    else {
+      a = (uv_end[1] - uv_start[1]) / (uv_end[0] - uv_start[0]);
     }
-
-    eve = eve_next;
   }
 
-  /* now we have all verts, make into a line */
-  if (BLI_array_len(eve_line) > 2) {
-
-    /* we know the returns from these must be valid */
-    const float *uv_start = uvedit_first_selected_uv_from_vertex(
-        scene, eve_line[0], cd_loop_uv_offset);
-    const float *uv_end = uvedit_first_selected_uv_from_vertex(
-        scene, eve_line[BLI_array_len(eve_line) - 1], cd_loop_uv_offset);
-    /* For UV_STRAIGHTEN_X & UV_STRAIGHTEN_Y modes */
-    float a = 0.0f;
-    eUVWeldAlign tool_local = tool;
-
+  bool changed = false;
+  for (int j = 0; j < len; j++) {
+    MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(element[j].l, cd_loop_uv_offset);
+    /* Projection of point (x, y) over line (x1, y1, x2, y2) along X axis:
+     * new_y = (y2 - y1) / (x2 - x1) * (x - x1) + y1
+     * Maybe this should be a BLI func? Or is it already existing?
+     * Could use interp_v2_v2v2, but not sure it's worth it here. */
     if (tool_local == UV_STRAIGHTEN_X) {
-      if (uv_start[1] == uv_end[1]) {
-        tool_local = UV_STRAIGHTEN;
-      }
-      else {
-        a = (uv_end[0] - uv_start[0]) / (uv_end[1] - uv_start[1]);
-      }
+      luv->uv[0] = a * (luv->uv[1] - uv_start[1]) + uv_start[0];
     }
     else if (tool_local == UV_STRAIGHTEN_Y) {
-      if (uv_start[0] == uv_end[0]) {
-        tool_local = UV_STRAIGHTEN;
-      }
-      else {
-        a = (uv_end[1] - uv_start[1]) / (uv_end[0] - uv_start[0]);
-      }
+      luv->uv[1] = a * (luv->uv[0] - uv_start[0]) + uv_start[1];
     }
+    else {
+      closest_to_line_segment_v2(luv->uv, luv->uv, uv_start, uv_end);
+    }
+    changed = true; /* TODO: Did the UV actually move? */
+  }
+  return changed;
+}
 
-    /* go over all verts except for endpoints */
-    for (i = 0; i < BLI_array_len(eve_line); i++) {
-      BM_ITER_ELEM (l, &liter, eve_line[i], BM_LOOPS_OF_VERT) {
-        if (!uvedit_face_visible_test(scene, l->f)) {
-          continue;
-        }
+/* Group selected UVs into islands, then apply uvedit_uv_straighten_elements to each island. */
+static bool uvedit_uv_straighten(Scene *scene, BMesh *bm, eUVWeldAlign tool)
+{
+  const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV);
+  if (cd_loop_uv_offset == -1) {
+    return false;
+  }
 
-        if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) {
-          MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
-          /* Projection of point (x, y) over line (x1, y1, x2, y2) along X axis:
-           * new_y = (y2 - y1) / (x2 - x1) * (x - x1) + y1
-           * Maybe this should be a BLI func? Or is it already existing?
-           * Could use interp_v2_v2v2, but not sure it's worth it here. */
-          if (tool_local == UV_STRAIGHTEN_X) {
-            luv->uv[0] = a * (luv->uv[1] - uv_start[1]) + uv_start[0];
-          }
-          else if (tool_local == UV_STRAIGHTEN_Y) {
-            luv->uv[1] = a * (luv->uv[0] - uv_start[0]) + uv_start[1];
-          }
-          else {
-            closest_to_line_segment_v2(luv->uv, luv->uv, uv_start, uv_end);
-          }
-          changed = true;
-        }
-      }
-    }
+  UvElementMap *element_map = BM_uv_element_map_create(bm, scene, false, true, false, true);
+  if (element_map == NULL) {
+    return false;
   }
-  else {
-    /* error - not a line, needs 3+ points. */
+
+  bool changed = false;
+
+  /* Loop backwards to simplify logic. */
+  int j1 = element_map->totalUVs;
+  for (int i = element_map->totalIslands - 1; i >= 0; --i) {
+    int j0 = element_map->islandIndices[i];
+    changed |= uvedit_uv_straighten_elements(
+        element_map->buf + j0, j1 - j0, cd_loop_uv_offset, tool);
+    j1 = j0;
   }
 
-  MEM_SAFE_FREE(eve_line);
+  BM_uv_element_map_free(element_map);
   return changed;
 }
 
-- 
cgit v1.2.3


From faa97de208dab9c442213c8265092876138e3ec0 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Mon, 4 Jul 2022 15:06:23 +1000
Subject: Cleanup: correct function signature for UI_block_add_view for
 grid_view

---
 source/blender/editors/include/UI_interface.hh     | 2 +-
 source/blender/editors/interface/interface_view.cc | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/source/blender/editors/include/UI_interface.hh b/source/blender/editors/include/UI_interface.hh
index 3dc56b01993..a27edb54c0f 100644
--- a/source/blender/editors/include/UI_interface.hh
+++ b/source/blender/editors/include/UI_interface.hh
@@ -59,7 +59,7 @@ void attribute_search_add_items(
 blender::ui::AbstractGridView *UI_block_add_view(
     uiBlock &block,
     blender::StringRef idname,
-    std::unique_ptr tree_view);
+    std::unique_ptr grid_view);
 blender::ui::AbstractTreeView *UI_block_add_view(
     uiBlock &block,
     blender::StringRef idname,
diff --git a/source/blender/editors/interface/interface_view.cc b/source/blender/editors/interface/interface_view.cc
index 72a6e3ba5e3..2d93e03ea18 100644
--- a/source/blender/editors/interface/interface_view.cc
+++ b/source/blender/editors/interface/interface_view.cc
@@ -57,9 +57,9 @@ static T *ui_block_add_view_impl(uiBlock &block,
 
 AbstractGridView *UI_block_add_view(uiBlock &block,
                                     StringRef idname,
-                                    std::unique_ptr tree_view)
+                                    std::unique_ptr grid_view)
 {
-  return ui_block_add_view_impl(block, idname, std::move(tree_view));
+  return ui_block_add_view_impl(block, idname, std::move(grid_view));
 }
 
 AbstractTreeView *UI_block_add_view(uiBlock &block,
-- 
cgit v1.2.3


From 148dcb395401b6b11a5a56b03c449e9852e5e876 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Mon, 4 Jul 2022 15:16:24 +1000
Subject: Cleanup: spelling in comments

---
 source/blender/blenfont/intern/blf_glyph.c            |  2 +-
 source/blender/blenfont/intern/blf_thumbs.c           |  2 +-
 source/blender/blenkernel/BKE_image.h                 |  2 +-
 source/blender/blenkernel/intern/tracking.c           |  2 +-
 source/blender/compositor/intern/COM_Converter.h      |  4 ++--
 source/blender/editors/space_text/text_format_lua.c   | 13 +++++++------
 source/blender/editors/space_text/text_format_osl.c   | 11 ++++++-----
 source/blender/editors/space_text/text_format_pov.c   | 11 ++++++-----
 .../blender/editors/space_text/text_format_pov_ini.c  | 11 ++++++-----
 source/blender/editors/space_text/text_format_py.c    | 11 ++++++-----
 source/blender/editors/uvedit/uvedit_ops.c            | 14 +++++++++-----
 source/blender/gpu/metal/mtl_backend.mm               |  2 +-
 source/blender/gpu/metal/mtl_common.hh                |  2 +-
 source/blender/gpu/metal/mtl_context.mm               |  4 ++--
 source/blender/gpu/metal/mtl_memory.hh                | 19 ++++++++++---------
 source/blender/gpu/metal/mtl_memory.mm                |  6 +++---
 16 files changed, 63 insertions(+), 53 deletions(-)

diff --git a/source/blender/blenfont/intern/blf_glyph.c b/source/blender/blenfont/intern/blf_glyph.c
index 63cf3b3e564..2eb43e3df43 100644
--- a/source/blender/blenfont/intern/blf_glyph.c
+++ b/source/blender/blenfont/intern/blf_glyph.c
@@ -378,7 +378,7 @@ static eUnicodeBlock unicode_blocks[] = {
     {0xFE30, 0xFE4F, 65},     /* CJK Compatibility Forms. */
     {0xFE50, 0xFE6F, 66},     /* Small Form Variants. */
     {0xFE70, 0xFEFF, 67},     /* Arabic Presentation Forms-B. */
-    {0xFF00, 0xFFEF, 68},     /* Halfwidth And Fullwidth Forms. */
+    {0xFF00, 0xFFEF, 68},     /* Half-width And Full-width Forms. */
     {0xFFF0, 0xFFFF, 69},     /* Specials. */
     {0x10000, 0x1013F, 101},  /* Linear B. */
     {0x10140, 0x1018F, 102},  /* Ancient Greek Numbers. */
diff --git a/source/blender/blenfont/intern/blf_thumbs.c b/source/blender/blenfont/intern/blf_thumbs.c
index a75072f854f..9460e9413d1 100644
--- a/source/blender/blenfont/intern/blf_thumbs.c
+++ b/source/blender/blenfont/intern/blf_thumbs.c
@@ -87,7 +87,7 @@ void BLF_thumb_preview(const char *filepath,
     font->pos[1] -= (int)((float)blf_font_ascender(font) * 1.1f);
 
     /* We fallback to default english strings in case not enough chars are available in current
-     * font for given translated string (useful in non-latin i18n context, like Chinese,
+     * font for given translated string (useful in non-Latin i18n context, like Chinese,
      * since many fonts will then show nothing but ugly 'missing char' in their preview).
      * Does not handle all cases, but much better than nothing.
      */
diff --git a/source/blender/blenkernel/BKE_image.h b/source/blender/blenkernel/BKE_image.h
index e54932fbc4e..6ec1285af0c 100644
--- a/source/blender/blenkernel/BKE_image.h
+++ b/source/blender/blenkernel/BKE_image.h
@@ -198,7 +198,7 @@ struct Image *BKE_image_add_from_imbuf(struct Main *bmain, struct ImBuf *ibuf, c
  * For a non-viewer single-buffer image (single frame file, or generated image) replace its image
  * buffer with the given one.
  * If an unsupported image type (multi-layer, image sequence, ...) the function will assert in the
- * debug mode and will have an underfined behavior in the release mode.
+ * debug mode and will have an undefined behavior in the release mode.
  */
 void BKE_image_replace_imbuf(struct Image *image, struct ImBuf *ibuf);
 
diff --git a/source/blender/blenkernel/intern/tracking.c b/source/blender/blenkernel/intern/tracking.c
index 6abc40e89bc..cd1af5a8de4 100644
--- a/source/blender/blenkernel/intern/tracking.c
+++ b/source/blender/blenkernel/intern/tracking.c
@@ -2839,7 +2839,7 @@ ImBuf *BKE_tracking_get_plane_imbuf(const ImBuf *frame_ibuf,
   ImBuf *plane_ibuf = IMB_allocImBuf(
       num_samples_x, num_samples_y, 32, frame_ibuf->rect_float ? IB_rectfloat : IB_rect);
 
-  /* Calculate corner coordinates in pixel space, as spearate X/Y arrays. */
+  /* Calculate corner coordinates in pixel space, as separate X/Y arrays. */
   const double src_pixel_x[4] = {corners[0][0] * frame_width,
                                  corners[1][0] * frame_width,
                                  corners[2][0] * frame_width,
diff --git a/source/blender/compositor/intern/COM_Converter.h b/source/blender/compositor/intern/COM_Converter.h
index e0164b9162d..eba61e07e1a 100644
--- a/source/blender/compositor/intern/COM_Converter.h
+++ b/source/blender/compositor/intern/COM_Converter.h
@@ -37,8 +37,8 @@ Node *COM_convert_bnode(bNode *b_node);
 bool COM_bnode_is_fast_node(const bNode &b_node);
 
 /**
- * \brief This function will add a datetype conversion rule when the to-socket does not support the
- * from-socket actual data type.
+ * \brief This function will add a date-type conversion rule when the to-socket does not support
+ * the from-socket actual data type.
  */
 NodeOperation *COM_convert_data_type(const NodeOperationOutput &from,
                                      const NodeOperationInput &to);
diff --git a/source/blender/editors/space_text/text_format_lua.c b/source/blender/editors/space_text/text_format_lua.c
index f7d7f9c6238..4b3ee1c15c7 100644
--- a/source/blender/editors/space_text/text_format_lua.c
+++ b/source/blender/editors/space_text/text_format_lua.c
@@ -264,7 +264,7 @@ static void txtfmt_lua_format_line(SpaceText *st, TextLine *line, const bool do_
         cont = (*str == '"') ? FMT_CONT_QUOTEDOUBLE : FMT_CONT_QUOTESINGLE;
         *fmt = FMT_TYPE_STRING;
       }
-      /* White-space (all ws. has been converted to spaces). */
+      /* White-space (all white-space has been converted to spaces). */
       else if (*str == ' ') {
         *fmt = FMT_TYPE_WHITESPACE;
       }
@@ -287,18 +287,19 @@ static void txtfmt_lua_format_line(SpaceText *st, TextLine *line, const bool do_
       else if ((*str != '#') && text_check_delim(*str)) {
         *fmt = FMT_TYPE_SYMBOL;
       }
-      /* Identifiers and other text (no previous ws. or delims. so text continues) */
+      /* Identifiers and other text (no previous white-space/delimiters so text continues). */
       else if (prev == FMT_TYPE_DEFAULT) {
         str += BLI_str_utf8_size_safe(str) - 1;
         *fmt = FMT_TYPE_DEFAULT;
       }
-      /* Not ws, a digit, punct, or continuing text. Must be new, check for special words */
+      /* Not white-space, a digit, punctuation, or continuing text.
+       * Must be new, check for special words. */
       else {
-        /* Keep aligned args for readability. */
+        /* Keep aligned arguments for readability. */
         /* clang-format off */
 
-        /* Special vars(v) or built-in keywords(b) */
-        /* keep in sync with 'txtfmt_osl_format_identifier()' */
+        /* Special `vars(v)` or built-in `keywords(b)` */
+        /* keep in sync with `txtfmt_osl_format_identifier()`. */
         if        ((i = txtfmt_lua_find_specialvar(str))   != -1) { prev = FMT_TYPE_SPECIAL;
         } else if ((i = txtfmt_lua_find_keyword(str))      != -1) { prev = FMT_TYPE_KEYWORD;
         }
diff --git a/source/blender/editors/space_text/text_format_osl.c b/source/blender/editors/space_text/text_format_osl.c
index 27dc1174c49..575eadeee66 100644
--- a/source/blender/editors/space_text/text_format_osl.c
+++ b/source/blender/editors/space_text/text_format_osl.c
@@ -285,7 +285,7 @@ static void txtfmt_osl_format_line(SpaceText *st, TextLine *line, const bool do_
         cont = (*str == '"') ? FMT_CONT_QUOTEDOUBLE : FMT_CONT_QUOTESINGLE;
         *fmt = FMT_TYPE_STRING;
       }
-      /* White-space (all ws. has been converted to spaces). */
+      /* White-space (all white-space has been converted to spaces). */
       else if (*str == ' ') {
         *fmt = FMT_TYPE_WHITESPACE;
       }
@@ -298,18 +298,19 @@ static void txtfmt_osl_format_line(SpaceText *st, TextLine *line, const bool do_
       else if ((*str != '#') && text_check_delim(*str)) {
         *fmt = FMT_TYPE_SYMBOL;
       }
-      /* Identifiers and other text (no previous ws. or delims. so text continues) */
+      /* Identifiers and other text (no previous white-space or delimiters. so text continues). */
       else if (prev == FMT_TYPE_DEFAULT) {
         str += BLI_str_utf8_size_safe(str) - 1;
         *fmt = FMT_TYPE_DEFAULT;
       }
-      /* Not ws, a digit, punct, or continuing text. Must be new, check for special words */
+      /* Not white-space, a digit, punctuation, or continuing text.
+       * Must be new, check for special words. */
       else {
-        /* Keep aligned args for readability. */
+        /* Keep aligned arguments for readability. */
         /* clang-format off */
 
         /* Special vars(v) or built-in keywords(b) */
-        /* keep in sync with 'txtfmt_osl_format_identifier()' */
+        /* keep in sync with `txtfmt_osl_format_identifier()`. */
         if        ((i = txtfmt_osl_find_specialvar(str))   != -1) { prev = FMT_TYPE_SPECIAL;
         } else if ((i = txtfmt_osl_find_builtinfunc(str))  != -1) { prev = FMT_TYPE_KEYWORD;
         } else if ((i = txtfmt_osl_find_reserved(str))     != -1) { prev = FMT_TYPE_RESERVED;
diff --git a/source/blender/editors/space_text/text_format_pov.c b/source/blender/editors/space_text/text_format_pov.c
index 7c2f098f0fa..7c2c4829ad3 100644
--- a/source/blender/editors/space_text/text_format_pov.c
+++ b/source/blender/editors/space_text/text_format_pov.c
@@ -857,7 +857,7 @@ static void txtfmt_pov_format_line(SpaceText *st, TextLine *line, const bool do_
         cont = (*str == '"') ? FMT_CONT_QUOTEDOUBLE : FMT_CONT_QUOTESINGLE;
         *fmt = FMT_TYPE_STRING;
       }
-      /* White-space (all ws. has been converted to spaces). */
+      /* White-space (all white-space has been converted to spaces). */
       else if (*str == ' ') {
         *fmt = FMT_TYPE_WHITESPACE;
       }
@@ -880,18 +880,19 @@ static void txtfmt_pov_format_line(SpaceText *st, TextLine *line, const bool do_
       else if (text_check_delim(*str)) {
         *fmt = FMT_TYPE_SYMBOL;
       }
-      /* Identifiers and other text (no previous ws. or delims. so text continues) */
+      /* Identifiers and other text (no previous white-space/delimiters so text continues). */
       else if (prev == FMT_TYPE_DEFAULT) {
         str += BLI_str_utf8_size_safe(str) - 1;
         *fmt = FMT_TYPE_DEFAULT;
       }
-      /* Not ws, a digit, punct, or continuing text. Must be new, check for special words */
+      /* Not white-space, a digit, punctuation, or continuing text.
+       * Must be new, check for special words. */
       else {
-        /* Keep aligned args for readability. */
+        /* Keep aligned arguments for readability. */
         /* clang-format off */
 
         /* Special vars(v) or built-in keywords(b) */
-        /* keep in sync with 'txtfmt_pov_format_identifier()' */
+        /* keep in sync with `txtfmt_pov_format_identifier()`. */
         if        ((i = txtfmt_pov_find_specialvar(str))        != -1) { prev = FMT_TYPE_SPECIAL;
         } else if ((i = txtfmt_pov_find_keyword(str))           != -1) { prev = FMT_TYPE_KEYWORD;
         } else if ((i = txtfmt_pov_find_reserved_keywords(str)) != -1) { prev = FMT_TYPE_RESERVED;
diff --git a/source/blender/editors/space_text/text_format_pov_ini.c b/source/blender/editors/space_text/text_format_pov_ini.c
index 6844f08cca6..dda3b7089fe 100644
--- a/source/blender/editors/space_text/text_format_pov_ini.c
+++ b/source/blender/editors/space_text/text_format_pov_ini.c
@@ -435,7 +435,7 @@ static void txtfmt_pov_ini_format_line(SpaceText *st, TextLine *line, const bool
         cont = (*str == '"') ? FMT_CONT_QUOTEDOUBLE : FMT_CONT_QUOTESINGLE;
         *fmt = FMT_TYPE_STRING;
       }
-      /* White-space (all ws. has been converted to spaces). */
+      /* White-space (all white-space has been converted to spaces). */
       else if (*str == ' ') {
         *fmt = FMT_TYPE_WHITESPACE;
       }
@@ -458,18 +458,19 @@ static void txtfmt_pov_ini_format_line(SpaceText *st, TextLine *line, const bool
       else if ((*str != '#') && text_check_delim(*str)) {
         *fmt = FMT_TYPE_SYMBOL;
       }
-      /* Identifiers and other text (no previous ws. or delims. so text continues) */
+      /* Identifiers and other text (no previous white-space/delimiters so text continues). */
       else if (prev == FMT_TYPE_DEFAULT) {
         str += BLI_str_utf8_size_safe(str) - 1;
         *fmt = FMT_TYPE_DEFAULT;
       }
-      /* Not ws, a digit, punct, or continuing text. Must be new, check for special words */
+      /* Not white-space, a digit, punctuation, or continuing text.
+       * Must be new, check for special words */
       else {
-        /* Keep aligned args for readability. */
+        /* Keep aligned arguments for readability. */
         /* clang-format off */
 
         /* Special vars(v) or built-in keywords(b) */
-        /* keep in sync with 'txtfmt_ini_format_identifier()' */
+        /* keep in sync with `txtfmt_ini_format_identifier()`. */
         if        ((i = txtfmt_ini_find_keyword(str))  != -1) { prev = FMT_TYPE_KEYWORD;
         } else if ((i = txtfmt_ini_find_reserved(str)) != -1) { prev = FMT_TYPE_RESERVED;
 }
diff --git a/source/blender/editors/space_text/text_format_py.c b/source/blender/editors/space_text/text_format_py.c
index 47d0168195b..aa361f07932 100644
--- a/source/blender/editors/space_text/text_format_py.c
+++ b/source/blender/editors/space_text/text_format_py.c
@@ -425,7 +425,7 @@ static void txtfmt_py_format_line(SpaceText *st, TextLine *line, const bool do_n
         }
         *fmt = FMT_TYPE_STRING;
       }
-      /* White-space (all ws. has been converted to spaces). */
+      /* White-space (all white-space has been converted to spaces). */
       else if (*str == ' ') {
         *fmt = FMT_TYPE_WHITESPACE;
       }
@@ -447,18 +447,19 @@ static void txtfmt_py_format_line(SpaceText *st, TextLine *line, const bool do_n
       else if ((*str != '@') && text_check_delim(*str)) {
         *fmt = FMT_TYPE_SYMBOL;
       }
-      /* Identifiers and other text (no previous ws. or delims. so text continues) */
+      /* Identifiers and other text (no previous white-space/delimiters so text continues). */
       else if (prev == FMT_TYPE_DEFAULT) {
         str += BLI_str_utf8_size_safe(str) - 1;
         *fmt = FMT_TYPE_DEFAULT;
       }
-      /* Not ws, a digit, punct, or continuing text. Must be new, check for special words */
+      /* Not white-space, a digit, punctuation, or continuing text.
+       * Must be new, check for special words. */
       else {
-        /* Keep aligned args for readability. */
+        /* Keep aligned arguments for readability. */
         /* clang-format off */
 
         /* Special vars(v) or built-in keywords(b) */
-        /* keep in sync with 'txtfmt_py_format_identifier()' */
+        /* keep in sync with `txtfmt_py_format_identifier()`. */
         if        ((i = txtfmt_py_find_specialvar(str))   != -1) { prev = FMT_TYPE_SPECIAL;
         } else if ((i = txtfmt_py_find_builtinfunc(str))  != -1) { prev = FMT_TYPE_KEYWORD;
         } else if ((i = txtfmt_py_find_decorator(str))    != -1) { prev = FMT_TYPE_DIRECTIVE;
diff --git a/source/blender/editors/uvedit/uvedit_ops.c b/source/blender/editors/uvedit/uvedit_ops.c
index 70c0333fb27..4844ff22b68 100644
--- a/source/blender/editors/uvedit/uvedit_ops.c
+++ b/source/blender/editors/uvedit/uvedit_ops.c
@@ -411,7 +411,7 @@ static bool uvedit_uv_align_weld(Scene *scene,
   return changed;
 }
 
-/* Bitwise-or together, then choose MLoopUV with highest value. */
+/** Bitwise-or together, then choose #MLoopUV with highest value. */
 typedef enum eUVEndPointPrecedence {
   UVEP_INVALID = 0,
   UVEP_SELECTED = (1 << 0),
@@ -427,7 +427,8 @@ static eUVEndPointPrecedence uvedit_line_update_get_precedence(const MLoopUV *lu
   return precedence;
 }
 
-/* Helper to find two endpoints (`a` and `b`) which have higher precedence, and are far apart.
+/**
+ * Helper to find two endpoints (`a` and `b`) which have higher precedence, and are far apart.
  * Note that is only a heuristic and won't always find the best two endpoints.
  */
 static bool uvedit_line_update_endpoint(const MLoopUV *luv,
@@ -442,7 +443,7 @@ static bool uvedit_line_update_endpoint(const MLoopUV *luv,
   float len_sq_b = len_squared_v2v2(uv_b, luv->uv);
 
   /* Caching the value of `len_sq_ab` is unlikely to be faster than recalculating.
-   * Profile before optmizing. */
+   * Profile before optimizing. */
   float len_sq_ab = len_squared_v2v2(uv_a, uv_b);
 
   if ((*prec_a < flags && 0.0f < len_sq_b) || (*prec_a == flags && len_sq_ab < len_sq_b)) {
@@ -460,7 +461,8 @@ static bool uvedit_line_update_endpoint(const MLoopUV *luv,
   return false;
 }
 
-/* Find two end extreme points to specify a line, then straighten `len` elements
+/**
+ * Find two end extreme points to specify a line, then straighten `len` elements
  * by moving UVs on the X-axis, Y-axis, or the closest point on the line segment.
  */
 static bool uvedit_uv_straighten_elements(const UvElement *element,
@@ -532,7 +534,9 @@ static bool uvedit_uv_straighten_elements(const UvElement *element,
   return changed;
 }
 
-/* Group selected UVs into islands, then apply uvedit_uv_straighten_elements to each island. */
+/**
+ * Group selected UVs into islands, then apply uvedit_uv_straighten_elements to each island.
+ */
 static bool uvedit_uv_straighten(Scene *scene, BMesh *bm, eUVWeldAlign tool)
 {
   const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV);
diff --git a/source/blender/gpu/metal/mtl_backend.mm b/source/blender/gpu/metal/mtl_backend.mm
index 117b8352a0a..d2524cc16e3 100644
--- a/source/blender/gpu/metal/mtl_backend.mm
+++ b/source/blender/gpu/metal/mtl_backend.mm
@@ -127,7 +127,7 @@ void MTLBackend::render_end()
 
 void MTLBackend::render_step()
 {
-  /* NOTE(Metal): Primarily called from main thread, but below datastructures
+  /* NOTE(Metal): Primarily called from main thread, but below data-structures
    * and operations are thread-safe, and GPUContext rendering coordination
    * is also thread-safe. */
 
diff --git a/source/blender/gpu/metal/mtl_common.hh b/source/blender/gpu/metal/mtl_common.hh
index 28404d91b4b..44ba786f90f 100644
--- a/source/blender/gpu/metal/mtl_common.hh
+++ b/source/blender/gpu/metal/mtl_common.hh
@@ -10,7 +10,7 @@
 #define MTL_MAX_COMMAND_BUFFERS 64
 
 /* Number of frames for which we retain in-flight resources such as scratch buffers.
- * Set as number of GPU frames in flight, plus an additioanl value for extra possible CPU frame. */
+ * Set as number of GPU frames in flight, plus an additional value for extra possible CPU frame. */
 #define MTL_NUM_SAFE_FRAMES (MTL_MAX_DRAWABLES + 1)
 
 #endif
diff --git a/source/blender/gpu/metal/mtl_context.mm b/source/blender/gpu/metal/mtl_context.mm
index 2cf70718b76..2ab2e20158a 100644
--- a/source/blender/gpu/metal/mtl_context.mm
+++ b/source/blender/gpu/metal/mtl_context.mm
@@ -16,7 +16,7 @@ using namespace blender::gpu;
 
 namespace blender::gpu {
 
-/* Global memory mamnager */
+/* Global memory manager. */
 MTLBufferPool MTLContext::global_memory_manager;
 
 /* -------------------------------------------------------------------- */
@@ -51,7 +51,7 @@ MTLContext::MTLContext(void *ghost_window) : memory_manager(*this), main_command
   this->memory_manager.init();
   this->state_manager = new MTLStateManager(this);
 
-  /* Ensure global memory manager is initialied */
+  /* Ensure global memory manager is initialized. */
   MTLContext::global_memory_manager.init(this->device);
 
   /* Initialize texture read/update structures. */
diff --git a/source/blender/gpu/metal/mtl_memory.hh b/source/blender/gpu/metal/mtl_memory.hh
index 81793b0647c..4dbc1b29c6b 100644
--- a/source/blender/gpu/metal/mtl_memory.hh
+++ b/source/blender/gpu/metal/mtl_memory.hh
@@ -23,7 +23,7 @@
  * The Metal Backend Memory manager is designed to provide an interface
  * for all other MTL_* modules where memory allocation is required.
  *
- * Different allocation strategies and datastructures are used depending
+ * Different allocation strategies and data-structures are used depending
  * on how the data is used by the backend. These aim to optimally handle
  * system memory and abstract away any complexity from the MTL_* modules
  * themselves.
@@ -107,8 +107,8 @@ class MTLBuffer {
   /* Metal resource. */
   id metal_buffer_;
 
-  /* Host-visible mapped-memory pointer. Behaviour depends on buffer type:
-   * - Shared buffers: pointer represents base address of MTLBuffer whose data
+  /* Host-visible mapped-memory pointer. Behavior depends on buffer type:
+   * - Shared buffers: pointer represents base address of #MTLBuffer whose data
    *                   access has shared access by both the CPU and GPU on
    *                   Unified Memory Architectures (UMA).
    * - Managed buffer: Host-side mapped buffer region for CPU (Host) access. Managed buffers
@@ -350,14 +350,14 @@ class MTLBufferPool {
   bool ensure_initialised_ = false;
   id device_ = nil;
 
-  /* The buffer selection aims to pick a buffer which meets the minimum size requierments.
+  /* The buffer selection aims to pick a buffer which meets the minimum size requirements.
    * To do this, we keep an ordered set of all available buffers. If the buffer is larger than the
-   * desired allocation size, we check it aginst `mtl_buffer_size_threshold_factor_`, which defines
-   * what % larger than the original allocation the buffer can be.
+   * desired allocation size, we check it against `mtl_buffer_size_threshold_factor_`,
+   * which defines what % larger than the original allocation the buffer can be.
    * - A higher value results in greater re-use of previously allocated buffers of similar sizes.
-   * - A lower value may result in more dynamic allocations, but minimised memory usage for a given
+   * - A lower value may result in more dynamic allocations, but minimized memory usage for a given
    *   scenario.
-   * The current value of 1.26 is calibrated for optimal performance and memory utilisation. */
+   * The current value of 1.26 is calibrated for optimal performance and memory utilization. */
   static constexpr float mtl_buffer_size_threshold_factor_ = 1.26;
 
   /* Buffer pools using MTLResourceOptions as key for allocation type.
@@ -453,7 +453,8 @@ class MTLScratchBufferManager {
   MTLScratchBufferManager(MTLContext &context) : context_(context){};
   ~MTLScratchBufferManager();
 
-  /* Explicit initialisation and freeing of resources. Init must occur after device creation. */
+  /* Explicit initialization and freeing of resources.
+   * Initialization must occur after device creation. */
   void init();
   void free();
 
diff --git a/source/blender/gpu/metal/mtl_memory.mm b/source/blender/gpu/metal/mtl_memory.mm
index 5c5938997e6..6316a49407d 100644
--- a/source/blender/gpu/metal/mtl_memory.mm
+++ b/source/blender/gpu/metal/mtl_memory.mm
@@ -58,7 +58,7 @@ void MTLBufferPool::free()
 
 gpu::MTLBuffer *MTLBufferPool::allocate_buffer(uint64_t size, bool cpu_visible, const void *bytes)
 {
-  /* Allocate buffer with default HW-compatible alignemnt of 256 bytes.
+  /* Allocate buffer with default HW-compatible alignment of 256 bytes.
    * See https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf for more. */
   return this->allocate_buffer_aligned(size, 256, cpu_visible, bytes);
 }
@@ -342,7 +342,7 @@ void MTLBufferPool::insert_buffer_into_pool(MTLResourceOptions options, gpu::MTL
   /* Ensure pool exists. */
   this->ensure_buffer_pool(options);
 
-  /* TODO(Metal): Support purgability - Allow buffer in pool to have its memory taken back by the
+  /* TODO(Metal): Support purgeability - Allow buffer in pool to have its memory taken back by the
    * OS if needed. As we keep allocations around, they may not actually be in use, but we can
    * ensure they do not block other apps from using memory. Upon a buffer being needed again, we
    * can reset this state.
@@ -615,7 +615,7 @@ void MTLScratchBufferManager::init()
   if (!this->initialised_) {
     BLI_assert(context_.device);
 
-    /* Initialise Scratch buffers */
+    /* Initialize Scratch buffers. */
     for (int sb = 0; sb < mtl_max_scratch_buffers_; sb++) {
       scratch_buffers_[sb] = new MTLCircularBuffer(
           context_, mtl_scratch_buffer_initial_size_, true);
-- 
cgit v1.2.3


From cbb897070d1e4f93c6a1ca107277944914a1ff51 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Mon, 4 Jul 2022 16:04:34 +1000
Subject: Cleanup: remove unused WM_event_is_last_mousemove

This was part of walk-mode logic that implemented it's own cursor-grab,
now this has been moved to use GHOST's cursor grabbing,
it's no longer needed.
---
 source/blender/windowmanager/WM_api.h                |  1 -
 source/blender/windowmanager/intern/wm_event_query.c | 10 ----------
 2 files changed, 11 deletions(-)

diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h
index ab48d6f6c39..d852b85a3d0 100644
--- a/source/blender/windowmanager/WM_api.h
+++ b/source/blender/windowmanager/WM_api.h
@@ -1527,7 +1527,6 @@ void WM_event_print(const struct wmEvent *event);
 bool WM_event_is_modal_drag_exit(const struct wmEvent *event,
                                  short init_event_type,
                                  short init_event_val);
-bool WM_event_is_last_mousemove(const struct wmEvent *event);
 bool WM_event_is_mouse_drag(const struct wmEvent *event);
 bool WM_event_is_mouse_drag_or_press(const wmEvent *event);
 int WM_event_drag_direction(const wmEvent *event);
diff --git a/source/blender/windowmanager/intern/wm_event_query.c b/source/blender/windowmanager/intern/wm_event_query.c
index b732bc91569..221073d288a 100644
--- a/source/blender/windowmanager/intern/wm_event_query.c
+++ b/source/blender/windowmanager/intern/wm_event_query.c
@@ -254,16 +254,6 @@ bool WM_event_is_modal_drag_exit(const wmEvent *event,
   return 0;
 }
 
-bool WM_event_is_last_mousemove(const wmEvent *event)
-{
-  while ((event = event->next)) {
-    if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) {
-      return false;
-    }
-  }
-  return true;
-}
-
 bool WM_event_is_mouse_drag(const wmEvent *event)
 {
   return (ISMOUSE_BUTTON(event->type) && (event->val == KM_CLICK_DRAG));
-- 
cgit v1.2.3


From c5d3846b1026f2a60d3fdd6b61570adcb6c2a2cf Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Mon, 4 Jul 2022 16:11:31 +1000
Subject: Fix use-after-free error when handling events that close windows

Regression in [0] caused operations such as file-load or file-new
from any window besides the first to write into the freed:
`wmWindow.eventstate`.

Resolve by copying the event instead of restoring the region relative
cursor position after modifying it.

[0]: 789b1617f70e07f1c9bcb5253f1233acacbf6c8a
---
 source/blender/windowmanager/intern/wm_event_system.cc | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/source/blender/windowmanager/intern/wm_event_system.cc b/source/blender/windowmanager/intern/wm_event_system.cc
index c282cda4305..0395e8bda7a 100644
--- a/source/blender/windowmanager/intern/wm_event_system.cc
+++ b/source/blender/windowmanager/intern/wm_event_system.cc
@@ -1378,22 +1378,20 @@ static int wm_operator_invoke(bContext *C,
     }
 
     if (op->type->invoke && event) {
-      /* Temporarily write into `mval` (not technically `const` correct) but this is restored. */
-      const int mval_prev[2] = {UNPACK2(event->mval)};
-      wm_region_mouse_co(C, (wmEvent *)event);
+      /* Make a copy of the event as it's `const` and the #wmEvent.mval to be written into. */
+      wmEvent event_temp = *event;
+      wm_region_mouse_co(C, &event_temp);
 
       if (op->type->flag & OPTYPE_UNDO) {
         wm->op_undo_depth++;
       }
 
-      retval = op->type->invoke(C, op, event);
+      retval = op->type->invoke(C, op, &event_temp);
       OPERATOR_RETVAL_CHECK(retval);
 
       if (op->type->flag & OPTYPE_UNDO && CTX_wm_manager(C) == wm) {
         wm->op_undo_depth--;
       }
-
-      copy_v2_v2_int(((wmEvent *)event)->mval, mval_prev);
     }
     else if (op->type->exec) {
       if (op->type->flag & OPTYPE_UNDO) {
-- 
cgit v1.2.3


From a720a4aabb44973a87d51e68037c0a694cdd0567 Mon Sep 17 00:00:00 2001
From: Joseph Eagar 
Date: Mon, 4 Jul 2022 01:17:19 -0700
Subject: Fix T98698: Division by zero in smear code when strength is zero

---
 source/blender/editors/sculpt_paint/sculpt_paint_color.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source/blender/editors/sculpt_paint/sculpt_paint_color.c b/source/blender/editors/sculpt_paint/sculpt_paint_color.c
index 2ac0dbdb9d4..ed56138adca 100644
--- a/source/blender/editors/sculpt_paint/sculpt_paint_color.c
+++ b/source/blender/editors/sculpt_paint/sculpt_paint_color.c
@@ -532,7 +532,7 @@ void SCULPT_do_smear_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode
   Brush *brush = BKE_paint_brush(&sd->paint);
   SculptSession *ss = ob->sculpt;
 
-  if (!SCULPT_has_colors(ss)) {
+  if (!SCULPT_has_colors(ss) || ss->cache->bstrength == 0.0f) {
     return;
   }
 
-- 
cgit v1.2.3


From 7be07a9d6e26fde833e24957e8263e5e07af8d2f Mon Sep 17 00:00:00 2001
From: Joseph Eagar 
Date: Mon, 4 Jul 2022 01:19:14 -0700
Subject: Cleanup: use local variable in smear code instead of
 ss->cache->bstrength

---
 source/blender/editors/sculpt_paint/sculpt_paint_color.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/source/blender/editors/sculpt_paint/sculpt_paint_color.c b/source/blender/editors/sculpt_paint/sculpt_paint_color.c
index ed56138adca..7e813590e21 100644
--- a/source/blender/editors/sculpt_paint/sculpt_paint_color.c
+++ b/source/blender/editors/sculpt_paint/sculpt_paint_color.c
@@ -427,7 +427,7 @@ static void do_smear_brush_task_cb_exec(void *__restrict userdata,
     madd_v3_v3fl(current_disp, no, -dot_v3v3(current_disp, no));
 
     normalize_v3_v3(current_disp_norm, current_disp);
-    mul_v3_v3fl(current_disp, current_disp_norm, ss->cache->bstrength);
+    mul_v3_v3fl(current_disp, current_disp_norm, bstrength);
 
     float accum[4] = {0.0f, 0.0f, 0.0f, 0.0f};
     float totw = 0.0f;
@@ -457,12 +457,12 @@ static void do_smear_brush_task_cb_exec(void *__restrict userdata,
         sub_v3_v3v3(vertex_disp, SCULPT_vertex_co_get(ss, ni.index), vd.co);
 
         /* Weight by how close we are to our target distance from vd.co. */
-        float w = (1.0f + fabsf(len_v3(vertex_disp) / ss->cache->bstrength - 1.0f));
+        float w = (1.0f + fabsf(len_v3(vertex_disp) / bstrength - 1.0f));
 
         /* TODO: use cotangents (or at least face areas) here. */
         float len = len_v3v3(SCULPT_vertex_co_get(ss, ni.index), nco);
         if (len > 0.0f) {
-          len = ss->cache->bstrength / len;
+          len = bstrength / len;
         }
         else { /* Coincident point. */
           len = 1.0f;
-- 
cgit v1.2.3


From 3f5073a8e298c474fac36f7193d291e6f9579277 Mon Sep 17 00:00:00 2001
From: Joseph Eagar 
Date: Mon, 4 Jul 2022 01:34:54 -0700
Subject: Fix T98884: Fix edge case crashes in gpu subdiv cache code

---
 .../draw/intern/draw_cache_impl_subdivision.cc     |  5 +++
 .../mesh_extractors/extract_mesh_ibo_lines.cc      | 46 +++++++++++++++++-----
 2 files changed, 41 insertions(+), 10 deletions(-)

diff --git a/source/blender/draw/intern/draw_cache_impl_subdivision.cc b/source/blender/draw/intern/draw_cache_impl_subdivision.cc
index afec92a894d..29ab814cfa4 100644
--- a/source/blender/draw/intern/draw_cache_impl_subdivision.cc
+++ b/source/blender/draw/intern/draw_cache_impl_subdivision.cc
@@ -1470,6 +1470,11 @@ void draw_subdiv_interp_custom_data(const DRWSubdivCache *cache,
 {
   GPUShader *shader = nullptr;
 
+  if (!draw_subdiv_cache_need_polygon_data(cache)) {
+    /* Happens on meshes with only loose geometry. */
+    return;
+  }
+
   if (dimensions == 1) {
     shader = get_subdiv_shader(SHADER_COMP_CUSTOM_DATA_INTERP_1D,
                                "#define SUBDIV_POLYGON_OFFSET\n"
diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_lines.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_lines.cc
index 51d98d1ff26..e6c0d815963 100644
--- a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_lines.cc
+++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_lines.cc
@@ -183,17 +183,43 @@ static void extract_lines_loose_geom_subdiv(const DRWSubdivCache *subdiv_cache,
 
   uint *flags_data = static_cast(GPU_vertbuf_get_data(flags));
 
-  if (mr->extract_type == MR_EXTRACT_MESH) {
-    const MEdge *medge = mr->medge;
-    for (DRWSubdivLooseEdge edge : loose_edges) {
-      *flags_data++ = (medge[edge.coarse_edge_index].flag & ME_HIDE) != 0;
+  switch (mr->extract_type) {
+    case MR_EXTRACT_MESH: {
+      const MEdge *medge = mr->medge;
+      for (DRWSubdivLooseEdge edge : loose_edges) {
+        *flags_data++ = (medge[edge.coarse_edge_index].flag & ME_HIDE) != 0;
+      }
+      break;
     }
-  }
-  else {
-    BMesh *bm = mr->bm;
-    for (DRWSubdivLooseEdge edge : loose_edges) {
-      const BMEdge *bm_edge = BM_edge_at_index(bm, edge.coarse_edge_index);
-      *flags_data++ = BM_elem_flag_test_bool(bm_edge, BM_ELEM_HIDDEN) != 0;
+    case MR_EXTRACT_MAPPED: {
+      if (mr->bm) {
+        for (DRWSubdivLooseEdge edge : loose_edges) {
+          const BMEdge *bm_edge = bm_original_edge_get(mr, edge.coarse_edge_index);
+          *flags_data++ = BM_elem_flag_test_bool(bm_edge, BM_ELEM_HIDDEN) != 0;
+        }
+      }
+      else {
+        for (DRWSubdivLooseEdge edge : loose_edges) {
+          int e = edge.coarse_edge_index;
+
+          if (mr->e_origindex && mr->e_origindex[e] != ORIGINDEX_NONE) {
+            *flags_data++ = (mr->medge[mr->e_origindex[e]].flag & ME_HIDE) != 0;
+          }
+          else {
+            *flags_data++ = false;
+          }
+        }
+      }
+
+      break;
+    }
+    case MR_EXTRACT_BMESH: {
+      BMesh *bm = mr->bm;
+      for (DRWSubdivLooseEdge edge : loose_edges) {
+        const BMEdge *bm_edge = BM_edge_at_index(bm, edge.coarse_edge_index);
+        *flags_data++ = BM_elem_flag_test_bool(bm_edge, BM_ELEM_HIDDEN) != 0;
+      }
+      break;
     }
   }
 
-- 
cgit v1.2.3


From 004913dd957b641eb1b283b469838220406d0787 Mon Sep 17 00:00:00 2001
From: Antonio Vazquez 
Date: Mon, 4 Jul 2022 10:58:42 +0200
Subject: Fix T99381: GPencil Unable to Sculpt last point using Subdivide
 Modifier

The problem was the last point had the original point, but the previous one not, so the loop ends before checking last point.

The solution is avoid the loop exist if the function is checking the previous point before last one.
---
 source/blender/editors/gpencil/gpencil_sculpt_paint.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/source/blender/editors/gpencil/gpencil_sculpt_paint.c b/source/blender/editors/gpencil/gpencil_sculpt_paint.c
index b68240362c5..e27cd255217 100644
--- a/source/blender/editors/gpencil/gpencil_sculpt_paint.c
+++ b/source/blender/editors/gpencil/gpencil_sculpt_paint.c
@@ -1518,7 +1518,8 @@ static bool gpencil_sculpt_brush_do_stroke(tGP_BrushEditData *gso,
 
           /* To each point individually... */
           pt = &gps->points[i];
-          if ((pt->runtime.pt_orig == NULL) && (tool != GPSCULPT_TOOL_GRAB)) {
+          if ((i != gps->totpoints - 2) && (pt->runtime.pt_orig == NULL) &&
+              (tool != GPSCULPT_TOOL_GRAB)) {
             continue;
           }
           pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt;
-- 
cgit v1.2.3


From 1c38bfdc6f6be53d8a8a306acae06403e2bab8a8 Mon Sep 17 00:00:00 2001
From: Sergey Sharybin 
Date: Mon, 4 Jul 2022 12:27:45 +0200
Subject: Cleanup: Clarify relation name for time relation to modifier

---
 source/blender/depsgraph/intern/builder/deg_builder_relations.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc
index c13c6d2f870..afcf3eaacb7 100644
--- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc
+++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc
@@ -2191,7 +2191,7 @@ void DepsgraphRelationBuilder::build_object_data_geometry(Object *object)
       }
       if (BKE_object_modifier_use_time(scene_, object, md)) {
         TimeSourceKey time_src_key;
-        add_relation(time_src_key, obdata_ubereval_key, "Time Source");
+        add_relation(time_src_key, obdata_ubereval_key, "Time Source -> Modifier");
       }
     }
   }
-- 
cgit v1.2.3


From af6f3a4020fc09cc45d69abb15a8a611ce6771ac Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Mon, 4 Jul 2022 08:50:33 -0500
Subject: Cleanup: Remove unused function

---
 .../blenkernel/BKE_geometry_set_instances.hh       | 11 -------
 .../blenkernel/intern/geometry_set_instances.cc    | 35 ----------------------
 2 files changed, 46 deletions(-)

diff --git a/source/blender/blenkernel/BKE_geometry_set_instances.hh b/source/blender/blenkernel/BKE_geometry_set_instances.hh
index dd4cfd69cb1..6d4b9a2128c 100644
--- a/source/blender/blenkernel/BKE_geometry_set_instances.hh
+++ b/source/blender/blenkernel/BKE_geometry_set_instances.hh
@@ -43,15 +43,4 @@ struct GeometryInstanceGroup {
 void geometry_set_gather_instances(const GeometrySet &geometry_set,
                                    Vector &r_instance_groups);
 
-/**
- * Add information about all the attributes on every component of the type. The resulting info
- * will contain the highest complexity data type and the highest priority domain among every
- * attribute with the given name on all of the input components.
- */
-void geometry_set_gather_instances_attribute_info(
-    Span set_groups,
-    Span component_types,
-    const Set &ignored_attributes,
-    Map &r_attributes);
-
 }  // namespace blender::bke
diff --git a/source/blender/blenkernel/intern/geometry_set_instances.cc b/source/blender/blenkernel/intern/geometry_set_instances.cc
index 6b260207995..fbd676e4bee 100644
--- a/source/blender/blenkernel/intern/geometry_set_instances.cc
+++ b/source/blender/blenkernel/intern/geometry_set_instances.cc
@@ -157,41 +157,6 @@ void geometry_set_gather_instances(const GeometrySet &geometry_set,
   geometry_set_collect_recursive(geometry_set, float4x4::identity(), r_instance_groups);
 }
 
-void geometry_set_gather_instances_attribute_info(Span set_groups,
-                                                  Span component_types,
-                                                  const Set &ignored_attributes,
-                                                  Map &r_attributes)
-{
-  for (const GeometryInstanceGroup &set_group : set_groups) {
-    const GeometrySet &set = set_group.geometry_set;
-    for (const GeometryComponentType component_type : component_types) {
-      if (!set.has(component_type)) {
-        continue;
-      }
-      const GeometryComponent &component = *set.get_component_for_read(component_type);
-
-      component.attribute_foreach(
-          [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
-            if (attribute_id.is_named() && ignored_attributes.contains(attribute_id.name())) {
-              return true;
-            }
-            auto add_info = [&](AttributeKind *attribute_kind) {
-              attribute_kind->domain = meta_data.domain;
-              attribute_kind->data_type = meta_data.data_type;
-            };
-            auto modify_info = [&](AttributeKind *attribute_kind) {
-              attribute_kind->domain = meta_data.domain; /* TODO: Use highest priority domain. */
-              attribute_kind->data_type = bke::attribute_data_type_highest_complexity(
-                  {attribute_kind->data_type, meta_data.data_type});
-            };
-
-            r_attributes.add_or_modify(attribute_id, add_info, modify_info);
-            return true;
-          });
-    }
-  }
-}
-
 }  // namespace blender::bke
 
 void InstancesComponent::foreach_referenced_geometry(
-- 
cgit v1.2.3


From abbc8333ac1ea150fd89d6b32f548ad2da5ae686 Mon Sep 17 00:00:00 2001
From: Dalai Felinto 
Date: Mon, 4 Jul 2022 16:08:29 +0200
Subject: Fix curves sculpting Selection Paint missing refresh

This was reported as part of D15219 but it is a problem in master, not
in the patch.

EEVEE is drawing with a cheap sampling when navigating or painting, but
we need to clear the painting flag when we are done painting.

For a rainy day: DRW_state_is_navigating() should be renamed to indicate
that it also checks for the painting flag.
---
 source/blender/editors/sculpt_paint/curves_sculpt_intern.hh          | 2 +-
 source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh
index b26649e746b..4cfaf7ebfc9 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh
@@ -111,7 +111,7 @@ class CurvesSculptCommonContext {
   const Scene *scene = nullptr;
   ARegion *region = nullptr;
   const View3D *v3d = nullptr;
-  const RegionView3D *rv3d = nullptr;
+  RegionView3D *rv3d = nullptr;
 
   CurvesSculptCommonContext(const bContext &C);
 };
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc b/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc
index b40aebcaaf1..353b84236a3 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc
@@ -140,6 +140,7 @@ struct SelectionPaintOperationExecutor {
      * selection is handled as a generic attribute for now. */
     DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY);
     WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id);
+    ctx_.rv3d->rflag &= ~RV3D_PAINTING;
     ED_region_tag_redraw(ctx_.region);
   }
 
-- 
cgit v1.2.3


From c63569c0e0e24f517ce9462f0afae89f0d85c1eb Mon Sep 17 00:00:00 2001
From: Julian Eisel 
Date: Mon, 4 Jul 2022 15:29:24 +0200
Subject: Cleanup: Correct UI view comments

---
 source/blender/editors/include/UI_interface.hh     | 2 +-
 source/blender/editors/interface/interface_view.cc | 3 +--
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/source/blender/editors/include/UI_interface.hh b/source/blender/editors/include/UI_interface.hh
index a27edb54c0f..63cf8fa0c56 100644
--- a/source/blender/editors/include/UI_interface.hh
+++ b/source/blender/editors/include/UI_interface.hh
@@ -54,7 +54,7 @@ void attribute_search_add_items(
 }  // namespace blender::ui
 
 /**
- * Override this for all available tree types.
+ * Override this for all available view types.
  */
 blender::ui::AbstractGridView *UI_block_add_view(
     uiBlock &block,
diff --git a/source/blender/editors/interface/interface_view.cc b/source/blender/editors/interface/interface_view.cc
index 2d93e03ea18..70728565263 100644
--- a/source/blender/editors/interface/interface_view.cc
+++ b/source/blender/editors/interface/interface_view.cc
@@ -33,8 +33,7 @@ using namespace blender;
 using namespace blender::ui;
 
 /**
- * Wrapper to store views in a #ListBase. There's no `uiView` base class, we just store views as a
- * #std::variant.
+ * Wrapper to store views in a #ListBase, addressable via an identifier.
  */
 struct ViewLink : public Link {
   std::string idname;
-- 
cgit v1.2.3


From 8fb8a6529fa64d6e17042cdf469d9920eb939660 Mon Sep 17 00:00:00 2001
From: Aras Pranckevicius 
Date: Mon, 4 Jul 2022 19:10:13 +0300
Subject: OBJ: remove "experimental" from C++ based importer/exporter, mark
 Python legacy

By now I'm not aware of any serious regressions or missing functionality
in the C++ based OBJ importer/exporter. They have more features (vertex colors
support), and are way faster than the Python based importer/exporter.

Reviewed By: Thomas Dinges, Howard Trickey
Differential Revision: https://developer.blender.org/D15360
---
 release/datafiles/locale                      | 2 +-
 release/scripts/addons                        | 2 +-
 release/scripts/startup/bl_ui/space_topbar.py | 4 ++--
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/release/datafiles/locale b/release/datafiles/locale
index 9a85b137951..055bc5223c1 160000
--- a/release/datafiles/locale
+++ b/release/datafiles/locale
@@ -1 +1 @@
-Subproject commit 9a85b13795157560b319235c63f5a13b0107ba41
+Subproject commit 055bc5223c1cd249e32ccbc8e8796ba9925c8c33
diff --git a/release/scripts/addons b/release/scripts/addons
index 807a64cdfc5..403b95ef6ff 160000
--- a/release/scripts/addons
+++ b/release/scripts/addons
@@ -1 +1 @@
-Subproject commit 807a64cdfc50de1cfb263f2eb68680feddb66ec7
+Subproject commit 403b95ef6ff38918de966ed2a5843cfa3274a58b
diff --git a/release/scripts/startup/bl_ui/space_topbar.py b/release/scripts/startup/bl_ui/space_topbar.py
index d8bd724f554..31ecd67eb08 100644
--- a/release/scripts/startup/bl_ui/space_topbar.py
+++ b/release/scripts/startup/bl_ui/space_topbar.py
@@ -457,7 +457,7 @@ class TOPBAR_MT_file_import(Menu):
             self.layout.operator("wm.gpencil_import_svg", text="SVG as Grease Pencil")
 
         if bpy.app.build_options.io_wavefront_obj:
-            self.layout.operator("wm.obj_import", text="Wavefront (.obj) (experimental)")
+            self.layout.operator("wm.obj_import", text="Wavefront (.obj)")
         if bpy.app.build_options.io_stl:
             self.layout.operator("wm.stl_import", text="STL (.stl) (experimental)")
 
@@ -485,7 +485,7 @@ class TOPBAR_MT_file_export(Menu):
                 self.layout.operator("wm.gpencil_export_pdf", text="Grease Pencil as PDF")
 
         if bpy.app.build_options.io_wavefront_obj:
-            self.layout.operator("wm.obj_export", text="Wavefront (.obj) (experimental)")
+            self.layout.operator("wm.obj_export", text="Wavefront (.obj)")
 
 
 class TOPBAR_MT_file_external_data(Menu):
-- 
cgit v1.2.3


From dccdc6213ec6ff3c3ff681090f56648ec4023034 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Mon, 4 Jul 2022 11:50:33 -0500
Subject: Curves: Expose function to calculate vector handles

---
 source/blender/blenkernel/BKE_curves.hh          | 13 +++++++++++++
 source/blender/blenkernel/intern/curve_bezier.cc |  4 ++--
 2 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh
index b1f096b1c39..cc0c607f9bb 100644
--- a/source/blender/blenkernel/BKE_curves.hh
+++ b/source/blender/blenkernel/BKE_curves.hh
@@ -515,6 +515,14 @@ void calculate_evaluated_offsets(Span handle_types_left,
                                  int resolution,
                                  MutableSpan evaluated_offsets);
 
+/**
+ * Calculate the automatically defined positions for a vector handle (#BEZIER_HANDLE_VECTOR). While
+ * this can be calculated automatically with #calculate_auto_handles, when more context is
+ * available, it can be preferable for performance reasons to calculate it for a single segment
+ * when necessary.
+ */
+float3 calculate_vector_handle(const float3 &point, const float3 &next_point);
+
 /**
  * Recalculate all auto (#BEZIER_HANDLE_AUTO) and vector (#BEZIER_HANDLE_VECTOR) handles with
  * positions automatically derived from the neighboring control points, and update aligned
@@ -819,6 +827,11 @@ inline bool point_is_sharp(const Span handle_types_left,
          ELEM(handle_types_right[index], BEZIER_HANDLE_VECTOR, BEZIER_HANDLE_FREE);
 }
 
+inline float3 calculate_vector_handle(const float3 &point, const float3 &next_point)
+{
+  return math::interpolate(point, next_point, 1.0f / 3.0f);
+}
+
 /** \} */
 
 }  // namespace curves::bezier
diff --git a/source/blender/blenkernel/intern/curve_bezier.cc b/source/blender/blenkernel/intern/curve_bezier.cc
index 5ba17f1761b..1d6ee4938b5 100644
--- a/source/blender/blenkernel/intern/curve_bezier.cc
+++ b/source/blender/blenkernel/intern/curve_bezier.cc
@@ -106,11 +106,11 @@ static void calculate_point_handles(const HandleType type_left,
   }
 
   if (type_left == BEZIER_HANDLE_VECTOR) {
-    left = math::interpolate(position, prev_position, 1.0f / 3.0f);
+    left = calculate_vector_handle(position, prev_position);
   }
 
   if (type_right == BEZIER_HANDLE_VECTOR) {
-    right = math::interpolate(position, next_position, 1.0f / 3.0f);
+    right = calculate_vector_handle(position, next_position);
   }
 
   /* When one of the handles is "aligned" handle, it must be aligned with the other, i.e. point in
-- 
cgit v1.2.3


From 242bfd28ce5f469ed5cbec8958dceeb43dca9fcf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cle=CC=81ment=20Foucault?= 
Date: Mon, 4 Jul 2022 19:23:04 +0200
Subject: METAL: Add license header to new files

---
 source/blender/gpu/metal/mtl_command_buffer.mm | 1 +
 source/blender/gpu/metal/mtl_framebuffer.hh    | 2 ++
 source/blender/gpu/metal/mtl_framebuffer.mm    | 2 ++
 source/blender/gpu/metal/mtl_memory.hh         | 1 +
 source/blender/gpu/metal/mtl_memory.mm         | 1 +
 source/blender/gpu/metal/mtl_state.hh          | 2 ++
 source/blender/gpu/metal/mtl_state.mm          | 2 ++
 7 files changed, 11 insertions(+)

diff --git a/source/blender/gpu/metal/mtl_command_buffer.mm b/source/blender/gpu/metal/mtl_command_buffer.mm
index f9edd87a73c..7b6066b3d86 100644
--- a/source/blender/gpu/metal/mtl_command_buffer.mm
+++ b/source/blender/gpu/metal/mtl_command_buffer.mm
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 
 #include "DNA_userdef_types.h"
 
diff --git a/source/blender/gpu/metal/mtl_framebuffer.hh b/source/blender/gpu/metal/mtl_framebuffer.hh
index d6fa1850109..d6e9fa76b70 100644
--- a/source/blender/gpu/metal/mtl_framebuffer.hh
+++ b/source/blender/gpu/metal/mtl_framebuffer.hh
@@ -1,3 +1,5 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
 /** \file
  * \ingroup gpu
  *
diff --git a/source/blender/gpu/metal/mtl_framebuffer.mm b/source/blender/gpu/metal/mtl_framebuffer.mm
index b0a90829c0a..515dd70e5de 100644
--- a/source/blender/gpu/metal/mtl_framebuffer.mm
+++ b/source/blender/gpu/metal/mtl_framebuffer.mm
@@ -1,3 +1,5 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
 /** \file
  * \ingroup gpu
  */
diff --git a/source/blender/gpu/metal/mtl_memory.hh b/source/blender/gpu/metal/mtl_memory.hh
index 4dbc1b29c6b..daa049e78af 100644
--- a/source/blender/gpu/metal/mtl_memory.hh
+++ b/source/blender/gpu/metal/mtl_memory.hh
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 
 #pragma once
 
diff --git a/source/blender/gpu/metal/mtl_memory.mm b/source/blender/gpu/metal/mtl_memory.mm
index 6316a49407d..e5db32ed1b1 100644
--- a/source/blender/gpu/metal/mtl_memory.mm
+++ b/source/blender/gpu/metal/mtl_memory.mm
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 
 #include "BKE_global.h"
 
diff --git a/source/blender/gpu/metal/mtl_state.hh b/source/blender/gpu/metal/mtl_state.hh
index ddb27a444d4..e6472491b35 100644
--- a/source/blender/gpu/metal/mtl_state.hh
+++ b/source/blender/gpu/metal/mtl_state.hh
@@ -1,3 +1,5 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
 /** \file
  * \ingroup gpu
  */
diff --git a/source/blender/gpu/metal/mtl_state.mm b/source/blender/gpu/metal/mtl_state.mm
index 59c258a0d12..0f2d4d7dc48 100644
--- a/source/blender/gpu/metal/mtl_state.mm
+++ b/source/blender/gpu/metal/mtl_state.mm
@@ -1,3 +1,5 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
 /** \file
  * \ingroup gpu
  */
-- 
cgit v1.2.3


From 6e879c39987cb977299ee8ab09158c283d70a351 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Mon, 4 Jul 2022 15:30:42 -0500
Subject: BLI: Use simpler sliced generic virtual arrays when possible

This is just a theoretical improvement currently, I won't try to justify
it with some microbenchmark, but it should be better to use the
specialized single and span virtual arrays when slicing a `GVArray`,
since any use of `GVArrayImpl_For_SlicedGVArray` has extra overhead.

Differential Revision: https://developer.blender.org/D15361
---
 source/blender/blenlib/intern/generic_virtual_array.cc | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/source/blender/blenlib/intern/generic_virtual_array.cc b/source/blender/blenlib/intern/generic_virtual_array.cc
index 1f6e6cd6e18..532c7b9b626 100644
--- a/source/blender/blenlib/intern/generic_virtual_array.cc
+++ b/source/blender/blenlib/intern/generic_virtual_array.cc
@@ -688,6 +688,15 @@ GVArray GVArray::ForEmpty(const CPPType &type)
 
 GVArray GVArray::slice(IndexRange slice) const
 {
+  const CommonVArrayInfo info = this->common_info();
+  if (info.type == CommonVArrayInfo::Type::Single) {
+    return GVArray::ForSingle(this->type(), slice.size(), info.data);
+  }
+  /* Need to check for ownership, because otherwise the referenced data can be destructed when
+   * #this is destructed. */
+  if (info.type == CommonVArrayInfo::Type::Span && !info.may_have_ownership) {
+    return GVArray::ForSpan(GSpan(this->type(), info.data, this->size()).slice(slice));
+  }
   return GVArray::For(*this, slice);
 }
 
-- 
cgit v1.2.3


From 7537369498bf61f872554b0ce2efc439008165a4 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Tue, 5 Jul 2022 13:41:49 +1000
Subject: Python: remove invalid Py_TPFLAGS_HAVE_GC usage

Blender wouldn't start with Python 3.11 because of an error in
Py_TPFLAGS_HAVE_GC usage for `bpy.app.handlers.persistent`.
Remove this flag as it's not necessary.

Part of fix for T99277.
---
 source/blender/python/intern/bpy_app_handlers.c | 70 ++++++++++++-------------
 1 file changed, 35 insertions(+), 35 deletions(-)

diff --git a/source/blender/python/intern/bpy_app_handlers.c b/source/blender/python/intern/bpy_app_handlers.c
index 641727927ec..8c5fb22eab1 100644
--- a/source/blender/python/intern/bpy_app_handlers.c
+++ b/source/blender/python/intern/bpy_app_handlers.c
@@ -145,41 +145,41 @@ static PyTypeObject BPyPersistent_Type = {
     0,                /* tp_basicsize */
     0,                /* tp_itemsize */
     /* methods */
-    0,                                                             /* tp_dealloc */
-    0,                                                             /* tp_print */
-    0,                                                             /* tp_getattr */
-    0,                                                             /* tp_setattr */
-    0,                                                             /* tp_reserved */
-    0,                                                             /* tp_repr */
-    0,                                                             /* tp_as_number */
-    0,                                                             /* tp_as_sequence */
-    0,                                                             /* tp_as_mapping */
-    0,                                                             /* tp_hash */
-    0,                                                             /* tp_call */
-    0,                                                             /* tp_str */
-    0,                                                             /* tp_getattro */
-    0,                                                             /* tp_setattro */
-    0,                                                             /* tp_as_buffer */
-    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, /* tp_flags */
-    0,                                                             /* tp_doc */
-    0,                                                             /* tp_traverse */
-    0,                                                             /* tp_clear */
-    0,                                                             /* tp_richcompare */
-    0,                                                             /* tp_weaklistoffset */
-    0,                                                             /* tp_iter */
-    0,                                                             /* tp_iternext */
-    0,                                                             /* tp_methods */
-    0,                                                             /* tp_members */
-    0,                                                             /* tp_getset */
-    0,                                                             /* tp_base */
-    0,                                                             /* tp_dict */
-    0,                                                             /* tp_descr_get */
-    0,                                                             /* tp_descr_set */
-    0,                                                             /* tp_dictoffset */
-    0,                                                             /* tp_init */
-    0,                                                             /* tp_alloc */
-    bpy_app_handlers_persistent_new,                               /* tp_new */
-    0,                                                             /* tp_free */
+    0,                                        /* tp_dealloc */
+    0,                                        /* tp_print */
+    0,                                        /* tp_getattr */
+    0,                                        /* tp_setattr */
+    0,                                        /* tp_reserved */
+    0,                                        /* tp_repr */
+    0,                                        /* tp_as_number */
+    0,                                        /* tp_as_sequence */
+    0,                                        /* tp_as_mapping */
+    0,                                        /* tp_hash */
+    0,                                        /* tp_call */
+    0,                                        /* tp_str */
+    0,                                        /* tp_getattro */
+    0,                                        /* tp_setattro */
+    0,                                        /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+    0,                                        /* tp_doc */
+    0,                                        /* tp_traverse */
+    0,                                        /* tp_clear */
+    0,                                        /* tp_richcompare */
+    0,                                        /* tp_weaklistoffset */
+    0,                                        /* tp_iter */
+    0,                                        /* tp_iternext */
+    0,                                        /* tp_methods */
+    0,                                        /* tp_members */
+    0,                                        /* tp_getset */
+    0,                                        /* tp_base */
+    0,                                        /* tp_dict */
+    0,                                        /* tp_descr_get */
+    0,                                        /* tp_descr_set */
+    0,                                        /* tp_dictoffset */
+    0,                                        /* tp_init */
+    0,                                        /* tp_alloc */
+    bpy_app_handlers_persistent_new,          /* tp_new */
+    0,                                        /* tp_free */
 };
 
 static PyObject *py_cb_array[BKE_CB_EVT_TOT] = {NULL};
-- 
cgit v1.2.3


From dfa52017638abdf59791e5588c439d3a558a191d Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Tue, 5 Jul 2022 13:41:53 +1000
Subject: Python: add opcodes for safe py-drivers

New opcodes added since 3.7 meant some actions such as `len()`
were disabled in safe py-driver execution.

The following opcodes have been added, see [0] for details:

- ROT_FOUR: similar to existing ROT_* opcodes, added v3.8.

- ROT_N: similar to existing ROT_* opcodes, added v3.10.

- GET_LEN: Push len(TOS) onto the stack, added v3.10.

- IS_OP: for ternary operator, added v3.9.

- BUILD_SLICE: access `slice` built-in, doesn't expose new
  functionality beyond existing `__getitem__` access.

[0]: https://docs.python.org/3.10/library/dis.html
---
 source/blender/python/intern/bpy_driver.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/source/blender/python/intern/bpy_driver.c b/source/blender/python/intern/bpy_driver.c
index a9cc0019783..05def07a6d7 100644
--- a/source/blender/python/intern/bpy_driver.c
+++ b/source/blender/python/intern/bpy_driver.c
@@ -290,6 +290,7 @@ static const char secure_opcodes[255] = {
     OK_OP(ROT_THREE),
     OK_OP(DUP_TOP),
     OK_OP(DUP_TOP_TWO),
+    OK_OP(ROT_FOUR),
     OK_OP(NOP),
     OK_OP(UNARY_POSITIVE),
     OK_OP(UNARY_NEGATIVE),
@@ -307,6 +308,7 @@ static const char secure_opcodes[255] = {
     OK_OP(BINARY_TRUE_DIVIDE),
     OK_OP(INPLACE_FLOOR_DIVIDE),
     OK_OP(INPLACE_TRUE_DIVIDE),
+    OK_OP(GET_LEN),
     OK_OP(INPLACE_ADD),
     OK_OP(INPLACE_SUBTRACT),
     OK_OP(INPLACE_MULTIPLY),
@@ -323,6 +325,7 @@ static const char secure_opcodes[255] = {
     OK_OP(INPLACE_XOR),
     OK_OP(INPLACE_OR),
     OK_OP(RETURN_VALUE),
+    OK_OP(ROT_N),
     OK_OP(BUILD_TUPLE),
     OK_OP(BUILD_LIST),
     OK_OP(BUILD_SET),
@@ -335,9 +338,11 @@ static const char secure_opcodes[255] = {
     OK_OP(POP_JUMP_IF_FALSE),
     OK_OP(POP_JUMP_IF_TRUE),
     OK_OP(LOAD_GLOBAL),
+    OK_OP(IS_OP),
     OK_OP(LOAD_FAST),
     OK_OP(STORE_FAST),
     OK_OP(DELETE_FAST),
+    OK_OP(BUILD_SLICE),
     OK_OP(LOAD_DEREF),
     OK_OP(STORE_DEREF),
 
-- 
cgit v1.2.3


From 780c0ea097444c3be60314dffd203c099720badb Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Tue, 5 Jul 2022 13:41:55 +1000
Subject: Python: support v3.11 (beta) with changes to PyFrameObject & opcodes

- Use API calls to access frame-data as PyFrameObject is now opaque.
- Update opcodes allowed for safe driver evaluation.

**Details**

Some opcodes have been added for safe-driver evaluation.
Python 3.11 removes many opcodes - the number of accepted opcodes in
Blender's listing dropped from 65 to 43) however some new opcodes
also needed to be added. As this relates to security details about newly
added opcodes have been noted below (see [0] for full documentation).

Newly added opcodes:

- CACHE:
  Used to control caching instructions.

- RESUME:
  A no-op. Performs internal checks.

- BINARY_OP:
  Implements the binary and in-place operators,
  replacing specific binary operations.

- CALL, PRECALL, KW_NAMES:
  Used for calling functions, replacing some existing opcodes.

- POP_JUMP_{FORWARD/BACKWARD}_IF_{TRUE/FALSE/NONE/NOT_NONE}.
  Manipulate the byte-code counter.

- SWAP, PUSH_NULL.
  Stack manipulation.

Resolves T99277.

[0]: https://docs.python.org/3.11/library/dis.html
---
 source/blender/python/generic/py_capi_utils.c |  8 +++-
 source/blender/python/intern/bpy_driver.c     | 62 ++++++++++++++++++++++++++-
 source/blender/python/intern/bpy_interface.c  | 19 ++++----
 source/blender/python/intern/bpy_traceback.c  |  3 +-
 4 files changed, 79 insertions(+), 13 deletions(-)

diff --git a/source/blender/python/generic/py_capi_utils.c b/source/blender/python/generic/py_capi_utils.c
index d2e3c44c1b6..8230801e184 100644
--- a/source/blender/python/generic/py_capi_utils.c
+++ b/source/blender/python/generic/py_capi_utils.c
@@ -641,6 +641,7 @@ void PyC_StackSpit(void)
 void PyC_FileAndNum(const char **r_filename, int *r_lineno)
 {
   PyFrameObject *frame;
+  PyCodeObject *code;
 
   if (r_filename) {
     *r_filename = NULL;
@@ -649,13 +650,16 @@ void PyC_FileAndNum(const char **r_filename, int *r_lineno)
     *r_lineno = -1;
   }
 
-  if (!(frame = PyThreadState_GET()->frame)) {
+  if (!(frame = PyEval_GetFrame())) {
+    return;
+  }
+  if (!(code = PyFrame_GetCode(frame))) {
     return;
   }
 
   /* when executing a script */
   if (r_filename) {
-    *r_filename = PyUnicode_AsUTF8(frame->f_code->co_filename);
+    *r_filename = PyUnicode_AsUTF8(code->co_filename);
   }
 
   /* when executing a module */
diff --git a/source/blender/python/intern/bpy_driver.c b/source/blender/python/intern/bpy_driver.c
index 05def07a6d7..f71cf164e8c 100644
--- a/source/blender/python/intern/bpy_driver.c
+++ b/source/blender/python/intern/bpy_driver.c
@@ -285,6 +285,56 @@ static void pydriver_error(ChannelDriver *driver)
 #  define OK_OP(op) [op] = 1
 
 static const char secure_opcodes[255] = {
+#  if PY_VERSION_HEX >= 0x030b0000 /* Python 3.11 & newer. */
+
+    OK_OP(CACHE),
+    OK_OP(POP_TOP),
+    OK_OP(PUSH_NULL),
+    OK_OP(NOP),
+    OK_OP(UNARY_POSITIVE),
+    OK_OP(UNARY_NEGATIVE),
+    OK_OP(UNARY_NOT),
+    OK_OP(UNARY_INVERT),
+    OK_OP(BINARY_SUBSCR),
+    OK_OP(GET_LEN),
+    OK_OP(RETURN_VALUE),
+    OK_OP(SWAP),
+    OK_OP(BUILD_TUPLE),
+    OK_OP(BUILD_LIST),
+    OK_OP(BUILD_SET),
+    OK_OP(BUILD_MAP),
+    OK_OP(COMPARE_OP),
+    OK_OP(JUMP_FORWARD),
+    OK_OP(JUMP_IF_FALSE_OR_POP),
+    OK_OP(JUMP_IF_TRUE_OR_POP),
+    OK_OP(POP_JUMP_FORWARD_IF_FALSE),
+    OK_OP(POP_JUMP_FORWARD_IF_TRUE),
+    OK_OP(LOAD_GLOBAL),
+    OK_OP(IS_OP),
+    OK_OP(BINARY_OP),
+    OK_OP(LOAD_FAST),
+    OK_OP(STORE_FAST),
+    OK_OP(DELETE_FAST),
+    OK_OP(POP_JUMP_FORWARD_IF_NOT_NONE),
+    OK_OP(POP_JUMP_FORWARD_IF_NONE),
+    OK_OP(BUILD_SLICE),
+    OK_OP(LOAD_DEREF),
+    OK_OP(STORE_DEREF),
+    OK_OP(RESUME),
+    OK_OP(POP_JUMP_BACKWARD_IF_NOT_NONE),
+    OK_OP(POP_JUMP_BACKWARD_IF_NONE),
+    OK_OP(POP_JUMP_BACKWARD_IF_FALSE),
+    OK_OP(POP_JUMP_BACKWARD_IF_TRUE),
+
+    /* Special cases. */
+    OK_OP(LOAD_CONST), /* Ok because constants are accepted. */
+    OK_OP(LOAD_NAME),  /* Ok, because `PyCodeObject.names` is checked. */
+    OK_OP(CALL),       /* Ok, because we check its "name" before calling. */
+    OK_OP(KW_NAMES),   /* Ok, because it's used for calling functions with keyword arguments. */
+    OK_OP(PRECALL),    /* Ok, because it's used for calling. */
+
+#  else /* Python 3.10 and older. */
+
     OK_OP(POP_TOP),
     OK_OP(ROT_TWO),
     OK_OP(ROT_THREE),
@@ -352,6 +402,8 @@ static const char secure_opcodes[255] = {
     OK_OP(CALL_FUNCTION), /* Ok, because we check its "name" before calling. */
     OK_OP(CALL_FUNCTION_KW),
     OK_OP(CALL_FUNCTION_EX),
+
+#  endif /* Python 3.10 and older. */
 };
 
 #  undef OK_OP
@@ -388,7 +440,15 @@ static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code, PyObject *d
     const _Py_CODEUNIT *codestr;
     Py_ssize_t code_len;
 
-    PyBytes_AsStringAndSize(py_code->co_code, (char **)&codestr, &code_len);
+    PyObject *co_code;
+
+#  if PY_VERSION_HEX >= 0x030b0000 /* Python 3.11 & newer. */
+    co_code = py_code->_co_code;
+#  else
+    co_code = py_code->co_code;
+#  endif
+
+    PyBytes_AsStringAndSize(co_code, (char **)&codestr, &code_len);
     code_len /= sizeof(*codestr);
 
     for (Py_ssize_t i = 0; i < code_len; i++) {
diff --git a/source/blender/python/intern/bpy_interface.c b/source/blender/python/intern/bpy_interface.c
index 0ab8b4385e5..ea64fa6c098 100644
--- a/source/blender/python/intern/bpy_interface.c
+++ b/source/blender/python/intern/bpy_interface.c
@@ -582,16 +582,17 @@ void BPY_python_use_system_env(void)
 void BPY_python_backtrace(FILE *fp)
 {
   fputs("\n# Python backtrace\n", fp);
-  PyThreadState *tstate = PyGILState_GetThisThreadState();
-  if (tstate != NULL && tstate->frame != NULL) {
-    PyFrameObject *frame = tstate->frame;
-    do {
-      const int line = PyCode_Addr2Line(frame->f_code, frame->f_lasti);
-      const char *filepath = PyUnicode_AsUTF8(frame->f_code->co_filename);
-      const char *funcname = PyUnicode_AsUTF8(frame->f_code->co_name);
-      fprintf(fp, "  File \"%s\", line %d in %s\n", filepath, line, funcname);
-    } while ((frame = frame->f_back));
+  PyFrameObject *frame;
+  if (!(frame = PyEval_GetFrame())) {
+    return;
   }
+  do {
+    PyCodeObject *code = PyFrame_GetCode(frame);
+    const int line = PyFrame_GetLineNumber(frame);
+    const char *filepath = PyUnicode_AsUTF8(code->co_filename);
+    const char *funcname = PyUnicode_AsUTF8(code->co_name);
+    fprintf(fp, "  File \"%s\", line %d in %s\n", filepath, line, funcname);
+  } while ((frame = PyFrame_GetBack(frame)));
 }
 
 void BPY_DECREF(void *pyob_ptr)
diff --git a/source/blender/python/intern/bpy_traceback.c b/source/blender/python/intern/bpy_traceback.c
index cb93843a6de..1f2458c752f 100644
--- a/source/blender/python/intern/bpy_traceback.c
+++ b/source/blender/python/intern/bpy_traceback.c
@@ -20,7 +20,8 @@
 
 static const char *traceback_filepath(PyTracebackObject *tb, PyObject **coerce)
 {
-  *coerce = PyUnicode_EncodeFSDefault(tb->tb_frame->f_code->co_filename);
+  PyCodeObject *code = PyFrame_GetCode(tb->tb_frame);
+  *coerce = PyUnicode_EncodeFSDefault(code->co_filename);
   return PyBytes_AS_STRING(*coerce);
 }
 
-- 
cgit v1.2.3


From 9145a4d08ff76052831971f576407871d6c6aca1 Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Tue, 5 Jul 2022 13:58:52 +1000
Subject: GPU: add missing license header

---
 source/blender/gpu/GPU_common_types.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/source/blender/gpu/GPU_common_types.h b/source/blender/gpu/GPU_common_types.h
index 5913caf72e3..13535a4fb3b 100644
--- a/source/blender/gpu/GPU_common_types.h
+++ b/source/blender/gpu/GPU_common_types.h
@@ -1,3 +1,5 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
 /** \file
  * \ingroup gpu
  */
-- 
cgit v1.2.3


From 8c33a53b172898d205aadf91f28df42e675cb95b Mon Sep 17 00:00:00 2001
From: Campbell Barton 
Date: Tue, 5 Jul 2022 14:32:53 +1000
Subject: Cleanup: format

---
 source/blender/editors/object/object_vgroup.c             |  2 +-
 source/blender/editors/space_sequencer/sequencer_select.c |  6 ++++--
 tests/python/bl_blendfile_library_overrides.py            | 11 +++++++++--
 3 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/source/blender/editors/object/object_vgroup.c b/source/blender/editors/object/object_vgroup.c
index 88b27bd4cea..158bc28662a 100644
--- a/source/blender/editors/object/object_vgroup.c
+++ b/source/blender/editors/object/object_vgroup.c
@@ -733,7 +733,7 @@ const EnumPropertyItem *ED_object_vgroup_selection_itemf_helper(const bContext *
 
   /* Set `Deform Bone` as default selection if armature is present. */
   RNA_def_property_enum_default(
-    prop, BKE_modifiers_is_deformed_by_armature(ob) ? WT_VGROUP_BONE_DEFORM : WT_VGROUP_ALL);
+      prop, BKE_modifiers_is_deformed_by_armature(ob) ? WT_VGROUP_BONE_DEFORM : WT_VGROUP_ALL);
 
   RNA_enum_item_end(&item, &totitem);
   *r_free = true;
diff --git a/source/blender/editors/space_sequencer/sequencer_select.c b/source/blender/editors/space_sequencer/sequencer_select.c
index 3f48404053d..4aaa3aeb2ff 100644
--- a/source/blender/editors/space_sequencer/sequencer_select.c
+++ b/source/blender/editors/space_sequencer/sequencer_select.c
@@ -589,8 +589,10 @@ static void sequencer_select_side_of_frame(const bContext *C,
 
   const float x = UI_view2d_region_to_view_x(v2d, mval[0]);
   LISTBASE_FOREACH (Sequence *, seq_iter, SEQ_active_seqbase_get(ed)) {
-    if (((x < scene->r.cfra) && (SEQ_time_right_handle_frame_get(scene, seq_iter) <= scene->r.cfra)) ||
-        ((x >= scene->r.cfra) && (SEQ_time_left_handle_frame_get(scene, seq_iter) >= scene->r.cfra))) {
+    if (((x < scene->r.cfra) &&
+         (SEQ_time_right_handle_frame_get(scene, seq_iter) <= scene->r.cfra)) ||
+        ((x >= scene->r.cfra) &&
+         (SEQ_time_left_handle_frame_get(scene, seq_iter) >= scene->r.cfra))) {
       /* Select left or right. */
       seq_iter->flag |= SELECT;
       recurs_sel_seq(seq_iter);
diff --git a/tests/python/bl_blendfile_library_overrides.py b/tests/python/bl_blendfile_library_overrides.py
index 46954c08b8c..3ba99bd61e4 100644
--- a/tests/python/bl_blendfile_library_overrides.py
+++ b/tests/python/bl_blendfile_library_overrides.py
@@ -230,7 +230,11 @@ class TestLibraryOverridesResync(TestHelper, unittest.TestCase):
         bpy.data.orphans_purge()
 
         link_dir = self.output_path / "Collection"
-        bpy.ops.wm.link(directory=str(link_dir), filename=TestLibraryOverridesResync.DATA_NAME_CONTAINER, instance_collections=False)
+        bpy.ops.wm.link(
+            directory=str(link_dir),
+            filename=TestLibraryOverridesResync.DATA_NAME_CONTAINER,
+            instance_collections=False,
+        )
 
         linked_collection_container = bpy.data.collections[TestLibraryOverridesResync.DATA_NAME_CONTAINER]
         assert(linked_collection_container.library is not None)
@@ -244,7 +248,10 @@ class TestLibraryOverridesResync(TestHelper, unittest.TestCase):
         assert(len(bpy.data.armatures) == 1)
         assert(all(id_.library is not None for id_ in bpy.data.armatures))
 
-        override_collection_container = linked_collection_container.override_hierarchy_create(bpy.context.scene, bpy.context.view_layer)
+        override_collection_container = linked_collection_container.override_hierarchy_create(
+            bpy.context.scene,
+            bpy.context.view_layer,
+        )
         assert(override_collection_container.library is None)
         assert(override_collection_container.override_library is not None)
         # Objects and collections are duplicated as overrides, but meshes and armatures remain only linked data.
-- 
cgit v1.2.3


From 322abb2e4b5ca145d854284159e198556d03a6c7 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Mon, 4 Jul 2022 23:54:06 -0500
Subject: Geometry Nodes: Use alphabetical order for UV nodes in add menu

---
 release/scripts/startup/nodeitems_builtins.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py
index 21bb3d01616..3f49fb9fb58 100644
--- a/release/scripts/startup/nodeitems_builtins.py
+++ b/release/scripts/startup/nodeitems_builtins.py
@@ -164,8 +164,8 @@ def uv_node_items(context):
     space = context.space_data
     if not space:
         return
-    yield NodeItem("GeometryNodeUVUnwrap")
     yield NodeItem("GeometryNodeUVPackIslands")
+    yield NodeItem("GeometryNodeUVUnwrap")
 
 
 # Custom Menu for Geometry Node Input Nodes.
@@ -665,7 +665,6 @@ geometry_node_categories = [
         NodeItem("GeometryNodeCurvePrimitiveBezierSegment"),
     ]),
     GeometryNodeCategory("GEO_GEOMETRY", "Geometry", items=geometry_node_items),
-    GeometryNodeCategory("GEO_UV", "UV", items=uv_node_items),
     GeometryNodeCategory("GEO_INPUT", "Input", items=geometry_input_node_items),
     GeometryNodeCategory("GEO_INSTANCE", "Instances", items=geometry_instance_node_items),
     GeometryNodeCategory("GEO_MATERIAL", "Material", items=geometry_material_node_items),
@@ -721,6 +720,7 @@ geometry_node_categories = [
         NodeItem("FunctionNodeRandomValue"),
         NodeItem("FunctionNodeAlignEulerToVector"),
     ]),
+    GeometryNodeCategory("GEO_UV", "UV", items=uv_node_items),
     GeometryNodeCategory("GEO_VECTOR", "Vector", items=[
         NodeItem("ShaderNodeVectorCurve"),
         NodeItem("ShaderNodeSeparateXYZ"),
-- 
cgit v1.2.3


From bd00324c264c07d7872071e9ef888b6b92e5645f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20de=20Vill=C3=A8le?= 
Date: Mon, 4 Jul 2022 12:59:10 +0200
Subject: NLA: change behavior of 'Frame Start' / 'End' sliders

Change the behavior of the "Frame start" and [Frame] "End" fields of an
NLA Strip in the NLA editor.

Frame Start now behaves like translating with {key G} and moving the
mouse. It also updates the Frame End to ensure the strip remains the
same length.

Frame End changes the length of the strip, based on the Repeat property.
If there are no repeats (i.e. number of repeats = 1) the underlying
Action will change length, such that more or less of its keyframes will
be part of the NLA strip. If there are repeats (i.e. number of repeats
smaller or larger than 1), the number of repeats will change. Either
way, the effective end frame off the strip will be the one set in the
Frame End slider.

The old behavior of stretching time has been removed. It is still
possible to stretch time of a strip, but this no longer automatically
happens when manipulating the Frame Start and Frame End sliders.

**Technical details:** new RNA properties `frame_start_ui` and
`frame_end_ui` have been added. Changing those values (for example via
the sliders, but also via Python) trigger the above behavior. The
behavior of the already-existing `frame_start` and `frame_end`
properties has been simplified, such that these can be set from Python
without many side-effects, simplifying import of data into the NLA.

Reviewed By: sybren, RiggingDojo

Differential Revision: https://developer.blender.org/D14658
---
 source/blender/blenkernel/BKE_nla.h            |  29 ++++
 source/blender/blenkernel/intern/nla.c         |  34 ++++
 source/blender/editors/space_nla/nla_buttons.c |   4 +-
 source/blender/makesrna/intern/rna_nla.c       | 223 +++++++++++++++++++------
 4 files changed, 241 insertions(+), 49 deletions(-)

diff --git a/source/blender/blenkernel/BKE_nla.h b/source/blender/blenkernel/BKE_nla.h
index 215adc3e67b..a9444156498 100644
--- a/source/blender/blenkernel/BKE_nla.h
+++ b/source/blender/blenkernel/BKE_nla.h
@@ -7,6 +7,9 @@
  * \ingroup bke
  */
 
+/* temp constant defined for these funcs only... */
+#  define NLASTRIP_MIN_LEN_THRESH 0.1f
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -218,6 +221,32 @@ bool BKE_nlatrack_is_nonlocal_in_liboverride(const struct ID *id, const struct N
 
 /* ............ */
 
+/**
+ * Compute the left-hand-side 'frame limit' of that strip, in its NLA track.
+ *
+ * \details This is either :
+ * - the end frame of the previous strip, if the strip's track contains another strip on it left
+ * - the macro MINFRAMEF, if no strips are to the left of this strip in its track
+ *
+ * \param strip The strip to compute the left-hand-side 'frame limit' of.
+ * \return The beginning frame of the previous strip, or MINFRAMEF if no strips are next in that track.
+ */
+float BKE_nlastrip_compute_frame_from_previous_strip(struct NlaStrip *strip);
+/**
+ * Compute the right-hand-side 'frame limit' of that strip, in its NLA track.
+ *
+ * \details This is either :
+ *
+ * - the begin frame of the next strip, if the strip's track contains another strip on it right
+ * - the macro MAXFRAMEF, if no strips are to the right of this strip in its track
+ *
+ * \param strip The strip to compute the right-hand-side 'frame limit' of.
+ * \return The beginning frame of the next strip, or MAXFRAMEF if no strips are next in that track.
+ */
+float BKE_nlastrip_compute_frame_to_next_strip(struct NlaStrip *strip);
+
+/* ............ */
+
 /**
  * Find the active NLA-strip within the given track.
  */
diff --git a/source/blender/blenkernel/intern/nla.c b/source/blender/blenkernel/intern/nla.c
index 10abb8f20df..3af5e4789d2 100644
--- a/source/blender/blenkernel/intern/nla.c
+++ b/source/blender/blenkernel/intern/nla.c
@@ -1239,6 +1239,40 @@ static NlaStrip *nlastrip_find_active(ListBase /* NlaStrip */ *strips)
   return NULL;
 }
 
+float BKE_nlastrip_compute_frame_from_previous_strip(NlaStrip *strip)
+{
+  float limit_prev = MINFRAMEF;
+
+  /* Find the previous end frame, with a special case if the previous strip was a transition : */
+  if (strip->prev) {
+    if (strip->prev->type == NLASTRIP_TYPE_TRANSITION) {
+      limit_prev = strip->prev->start + NLASTRIP_MIN_LEN_THRESH;
+    }
+    else {
+      limit_prev = strip->prev->end;
+    }
+  }
+
+  return limit_prev;
+}
+
+float BKE_nlastrip_compute_frame_to_next_strip(NlaStrip* strip)
+{
+  float limit_next = MAXFRAMEF;
+
+  /* Find the next begin frame, with a special case if the next strip's a transition : */
+  if (strip->next) {
+    if (strip->next->type == NLASTRIP_TYPE_TRANSITION) {
+      limit_next = strip->next->end - NLASTRIP_MIN_LEN_THRESH;
+    }
+    else {
+      limit_next = strip->next->start;
+    }
+  }
+
+  return limit_next;
+}
+
 NlaStrip *BKE_nlastrip_find_active(NlaTrack *nlt)
 {
   if (nlt == NULL) {
diff --git a/source/blender/editors/space_nla/nla_buttons.c b/source/blender/editors/space_nla/nla_buttons.c
index f89bfd2a36a..3b469746f17 100644
--- a/source/blender/editors/space_nla/nla_buttons.c
+++ b/source/blender/editors/space_nla/nla_buttons.c
@@ -393,8 +393,8 @@ static void nla_panel_properties(const bContext *C, Panel *panel)
 
   /* strip extents */
   column = uiLayoutColumn(layout, true);
-  uiItemR(column, &strip_ptr, "frame_start", 0, IFACE_("Frame Start"), ICON_NONE);
-  uiItemR(column, &strip_ptr, "frame_end", 0, IFACE_("End"), ICON_NONE);
+  uiItemR(column, &strip_ptr, "frame_start_ui", 0, IFACE_("Frame Start"), ICON_NONE);
+  uiItemR(column, &strip_ptr, "frame_end_ui", 0, IFACE_("End"), ICON_NONE);
 
   /* Evaluation-Related Strip Properties ------------------ */
 
diff --git a/source/blender/makesrna/intern/rna_nla.c b/source/blender/makesrna/intern/rna_nla.c
index 978a94ca7b0..f1aa9925a41 100644
--- a/source/blender/makesrna/intern/rna_nla.c
+++ b/source/blender/makesrna/intern/rna_nla.c
@@ -83,9 +83,6 @@ const EnumPropertyItem rna_enum_nla_mode_extend_items[] = {
 #  include "DEG_depsgraph.h"
 #  include "DEG_depsgraph_build.h"
 
-/* temp constant defined for these funcs only... */
-#  define NLASTRIP_MIN_LEN_THRESH 0.1f
-
 static void rna_NlaStrip_name_set(PointerRNA *ptr, const char *value)
 {
   NlaStrip *data = (NlaStrip *)ptr->data;
@@ -165,75 +162,182 @@ static void rna_NlaStrip_transform_update(Main *bmain, Scene *scene, PointerRNA
 
 static void rna_NlaStrip_start_frame_set(PointerRNA *ptr, float value)
 {
+  /* Simply set the frame start in a valid range : if there are any NLA strips before/after, clamp
+   * the start value. If the new start value is past-the-end, clamp it. Otherwise, set it.
+   *
+   * NOTE : Unless neighbouring strips are transitions, NLASTRIP_MIN_LEN_THRESH is not needed, as
+   * strips can be 'glued' to one another. If they are however, ensure transitions have a bit of
+   * time alloted in order to be performed.
+   */
   NlaStrip *data = (NlaStrip *)ptr->data;
 
-  /* Clamp value to lie within valid limits:
-   * - Cannot start past the end of the strip + some flexibility threshold.
-   * - Cannot start before the previous strip (if present) ends.
-   *   -> But if it was a transition,
-   *   we could go up to the start of the strip + some flexibility threshold.
-   *   as long as we re-adjust the transition afterwards.
-   * - Minimum frame is -MAXFRAME so that we don't get clipping on frame 0.
-   */
-  if (data->prev) {
-    if (data->prev->type == NLASTRIP_TYPE_TRANSITION) {
-      CLAMP(
-          value, data->prev->start + NLASTRIP_MIN_LEN_THRESH, data->end - NLASTRIP_MIN_LEN_THRESH);
+  const float limit_prev = BKE_nlastrip_compute_frame_from_previous_strip(data);
+  const float limit_next = BKE_nlastrip_compute_frame_to_next_strip(data);
+  CLAMP(value, limit_prev, limit_next);
 
-      /* re-adjust the transition to stick to the endpoints of the action-clips */
-      data->prev->end = value;
-    }
-    else {
-      CLAMP(value, data->prev->end, data->end - NLASTRIP_MIN_LEN_THRESH);
+  data->start = value;
+
+  /* The ONLY case where we actively modify the value set by the user, is in case the start value
+   * value is past the old end frame (here delta = NLASTRIP_MIN_LEN_THRESH) :
+   * - if there's no "room" for the end frame to be placed at (new_start + delta), move old_end to
+   *     the limit, and new_start to (limit - delta)
+   * - otherwise, do _not_ change the end frame. This property is not accessible from the UI, and
+   *     can only be set via scripts. The script should be responsible of setting the end frame.
+   */
+  if (data->start > (data->end - NLASTRIP_MIN_LEN_THRESH)) {
+    /* If past-the-allowed-end : */
+    if ((data->start + NLASTRIP_MIN_LEN_THRESH) > limit_next) {
+      data->end = limit_next;
+      data->start = data->end - NLASTRIP_MIN_LEN_THRESH;
     }
   }
-  else {
-    CLAMP(value, MINAFRAME, data->end);
+
+  /* Ensure transitions are kept 'glued' to the strip : */
+  if (data->prev && data->prev->type == NLASTRIP_TYPE_TRANSITION) {
+    data->prev->end = data->start;
   }
+}
+
+static void rna_NlaStrip_frame_start_ui_set(PointerRNA *ptr, float value)
+{
+  NlaStrip *data = (NlaStrip *)ptr->data;
+
+  /* Changing the NLA strip's start frame is exactly the same as translating it in the NLA editor.
+   * When 'translating' the clip, the length of it should stay identical. Se we also need to set
+   * this strip's end frame after modifying its start (to `start + (old_end - old_start)`).
+   * Of course, we might have a few other strips on this NLA track, so we have to respect the
+   * previous strip's end frame.
+   *
+   * Also, different types of NLA strips (*_CLIP, *_TRANSITION, *_META, *_SOUND) have their own
+   * properties to respect. Needs testing on a real-world use case for the transition, meta, and
+   * sound types.
+   */
+
+  /* The strip's total length before modifying it & also how long we'd like it to be afterwards. */
+  const float striplen = data->end - data->start;
+
+  /* We're only modifying one strip at a time. The start and end times of its neighbors should not
+   * change. As such, here are the 'bookends' (frame limits) for the start position to respect :
+   * - if a next strip exists, don't allow the strip to start after (next->end - striplen - delta),
+   *   (delta being the min length of a Nla Strip : the NLASTRIP_MIN_THRESH macro)
+   * - if a previous strip exists, don't allow this strip to start before it (data->prev) ends
+   * - otherwise, limit to the program limit macros defined in DNA_scene_types.h : {MINA|MAX}FRAMEF
+   */
+  const float limit_prev = BKE_nlastrip_compute_frame_from_previous_strip(data);
+  const float limit_next = BKE_nlastrip_compute_frame_to_next_strip(data) - striplen;
+  /* For above : we want to be able to fit the entire strip before the next frame limit, so shift
+   * the next limit by 'striplen' no matter the context. */
+
+  CLAMP(value, limit_prev, limit_next);
   data->start = value;
+
+  if (data->type != NLASTRIP_TYPE_TRANSITION) {
+    data->end = data->start + striplen;
+  }
+
+  /* Update properties of the prev/next strips if they are transitions to ensure consistency : */
+  if (data->prev && data->prev->type == NLASTRIP_TYPE_TRANSITION) {
+    data->prev->end = data->start;
+  }
+  if (data->next && data->next->type == NLASTRIP_TYPE_TRANSITION) {
+    data->next->start = data->end;
+  }
 }
 
 static void rna_NlaStrip_end_frame_set(PointerRNA *ptr, float value)
 {
   NlaStrip *data = (NlaStrip *)ptr->data;
 
+  const float limit_prev = BKE_nlastrip_compute_frame_from_previous_strip(data);
+  const float limit_next = BKE_nlastrip_compute_frame_to_next_strip(data);
+  CLAMP(value, limit_prev, limit_next);
+
+  data->end = value;
+
+  /* The ONLY case where we actively modify the value set by the user, is in case the start value
+   * value is past the old end frame (here delta = NLASTRIP_MIN_LEN_THRESH) :
+   * - if there's no "room" for the end frame to be placed at (new_start + delta), move old_end to
+   *     the limit, and new_start to (limit - delta)
+   * - otherwise, do _not_ change the end frame. This property is not accessible from the UI, and
+   *     can only be set via scripts. The script should be responsable for setting the end frame.
+   */
+  if (data->end < (data->start + NLASTRIP_MIN_LEN_THRESH)) {
+    /* If before-the-allowed-start : */
+    if ((data->end - NLASTRIP_MIN_LEN_THRESH) < limit_prev) {
+      data->start = limit_prev;
+      data->end = data->start + NLASTRIP_MIN_LEN_THRESH;
+    }
+  }
+
+  /* Ensure transitions are kept "glued" to the strip : */
+  if (data->next && data->next->type == NLASTRIP_TYPE_TRANSITION) {
+    data->next->start = data->end;
+  }
+}
+
+static void rna_NlaStrip_frame_end_ui_set(PointerRNA *ptr, float value)
+{
+  NlaStrip *data = (NlaStrip *)ptr->data;
+
+  /* Changing the strip's end frame will update its action 'range' (defined by actstart->actend) to
+   * accomodate the extra length of the strip. No other parameters of the strip will change. But
+   * this means we have to get the current strip's end frame right now :
+   */
+  const float old_strip_end = data->end;
+
   /* clamp value to lie within valid limits
    * - must not have zero or negative length strip, so cannot start before the first frame
    *   + some minimum-strip-length threshold
    * - cannot end later than the start of the next strip (if present)
-   *   -> but if it was a transition,
-   *   we could go up to the start of the end - some flexibility threshold
-   *   as long as we re-adjust the transition afterwards
+   *   -> relies on the BKE_nlastrip_compute_frame_to_next_strip() function
    */
-  if (data->next) {
-    if (data->next->type == NLASTRIP_TYPE_TRANSITION) {
-      CLAMP(
-          value, data->start + NLASTRIP_MIN_LEN_THRESH, data->next->end - NLASTRIP_MIN_LEN_THRESH);
+  const float limit_prev = data->start + NLASTRIP_MIN_LEN_THRESH;
+  const float limit_next = BKE_nlastrip_compute_frame_to_next_strip(data);
 
-      /* readjust the transition to stick to the endpoints of the action-clips */
-      data->next->start = value;
-    }
-    else {
-      CLAMP(value, data->start + NLASTRIP_MIN_LEN_THRESH, data->next->start);
-    }
-  }
-  else {
-    CLAMP(value, data->start + NLASTRIP_MIN_LEN_THRESH, MAXFRAME);
-  }
+  CLAMP(value, limit_prev, limit_next);
   data->end = value;
 
-  /* calculate the lengths the strip and its action (if applicable) */
-  if (data->type == NLASTRIP_TYPE_CLIP) {
-    float len, actlen;
+  /* Only adjust transitions at this stage : */
+  if (data->next && data->next->type == NLASTRIP_TYPE_TRANSITION) {
+    data->next->start = value;
+  }
 
-    len = data->end - data->start;
-    actlen = data->actend - data->actstart;
+  /* calculate the lengths the strip and its action : *
+   * (Meta and transitions shouldn't be updated, but clip and sound should) */
+  if (data->type == NLASTRIP_TYPE_CLIP || data->type == NLASTRIP_TYPE_SOUND) {
+    float actlen = data->actend - data->actstart;
     if (IS_EQF(actlen, 0.0f)) {
-      actlen = 1.0f;
+      actlen = 1.0f; /* Only sanity check needed : we use this as divisor later on. */
+    }
+
+    /* Modify the strip's action end frame, or repeat based on :
+     * - if data->repeat == 1.0f, modify the action end frame :
+     *   - if the number of frames to substract is the number of frames, set the action end frame
+     *     to the action start + 1 and modify the end of the strip to add that frame
+     *   - if the number of frames
+     * - otherwise, modify the repeat property to accomodate for the new length
+     */
+    float action_length_delta = (old_strip_end - data->end) / data->scale;
+    /* If no repeats are used, then modify the action end frame : */
+    if (IS_EQF(data->repeat, 1.0f)) {
+      /* If they're equal, strip has been reduced by the same amount as the whole strip length, so
+       * clamp the action clip length to 1 frame, and add a frame to end so that len(strip)!=0 :*/
+      if (IS_EQF(action_length_delta, actlen)) {
+        data->actend = data->actstart + 1.0f;
+        data->end += 1.0f;
+      }
+      else if (action_length_delta < actlen) {
+        /* Now, adjust the new strip's actend to the value it's supposed to have : */
+        data->actend = data->actend - action_length_delta;
+      }
+      /* The case where the delta is bigger than the action length should not be possible, since
+       * data->end is guaranteed to be clamped to data->start + threshold above.
+       */
+    }
+    else {
+      data->repeat -= (action_length_delta / actlen);
     }
 
-    /* now, adjust the 'scale' setting to reflect this (so that this change can be valid) */
-    data->scale = len / ((actlen)*data->repeat);
   }
 }
 
@@ -640,6 +744,31 @@ static void rna_def_nlastrip(BlenderRNA *brna)
   RNA_def_property_update(
       prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_transform_update");
 
+  /* Strip extents, when called from UI elements : */
+  prop = RNA_def_property(srna, "frame_start_ui", PROP_FLOAT, PROP_TIME);
+  RNA_def_property_float_sdna(prop, NULL, "start");
+  RNA_def_property_float_funcs(prop, NULL, "rna_NlaStrip_frame_start_ui_set", NULL);
+  RNA_def_property_ui_text(
+    prop,
+    "Start Frame (manipulated from UI)",
+    "Start frame of the NLA strip. Note : changing this value also updates the value of "
+      "the strip's end frame. If only the start frame should be changed, see the \"frame_start\" "
+      "property instead.");
+  RNA_def_property_update(
+      prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_transform_update");
+
+  prop = RNA_def_property(srna, "frame_end_ui", PROP_FLOAT, PROP_TIME);
+  RNA_def_property_float_sdna(prop, NULL, "end");
+  RNA_def_property_float_funcs(prop, NULL, "rna_NlaStrip_frame_end_ui_set", NULL);
+  RNA_def_property_ui_text(
+    prop,
+    "End Frame (manipulated from UI)",
+      "End frame of the NLA strip. Note : changing this value also updates the value of "
+      "the strip's start frame. If only the end frame should be changed, see the \"frame_end\" "
+      "property instead.");
+  RNA_def_property_update(
+      prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_transform_update");
+
   /* Blending */
   prop = RNA_def_property(srna, "blend_in", PROP_FLOAT, PROP_NONE);
   RNA_def_property_float_sdna(prop, NULL, "blendin");
-- 
cgit v1.2.3


From fdb854b9320d66297c8d1f03746b340b0798ed86 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= 
Date: Tue, 5 Jul 2022 11:34:40 +0200
Subject: Cleanup: NLA, reformatting code

No functional changes.
---
 source/blender/blenkernel/BKE_nla.h      |  5 +++--
 source/blender/blenkernel/intern/nla.c   |  2 +-
 source/blender/makesrna/intern/rna_nla.c | 11 +++++------
 3 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/source/blender/blenkernel/BKE_nla.h b/source/blender/blenkernel/BKE_nla.h
index a9444156498..11642763074 100644
--- a/source/blender/blenkernel/BKE_nla.h
+++ b/source/blender/blenkernel/BKE_nla.h
@@ -8,7 +8,7 @@
  */
 
 /* temp constant defined for these funcs only... */
-#  define NLASTRIP_MIN_LEN_THRESH 0.1f
+#define NLASTRIP_MIN_LEN_THRESH 0.1f
 
 #ifdef __cplusplus
 extern "C" {
@@ -229,7 +229,8 @@ bool BKE_nlatrack_is_nonlocal_in_liboverride(const struct ID *id, const struct N
  * - the macro MINFRAMEF, if no strips are to the left of this strip in its track
  *
  * \param strip The strip to compute the left-hand-side 'frame limit' of.
- * \return The beginning frame of the previous strip, or MINFRAMEF if no strips are next in that track.
+ * \return The beginning frame of the previous strip, or MINFRAMEF if no strips are next in that
+ * track.
  */
 float BKE_nlastrip_compute_frame_from_previous_strip(struct NlaStrip *strip);
 /**
diff --git a/source/blender/blenkernel/intern/nla.c b/source/blender/blenkernel/intern/nla.c
index 3af5e4789d2..9457c20eb7d 100644
--- a/source/blender/blenkernel/intern/nla.c
+++ b/source/blender/blenkernel/intern/nla.c
@@ -1256,7 +1256,7 @@ float BKE_nlastrip_compute_frame_from_previous_strip(NlaStrip *strip)
   return limit_prev;
 }
 
-float BKE_nlastrip_compute_frame_to_next_strip(NlaStrip* strip)
+float BKE_nlastrip_compute_frame_to_next_strip(NlaStrip *strip)
 {
   float limit_next = MAXFRAMEF;
 
diff --git a/source/blender/makesrna/intern/rna_nla.c b/source/blender/makesrna/intern/rna_nla.c
index f1aa9925a41..5af3f94d075 100644
--- a/source/blender/makesrna/intern/rna_nla.c
+++ b/source/blender/makesrna/intern/rna_nla.c
@@ -337,7 +337,6 @@ static void rna_NlaStrip_frame_end_ui_set(PointerRNA *ptr, float value)
     else {
       data->repeat -= (action_length_delta / actlen);
     }
-
   }
 }
 
@@ -749,9 +748,9 @@ static void rna_def_nlastrip(BlenderRNA *brna)
   RNA_def_property_float_sdna(prop, NULL, "start");
   RNA_def_property_float_funcs(prop, NULL, "rna_NlaStrip_frame_start_ui_set", NULL);
   RNA_def_property_ui_text(
-    prop,
-    "Start Frame (manipulated from UI)",
-    "Start frame of the NLA strip. Note : changing this value also updates the value of "
+      prop,
+      "Start Frame (manipulated from UI)",
+      "Start frame of the NLA strip. Note : changing this value also updates the value of "
       "the strip's end frame. If only the start frame should be changed, see the \"frame_start\" "
       "property instead.");
   RNA_def_property_update(
@@ -761,8 +760,8 @@ static void rna_def_nlastrip(BlenderRNA *brna)
   RNA_def_property_float_sdna(prop, NULL, "end");
   RNA_def_property_float_funcs(prop, NULL, "rna_NlaStrip_frame_end_ui_set", NULL);
   RNA_def_property_ui_text(
-    prop,
-    "End Frame (manipulated from UI)",
+      prop,
+      "End Frame (manipulated from UI)",
       "End frame of the NLA strip. Note : changing this value also updates the value of "
       "the strip's start frame. If only the end frame should be changed, see the \"frame_end\" "
       "property instead.");
-- 
cgit v1.2.3


From 935ef06fd13ada29f025ddaf04bf72761495d3e7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= 
Date: Tue, 5 Jul 2022 12:18:20 +0200
Subject: NLA: fix punctuation of tooltips

---
 source/blender/makesrna/intern/rna_nla.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/source/blender/makesrna/intern/rna_nla.c b/source/blender/makesrna/intern/rna_nla.c
index 5af3f94d075..f89744a8568 100644
--- a/source/blender/makesrna/intern/rna_nla.c
+++ b/source/blender/makesrna/intern/rna_nla.c
@@ -750,9 +750,9 @@ static void rna_def_nlastrip(BlenderRNA *brna)
   RNA_def_property_ui_text(
       prop,
       "Start Frame (manipulated from UI)",
-      "Start frame of the NLA strip. Note : changing this value also updates the value of "
+      "Start frame of the NLA strip. Note: changing this value also updates the value of "
       "the strip's end frame. If only the start frame should be changed, see the \"frame_start\" "
-      "property instead.");
+      "property instead");
   RNA_def_property_update(
       prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_transform_update");
 
@@ -762,9 +762,9 @@ static void rna_def_nlastrip(BlenderRNA *brna)
   RNA_def_property_ui_text(
       prop,
       "End Frame (manipulated from UI)",
-      "End frame of the NLA strip. Note : changing this value also updates the value of "
+      "End frame of the NLA strip. Note: changing this value also updates the value of "
       "the strip's start frame. If only the end frame should be changed, see the \"frame_end\" "
-      "property instead.");
+      "property instead");
   RNA_def_property_update(
       prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_transform_update");
 
-- 
cgit v1.2.3


From 598a26fd8a59ae0b3012ce9577160fb68d681d17 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= 
Date: Tue, 5 Jul 2022 12:20:59 +0200
Subject: NLA: update description of `frame_end_ui` RNA property

The description incorrectly mentioned it changes the start frame as well,
but it changes the strip's repeats or the action's end frame.
---
 source/blender/makesrna/intern/rna_nla.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/source/blender/makesrna/intern/rna_nla.c b/source/blender/makesrna/intern/rna_nla.c
index f89744a8568..bbe9ea13a0b 100644
--- a/source/blender/makesrna/intern/rna_nla.c
+++ b/source/blender/makesrna/intern/rna_nla.c
@@ -763,8 +763,8 @@ static void rna_def_nlastrip(BlenderRNA *brna)
       prop,
       "End Frame (manipulated from UI)",
       "End frame of the NLA strip. Note: changing this value also updates the value of "
-      "the strip's start frame. If only the end frame should be changed, see the \"frame_end\" "
-      "property instead");
+      "the strip's repeats or its action's end frame. If only the end frame should be "
+      "changed, see the \"frame_end\" property instead");
   RNA_def_property_update(
       prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_transform_update");
 
-- 
cgit v1.2.3


From ce1d023667c8d3ca565a67f8205e532b9aaad848 Mon Sep 17 00:00:00 2001
From: Bastien Montagne 
Date: Tue, 5 Jul 2022 12:45:54 +0200
Subject: Fix (unreported) liboverride: incomplete hierarchy when root is not
 object/collection.

We do not (currently) consider other ID types as 'end points' justifying
to create an override hierarchy, however if the 'root' ID (i.e. the ID
the user selected as base to create the override) is not an object or
collection, we still want to check all of its dependencies.

This fixes e.g. if a material depends on another Empty object, and user
tries to hierarchy-override that material, its Empty dependency not
being overridden.
---
 source/blender/blenkernel/intern/lib_override.cc | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/source/blender/blenkernel/intern/lib_override.cc b/source/blender/blenkernel/intern/lib_override.cc
index feb6fb95f5c..d816b5ede5f 100644
--- a/source/blender/blenkernel/intern/lib_override.cc
+++ b/source/blender/blenkernel/intern/lib_override.cc
@@ -937,11 +937,6 @@ static void lib_override_linked_group_tag(LibOverrideGroupTagData *data)
     id_root->tag |= data->tag;
   }
 
-  /* Only objects and groups are currently considered as 'keys' in override hierarchies. */
-  if (!ELEM(GS(id_root->name), ID_OB, ID_GR)) {
-    return;
-  }
-
   /* Tag all collections and objects recursively. */
   lib_override_linked_group_tag_recursive(data);
 
-- 
cgit v1.2.3


From 7f24d90f11a6d26b2737bf28df1c66d169e7e8c2 Mon Sep 17 00:00:00 2001
From: Bastien Montagne 
Date: Tue, 5 Jul 2022 12:49:53 +0200
Subject: Fix T99272: Regression: location override ignored when used in a
 shadertree.

This is not strictly speaking a regression, this worked before partial
resync was introduced purely because the whole override hierarchy was
systematically re-built.

But support for material pointers in obdata (meshes etc.) was simply not
implemented.

NOTE: This commit also greatly improves general support of materials in
liboverrides, although there is still more work needed in that area to
consider it properly supported.
---
 source/blender/makesrna/intern/rna_mesh.c | 52 +++++++++++++++++++++++++++++++
 1 file changed, 52 insertions(+)

diff --git a/source/blender/makesrna/intern/rna_mesh.c b/source/blender/makesrna/intern/rna_mesh.c
index 015b6d4055b..65468977ccb 100644
--- a/source/blender/makesrna/intern/rna_mesh.c
+++ b/source/blender/makesrna/intern/rna_mesh.c
@@ -1723,6 +1723,56 @@ static void UNUSED_FUNCTION(rna_mesh_unused)(void)
   /* end unused function block */
 }
 
+static bool rna_Mesh_materials_override_apply(Main *bmain,
+                                              PointerRNA *ptr_dst,
+                                              PointerRNA *UNUSED(ptr_src),
+                                              PointerRNA *UNUSED(ptr_storage),
+                                              PropertyRNA *prop_dst,
+                                              PropertyRNA *UNUSED(prop_src),
+                                              PropertyRNA *UNUSED(prop_storage),
+                                              const int UNUSED(len_dst),
+                                              const int UNUSED(len_src),
+                                              const int UNUSED(len_storage),
+                                              PointerRNA *ptr_item_dst,
+                                              PointerRNA *ptr_item_src,
+                                              PointerRNA *UNUSED(ptr_item_storage),
+                                              IDOverrideLibraryPropertyOperation *opop)
+{
+  BLI_assert_msg(opop->operation == IDOVERRIDE_LIBRARY_OP_REPLACE,
+                 "Unsupported RNA override operation on collections' objects");
+  UNUSED_VARS_NDEBUG(opop);
+
+  Mesh *mesh_dst = (Mesh *)ptr_dst->owner_id;
+
+  if (ptr_item_dst->type == NULL || ptr_item_src->type == NULL) {
+    // BLI_assert_msg(0, "invalid source or destination material.");
+    return false;
+  }
+
+  Material *mat_dst = ptr_item_dst->data;
+  Material *mat_src = ptr_item_src->data;
+
+  if (mat_src == mat_dst) {
+    return true;
+  }
+
+  bool is_modified = false;
+  for (int i = 0; i < mesh_dst->totcol; i++) {
+    if (mesh_dst->mat[i] == mat_dst) {
+      id_us_min(&mat_dst->id);
+      mesh_dst->mat[i] = mat_src;
+      id_us_plus(&mat_src->id);
+      is_modified = true;
+    }
+  }
+
+  if (is_modified) {
+    RNA_property_update_main(bmain, NULL, ptr_dst, prop_dst);
+  }
+
+  return true;
+}
+
 /** \} */
 
 #else
@@ -2478,6 +2528,8 @@ void rna_def_texmat_common(StructRNA *srna, const char *texspace_editable)
   RNA_def_property_struct_type(prop, "Material");
   RNA_def_property_ui_text(prop, "Materials", "");
   RNA_def_property_srna(prop, "IDMaterials"); /* see rna_ID.c */
+  RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+  RNA_def_property_override_funcs(prop, NULL, NULL, "rna_Mesh_materials_override_apply");
   RNA_def_property_collection_funcs(
       prop, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "rna_IDMaterials_assign_int");
 }
-- 
cgit v1.2.3


From c46d4d9fad5e16daa9f50e30e6373d20b8386bbb Mon Sep 17 00:00:00 2001
From: Jacques Lucke 
Date: Tue, 5 Jul 2022 14:56:04 +0200
Subject: Curves: move curves surface transforms to blenkernel

This utility struct is useful outside of sculpting code as well.
---
 source/blender/blenkernel/BKE_curves.hh                  | 13 +++++++++++++
 source/blender/blenkernel/intern/curves.cc               | 14 ++++++++++++++
 source/blender/editors/sculpt_paint/curves_sculpt_add.cc |  4 ++--
 .../blender/editors/sculpt_paint/curves_sculpt_brush.cc  | 16 +---------------
 .../blender/editors/sculpt_paint/curves_sculpt_comb.cc   |  4 ++--
 .../blender/editors/sculpt_paint/curves_sculpt_delete.cc |  4 ++--
 .../editors/sculpt_paint/curves_sculpt_density.cc        | 10 +++++-----
 .../editors/sculpt_paint/curves_sculpt_grow_shrink.cc    |  4 ++--
 .../blender/editors/sculpt_paint/curves_sculpt_intern.hh | 16 ++--------------
 .../blender/editors/sculpt_paint/curves_sculpt_pinch.cc  |  4 ++--
 .../blender/editors/sculpt_paint/curves_sculpt_puff.cc   |  4 ++--
 .../sculpt_paint/curves_sculpt_selection_paint.cc        |  4 ++--
 .../blender/editors/sculpt_paint/curves_sculpt_slide.cc  |  4 ++--
 .../blender/editors/sculpt_paint/curves_sculpt_smooth.cc |  4 ++--
 .../editors/sculpt_paint/curves_sculpt_snake_hook.cc     |  4 ++--
 15 files changed, 55 insertions(+), 54 deletions(-)

diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh
index cc0c607f9bb..767936e2a26 100644
--- a/source/blender/blenkernel/BKE_curves.hh
+++ b/source/blender/blenkernel/BKE_curves.hh
@@ -836,4 +836,17 @@ inline float3 calculate_vector_handle(const float3 &point, const float3 &next_po
 
 }  // namespace curves::bezier
 
+struct CurvesSurfaceTransforms {
+  float4x4 curves_to_world;
+  float4x4 curves_to_surface;
+  float4x4 world_to_curves;
+  float4x4 world_to_surface;
+  float4x4 surface_to_world;
+  float4x4 surface_to_curves;
+  float4x4 surface_to_curves_normal;
+
+  CurvesSurfaceTransforms() = default;
+  CurvesSurfaceTransforms(const Object &curves_ob, const Object *surface_ob);
+};
+
 }  // namespace blender::bke
diff --git a/source/blender/blenkernel/intern/curves.cc b/source/blender/blenkernel/intern/curves.cc
index 7ad83263b73..78791b55b4d 100644
--- a/source/blender/blenkernel/intern/curves.cc
+++ b/source/blender/blenkernel/intern/curves.cc
@@ -388,4 +388,18 @@ Curves *curves_new_nomain(CurvesGeometry curves)
   return curves_id;
 }
 
+CurvesSurfaceTransforms::CurvesSurfaceTransforms(const Object &curves_ob, const Object *surface_ob)
+{
+  this->curves_to_world = curves_ob.obmat;
+  this->world_to_curves = this->curves_to_world.inverted();
+
+  if (surface_ob != nullptr) {
+    this->surface_to_world = surface_ob->obmat;
+    this->world_to_surface = this->surface_to_world.inverted();
+    this->surface_to_curves = this->world_to_curves * this->surface_to_world;
+    this->curves_to_surface = this->world_to_surface * this->curves_to_world;
+    this->surface_to_curves_normal = this->surface_to_curves.inverted().transposed();
+  }
+}
+
 }  // namespace blender::bke
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc
index b7f496889c0..e5e6cfef8ae 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc
@@ -97,7 +97,7 @@ struct AddOperationExecutor {
   float brush_radius_re_;
   float2 brush_pos_re_;
 
-  CurvesSculptTransforms transforms_;
+  CurvesSurfaceTransforms transforms_;
 
   BVHTreeFromMesh surface_bvh_;
 
@@ -123,7 +123,7 @@ struct AddOperationExecutor {
       return;
     }
 
-    transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface);
+    transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface);
 
     surface_ob_ = curves_id_->surface;
     surface_ = static_cast(surface_ob_->data);
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc b/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc
index 7d17db515fb..10564942ab9 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc
@@ -258,7 +258,7 @@ std::optional sample_curves_surface_3d_brush(
     const Depsgraph &depsgraph,
     const ARegion ®ion,
     const View3D &v3d,
-    const CurvesSculptTransforms &transforms,
+    const CurvesSurfaceTransforms &transforms,
     const BVHTreeFromMesh &surface_bvh,
     const float2 &brush_pos_re,
     const float brush_radius_re)
@@ -380,18 +380,4 @@ CurvesSculptCommonContext::CurvesSculptCommonContext(const bContext &C)
   this->rv3d = CTX_wm_region_view3d(&C);
 }
 
-CurvesSculptTransforms::CurvesSculptTransforms(const Object &curves_ob, const Object *surface_ob)
-{
-  this->curves_to_world = curves_ob.obmat;
-  this->world_to_curves = this->curves_to_world.inverted();
-
-  if (surface_ob != nullptr) {
-    this->surface_to_world = surface_ob->obmat;
-    this->world_to_surface = this->surface_to_world.inverted();
-    this->surface_to_curves = this->world_to_curves * this->surface_to_world;
-    this->curves_to_surface = this->world_to_surface * this->curves_to_world;
-    this->surface_to_curves_normal = this->surface_to_curves.inverted().transposed();
-  }
-}
-
 }  // namespace blender::ed::sculpt_paint
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc b/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc
index 541bf9d8253..449f1786167 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc
@@ -100,7 +100,7 @@ struct CombOperationExecutor {
   float2 brush_pos_re_;
   float2 brush_pos_diff_re_;
 
-  CurvesSculptTransforms transforms_;
+  CurvesSurfaceTransforms transforms_;
 
   CombOperationExecutor(const bContext &C) : ctx_(C)
   {
@@ -128,7 +128,7 @@ struct CombOperationExecutor {
       return;
     }
 
-    transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface);
+    transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface);
 
     point_factors_ = get_point_selection(*curves_id_);
     curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_);
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc b/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc
index eab7dabcd22..777ebd16110 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc
@@ -76,7 +76,7 @@ struct DeleteOperationExecutor {
 
   float2 brush_pos_re_;
 
-  CurvesSculptTransforms transforms_;
+  CurvesSurfaceTransforms transforms_;
 
   DeleteOperationExecutor(const bContext &C) : ctx_(C)
   {
@@ -100,7 +100,7 @@ struct DeleteOperationExecutor {
 
     brush_pos_re_ = stroke_extension.mouse_position;
 
-    transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface);
+    transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface);
 
     const eBrushFalloffShape falloff_shape = static_cast(
         brush_->falloff_shape);
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc
index be936b4cbda..826b0611e81 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc
@@ -74,7 +74,7 @@ struct DensityAddOperationExecutor {
   float brush_radius_re_;
   float2 brush_pos_re_;
 
-  CurvesSculptTransforms transforms_;
+  CurvesSurfaceTransforms transforms_;
 
   BVHTreeFromMesh surface_bvh_;
 
@@ -105,7 +105,7 @@ struct DensityAddOperationExecutor {
     surface_looptris_ = {BKE_mesh_runtime_looptri_ensure(surface_),
                          BKE_mesh_runtime_looptri_len(surface_)};
 
-    transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface);
+    transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface);
 
     if (!CustomData_has_layer(&surface_->ldata, CD_NORMAL)) {
       BKE_mesh_calc_normals_split(surface_);
@@ -449,7 +449,7 @@ struct DensitySubtractOperationExecutor {
 
   float minimum_distance_;
 
-  CurvesSculptTransforms transforms_;
+  CurvesSurfaceTransforms transforms_;
   BVHTreeFromMesh surface_bvh_;
 
   KDTree_3d *root_points_kdtree_;
@@ -489,7 +489,7 @@ struct DensitySubtractOperationExecutor {
 
     curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_);
 
-    transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface);
+    transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface);
     const eBrushFalloffShape falloff_shape = static_cast(
         brush_->falloff_shape);
     BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2);
@@ -743,7 +743,7 @@ static bool use_add_density_mode(const BrushStrokeMode brush_mode,
     return true;
   }
 
-  const CurvesSculptTransforms transforms(curves_ob, curves_id.surface);
+  const CurvesSurfaceTransforms transforms(curves_ob, curves_id.surface);
   BVHTreeFromMesh surface_bvh;
   BKE_bvhtree_from_mesh_get(
       &surface_bvh, static_cast(curves_id.surface->data), BVHTREE_FROM_LOOPTRI, 2);
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc b/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc
index cf893f09fc6..709ecc79967 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc
@@ -247,7 +247,7 @@ struct CurvesEffectOperationExecutor {
 
   eBrushFalloffShape falloff_shape_;
 
-  CurvesSculptTransforms transforms_;
+  CurvesSurfaceTransforms transforms_;
 
   float2 brush_pos_start_re_;
   float2 brush_pos_end_re_;
@@ -289,7 +289,7 @@ struct CurvesEffectOperationExecutor {
 
     falloff_shape_ = eBrushFalloffShape(brush_->falloff_shape);
 
-    transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface);
+    transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface);
 
     brush_pos_start_re_ = self.last_mouse_position_;
     brush_pos_end_re_ = stroke_extension.mouse_position;
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh
index 4cfaf7ebfc9..c31bba2fe1e 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh
@@ -26,6 +26,7 @@ struct BVHTreeFromMesh;
 namespace blender::ed::sculpt_paint {
 
 using bke::CurvesGeometry;
+using bke::CurvesSurfaceTransforms;
 
 struct StrokeExtension {
   bool is_first;
@@ -116,24 +117,11 @@ class CurvesSculptCommonContext {
   CurvesSculptCommonContext(const bContext &C);
 };
 
-struct CurvesSculptTransforms {
-  float4x4 curves_to_world;
-  float4x4 curves_to_surface;
-  float4x4 world_to_curves;
-  float4x4 world_to_surface;
-  float4x4 surface_to_world;
-  float4x4 surface_to_curves;
-  float4x4 surface_to_curves_normal;
-
-  CurvesSculptTransforms() = default;
-  CurvesSculptTransforms(const Object &curves_ob, const Object *surface_ob);
-};
-
 std::optional sample_curves_surface_3d_brush(
     const Depsgraph &depsgraph,
     const ARegion ®ion,
     const View3D &v3d,
-    const CurvesSculptTransforms &transforms,
+    const CurvesSurfaceTransforms &transforms,
     const BVHTreeFromMesh &surface_bvh,
     const float2 &brush_pos_re,
     const float brush_radius_re);
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc b/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc
index 5b7359a3905..689b7d22e5e 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc
@@ -68,7 +68,7 @@ struct PinchOperationExecutor {
   Vector selected_curve_indices_;
   IndexMask curve_selection_;
 
-  CurvesSculptTransforms transforms_;
+  CurvesSurfaceTransforms transforms_;
 
   const CurvesSculpt *curves_sculpt_ = nullptr;
   const Brush *brush_ = nullptr;
@@ -103,7 +103,7 @@ struct PinchOperationExecutor {
 
     invert_factor_ = self_->invert_pinch_ ? -1.0f : 1.0f;
 
-    transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface);
+    transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface);
 
     point_factors_ = get_point_selection(*curves_id_);
     curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_);
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc b/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc
index dc747fd0bce..83cfda6dc00 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc
@@ -80,7 +80,7 @@ struct PuffOperationExecutor {
 
   eBrushFalloffShape falloff_shape_;
 
-  CurvesSculptTransforms transforms_;
+  CurvesSurfaceTransforms transforms_;
 
   Object *surface_ob_ = nullptr;
   Mesh *surface_ = nullptr;
@@ -122,7 +122,7 @@ struct PuffOperationExecutor {
     surface_ob_ = curves_id_->surface;
     surface_ = static_cast(surface_ob_->data);
 
-    transforms_ = CurvesSculptTransforms(*object_, surface_ob_);
+    transforms_ = CurvesSurfaceTransforms(*object_, surface_ob_);
 
     if (!CustomData_has_layer(&surface_->ldata, CD_NORMAL)) {
       BKE_mesh_calc_normals_split(surface_);
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc b/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc
index 353b84236a3..399d2c73ec3 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc
@@ -66,7 +66,7 @@ struct SelectionPaintOperationExecutor {
 
   float2 brush_pos_re_;
 
-  CurvesSculptTransforms transforms_;
+  CurvesSurfaceTransforms transforms_;
 
   SelectionPaintOperationExecutor(const bContext &C) : ctx_(C)
   {
@@ -104,7 +104,7 @@ struct SelectionPaintOperationExecutor {
       }
     }
 
-    transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface);
+    transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface);
 
     const eBrushFalloffShape falloff_shape = static_cast(
         brush_->falloff_shape);
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc b/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc
index 2739b5869d5..dedc880988d 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc
@@ -98,7 +98,7 @@ struct SlideOperationExecutor {
   float2 brush_pos_re_;
   float2 brush_pos_diff_re_;
 
-  CurvesSculptTransforms transforms_;
+  CurvesSurfaceTransforms transforms_;
 
   BVHTreeFromMesh surface_bvh_;
 
@@ -136,7 +136,7 @@ struct SlideOperationExecutor {
     brush_pos_diff_re_ = brush_pos_re_ - brush_pos_prev_re_;
     BLI_SCOPED_DEFER([&]() { self_->brush_pos_last_re_ = brush_pos_re_; });
 
-    transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface);
+    transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface);
 
     surface_ob_ = curves_id_->surface;
     surface_ = static_cast(surface_ob_->data);
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_smooth.cc b/source/blender/editors/sculpt_paint/curves_sculpt_smooth.cc
index e72b17d448b..f874a9fc255 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_smooth.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_smooth.cc
@@ -52,7 +52,7 @@ struct SmoothOperationExecutor {
   float brush_strength_;
   float2 brush_pos_re_;
 
-  CurvesSculptTransforms transforms_;
+  CurvesSurfaceTransforms transforms_;
 
   SmoothOperationExecutor(const bContext &C) : ctx_(C)
   {
@@ -79,7 +79,7 @@ struct SmoothOperationExecutor {
 
     point_factors_ = get_point_selection(*curves_id_);
     curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_);
-    transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface);
+    transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface);
 
     const eBrushFalloffShape falloff_shape = static_cast(
         brush_->falloff_shape);
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc b/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc
index b63e5a7756b..ec0e8ff45e5 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc
@@ -89,7 +89,7 @@ struct SnakeHookOperatorExecutor {
   Vector selected_curve_indices_;
   IndexMask curve_selection_;
 
-  CurvesSculptTransforms transforms_;
+  CurvesSurfaceTransforms transforms_;
 
   float2 brush_pos_prev_re_;
   float2 brush_pos_re_;
@@ -123,7 +123,7 @@ struct SnakeHookOperatorExecutor {
       return;
     }
 
-    transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface);
+    transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface);
 
     curve_factors_ = get_curves_selection(*curves_id_);
     curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_);
-- 
cgit v1.2.3


From 7ff054c6d1be0e9f022215c86426805032adc196 Mon Sep 17 00:00:00 2001
From: Jacques Lucke 
Date: Tue, 5 Jul 2022 15:06:14 +0200
Subject: Cleanup: use curves surface transform utility in operators

---
 source/blender/editors/curves/intern/curves_ops.cc | 25 +++++++++-------------
 1 file changed, 10 insertions(+), 15 deletions(-)

diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc
index 2ac1a576286..aca074a1d61 100644
--- a/source/blender/editors/curves/intern/curves_ops.cc
+++ b/source/blender/editors/curves/intern/curves_ops.cc
@@ -243,17 +243,14 @@ static void try_convert_single_object(Object &curves_ob,
   }
 
   /* Prepare transformation matrices. */
-  const float4x4 curves_to_world_mat = curves_ob.obmat;
-  const float4x4 surface_to_world_mat = surface_ob.obmat;
-  const float4x4 world_to_surface_mat = surface_to_world_mat.inverted();
-  const float4x4 curves_to_surface_mat = world_to_surface_mat * curves_to_world_mat;
+  const bke::CurvesSurfaceTransforms transforms{curves_ob, &surface_ob};
 
   for (const int new_hair_i : IndexRange(hair_num)) {
     const int curve_i = new_hair_i;
     const IndexRange points = curves.points_for_curve(curve_i);
 
     const float3 &root_pos_cu = positions_cu[points.first()];
-    const float3 root_pos_su = curves_to_surface_mat * root_pos_cu;
+    const float3 root_pos_su = transforms.curves_to_surface * root_pos_cu;
 
     BVHTreeNearest nearest;
     nearest.dist_sq = FLT_MAX;
@@ -293,7 +290,7 @@ static void try_convert_single_object(Object &curves_ob,
 
     for (const int key_i : hair_keys.index_range()) {
       const float3 &key_pos_cu = positions_cu[points[key_i]];
-      const float3 key_pos_su = curves_to_surface_mat * key_pos_cu;
+      const float3 key_pos_su = transforms.curves_to_surface * key_pos_cu;
       const float3 key_pos_ha = surface_to_hair_mat * key_pos_su;
 
       HairKey &key = hair_keys[key_i];
@@ -558,12 +555,7 @@ static int snap_curves_to_surface_exec(bContext *C, wmOperator *op)
     const Span surface_looptris = {BKE_mesh_runtime_looptri_ensure(&surface_mesh),
                                              BKE_mesh_runtime_looptri_len(&surface_mesh)};
 
-    const float4x4 curves_to_world_mat = curves_ob->obmat;
-    const float4x4 world_to_curves_mat = curves_to_world_mat.inverted();
-    const float4x4 surface_to_world_mat = surface_ob.obmat;
-    const float4x4 world_to_surface_mat = surface_to_world_mat.inverted();
-    const float4x4 curves_to_surface_mat = world_to_surface_mat * curves_to_world_mat;
-    const float4x4 surface_to_curves_mat = world_to_curves_mat * surface_to_world_mat;
+    const bke::CurvesSurfaceTransforms transforms{*curves_ob, &surface_ob};
 
     switch (attach_mode) {
       case AttachMode::Nearest: {
@@ -576,7 +568,8 @@ static int snap_curves_to_surface_exec(bContext *C, wmOperator *op)
             const IndexRange points = curves.points_for_curve(curve_i);
             const int first_point_i = points.first();
             const float3 old_first_point_pos_cu = positions_cu[first_point_i];
-            const float3 old_first_point_pos_su = curves_to_surface_mat * old_first_point_pos_cu;
+            const float3 old_first_point_pos_su = transforms.curves_to_surface *
+                                                  old_first_point_pos_cu;
 
             BVHTreeNearest nearest;
             nearest.index = -1;
@@ -592,7 +585,8 @@ static int snap_curves_to_surface_exec(bContext *C, wmOperator *op)
             }
 
             const float3 new_first_point_pos_su = nearest.co;
-            const float3 new_first_point_pos_cu = surface_to_curves_mat * new_first_point_pos_su;
+            const float3 new_first_point_pos_cu = transforms.surface_to_curves *
+                                                  new_first_point_pos_su;
             const float3 pos_diff_cu = new_first_point_pos_cu - old_first_point_pos_cu;
 
             for (float3 &pos_cu : positions_cu.slice(points)) {
@@ -651,7 +645,8 @@ static int snap_curves_to_surface_exec(bContext *C, wmOperator *op)
 
             float3 new_first_point_pos_su;
             interp_v3_v3v3v3(new_first_point_pos_su, p0_su, p1_su, p2_su, bary_coords);
-            const float3 new_first_point_pos_cu = surface_to_curves_mat * new_first_point_pos_su;
+            const float3 new_first_point_pos_cu = transforms.surface_to_curves *
+                                                  new_first_point_pos_su;
 
             const float3 pos_diff_cu = new_first_point_pos_cu - old_first_point_pos_cu;
             for (float3 &pos_cu : positions_cu.slice(points)) {
-- 
cgit v1.2.3


From b98d116257aee64f2e79490c89f4aa7bcca4a9cd Mon Sep 17 00:00:00 2001
From: Jacques Lucke 
Date: Tue, 5 Jul 2022 15:36:00 +0200
Subject: BLI: use a slightly less trivial reverse uv sampler

This approach is still far from ideal, but at least it has linear
complexity in the common case instead of quadratic.
---
 source/blender/geometry/GEO_reverse_uv_sampler.hh  |  4 ++
 .../blender/geometry/intern/reverse_uv_sampler.cc  | 54 ++++++++++++++++++++--
 2 files changed, 53 insertions(+), 5 deletions(-)

diff --git a/source/blender/geometry/GEO_reverse_uv_sampler.hh b/source/blender/geometry/GEO_reverse_uv_sampler.hh
index d392b65eaf4..ee91e0b0731 100644
--- a/source/blender/geometry/GEO_reverse_uv_sampler.hh
+++ b/source/blender/geometry/GEO_reverse_uv_sampler.hh
@@ -5,6 +5,7 @@
 #include 
 
 #include "BLI_math_vector.hh"
+#include "BLI_multi_value_map.hh"
 #include "BLI_span.hh"
 
 #include "DNA_meshdata_types.h"
@@ -20,6 +21,8 @@ class ReverseUVSampler {
  private:
   const Span uv_map_;
   const Span looptris_;
+  int resolution_;
+  MultiValueMap looptris_by_cell_;
 
  public:
   ReverseUVSampler(const Span uv_map, const Span looptris);
@@ -37,6 +40,7 @@ class ReverseUVSampler {
   };
 
   Result sample(const float2 &query_uv) const;
+  void sample_many(Span query_uvs, MutableSpan r_results) const;
 };
 
 }  // namespace blender::geometry
diff --git a/source/blender/geometry/intern/reverse_uv_sampler.cc b/source/blender/geometry/intern/reverse_uv_sampler.cc
index 9aa98895a86..87ba2c77657 100644
--- a/source/blender/geometry/intern/reverse_uv_sampler.cc
+++ b/source/blender/geometry/intern/reverse_uv_sampler.cc
@@ -3,22 +3,55 @@
 #include "GEO_reverse_uv_sampler.hh"
 
 #include "BLI_math_geom.h"
+#include "BLI_math_vector.hh"
+#include "BLI_task.hh"
+#include "BLI_timeit.hh"
 
 namespace blender::geometry {
 
+static int2 uv_to_cell_key(const float2 &uv, const int resolution)
+{
+  return int2{uv * resolution};
+}
+
 ReverseUVSampler::ReverseUVSampler(const Span uv_map, const Span looptris)
     : uv_map_(uv_map), looptris_(looptris)
 {
+  resolution_ = std::max(3, std::sqrt(looptris.size()) * 2);
+
+  for (const int looptri_index : looptris.index_range()) {
+    const MLoopTri &looptri = looptris[looptri_index];
+    const float2 &uv_0 = uv_map_[looptri.tri[0]];
+    const float2 &uv_1 = uv_map_[looptri.tri[1]];
+    const float2 &uv_2 = uv_map_[looptri.tri[2]];
+
+    const int2 key_0 = uv_to_cell_key(uv_0, resolution_);
+    const int2 key_1 = uv_to_cell_key(uv_1, resolution_);
+    const int2 key_2 = uv_to_cell_key(uv_2, resolution_);
+
+    const int2 min_key = math::min(math::min(key_0, key_1), key_2);
+    const int2 max_key = math::max(math::max(key_0, key_1), key_2);
+
+    for (int key_x = min_key.x; key_x <= max_key.x; key_x++) {
+      for (int key_y = min_key.y; key_y <= max_key.y; key_y++) {
+        const int2 key{key_x, key_y};
+        looptris_by_cell_.add(key, looptri_index);
+      }
+    }
+  }
 }
 
 ReverseUVSampler::Result ReverseUVSampler::sample(const float2 &query_uv) const
 {
-  for (const MLoopTri &looptri : looptris_) {
-    const float2 &uv0 = uv_map_[looptri.tri[0]];
-    const float2 &uv1 = uv_map_[looptri.tri[1]];
-    const float2 &uv2 = uv_map_[looptri.tri[2]];
+  const int2 cell_key = uv_to_cell_key(query_uv, resolution_);
+  const Span looptri_indices = looptris_by_cell_.lookup(cell_key);
+  for (const int looptri_index : looptri_indices) {
+    const MLoopTri &looptri = looptris_[looptri_index];
+    const float2 &uv_0 = uv_map_[looptri.tri[0]];
+    const float2 &uv_1 = uv_map_[looptri.tri[1]];
+    const float2 &uv_2 = uv_map_[looptri.tri[2]];
     float3 bary_weights;
-    if (!barycentric_coords_v2(uv0, uv1, uv2, query_uv, bary_weights)) {
+    if (!barycentric_coords_v2(uv_0, uv_1, uv_2, query_uv, bary_weights)) {
       continue;
     }
     if (IN_RANGE_INCL(bary_weights.x, 0.0f, 1.0f) && IN_RANGE_INCL(bary_weights.y, 0.0f, 1.0f) &&
@@ -29,4 +62,15 @@ ReverseUVSampler::Result ReverseUVSampler::sample(const float2 &query_uv) const
   return Result{};
 }
 
+void ReverseUVSampler::sample_many(const Span query_uvs,
+                                   MutableSpan r_results) const
+{
+  BLI_assert(query_uvs.size() == r_results.size());
+  threading::parallel_for(query_uvs.index_range(), 256, [&](const IndexRange range) {
+    for (const int i : range) {
+      r_results[i] = this->sample(query_uvs[i]);
+    }
+  });
+}
+
 }  // namespace blender::geometry
-- 
cgit v1.2.3


From d4099465cddc2d3ec3653220f6b775ba3ffb3ea0 Mon Sep 17 00:00:00 2001
From: Jacques Lucke 
Date: Tue, 5 Jul 2022 15:37:34 +0200
Subject: Cleanup: extract function to snap curves to surface

This makes it possible to use this function without having
to call an operator. This is currently used by D14864.
---
 source/blender/editors/curves/intern/curves_ops.cc | 271 +++++++++++----------
 1 file changed, 143 insertions(+), 128 deletions(-)

diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc
index aca074a1d61..d9f207103ae 100644
--- a/source/blender/editors/curves/intern/curves_ops.cc
+++ b/source/blender/editors/curves/intern/curves_ops.cc
@@ -517,152 +517,167 @@ static bool snap_curves_to_surface_poll(bContext *C)
   return true;
 }
 
+static void snap_curves_to_surface_exec_object(Object &curves_ob,
+                                               const Object &surface_ob,
+                                               const AttachMode attach_mode,
+                                               bool *r_invalid_uvs,
+                                               bool *r_missing_uvs)
+{
+  Curves &curves_id = *static_cast(curves_ob.data);
+  CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry);
+
+  Mesh &surface_mesh = *static_cast(surface_ob.data);
+
+  MeshComponent surface_mesh_component;
+  surface_mesh_component.replace(&surface_mesh, GeometryOwnershipType::ReadOnly);
+
+  VArraySpan surface_uv_map;
+  if (curves_id.surface_uv_map != nullptr) {
+    surface_uv_map = surface_mesh_component
+                         .attribute_try_get_for_read(
+                             curves_id.surface_uv_map, ATTR_DOMAIN_CORNER, CD_PROP_FLOAT2)
+                         .typed();
+  }
+
+  MutableSpan positions_cu = curves.positions_for_write();
+  MutableSpan surface_uv_coords = curves.surface_uv_coords_for_write();
+
+  const Span surface_looptris = {BKE_mesh_runtime_looptri_ensure(&surface_mesh),
+                                           BKE_mesh_runtime_looptri_len(&surface_mesh)};
+
+  const bke::CurvesSurfaceTransforms transforms{curves_ob, &surface_ob};
+
+  switch (attach_mode) {
+    case AttachMode::Nearest: {
+      BVHTreeFromMesh surface_bvh;
+      BKE_bvhtree_from_mesh_get(&surface_bvh, &surface_mesh, BVHTREE_FROM_LOOPTRI, 2);
+      BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh); });
+
+      threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange curves_range) {
+        for (const int curve_i : curves_range) {
+          const IndexRange points = curves.points_for_curve(curve_i);
+          const int first_point_i = points.first();
+          const float3 old_first_point_pos_cu = positions_cu[first_point_i];
+          const float3 old_first_point_pos_su = transforms.curves_to_surface *
+                                                old_first_point_pos_cu;
+
+          BVHTreeNearest nearest;
+          nearest.index = -1;
+          nearest.dist_sq = FLT_MAX;
+          BLI_bvhtree_find_nearest(surface_bvh.tree,
+                                   old_first_point_pos_su,
+                                   &nearest,
+                                   surface_bvh.nearest_callback,
+                                   &surface_bvh);
+          const int looptri_index = nearest.index;
+          if (looptri_index == -1) {
+            continue;
+          }
+
+          const float3 new_first_point_pos_su = nearest.co;
+          const float3 new_first_point_pos_cu = transforms.surface_to_curves *
+                                                new_first_point_pos_su;
+          const float3 pos_diff_cu = new_first_point_pos_cu - old_first_point_pos_cu;
+
+          for (float3 &pos_cu : positions_cu.slice(points)) {
+            pos_cu += pos_diff_cu;
+          }
+
+          if (!surface_uv_map.is_empty()) {
+            const MLoopTri &looptri = surface_looptris[looptri_index];
+            const int corner0 = looptri.tri[0];
+            const int corner1 = looptri.tri[1];
+            const int corner2 = looptri.tri[2];
+            const float2 &uv0 = surface_uv_map[corner0];
+            const float2 &uv1 = surface_uv_map[corner1];
+            const float2 &uv2 = surface_uv_map[corner2];
+            const float3 &p0_su = surface_mesh.mvert[surface_mesh.mloop[corner0].v].co;
+            const float3 &p1_su = surface_mesh.mvert[surface_mesh.mloop[corner1].v].co;
+            const float3 &p2_su = surface_mesh.mvert[surface_mesh.mloop[corner2].v].co;
+            float3 bary_coords;
+            interp_weights_tri_v3(bary_coords, p0_su, p1_su, p2_su, new_first_point_pos_su);
+            const float2 uv = attribute_math::mix3(bary_coords, uv0, uv1, uv2);
+            surface_uv_coords[curve_i] = uv;
+          }
+        }
+      });
+      break;
+    }
+    case AttachMode::Deform: {
+      if (surface_uv_map.is_empty()) {
+        *r_missing_uvs = true;
+        break;
+      }
+      using geometry::ReverseUVSampler;
+      ReverseUVSampler reverse_uv_sampler{surface_uv_map, surface_looptris};
+
+      threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange curves_range) {
+        for (const int curve_i : curves_range) {
+          const IndexRange points = curves.points_for_curve(curve_i);
+          const int first_point_i = points.first();
+          const float3 old_first_point_pos_cu = positions_cu[first_point_i];
+
+          const float2 uv = surface_uv_coords[curve_i];
+          ReverseUVSampler::Result lookup_result = reverse_uv_sampler.sample(uv);
+          if (lookup_result.type != ReverseUVSampler::ResultType::Ok) {
+            *r_invalid_uvs = true;
+            continue;
+          }
+
+          const MLoopTri &looptri = *lookup_result.looptri;
+          const float3 &bary_coords = lookup_result.bary_weights;
+
+          const float3 &p0_su = surface_mesh.mvert[surface_mesh.mloop[looptri.tri[0]].v].co;
+          const float3 &p1_su = surface_mesh.mvert[surface_mesh.mloop[looptri.tri[1]].v].co;
+          const float3 &p2_su = surface_mesh.mvert[surface_mesh.mloop[looptri.tri[2]].v].co;
+
+          float3 new_first_point_pos_su;
+          interp_v3_v3v3v3(new_first_point_pos_su, p0_su, p1_su, p2_su, bary_coords);
+          const float3 new_first_point_pos_cu = transforms.surface_to_curves *
+                                                new_first_point_pos_su;
+
+          const float3 pos_diff_cu = new_first_point_pos_cu - old_first_point_pos_cu;
+          for (float3 &pos_cu : positions_cu.slice(points)) {
+            pos_cu += pos_diff_cu;
+          }
+        }
+      });
+      break;
+    }
+  }
+
+  DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY);
+}
+
 static int snap_curves_to_surface_exec(bContext *C, wmOperator *op)
 {
   const AttachMode attach_mode = static_cast(RNA_enum_get(op->ptr, "attach_mode"));
 
-  std::atomic found_invalid_uv = false;
+  bool found_invalid_uvs = false;
+  bool found_missing_uvs = false;
 
   CTX_DATA_BEGIN (C, Object *, curves_ob, selected_objects) {
     if (curves_ob->type != OB_CURVES) {
       continue;
     }
     Curves &curves_id = *static_cast(curves_ob->data);
-    CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry);
     if (curves_id.surface == nullptr) {
       continue;
     }
-    Object &surface_ob = *curves_id.surface;
-    if (surface_ob.type != OB_MESH) {
+    if (curves_id.surface->type != OB_MESH) {
       continue;
     }
-    Mesh &surface_mesh = *static_cast(surface_ob.data);
-
-    MeshComponent surface_mesh_component;
-    surface_mesh_component.replace(&surface_mesh, GeometryOwnershipType::ReadOnly);
-
-    VArraySpan surface_uv_map;
-    if (curves_id.surface_uv_map != nullptr) {
-      surface_uv_map = surface_mesh_component
-                           .attribute_try_get_for_read(
-                               curves_id.surface_uv_map, ATTR_DOMAIN_CORNER, CD_PROP_FLOAT2)
-                           .typed();
-    }
-
-    MutableSpan positions_cu = curves.positions_for_write();
-    MutableSpan surface_uv_coords = curves.surface_uv_coords_for_write();
-
-    const Span surface_looptris = {BKE_mesh_runtime_looptri_ensure(&surface_mesh),
-                                             BKE_mesh_runtime_looptri_len(&surface_mesh)};
-
-    const bke::CurvesSurfaceTransforms transforms{*curves_ob, &surface_ob};
-
-    switch (attach_mode) {
-      case AttachMode::Nearest: {
-        BVHTreeFromMesh surface_bvh;
-        BKE_bvhtree_from_mesh_get(&surface_bvh, &surface_mesh, BVHTREE_FROM_LOOPTRI, 2);
-        BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh); });
-
-        threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange curves_range) {
-          for (const int curve_i : curves_range) {
-            const IndexRange points = curves.points_for_curve(curve_i);
-            const int first_point_i = points.first();
-            const float3 old_first_point_pos_cu = positions_cu[first_point_i];
-            const float3 old_first_point_pos_su = transforms.curves_to_surface *
-                                                  old_first_point_pos_cu;
-
-            BVHTreeNearest nearest;
-            nearest.index = -1;
-            nearest.dist_sq = FLT_MAX;
-            BLI_bvhtree_find_nearest(surface_bvh.tree,
-                                     old_first_point_pos_su,
-                                     &nearest,
-                                     surface_bvh.nearest_callback,
-                                     &surface_bvh);
-            const int looptri_index = nearest.index;
-            if (looptri_index == -1) {
-              continue;
-            }
-
-            const float3 new_first_point_pos_su = nearest.co;
-            const float3 new_first_point_pos_cu = transforms.surface_to_curves *
-                                                  new_first_point_pos_su;
-            const float3 pos_diff_cu = new_first_point_pos_cu - old_first_point_pos_cu;
-
-            for (float3 &pos_cu : positions_cu.slice(points)) {
-              pos_cu += pos_diff_cu;
-            }
-
-            if (!surface_uv_map.is_empty()) {
-              const MLoopTri &looptri = surface_looptris[looptri_index];
-              const int corner0 = looptri.tri[0];
-              const int corner1 = looptri.tri[1];
-              const int corner2 = looptri.tri[2];
-              const float2 &uv0 = surface_uv_map[corner0];
-              const float2 &uv1 = surface_uv_map[corner1];
-              const float2 &uv2 = surface_uv_map[corner2];
-              const float3 &p0_su = surface_mesh.mvert[surface_mesh.mloop[corner0].v].co;
-              const float3 &p1_su = surface_mesh.mvert[surface_mesh.mloop[corner1].v].co;
-              const float3 &p2_su = surface_mesh.mvert[surface_mesh.mloop[corner2].v].co;
-              float3 bary_coords;
-              interp_weights_tri_v3(bary_coords, p0_su, p1_su, p2_su, new_first_point_pos_su);
-              const float2 uv = attribute_math::mix3(bary_coords, uv0, uv1, uv2);
-              surface_uv_coords[curve_i] = uv;
-            }
-          }
-        });
-        break;
-      }
-      case AttachMode::Deform: {
-        if (surface_uv_map.is_empty()) {
-          BKE_report(op->reports,
-                     RPT_ERROR,
-                     "Curves do not have attachment information that can be used for deformation");
-          break;
-        }
-        using geometry::ReverseUVSampler;
-        ReverseUVSampler reverse_uv_sampler{surface_uv_map, surface_looptris};
-
-        threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange curves_range) {
-          for (const int curve_i : curves_range) {
-            const IndexRange points = curves.points_for_curve(curve_i);
-            const int first_point_i = points.first();
-            const float3 old_first_point_pos_cu = positions_cu[first_point_i];
-
-            const float2 uv = surface_uv_coords[curve_i];
-            ReverseUVSampler::Result lookup_result = reverse_uv_sampler.sample(uv);
-            if (lookup_result.type != ReverseUVSampler::ResultType::Ok) {
-              found_invalid_uv = true;
-              continue;
-            }
-
-            const MLoopTri &looptri = *lookup_result.looptri;
-            const float3 &bary_coords = lookup_result.bary_weights;
-
-            const float3 &p0_su = surface_mesh.mvert[surface_mesh.mloop[looptri.tri[0]].v].co;
-            const float3 &p1_su = surface_mesh.mvert[surface_mesh.mloop[looptri.tri[1]].v].co;
-            const float3 &p2_su = surface_mesh.mvert[surface_mesh.mloop[looptri.tri[2]].v].co;
-
-            float3 new_first_point_pos_su;
-            interp_v3_v3v3v3(new_first_point_pos_su, p0_su, p1_su, p2_su, bary_coords);
-            const float3 new_first_point_pos_cu = transforms.surface_to_curves *
-                                                  new_first_point_pos_su;
-
-            const float3 pos_diff_cu = new_first_point_pos_cu - old_first_point_pos_cu;
-            for (float3 &pos_cu : positions_cu.slice(points)) {
-              pos_cu += pos_diff_cu;
-            }
-          }
-        });
-        break;
-      }
-    }
-
-    DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY);
+    snap_curves_to_surface_exec_object(
+        *curves_ob, *curves_id.surface, attach_mode, &found_invalid_uvs, &found_missing_uvs);
   }
   CTX_DATA_END;
 
-  if (found_invalid_uv) {
+  if (found_missing_uvs) {
+    BKE_report(op->reports,
+               RPT_ERROR,
+               "Curves do not have attachment information that can be used for deformation");
+  }
+  if (found_invalid_uvs) {
     BKE_report(op->reports, RPT_INFO, "Could not snap some curves to the surface");
   }
 
-- 
cgit v1.2.3


From 8f0907b79701f3fa13b66528cfaeb901bf84e930 Mon Sep 17 00:00:00 2001
From: Jacques Lucke 
Date: Tue, 5 Jul 2022 15:38:30 +0200
Subject: BLI: add float3x3 * float3 operator overload

---
 source/blender/blenlib/BLI_float3x3.hh | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/source/blender/blenlib/BLI_float3x3.hh b/source/blender/blenlib/BLI_float3x3.hh
index 62478556d9b..6a9e7dd04f0 100644
--- a/source/blender/blenlib/BLI_float3x3.hh
+++ b/source/blender/blenlib/BLI_float3x3.hh
@@ -152,6 +152,13 @@ struct float3x3 {
     return result;
   }
 
+  friend float3 operator*(const float3x3 &a, const float3 &b)
+  {
+    float3 result;
+    mul_v3_m3v3(result, a.values, b);
+    return result;
+  }
+
   void operator*=(const float3x3 &other)
   {
     mul_m3_m3_post(values, other.values);
-- 
cgit v1.2.3


From 31f0845b7e5e86a4e99dc586e2a7ed89f5271d80 Mon Sep 17 00:00:00 2001
From: Sergey Sharybin 
Date: Tue, 5 Jul 2022 15:55:53 +0200
Subject: Fix tracking header not being self-sufficient

It used size_t type without including any header to define it.
---
 source/blender/blenkernel/BKE_tracking.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/source/blender/blenkernel/BKE_tracking.h b/source/blender/blenkernel/BKE_tracking.h
index 23b1f7c09bb..89f30ce8ef8 100644
--- a/source/blender/blenkernel/BKE_tracking.h
+++ b/source/blender/blenkernel/BKE_tracking.h
@@ -7,6 +7,8 @@
  * \ingroup bke
  */
 
+#include "BLI_sys_types.h"
+
 #ifdef __cplusplus
 extern "C" {
 #endif
-- 
cgit v1.2.3


From 329efa23d0e5555243a413407e8d6cc9e4cf7d65 Mon Sep 17 00:00:00 2001
From: Sergey Sharybin 
Date: Tue, 5 Jul 2022 15:56:39 +0200
Subject: Cleanup: Unused headers in generic compositor nodes header

Move headers to node files which actually need those.
There is no need for all nodes to have all those headers
included indirectly.
---
 source/blender/nodes/composite/node_composite_util.hh        | 12 ------------
 .../nodes/composite/nodes/node_composite_cryptomatte.cc      |  3 +++
 .../blender/nodes/composite/nodes/node_composite_curves.cc   |  2 ++
 .../nodes/composite/nodes/node_composite_huecorrect.cc       |  2 ++
 source/blender/nodes/composite/nodes/node_composite_image.cc |  2 ++
 .../nodes/composite/nodes/node_composite_map_value.cc        |  2 ++
 .../nodes/composite/nodes/node_composite_moviedistortion.cc  |  1 +
 .../blender/nodes/composite/nodes/node_composite_trackpos.cc |  2 ++
 .../nodes/composite/nodes/node_composite_val_to_rgb.cc       |  2 ++
 9 files changed, 16 insertions(+), 12 deletions(-)

diff --git a/source/blender/nodes/composite/node_composite_util.hh b/source/blender/nodes/composite/node_composite_util.hh
index 3e9c43aa7d2..14210cedc95 100644
--- a/source/blender/nodes/composite/node_composite_util.hh
+++ b/source/blender/nodes/composite/node_composite_util.hh
@@ -8,24 +8,12 @@
 #pragma once
 
 #include "DNA_ID.h"
-#include "DNA_movieclip_types.h"
 #include "DNA_node_types.h"
 
 #include "BLT_translation.h"
 
-#include "BKE_colorband.h"
-#include "BKE_colortools.h"
-#include "BKE_image.h"
-#include "BKE_texture.h"
-#include "BKE_tracking.h"
-
 #include "node_util.h"
 
-#include "IMB_imbuf.h"
-#include "IMB_imbuf_types.h"
-
-#include "RE_pipeline.h"
-
 #include "NOD_composite.h"
 #include "NOD_socket.h"
 #include "NOD_socket_declarations.hh"
diff --git a/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc b/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc
index 9193f91087a..5462441660c 100644
--- a/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc
+++ b/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc
@@ -17,12 +17,15 @@
 #include "BKE_context.h"
 #include "BKE_cryptomatte.hh"
 #include "BKE_global.h"
+#include "BKE_image.h"
 #include "BKE_lib_id.h"
 #include "BKE_library.h"
 #include "BKE_main.h"
 
 #include "MEM_guardedalloc.h"
 
+#include "RE_pipeline.h"
+
 #include 
 
 /* -------------------------------------------------------------------- */
diff --git a/source/blender/nodes/composite/nodes/node_composite_curves.cc b/source/blender/nodes/composite/nodes/node_composite_curves.cc
index fff0d467f75..802664d7934 100644
--- a/source/blender/nodes/composite/nodes/node_composite_curves.cc
+++ b/source/blender/nodes/composite/nodes/node_composite_curves.cc
@@ -5,6 +5,8 @@
  * \ingroup cmpnodes
  */
 
+#include "BKE_colortools.h"
+
 #include "UI_interface.h"
 #include "UI_resources.h"
 
diff --git a/source/blender/nodes/composite/nodes/node_composite_huecorrect.cc b/source/blender/nodes/composite/nodes/node_composite_huecorrect.cc
index bb5e6bf06a8..d252d96f8c3 100644
--- a/source/blender/nodes/composite/nodes/node_composite_huecorrect.cc
+++ b/source/blender/nodes/composite/nodes/node_composite_huecorrect.cc
@@ -7,6 +7,8 @@
 
 #include "node_composite_util.hh"
 
+#include "BKE_colortools.h"
+
 namespace blender::nodes::node_composite_huecorrect_cc {
 
 static void cmp_node_huecorrect_declare(NodeDeclarationBuilder &b)
diff --git a/source/blender/nodes/composite/nodes/node_composite_image.cc b/source/blender/nodes/composite/nodes/node_composite_image.cc
index d071e9f13db..d75aa575395 100644
--- a/source/blender/nodes/composite/nodes/node_composite_image.cc
+++ b/source/blender/nodes/composite/nodes/node_composite_image.cc
@@ -12,6 +12,7 @@
 
 #include "BKE_context.h"
 #include "BKE_global.h"
+#include "BKE_image.h"
 #include "BKE_lib_id.h"
 #include "BKE_main.h"
 #include "BKE_scene.h"
@@ -19,6 +20,7 @@
 #include "DNA_scene_types.h"
 
 #include "RE_engine.h"
+#include "RE_pipeline.h"
 
 #include "RNA_access.h"
 
diff --git a/source/blender/nodes/composite/nodes/node_composite_map_value.cc b/source/blender/nodes/composite/nodes/node_composite_map_value.cc
index b069cce93fc..bb42628ed3d 100644
--- a/source/blender/nodes/composite/nodes/node_composite_map_value.cc
+++ b/source/blender/nodes/composite/nodes/node_composite_map_value.cc
@@ -5,6 +5,8 @@
  * \ingroup cmpnodes
  */
 
+#include "BKE_texture.h"
+
 #include "RNA_access.h"
 
 #include "UI_interface.h"
diff --git a/source/blender/nodes/composite/nodes/node_composite_moviedistortion.cc b/source/blender/nodes/composite/nodes/node_composite_moviedistortion.cc
index 9c6c6a40b2c..4d52a767b8a 100644
--- a/source/blender/nodes/composite/nodes/node_composite_moviedistortion.cc
+++ b/source/blender/nodes/composite/nodes/node_composite_moviedistortion.cc
@@ -7,6 +7,7 @@
 
 #include "BKE_context.h"
 #include "BKE_lib_id.h"
+#include "BKE_tracking.h"
 
 #include "UI_interface.h"
 #include "UI_resources.h"
diff --git a/source/blender/nodes/composite/nodes/node_composite_trackpos.cc b/source/blender/nodes/composite/nodes/node_composite_trackpos.cc
index 17a086f306f..3dc68b8ef0b 100644
--- a/source/blender/nodes/composite/nodes/node_composite_trackpos.cc
+++ b/source/blender/nodes/composite/nodes/node_composite_trackpos.cc
@@ -5,6 +5,8 @@
  * \ingroup cmpnodes
  */
 
+#include "BKE_tracking.h"
+
 #include "RNA_access.h"
 #include "RNA_prototypes.h"
 
diff --git a/source/blender/nodes/composite/nodes/node_composite_val_to_rgb.cc b/source/blender/nodes/composite/nodes/node_composite_val_to_rgb.cc
index f71028bf8c1..0dfdeda24e6 100644
--- a/source/blender/nodes/composite/nodes/node_composite_val_to_rgb.cc
+++ b/source/blender/nodes/composite/nodes/node_composite_val_to_rgb.cc
@@ -7,6 +7,8 @@
 
 #include "node_composite_util.hh"
 
+#include "BKE_colorband.h"
+
 /* **************** VALTORGB ******************** */
 
 namespace blender::nodes::node_composite_val_to_rgb_cc {
-- 
cgit v1.2.3


From 883d8ea16c36a0e1d56826e3bc4f072d6aa58618 Mon Sep 17 00:00:00 2001
From: Sebastian Parborg 
Date: Tue, 5 Jul 2022 16:31:13 +0200
Subject: Fix: Memleak in sequencer drag and drop code

---
 source/blender/editors/space_sequencer/sequencer_drag_drop.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/source/blender/editors/space_sequencer/sequencer_drag_drop.c b/source/blender/editors/space_sequencer/sequencer_drag_drop.c
index 94427009939..f6561cf07b9 100644
--- a/source/blender/editors/space_sequencer/sequencer_drag_drop.c
+++ b/source/blender/editors/space_sequencer/sequencer_drag_drop.c
@@ -179,6 +179,7 @@ static void sequencer_drop_copy(bContext *C, wmDrag *drag, wmDropBox *drop)
       if (max_channel != -1) {
         RNA_int_set(drop->ptr, "channel", max_channel);
       }
+      SEQ_collection_free(strips);
     }
   }
 }
-- 
cgit v1.2.3


From c52a18abf84b29ca19aa79ef1ce580e67a437779 Mon Sep 17 00:00:00 2001
From: Dalai Felinto 
Date: Tue, 5 Jul 2022 17:43:34 +0200
Subject: UI: Curves Sculpting - Remove duplicated entry for Curve Length

---
 release/scripts/startup/bl_ui/properties_paint_common.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/release/scripts/startup/bl_ui/properties_paint_common.py b/release/scripts/startup/bl_ui/properties_paint_common.py
index f0034a3d710..9b1cf11f6e7 100644
--- a/release/scripts/startup/bl_ui/properties_paint_common.py
+++ b/release/scripts/startup/bl_ui/properties_paint_common.py
@@ -782,7 +782,6 @@ def brush_settings(layout, context, brush, popover=False):
     elif mode == 'SCULPT_CURVES':
         if brush.curves_sculpt_tool == 'ADD':
             layout.prop(brush.curves_sculpt_settings, "add_amount")
-            layout.prop(brush.curves_sculpt_settings, "curve_length")
             col = layout.column(heading="Interpolate", align=True)
             col.prop(brush.curves_sculpt_settings, "interpolate_length", text="Length")
             col.prop(brush.curves_sculpt_settings, "interpolate_shape", text="Shape")
-- 
cgit v1.2.3


From 7688f0ace7a6c45aaa8304d2a26a760be0056aa6 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Tue, 5 Jul 2022 15:51:12 -0500
Subject: Curves: Move type conversion to the geometry module

This helps to separate concerns, and makes the functionality
available for edit mode.
---
 source/blender/geometry/CMakeLists.txt             |   2 +
 source/blender/geometry/GEO_set_curve_type.hh      |  40 ++
 source/blender/geometry/intern/set_curve_type.cc   | 711 +++++++++++++++++++++
 .../geometry/nodes/node_geo_curve_spline_type.cc   | 672 +------------------
 4 files changed, 764 insertions(+), 661 deletions(-)
 create mode 100644 source/blender/geometry/GEO_set_curve_type.hh
 create mode 100644 source/blender/geometry/intern/set_curve_type.cc

diff --git a/source/blender/geometry/CMakeLists.txt b/source/blender/geometry/CMakeLists.txt
index f0fb5c5c9af..21b2071d0e6 100644
--- a/source/blender/geometry/CMakeLists.txt
+++ b/source/blender/geometry/CMakeLists.txt
@@ -24,6 +24,7 @@ set(SRC
   intern/realize_instances.cc
   intern/resample_curves.cc
   intern/reverse_uv_sampler.cc
+  intern/set_curve_type.cc
   intern/uv_parametrizer.c
 
   GEO_add_curves_on_mesh.hh
@@ -35,6 +36,7 @@ set(SRC
   GEO_realize_instances.hh
   GEO_resample_curves.hh
   GEO_reverse_uv_sampler.hh
+  GEO_set_curve_type.hh
   GEO_uv_parametrizer.h
 )
 
diff --git a/source/blender/geometry/GEO_set_curve_type.hh b/source/blender/geometry/GEO_set_curve_type.hh
new file mode 100644
index 00000000000..f7ac8be5889
--- /dev/null
+++ b/source/blender/geometry/GEO_set_curve_type.hh
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#pragma once
+
+#include "DNA_curves_types.h"
+
+#include "BLI_function_ref.hh"
+#include "BLI_index_mask.hh"
+
+struct Curves;
+struct CurveComponent;
+
+namespace blender::bke {
+class CurvesGeometry;
+}
+
+namespace blender::geometry {
+
+/**
+ * Try the conversion to the #dst_type-- avoiding the majority of the work done in
+ * #convert_curves by modifying an existing object in place rather than creating a new one.
+ *
+ * \note This function is necessary because attributes do not have proper support for CoW.
+ *
+ * \param get_writable_curves_fn: Should return the write-able curves to change directly if
+ * possible. This is a function in order to avoid the cost of retrieval when unnecessary.
+ */
+bool try_curves_conversion_in_place(IndexMask selection,
+                                    CurveType dst_type,
+                                    FunctionRef get_writable_curves_fn);
+
+/**
+ * Change the types of the selected curves, potentially changing the total point count.
+ */
+Curves *convert_curves(const CurveComponent &src_component,
+                       const bke::CurvesGeometry &src_curves,
+                       IndexMask selection,
+                       CurveType dst_type);
+
+}  // namespace blender::geometry
diff --git a/source/blender/geometry/intern/set_curve_type.cc b/source/blender/geometry/intern/set_curve_type.cc
new file mode 100644
index 00000000000..d7a5bc9b27d
--- /dev/null
+++ b/source/blender/geometry/intern/set_curve_type.cc
@@ -0,0 +1,711 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "BKE_attribute_math.hh"
+#include "BKE_curves.hh"
+#include "BKE_curves_utils.hh"
+#include "BKE_geometry_set.hh"
+
+#include "BLI_task.hh"
+
+#include "GEO_set_curve_type.hh"
+
+namespace blender::geometry {
+
+/**
+ * This function answers the question about possible conversion method for NURBS-to-Bezier. In
+ * general for 3rd degree NURBS curves there is one-to-one relation with 3rd degree Bezier curves
+ * that can be exploit for conversion - Bezier handles sit on NURBS hull segments and in the middle
+ * between those handles are Bezier anchor points.
+ */
+static bool is_nurbs_to_bezier_one_to_one(const KnotsMode knots_mode)
+{
+  if (ELEM(knots_mode, NURBS_KNOT_MODE_NORMAL, NURBS_KNOT_MODE_ENDPOINT)) {
+    return true;
+  }
+  return false;
+}
+
+/**
+ * As an optimization, just change the types on a mutable curves data-block when the conversion is
+ * simple. This could be expanded to more cases where the number of points doesn't change in the
+ * future, though that might require properly initializing some attributes, or removing others.
+ */
+static bool conversion_can_change_point_num(const CurveType dst_type)
+{
+  if (ELEM(dst_type, CURVE_TYPE_CATMULL_ROM, CURVE_TYPE_POLY)) {
+    /* The conversion to Catmull Rom or Poly should never change the number of points, no matter
+     * the source type (Bezier to Catmull Rom conversion cannot maintain the same shape anyway). */
+    return false;
+  }
+  return true;
+}
+
+template
+static void scale_input_assign(const Span src,
+                               const int scale,
+                               const int offset,
+                               MutableSpan dst)
+{
+  for (const int i : dst.index_range()) {
+    dst[i] = src[i * scale + offset];
+  }
+}
+
+/**
+ * The Bezier control point and its handles become three control points on the NURBS curve,
+ * so each attribute value is duplicated three times.
+ */
+template static void bezier_generic_to_nurbs(const Span src, MutableSpan dst)
+{
+  for (const int i : src.index_range()) {
+    dst[i * 3] = src[i];
+    dst[i * 3 + 1] = src[i];
+    dst[i * 3 + 2] = src[i];
+  }
+}
+
+static void bezier_generic_to_nurbs(const GSpan src, GMutableSpan dst)
+{
+  attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
+    using T = decltype(dummy);
+    bezier_generic_to_nurbs(src.typed(), dst.typed());
+  });
+}
+
+static void bezier_positions_to_nurbs(const Span src_positions,
+                                      const Span src_handles_l,
+                                      const Span src_handles_r,
+                                      MutableSpan dst_positions)
+{
+  for (const int i : src_positions.index_range()) {
+    dst_positions[i * 3] = src_handles_l[i];
+    dst_positions[i * 3 + 1] = src_positions[i];
+    dst_positions[i * 3 + 2] = src_handles_r[i];
+  }
+}
+
+static void catmull_rom_to_bezier_handles(const Span src_positions,
+                                          const bool cyclic,
+                                          MutableSpan dst_handles_l,
+                                          MutableSpan dst_handles_r)
+{
+  /* Catmull Rom curves are the same as Bezier curves with automatically defined handle positions.
+   * This constant defines the portion of the distance between the next/previous points to use for
+   * the length of the handles. */
+  constexpr float handle_scale = 1.0f / 6.0f;
+
+  if (src_positions.size() == 1) {
+    dst_handles_l.first() = src_positions.first();
+    dst_handles_r.first() = src_positions.first();
+    return;
+  }
+
+  const float3 first_offset = cyclic ? src_positions[1] - src_positions.last() :
+                                       src_positions[1] - src_positions[0];
+  dst_handles_r.first() = src_positions.first() + first_offset * handle_scale;
+  dst_handles_l.first() = src_positions.first() - first_offset * handle_scale;
+
+  const float3 last_offset = cyclic ? src_positions.first() - src_positions.last(1) :
+                                      src_positions.last() - src_positions.last(1);
+  dst_handles_l.last() = src_positions.last() - last_offset * handle_scale;
+  dst_handles_r.last() = src_positions.last() + last_offset * handle_scale;
+
+  for (const int i : src_positions.index_range().drop_front(1).drop_back(1)) {
+    const float3 left_offset = src_positions[i - 1] - src_positions[i + 1];
+    dst_handles_l[i] = src_positions[i] + left_offset * handle_scale;
+
+    const float3 right_offset = src_positions[i + 1] - src_positions[i - 1];
+    dst_handles_r[i] = src_positions[i] + right_offset * handle_scale;
+  }
+}
+
+static void catmull_rom_to_nurbs_positions(const Span src_positions,
+                                           const bool cyclic,
+                                           MutableSpan dst_positions)
+{
+  /* Convert the Catmull Rom position data to Bezier handles in order to reuse the Bezier to
+   * NURBS positions assignment. If this becomes a bottleneck, this step could be avoided. */
+  Array bezier_handles_l(src_positions.size());
+  Array bezier_handles_r(src_positions.size());
+  catmull_rom_to_bezier_handles(src_positions, cyclic, bezier_handles_l, bezier_handles_r);
+  bezier_positions_to_nurbs(src_positions, bezier_handles_l, bezier_handles_r, dst_positions);
+}
+
+template
+static void nurbs_to_bezier_assign(const Span src,
+                                   const MutableSpan dst,
+                                   const KnotsMode knots_mode)
+{
+  switch (knots_mode) {
+    case NURBS_KNOT_MODE_NORMAL:
+      for (const int i : dst.index_range()) {
+        dst[i] = src[(i + 1) % src.size()];
+      }
+      break;
+    case NURBS_KNOT_MODE_ENDPOINT:
+      for (const int i : dst.index_range().drop_back(1).drop_front(1)) {
+        dst[i] = src[i + 1];
+      }
+      dst.first() = src.first();
+      dst.last() = src.last();
+      break;
+    default:
+      /* Every 3rd NURBS position (starting from index 1) should have its attributes transferred.
+       */
+      scale_input_assign(src, 3, 1, dst);
+  }
+}
+
+static void nurbs_to_bezier_assign(const GSpan src, const KnotsMode knots_mode, GMutableSpan dst)
+{
+  attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
+    using T = decltype(dummy);
+    nurbs_to_bezier_assign(src.typed(), dst.typed(), knots_mode);
+  });
+}
+
+static Vector create_nurbs_to_bezier_handles(const Span nurbs_positions,
+                                                     const KnotsMode knots_mode)
+{
+  const int nurbs_positions_num = nurbs_positions.size();
+  Vector handle_positions;
+
+  if (is_nurbs_to_bezier_one_to_one(knots_mode)) {
+    const bool is_periodic = knots_mode == NURBS_KNOT_MODE_NORMAL;
+    if (is_periodic) {
+      handle_positions.append(nurbs_positions[1] +
+                              ((nurbs_positions[0] - nurbs_positions[1]) / 3));
+    }
+    else {
+      handle_positions.append(2 * nurbs_positions[0] - nurbs_positions[1]);
+      handle_positions.append(nurbs_positions[1]);
+    }
+
+    /* Place Bezier handles on interior NURBS hull segments. Those handles can be either placed on
+     * endpoints, midpoints or 1/3 of the distance of a hull segment. */
+    const int segments_num = nurbs_positions_num - 1;
+    const bool ignore_interior_segment = segments_num == 3 && is_periodic == false;
+    if (ignore_interior_segment == false) {
+      const float mid_offset = (float)(segments_num - 1) / 2.0f;
+      for (const int i : IndexRange(1, segments_num - 2)) {
+        /* Divisor can have values: 1, 2 or 3. */
+        const int divisor = is_periodic ?
+                                3 :
+                                std::min(3, (int)(-std::abs(i - mid_offset) + mid_offset + 1.0f));
+        const float3 &p1 = nurbs_positions[i];
+        const float3 &p2 = nurbs_positions[i + 1];
+        const float3 displacement = (p2 - p1) / divisor;
+        const int num_handles_on_segment = divisor < 3 ? 1 : 2;
+        for (int j : IndexRange(1, num_handles_on_segment)) {
+          handle_positions.append(p1 + (displacement * j));
+        }
+      }
+    }
+
+    const int last_index = nurbs_positions_num - 1;
+    if (is_periodic) {
+      handle_positions.append(
+          nurbs_positions[last_index - 1] +
+          ((nurbs_positions[last_index] - nurbs_positions[last_index - 1]) / 3));
+    }
+    else {
+      handle_positions.append(nurbs_positions[last_index - 1]);
+      handle_positions.append(2 * nurbs_positions[last_index] - nurbs_positions[last_index - 1]);
+    }
+  }
+  else {
+    for (const int i : IndexRange(nurbs_positions_num)) {
+      if (i % 3 == 1) {
+        continue;
+      }
+      handle_positions.append(nurbs_positions[i]);
+    }
+    if (nurbs_positions_num % 3 == 1) {
+      handle_positions.pop_last();
+    }
+    else if (nurbs_positions_num % 3 == 2) {
+      const int last_index = nurbs_positions_num - 1;
+      handle_positions.append(2 * nurbs_positions[last_index] - nurbs_positions[last_index - 1]);
+    }
+  }
+
+  return handle_positions;
+}
+
+static void create_nurbs_to_bezier_positions(const Span nurbs_positions,
+                                             const Span handle_positions,
+                                             const KnotsMode knots_mode,
+                                             MutableSpan bezier_positions)
+{
+  if (is_nurbs_to_bezier_one_to_one(knots_mode)) {
+    for (const int i : bezier_positions.index_range()) {
+      bezier_positions[i] = math::interpolate(
+          handle_positions[i * 2], handle_positions[i * 2 + 1], 0.5f);
+    }
+  }
+  else {
+    /* Every 3rd NURBS position (starting from index 1) should be converted to Bezier position. */
+    scale_input_assign(nurbs_positions, 3, 1, bezier_positions);
+  }
+}
+
+static int to_bezier_size(const CurveType src_type,
+                          const bool cyclic,
+                          const KnotsMode knots_mode,
+                          const int src_size)
+{
+  switch (src_type) {
+    case CURVE_TYPE_NURBS: {
+      if (is_nurbs_to_bezier_one_to_one(knots_mode)) {
+        return cyclic ? src_size : src_size - 2;
+      }
+      return (src_size + 1) / 3;
+    }
+    default:
+      return src_size;
+  }
+}
+
+static int to_nurbs_size(const CurveType src_type, const int src_size)
+{
+  switch (src_type) {
+    case CURVE_TYPE_BEZIER:
+    case CURVE_TYPE_CATMULL_ROM:
+      return src_size * 3;
+    default:
+      return src_size;
+  }
+}
+
+static void retrieve_curve_sizes(const bke::CurvesGeometry &curves, MutableSpan sizes)
+{
+  threading::parallel_for(curves.curves_range(), 4096, [&](IndexRange range) {
+    for (const int i : range) {
+      sizes[i] = curves.points_for_curve(i).size();
+    }
+  });
+}
+
+struct GenericAttributes : NonCopyable, NonMovable {
+  Vector src;
+  Vector dst;
+
+  Vector attributes;
+};
+
+static void retrieve_generic_point_attributes(const CurveComponent &src_component,
+                                              CurveComponent &dst_component,
+                                              GenericAttributes &attributes)
+{
+  src_component.attribute_foreach(
+      [&](const bke::AttributeIDRef &id, const AttributeMetaData meta_data) {
+        if (meta_data.domain != ATTR_DOMAIN_POINT) {
+          /* Curve domain attributes are all copied directly to the result in one step. */
+          return true;
+        }
+        if (src_component.attribute_is_builtin(id)) {
+          if (!(id.is_named() && ELEM(id, "tilt", "radius"))) {
+            return true;
+          }
+        }
+
+        GVArray src_attribute = src_component.attribute_try_get_for_read(id, ATTR_DOMAIN_POINT);
+        BLI_assert(src_attribute);
+        attributes.src.append(src_attribute.get_internal_span());
+
+        bke::OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+            id, ATTR_DOMAIN_POINT, meta_data.data_type);
+        attributes.dst.append(dst_attribute.as_span());
+        attributes.attributes.append(std::move(dst_attribute));
+
+        return true;
+      });
+}
+
+static Curves *create_result_curves(const bke::CurvesGeometry &src_curves,
+                                    const IndexMask selection,
+                                    const CurveType dst_type)
+{
+  Curves *dst_curves_id = bke::curves_new_nomain(0, src_curves.curves_num());
+  bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry);
+  CurveComponent dst_component;
+  dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable);
+  /* Directly copy curve attributes, since they stay the same (except for curve types). */
+  CustomData_copy(&src_curves.curve_data,
+                  &dst_curves.curve_data,
+                  CD_MASK_ALL,
+                  CD_DUPLICATE,
+                  src_curves.curves_num());
+
+  dst_curves.fill_curve_types(selection, dst_type);
+
+  return dst_curves_id;
+}
+
+static Curves *convert_curves_to_bezier(const CurveComponent &src_component,
+                                        const bke::CurvesGeometry &src_curves,
+                                        const IndexMask selection)
+{
+  const VArray src_knot_modes = src_curves.nurbs_knots_modes();
+  const VArray src_types = src_curves.curve_types();
+  const VArray src_cyclic = src_curves.cyclic();
+  const Span src_positions = src_curves.positions();
+
+  Curves *dst_curves_id = create_result_curves(src_curves, selection, CURVE_TYPE_BEZIER);
+  bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry);
+  CurveComponent dst_component;
+  dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable);
+
+  MutableSpan dst_offsets = dst_curves.offsets_for_write();
+  retrieve_curve_sizes(src_curves, dst_curves.offsets_for_write());
+  threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) {
+    for (const int i : selection.slice(range)) {
+      dst_offsets[i] = to_bezier_size(
+          CurveType(src_types[i]), src_cyclic[i], KnotsMode(src_knot_modes[i]), dst_offsets[i]);
+    }
+  });
+  bke::curves::accumulate_counts_to_offsets(dst_offsets);
+  dst_curves.resize(dst_offsets.last(), dst_curves.curves_num());
+
+  GenericAttributes attributes;
+  retrieve_generic_point_attributes(src_component, dst_component, attributes);
+
+  MutableSpan dst_positions = dst_curves.positions_for_write();
+  MutableSpan dst_handles_l = dst_curves.handle_positions_left_for_write();
+  MutableSpan dst_handles_r = dst_curves.handle_positions_right_for_write();
+  MutableSpan dst_types_l = dst_curves.handle_types_left_for_write();
+  MutableSpan dst_types_r = dst_curves.handle_types_right_for_write();
+  MutableSpan dst_weights = dst_curves.nurbs_weights_for_write();
+
+  auto catmull_rom_to_bezier = [&](IndexMask selection) {
+    bke::curves::fill_points(dst_curves, selection, BEZIER_HANDLE_ALIGN, dst_types_l);
+    bke::curves::fill_points(dst_curves, selection, BEZIER_HANDLE_ALIGN, dst_types_r);
+    bke::curves::copy_point_data(src_curves, dst_curves, selection, src_positions, dst_positions);
+
+    threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) {
+      for (const int i : selection.slice(range)) {
+        const IndexRange src_points = src_curves.points_for_curve(i);
+        const IndexRange dst_points = dst_curves.points_for_curve(i);
+        catmull_rom_to_bezier_handles(src_positions.slice(src_points),
+                                      src_cyclic[i],
+                                      dst_handles_l.slice(dst_points),
+                                      dst_handles_r.slice(dst_points));
+      }
+    });
+
+    for (const int i : attributes.src.index_range()) {
+      bke::curves::copy_point_data(
+          src_curves, dst_curves, selection, attributes.src[i], attributes.dst[i]);
+    }
+  };
+
+  auto poly_to_bezier = [&](IndexMask selection) {
+    bke::curves::copy_point_data(src_curves, dst_curves, selection, src_positions, dst_positions);
+    bke::curves::fill_points(dst_curves, selection, BEZIER_HANDLE_VECTOR, dst_types_l);
+    bke::curves::fill_points(dst_curves, selection, BEZIER_HANDLE_VECTOR, dst_types_r);
+    dst_curves.calculate_bezier_auto_handles();
+    for (const int i : attributes.src.index_range()) {
+      bke::curves::copy_point_data(
+          src_curves, dst_curves, selection, attributes.src[i], attributes.dst[i]);
+    }
+  };
+
+  auto bezier_to_bezier = [&](IndexMask selection) {
+    const VArraySpan src_types_l = src_curves.handle_types_left();
+    const VArraySpan src_types_r = src_curves.handle_types_right();
+    const Span src_handles_l = src_curves.handle_positions_left();
+    const Span src_handles_r = src_curves.handle_positions_right();
+
+    bke::curves::copy_point_data(src_curves, dst_curves, selection, src_positions, dst_positions);
+    bke::curves::copy_point_data(src_curves, dst_curves, selection, src_handles_l, dst_handles_l);
+    bke::curves::copy_point_data(src_curves, dst_curves, selection, src_handles_r, dst_handles_r);
+    bke::curves::copy_point_data(src_curves, dst_curves, selection, src_types_l, dst_types_l);
+    bke::curves::copy_point_data(src_curves, dst_curves, selection, src_types_r, dst_types_r);
+
+    dst_curves.calculate_bezier_auto_handles();
+
+    for (const int i : attributes.src.index_range()) {
+      bke::curves::copy_point_data(
+          src_curves, dst_curves, selection, attributes.src[i], attributes.dst[i]);
+    }
+  };
+
+  auto nurbs_to_bezier = [&](IndexMask selection) {
+    bke::curves::fill_points(dst_curves, selection, BEZIER_HANDLE_ALIGN, dst_types_l);
+    bke::curves::fill_points(dst_curves, selection, BEZIER_HANDLE_ALIGN, dst_types_r);
+    bke::curves::fill_points(dst_curves, selection, 0.0f, dst_weights);
+
+    threading::parallel_for(selection.index_range(), 64, [&](IndexRange range) {
+      for (const int i : selection.slice(range)) {
+        const IndexRange src_points = src_curves.points_for_curve(i);
+        const IndexRange dst_points = dst_curves.points_for_curve(i);
+        const Span src_curve_positions = src_positions.slice(src_points);
+
+        KnotsMode knots_mode = KnotsMode(src_knot_modes[i]);
+        Span nurbs_positions = src_curve_positions;
+        Vector nurbs_positions_vector;
+        if (src_cyclic[i] && is_nurbs_to_bezier_one_to_one(knots_mode)) {
+          /* For conversion treat this as periodic closed curve. Extend NURBS hull to first and
+           * second point which will act as a skeleton for placing Bezier handles. */
+          nurbs_positions_vector.extend(src_curve_positions);
+          nurbs_positions_vector.append(src_curve_positions[0]);
+          nurbs_positions_vector.append(src_curve_positions[1]);
+          nurbs_positions = nurbs_positions_vector;
+          knots_mode = NURBS_KNOT_MODE_NORMAL;
+        }
+
+        const Vector handle_positions = create_nurbs_to_bezier_handles(nurbs_positions,
+                                                                               knots_mode);
+
+        scale_input_assign(handle_positions.as_span(), 2, 0, dst_handles_l.slice(dst_points));
+        scale_input_assign(handle_positions.as_span(), 2, 1, dst_handles_r.slice(dst_points));
+
+        create_nurbs_to_bezier_positions(
+            nurbs_positions, handle_positions, knots_mode, dst_positions.slice(dst_points));
+      }
+    });
+
+    for (const int i_attribute : attributes.src.index_range()) {
+      threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) {
+        for (const int i : selection.slice(range)) {
+          const IndexRange src_points = src_curves.points_for_curve(i);
+          const IndexRange dst_points = dst_curves.points_for_curve(i);
+          nurbs_to_bezier_assign(attributes.src[i_attribute].slice(src_points),
+                                 KnotsMode(src_knot_modes[i]),
+                                 attributes.dst[i_attribute].slice(dst_points));
+        }
+      });
+    }
+  };
+
+  bke::curves::foreach_curve_by_type(src_curves.curve_types(),
+                                     src_curves.curve_type_counts(),
+                                     selection,
+                                     catmull_rom_to_bezier,
+                                     poly_to_bezier,
+                                     bezier_to_bezier,
+                                     nurbs_to_bezier);
+
+  const Vector unselected_ranges = selection.extract_ranges_invert(
+      src_curves.curves_range());
+
+  for (const int i : attributes.src.index_range()) {
+    bke::curves::copy_point_data(
+        src_curves, dst_curves, unselected_ranges, attributes.src[i], attributes.dst[i]);
+  }
+
+  for (bke::OutputAttribute &attribute : attributes.attributes) {
+    attribute.save();
+  }
+
+  return dst_curves_id;
+}
+
+static Curves *convert_curves_to_nurbs(const CurveComponent &src_component,
+                                       const bke::CurvesGeometry &src_curves,
+                                       const IndexMask selection)
+{
+  const VArray src_types = src_curves.curve_types();
+  const VArray src_cyclic = src_curves.cyclic();
+  const Span src_positions = src_curves.positions();
+
+  Curves *dst_curves_id = create_result_curves(src_curves, selection, CURVE_TYPE_NURBS);
+  bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry);
+  CurveComponent dst_component;
+  dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable);
+
+  MutableSpan dst_offsets = dst_curves.offsets_for_write();
+  retrieve_curve_sizes(src_curves, dst_curves.offsets_for_write());
+  threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) {
+    for (const int i : selection.slice(range)) {
+      dst_offsets[i] = to_nurbs_size(CurveType(src_types[i]), dst_offsets[i]);
+    }
+  });
+  bke::curves::accumulate_counts_to_offsets(dst_offsets);
+  dst_curves.resize(dst_offsets.last(), dst_curves.curves_num());
+
+  GenericAttributes attributes;
+  retrieve_generic_point_attributes(src_component, dst_component, attributes);
+
+  MutableSpan dst_positions = dst_curves.positions_for_write();
+
+  auto fill_weights_if_necessary = [&](const IndexMask selection) {
+    if (!src_curves.nurbs_weights().is_empty()) {
+      bke::curves::fill_points(dst_curves, selection, 1.0f, dst_curves.nurbs_weights_for_write());
+    }
+  };
+
+  auto catmull_rom_to_nurbs = [&](IndexMask selection) {
+    dst_curves.nurbs_orders_for_write().fill_indices(selection, 4);
+    dst_curves.nurbs_knots_modes_for_write().fill_indices(selection, NURBS_KNOT_MODE_BEZIER);
+    fill_weights_if_necessary(selection);
+
+    threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) {
+      for (const int i : selection.slice(range)) {
+        const IndexRange src_points = src_curves.points_for_curve(i);
+        const IndexRange dst_points = dst_curves.points_for_curve(i);
+        catmull_rom_to_nurbs_positions(
+            src_positions.slice(src_points), src_cyclic[i], dst_positions.slice(dst_points));
+      }
+    });
+
+    for (const int i_attribute : attributes.src.index_range()) {
+      threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) {
+        for (const int i : selection.slice(range)) {
+          const IndexRange src_points = src_curves.points_for_curve(i);
+          const IndexRange dst_points = dst_curves.points_for_curve(i);
+          bezier_generic_to_nurbs(attributes.src[i_attribute].slice(src_points),
+                                  attributes.dst[i_attribute].slice(dst_points));
+        }
+      });
+    }
+  };
+
+  auto poly_to_nurbs = [&](IndexMask selection) {
+    dst_curves.nurbs_orders_for_write().fill_indices(selection, 4);
+    bke::curves::copy_point_data(src_curves, dst_curves, selection, src_positions, dst_positions);
+    fill_weights_if_necessary(selection);
+
+    /* Avoid using "Endpoint" knots modes for cyclic curves, since it adds a sharp point at the
+     * start/end. */
+    if (src_cyclic.is_single()) {
+      dst_curves.nurbs_knots_modes_for_write().fill_indices(
+          selection,
+          src_cyclic.get_internal_single() ? NURBS_KNOT_MODE_NORMAL : NURBS_KNOT_MODE_ENDPOINT);
+    }
+    else {
+      VArraySpan cyclic{src_cyclic};
+      MutableSpan knots_modes = dst_curves.nurbs_knots_modes_for_write();
+      threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) {
+        for (const int i : selection.slice(range)) {
+          knots_modes[i] = cyclic[i] ? NURBS_KNOT_MODE_NORMAL : NURBS_KNOT_MODE_ENDPOINT;
+        }
+      });
+    }
+
+    for (const int i_attribute : attributes.src.index_range()) {
+      bke::curves::copy_point_data(src_curves,
+                                   dst_curves,
+                                   selection,
+                                   attributes.src[i_attribute],
+                                   attributes.dst[i_attribute]);
+    }
+  };
+
+  auto bezier_to_nurbs = [&](IndexMask selection) {
+    const Span src_handles_l = src_curves.handle_positions_left();
+    const Span src_handles_r = src_curves.handle_positions_right();
+
+    dst_curves.nurbs_orders_for_write().fill_indices(selection, 4);
+    dst_curves.nurbs_knots_modes_for_write().fill_indices(selection, NURBS_KNOT_MODE_BEZIER);
+    fill_weights_if_necessary(selection);
+
+    threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) {
+      for (const int i : selection.slice(range)) {
+        const IndexRange src_points = src_curves.points_for_curve(i);
+        const IndexRange dst_points = dst_curves.points_for_curve(i);
+        bezier_positions_to_nurbs(src_positions.slice(src_points),
+                                  src_handles_l.slice(src_points),
+                                  src_handles_r.slice(src_points),
+                                  dst_positions.slice(dst_points));
+      }
+    });
+
+    for (const int i_attribute : attributes.src.index_range()) {
+      threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) {
+        for (const int i : selection.slice(range)) {
+          const IndexRange src_points = src_curves.points_for_curve(i);
+          const IndexRange dst_points = dst_curves.points_for_curve(i);
+          bezier_generic_to_nurbs(attributes.src[i_attribute].slice(src_points),
+                                  attributes.dst[i_attribute].slice(dst_points));
+        }
+      });
+    }
+  };
+
+  auto nurbs_to_nurbs = [&](IndexMask selection) {
+    bke::curves::copy_point_data(src_curves, dst_curves, selection, src_positions, dst_positions);
+
+    if (!src_curves.nurbs_weights().is_empty()) {
+      bke::curves::copy_point_data(src_curves,
+                                   dst_curves,
+                                   selection,
+                                   src_curves.nurbs_weights(),
+                                   dst_curves.nurbs_weights_for_write());
+    }
+
+    for (const int i_attribute : attributes.src.index_range()) {
+      bke::curves::copy_point_data(src_curves,
+                                   dst_curves,
+                                   selection,
+                                   attributes.src[i_attribute],
+                                   attributes.dst[i_attribute]);
+    }
+  };
+
+  bke::curves::foreach_curve_by_type(src_curves.curve_types(),
+                                     src_curves.curve_type_counts(),
+                                     selection,
+                                     catmull_rom_to_nurbs,
+                                     poly_to_nurbs,
+                                     bezier_to_nurbs,
+                                     nurbs_to_nurbs);
+
+  const Vector unselected_ranges = selection.extract_ranges_invert(
+      src_curves.curves_range());
+
+  for (const int i : attributes.src.index_range()) {
+    bke::curves::copy_point_data(
+        src_curves, dst_curves, unselected_ranges, attributes.src[i], attributes.dst[i]);
+  }
+
+  for (bke::OutputAttribute &attribute : attributes.attributes) {
+    attribute.save();
+  }
+
+  return dst_curves_id;
+}
+
+static bke::CurvesGeometry convert_curves_trivial(const bke::CurvesGeometry &src_curves,
+                                                  const IndexMask selection,
+                                                  const CurveType dst_type)
+{
+  bke::CurvesGeometry dst_curves(src_curves);
+  dst_curves.fill_curve_types(selection, dst_type);
+  dst_curves.remove_attributes_based_on_types();
+  return dst_curves;
+}
+
+Curves *convert_curves(const CurveComponent &src_component,
+                       const bke::CurvesGeometry &src_curves,
+                       const IndexMask selection,
+                       const CurveType dst_type)
+{
+  switch (dst_type) {
+    case CURVE_TYPE_CATMULL_ROM:
+    case CURVE_TYPE_POLY:
+      return bke::curves_new_nomain(convert_curves_trivial(src_curves, selection, dst_type));
+    case CURVE_TYPE_BEZIER:
+      return convert_curves_to_bezier(src_component, src_curves, selection);
+    case CURVE_TYPE_NURBS:
+      return convert_curves_to_nurbs(src_component, src_curves, selection);
+  }
+  BLI_assert_unreachable();
+  return nullptr;
+}
+
+bool try_curves_conversion_in_place(const IndexMask selection,
+                                    const CurveType dst_type,
+                                    FunctionRef get_writable_curves_fn)
+{
+  if (conversion_can_change_point_num(dst_type)) {
+    return false;
+  }
+  Curves &curves_id = get_writable_curves_fn();
+  bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry);
+  curves.fill_curve_types(selection, dst_type);
+  curves.remove_attributes_based_on_types();
+  return true;
+}
+
+}  // namespace blender::geometry
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc
index 5c836391abe..183c98e9c9f 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc
@@ -1,16 +1,12 @@
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 
-#include 
-
-#include "BKE_attribute_math.hh"
 #include "BKE_curves.hh"
-#include "BKE_curves_utils.hh"
-
-#include "BLI_task.hh"
 
 #include "UI_interface.h"
 #include "UI_resources.h"
 
+#include "GEO_set_curve_type.hh"
+
 #include "node_geometry_util.hh"
 
 namespace blender::nodes::node_geo_curve_spline_type_cc {
@@ -37,625 +33,6 @@ static void node_init(bNodeTree *UNUSED(tree), bNode *node)
   node->storage = data;
 }
 
-/**
- * This function answers the question about possible conversion method for NURBS-to-Bezier. In
- * general for 3rd degree NURBS curves there is one-to-one relation with 3rd degree Bezier curves
- * that can be exploit for conversion - Bezier handles sit on NURBS hull segments and in the middle
- * between those handles are Bezier anchor points.
- */
-static bool is_nurbs_to_bezier_one_to_one(const KnotsMode knots_mode)
-{
-  if (ELEM(knots_mode, NURBS_KNOT_MODE_NORMAL, NURBS_KNOT_MODE_ENDPOINT)) {
-    return true;
-  }
-  return false;
-}
-
-/**
- * As an optimization, just change the types on a mutable curves data-block when the conversion is
- * simple. This could be expanded to more cases where the number of points doesn't change in the
- * future, though that might require properly initializing some attributes, or removing others.
- */
-static bool conversion_can_change_point_num(const CurveType dst_type)
-{
-  if (ELEM(dst_type, CURVE_TYPE_CATMULL_ROM, CURVE_TYPE_POLY)) {
-    /* The conversion to Catmull Rom or Poly should never change the number of points, no matter
-     * the source type (Bezier to Catmull Rom conversion cannot maintain the same shape anyway). */
-    return false;
-  }
-  return true;
-}
-
-template
-static void scale_input_assign(const Span src,
-                               const int scale,
-                               const int offset,
-                               MutableSpan dst)
-{
-  for (const int i : dst.index_range()) {
-    dst[i] = src[i * scale + offset];
-  }
-}
-
-/**
- * The Bezier control point and its handles become three control points on the NURBS curve,
- * so each attribute value is duplicated three times.
- */
-template static void bezier_generic_to_nurbs(const Span src, MutableSpan dst)
-{
-  for (const int i : src.index_range()) {
-    dst[i * 3] = src[i];
-    dst[i * 3 + 1] = src[i];
-    dst[i * 3 + 2] = src[i];
-  }
-}
-
-static void bezier_generic_to_nurbs(const GSpan src, GMutableSpan dst)
-{
-  attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
-    using T = decltype(dummy);
-    bezier_generic_to_nurbs(src.typed(), dst.typed());
-  });
-}
-
-static void bezier_positions_to_nurbs(const Span src_positions,
-                                      const Span src_handles_l,
-                                      const Span src_handles_r,
-                                      MutableSpan dst_positions)
-{
-  for (const int i : src_positions.index_range()) {
-    dst_positions[i * 3] = src_handles_l[i];
-    dst_positions[i * 3 + 1] = src_positions[i];
-    dst_positions[i * 3 + 2] = src_handles_r[i];
-  }
-}
-
-static void catmull_rom_to_bezier_handles(const Span src_positions,
-                                          const bool cyclic,
-                                          MutableSpan dst_handles_l,
-                                          MutableSpan dst_handles_r)
-{
-  /* Catmull Rom curves are the same as Bezier curves with automatically defined handle positions.
-   * This constant defines the portion of the distance between the next/previous points to use for
-   * the length of the handles. */
-  constexpr float handle_scale = 1.0f / 6.0f;
-
-  if (src_positions.size() == 1) {
-    dst_handles_l.first() = src_positions.first();
-    dst_handles_r.first() = src_positions.first();
-    return;
-  }
-
-  const float3 first_offset = cyclic ? src_positions[1] - src_positions.last() :
-                                       src_positions[1] - src_positions[0];
-  dst_handles_r.first() = src_positions.first() + first_offset * handle_scale;
-  dst_handles_l.first() = src_positions.first() - first_offset * handle_scale;
-
-  const float3 last_offset = cyclic ? src_positions.first() - src_positions.last(1) :
-                                      src_positions.last() - src_positions.last(1);
-  dst_handles_l.last() = src_positions.last() - last_offset * handle_scale;
-  dst_handles_r.last() = src_positions.last() + last_offset * handle_scale;
-
-  for (const int i : src_positions.index_range().drop_front(1).drop_back(1)) {
-    const float3 left_offset = src_positions[i - 1] - src_positions[i + 1];
-    dst_handles_l[i] = src_positions[i] + left_offset * handle_scale;
-
-    const float3 right_offset = src_positions[i + 1] - src_positions[i - 1];
-    dst_handles_r[i] = src_positions[i] + right_offset * handle_scale;
-  }
-}
-
-static void catmull_rom_to_nurbs_positions(const Span src_positions,
-                                           const bool cyclic,
-                                           MutableSpan dst_positions)
-{
-  /* Convert the Catmull Rom position data to Bezier handles in order to reuse the Bezier to
-   * NURBS positions assignment. If this becomes a bottleneck, this step could be avoided. */
-  Array bezier_handles_l(src_positions.size());
-  Array bezier_handles_r(src_positions.size());
-  catmull_rom_to_bezier_handles(src_positions, cyclic, bezier_handles_l, bezier_handles_r);
-  bezier_positions_to_nurbs(src_positions, bezier_handles_l, bezier_handles_r, dst_positions);
-}
-
-template
-static void nurbs_to_bezier_assign(const Span src,
-                                   const MutableSpan dst,
-                                   const KnotsMode knots_mode)
-{
-  switch (knots_mode) {
-    case NURBS_KNOT_MODE_NORMAL:
-      for (const int i : dst.index_range()) {
-        dst[i] = src[(i + 1) % src.size()];
-      }
-      break;
-    case NURBS_KNOT_MODE_ENDPOINT:
-      for (const int i : dst.index_range().drop_back(1).drop_front(1)) {
-        dst[i] = src[i + 1];
-      }
-      dst.first() = src.first();
-      dst.last() = src.last();
-      break;
-    default:
-      /* Every 3rd NURBS position (starting from index 1) should have its attributes transferred.
-       */
-      scale_input_assign(src, 3, 1, dst);
-  }
-}
-
-static void nurbs_to_bezier_assign(const GSpan src, const KnotsMode knots_mode, GMutableSpan dst)
-{
-  attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
-    using T = decltype(dummy);
-    nurbs_to_bezier_assign(src.typed(), dst.typed(), knots_mode);
-  });
-}
-
-static Vector create_nurbs_to_bezier_handles(const Span nurbs_positions,
-                                                     const KnotsMode knots_mode)
-{
-  const int nurbs_positions_num = nurbs_positions.size();
-  Vector handle_positions;
-
-  if (is_nurbs_to_bezier_one_to_one(knots_mode)) {
-    const bool is_periodic = knots_mode == NURBS_KNOT_MODE_NORMAL;
-    if (is_periodic) {
-      handle_positions.append(nurbs_positions[1] +
-                              ((nurbs_positions[0] - nurbs_positions[1]) / 3));
-    }
-    else {
-      handle_positions.append(2 * nurbs_positions[0] - nurbs_positions[1]);
-      handle_positions.append(nurbs_positions[1]);
-    }
-
-    /* Place Bezier handles on interior NURBS hull segments. Those handles can be either placed on
-     * endpoints, midpoints or 1/3 of the distance of a hull segment. */
-    const int segments_num = nurbs_positions_num - 1;
-    const bool ignore_interior_segment = segments_num == 3 && is_periodic == false;
-    if (ignore_interior_segment == false) {
-      const float mid_offset = (float)(segments_num - 1) / 2.0f;
-      for (const int i : IndexRange(1, segments_num - 2)) {
-        /* Divisor can have values: 1, 2 or 3. */
-        const int divisor = is_periodic ?
-                                3 :
-                                std::min(3, (int)(-std::abs(i - mid_offset) + mid_offset + 1.0f));
-        const float3 &p1 = nurbs_positions[i];
-        const float3 &p2 = nurbs_positions[i + 1];
-        const float3 displacement = (p2 - p1) / divisor;
-        const int num_handles_on_segment = divisor < 3 ? 1 : 2;
-        for (int j : IndexRange(1, num_handles_on_segment)) {
-          handle_positions.append(p1 + (displacement * j));
-        }
-      }
-    }
-
-    const int last_index = nurbs_positions_num - 1;
-    if (is_periodic) {
-      handle_positions.append(
-          nurbs_positions[last_index - 1] +
-          ((nurbs_positions[last_index] - nurbs_positions[last_index - 1]) / 3));
-    }
-    else {
-      handle_positions.append(nurbs_positions[last_index - 1]);
-      handle_positions.append(2 * nurbs_positions[last_index] - nurbs_positions[last_index - 1]);
-    }
-  }
-  else {
-    for (const int i : IndexRange(nurbs_positions_num)) {
-      if (i % 3 == 1) {
-        continue;
-      }
-      handle_positions.append(nurbs_positions[i]);
-    }
-    if (nurbs_positions_num % 3 == 1) {
-      handle_positions.pop_last();
-    }
-    else if (nurbs_positions_num % 3 == 2) {
-      const int last_index = nurbs_positions_num - 1;
-      handle_positions.append(2 * nurbs_positions[last_index] - nurbs_positions[last_index - 1]);
-    }
-  }
-
-  return handle_positions;
-}
-
-static void create_nurbs_to_bezier_positions(const Span nurbs_positions,
-                                             const Span handle_positions,
-                                             const KnotsMode knots_mode,
-                                             MutableSpan bezier_positions)
-{
-  if (is_nurbs_to_bezier_one_to_one(knots_mode)) {
-    for (const int i : bezier_positions.index_range()) {
-      bezier_positions[i] = math::interpolate(
-          handle_positions[i * 2], handle_positions[i * 2 + 1], 0.5f);
-    }
-  }
-  else {
-    /* Every 3rd NURBS position (starting from index 1) should be converted to Bezier position. */
-    scale_input_assign(nurbs_positions, 3, 1, bezier_positions);
-  }
-}
-
-static int to_bezier_size(const CurveType src_type,
-                          const bool cyclic,
-                          const KnotsMode knots_mode,
-                          const int src_size)
-{
-  switch (src_type) {
-    case CURVE_TYPE_NURBS: {
-      if (is_nurbs_to_bezier_one_to_one(knots_mode)) {
-        return cyclic ? src_size : src_size - 2;
-      }
-      return (src_size + 1) / 3;
-    }
-    default:
-      return src_size;
-  }
-}
-
-static int to_nurbs_size(const CurveType src_type, const int src_size)
-{
-  switch (src_type) {
-    case CURVE_TYPE_BEZIER:
-    case CURVE_TYPE_CATMULL_ROM:
-      return src_size * 3;
-    default:
-      return src_size;
-  }
-}
-
-struct GenericAttributes : NonCopyable, NonMovable {
-  Vector src;
-  Vector dst;
-
-  Vector attributes;
-};
-
-static void retrieve_generic_point_attributes(const CurveComponent &src_component,
-                                              CurveComponent &dst_component,
-                                              GenericAttributes &attributes)
-{
-  src_component.attribute_foreach(
-      [&](const AttributeIDRef &id, const AttributeMetaData meta_data) {
-        if (meta_data.domain != ATTR_DOMAIN_POINT) {
-          /* Curve domain attributes are all copied directly to the result in one step. */
-          return true;
-        }
-        if (src_component.attribute_is_builtin(id)) {
-          if (!(id.is_named() && ELEM(id, "tilt", "radius"))) {
-            return true;
-          }
-        }
-
-        GVArray src_attribute = src_component.attribute_try_get_for_read(id, ATTR_DOMAIN_POINT);
-        BLI_assert(src_attribute);
-        attributes.src.append(src_attribute.get_internal_span());
-
-        OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
-            id, ATTR_DOMAIN_POINT, meta_data.data_type);
-        attributes.dst.append(dst_attribute.as_span());
-        attributes.attributes.append(std::move(dst_attribute));
-
-        return true;
-      });
-}
-
-static void convert_to_bezier(const CurveComponent &src_component,
-                              const bke::CurvesGeometry &src_curves,
-                              const IndexMask selection,
-                              CurveComponent &dst_component,
-                              bke::CurvesGeometry &dst_curves)
-{
-  const Vector unselected_ranges = selection.extract_ranges_invert(
-      src_curves.curves_range());
-
-  const VArray src_knot_modes = src_curves.nurbs_knots_modes();
-  const VArray src_types = src_curves.curve_types();
-  const VArray src_cyclic = src_curves.cyclic();
-  const Span src_positions = src_curves.positions();
-
-  MutableSpan dst_offsets = dst_curves.offsets_for_write();
-  bke::curves::fill_curve_counts(src_curves, unselected_ranges, dst_curves.offsets_for_write());
-  threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) {
-    for (const int i : selection.slice(range)) {
-      const CurveType type = CurveType(src_types[i]);
-      const KnotsMode knots_mode = KnotsMode(src_knot_modes[i]);
-      const IndexRange points = src_curves.points_for_curve(i);
-      dst_offsets[i] = to_bezier_size(type, src_cyclic[i], knots_mode, points.size());
-    }
-  });
-  bke::curves::accumulate_counts_to_offsets(dst_offsets);
-  dst_curves.resize(dst_offsets.last(), dst_curves.curves_num());
-
-  GenericAttributes attributes;
-  retrieve_generic_point_attributes(src_component, dst_component, attributes);
-
-  MutableSpan dst_positions = dst_curves.positions_for_write();
-  MutableSpan dst_handles_l = dst_curves.handle_positions_left_for_write();
-  MutableSpan dst_handles_r = dst_curves.handle_positions_right_for_write();
-  MutableSpan dst_types_l = dst_curves.handle_types_left_for_write();
-  MutableSpan dst_types_r = dst_curves.handle_types_right_for_write();
-  MutableSpan dst_weights = dst_curves.nurbs_weights_for_write();
-
-  auto catmull_rom_to_bezier = [&](IndexMask selection) {
-    bke::curves::fill_points(dst_curves, selection, BEZIER_HANDLE_ALIGN, dst_types_l);
-    bke::curves::fill_points(dst_curves, selection, BEZIER_HANDLE_ALIGN, dst_types_r);
-    bke::curves::copy_point_data(src_curves, dst_curves, selection, src_positions, dst_positions);
-
-    threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) {
-      for (const int i : selection.slice(range)) {
-        const IndexRange src_points = src_curves.points_for_curve(i);
-        const IndexRange dst_points = dst_curves.points_for_curve(i);
-        catmull_rom_to_bezier_handles(src_positions.slice(src_points),
-                                      src_cyclic[i],
-                                      dst_handles_l.slice(dst_points),
-                                      dst_handles_r.slice(dst_points));
-      }
-    });
-
-    for (const int i : attributes.src.index_range()) {
-      bke::curves::copy_point_data(
-          src_curves, dst_curves, selection, attributes.src[i], attributes.dst[i]);
-    }
-  };
-
-  auto poly_to_bezier = [&](IndexMask selection) {
-    bke::curves::copy_point_data(src_curves, dst_curves, selection, src_positions, dst_positions);
-    bke::curves::fill_points(dst_curves, selection, BEZIER_HANDLE_VECTOR, dst_types_l);
-    bke::curves::fill_points(dst_curves, selection, BEZIER_HANDLE_VECTOR, dst_types_r);
-    dst_curves.calculate_bezier_auto_handles();
-    for (const int i : attributes.src.index_range()) {
-      bke::curves::copy_point_data(
-          src_curves, dst_curves, selection, attributes.src[i], attributes.dst[i]);
-    }
-  };
-
-  auto bezier_to_bezier = [&](IndexMask selection) {
-    const VArraySpan src_types_l = src_curves.handle_types_left();
-    const VArraySpan src_types_r = src_curves.handle_types_right();
-    const Span src_handles_l = src_curves.handle_positions_left();
-    const Span src_handles_r = src_curves.handle_positions_right();
-
-    bke::curves::copy_point_data(src_curves, dst_curves, selection, src_positions, dst_positions);
-    bke::curves::copy_point_data(src_curves, dst_curves, selection, src_handles_l, dst_handles_l);
-    bke::curves::copy_point_data(src_curves, dst_curves, selection, src_handles_r, dst_handles_r);
-    bke::curves::copy_point_data(src_curves, dst_curves, selection, src_types_l, dst_types_l);
-    bke::curves::copy_point_data(src_curves, dst_curves, selection, src_types_r, dst_types_r);
-
-    dst_curves.calculate_bezier_auto_handles();
-
-    for (const int i : attributes.src.index_range()) {
-      bke::curves::copy_point_data(
-          src_curves, dst_curves, selection, attributes.src[i], attributes.dst[i]);
-    }
-  };
-
-  auto nurbs_to_bezier = [&](IndexMask selection) {
-    bke::curves::fill_points(dst_curves, selection, BEZIER_HANDLE_ALIGN, dst_types_l);
-    bke::curves::fill_points(dst_curves, selection, BEZIER_HANDLE_ALIGN, dst_types_r);
-    bke::curves::fill_points(dst_curves, selection, 0.0f, dst_weights);
-
-    threading::parallel_for(selection.index_range(), 64, [&](IndexRange range) {
-      for (const int i : selection.slice(range)) {
-        const IndexRange src_points = src_curves.points_for_curve(i);
-        const IndexRange dst_points = dst_curves.points_for_curve(i);
-        const Span src_curve_positions = src_positions.slice(src_points);
-
-        KnotsMode knots_mode = KnotsMode(src_knot_modes[i]);
-        Span nurbs_positions = src_curve_positions;
-        Vector nurbs_positions_vector;
-        if (src_cyclic[i] && is_nurbs_to_bezier_one_to_one(knots_mode)) {
-          /* For conversion treat this as periodic closed curve. Extend NURBS hull to first and
-           * second point which will act as a skeleton for placing Bezier handles. */
-          nurbs_positions_vector.extend(src_curve_positions);
-          nurbs_positions_vector.append(src_curve_positions[0]);
-          nurbs_positions_vector.append(src_curve_positions[1]);
-          nurbs_positions = nurbs_positions_vector;
-          knots_mode = NURBS_KNOT_MODE_NORMAL;
-        }
-
-        const Vector handle_positions = create_nurbs_to_bezier_handles(nurbs_positions,
-                                                                               knots_mode);
-
-        scale_input_assign(handle_positions.as_span(), 2, 0, dst_handles_l.slice(dst_points));
-        scale_input_assign(handle_positions.as_span(), 2, 1, dst_handles_r.slice(dst_points));
-
-        create_nurbs_to_bezier_positions(
-            nurbs_positions, handle_positions, knots_mode, dst_positions.slice(dst_points));
-      }
-    });
-
-    for (const int i_attribute : attributes.src.index_range()) {
-      threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) {
-        for (const int i : selection.slice(range)) {
-          const IndexRange src_points = src_curves.points_for_curve(i);
-          const IndexRange dst_points = dst_curves.points_for_curve(i);
-          nurbs_to_bezier_assign(attributes.src[i_attribute].slice(src_points),
-                                 KnotsMode(src_knot_modes[i]),
-                                 attributes.dst[i_attribute].slice(dst_points));
-        }
-      });
-    }
-  };
-
-  bke::curves::foreach_curve_by_type(src_curves.curve_types(),
-                                     src_curves.curve_type_counts(),
-                                     selection,
-                                     catmull_rom_to_bezier,
-                                     poly_to_bezier,
-                                     bezier_to_bezier,
-                                     nurbs_to_bezier);
-
-  for (const int i : attributes.src.index_range()) {
-    bke::curves::copy_point_data(
-        src_curves, dst_curves, unselected_ranges, attributes.src[i], attributes.dst[i]);
-  }
-
-  for (OutputAttribute &attribute : attributes.attributes) {
-    attribute.save();
-  }
-}
-
-static void convert_to_nurbs(const CurveComponent &src_component,
-                             const bke::CurvesGeometry &src_curves,
-                             const IndexMask selection,
-                             CurveComponent &dst_component,
-                             bke::CurvesGeometry &dst_curves)
-{
-  const Vector unselected_ranges = selection.extract_ranges_invert(
-      src_curves.curves_range());
-
-  const VArray src_types = src_curves.curve_types();
-  const VArray src_cyclic = src_curves.cyclic();
-  const Span src_positions = src_curves.positions();
-
-  MutableSpan dst_offsets = dst_curves.offsets_for_write();
-  bke::curves::fill_curve_counts(src_curves, unselected_ranges, dst_curves.offsets_for_write());
-  threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) {
-    for (const int i : selection.slice(range)) {
-      const IndexRange points = src_curves.points_for_curve(i);
-      dst_offsets[i] = to_nurbs_size(CurveType(src_types[i]), points.size());
-    }
-  });
-  bke::curves::accumulate_counts_to_offsets(dst_offsets);
-  dst_curves.resize(dst_offsets.last(), dst_curves.curves_num());
-
-  GenericAttributes attributes;
-  retrieve_generic_point_attributes(src_component, dst_component, attributes);
-
-  MutableSpan dst_positions = dst_curves.positions_for_write();
-
-  auto fill_weights_if_necessary = [&](const IndexMask selection) {
-    if (!src_curves.nurbs_weights().is_empty()) {
-      bke::curves::fill_points(dst_curves, selection, 1.0f, dst_curves.nurbs_weights_for_write());
-    }
-  };
-
-  auto catmull_rom_to_nurbs = [&](IndexMask selection) {
-    dst_curves.nurbs_orders_for_write().fill_indices(selection, 4);
-    dst_curves.nurbs_knots_modes_for_write().fill_indices(selection, NURBS_KNOT_MODE_BEZIER);
-    fill_weights_if_necessary(selection);
-
-    threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) {
-      for (const int i : selection.slice(range)) {
-        const IndexRange src_points = src_curves.points_for_curve(i);
-        const IndexRange dst_points = dst_curves.points_for_curve(i);
-        catmull_rom_to_nurbs_positions(
-            src_positions.slice(src_points), src_cyclic[i], dst_positions.slice(dst_points));
-      }
-    });
-
-    for (const int i_attribute : attributes.src.index_range()) {
-      threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) {
-        for (const int i : selection.slice(range)) {
-          const IndexRange src_points = src_curves.points_for_curve(i);
-          const IndexRange dst_points = dst_curves.points_for_curve(i);
-          bezier_generic_to_nurbs(attributes.src[i_attribute].slice(src_points),
-                                  attributes.dst[i_attribute].slice(dst_points));
-        }
-      });
-    }
-  };
-
-  auto poly_to_nurbs = [&](IndexMask selection) {
-    dst_curves.nurbs_orders_for_write().fill_indices(selection, 4);
-    bke::curves::copy_point_data(src_curves, dst_curves, selection, src_positions, dst_positions);
-    fill_weights_if_necessary(selection);
-
-    /* Avoid using "Endpoint" knots modes for cyclic curves, since it adds a sharp point at the
-     * start/end. */
-    if (src_cyclic.is_single()) {
-      dst_curves.nurbs_knots_modes_for_write().fill_indices(
-          selection,
-          src_cyclic.get_internal_single() ? NURBS_KNOT_MODE_NORMAL : NURBS_KNOT_MODE_ENDPOINT);
-    }
-    else {
-      VArraySpan cyclic{src_cyclic};
-      MutableSpan knots_modes = dst_curves.nurbs_knots_modes_for_write();
-      threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) {
-        for (const int i : selection.slice(range)) {
-          knots_modes[i] = cyclic[i] ? NURBS_KNOT_MODE_NORMAL : NURBS_KNOT_MODE_ENDPOINT;
-        }
-      });
-    }
-
-    for (const int i_attribute : attributes.src.index_range()) {
-      bke::curves::copy_point_data(src_curves,
-                                   dst_curves,
-                                   selection,
-                                   attributes.src[i_attribute],
-                                   attributes.dst[i_attribute]);
-    }
-  };
-
-  auto bezier_to_nurbs = [&](IndexMask selection) {
-    const Span src_handles_l = src_curves.handle_positions_left();
-    const Span src_handles_r = src_curves.handle_positions_right();
-
-    dst_curves.nurbs_orders_for_write().fill_indices(selection, 4);
-    dst_curves.nurbs_knots_modes_for_write().fill_indices(selection, NURBS_KNOT_MODE_BEZIER);
-    fill_weights_if_necessary(selection);
-
-    threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) {
-      for (const int i : selection.slice(range)) {
-        const IndexRange src_points = src_curves.points_for_curve(i);
-        const IndexRange dst_points = dst_curves.points_for_curve(i);
-        bezier_positions_to_nurbs(src_positions.slice(src_points),
-                                  src_handles_l.slice(src_points),
-                                  src_handles_r.slice(src_points),
-                                  dst_positions.slice(dst_points));
-      }
-    });
-
-    for (const int i_attribute : attributes.src.index_range()) {
-      threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) {
-        for (const int i : selection.slice(range)) {
-          const IndexRange src_points = src_curves.points_for_curve(i);
-          const IndexRange dst_points = dst_curves.points_for_curve(i);
-          bezier_generic_to_nurbs(attributes.src[i_attribute].slice(src_points),
-                                  attributes.dst[i_attribute].slice(dst_points));
-        }
-      });
-    }
-  };
-
-  auto nurbs_to_nurbs = [&](IndexMask selection) {
-    bke::curves::copy_point_data(src_curves, dst_curves, selection, src_positions, dst_positions);
-
-    if (!src_curves.nurbs_weights().is_empty()) {
-      bke::curves::copy_point_data(src_curves,
-                                   dst_curves,
-                                   selection,
-                                   src_curves.nurbs_weights(),
-                                   dst_curves.nurbs_weights_for_write());
-    }
-
-    for (const int i_attribute : attributes.src.index_range()) {
-      bke::curves::copy_point_data(src_curves,
-                                   dst_curves,
-                                   selection,
-                                   attributes.src[i_attribute],
-                                   attributes.dst[i_attribute]);
-    }
-  };
-
-  bke::curves::foreach_curve_by_type(src_curves.curve_types(),
-                                     src_curves.curve_type_counts(),
-                                     selection,
-                                     catmull_rom_to_nurbs,
-                                     poly_to_nurbs,
-                                     bezier_to_nurbs,
-                                     nurbs_to_nurbs);
-
-  for (const int i : attributes.src.index_range()) {
-    bke::curves::copy_point_data(
-        src_curves, dst_curves, unselected_ranges, attributes.src[i], attributes.dst[i]);
-  }
-
-  for (OutputAttribute &attribute : attributes.attributes) {
-    attribute.save();
-  }
-}
-
 static void node_geo_exec(GeoNodeExecParams params)
 {
   const NodeGeometryCurveSplineType &storage = node_storage(params.node());
@@ -676,50 +53,23 @@ static void node_geo_exec(GeoNodeExecParams params)
     }
 
     GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_CURVE};
-    const int domain_size = src_component.attribute_domain_num(ATTR_DOMAIN_CURVE);
-
-    fn::FieldEvaluator evaluator{field_context, domain_size};
+    fn::FieldEvaluator evaluator{field_context, src_curves.curves_num()};
     evaluator.set_selection(selection_field);
     evaluator.evaluate();
     const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
-    if (!conversion_can_change_point_num(dst_type)) {
-      CurveComponent &dst_component = geometry_set.get_component_for_write();
-      Curves &curves_id = *dst_component.get_for_write();
-      bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry);
-      curves.fill_curve_types(selection, dst_type);
-      curves.remove_attributes_based_on_types();
+    if (selection.is_empty()) {
       return;
     }
 
-    Curves *dst_curves_id = bke::curves_new_nomain(0, src_curves.curves_num());
-    bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry);
-    CurveComponent dst_component;
-    dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable);
-    /* Directly copy curve attributes, since they stay the same (except for curve types). */
-    CustomData_copy(&src_curves.curve_data,
-                    &dst_curves.curve_data,
-                    CD_MASK_ALL,
-                    CD_DUPLICATE,
-                    src_curves.curves_num());
-
-    dst_curves.fill_curve_types(selection, dst_type);
-
-    switch (dst_type) {
-      case CURVE_TYPE_CATMULL_ROM:
-      case CURVE_TYPE_POLY:
-        /* Converting to Catmull Rom curves or poly curves should be handled
-         * above by the optimization to avoid changing the point count. */
-        BLI_assert_unreachable();
-        break;
-      case CURVE_TYPE_BEZIER:
-        convert_to_bezier(src_component, src_curves, selection, dst_component, dst_curves);
-        break;
-      case CURVE_TYPE_NURBS:
-        convert_to_nurbs(src_component, src_curves, selection, dst_component, dst_curves);
-        break;
+    if (geometry::try_curves_conversion_in_place(selection, dst_type, [&]() -> Curves & {
+          return *geometry_set.get_curves_for_write();
+        })) {
+      return;
     }
 
-    geometry_set.replace_curves(dst_curves_id);
+    Curves *dst_curves = geometry::convert_curves(src_component, src_curves, selection, dst_type);
+
+    geometry_set.replace_curves(dst_curves);
   });
 
   params.set_output("Curve", std::move(geometry_set));
-- 
cgit v1.2.3


From 9435ee8c65193e3d4af8f1ac5b07b7884cf62bd5 Mon Sep 17 00:00:00 2001
From: Hans Goudey 
Date: Tue, 5 Jul 2022 16:08:37 -0500
Subject: Curves: Port subdivide node to the new data-block

This commit moves the subdivide curve node implementation to the
geometry module, changes it to work on the new curves data-block,
and adds support for Catmull Rom curves. Internally I also added
support for a curve domain selection. That isn't used, but it's
nice to have the option anyway.

Users should notice better performance as well, since we can avoid
many small allocations, and there is no conversion to and from the
old curve type.

The code uses a similar structure to the resample node (60a6fbf5b599)
and the set type node (9e393fc2f125). The resample curves node can be
restructured to be more similar to this soon though.

Differential Revision: https://developer.blender.org/D15334
---
 source/blender/blenkernel/BKE_curves.hh            |  50 +++
 source/blender/blenkernel/intern/curve_bezier.cc   |  27 +-
 .../blender/blenkernel/intern/curve_catmull_rom.cc | 101 +++--
 source/blender/blenlib/BLI_index_range.hh          |  19 +-
 source/blender/geometry/CMakeLists.txt             |   2 +
 source/blender/geometry/GEO_subdivide_curves.hh    |  26 ++
 source/blender/geometry/intern/subdivide_curves.cc | 486 +++++++++++++++++++++
 .../geometry/nodes/node_geo_curve_subdivide.cc     | 319 +-------------
 8 files changed, 687 insertions(+), 343 deletions(-)
 create mode 100644 source/blender/geometry/GEO_subdivide_curves.hh
 create mode 100644 source/blender/geometry/intern/subdivide_curves.cc

diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh
index 767936e2a26..3e00dc78b74 100644
--- a/source/blender/blenkernel/BKE_curves.hh
+++ b/source/blender/blenkernel/BKE_curves.hh
@@ -483,6 +483,8 @@ namespace bezier {
  * Return true if the handles that make up a segment both have a vector type. Vector segments for
  * Bezier curves have special behavior because they aren't divided into many evaluated points.
  */
+bool segment_is_vector(const HandleType left, const HandleType right);
+bool segment_is_vector(const int8_t left, const int8_t right);
 bool segment_is_vector(Span handle_types_left,
                        Span handle_types_right,
                        int segment_index);
@@ -515,6 +517,35 @@ void calculate_evaluated_offsets(Span handle_types_left,
                                  int resolution,
                                  MutableSpan evaluated_offsets);
 
+/** See #insert. */
+struct Insertion {
+  float3 handle_prev;
+  float3 left_handle;
+  float3 position;
+  float3 right_handle;
+  float3 handle_next;
+};
+
+/**
+ * Compute the Bezier segment insertion for the given parameter on the segment, returning
+ * the position and handles of the new point and the updated existing handle positions.
+ * 
+ *           handle_prev         handle_next
+ *                x-----------------x
+ *               /                   \
+ *              /      x---O---x      \
+ *             /        result         \
+ *            /                         \
+ *           O                           O
+ *       point_prev                   point_next
+ * 
+ */ +Insertion insert(const float3 &point_prev, + const float3 &handle_prev, + const float3 &handle_next, + const float3 &point_next, + float parameter); + /** * Calculate the automatically defined positions for a vector handle (#BEZIER_HANDLE_VECTOR). While * this can be calculated automatically with #calculate_auto_handles, when more context is @@ -607,6 +638,15 @@ int calculate_evaluated_num(int points_num, bool cyclic, int resolution); */ void interpolate_to_evaluated(GSpan src, bool cyclic, int resolution, GMutableSpan dst); +/** + * Evaluate the Catmull Rom curve. The size of each segment and its offset in the #dst span + * is encoded in #evaluated_offsets, with the same method as #CurvesGeometry::offsets(). + */ +void interpolate_to_evaluated(const GSpan src, + const bool cyclic, + const Span evaluated_offsets, + GMutableSpan dst); + } // namespace catmull_rom /** \} */ @@ -827,6 +867,16 @@ inline bool point_is_sharp(const Span handle_types_left, ELEM(handle_types_right[index], BEZIER_HANDLE_VECTOR, BEZIER_HANDLE_FREE); } +inline bool segment_is_vector(const HandleType left, const HandleType right) +{ + return left == BEZIER_HANDLE_VECTOR && right == BEZIER_HANDLE_VECTOR; +} + +inline bool segment_is_vector(const int8_t left, const int8_t right) +{ + return segment_is_vector(HandleType(left), HandleType(right)); +} + inline float3 calculate_vector_handle(const float3 &point, const float3 &next_point) { return math::interpolate(point, next_point, 1.0f / 3.0f); diff --git a/source/blender/blenkernel/intern/curve_bezier.cc b/source/blender/blenkernel/intern/curve_bezier.cc index 1d6ee4938b5..59b09384698 100644 --- a/source/blender/blenkernel/intern/curve_bezier.cc +++ b/source/blender/blenkernel/intern/curve_bezier.cc @@ -16,15 +16,14 @@ bool segment_is_vector(const Span handle_types_left, const int segment_index) { BLI_assert(handle_types_left.index_range().drop_back(1).contains(segment_index)); - return handle_types_right[segment_index] == BEZIER_HANDLE_VECTOR && - handle_types_left[segment_index + 1] == BEZIER_HANDLE_VECTOR; + return segment_is_vector(handle_types_right[segment_index], + handle_types_left[segment_index + 1]); } bool last_cyclic_segment_is_vector(const Span handle_types_left, const Span handle_types_right) { - return handle_types_right.last() == BEZIER_HANDLE_VECTOR && - handle_types_left.first() == BEZIER_HANDLE_VECTOR; + return segment_is_vector(handle_types_right.last(), handle_types_left.first()); } void calculate_evaluated_offsets(const Span handle_types_left, @@ -59,6 +58,26 @@ void calculate_evaluated_offsets(const Span handle_types_left, evaluated_offsets.last() = offset; } +Insertion insert(const float3 &point_prev, + const float3 &handle_prev, + const float3 &handle_next, + const float3 &point_next, + float parameter) +{ + /* De Casteljau Bezier subdivision. */ + BLI_assert(parameter <= 1.0f && parameter >= 0.0f); + + const float3 center_point = math::interpolate(handle_prev, handle_next, parameter); + + Insertion result; + result.handle_prev = math::interpolate(point_prev, handle_prev, parameter); + result.handle_next = math::interpolate(handle_next, point_next, parameter); + result.left_handle = math::interpolate(result.handle_prev, center_point, parameter); + result.right_handle = math::interpolate(center_point, result.handle_next, parameter); + result.position = math::interpolate(result.left_handle, result.right_handle, parameter); + return result; +} + static float3 calculate_aligned_handle(const float3 &position, const float3 &other_handle, const float3 &aligned_handle) diff --git a/source/blender/blenkernel/intern/curve_catmull_rom.cc b/source/blender/blenkernel/intern/curve_catmull_rom.cc index 1875c7b366a..952d59edcf9 100644 --- a/source/blender/blenkernel/intern/curve_catmull_rom.cc +++ b/source/blender/blenkernel/intern/curve_catmull_rom.cc @@ -39,15 +39,18 @@ static void evaluate_segment(const T &a, const T &b, const T &c, const T &d, Mut } } -template +/** + * \param range_fn: Returns an index range describing where in the #dst span each segment should be + * evaluated to, and how many points to add to it. This is used to avoid the need to allocate an + * actual offsets array in typical evaluation use cases where the resolution is per-curve. + */ +template static void interpolate_to_evaluated(const Span src, const bool cyclic, - const int resolution, + const RangeForSegmentFn &range_fn, MutableSpan dst) { - BLI_assert(dst.size() == calculate_evaluated_num(src.size(), cyclic, resolution)); - /* - First deal with one and two point curves need special attention. * - Then evaluate the first and last segment(s) whose control points need to wrap around * to the other side of the source array. @@ -57,11 +60,14 @@ static void interpolate_to_evaluated(const Span src, dst.first() = src.first(); return; } + + const IndexRange first = range_fn(0); + if (src.size() == 2) { - evaluate_segment(src.first(), src.first(), src.last(), src.last(), dst.take_front(resolution)); + evaluate_segment(src.first(), src.first(), src.last(), src.last(), dst.slice(first)); if (cyclic) { - evaluate_segment( - src.last(), src.last(), src.first(), src.first(), dst.take_back(resolution)); + const IndexRange last = range_fn(1); + evaluate_segment(src.last(), src.last(), src.first(), src.first(), dst.slice(last)); } else { dst.last() = src.last(); @@ -69,39 +75,65 @@ static void interpolate_to_evaluated(const Span src, return; } + const IndexRange second_to_last = range_fn(src.index_range().last(1)); + const IndexRange last = range_fn(src.index_range().last()); if (cyclic) { - /* The first segment. */ - evaluate_segment(src.last(), src[0], src[1], src[2], dst.take_front(resolution)); - /* The second-to-last segment. */ - evaluate_segment(src.last(2), - src.last(1), - src.last(), - src.first(), - dst.take_back(resolution * 2).drop_back(resolution)); - /* The last segment. */ - evaluate_segment(src.last(1), src.last(), src[0], src[1], dst.take_back(resolution)); + evaluate_segment(src.last(), src[0], src[1], src[2], dst.slice(first)); + evaluate_segment(src.last(2), src.last(1), src.last(), src.first(), dst.slice(second_to_last)); + evaluate_segment(src.last(1), src.last(), src[0], src[1], dst.slice(last)); } else { - /* The first segment. */ - evaluate_segment(src[0], src[0], src[1], src[2], dst.take_front(resolution)); - /* The last segment. */ - evaluate_segment( - src.last(2), src.last(1), src.last(), src.last(), dst.drop_back(1).take_back(resolution)); - /* The final point of the last segment. */ + evaluate_segment(src[0], src[0], src[1], src[2], dst.slice(first)); + evaluate_segment(src.last(2), src.last(1), src.last(), src.last(), dst.slice(second_to_last)); + /* For non-cyclic curves, the last segment should always just have a single point. We could + * assert that the size of the provided range is 1 here, but that would require specializing + * the #range_fn implementation for the last point, which may have a performance cost. */ dst.last() = src.last(); } /* Evaluate every segment that isn't the first or last. */ - const int grain_size = std::max(512 / resolution, 1); const IndexRange inner_range = src.index_range().drop_back(2).drop_front(1); - threading::parallel_for(inner_range, grain_size, [&](IndexRange range) { + threading::parallel_for(inner_range, 512, [&](IndexRange range) { for (const int i : range) { - const IndexRange segment_range(resolution * i, resolution); - evaluate_segment(src[i - 1], src[i], src[i + 1], src[i + 2], dst.slice(segment_range)); + const IndexRange segment = range_fn(i); + evaluate_segment(src[i - 1], src[i], src[i + 1], src[i + 2], dst.slice(segment)); } }); } +template +static void interpolate_to_evaluated(const Span src, + const bool cyclic, + const int resolution, + MutableSpan dst) + +{ + BLI_assert(dst.size() == calculate_evaluated_num(src.size(), cyclic, resolution)); + interpolate_to_evaluated( + src, + cyclic, + [resolution](const int segment_i) -> IndexRange { + return {segment_i * resolution, resolution}; + }, + dst); +} + +template +static void interpolate_to_evaluated(const Span src, + const bool cyclic, + const Span evaluated_offsets, + MutableSpan dst) + +{ + interpolate_to_evaluated( + src, + cyclic, + [evaluated_offsets](const int segment_i) -> IndexRange { + return bke::offsets_to_range(evaluated_offsets, segment_i); + }, + dst); +} + void interpolate_to_evaluated(const GSpan src, const bool cyclic, const int resolution, @@ -117,4 +149,19 @@ void interpolate_to_evaluated(const GSpan src, }); } +void interpolate_to_evaluated(const GSpan src, + const bool cyclic, + const Span evaluated_offsets, + GMutableSpan dst) +{ + attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + /* TODO: Use DefaultMixer or other generic mixing in the basis evaluation function to simplify + * supporting more types. */ + if constexpr (is_same_any_v) { + interpolate_to_evaluated(src.typed(), cyclic, evaluated_offsets, dst.typed()); + } + }); +} + } // namespace blender::bke::curves::catmull_rom diff --git a/source/blender/blenlib/BLI_index_range.hh b/source/blender/blenlib/BLI_index_range.hh index 7d5c2400bba..bd0a7e5bb7a 100644 --- a/source/blender/blenlib/BLI_index_range.hh +++ b/source/blender/blenlib/BLI_index_range.hh @@ -186,13 +186,15 @@ class IndexRange { } /** - * Get the last element in the range. - * Asserts when the range is empty. + * Get the nth last element in the range. + * Asserts when the range is empty or when n is negative. */ - constexpr int64_t last() const + constexpr int64_t last(const int64_t n = 0) const { + BLI_assert(n >= 0); + BLI_assert(n < size_); BLI_assert(this->size() > 0); - return start_ + size_ - 1; + return start_ + size_ - 1 - n; } /** @@ -279,6 +281,15 @@ class IndexRange { return IndexRange(start_ + size_ - new_size, new_size); } + /** + * Move the range forward or backward within the larger array. The amount may be negative, + * but its absolute value cannot be greater than the existing start of the range. + */ + constexpr IndexRange shift(int64_t n) const + { + return IndexRange(start_ + n, size_); + } + /** * Get read-only access to a memory buffer that contains the range as actual numbers. */ diff --git a/source/blender/geometry/CMakeLists.txt b/source/blender/geometry/CMakeLists.txt index 21b2071d0e6..df66a806c16 100644 --- a/source/blender/geometry/CMakeLists.txt +++ b/source/blender/geometry/CMakeLists.txt @@ -25,6 +25,7 @@ set(SRC intern/resample_curves.cc intern/reverse_uv_sampler.cc intern/set_curve_type.cc + intern/subdivide_curves.cc intern/uv_parametrizer.c GEO_add_curves_on_mesh.hh @@ -37,6 +38,7 @@ set(SRC GEO_resample_curves.hh GEO_reverse_uv_sampler.hh GEO_set_curve_type.hh + GEO_subdivide_curves.hh GEO_uv_parametrizer.h ) diff --git a/source/blender/geometry/GEO_subdivide_curves.hh b/source/blender/geometry/GEO_subdivide_curves.hh new file mode 100644 index 00000000000..4f671467b24 --- /dev/null +++ b/source/blender/geometry/GEO_subdivide_curves.hh @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_function_ref.hh" +#include "BLI_index_mask.hh" + +#include "BKE_curves.hh" + +struct CurveComponent; + +namespace blender::geometry { + +/** + * Add more points along each segment, with the amount of points to add in each segment described + * by the #cuts input. The new points are equidistant in parameter space, but not in the actual + * distances. + * + * \param selection: A selection of curves to consider when subdividing. + */ +Curves *subdivide_curves(const CurveComponent &src_component, + const bke::CurvesGeometry &src_curves, + IndexMask selection, + const VArray &cuts); + +} // namespace blender::geometry diff --git a/source/blender/geometry/intern/subdivide_curves.cc b/source/blender/geometry/intern/subdivide_curves.cc new file mode 100644 index 00000000000..4fb21e53013 --- /dev/null +++ b/source/blender/geometry/intern/subdivide_curves.cc @@ -0,0 +1,486 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_attribute_math.hh" +#include "BKE_curves.hh" +#include "BKE_curves_utils.hh" +#include "BKE_geometry_set.hh" + +#include "BLI_task.hh" + +#include "GEO_subdivide_curves.hh" + +namespace blender::geometry { + +/** + * \warning Only the curve domain of the input is copied, so the result is invalid! + */ +static Curves *create_result_curves(const bke::CurvesGeometry &src_curves) +{ + Curves *dst_curves_id = bke::curves_new_nomain(0, src_curves.curves_num()); + bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry); + CurveComponent dst_component; + dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable); + /* Directly copy curve attributes, since they stay the same. */ + CustomData_copy(&src_curves.curve_data, + &dst_curves.curve_data, + CD_MASK_ALL, + CD_DUPLICATE, + src_curves.curves_num()); + dst_curves.runtime->type_counts = src_curves.runtime->type_counts; + + return dst_curves_id; +} + +/** + * Return a range used to retrieve values from an array of values stored per point, but with an + * extra element at the end of each curve. This is useful for offsets within curves, where it is + * convenient to store the first 0 and have the last offset be the total result curve size. + */ +static IndexRange curve_dst_offsets(const IndexRange points, const int curve_index) +{ + return {curve_index + points.start(), points.size() + 1}; +} + +static void calculate_result_offsets(const bke::CurvesGeometry &src_curves, + const IndexMask selection, + const Span unselected_ranges, + const VArray &cuts, + const Span cyclic, + MutableSpan dst_curve_offsets, + MutableSpan dst_point_offsets) +{ + /* Fill the array with each curve's point count, then accumulate them to the offsets. */ + bke::curves::fill_curve_counts(src_curves, unselected_ranges, dst_curve_offsets); + threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) { + for (const int curve_i : selection.slice(range)) { + const IndexRange src_points = src_curves.points_for_curve(curve_i); + const IndexRange src_segments = curve_dst_offsets(src_points, curve_i); + + MutableSpan point_offsets = dst_point_offsets.slice(src_segments); + + MutableSpan point_counts = point_offsets.drop_back(1); + cuts.materialize_compressed(src_points, point_counts); + for (int &count : point_counts) { + /* Make sure the number of cuts is greater than zero and add one for the existing point. */ + count = std::max(count, 0) + 1; + } + if (!cyclic[curve_i]) { + /* The last point only has a segment to be subdivided if the curve isn't cyclic. */ + point_counts.last() = 1; + } + + bke::curves::accumulate_counts_to_offsets(point_offsets); + dst_curve_offsets[curve_i] = point_offsets.last(); + } + }); + bke::curves::accumulate_counts_to_offsets(dst_curve_offsets); +} + +struct AttributeTransferData { + /* Expect that if an attribute exists, it is stored as a contiguous array internally anyway. */ + GVArraySpan src; + bke::OutputAttribute dst; +}; + +static Vector retrieve_point_attributes(const CurveComponent &src_component, + CurveComponent &dst_component, + const Set &skip = {}) +{ + Vector attributes; + src_component.attribute_foreach( + [&](const bke::AttributeIDRef &id, const AttributeMetaData meta_data) { + if (meta_data.domain != ATTR_DOMAIN_POINT) { + /* Curve domain attributes are all copied directly to the result in one step. */ + return true; + } + if (id.is_named() && skip.contains(id.name())) { + return true; + } + + GVArray src = src_component.attribute_try_get_for_read(id, ATTR_DOMAIN_POINT); + BLI_assert(src); + bke::OutputAttribute dst = dst_component.attribute_try_get_for_output_only( + id, ATTR_DOMAIN_POINT, meta_data.data_type); + BLI_assert(dst); + attributes.append({std::move(src), std::move(dst)}); + + return true; + }); + return attributes; +} + +template +static inline void linear_interpolation(const T &a, const T &b, MutableSpan dst) +{ + dst.first() = a; + const float step = 1.0f / dst.size(); + for (const int i : dst.index_range().drop_front(1)) { + dst[i] = attribute_math::mix2(i * step, a, b); + } +} + +template +static void subdivide_attribute_linear(const bke::CurvesGeometry &src_curves, + const bke::CurvesGeometry &dst_curves, + const IndexMask selection, + const Span point_offsets, + const Span src, + MutableSpan dst) +{ + threading::parallel_for(selection.index_range(), 512, [&](IndexRange selection_range) { + for (const int curve_i : selection.slice(selection_range)) { + const IndexRange src_points = src_curves.points_for_curve(curve_i); + const IndexRange src_segments = curve_dst_offsets(src_points, curve_i); + const Span offsets = point_offsets.slice(src_segments); + + const IndexRange dst_points = dst_curves.points_for_curve(curve_i); + const Span curve_src = src.slice(src_points); + MutableSpan curve_dst = dst.slice(dst_points); + + threading::parallel_for(curve_src.index_range().drop_back(1), 1024, [&](IndexRange range) { + for (const int i : range) { + const IndexRange segment_points = bke::offsets_to_range(offsets, i); + linear_interpolation(curve_src[i], curve_src[i + 1], curve_dst.slice(segment_points)); + } + }); + + const IndexRange dst_last_segment = bke::offsets_to_range(offsets, src_points.size() - 1); + linear_interpolation(curve_src.last(), curve_src.first(), dst.slice(dst_last_segment)); + } + }); +} + +static void subdivide_attribute_linear(const bke::CurvesGeometry &src_curves, + const bke::CurvesGeometry &dst_curves, + const IndexMask selection, + const Span point_offsets, + const GSpan src, + GMutableSpan dst) +{ + attribute_math::convert_to_static_type(dst.type(), [&](auto dummy) { + using T = decltype(dummy); + subdivide_attribute_linear( + src_curves, dst_curves, selection, point_offsets, src.typed(), dst.typed()); + }); +} + +template +static void subdivide_attribute_catmull_rom(const bke::CurvesGeometry &src_curves, + const bke::CurvesGeometry &dst_curves, + const IndexMask selection, + const Span point_offsets, + const Span cyclic, + const Span src, + MutableSpan dst) +{ + threading::parallel_for(selection.index_range(), 512, [&](IndexRange selection_range) { + for (const int curve_i : selection.slice(selection_range)) { + const IndexRange src_points = src_curves.points_for_curve(curve_i); + const IndexRange src_segments = curve_dst_offsets(src_points, curve_i); + const IndexRange dst_points = dst_curves.points_for_curve(curve_i); + + bke::curves::catmull_rom::interpolate_to_evaluated(src.slice(src_points), + cyclic[curve_i], + point_offsets.slice(src_segments), + dst.slice(dst_points)); + } + }); +} + +static void subdivide_attribute_catmull_rom(const bke::CurvesGeometry &src_curves, + const bke::CurvesGeometry &dst_curves, + const IndexMask selection, + const Span point_offsets, + const Span cyclic, + const GSpan src, + GMutableSpan dst) +{ + attribute_math::convert_to_static_type(dst.type(), [&](auto dummy) { + using T = decltype(dummy); + subdivide_attribute_catmull_rom( + src_curves, dst_curves, selection, point_offsets, cyclic, src.typed(), dst.typed()); + }); +} + +static void subdivide_bezier_segment(const float3 &position_prev, + const float3 &handle_prev, + const float3 &handle_next, + const float3 &position_next, + const HandleType type_prev, + const HandleType type_next, + const IndexRange segment_points, + MutableSpan dst_positions, + MutableSpan dst_handles_l, + MutableSpan dst_handles_r, + MutableSpan dst_types_l, + MutableSpan dst_types_r, + const bool is_last_cyclic_segment) +{ + auto fill_segment_handle_types = [&](const HandleType type) { + /* Also change the left handle of the control point following the segment's points. And don't + * change the left handle of the first point, since that is part of the previous segment. */ + dst_types_l.slice(segment_points.shift(1)).fill(type); + dst_types_r.slice(segment_points).fill(type); + }; + + if (bke::curves::bezier::segment_is_vector(type_prev, type_next)) { + linear_interpolation(position_prev, position_next, dst_positions.slice(segment_points)); + fill_segment_handle_types(BEZIER_HANDLE_VECTOR); + } + else { + /* The first point in the segment is always copied. */ + dst_positions[segment_points.first()] = position_prev; + + /* Non-vector segments in the result curve are given free handles. This could possibly be + * improved with another pass that sets handles to aligned where possible, but currently that + * does not provide much benefit for the increased complexity. */ + fill_segment_handle_types(BEZIER_HANDLE_FREE); + + /* In order to generate a Bezier curve with the same shape as the input curve, apply the + * De Casteljau algorithm iteratively for the provided number of cuts, constantly updating the + * previous result point's right handle and the left handle at the end of the segment. */ + float3 segment_start = position_prev; + float3 segment_handle_prev = handle_prev; + float3 segment_handle_next = handle_next; + const float3 segment_end = position_next; + + for (const int i : IndexRange(segment_points.size() - 1)) { + const float parameter = 1.0f / (segment_points.size() - i); + const int point_i = segment_points[i]; + bke::curves::bezier::Insertion insert = bke::curves::bezier::insert( + segment_start, segment_handle_prev, segment_handle_next, segment_end, parameter); + + /* Copy relevant temporary data to the result. */ + dst_handles_r[point_i] = insert.handle_prev; + dst_handles_l[point_i + 1] = insert.left_handle; + dst_positions[point_i + 1] = insert.position; + + /* Update the segment to prepare it for the next subdivision. */ + segment_start = insert.position; + segment_handle_prev = insert.right_handle; + segment_handle_next = insert.handle_next; + } + + /* Copy the handles for the last segment from the working variables. */ + const int i_segment_last = is_last_cyclic_segment ? 0 : segment_points.one_after_last(); + dst_handles_r[segment_points.last()] = segment_handle_prev; + dst_handles_l[i_segment_last] = segment_handle_next; + } +} + +static void subdivide_bezier_positions(const Span src_positions, + const Span src_types_l, + const Span src_types_r, + const Span src_handles_l, + const Span src_handles_r, + const Span evaluated_offsets, + const bool cyclic, + MutableSpan dst_positions, + MutableSpan dst_types_l, + MutableSpan dst_types_r, + MutableSpan dst_handles_l, + MutableSpan dst_handles_r) +{ + threading::parallel_for(src_positions.index_range().drop_back(1), 512, [&](IndexRange range) { + for (const int segment_i : range) { + const IndexRange segment = bke::offsets_to_range(evaluated_offsets, segment_i); + subdivide_bezier_segment(src_positions[segment_i], + src_handles_r[segment_i], + src_handles_l[segment_i + 1], + src_positions[segment_i + 1], + HandleType(src_types_r[segment_i]), + HandleType(src_types_l[segment_i + 1]), + segment, + dst_positions, + dst_handles_l, + dst_handles_r, + dst_types_l, + dst_types_r, + false); + } + }); + + if (cyclic) { + const int last_index = src_positions.index_range().last(); + const IndexRange segment = bke::offsets_to_range(evaluated_offsets, last_index); + const HandleType type_prev = HandleType(src_types_r.last()); + const HandleType type_next = HandleType(src_types_l.first()); + subdivide_bezier_segment(src_positions.last(), + src_handles_r.last(), + src_handles_l.first(), + src_positions.first(), + type_prev, + type_next, + segment, + dst_positions, + dst_handles_l, + dst_handles_r, + dst_types_l, + dst_types_r, + true); + + if (bke::curves::bezier::segment_is_vector(type_prev, type_next)) { + dst_types_l.first() = BEZIER_HANDLE_VECTOR; + dst_types_r.last() = BEZIER_HANDLE_VECTOR; + } + else { + dst_types_l.first() = BEZIER_HANDLE_FREE; + dst_types_r.last() = BEZIER_HANDLE_FREE; + } + } + else { + dst_positions.last() = src_positions.last(); + dst_types_l.first() = src_types_l.first(); + dst_types_r.last() = src_types_r.last(); + dst_handles_l.first() = src_handles_l.first(); + dst_handles_r.last() = src_handles_r.last(); + } + + /* TODO: It would be possible to avoid calling this for all segments besides vector segments. */ + bke::curves::bezier::calculate_auto_handles( + cyclic, dst_types_l, dst_types_r, dst_positions, dst_handles_l, dst_handles_r); +} + +Curves *subdivide_curves(const CurveComponent &src_component, + const bke::CurvesGeometry &src_curves, + const IndexMask selection, + const VArray &cuts) +{ + const Vector unselected_ranges = selection.extract_ranges_invert( + src_curves.curves_range()); + + /* Cyclic is accessed a lot, it's probably worth it to make sure it's a span. */ + const VArraySpan cyclic{src_curves.cyclic()}; + + Curves *dst_curves_id = create_result_curves(src_curves); + bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry); + CurveComponent dst_component; + dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable); + + /* For each point, this contains the point offset in the corresponding result curve, + * starting at zero. For example for two curves with four points each, the values might + * look like this: + * + * | | Curve 0 | Curve 1 | + * | ------------------- |---|---|---|---|---|---|---|---|---|----| + * | Cuts | 0 | 3 | 0 | 0 | - | 2 | 0 | 0 | 4 | - | + * | New Point Count | 1 | 4 | 1 | 1 | - | 3 | 1 | 1 | 5 | - | + * | Accumulated Offsets | 0 | 1 | 5 | 6 | 7 | 0 | 3 | 4 | 5 | 10 | + * + * Storing the leading zero is unnecessary but makes the array a bit simpler to use by avoiding + * a check for the first segment, and because some existing utilities also use leading zeros. */ + Array dst_point_offsets(src_curves.points_num() + src_curves.curves_num()); +#ifdef DEBUG + dst_point_offsets.fill(-1); +#endif + calculate_result_offsets(src_curves, + selection, + unselected_ranges, + cuts, + cyclic, + dst_curves.offsets_for_write(), + dst_point_offsets); + const Span point_offsets = dst_point_offsets.as_span(); + + dst_curves.resize(dst_curves.offsets().last(), dst_curves.curves_num()); + + auto subdivide_catmull_rom = [&](IndexMask selection) { + for (auto &attribute : retrieve_point_attributes(src_component, dst_component)) { + subdivide_attribute_catmull_rom(src_curves, + dst_curves, + selection, + point_offsets, + cyclic, + attribute.src, + attribute.dst.as_span()); + attribute.dst.save(); + } + }; + + auto subdivide_poly = [&](IndexMask selection) { + for (auto &attribute : retrieve_point_attributes(src_component, dst_component)) { + subdivide_attribute_linear(src_curves, + dst_curves, + selection, + point_offsets, + attribute.src, + attribute.dst.as_span()); + attribute.dst.save(); + } + }; + + auto subdivide_bezier = [&](IndexMask selection) { + const Span src_positions = src_curves.positions(); + const VArraySpan src_types_l{src_curves.handle_types_left()}; + const VArraySpan src_types_r{src_curves.handle_types_right()}; + const Span src_handles_l = src_curves.handle_positions_left(); + const Span src_handles_r = src_curves.handle_positions_right(); + + MutableSpan dst_positions = dst_curves.positions_for_write(); + MutableSpan dst_types_l = dst_curves.handle_types_left_for_write(); + MutableSpan dst_types_r = dst_curves.handle_types_right_for_write(); + MutableSpan dst_handles_l = dst_curves.handle_positions_left_for_write(); + MutableSpan dst_handles_r = dst_curves.handle_positions_right_for_write(); + + threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) { + for (const int curve_i : selection.slice(range)) { + const IndexRange src_points = src_curves.points_for_curve(curve_i); + const IndexRange src_segments = curve_dst_offsets(src_points, curve_i); + + const IndexRange dst_points = dst_curves.points_for_curve(curve_i); + subdivide_bezier_positions(src_positions.slice(src_points), + src_types_l.slice(src_points), + src_types_r.slice(src_points), + src_handles_l.slice(src_points), + src_handles_r.slice(src_points), + point_offsets.slice(src_segments), + cyclic[curve_i], + dst_positions.slice(dst_points), + dst_types_l.slice(dst_points), + dst_types_r.slice(dst_points), + dst_handles_l.slice(dst_points), + dst_handles_r.slice(dst_points)); + } + }); + + for (auto &attribute : retrieve_point_attributes(src_component, + dst_component, + {"position", + "handle_type_left", + "handle_type_right", + "handle_right", + "handle_left"})) { + subdivide_attribute_linear(src_curves, + dst_curves, + selection, + point_offsets, + attribute.src, + attribute.dst.as_span()); + attribute.dst.save(); + } + }; + + /* NURBS curves are just treated as poly curves. NURBS subdivision that maintains + * their shape may be possible, but probably wouldn't work with the "cuts" input. */ + auto subdivide_nurbs = subdivide_poly; + + bke::curves::foreach_curve_by_type(src_curves.curve_types(), + src_curves.curve_type_counts(), + selection, + subdivide_catmull_rom, + subdivide_poly, + subdivide_bezier, + subdivide_nurbs); + + if (!unselected_ranges.is_empty()) { + for (auto &attribute : retrieve_point_attributes(src_component, dst_component)) { + bke::curves::copy_point_data( + src_curves, dst_curves, unselected_ranges, attribute.src, attribute.dst.as_span()); + attribute.dst.save(); + } + } + + return dst_curves_id; +} + +} // namespace blender::geometry diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc index 4d8745bf79e..864e6289135 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc @@ -1,10 +1,8 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include "BLI_task.hh" -#include "BLI_timeit.hh" +#include "BKE_curves.hh" -#include "BKE_attribute_math.hh" -#include "BKE_spline.hh" +#include "GEO_subdivide_curves.hh" #include "UI_interface.h" #include "UI_resources.h" @@ -26,302 +24,6 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output(N_("Curve")); } -static Array get_subdivided_offsets(const Spline &spline, - const VArray &cuts, - const int spline_offset) -{ - Array offsets(spline.segments_num() + 1); - int offset = 0; - for (const int i : IndexRange(spline.segments_num())) { - offsets[i] = offset; - offset = offset + std::max(cuts[spline_offset + i], 0) + 1; - } - offsets.last() = offset; - return offsets; -} - -template -static void subdivide_attribute(Span src, - const Span offsets, - const bool is_cyclic, - MutableSpan dst) -{ - const int src_num = src.size(); - threading::parallel_for(IndexRange(src_num - 1), 1024, [&](IndexRange range) { - for (const int i : range) { - const int cuts = offsets[i + 1] - offsets[i]; - dst[offsets[i]] = src[i]; - const float factor_delta = cuts == 0 ? 1.0f : 1.0f / cuts; - for (const int cut : IndexRange(cuts)) { - const float factor = cut * factor_delta; - dst[offsets[i] + cut] = attribute_math::mix2(factor, src[i], src[i + 1]); - } - } - }); - - if (is_cyclic) { - const int i = src_num - 1; - const int cuts = offsets[i + 1] - offsets[i]; - dst[offsets[i]] = src.last(); - const float factor_delta = cuts == 0 ? 1.0f : 1.0f / cuts; - for (const int cut : IndexRange(cuts)) { - const float factor = cut * factor_delta; - dst[offsets[i] + cut] = attribute_math::mix2(factor, src.last(), src.first()); - } - } - else { - dst.last() = src.last(); - } -} - -/** - * In order to generate a Bezier spline with the same shape as the input spline, apply the - * De Casteljau algorithm iteratively for the provided number of cuts, constantly updating the - * previous result point's right handle and the left handle at the end of the segment. - * - * \note Non-vector segments in the result spline are given free handles. This could possibly be - * improved with another pass that sets handles to aligned where possible, but currently that does - * not provide much benefit for the increased complexity. - */ -static void subdivide_bezier_segment(const BezierSpline &src, - const int index, - const int offset, - const int result_num, - Span src_positions, - Span src_handles_left, - Span src_handles_right, - MutableSpan dst_positions, - MutableSpan dst_handles_left, - MutableSpan dst_handles_right, - MutableSpan dst_type_left, - MutableSpan dst_type_right) -{ - const bool is_last_cyclic_segment = index == (src.size() - 1); - const int next_index = is_last_cyclic_segment ? 0 : index + 1; - - /* The first point in the segment is always copied. */ - dst_positions[offset] = src_positions[index]; - - if (src.segment_is_vector(index)) { - if (is_last_cyclic_segment) { - dst_type_left.first() = BEZIER_HANDLE_VECTOR; - } - dst_type_left.slice(offset + 1, result_num).fill(BEZIER_HANDLE_VECTOR); - dst_type_right.slice(offset, result_num).fill(BEZIER_HANDLE_VECTOR); - - const float factor_delta = 1.0f / result_num; - for (const int cut : IndexRange(result_num)) { - const float factor = cut * factor_delta; - dst_positions[offset + cut] = attribute_math::mix2( - factor, src_positions[index], src_positions[next_index]); - } - } - else { - if (is_last_cyclic_segment) { - dst_type_left.first() = BEZIER_HANDLE_FREE; - } - dst_type_left.slice(offset + 1, result_num).fill(BEZIER_HANDLE_FREE); - dst_type_right.slice(offset, result_num).fill(BEZIER_HANDLE_FREE); - - const int i_segment_last = is_last_cyclic_segment ? 0 : offset + result_num; - - /* Create a Bezier segment to update iteratively for every subdivision - * and references to the meaningful values for ease of use. */ - BezierSpline temp; - temp.resize(2); - float3 &segment_start = temp.positions().first(); - float3 &segment_end = temp.positions().last(); - float3 &handle_prev = temp.handle_positions_right().first(); - float3 &handle_next = temp.handle_positions_left().last(); - segment_start = src_positions[index]; - segment_end = src_positions[next_index]; - handle_prev = src_handles_right[index]; - handle_next = src_handles_left[next_index]; - - for (const int cut : IndexRange(result_num - 1)) { - const float parameter = 1.0f / (result_num - cut); - const BezierSpline::InsertResult insert = temp.calculate_segment_insertion(0, 1, parameter); - - /* Copy relevant temporary data to the result. */ - dst_handles_right[offset + cut] = insert.handle_prev; - dst_handles_left[offset + cut + 1] = insert.left_handle; - dst_positions[offset + cut + 1] = insert.position; - - /* Update the segment to prepare it for the next subdivision. */ - segment_start = insert.position; - handle_prev = insert.right_handle; - handle_next = insert.handle_next; - } - - /* Copy the handles for the last segment from the temporary spline. */ - dst_handles_right[offset + result_num - 1] = handle_prev; - dst_handles_left[i_segment_last] = handle_next; - } -} - -static void subdivide_bezier_spline(const BezierSpline &src, - const Span offsets, - BezierSpline &dst) -{ - Span src_positions = src.positions(); - Span src_handles_left = src.handle_positions_left(); - Span src_handles_right = src.handle_positions_right(); - MutableSpan dst_positions = dst.positions(); - MutableSpan dst_handles_left = dst.handle_positions_left(); - MutableSpan dst_handles_right = dst.handle_positions_right(); - MutableSpan dst_type_left = dst.handle_types_left(); - MutableSpan dst_type_right = dst.handle_types_right(); - - threading::parallel_for(IndexRange(src.size() - 1), 512, [&](IndexRange range) { - for (const int i : range) { - subdivide_bezier_segment(src, - i, - offsets[i], - offsets[i + 1] - offsets[i], - src_positions, - src_handles_left, - src_handles_right, - dst_positions, - dst_handles_left, - dst_handles_right, - dst_type_left, - dst_type_right); - } - }); - - if (src.is_cyclic()) { - const int i_last = src.size() - 1; - subdivide_bezier_segment(src, - i_last, - offsets[i_last], - offsets.last() - offsets[i_last], - src_positions, - src_handles_left, - src_handles_right, - dst_positions, - dst_handles_left, - dst_handles_right, - dst_type_left, - dst_type_right); - } - else { - dst_positions.last() = src_positions.last(); - dst_type_left.first() = src.handle_types_left().first(); - dst_type_right.last() = src.handle_types_right().last(); - dst_handles_left.first() = src_handles_left.first(); - dst_handles_right.last() = src_handles_right.last(); - } -} - -static void subdivide_builtin_attributes(const Spline &src_spline, - const Span offsets, - Spline &dst_spline) -{ - const bool is_cyclic = src_spline.is_cyclic(); - subdivide_attribute(src_spline.radii(), offsets, is_cyclic, dst_spline.radii()); - subdivide_attribute(src_spline.tilts(), offsets, is_cyclic, dst_spline.tilts()); - switch (src_spline.type()) { - case CURVE_TYPE_POLY: { - const PolySpline &src = static_cast(src_spline); - PolySpline &dst = static_cast(dst_spline); - subdivide_attribute(src.positions(), offsets, is_cyclic, dst.positions()); - break; - } - case CURVE_TYPE_BEZIER: { - const BezierSpline &src = static_cast(src_spline); - BezierSpline &dst = static_cast(dst_spline); - subdivide_bezier_spline(src, offsets, dst); - dst.mark_cache_invalid(); - break; - } - case CURVE_TYPE_NURBS: { - const NURBSpline &src = static_cast(src_spline); - NURBSpline &dst = static_cast(dst_spline); - subdivide_attribute(src.positions(), offsets, is_cyclic, dst.positions()); - subdivide_attribute(src.weights(), offsets, is_cyclic, dst.weights()); - break; - } - case CURVE_TYPE_CATMULL_ROM: { - BLI_assert_unreachable(); - break; - } - } -} - -static void subdivide_dynamic_attributes(const Spline &src_spline, - const Span offsets, - Spline &dst_spline) -{ - const bool is_cyclic = src_spline.is_cyclic(); - src_spline.attributes.foreach_attribute( - [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { - std::optional src = src_spline.attributes.get_for_read(attribute_id); - BLI_assert(src); - - if (!dst_spline.attributes.create(attribute_id, meta_data.data_type)) { - /* Since the source spline of the same type had the attribute, adding it should work. */ - BLI_assert_unreachable(); - } - - std::optional dst = dst_spline.attributes.get_for_write(attribute_id); - BLI_assert(dst); - - attribute_math::convert_to_static_type(dst->type(), [&](auto dummy) { - using T = decltype(dummy); - subdivide_attribute(src->typed(), offsets, is_cyclic, dst->typed()); - }); - return true; - }, - ATTR_DOMAIN_POINT); -} - -static SplinePtr subdivide_spline(const Spline &spline, - const VArray &cuts, - const int spline_offset) -{ - if (spline.size() <= 1) { - return spline.copy(); - } - - /* Since we expect to access each value many times, it should be worth it to make sure count - * of cuts is a real span (especially considering the note below). Using the offset at each - * point facilitates subdividing in parallel later. */ - Array offsets = get_subdivided_offsets(spline, cuts, spline_offset); - const int result_num = offsets.last() + int(!spline.is_cyclic()); - SplinePtr new_spline = spline.copy_only_settings(); - new_spline->resize(result_num); - subdivide_builtin_attributes(spline, offsets, *new_spline); - subdivide_dynamic_attributes(spline, offsets, *new_spline); - return new_spline; -} - -/** - * \note Passing the virtual array for the entire spline is possibly quite inefficient here when - * the attribute was on the point domain and stored separately for each spline already, and it - * prevents some other optimizations like skipping splines with a single attribute value of < 1. - * However, it allows the node to access builtin attribute easily, so it the makes most sense this - * way until the attribute API is refactored. - */ -static std::unique_ptr subdivide_curve(const CurveEval &input_curve, - const VArray &cuts) -{ - const Array control_point_offsets = input_curve.control_point_offsets(); - const Span input_splines = input_curve.splines(); - - std::unique_ptr output_curve = std::make_unique(); - output_curve->resize(input_splines.size()); - output_curve->attributes = input_curve.attributes; - MutableSpan output_splines = output_curve->splines(); - - threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { - for (const int i : range) { - output_splines[i] = subdivide_spline(*input_splines[i], cuts, control_point_offsets[i]); - } - }); - - return output_curve; -} - static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input("Curve"); @@ -333,20 +35,21 @@ static void node_geo_exec(GeoNodeExecParams params) } const CurveComponent &component = *geometry_set.get_component_for_read(); - GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; - const int domain_num = component.attribute_domain_num(ATTR_DOMAIN_POINT); + const bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap( + component.get_for_read()->geometry); - fn::FieldEvaluator evaluator{field_context, domain_num}; + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; + fn::FieldEvaluator evaluator{field_context, curves.points_num()}; evaluator.add(cuts_field); evaluator.evaluate(); - const VArray &cuts = evaluator.get_evaluated(0); - + const VArray cuts = evaluator.get_evaluated(0); if (cuts.is_single() && cuts.get_internal_single() < 1) { return; } - std::unique_ptr output_curve = subdivide_curve( - *curves_to_curve_eval(*component.get_for_read()), cuts); - geometry_set.replace_curves(curve_eval_to_curves(*output_curve)); + + Curves *result = geometry::subdivide_curves(component, curves, curves.curves_range(), cuts); + + geometry_set.replace_curves(result); }); params.set_output("Curve", geometry_set); } -- cgit v1.2.3 From 1a820680a1bafda79ac3edc328f75662513029f9 Mon Sep 17 00:00:00 2001 From: Iliay Katueshenock Date: Tue, 5 Jul 2022 17:50:59 -0500 Subject: Fix: Tests: Incorrect curve construction The offsets were filled with the same value, but they must be the total accumulated point count. Differential Revision: https://developer.blender.org/D15374 --- source/blender/blenkernel/intern/curves_geometry_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/blenkernel/intern/curves_geometry_test.cc b/source/blender/blenkernel/intern/curves_geometry_test.cc index 48493743cfc..2c87fc539fe 100644 --- a/source/blender/blenkernel/intern/curves_geometry_test.cc +++ b/source/blender/blenkernel/intern/curves_geometry_test.cc @@ -16,7 +16,7 @@ static CurvesGeometry create_basic_curves(const int points_size, const int curve const int curve_length = points_size / curves_size; for (const int i : curves.curves_range()) { - curves.offsets_for_write()[i] = points_size * curve_length; + curves.offsets_for_write()[i] = curve_length * i; } curves.offsets_for_write().last() = points_size; -- cgit v1.2.3 From faac25fefedf4b3318d4f129ebda9efca16e48c5 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 5 Jul 2022 18:01:08 -0500 Subject: Fix T99284: Undefined values output from UV nodes When committing D14389 I assumed that the output arrays didn't need to be initialized, but the UV parameterizer uses the intial values of UVs. --- source/blender/nodes/geometry/nodes/node_geo_uv_unwrap.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_uv_unwrap.cc b/source/blender/nodes/geometry/nodes/node_geo_uv_unwrap.cc index bf960c5c809..364106455b6 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_uv_unwrap.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_uv_unwrap.cc @@ -82,7 +82,7 @@ static VArray construct_uv_gvarray(const MeshComponent &component, edge_evaluator.evaluate(); const IndexMask seam = edge_evaluator.get_evaluated_as_mask(0); - Array uv(mesh->totloop); + Array uv(mesh->totloop, float3(0)); ParamHandle *handle = GEO_uv_parametrizer_construct_begin(); for (const int mp_index : selection) { -- cgit v1.2.3 From 1f0048cc2dc37097dce99157e8b0c88311fe42bc Mon Sep 17 00:00:00 2001 From: Loren Osborn Date: Wed, 6 Jul 2022 00:06:16 -0500 Subject: Cleanup: Fix compiler warnings Use consistent class/struct declaration in forward declarations. Differential Revision: https://developer.blender.org/D15382 --- source/blender/geometry/GEO_set_curve_type.hh | 2 +- source/blender/geometry/GEO_subdivide_curves.hh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/geometry/GEO_set_curve_type.hh b/source/blender/geometry/GEO_set_curve_type.hh index f7ac8be5889..6a75450006b 100644 --- a/source/blender/geometry/GEO_set_curve_type.hh +++ b/source/blender/geometry/GEO_set_curve_type.hh @@ -8,7 +8,7 @@ #include "BLI_index_mask.hh" struct Curves; -struct CurveComponent; +class CurveComponent; namespace blender::bke { class CurvesGeometry; diff --git a/source/blender/geometry/GEO_subdivide_curves.hh b/source/blender/geometry/GEO_subdivide_curves.hh index 4f671467b24..66c2eb53496 100644 --- a/source/blender/geometry/GEO_subdivide_curves.hh +++ b/source/blender/geometry/GEO_subdivide_curves.hh @@ -7,7 +7,7 @@ #include "BKE_curves.hh" -struct CurveComponent; +class CurveComponent; namespace blender::geometry { -- cgit v1.2.3 From db9e08a0d1843d0d9efbc941f1dc85db22954dff Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 6 Jul 2022 14:24:37 +1000 Subject: Cleanup: spelling in comments --- intern/ghost/intern/GHOST_SystemCocoa.mm | 4 ++-- source/blender/blenkernel/BKE_nla.h | 4 ++-- source/blender/makesrna/intern/rna_nla.c | 18 +++++++++--------- source/blender/windowmanager/xr/intern/wm_xr_intern.h | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/intern/ghost/intern/GHOST_SystemCocoa.mm b/intern/ghost/intern/GHOST_SystemCocoa.mm index 4b2529f5169..0a905f5093e 100644 --- a/intern/ghost/intern/GHOST_SystemCocoa.mm +++ b/intern/ghost/intern/GHOST_SystemCocoa.mm @@ -787,7 +787,7 @@ GHOST_IWindow *GHOST_SystemCocoa::getWindowUnderCursor(int32_t x, int32_t y) } /** - * \note : returns coordinates in Cocoa screen coordinates + * \note returns coordinates in Cocoa screen coordinates. */ GHOST_TSuccess GHOST_SystemCocoa::getCursorPosition(int32_t &x, int32_t &y) const { @@ -800,7 +800,7 @@ GHOST_TSuccess GHOST_SystemCocoa::getCursorPosition(int32_t &x, int32_t &y) cons } /** - * \note : expect Cocoa screen coordinates + * \note expect Cocoa screen coordinates. */ GHOST_TSuccess GHOST_SystemCocoa::setCursorPosition(int32_t x, int32_t y) { diff --git a/source/blender/blenkernel/BKE_nla.h b/source/blender/blenkernel/BKE_nla.h index 11642763074..2613f4286f0 100644 --- a/source/blender/blenkernel/BKE_nla.h +++ b/source/blender/blenkernel/BKE_nla.h @@ -228,7 +228,7 @@ bool BKE_nlatrack_is_nonlocal_in_liboverride(const struct ID *id, const struct N * - the end frame of the previous strip, if the strip's track contains another strip on it left * - the macro MINFRAMEF, if no strips are to the left of this strip in its track * - * \param strip The strip to compute the left-hand-side 'frame limit' of. + * \param strip: The strip to compute the left-hand-side 'frame limit' of. * \return The beginning frame of the previous strip, or MINFRAMEF if no strips are next in that * track. */ @@ -241,7 +241,7 @@ float BKE_nlastrip_compute_frame_from_previous_strip(struct NlaStrip *strip); * - the begin frame of the next strip, if the strip's track contains another strip on it right * - the macro MAXFRAMEF, if no strips are to the right of this strip in its track * - * \param strip The strip to compute the right-hand-side 'frame limit' of. + * \param strip: The strip to compute the right-hand-side 'frame limit' of. * \return The beginning frame of the next strip, or MAXFRAMEF if no strips are next in that track. */ float BKE_nlastrip_compute_frame_to_next_strip(struct NlaStrip *strip); diff --git a/source/blender/makesrna/intern/rna_nla.c b/source/blender/makesrna/intern/rna_nla.c index bbe9ea13a0b..524e3134f9c 100644 --- a/source/blender/makesrna/intern/rna_nla.c +++ b/source/blender/makesrna/intern/rna_nla.c @@ -165,9 +165,9 @@ static void rna_NlaStrip_start_frame_set(PointerRNA *ptr, float value) /* Simply set the frame start in a valid range : if there are any NLA strips before/after, clamp * the start value. If the new start value is past-the-end, clamp it. Otherwise, set it. * - * NOTE : Unless neighbouring strips are transitions, NLASTRIP_MIN_LEN_THRESH is not needed, as + * NOTE: Unless neighboring strips are transitions, NLASTRIP_MIN_LEN_THRESH is not needed, as * strips can be 'glued' to one another. If they are however, ensure transitions have a bit of - * time alloted in order to be performed. + * time allotted in order to be performed. */ NlaStrip *data = (NlaStrip *)ptr->data; @@ -255,11 +255,11 @@ static void rna_NlaStrip_end_frame_set(PointerRNA *ptr, float value) data->end = value; /* The ONLY case where we actively modify the value set by the user, is in case the start value - * value is past the old end frame (here delta = NLASTRIP_MIN_LEN_THRESH) : + * value is past the old end frame (here delta = NLASTRIP_MIN_LEN_THRESH): * - if there's no "room" for the end frame to be placed at (new_start + delta), move old_end to - * the limit, and new_start to (limit - delta) + * the limit, and new_start to (limit - delta) * - otherwise, do _not_ change the end frame. This property is not accessible from the UI, and - * can only be set via scripts. The script should be responsable for setting the end frame. + * can only be set via scripts. The script should be responsible for setting the end frame. */ if (data->end < (data->start + NLASTRIP_MIN_LEN_THRESH)) { /* If before-the-allowed-start : */ @@ -269,7 +269,7 @@ static void rna_NlaStrip_end_frame_set(PointerRNA *ptr, float value) } } - /* Ensure transitions are kept "glued" to the strip : */ + /* Ensure transitions are kept "glued" to the strip: */ if (data->next && data->next->type == NLASTRIP_TYPE_TRANSITION) { data->next->start = data->end; } @@ -280,7 +280,7 @@ static void rna_NlaStrip_frame_end_ui_set(PointerRNA *ptr, float value) NlaStrip *data = (NlaStrip *)ptr->data; /* Changing the strip's end frame will update its action 'range' (defined by actstart->actend) to - * accomodate the extra length of the strip. No other parameters of the strip will change. But + * accommodate the extra length of the strip. No other parameters of the strip will change. But * this means we have to get the current strip's end frame right now : */ const float old_strip_end = data->end; @@ -312,10 +312,10 @@ static void rna_NlaStrip_frame_end_ui_set(PointerRNA *ptr, float value) /* Modify the strip's action end frame, or repeat based on : * - if data->repeat == 1.0f, modify the action end frame : - * - if the number of frames to substract is the number of frames, set the action end frame + * - if the number of frames to subtract is the number of frames, set the action end frame * to the action start + 1 and modify the end of the strip to add that frame * - if the number of frames - * - otherwise, modify the repeat property to accomodate for the new length + * - otherwise, modify the repeat property to accommodate for the new length */ float action_length_delta = (old_strip_end - data->end) / data->scale; /* If no repeats are used, then modify the action end frame : */ diff --git a/source/blender/windowmanager/xr/intern/wm_xr_intern.h b/source/blender/windowmanager/xr/intern/wm_xr_intern.h index bb24514457d..8ad694b6fd2 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_intern.h +++ b/source/blender/windowmanager/xr/intern/wm_xr_intern.h @@ -109,7 +109,7 @@ typedef struct wmXrDrawData { GHOST_XrPose base_pose; /** Base scale (uniform, world space). */ float base_scale; - /** Offset to _substract_ from the OpenXR eye and viewer pose to get the wanted effective pose + /** Offset to _subtract_ from the OpenXR eye and viewer pose to get the wanted effective pose * (e.g. a pose exactly at the landmark position). */ float eye_position_ofs[3]; /* Local/view space. */ } wmXrDrawData; -- cgit v1.2.3 From d9505831a4549e872bcef023ac082d002e8d8392 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 6 Jul 2022 14:25:33 +1000 Subject: Cleanup: declare local variables static --- intern/ghost/intern/GHOST_SystemWayland.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index c360423c256..8cd3476c4d6 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -1205,7 +1205,7 @@ static void cursor_buffer_handle_release(void *data, struct wl_buffer *wl_buffer } } -const struct wl_buffer_listener cursor_buffer_listener = { +static const struct wl_buffer_listener cursor_buffer_listener = { cursor_buffer_handle_release, }; @@ -1264,7 +1264,7 @@ static void cursor_surface_handle_leave(void *data, update_cursor_scale(input->cursor, input->system->shm()); } -struct wl_surface_listener cursor_surface_listener = { +static const struct wl_surface_listener cursor_surface_listener = { cursor_surface_handle_enter, cursor_surface_handle_leave, }; @@ -1750,7 +1750,7 @@ static void tablet_seat_handle_pad_added(void * /*data*/, /* Pass. */ } -const struct zwp_tablet_seat_v2_listener tablet_seat_listener = { +static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = { tablet_seat_handle_tablet_added, tablet_seat_handle_tool_added, tablet_seat_handle_pad_added, -- cgit v1.2.3 From e58e023e1a3e10f4ff2557aedcd730b5dba23579 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 5 Jul 2022 14:49:36 +1000 Subject: GHOST/Wayland: support dynamic loading libraries for Wayland Add intern/wayland_dynload which is used when WITH_GHOST_WAYLAND_DYNLOAD is enabled (off by default). When enabled, systems without Wayland installed will fall back to X11. This allows Blender to dynamically load: - libwayland-client - libwayland-cursor - libwayland-egl - libdecor-0 (when WITH_GHOST_WAYLAND_LIBDECOR is enabled). --- CMakeLists.txt | 3 + build_files/cmake/platform/platform_unix.cmake | 19 ++- intern/CMakeLists.txt | 7 + intern/ghost/CMakeLists.txt | 10 ++ intern/ghost/intern/GHOST_ISystem.cpp | 15 ++- intern/ghost/intern/GHOST_SystemWayland.cpp | 42 ++++++ intern/ghost/intern/GHOST_SystemWayland.h | 14 ++ intern/ghost/intern/GHOST_WindowWayland.cpp | 9 +- intern/wayland_dynload/CMakeLists.txt | 44 +++++++ .../wayland_dynload/extern/wayland_dynload_API.h | 31 +++++ .../extern/wayland_dynload_client.h | 128 ++++++++++++++++++ .../extern/wayland_dynload_cursor.h | 76 +++++++++++ .../wayland_dynload/extern/wayland_dynload_egl.h | 68 ++++++++++ .../extern/wayland_dynload_libdecor.h | 143 +++++++++++++++++++++ .../intern/wayland_dynload_client.c | 79 ++++++++++++ .../intern/wayland_dynload_cursor.c | 61 +++++++++ .../wayland_dynload/intern/wayland_dynload_egl.c | 61 +++++++++ .../intern/wayland_dynload_libdecor.c | 61 +++++++++ .../wayland_dynload/intern/wayland_dynload_utils.c | 40 ++++++ .../wayland_dynload/intern/wayland_dynload_utils.h | 29 +++++ 20 files changed, 931 insertions(+), 9 deletions(-) create mode 100644 intern/wayland_dynload/CMakeLists.txt create mode 100644 intern/wayland_dynload/extern/wayland_dynload_API.h create mode 100644 intern/wayland_dynload/extern/wayland_dynload_client.h create mode 100644 intern/wayland_dynload/extern/wayland_dynload_cursor.h create mode 100644 intern/wayland_dynload/extern/wayland_dynload_egl.h create mode 100644 intern/wayland_dynload/extern/wayland_dynload_libdecor.h create mode 100644 intern/wayland_dynload/intern/wayland_dynload_client.c create mode 100644 intern/wayland_dynload/intern/wayland_dynload_cursor.c create mode 100644 intern/wayland_dynload/intern/wayland_dynload_egl.c create mode 100644 intern/wayland_dynload/intern/wayland_dynload_libdecor.c create mode 100644 intern/wayland_dynload/intern/wayland_dynload_utils.c create mode 100644 intern/wayland_dynload/intern/wayland_dynload_utils.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b2b8c56001b..1416b5b4189 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -229,6 +229,9 @@ if(UNIX AND NOT (APPLE OR HAIKU)) option(WITH_GHOST_WAYLAND_DBUS "Optionally build with DBUS support (used for Cursor themes). May hang on startup systems where DBUS is not used." OFF) mark_as_advanced(WITH_GHOST_WAYLAND_DBUS) + + option(WITH_GHOST_WAYLAND_DYNLOAD "Enable runtime dynamic WAYLAND libraries loading" OFF) + mark_as_advanced(WITH_GHOST_WAYLAND_DYNLOAD) endif() endif() diff --git a/build_files/cmake/platform/platform_unix.cmake b/build_files/cmake/platform/platform_unix.cmake index dff860d9876..4b654420531 100644 --- a/build_files/cmake/platform/platform_unix.cmake +++ b/build_files/cmake/platform/platform_unix.cmake @@ -641,12 +641,17 @@ if(WITH_GHOST_WAYLAND) endif() list(APPEND PLATFORM_LINKLIBS - ${wayland-client_LINK_LIBRARIES} - ${wayland-egl_LINK_LIBRARIES} ${xkbcommon_LINK_LIBRARIES} - ${wayland-cursor_LINK_LIBRARIES} ) + if(NOT WITH_GHOST_WAYLAND_DYNLOAD) + list(APPEND PLATFORM_LINKLIBS + ${wayland-client_LINK_LIBRARIES} + ${wayland-egl_LINK_LIBRARIES} + ${wayland-cursor_LINK_LIBRARIES} + ) + endif() + if(WITH_GHOST_WAYLAND_DBUS) list(APPEND PLATFORM_LINKLIBS ${dbus_LINK_LIBRARIES} @@ -655,9 +660,11 @@ if(WITH_GHOST_WAYLAND) endif() if(WITH_GHOST_WAYLAND_LIBDECOR) - list(APPEND PLATFORM_LINKLIBS - ${libdecor_LIBRARIES} - ) + if(NOT WITH_GHOST_WAYLAND_DYNLOAD) + list(APPEND PLATFORM_LINKLIBS + ${libdecor_LIBRARIES} + ) + endif() add_definitions(-DWITH_GHOST_WAYLAND_LIBDECOR) endif() endif() diff --git a/intern/CMakeLists.txt b/intern/CMakeLists.txt index 2ff2fb39806..6387fd016ba 100644 --- a/intern/CMakeLists.txt +++ b/intern/CMakeLists.txt @@ -67,3 +67,10 @@ endif() if(UNIX AND NOT APPLE) add_subdirectory(libc_compat) endif() + +if(UNIX AND NOT APPLE) + # Important this comes after "ghost" as it uses includes defined by GHOST's CMake. + if(WITH_GHOST_WAYLAND AND WITH_GHOST_WAYLAND_DYNLOAD) + add_subdirectory(wayland_dynload) + endif() +endif() diff --git a/intern/ghost/CMakeLists.txt b/intern/ghost/CMakeLists.txt index 6a11d00cbc4..0203c5ecf5d 100644 --- a/intern/ghost/CMakeLists.txt +++ b/intern/ghost/CMakeLists.txt @@ -270,6 +270,16 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND) ${wayland-cursor_INCLUDE_DIRS} ) + if(WITH_GHOST_WAYLAND_DYNLOAD) + list(APPEND INC_SYS + ../../intern/wayland_dynload/extern + ) + list(APPEND LIB + bf_intern_wayland_dynload + ) + add_definitions(-DWITH_GHOST_WAYLAND_DYNLOAD) + endif() + if(WITH_GHOST_WAYLAND_DBUS) list(APPEND INC_SYS ${dbus_INCLUDE_DIRS} diff --git a/intern/ghost/intern/GHOST_ISystem.cpp b/intern/ghost/intern/GHOST_ISystem.cpp index 745d5faeed4..4f6a9531077 100644 --- a/intern/ghost/intern/GHOST_ISystem.cpp +++ b/intern/ghost/intern/GHOST_ISystem.cpp @@ -37,12 +37,23 @@ GHOST_TSuccess GHOST_ISystem::createSystem() { GHOST_TSuccess success; if (!m_system) { + +#if defined(WITH_HEADLESS) + /* Pass. */ +#elif defined(WITH_GHOST_WAYLAND) +# if defined(WITH_GHOST_WAYLAND_DYNLOAD) + const bool has_wayland_libraries = ghost_wl_dynload_libraries(); +# else + const bool has_wayland_libraries = true; +# endif +#endif + #if defined(WITH_HEADLESS) m_system = new GHOST_SystemNULL(); #elif defined(WITH_GHOST_X11) && defined(WITH_GHOST_WAYLAND) /* Special case, try Wayland, fall back to X11. */ try { - m_system = new GHOST_SystemWayland(); + m_system = has_wayland_libraries ? new GHOST_SystemWayland() : nullptr; } catch (const std::runtime_error &) { /* fallback to X11. */ @@ -55,7 +66,7 @@ GHOST_TSuccess GHOST_ISystem::createSystem() #elif defined(WITH_GHOST_X11) m_system = new GHOST_SystemX11(); #elif defined(WITH_GHOST_WAYLAND) - m_system = new GHOST_SystemWayland(); + m_system = has_wayland_libraries ? new GHOST_SystemWayland() : nullptr; #elif defined(WITH_GHOST_SDL) m_system = new GHOST_SystemSDL(); #elif defined(WIN32) diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 8cd3476c4d6..98526eb0bc6 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -16,7 +16,15 @@ #include "GHOST_ContextEGL.h" +#ifdef WITH_GHOST_WAYLAND_DYNLOAD +# include /* For `ghost_wl_dynload_libraries`. */ +#endif + #include + +#ifdef WITH_GHOST_WAYLAND_DYNLOAD +# include +#endif #include #include @@ -26,6 +34,9 @@ #include #include +#ifdef WITH_GHOST_WAYLAND_DYNLOAD +# include +#endif #include #include "GHOST_WaylandCursorSettings.h" @@ -3614,4 +3625,35 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod return GHOST_kSuccess; } +#ifdef WITH_GHOST_WAYLAND_DYNLOAD +bool ghost_wl_dynload_libraries() +{ + /* Only report when `libwayland-client` is not found when building without X11, + * which will be used as a fallback. */ +# ifdef WITH_GHOST_X11 + bool verbose = false; +# else + bool verbose = true; +# endif + + if (wayland_dynload_client_init(verbose) && /* `libwayland-client`. */ + wayland_dynload_cursor_init(verbose) && /* `libwayland-cursor`. */ + wayland_dynload_egl_init(verbose) && /* `libwayland-egl`. */ +# ifdef WITH_GHOST_WAYLAND_LIBDECOR + wayland_dynload_libdecor_init(verbose) && /* `libdecor-0`. */ +# endif + true) { + return true; + } +# ifdef WITH_GHOST_WAYLAND_LIBDECOR + wayland_dynload_libdecor_exit(); +# endif + wayland_dynload_client_exit(); + wayland_dynload_cursor_exit(); + wayland_dynload_egl_exit(); + + return false; +} +#endif /* WITH_GHOST_WAYLAND_DYNLOAD */ + /** \} */ diff --git a/intern/ghost/intern/GHOST_SystemWayland.h b/intern/ghost/intern/GHOST_SystemWayland.h index e336b02ec9e..e40448ff20b 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.h +++ b/intern/ghost/intern/GHOST_SystemWayland.h @@ -11,9 +11,15 @@ #include "GHOST_System.h" #include "GHOST_WindowWayland.h" +#ifdef WITH_GHOST_WAYLAND_DYNLOAD +# include +#endif #include #ifdef WITH_GHOST_WAYLAND_LIBDECOR +# ifdef WITH_GHOST_WAYLAND_DYNLOAD +# include +# endif # include #else /* Generated by `wayland-scanner`. */ @@ -35,6 +41,14 @@ bool ghost_wl_surface_own(const struct wl_surface *surface); void ghost_wl_surface_tag(struct wl_surface *surface); GHOST_WindowWayland *ghost_wl_surface_user_data(struct wl_surface *surface); +#ifdef WITH_GHOST_WAYLAND_DYNLOAD +/** + * Return true when all required WAYLAND libraries are present, + * Performs dynamic loading when `WITH_GHOST_WAYLAND_DYNLOAD` is in use. + */ +bool ghost_wl_dynload_libraries(); +#endif + struct output_t { struct wl_output *wl_output = nullptr; struct zxdg_output_v1 *xdg_output = nullptr; diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp index d26dfe0e7e6..77eddbb13a5 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cpp +++ b/intern/ghost/intern/GHOST_WindowWayland.cpp @@ -15,11 +15,18 @@ #include "GHOST_ContextNone.h" #include + +#ifdef WITH_GHOST_WAYLAND_DYNLOAD +# include +#endif #include #include /* For `std::find`. */ #ifdef WITH_GHOST_WAYLAND_LIBDECOR +# ifdef WITH_GHOST_WAYLAND_DYNLOAD +# include +# endif # include #endif @@ -347,7 +354,7 @@ static void surface_handle_leave(void *data, } } -struct wl_surface_listener wl_surface_listener = { +static struct wl_surface_listener wl_surface_listener = { surface_handle_enter, surface_handle_leave, }; diff --git a/intern/wayland_dynload/CMakeLists.txt b/intern/wayland_dynload/CMakeLists.txt new file mode 100644 index 00000000000..2b1a6370126 --- /dev/null +++ b/intern/wayland_dynload/CMakeLists.txt @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +set(INC + extern + + # For internal includes. + intern +) + +set(INC_SYS + ${wayland-client_INCLUDE_DIRS} + ${wayland-egl_INCLUDE_DIRS} + ${wayland-cursor_INCLUDE_DIRS} +) + +set(SRC + intern/wayland_dynload_client.c + intern/wayland_dynload_cursor.c + intern/wayland_dynload_egl.c + intern/wayland_dynload_utils.c + + extern/wayland_dynload_API.h + extern/wayland_dynload_client.h + extern/wayland_dynload_cursor.h + extern/wayland_dynload_egl.h + intern/wayland_dynload_utils.h +) + +if(WITH_GHOST_WAYLAND_LIBDECOR) + list(APPEND INC_SYS + ${libdecor_INCLUDE_DIRS} + ) + list(APPEND SRC + intern/wayland_dynload_libdecor.c + + extern/wayland_dynload_libdecor.h + ) +endif() + +set(LIB +) + + +blender_add_lib(bf_intern_wayland_dynload "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/intern/wayland_dynload/extern/wayland_dynload_API.h b/intern/wayland_dynload/extern/wayland_dynload_API.h new file mode 100644 index 00000000000..07ff00b5a76 --- /dev/null +++ b/intern/wayland_dynload/extern/wayland_dynload_API.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup intern_wayland_dynload + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +bool wayland_dynload_client_init(bool verbose); +void wayland_dynload_client_exit(void); + +bool wayland_dynload_cursor_init(bool verbose); +void wayland_dynload_cursor_exit(void); + +bool wayland_dynload_egl_init(bool verbose); +void wayland_dynload_egl_exit(void); + +#ifdef WITH_GHOST_WAYLAND_LIBDECOR +bool wayland_dynload_libdecor_init(bool verbose); +void wayland_dynload_libdecor_exit(void); +#endif + +#ifdef __cplusplus +} +#endif diff --git a/intern/wayland_dynload/extern/wayland_dynload_client.h b/intern/wayland_dynload/extern/wayland_dynload_client.h new file mode 100644 index 00000000000..8e9dddd91a3 --- /dev/null +++ b/intern/wayland_dynload/extern/wayland_dynload_client.h @@ -0,0 +1,128 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup intern_wayland_dynload + * + * Wrapper functions for ``. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef WAYLAND_DYNLOAD_FN +WAYLAND_DYNLOAD_FN(wl_display_connect) +WAYLAND_DYNLOAD_FN(wl_display_disconnect) +WAYLAND_DYNLOAD_FN(wl_display_dispatch) +WAYLAND_DYNLOAD_FN(wl_display_roundtrip) +WAYLAND_DYNLOAD_FN(wl_display_flush) +WAYLAND_DYNLOAD_FN(wl_log_set_handler_client) +WAYLAND_DYNLOAD_FN(wl_proxy_add_listener) +WAYLAND_DYNLOAD_FN(wl_proxy_destroy) +WAYLAND_DYNLOAD_FN(wl_proxy_marshal_flags) +WAYLAND_DYNLOAD_FN(wl_proxy_marshal_array_flags) +WAYLAND_DYNLOAD_FN(wl_proxy_set_user_data) +WAYLAND_DYNLOAD_FN(wl_proxy_get_user_data) +WAYLAND_DYNLOAD_FN(wl_proxy_get_version) +WAYLAND_DYNLOAD_FN(wl_proxy_get_tag) +WAYLAND_DYNLOAD_FN(wl_proxy_set_tag) +#elif defined(WAYLAND_DYNLOAD_IFACE) +WAYLAND_DYNLOAD_IFACE(wl_buffer_interface) +WAYLAND_DYNLOAD_IFACE(wl_compositor_interface) +WAYLAND_DYNLOAD_IFACE(wl_data_device_interface) +WAYLAND_DYNLOAD_IFACE(wl_data_device_manager_interface) +WAYLAND_DYNLOAD_IFACE(wl_data_source_interface) +WAYLAND_DYNLOAD_IFACE(wl_keyboard_interface) +WAYLAND_DYNLOAD_IFACE(wl_output_interface) +WAYLAND_DYNLOAD_IFACE(wl_pointer_interface) +WAYLAND_DYNLOAD_IFACE(wl_region_interface) +WAYLAND_DYNLOAD_IFACE(wl_registry_interface) +WAYLAND_DYNLOAD_IFACE(wl_seat_interface) +WAYLAND_DYNLOAD_IFACE(wl_shm_interface) +WAYLAND_DYNLOAD_IFACE(wl_shm_pool_interface) +WAYLAND_DYNLOAD_IFACE(wl_surface_interface) +#else + +/* Header guard. */ +# if !defined(__WAYLAND_DYNLOAD_CLIENT_H__) && !defined(WAYLAND_DYNLOAD_VALIDATE) +# define __WAYLAND_DYNLOAD_CLIENT_H__ + +# ifndef WAYLAND_DYNLOAD_VALIDATE +# include +extern struct WaylandDynload_Client wayland_dynload_client; +# endif + +/* Support validating declarations against the header. */ +# ifndef WAYLAND_DYNLOAD_VALIDATE +# define WL_DYN_FN(a) (*a) +# else +# define WL_DYN_FN(a) (a) +# endif + +# ifndef WAYLAND_DYNLOAD_VALIDATE +struct WaylandDynload_Client { +# endif + struct wl_display *WL_DYN_FN(wl_display_connect)(const char *name); + void WL_DYN_FN(wl_display_disconnect)(struct wl_display *display); + int WL_DYN_FN(wl_display_dispatch)(struct wl_display *display); + int WL_DYN_FN(wl_display_roundtrip)(struct wl_display *display); + int WL_DYN_FN(wl_display_flush)(struct wl_display *display); + void WL_DYN_FN(wl_log_set_handler_client)(wl_log_func_t); + int WL_DYN_FN(wl_proxy_add_listener)(struct wl_proxy *proxy, + void (**implementation)(void), + void *data); + void WL_DYN_FN(wl_proxy_destroy)(struct wl_proxy *proxy); + struct wl_proxy *WL_DYN_FN(wl_proxy_marshal_flags)(struct wl_proxy *proxy, + uint32_t opcode, + const struct wl_interface *interface, + uint32_t version, + uint32_t flags, + ...); + struct wl_proxy *WL_DYN_FN(wl_proxy_marshal_array_flags)(struct wl_proxy *proxy, + uint32_t opcode, + const struct wl_interface *interface, + uint32_t version, + uint32_t flags, + union wl_argument *args); + void WL_DYN_FN(wl_proxy_set_user_data)(struct wl_proxy *proxy, void *user_data); + void *WL_DYN_FN(wl_proxy_get_user_data)(struct wl_proxy *proxy); + uint32_t WL_DYN_FN(wl_proxy_get_version)(struct wl_proxy *proxy); + const char *const *WL_DYN_FN(wl_proxy_get_tag)(struct wl_proxy *proxy); + void WL_DYN_FN(wl_proxy_set_tag)(struct wl_proxy *proxy, const char *const *tag); + +# ifndef WAYLAND_DYNLOAD_VALIDATE +}; +# endif +# undef WL_DYN_FN + +# ifndef WAYLAND_DYNLOAD_VALIDATE +# define wl_display_connect(...) (*wayland_dynload_client.wl_display_connect)(__VA_ARGS__) +# define wl_display_disconnect(...) \ + (*wayland_dynload_client.wl_display_disconnect)(__VA_ARGS__) +# define wl_display_dispatch(...) (*wayland_dynload_client.wl_display_dispatch)(__VA_ARGS__) +# define wl_display_roundtrip(...) (*wayland_dynload_client.wl_display_roundtrip)(__VA_ARGS__) +# define wl_display_flush(...) (*wayland_dynload_client.wl_display_flush)(__VA_ARGS__) +# define wl_log_set_handler_client(...) \ + (*wayland_dynload_client.wl_log_set_handler_client)(__VA_ARGS__) +# define wl_proxy_add_listener(...) \ + (*wayland_dynload_client.wl_proxy_add_listener)(__VA_ARGS__) +# define wl_proxy_destroy(...) (*wayland_dynload_client.wl_proxy_destroy)(__VA_ARGS__) +# define wl_proxy_marshal_flags(...) \ + (*wayland_dynload_client.wl_proxy_marshal_flags)(__VA_ARGS__) +# define wl_proxy_marshal_array_flags(...) \ + (*wayland_dynload_client.wl_proxy_marshal_array_flags)(__VA_ARGS__) +# define wl_proxy_set_user_data(...) \ + (*wayland_dynload_client.wl_proxy_set_user_data)(__VA_ARGS__) +# define wl_proxy_get_user_data(...) \ + (*wayland_dynload_client.wl_proxy_get_user_data)(__VA_ARGS__) +# define wl_proxy_get_version(...) (*wayland_dynload_client.wl_proxy_get_version)(__VA_ARGS__) +# define wl_proxy_get_tag(...) (*wayland_dynload_client.wl_proxy_get_tag)(__VA_ARGS__) +# define wl_proxy_set_tag(...) (*wayland_dynload_client.wl_proxy_set_tag)(__VA_ARGS__) + +# endif /* !WAYLAND_DYNLOAD_VALIDATE */ +# endif /* !defined(__WAYLAND_DYNLOAD_CLIENT_H__) && !defined(WAYLAND_DYNLOAD_VALIDATE) */ +#endif /* !defined(WAYLAND_DYNLOAD_FN) && !defined(WAYLAND_DYNLOAD_IFACE) */ + +#ifdef __cplusplus +} +#endif diff --git a/intern/wayland_dynload/extern/wayland_dynload_cursor.h b/intern/wayland_dynload/extern/wayland_dynload_cursor.h new file mode 100644 index 00000000000..3c444069a43 --- /dev/null +++ b/intern/wayland_dynload/extern/wayland_dynload_cursor.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup intern_wayland_dynload + * + * Wrapper functions for ``. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef WAYLAND_DYNLOAD_FN +WAYLAND_DYNLOAD_FN(wl_cursor_theme_load) +WAYLAND_DYNLOAD_FN(wl_cursor_theme_destroy) +WAYLAND_DYNLOAD_FN(wl_cursor_theme_get_cursor) +WAYLAND_DYNLOAD_FN(wl_cursor_image_get_buffer) +WAYLAND_DYNLOAD_FN(wl_cursor_frame) +WAYLAND_DYNLOAD_FN(wl_cursor_frame_and_duration) +#elif defined(WAYLAND_DYNLOAD_IFACE) +/* No interfaces. */ +#else + +/* Header guard. */ +# if !defined(__WAYLAND_DYNLOAD_CURSOR_H__) && !defined(WAYLAND_DYNLOAD_VALIDATE) +# define __WAYLAND_DYNLOAD_CURSOR_H__ + +# include +extern struct WaylandDynload_Cursor wayland_dynload_cursor; + +/* Support validating declarations against the header. */ +# ifndef WAYLAND_DYNLOAD_VALIDATE +# define WL_DYN_FN(a) (*a) +# else +# define WL_DYN_FN(a) (a) +# endif + +# ifndef WAYLAND_DYNLOAD_VALIDATE +struct WaylandDynload_Cursor { +# endif + + struct wl_cursor_theme *WL_DYN_FN(wl_cursor_theme_load)(const char *name, + int size, + struct wl_shm *shm); + void WL_DYN_FN(wl_cursor_theme_destroy)(struct wl_cursor_theme *theme); + struct wl_cursor *WL_DYN_FN(wl_cursor_theme_get_cursor)(struct wl_cursor_theme *theme, + const char *name); + struct wl_buffer *WL_DYN_FN(wl_cursor_image_get_buffer)(struct wl_cursor_image *image); + int WL_DYN_FN(wl_cursor_frame)(struct wl_cursor *cursor, uint32_t time); + int WL_DYN_FN(wl_cursor_frame_and_duration)(struct wl_cursor *cursor, + uint32_t time, + uint32_t *duration); + +# ifndef WAYLAND_DYNLOAD_VALIDATE +}; +# endif +# undef WL_DYN_FN + +# ifndef WAYLAND_DYNLOAD_VALIDATE +# define wl_cursor_theme_load(...) (*wayland_dynload_cursor.wl_cursor_theme_load)(__VA_ARGS__) +# define wl_cursor_theme_destroy(...) \ + (*wayland_dynload_cursor.wl_cursor_theme_destroy)(__VA_ARGS__) +# define wl_cursor_theme_get_cursor(...) \ + (*wayland_dynload_cursor.wl_cursor_theme_get_cursor)(__VA_ARGS__) +# define wl_cursor_image_get_buffer(...) \ + (*wayland_dynload_cursor.wl_cursor_image_get_buffer)(__VA_ARGS__) +# define wl_cursor_frame(...) (*wayland_dynload_cursor.wl_cursor_frame)(__VA_ARGS__) +# define wl_cursor_frame_and_duration(...) \ + (*wayland_dynload_cursor.wl_cursor_frame_and_duration)(__VA_ARGS__) +# endif /* !WAYLAND_DYNLOAD_VALIDATE */ +# endif /* !__WAYLAND_DYNLOAD_CLIENT_H__ */ +#endif /* !defined(WAYLAND_DYNLOAD_FN) && !defined(WAYLAND_DYNLOAD_IFACE) */ + +#ifdef __cplusplus +} +#endif diff --git a/intern/wayland_dynload/extern/wayland_dynload_egl.h b/intern/wayland_dynload/extern/wayland_dynload_egl.h new file mode 100644 index 00000000000..3829ac83301 --- /dev/null +++ b/intern/wayland_dynload/extern/wayland_dynload_egl.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup intern_wayland_dynload + * + * Wrapper functions for ``. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef WAYLAND_DYNLOAD_FN +WAYLAND_DYNLOAD_FN(wl_egl_window_create) +WAYLAND_DYNLOAD_FN(wl_egl_window_destroy) +WAYLAND_DYNLOAD_FN(wl_egl_window_resize) +WAYLAND_DYNLOAD_FN(wl_egl_window_get_attached_size) +#elif defined(WAYLAND_DYNLOAD_IFACE) +/* No interfaces. */ +#else + +/* Header guard. */ +# if !defined(__WAYLAND_DYNLOAD_EGL_H__) && !defined(WAYLAND_DYNLOAD_VALIDATE) +# define __WAYLAND_DYNLOAD_EGL_H__ + +# include +extern struct WaylandDynload_EGL wayland_dynload_egl; + +/* Support validating declarations against the header. */ +# ifndef WAYLAND_DYNLOAD_VALIDATE +# define WL_DYN_FN(a) (*a) +# else +# define WL_DYN_FN(a) (a) +# endif + +# ifndef WAYLAND_DYNLOAD_VALIDATE +struct WaylandDynload_EGL { +# endif + + struct wl_egl_window *WL_DYN_FN(wl_egl_window_create)(struct wl_surface *surface, + int width, + int height); + void WL_DYN_FN(wl_egl_window_destroy)(struct wl_egl_window *egl_window); + void WL_DYN_FN(wl_egl_window_resize)( + struct wl_egl_window *egl_window, int width, int height, int dx, int dy); + void WL_DYN_FN(wl_egl_window_get_attached_size)(struct wl_egl_window *egl_window, + int *width, + int *height); + +# ifndef WAYLAND_DYNLOAD_VALIDATE +}; +# endif +# undef WL_DYN_FN + +# ifndef WAYLAND_DYNLOAD_VALIDATE +# define wl_egl_window_create(...) (*wayland_dynload_egl.wl_egl_window_create)(__VA_ARGS__) +# define wl_egl_window_destroy(...) (*wayland_dynload_egl.wl_egl_window_destroy)(__VA_ARGS__) +# define wl_egl_window_resize(...) (*wayland_dynload_egl.wl_egl_window_resize)(__VA_ARGS__) +# define wl_egl_window_get_attached_size(...) \ + (*wayland_dynload_egl.wl_egl_window_get_attached_size)(__VA_ARGS__) + +# endif /* !WAYLAND_DYNLOAD_VALIDATE */ +# endif /* !defined(WAYLAND_DYNLOAD_FN) && !defined(WAYLAND_DYNLOAD_IFACE) */ +#endif /* !defined(__WAYLAND_DYNLOAD_EGL_H__) && !defined(WAYLAND_DYNLOAD_VALIDATE) */ + +#ifdef __cplusplus +} +#endif diff --git a/intern/wayland_dynload/extern/wayland_dynload_libdecor.h b/intern/wayland_dynload/extern/wayland_dynload_libdecor.h new file mode 100644 index 00000000000..9dcecb4d876 --- /dev/null +++ b/intern/wayland_dynload/extern/wayland_dynload_libdecor.h @@ -0,0 +1,143 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup intern_wayland_dynload + * + * Wrapper functions for ``. + * + * \note Not part of WAYLAND, but used with WAYLAND by GHOST. + * It follows WAYLAND conventions and is a middle-ware library that depends on `libwayland-client`. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef WAYLAND_DYNLOAD_FN +WAYLAND_DYNLOAD_FN(libdecor_configuration_get_content_size) +WAYLAND_DYNLOAD_FN(libdecor_configuration_get_window_state) +WAYLAND_DYNLOAD_FN(libdecor_decorate) +WAYLAND_DYNLOAD_FN(libdecor_dispatch) +WAYLAND_DYNLOAD_FN(libdecor_frame_commit) +WAYLAND_DYNLOAD_FN(libdecor_frame_map) +WAYLAND_DYNLOAD_FN(libdecor_frame_set_app_id) +WAYLAND_DYNLOAD_FN(libdecor_frame_set_fullscreen) +WAYLAND_DYNLOAD_FN(libdecor_frame_set_maximized) +WAYLAND_DYNLOAD_FN(libdecor_frame_set_min_content_size) +WAYLAND_DYNLOAD_FN(libdecor_frame_set_minimized) +WAYLAND_DYNLOAD_FN(libdecor_frame_set_parent) +WAYLAND_DYNLOAD_FN(libdecor_frame_set_title) +WAYLAND_DYNLOAD_FN(libdecor_frame_unref) +WAYLAND_DYNLOAD_FN(libdecor_frame_unset_fullscreen) +WAYLAND_DYNLOAD_FN(libdecor_frame_unset_maximized) +WAYLAND_DYNLOAD_FN(libdecor_new) +WAYLAND_DYNLOAD_FN(libdecor_state_free) +WAYLAND_DYNLOAD_FN(libdecor_state_new) +WAYLAND_DYNLOAD_FN(libdecor_unref) +#elif defined(WAYLAND_DYNLOAD_IFACE) +/* No interfaces. */ +#else + +/* Header guard. */ +# if !defined(__WAYLAND_DYNLOAD_LIBDECOR_H__) && !defined(WAYLAND_DYNLOAD_VALIDATE) +# define __WAYLAND_DYNLOAD_LIBDECOR_H__ + +# ifndef WAYLAND_DYNLOAD_VALIDATE +# include +extern struct WaylandDynload_Libdecor wayland_dynload_libdecor; +# endif + +/* Support validating declarations against the header. */ +# ifndef WAYLAND_DYNLOAD_VALIDATE +# define WL_DYN_FN(sym) (*sym) +# else +# define WL_DYN_FN(sym) (sym) +# endif + +# ifndef WAYLAND_DYNLOAD_VALIDATE +struct WaylandDynload_Libdecor { +# endif + + bool WL_DYN_FN(libdecor_configuration_get_content_size)( + struct libdecor_configuration *configuration, + struct libdecor_frame *frame, + int *width, + int *height); + bool WL_DYN_FN(libdecor_configuration_get_window_state)( + struct libdecor_configuration *configuration, enum libdecor_window_state *window_state); + struct libdecor_frame *WL_DYN_FN(libdecor_decorate)(struct libdecor *context, + struct wl_surface *surface, + struct libdecor_frame_interface *iface, + void *user_data); + int WL_DYN_FN(libdecor_dispatch)(struct libdecor *context, int timeout); + void WL_DYN_FN(libdecor_frame_commit)(struct libdecor_frame *frame, + struct libdecor_state *state, + struct libdecor_configuration *configuration); + void WL_DYN_FN(libdecor_frame_map)(struct libdecor_frame *frame); + void WL_DYN_FN(libdecor_frame_set_app_id)(struct libdecor_frame *frame, const char *app_id); + void WL_DYN_FN(libdecor_frame_set_fullscreen)(struct libdecor_frame *frame, + struct wl_output *output); + void WL_DYN_FN(libdecor_frame_set_maximized)(struct libdecor_frame *frame); + void WL_DYN_FN(libdecor_frame_set_min_content_size)(struct libdecor_frame *frame, + int content_width, + int content_height); + void WL_DYN_FN(libdecor_frame_set_minimized)(struct libdecor_frame *frame); + void WL_DYN_FN(libdecor_frame_set_parent)(struct libdecor_frame *frame, + struct libdecor_frame *parent); + void WL_DYN_FN(libdecor_frame_set_title)(struct libdecor_frame *frame, const char *title); + void WL_DYN_FN(libdecor_frame_unref)(struct libdecor_frame *frame); + void WL_DYN_FN(libdecor_frame_unset_fullscreen)(struct libdecor_frame *frame); + void WL_DYN_FN(libdecor_frame_unset_maximized)(struct libdecor_frame *frame); + struct libdecor *WL_DYN_FN(libdecor_new)(struct wl_display *display, + struct libdecor_interface *iface); + void WL_DYN_FN(libdecor_state_free)(struct libdecor_state *state); + struct libdecor_state *WL_DYN_FN(libdecor_state_new)(int width, int height); + void WL_DYN_FN(libdecor_unref)(struct libdecor *context); + +# ifndef WAYLAND_DYNLOAD_VALIDATE +}; +# endif +# undef WL_DYN_FN + +# ifndef WAYLAND_DYNLOAD_VALIDATE +# define libdecor_configuration_get_content_size(...) \ + (*wayland_dynload_libdecor.libdecor_configuration_get_content_size)(__VA_ARGS__) +# define libdecor_configuration_get_window_state(...) \ + (*wayland_dynload_libdecor.libdecor_configuration_get_window_state)(__VA_ARGS__) +# define libdecor_decorate(...) (*wayland_dynload_libdecor.libdecor_decorate)(__VA_ARGS__) +# define libdecor_dispatch(...) (*wayland_dynload_libdecor.libdecor_dispatch)(__VA_ARGS__) +# define libdecor_frame_commit(...) \ + (*wayland_dynload_libdecor.libdecor_frame_commit)(__VA_ARGS__) +# define libdecor_frame_map(...) (*wayland_dynload_libdecor.libdecor_frame_map)(__VA_ARGS__) +# define libdecor_frame_set_app_id(...) \ + (*wayland_dynload_libdecor.libdecor_frame_set_app_id)(__VA_ARGS__) +# define libdecor_frame_set_fullscreen(...) \ + (*wayland_dynload_libdecor.libdecor_frame_set_fullscreen)(__VA_ARGS__) +# define libdecor_frame_set_maximized(...) \ + (*wayland_dynload_libdecor.libdecor_frame_set_maximized)(__VA_ARGS__) +# define libdecor_frame_set_min_content_size(...) \ + (*wayland_dynload_libdecor.libdecor_frame_set_min_content_size)(__VA_ARGS__) +# define libdecor_frame_set_minimized(...) \ + (*wayland_dynload_libdecor.libdecor_frame_set_minimized)(__VA_ARGS__) +# define libdecor_frame_set_parent(...) \ + (*wayland_dynload_libdecor.libdecor_frame_set_parent)(__VA_ARGS__) +# define libdecor_frame_set_title(...) \ + (*wayland_dynload_libdecor.libdecor_frame_set_title)(__VA_ARGS__) +# define libdecor_frame_unref(...) \ + (*wayland_dynload_libdecor.libdecor_frame_unref)(__VA_ARGS__) +# define libdecor_frame_unset_fullscreen(...) \ + (*wayland_dynload_libdecor.libdecor_frame_unset_fullscreen)(__VA_ARGS__) +# define libdecor_frame_unset_maximized(...) \ + (*wayland_dynload_libdecor.libdecor_frame_unset_maximized)(__VA_ARGS__) +# define libdecor_new(...) (*wayland_dynload_libdecor.libdecor_new)(__VA_ARGS__) +# define libdecor_state_free(...) (*wayland_dynload_libdecor.libdecor_state_free)(__VA_ARGS__) +# define libdecor_state_new(...) (*wayland_dynload_libdecor.libdecor_state_new)(__VA_ARGS__) +# define libdecor_unref(...) (*wayland_dynload_libdecor.libdecor_unref)(__VA_ARGS__) + +# endif /* !WAYLAND_DYNLOAD_VALIDATE */ +# endif /* !defined(__WAYLAND_DYNLOAD_LIBDECOR_H__) && !defined(WAYLAND_DYNLOAD_VALIDATE) */ +#endif /* !defined(WAYLAND_DYNLOAD_FN) && !defined(WAYLAND_DYNLOAD_IFACE) */ + +#ifdef __cplusplus +} +#endif diff --git a/intern/wayland_dynload/intern/wayland_dynload_client.c b/intern/wayland_dynload/intern/wayland_dynload_client.c new file mode 100644 index 00000000000..68ba5374aba --- /dev/null +++ b/intern/wayland_dynload/intern/wayland_dynload_client.c @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup intern_wayland_dynload + * + * Wrapper functions for ``. + */ + +#include /* `atexit`. */ +#include + +#include "wayland_dynload_API.h" +#include "wayland_dynload_utils.h" + +#include "wayland_dynload_client.h" /* Own include. */ + +/* Public handle. */ +struct WaylandDynload_Client wayland_dynload_client = {NULL}; + +static DynamicLibrary lib = NULL; + +#define WAYLAND_DYNLOAD_IFACE(symbol) \ + extern struct wl_interface symbol; \ + struct wl_interface symbol; +#include "wayland_dynload_client.h" +#undef WAYLAND_DYNLOAD_IFACE + +bool wayland_dynload_client_init(const bool verbose) +{ + /* Library paths. */ + const char *paths[] = { + "libwayland-client.so.0", + "libwayland-client.so", + }; + const int paths_num = sizeof(paths) / sizeof(*paths); + int path_found; + if (!(lib = dynamic_library_open_array_with_error(paths, paths_num, verbose, &path_found))) { + return false; + } + if (atexit(wayland_dynload_client_exit)) { + return false; + } + +#define WAYLAND_DYNLOAD_IFACE(symbol) \ + { \ + const void *symbol_val; \ + if (!(symbol_val = dynamic_library_find_with_error(lib, #symbol, paths[path_found]))) { \ + return false; \ + } \ + memcpy(&symbol, symbol_val, sizeof(symbol)); \ + } +#include "wayland_dynload_client.h" +#undef WAYLAND_DYNLOAD_IFACE + +#define WAYLAND_DYNLOAD_FN(symbol) \ + if (!(wayland_dynload_client.symbol = dynamic_library_find_with_error( \ + lib, #symbol, paths[path_found]))) { \ + return false; \ + } +#include "wayland_dynload_client.h" +#undef WAYLAND_DYNLOAD_FN + + return true; +} + +void wayland_dynload_client_exit(void) +{ + if (lib != NULL) { + dynamic_library_close(lib); /* Ignore errors. */ + lib = NULL; + } +} + +/* Validate local signatures against the original header. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wredundant-decls" +#define WAYLAND_DYNLOAD_VALIDATE +#include "wayland_dynload_client.h" +#pragma GCC diagnostic pop diff --git a/intern/wayland_dynload/intern/wayland_dynload_cursor.c b/intern/wayland_dynload/intern/wayland_dynload_cursor.c new file mode 100644 index 00000000000..3d0526c7ba6 --- /dev/null +++ b/intern/wayland_dynload/intern/wayland_dynload_cursor.c @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup intern_wayland_dynload + * + * Wrapper functions for ``. + */ + +#include /* `atexit`. */ +#include + +#include "wayland_dynload_API.h" +#include "wayland_dynload_utils.h" + +#include "wayland_dynload_cursor.h" /* Own include. */ + +struct WaylandDynload_Cursor wayland_dynload_cursor = {NULL}; + +static DynamicLibrary lib = NULL; + +bool wayland_dynload_cursor_init(const bool verbose) +{ + /* Library paths. */ + const char *paths[] = { + "libwayland-cursor.so.0", + "libwayland-cursor.so", + }; + const int paths_num = sizeof(paths) / sizeof(*paths); + int path_index; + if (!(lib = dynamic_library_open_array_with_error(paths, paths_num, verbose, &path_index))) { + return false; + } + if (atexit(wayland_dynload_cursor_exit)) { + return false; + } + +#define WAYLAND_DYNLOAD_FN(symbol) \ + if (!(wayland_dynload_cursor.symbol = dynamic_library_find_with_error( \ + lib, #symbol, paths[path_index]))) { \ + return false; \ + } +#include "wayland_dynload_cursor.h" +#undef WAYLAND_DYNLOAD_FN + + return true; +} + +void wayland_dynload_cursor_exit(void) +{ + if (lib != NULL) { + dynamic_library_close(lib); /* Ignore errors. */ + lib = NULL; + } +} + +/* Validate local signatures against the original header. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wredundant-decls" +#define WAYLAND_DYNLOAD_VALIDATE +#include "wayland_dynload_cursor.h" +#pragma GCC diagnostic pop diff --git a/intern/wayland_dynload/intern/wayland_dynload_egl.c b/intern/wayland_dynload/intern/wayland_dynload_egl.c new file mode 100644 index 00000000000..b62adb6c90e --- /dev/null +++ b/intern/wayland_dynload/intern/wayland_dynload_egl.c @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup intern_wayland_dynload + * + * Wrapper functions for ``. + */ + +#include /* `atexit`. */ + +#include "wayland_dynload_API.h" +#include "wayland_dynload_utils.h" + +#include "wayland_dynload_egl.h" /* Own include. */ + +/* Public handle. */ +struct WaylandDynload_EGL wayland_dynload_egl = {NULL}; + +static DynamicLibrary lib = NULL; + +bool wayland_dynload_egl_init(const bool verbose) +{ + /* Library paths. */ + const char *paths[] = { + "libwayland-egl.so.0", + "libwayland-egl.so", + }; + const int paths_num = sizeof(paths) / sizeof(*paths); + int path_found = 0; + if (!(lib = dynamic_library_open_array_with_error(paths, paths_num, verbose, &path_found))) { + return false; + } + if (atexit(wayland_dynload_egl_exit)) { + return false; + } + +#define WAYLAND_DYNLOAD_FN(symbol) \ + if (!(wayland_dynload_egl.symbol = dynamic_library_find_with_error( \ + lib, #symbol, paths[path_found]))) { \ + return false; \ + } +#include "wayland_dynload_egl.h" +#undef WAYLAND_DYNLOAD_FN + + return true; +} + +void wayland_dynload_egl_exit(void) +{ + if (lib != NULL) { + dynamic_library_close(lib); /* Ignore errors. */ + lib = NULL; + } +} + +/* Validate local signatures against the original header. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wredundant-decls" +#define WAYLAND_DYNLOAD_VALIDATE +#include "wayland_dynload_egl.h" +#pragma GCC diagnostic pop diff --git a/intern/wayland_dynload/intern/wayland_dynload_libdecor.c b/intern/wayland_dynload/intern/wayland_dynload_libdecor.c new file mode 100644 index 00000000000..d8bdd27bb27 --- /dev/null +++ b/intern/wayland_dynload/intern/wayland_dynload_libdecor.c @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup intern_wayland_dynload + * + * Wrapper functions for ``. + */ + +#include /* `atexit`. */ + +#include "wayland_dynload_API.h" +#include "wayland_dynload_utils.h" + +#include "wayland_dynload_libdecor.h" /* Own include. */ + +/* Public handle. */ +struct WaylandDynload_Libdecor wayland_dynload_libdecor = {NULL}; + +static DynamicLibrary lib = NULL; + +bool wayland_dynload_libdecor_init(const bool verbose) +{ + /* Library paths. */ + const char *paths[] = { + "libdecor-0.so.0", + "libdecor-0.so", + }; + const int paths_num = sizeof(paths) / sizeof(*paths); + int path_index; + if (!(lib = dynamic_library_open_array_with_error(paths, paths_num, verbose, &path_index))) { + return false; + } + if (atexit(wayland_dynload_libdecor_exit)) { + return false; + } + +#define WAYLAND_DYNLOAD_FN(symbol) \ + if (!(wayland_dynload_libdecor.symbol = dynamic_library_find_with_error( \ + lib, #symbol, paths[path_index]))) { \ + return false; \ + } +#include "wayland_dynload_libdecor.h" +#undef WAYLAND_DYNLOAD_FN + + return true; +} + +void wayland_dynload_libdecor_exit(void) +{ + if (lib != NULL) { + dynamic_library_close(lib); /* Ignore errors. */ + lib = NULL; + } +} + +/* Validate local signatures against the original header. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wredundant-decls" +#define WAYLAND_DYNLOAD_VALIDATE +#include "wayland_dynload_libdecor.h" +#pragma GCC diagnostic pop diff --git a/intern/wayland_dynload/intern/wayland_dynload_utils.c b/intern/wayland_dynload/intern/wayland_dynload_utils.c new file mode 100644 index 00000000000..743dac14eec --- /dev/null +++ b/intern/wayland_dynload/intern/wayland_dynload_utils.c @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup intern_wayland_dynload + */ + +#include + +#include "wayland_dynload_utils.h" + +DynamicLibrary dynamic_library_open_array_with_error(const char **paths, + const int paths_num, + const bool verbose, + int *r_path_index) +{ + DynamicLibrary lib = NULL; + for (int a = 0; a < paths_num; a++) { + lib = dynamic_library_open(paths[a]); + if (lib) { + *r_path_index = a; + break; + } + } + if (lib == NULL) { + /* Use the last path as it's likely to be least specific. */ + if (verbose) { + fprintf(stderr, "Unable to find '%s'\n", paths[paths_num - 1]); + } + } + return lib; +} + +void *dynamic_library_find_with_error(DynamicLibrary lib, const char *symbol, const char *path_lib) +{ + void *symbol_var = dynamic_library_find(lib, symbol); + if (symbol_var == NULL) { + fprintf(stderr, "Unable to find '%s' in '%s'.\n", symbol, path_lib); + } + return symbol_var; +} diff --git a/intern/wayland_dynload/intern/wayland_dynload_utils.h b/intern/wayland_dynload/intern/wayland_dynload_utils.h new file mode 100644 index 00000000000..785f32521e4 --- /dev/null +++ b/intern/wayland_dynload/intern/wayland_dynload_utils.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup intern_wayland_dynload + * + * Utility defines. + */ + +#pragma once + +#include /* Dynamic loading. */ +#include + +typedef void *DynamicLibrary; + +#define dynamic_library_open(path) dlopen(path, RTLD_NOW) +#define dynamic_library_close(lib) dlclose(lib) +#define dynamic_library_find(lib, symbol) dlsym(lib, symbol) + +/** Loads a library from an array, printing an error when the symbol isn't found. */ +DynamicLibrary dynamic_library_open_array_with_error(const char **paths, + int paths_num, + bool verbose, + int *r_path_index); + +/** Find a symbol, printing an error when the symbol isn't found. */ +void *dynamic_library_find_with_error(DynamicLibrary lib, + const char *symbol, + const char *path_lib); -- cgit v1.2.3 From 26f721b51638f4a350e19c18406298f8cc57f453 Mon Sep 17 00:00:00 2001 From: Aras Pranckevicius Date: Wed, 6 Jul 2022 09:05:20 +0300 Subject: OBJ: extend test coverage for parsing MTL scale/offsets (T89421) The new OBJ/MTL importer was already handling case T89421 correctly, but there was no test coverage to prove it. Extend the tests to parse various forms of "-o" and "-s" (one, two, three numbers). --- .../io/wavefront_obj/tests/obj_mtl_parser_tests.cc | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/source/blender/io/wavefront_obj/tests/obj_mtl_parser_tests.cc b/source/blender/io/wavefront_obj/tests/obj_mtl_parser_tests.cc index 068cdc0bf3a..5b909865d9b 100644 --- a/source/blender/io/wavefront_obj/tests/obj_mtl_parser_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_mtl_parser_tests.cc @@ -158,7 +158,7 @@ TEST_F(obj_mtl_parser_test, all_objects) TEST_F(obj_mtl_parser_test, materials) { - MTLMaterial mat[5]; + MTLMaterial mat[6]; mat[0].name = "no_textures_red"; mat[0].Ka = {0.3f, 0.3f, 0.3f}; mat[0].Kd = {0.8f, 0.3f, 0.1f}; @@ -236,6 +236,20 @@ TEST_F(obj_mtl_parser_test, materials) bump.scale = {3, 4, 5}; } + mat[5].name = "Parser_ScaleOffset_Test"; + { + tex_map_XX &kd = mat[5].tex_map_of_type(eMTLSyntaxElement::map_Kd); + kd.translation = {2.5f, 0.0f, 0.0f}; + kd.image_path = "OffsetOneValue.png"; + tex_map_XX &ks = mat[5].tex_map_of_type(eMTLSyntaxElement::map_Ks); + ks.scale = {1.5f, 2.5f, 1.0f}; + ks.translation = {3.5f, 4.5f, 0.0f}; + ks.image_path = "ScaleOffsetBothTwovalues.png"; + tex_map_XX &ns = mat[5].tex_map_of_type(eMTLSyntaxElement::map_Ns); + ns.scale = {0.5f, 1.0f, 1.0f}; + ns.image_path = "ScaleOneValue.png"; + } + check("materials.mtl", mat, ARRAY_SIZE(mat)); } -- cgit v1.2.3 From 4b0e7fe511b14aecad105d83115592f43d27dbc0 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 6 Jul 2022 10:53:22 +0200 Subject: Fix T99462: Deleting Missing Libraries Crashes Blender. Usual same issue with outliner operations, where you apply it on one item, and then try to apply it again on same item listed somewhere else in the tree... Fixed by using the 'multi-tagged deletion' code we now have for IDs, that way tree-walking function just tags IDs for deletion, and they all get deleted at once at the end. --- .../editors/space_outliner/outliner_edit.cc | 67 +++++++++++++--------- .../editors/space_outliner/outliner_intern.hh | 2 +- .../editors/space_outliner/outliner_tools.cc | 11 +++- 3 files changed, 49 insertions(+), 31 deletions(-) diff --git a/source/blender/editors/space_outliner/outliner_edit.cc b/source/blender/editors/space_outliner/outliner_edit.cc index c4a9398a5f7..32860bc2cff 100644 --- a/source/blender/editors/space_outliner/outliner_edit.cc +++ b/source/blender/editors/space_outliner/outliner_edit.cc @@ -447,7 +447,7 @@ void OUTLINER_OT_item_rename(wmOperatorType *ot) /** \name ID Delete Operator * \{ */ -static void id_delete(bContext *C, ReportList *reports, TreeElement *te, TreeStoreElem *tselem) +static void id_delete_tag(bContext *C, ReportList *reports, TreeElement *te, TreeStoreElem *tselem) { Main *bmain = CTX_data_main(C); ID *id = tselem->id; @@ -484,35 +484,39 @@ static void id_delete(bContext *C, ReportList *reports, TreeElement *te, TreeSto return; } if (te->idcode == ID_WS) { - BKE_workspace_id_tag_all_visible(bmain, LIB_TAG_DOIT); - if (id->tag & LIB_TAG_DOIT) { + BKE_workspace_id_tag_all_visible(bmain, LIB_TAG_PRE_EXISTING); + if (id->tag & LIB_TAG_PRE_EXISTING) { BKE_reportf( reports, RPT_WARNING, "Cannot delete currently visible workspace id '%s'", id->name); + BKE_main_id_tag_idcode(bmain, ID_WS, LIB_TAG_PRE_EXISTING, false); return; } + BKE_main_id_tag_idcode(bmain, ID_WS, LIB_TAG_PRE_EXISTING, false); } - BKE_id_delete(bmain, id); + id->tag |= LIB_TAG_DOIT; WM_event_add_notifier(C, NC_WINDOW, nullptr); } -void id_delete_fn(bContext *C, - ReportList *reports, - Scene *UNUSED(scene), - TreeElement *te, - TreeStoreElem *UNUSED(tsep), - TreeStoreElem *tselem, - void *UNUSED(user_data)) +void id_delete_tag_fn(bContext *C, + ReportList *reports, + Scene *UNUSED(scene), + TreeElement *te, + TreeStoreElem *UNUSED(tsep), + TreeStoreElem *tselem, + void *UNUSED(user_data)) { - id_delete(C, reports, te, tselem); + id_delete_tag(C, reports, te, tselem); } -static int outliner_id_delete_invoke_do(bContext *C, - ReportList *reports, - TreeElement *te, - const float mval[2]) +static int outliner_id_delete_tag(bContext *C, + ReportList *reports, + TreeElement *te, + const float mval[2]) { + int id_tagged_num = 0; + if (mval[1] > te->ys && mval[1] < te->ys + UI_UNIT_Y) { TreeStoreElem *tselem = TREESTORE(te); @@ -522,26 +526,27 @@ static int outliner_id_delete_invoke_do(bContext *C, RPT_ERROR_INVALID_INPUT, "Cannot delete indirectly linked library '%s'", ((Library *)tselem->id)->filepath_abs); - return OPERATOR_CANCELLED; } - id_delete(C, reports, te, tselem); - return OPERATOR_FINISHED; + else { + id_delete_tag(C, reports, te, tselem); + id_tagged_num++; + } } } else { LISTBASE_FOREACH (TreeElement *, te_sub, &te->subtree) { - int ret; - if ((ret = outliner_id_delete_invoke_do(C, reports, te_sub, mval))) { - return ret; + if ((id_tagged_num += outliner_id_delete_tag(C, reports, te_sub, mval)) != 0) { + break; } } } - return 0; + return id_tagged_num; } static int outliner_id_delete_invoke(bContext *C, wmOperator *op, const wmEvent *event) { + Main *bmain = CTX_data_main(C); ARegion *region = CTX_wm_region(C); SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); float fmval[2]; @@ -550,15 +555,21 @@ static int outliner_id_delete_invoke(bContext *C, wmOperator *op, const wmEvent UI_view2d_region_to_view(®ion->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]); + int id_tagged_num = 0; + BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); LISTBASE_FOREACH (TreeElement *, te, &space_outliner->tree) { - int ret; - - if ((ret = outliner_id_delete_invoke_do(C, op->reports, te, fmval))) { - return ret; + if ((id_tagged_num += outliner_id_delete_tag(C, op->reports, te, fmval)) != 0) { + break; } } + if (id_tagged_num == 0) { + BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); + return OPERATOR_CANCELLED; + } - return OPERATOR_CANCELLED; + BKE_id_multi_tagged_delete(bmain); + BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); + return OPERATOR_FINISHED; } void OUTLINER_OT_id_delete(wmOperatorType *ot) diff --git a/source/blender/editors/space_outliner/outliner_intern.hh b/source/blender/editors/space_outliner/outliner_intern.hh index a0dcb49aa43..19090641173 100644 --- a/source/blender/editors/space_outliner/outliner_intern.hh +++ b/source/blender/editors/space_outliner/outliner_intern.hh @@ -437,7 +437,7 @@ void lib_reload_fn(struct bContext *C, struct TreeStoreElem *tselem, void *user_data); -void id_delete_fn(struct bContext *C, +void id_delete_tag_fn(struct bContext *C, struct ReportList *reports, struct Scene *scene, struct TreeElement *te, diff --git a/source/blender/editors/space_outliner/outliner_tools.cc b/source/blender/editors/space_outliner/outliner_tools.cc index ec19e8d5e5b..04e74f5438b 100644 --- a/source/blender/editors/space_outliner/outliner_tools.cc +++ b/source/blender/editors/space_outliner/outliner_tools.cc @@ -2185,6 +2185,7 @@ static const EnumPropertyItem *outliner_id_operation_itemf(bContext *C, static int outliner_id_operation_exec(bContext *C, wmOperator *op) { + Main *bmain = CTX_data_main(C); wmWindowManager *wm = CTX_wm_manager(C); Scene *scene = CTX_data_scene(C); SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); @@ -2381,8 +2382,10 @@ static int outliner_id_operation_exec(bContext *C, wmOperator *op) } case OUTLINER_IDOP_DELETE: { if (idlevel > 0) { + BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, id_delete_fn, nullptr); + C, op->reports, scene, space_outliner, id_delete_tag_fn, nullptr); + BKE_id_multi_tagged_delete(bmain); ED_undo_push(C, "Delete"); } break; @@ -2507,6 +2510,7 @@ static const EnumPropertyItem outliner_lib_op_type_items[] = { static int outliner_lib_operation_exec(bContext *C, wmOperator *op) { + Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); int scenelevel = 0, objectlevel = 0, idlevel = 0, datalevel = 0; @@ -2522,7 +2526,10 @@ static int outliner_lib_operation_exec(bContext *C, wmOperator *op) eOutlinerLibOpTypes event = (eOutlinerLibOpTypes)RNA_enum_get(op->ptr, "type"); switch (event) { case OL_LIB_DELETE: { - outliner_do_libdata_operation(C, op->reports, scene, space_outliner, id_delete_fn, nullptr); + BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); + outliner_do_libdata_operation( + C, op->reports, scene, space_outliner, id_delete_tag_fn, nullptr); + BKE_id_multi_tagged_delete(bmain); ED_undo_push(C, "Delete Library"); break; } -- cgit v1.2.3 From 94323bb427480aaaeea52f6833173866fbacf2e1 Mon Sep 17 00:00:00 2001 From: Aras Pranckevicius Date: Wed, 6 Jul 2022 13:29:59 +0300 Subject: IO: speed up import of large Alembic/USD/OBJ scenes by optimizing material assignment The importer parts that were doing assignment of materials to the imported objects/meshes were essentially having a quadratic complexity in terms of scene object count. For each material assigned to each object, they were scanning the whole scene, checking which other Objects use the same Mesh data, in order to resize their material arrays to match the size. Performance details (Windows, Ryzen 5950X): - Import OBJ Blender 3.0 splash scene (24k objects): 43.0s -> 32.9s - Import USD Disney Moana scene (260k objects): saves two hours (~7400s). Note that later on this crashes when trying to render the imported result; crashes in the same way/place both in master and this patch. Implementation details: The importers were doing "scan the world" basically twice for each object, for each material: once when creating a new material slot (assigns an empty material), and then again when assigning the material. However, all these importers (USD, Alembic, OBJ) always create one Object for one Mesh. So that whole quadratic complexity resulting from "scan the world for possible other users of this obdata" is completely not needed; it just never finds anything. So add a new dedicated function BKE_object_material_assign_single_obdata that skips the expensive part, but should only be used when the caller knows that the obdata has exactly one user (the passed object). Reviewed By: Bastien Montagne, Michael Kowalski Differential Revision: https://developer.blender.org/D15145 --- source/blender/blenkernel/BKE_material.h | 11 +++++++++++ source/blender/blenkernel/intern/material.c | 21 +++++++++++++++++++-- source/blender/io/alembic/intern/abc_reader_mesh.cc | 8 +++----- source/blender/io/usd/intern/usd_reader_mesh.cc | 19 +++++-------------- .../io/wavefront_obj/importer/obj_import_mesh.cc | 3 +-- 5 files changed, 39 insertions(+), 23 deletions(-) diff --git a/source/blender/blenkernel/BKE_material.h b/source/blender/blenkernel/BKE_material.h index 05e502f06c2..ab14fa92514 100644 --- a/source/blender/blenkernel/BKE_material.h +++ b/source/blender/blenkernel/BKE_material.h @@ -87,6 +87,17 @@ struct Material *BKE_object_material_get(struct Object *ob, short act); void BKE_id_material_assign(struct Main *bmain, struct ID *id, struct Material *ma, short act); void BKE_object_material_assign( struct Main *bmain, struct Object *ob, struct Material *ma, short act, int assign_type); + +/** + * Similar to #BKE_object_material_assign with #BKE_MAT_ASSIGN_OBDATA type, + * but does not scan whole Main for other usages of the same obdata. Only + * use in cases where you know that the object's obdata is only used by this one + * object. + */ +void BKE_object_material_assign_single_obdata(struct Main *bmain, + struct Object *ob, + struct Material *ma, + short act); /** * \warning this calls many more update calls per object then are needed, could be optimized. */ diff --git a/source/blender/blenkernel/intern/material.c b/source/blender/blenkernel/intern/material.c index 04a07fb42be..f899901b54e 100644 --- a/source/blender/blenkernel/intern/material.c +++ b/source/blender/blenkernel/intern/material.c @@ -954,7 +954,8 @@ void BKE_id_material_assign(Main *bmain, ID *id, Material *ma, short act) BKE_objects_materials_test_all(bmain, id); } -void BKE_object_material_assign(Main *bmain, Object *ob, Material *ma, short act, int assign_type) +static void object_material_assign( + Main *bmain, Object *ob, Material *ma, short act, int assign_type, bool do_test_all) { Material *mao, **matar, ***matarar; short *totcolp; @@ -1037,7 +1038,10 @@ void BKE_object_material_assign(Main *bmain, Object *ob, Material *ma, short act id_us_min(&mao->id); } (*matarar)[act - 1] = ma; - BKE_objects_materials_test_all(bmain, ob->data); /* Data may be used by several objects... */ + /* Data may be used by several objects. */ + if (do_test_all) { + BKE_objects_materials_test_all(bmain, ob->data); + } } if (ma) { @@ -1045,6 +1049,19 @@ void BKE_object_material_assign(Main *bmain, Object *ob, Material *ma, short act } } +void BKE_object_material_assign(Main *bmain, Object *ob, Material *ma, short act, int assign_type) +{ + object_material_assign(bmain, ob, ma, act, assign_type, true); +} + +void BKE_object_material_assign_single_obdata(struct Main *bmain, + struct Object *ob, + struct Material *ma, + short act) +{ + object_material_assign(bmain, ob, ma, act, BKE_MAT_ASSIGN_OBDATA, false); +} + void BKE_object_material_remap(Object *ob, const unsigned int *remap) { Material ***matar = BKE_object_material_array_p(ob); diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.cc b/source/blender/io/alembic/intern/abc_reader_mesh.cc index d8c48357fc0..df3559c108c 100644 --- a/source/blender/io/alembic/intern/abc_reader_mesh.cc +++ b/source/blender/io/alembic/intern/abc_reader_mesh.cc @@ -77,10 +77,8 @@ static void assign_materials(Main *bmain, const std::map &mat_index_map) { std::map::const_iterator it; - for (it = mat_index_map.begin(); it != mat_index_map.end(); ++it) { - if (!BKE_object_material_slot_add(bmain, ob)) { - return; - } + if (mat_index_map.size() > MAXMAT) { + return; } std::map matname_to_material = build_material_map(bmain); @@ -100,7 +98,7 @@ static void assign_materials(Main *bmain, assigned_mat = mat_iter->second; } - BKE_object_material_assign(bmain, ob, assigned_mat, mat_index, BKE_MAT_ASSIGN_OBDATA); + BKE_object_material_assign_single_obdata(bmain, ob, assigned_mat, mat_index); } } diff --git a/source/blender/io/usd/intern/usd_reader_mesh.cc b/source/blender/io/usd/intern/usd_reader_mesh.cc index 46749b03169..45657525527 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.cc +++ b/source/blender/io/usd/intern/usd_reader_mesh.cc @@ -116,24 +116,15 @@ static void assign_materials(Main *bmain, return; } - bool can_assign = true; - std::map::const_iterator it = mat_index_map.begin(); - - int matcount = 0; - for (; it != mat_index_map.end(); ++it, matcount++) { - if (!BKE_object_material_slot_add(bmain, ob)) { - can_assign = false; - break; - } - } - - if (!can_assign) { + if (mat_index_map.size() > MAXMAT) { return; } blender::io::usd::USDMaterialReader mat_reader(params, bmain); - for (it = mat_index_map.begin(); it != mat_index_map.end(); ++it) { + for (std::map::const_iterator it = mat_index_map.begin(); + it != mat_index_map.end(); + ++it) { Material *assigned_mat = find_existing_material( it->first, params, mat_name_to_mat, usd_path_to_mat_name); @@ -170,7 +161,7 @@ static void assign_materials(Main *bmain, } if (assigned_mat) { - BKE_object_material_assign(bmain, ob, assigned_mat, it->second, BKE_MAT_ASSIGN_OBDATA); + BKE_object_material_assign_single_obdata(bmain, ob, assigned_mat, it->second); } else { /* This shouldn't happen. */ diff --git a/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc b/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc index 1e0b36ae4cb..8cef0700b3b 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc +++ b/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc @@ -306,8 +306,7 @@ void MeshFromGeometry::create_materials(Main *bmain, if (mat == nullptr) { continue; } - BKE_object_material_slot_add(bmain, obj); - BKE_object_material_assign(bmain, obj, mat, obj->totcol, BKE_MAT_ASSIGN_USERPREF); + BKE_object_material_assign_single_obdata(bmain, obj, mat, obj->totcol + 1); } } -- cgit v1.2.3 From da852457040827f734287d7db98208e998cb3af6 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Tue, 5 Jul 2022 17:35:12 +0200 Subject: Compositor: Pre-fill active scene movie clip in more nodes Pre-fills movie clip from the scene to the following nodes: - Keying Screen - Plane Track Deform - Track Position The rest of tracking related nodes were already doing so. Differential Revision: https://developer.blender.org/D15377 --- .../nodes/composite/nodes/node_composite_keyingscreen.cc | 13 +++++++++++-- .../composite/nodes/node_composite_planetrackdeform.cc | 10 ++++++++-- .../nodes/composite/nodes/node_composite_trackpos.cc | 13 ++++++++++--- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/source/blender/nodes/composite/nodes/node_composite_keyingscreen.cc b/source/blender/nodes/composite/nodes/node_composite_keyingscreen.cc index c3ed5cd7aa8..e4e37f630a2 100644 --- a/source/blender/nodes/composite/nodes/node_composite_keyingscreen.cc +++ b/source/blender/nodes/composite/nodes/node_composite_keyingscreen.cc @@ -10,6 +10,9 @@ #include "BLI_math_base.h" #include "BLI_math_color.h" +#include "BKE_context.h" +#include "BKE_lib_id.h" + #include "RNA_access.h" #include "RNA_prototypes.h" @@ -27,10 +30,16 @@ static void cmp_node_keyingscreen_declare(NodeDeclarationBuilder &b) b.add_output(N_("Screen")); } -static void node_composit_init_keyingscreen(bNodeTree *UNUSED(ntree), bNode *node) +static void node_composit_init_keyingscreen(const bContext *C, PointerRNA *ptr) { + bNode *node = (bNode *)ptr->data; + NodeKeyingScreenData *data = MEM_cnew(__func__); node->storage = data; + + const Scene *scene = CTX_data_scene(C); + node->id = (ID *)scene->clip; + id_us_plus(node->id); } static void node_composit_buts_keyingscreen(uiLayout *layout, bContext *C, PointerRNA *ptr) @@ -71,7 +80,7 @@ void register_node_type_cmp_keyingscreen() cmp_node_type_base(&ntype, CMP_NODE_KEYINGSCREEN, "Keying Screen", NODE_CLASS_MATTE); ntype.declare = file_ns::cmp_node_keyingscreen_declare; ntype.draw_buttons = file_ns::node_composit_buts_keyingscreen; - node_type_init(&ntype, file_ns::node_composit_init_keyingscreen); + ntype.initfunc_api = file_ns::node_composit_init_keyingscreen; node_type_storage( &ntype, "NodeKeyingScreenData", node_free_standard_storage, node_copy_standard_storage); diff --git a/source/blender/nodes/composite/nodes/node_composite_planetrackdeform.cc b/source/blender/nodes/composite/nodes/node_composite_planetrackdeform.cc index fb0c03579a2..8055e350d51 100644 --- a/source/blender/nodes/composite/nodes/node_composite_planetrackdeform.cc +++ b/source/blender/nodes/composite/nodes/node_composite_planetrackdeform.cc @@ -22,12 +22,18 @@ static void cmp_node_planetrackdeform_declare(NodeDeclarationBuilder &b) b.add_output(N_("Plane")); } -static void init(bNodeTree *UNUSED(ntree), bNode *node) +static void init(const bContext *C, PointerRNA *ptr) { + bNode *node = (bNode *)ptr->data; + NodePlaneTrackDeformData *data = MEM_cnew(__func__); data->motion_blur_samples = 16; data->motion_blur_shutter = 0.5f; node->storage = data; + + const Scene *scene = CTX_data_scene(C); + node->id = (ID *)scene->clip; + id_us_plus(node->id); } static void node_composit_buts_planetrackdeform(uiLayout *layout, bContext *C, PointerRNA *ptr) @@ -90,7 +96,7 @@ void register_node_type_cmp_planetrackdeform() cmp_node_type_base(&ntype, CMP_NODE_PLANETRACKDEFORM, "Plane Track Deform", NODE_CLASS_DISTORT); ntype.declare = file_ns::cmp_node_planetrackdeform_declare; ntype.draw_buttons = file_ns::node_composit_buts_planetrackdeform; - node_type_init(&ntype, file_ns::init); + ntype.initfunc_api = file_ns::init; node_type_storage( &ntype, "NodePlaneTrackDeformData", node_free_standard_storage, node_copy_standard_storage); diff --git a/source/blender/nodes/composite/nodes/node_composite_trackpos.cc b/source/blender/nodes/composite/nodes/node_composite_trackpos.cc index 3dc68b8ef0b..723b82998ee 100644 --- a/source/blender/nodes/composite/nodes/node_composite_trackpos.cc +++ b/source/blender/nodes/composite/nodes/node_composite_trackpos.cc @@ -5,6 +5,8 @@ * \ingroup cmpnodes */ +#include "BKE_context.h" +#include "BKE_lib_id.h" #include "BKE_tracking.h" #include "RNA_access.h" @@ -24,11 +26,16 @@ static void cmp_node_trackpos_declare(NodeDeclarationBuilder &b) b.add_output(N_("Speed")).subtype(PROP_VELOCITY); } -static void init(bNodeTree *UNUSED(ntree), bNode *node) +static void init(const bContext *C, PointerRNA *ptr) { - NodeTrackPosData *data = MEM_cnew(__func__); + bNode *node = (bNode *)ptr->data; + NodeTrackPosData *data = MEM_cnew(__func__); node->storage = data; + + const Scene *scene = CTX_data_scene(C); + node->id = (ID *)scene->clip; + id_us_plus(node->id); } static void node_composit_buts_trackpos(uiLayout *layout, bContext *C, PointerRNA *ptr) @@ -90,7 +97,7 @@ void register_node_type_cmp_trackpos() cmp_node_type_base(&ntype, CMP_NODE_TRACKPOS, "Track Position", NODE_CLASS_INPUT); ntype.declare = file_ns::cmp_node_trackpos_declare; ntype.draw_buttons = file_ns::node_composit_buts_trackpos; - node_type_init(&ntype, file_ns::init); + ntype.initfunc_api = file_ns::init; node_type_storage( &ntype, "NodeTrackPosData", node_free_standard_storage, node_copy_standard_storage); -- cgit v1.2.3 From 9d0777e5144e6d1ee154e03a7cd4272468cdb422 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 6 Jul 2022 21:04:30 +1000 Subject: Fix T99368: Annotation lines doesn't start where clicked Caused by [0] which made accessing the drag-start require a function instead of being the value written into the event cursor coordinates. [0]: b8960267dd51f9108b3b49e9b762e6b4d35ae1dc --- source/blender/editors/gpencil/annotate_paint.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/source/blender/editors/gpencil/annotate_paint.c b/source/blender/editors/gpencil/annotate_paint.c index dd935e4f5f5..2d613e2f433 100644 --- a/source/blender/editors/gpencil/annotate_paint.c +++ b/source/blender/editors/gpencil/annotate_paint.c @@ -2062,11 +2062,18 @@ static void annotation_draw_apply_event( PointerRNA itemptr; float mousef[2]; - /* convert from window-space to area-space mouse coordinates - * add any x,y override position for fake events - */ - p->mval[0] = (float)event->mval[0] - x; - p->mval[1] = (float)event->mval[1] - y; + /* Convert from window-space to area-space mouse coordinates + * add any x,y override position for fake events. */ + if (p->flags & GP_PAINTFLAG_FIRSTRUN) { + /* The first run may be a drag event, see: T99368. */ + WM_event_drag_start_mval_fl(event, p->region, p->mval); + p->mval[0] -= x; + p->mval[1] -= y; + } + else { + p->mval[0] = (float)event->mval[0] - x; + p->mval[1] = (float)event->mval[1] - y; + } /* Key to toggle stabilization. */ if ((event->modifier & KM_SHIFT) && (p->paintmode == GP_PAINTMODE_DRAW)) { -- cgit v1.2.3 From 6636edbb00942a1a04bdf6f3cb843a1636ffa8b4 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 6 Jul 2022 15:20:25 +0200 Subject: BLI: improve reverse uv sample in edge cases Allow for a small epsilon to improve handling of uvs that are on edges. Generally, when using reverse uv sampling, we expect that the sampling is supposed to succeed. --- .../blender/geometry/intern/reverse_uv_sampler.cc | 29 ++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/source/blender/geometry/intern/reverse_uv_sampler.cc b/source/blender/geometry/intern/reverse_uv_sampler.cc index 87ba2c77657..54df43db4ea 100644 --- a/source/blender/geometry/intern/reverse_uv_sampler.cc +++ b/source/blender/geometry/intern/reverse_uv_sampler.cc @@ -45,6 +45,11 @@ ReverseUVSampler::Result ReverseUVSampler::sample(const float2 &query_uv) const { const int2 cell_key = uv_to_cell_key(query_uv, resolution_); const Span looptri_indices = looptris_by_cell_.lookup(cell_key); + + float best_dist = FLT_MAX; + float3 best_bary_weights; + const MLoopTri *best_looptri; + for (const int looptri_index : looptri_indices) { const MLoopTri &looptri = looptris_[looptri_index]; const float2 &uv_0 = uv_map_[looptri.tri[0]]; @@ -54,11 +59,31 @@ ReverseUVSampler::Result ReverseUVSampler::sample(const float2 &query_uv) const if (!barycentric_coords_v2(uv_0, uv_1, uv_2, query_uv, bary_weights)) { continue; } - if (IN_RANGE_INCL(bary_weights.x, 0.0f, 1.0f) && IN_RANGE_INCL(bary_weights.y, 0.0f, 1.0f) && - IN_RANGE_INCL(bary_weights.z, 0.0f, 1.0f)) { + + /* If #query_uv is in the triangle, the distance is <= 0. Otherwise, the larger the distance, + * the further away the uv is from the triangle. */ + const float x_dist = std::max(-bary_weights.x, bary_weights.x - 1.0f); + const float y_dist = std::max(-bary_weights.y, bary_weights.y - 1.0f); + const float z_dist = std::max(-bary_weights.z, bary_weights.z - 1.0f); + const float dist = MAX3(x_dist, y_dist, z_dist); + + if (dist <= 0.0f) { + /* Return early if the uv coordinate is in the triangle. */ return Result{ResultType::Ok, &looptri, bary_weights}; } + + if (dist < best_dist) { + best_dist = dist; + best_bary_weights = bary_weights; + best_looptri = &looptri; + } + } + + /* Allow for a small epsilon in case the uv is on th edge. */ + if (best_dist < 0.00001f) { + return Result{ResultType::Ok, best_looptri, math::clamp(best_bary_weights, 0.0f, 1.0f)}; } + return Result{}; } -- cgit v1.2.3 From 0df574b55ee9cf1b6c22a3a6a6cc0ef3a5c1fe83 Mon Sep 17 00:00:00 2001 From: Nikita Sirgienko Date: Wed, 6 Jul 2022 17:26:23 +0200 Subject: Cycles: Improve an occupancy for Intel GPUs Initially oneAPI implementation have waited after each memory operation, even if there was no need for this. Now, the implementation will wait only if it is really necessary - it have improved performance noticeble for some scenes and a bit for the rest of them. --- intern/cycles/kernel/device/oneapi/kernel.cpp | 32 +++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/intern/cycles/kernel/device/oneapi/kernel.cpp b/intern/cycles/kernel/device/oneapi/kernel.cpp index 11a551e822e..ec979db2455 100644 --- a/intern/cycles/kernel/device/oneapi/kernel.cpp +++ b/intern/cycles/kernel/device/oneapi/kernel.cpp @@ -103,8 +103,13 @@ bool oneapi_usm_memcpy(SyclQueue *queue_, void *dest, void *src, size_t num_byte sycl::queue *queue = reinterpret_cast(queue_); oneapi_check_usm(queue_, dest, true); oneapi_check_usm(queue_, src, true); + sycl::event mem_event = queue->memcpy(dest, src, num_bytes); +#ifdef WITH_CYCLES_DEBUG try { - sycl::event mem_event = queue->memcpy(dest, src, num_bytes); + /* NOTE(@nsirgien) Waiting on memory operation may give more preciese error + * messages in case of the problems, but due to impact on occupancy + * make sense enable it only during cycles debugging + */ mem_event.wait_and_throw(); return true; } @@ -114,6 +119,20 @@ bool oneapi_usm_memcpy(SyclQueue *queue_, void *dest, void *src, size_t num_byte } return false; } +#else + sycl::usm::alloc dest_type = get_pointer_type(dest, queue->get_context()); + sycl::usm::alloc src_type = get_pointer_type(src, queue->get_context()); + bool from_device_to_host + = dest_type == sycl::usm::alloc::host && src_type == sycl::usm::alloc::device; + bool host_or_device_memop_with_offset + = dest_type == sycl::usm::alloc::unknown || src_type == sycl::usm::alloc::unknown; + /* NOTE(@sirgienko) Host-side blocking wait on this operations is mandatory, host + * may don't wait until end of transfer before using the memory. + */ + if(from_device_to_host || host_or_device_memop_with_offset) + mem_event.wait(); + return true; +#endif } bool oneapi_usm_memset(SyclQueue *queue_, void *usm_ptr, unsigned char value, size_t num_bytes) @@ -121,8 +140,13 @@ bool oneapi_usm_memset(SyclQueue *queue_, void *usm_ptr, unsigned char value, si assert(queue_); sycl::queue *queue = reinterpret_cast(queue_); oneapi_check_usm(queue_, usm_ptr, true); + sycl::event mem_event = queue->memset(usm_ptr, value, num_bytes); +#ifdef WITH_CYCLES_DEBUG try { - sycl::event mem_event = queue->memset(usm_ptr, value, num_bytes); + /* NOTE(@nsirgien) Waiting on memory operation may give more preciese error + * messages in case of the problems, but due to impact on occupancy + * make sense enable it only during cycles debugging + */ mem_event.wait_and_throw(); return true; } @@ -132,6 +156,10 @@ bool oneapi_usm_memset(SyclQueue *queue_, void *usm_ptr, unsigned char value, si } return false; } +#else + (void)mem_event; + return true; +#endif } bool oneapi_queue_synchronize(SyclQueue *queue_) -- cgit v1.2.3 From 82fc8786ea182f0cdc9c2eedea53cbf3a3656e4d Mon Sep 17 00:00:00 2001 From: Siddhartha Jejurkar Date: Mon, 4 Jul 2022 11:49:07 +0530 Subject: Fix T99343: Missing RNA_def_property_update for show overlays in UV editor Reviewed By: jbakker Maniphest Tasks: T99343 Differential Revision: https://developer.blender.org/D15354 --- source/blender/makesrna/intern/rna_space.c | 1 + 1 file changed, 1 insertion(+) diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index 745c7137cb2..969d1f2075e 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -5298,6 +5298,7 @@ static void rna_def_space_image_overlay(BlenderRNA *brna) prop = RNA_def_property(srna, "show_overlays", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "overlay.flag", SI_OVERLAY_SHOW_OVERLAYS); RNA_def_property_ui_text(prop, "Show Overlays", "Display overlays like UV Maps and Metadata"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, NULL); prop = RNA_def_property(srna, "show_grid_background", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "overlay.flag", SI_OVERLAY_SHOW_GRID_BACKGROUND); -- cgit v1.2.3 From 8ea5a5259d2ccd179ee03178b1fe9d5b0250a4e8 Mon Sep 17 00:00:00 2001 From: Francesco Siddi Date: Wed, 6 Jul 2022 18:20:51 +0200 Subject: Icons: Add each icon to a named group The objects making up each icon are placed in a group named after the icon coordinates in the grid. This change has no impact on the current pipeline used to include icons in a Blender build, but lays the foundation to explore other workflows to do that, and tidies up the file. Differential Revision: https://developer.blender.org/D15251 --- release/datafiles/blender_icons.svg | 19321 ++++++++++++++++++---------------- 1 file changed, 10392 insertions(+), 8929 deletions(-) diff --git a/release/datafiles/blender_icons.svg b/release/datafiles/blender_icons.svg index 985714d813f..41707261ac6 100644 --- a/release/datafiles/blender_icons.svg +++ b/release/datafiles/blender_icons.svg @@ -1,24 +1,24 @@ + inkscape:export-filename="blender_icons.png" + style="display:inline;enable-background:new" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + sodipodi:docname="blender_icons.svg" + version="1.0" + inkscape:version="1.2 (dc2aeda, 2022-05-15)" + sodipodi:version="0.32" + id="svg2" + height="640" + width="602" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> @@ -27,7 +27,7 @@ image/svg+xml - + @@ -42,29 +42,21 @@ guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" - inkscape:window-width="1618" - inkscape:window-height="846" + inkscape:window-width="1728" + inkscape:window-height="1051" id="namedview34335" showgrid="false" - inkscape:zoom="16" - inkscape:cx="18.1714" - inkscape:cy="166.10682" - inkscape:window-x="185" - inkscape:window-y="159" + inkscape:zoom="1.2495612" + inkscape:cx="196.46897" + inkscape:cy="320.51252" + inkscape:window-x="767" + inkscape:window-y="120" inkscape:window-maximized="0" - inkscape:current-layer="layer8" /> + inkscape:current-layer="layer8" + inkscape:showpageshadow="2" + inkscape:deskcolor="#808080" /> - - + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> @@ -456,133 +448,133 @@ height="1" width="6" id="rect23767" - style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> @@ -590,7 +582,7 @@ id="g23819" transform="translate(0,420)"> @@ -738,133 +730,133 @@ height="1" width="6" id="rect23875" - style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> @@ -872,7 +864,7 @@ id="g23927" transform="translate(0,378)"> @@ -1013,7 +1005,7 @@ id="g23981" transform="translate(0,357)"> @@ -1161,133 +1153,133 @@ height="1" width="6" id="rect24037" - style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> @@ -1295,7 +1287,7 @@ id="g24089" transform="translate(0,315)"> @@ -1443,133 +1435,133 @@ height="1" width="6" id="rect24145" - style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> @@ -1577,7 +1569,7 @@ id="g24197" transform="translate(0,273)"> @@ -1725,133 +1717,133 @@ height="1" width="6" id="rect24253" - style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> @@ -1859,7 +1851,7 @@ id="g24305" transform="translate(0,231)"> @@ -2007,133 +1999,133 @@ height="1" width="6" id="rect24361" - style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> @@ -2141,7 +2133,7 @@ id="g24413" transform="translate(0,189)"> @@ -2289,133 +2281,133 @@ height="1" width="6" id="rect24469" - style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> @@ -2423,7 +2415,7 @@ id="g24521" transform="translate(0,147)"> @@ -2571,133 +2563,133 @@ height="1" width="6" id="rect24577" - style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> @@ -2705,7 +2697,7 @@ id="g24630" transform="translate(0,105)"> @@ -2853,133 +2845,133 @@ height="1" width="6" id="rect24686" - style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> @@ -2987,7 +2979,7 @@ id="g24738" transform="translate(0,63)"> @@ -3135,133 +3127,133 @@ height="1" width="6" id="rect24794" - style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> @@ -3269,7 +3261,7 @@ id="g24846" transform="translate(0,21)"> @@ -3416,133 +3408,133 @@ height="1" width="6" id="rect24902" - style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> @@ -3557,133 +3549,133 @@ height="1" width="6" id="rect39835" - style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> @@ -3691,7 +3683,7 @@ id="g39892" transform="translate(0,504)"> @@ -3839,133 +3831,133 @@ height="1" width="6" id="rect39951" - style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> @@ -3973,7 +3965,7 @@ id="g40005" transform="translate(0,546)"> @@ -4121,133 +4113,133 @@ height="1" width="6" id="rect40064" - style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> @@ -4255,7 +4247,7 @@ id="g40118" transform="translate(0,588)"> @@ -4404,9 +4396,9 @@ height="7" width="1" id="rect25956" - style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> + style="display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.7;marker:none;enable-background:accumulate" /> - - - - - - - - + id="g21571" + inkscape:label="DA-26"> + + + + + id="g21575" + inkscape:label="DA-25"> + + + + + id="g36632-6" + transform="translate(771.007,-831)" + style="display:inline;enable-background:new" + inkscape:label="DA-24"> + + id="g36628-4"> + + + - + id="g10845" + inkscape:label="DA-23" + style="display:inline;enable-background:new"> + + + - - + id="g19348" + transform="translate(-252,-1102)" + inkscape:label="DA-22"> + + + + + + + - + id="g15578-7" + transform="matrix(1,0,0,-1,894,921)" + inkscape:label="DA-21"> + + + + + inkscape:export-ydpi="96" + inkscape:label="DA-20"> + + + - + id="g37394" + inkscape:label="DA-19" + style="display:inline;enable-background:new"> + + + + + + + + + + - - - - - - - - + inkscape:label="DA-16"> + + - - - - - + id="g22365" + transform="translate(-1.85367e-6,-26)" + inkscape:label="DA-15"> + + sodipodi:nodetypes="sssssssssccccc" + inkscape:connector-curvature="0" + id="rect21916" + d="m 301.5,647 c -0.82235,0 -1.5,0.67765 -1.5,1.5 v 9 c 0,0.82235 0.67765,1.5 1.5,1.5 h 11 c 0.82235,0 1.5,-0.67765 1.5,-1.5 v -9 c 0,-0.82235 -0.67765,-1.5 -1.5,-1.5 z m -0.5,1 h 12 v 10 h -12 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + id="g37415" + inkscape:label="DA-14" + style="display:inline;enable-background:new"> - - - - - - - - - - - - - + sodipodi:nodetypes="sssss" /> + + + + + + + - - - + sodipodi:nodetypes="ccccccc" /> - - - - - - - - - + style="display:inline;fill:none;stroke-width:1.627;enable-background:new" + id="rect37443-5" + width="18" + height="18" + x="234.99995" + y="-636" + transform="scale(1,-1)" + inkscape:label="frame" /> + + + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 220.4707,623.75 c -0.26463,0.0156 -0.47113,0.23492 -0.4707,0.5 v 4.25 c 1.7e-4,0.35718 0.36395,0.59902 0.69336,0.46094 l 4.75,-2 c 0.39771,-0.16741 0.41088,-0.72615 0.0215,-0.91211 l -4.75,-2.25 c -0.076,-0.0366 -0.15996,-0.0534 -0.24416,-0.0488 z" + id="path14031" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccc" /> + + + + + + + + id="g21059" + inkscape:label="DA-8" + style="display:inline;enable-background:new"> + + inkscape:connector-curvature="0" + id="path9229" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 156,629.49414 c 0,0.27842 0.2216,0.50584 0.5,0.50586 h 7 c 0.4051,6e-4 0.6427,-0.45544 0.4102,-0.78711 l -3.5,-5 c -0.199,-0.28542 -0.6214,-0.28542 -0.8204,0 l -3.5,5 c -0.058,0.0826 -0.089,0.1806 -0.09,0.28125 z" + sodipodi:nodetypes="ccccccccc" + inkscape:label="path9228" /> - - - - - + id="g21032" + inkscape:label="DA-7" + style="display:inline;enable-background:new"> + + - - - - - - + id="g21016" + inkscape:label="DA-6" + style="display:inline;enable-background:new"> + - - - - - - - - - + id="g13525" + inkscape:label="DA-5" + style="display:inline;enable-background:new"> + + inkscape:connector-curvature="0" + id="path9205-9" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 94.50586,623 c -0.27842,0 -0.50585,0.2216 -0.50586,0.5 v 7 c -6.1e-4,0.4051 0.45544,0.6427 0.78711,0.4102 l 5,-3.5 c 0.28542,-0.199 0.28542,-0.6214 0,-0.8204 l -5,-3.5 c -0.0826,-0.058 -0.1806,-0.089 -0.28125,-0.09 z" + sodipodi:nodetypes="ccccccccc" /> - + id="g13493" + inkscape:label="DA-4" + style="display:inline;enable-background:new"> + + + + + - - - - - + inkscape:label="DA-2"> + - - - + id="g26759" + inkscape:label="CA-26" + style="display:inline;enable-background:new"> + + sodipodi:nodetypes="ccccccccccccc" /> + + + sodipodi:nodetypes="csssscccccc" + inkscape:connector-curvature="0" + id="path28911-6" + d="m 489,604 v 8 c 0,0.54532 0.45468,1 1,1 h 12 c 0.54532,0 1,-0.45468 1,-1 v -8 h -1 v 8 h -12 v -8 z" + style="opacity:0.6;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke" /> + - + style="display:inline;enable-background:new" + id="g28909-7" + transform="rotate(180,535.5,631.5)" + inkscape:label="CA-25"> + + + + + + id="g9217" + inkscape:label="CA-22" + style="display:inline;enable-background:new"> + id="path24965-8" + d="m 447.5,599 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 10 a 0.50005,0.50005 0 0 0 0.5,0.5 h 13 a 0.50005,0.50005 0 1 0 0,-1 H 448 v -9 h 9 v 7.5 a 0.50005,0.50005 0 1 0 1,0 v -8 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z m 9.99219,11.74219 A 0.50005,0.50005 0 0 0 457,611.25 v 1.25 a 0.50005,0.50005 0 1 0 1,0 v -1.25 a 0.50005,0.50005 0 0 0 -0.50781,-0.50781 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + - + id="g17698" + inkscape:label="CA-21" + style="display:inline;enable-background:new"> + + + + + + - - - - - + id="g13022" + transform="translate(-42.0072,397.993)" + style="display:inline;opacity:1;fill:#ffffff;enable-background:new" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96"> + + + id="g16002-8" + inkscape:label="CA-18"> - - + + + + + + + + + + + + + + + + + + + + + + inkscape:export-ydpi="96" + inkscape:label="CA-10"> + transform="translate(-126)" + id="g11139" + style="fill:#ffffff"> - + style="display:inline;fill:#ffffff;enable-background:new" + id="g22423" + inkscape:label="CA-9"> + id="g22461" + style="fill:#ffffff"> + + inkscape:export-ydpi="96" + inkscape:label="CA-8"> + style="fill:#ffffff;stroke:#ffffff;stroke-width:1.33333;stroke-miterlimit:4;stroke-dasharray:none"> - + inkscape:export-ydpi="96" + inkscape:label="CA-7"> - - - - - - + style="fill:#ffffff;stroke:#ffffff;stroke-width:1.33333" + id="g10313-3"> + + + inkscape:export-ydpi="96" + inkscape:label="CA-6"> + id="g7087-1" + style="fill:#ffffff;stroke:#ffffff;stroke-width:1.33333;stroke-miterlimit:4;stroke-dasharray:none"> - - - - + inkscape:label="CA-5"> - + id="g5620" + style="fill:#ffffff"> + + + + + + inkscape:export-ydpi="96" + inkscape:label="CA-4"> - - - + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.25;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 96,620 v 3.5 h 1 V 621 h 6 v 12 h -6 v -2.25 H 96 V 634 h 8 v -14 z" + id="rect12222" + inkscape:connector-curvature="0" /> - + id="g17695" + inkscape:label="CA-3" + style="display:inline;enable-background:new"> - - - - + inkscape:export-ydpi="96" + inkscape:label="CA-2"> + + transform="translate(-541,-51)" + style="display:inline;fill:#ffffff;enable-background:new" + id="g15431-6" + inkscape:label="CA-1"> + id="rect15415-8" + d="m 548,653 c -0.54532,0 -1,0.45468 -1,1 v 9 c 0,0.54532 0.45468,1 1,1 h 10 c 0.54532,0 1,-0.45468 1,-1 v -1 h -1 v 1 h -10 v -4 -5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + inkscape:connector-curvature="0" + id="rect15379-7" + d="m 550,650 c -0.54532,0 -1,0.45468 -1,1 v 9 c 0,0.54532 0.45468,1 1,1 h 10 c 0.54532,0 1,-0.45468 1,-1 v -9 c 0,-0.54532 -0.45468,-1 -1,-1 z m 0,1 h 2 v 2 h -2 z m 0,3 h 10 v 6 h -10 z" + style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke" /> - - + transform="translate(712,-665)" + style="display:inline;enable-background:new" + id="g25686" + inkscape:label="BA-26"> + transform="rotate(180,-177.4965,1250.005)" + id="g25671"> + - + transform="matrix(1,0,0,-1,-61.9929,2500.02)" + id="g25669"> + + + + id="g25684" + transform="matrix(1,0,0,-1,0,2500.01)"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m -232,1242.9863 c -0.50478,0 -1.00956,0.3375 -1,1.0137 v 7 h 2 v -7 c 0.01,-0.6762 -0.49522,-1.0137 -1,-1.0137 z m 0,9.0274 c -1.09865,0 -2,0.9013 -2,2 0,0.8274 -0.13264,1.3367 -0.33203,1.5937 -0.1994,0.257 -0.49978,0.3926 -1.16797,0.3926 -0.11793,0 -0.23139,0.046 -0.32031,0.123 -0.0251,0.022 -0.048,0.046 -0.0684,0.072 -0.0409,0.053 -0.0708,0.1132 -0.0879,0.1777 -0.004,0.016 -0.007,0.033 -0.01,0.049 -0.003,0.017 -0.005,0.034 -0.006,0.051 -0.002,0.033 -7.6e-4,0.067 0.004,0.1 0.005,0.033 0.0138,0.065 0.0254,0.096 0.0107,0.031 0.0244,0.061 0.041,0.09 0.009,0.015 0.0188,0.029 0.0293,0.043 0.0403,0.053 0.0907,0.098 0.14844,0.1308 0.0742,0.043 0.15827,0.067 0.24414,0.068 2.01924,0 3.30957,-0.2641 4.16992,-0.7461 0.85624,-0.4797 1.23067,-1.2322 1.30078,-1.918 0.0175,-0.1066 0.0293,-0.2142 0.0293,-0.3222 0,-1.0987 -0.90135,-2 -2,-2 z" + transform="matrix(1,0,0,-1,63,2500.01)" + id="path25673" + inkscape:connector-curvature="0" + sodipodi:nodetypes="sccccssscscccccccccccccss" /> + - - - - - + transform="translate(1223,668.012)" + style="display:inline;enable-background:new" + id="g27941-8" + inkscape:label="BA-25"> + - + transform="translate(1223,668.012)" + style="display:inline;enable-background:new" + id="g27937-4" + inkscape:label="BA-24"> - + id="g13413" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="BA-23"> + sodipodi:nodetypes="sssssssssssssss" /> - + style="display:inline;fill:#ffffff;enable-background:new" + id="g18361" + inkscape:label="BA-22"> + - - - + inkscape:export-ydpi="96" + inkscape:label="BA-21"> + style="opacity:0.7;fill:#ffffff;stroke-width:1.15355" + transform="matrix(0.867075,0,0,0.866701,799.686,161.073)" + id="g17477"> + - - + sodipodi:nodetypes="ccccccccc" /> + + + id="g21041" + inkscape:label="BA-19" + style="display:inline;enable-background:new"> + + + + - + id="g9914-5" + transform="translate(100,195.999)" + style="display:inline;opacity:0.6;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:new"> - - - + inkscape:export-ydpi="96" + inkscape:label="BA-17"> - - - - - + style="display:inline;enable-background:new" + id="g27501-8" + transform="translate(-20.9999,105)" + inkscape:label="BA-16"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.85;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 346.54688,478.16016 a 0.50005,0.50005 0 0 0 -0.18165,0.0293 c -1.54898,0.53108 -2.52602,2.0719 -2.34375,3.69922 0.18228,1.62732 1.47542,2.91657 3.10352,3.0918 1.6281,0.17523 3.16703,-0.80811 3.69141,-2.35938 a 0.50005,0.50005 0 1 0 -0.94727,-0.32031 c -0.37611,1.11265 -1.46896,1.81123 -2.63672,1.68555 -1.16775,-0.12568 -2.08606,-1.04179 -2.2168,-2.20899 -0.13073,-1.1672 0.56282,-2.26166 1.67383,-2.64257 a 0.50005,0.50005 0 0 0 -0.14257,-0.97461 z" + id="path27478-1" + inkscape:connector-curvature="0" /> + + sodipodi:nodetypes="cccccccccccccc" /> + id="g21047" + inkscape:label="BA-15" + style="display:inline;enable-background:new"> - + inkscape:export-filename="blender_icons.png" + id="g15178-7" + style="display:inline;fill:#ffffff;stroke:#ffffff;stroke-width:1.33333;stroke-miterlimit:4;stroke-dasharray:none;enable-background:new" + transform="matrix(-0.53033,-0.53033,-0.53033,0.53033,580.864,617.759)" + inkscape:label="BA-14"> + + + + + + - + id="g15242-0" + transform="translate(-558,-305)" + inkscape:label="BA-13"> + id="g15149-2" + transform="translate(21,43)" + style="fill:#ffffff"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="M 259.49219,578.99219 A 0.50005,0.50005 0 0 0 259,579.5 v 11.91992 a 0.50005,0.50005 0 0 0 0.11328,0.4043 0.50005,0.50005 0 0 0 0.0156,0.0195 0.50005,0.50005 0 0 0 0.0176,0.0176 0.50005,0.50005 0 0 0 0.004,0.002 0.50005,0.50005 0 0 0 0.0332,0.0312 0.50005,0.50005 0 0 0 0.004,0.002 A 0.50005,0.50005 0 0 0 259.58203,592 H 271.5 a 0.50005,0.50005 0 1 0 0,-1 H 260 v -11.5 a 0.50005,0.50005 0 0 0 -0.50781,-0.50781 z m 1.94922,3.08593 a 0.50005,0.50005 0 1 0 0,1 h 1 a 0.50005,0.50005 0 1 0 0,-1 z m 3.28515,0.70899 a 0.50005,0.50005 0 0 0 -0.18945,0.96484 l 1,0.41602 a 0.50005,0.50005 0 1 0 0.38477,-0.92188 l -1,-0.41797 a 0.50005,0.50005 0 0 0 -0.19532,-0.041 z m 2.57813,1.99609 a 0.50005,0.50005 0 0 0 -0.45508,0.69922 l 0.41797,1 a 0.50037731,0.50037731 0 0 0 0.92383,-0.38476 l -0.41797,-1 a 0.50005,0.50005 0 0 0 -0.46875,-0.31446 z m 1.16601,3.25 a 0.50005,0.50005 0 0 0 -0.49218,0.50586 v 1 a 0.50005,0.50005 0 1 0 1,0 v -1 a 0.50005,0.50005 0 0 0 -0.50782,-0.50586 z" + transform="translate(537,262)" + id="path15133-4" + inkscape:connector-curvature="0" /> + - - - - - - + id="g15124-5" + transform="translate(-558,-200)" + inkscape:label="BA-12"> - + inkscape:export-ydpi="96" + inkscape:label="BA-11"> + inkscape:export-ydpi="96" + inkscape:label="BA-10"> + - - + id="g4159-3" + transform="translate(61.9487,1.10183)" + style="display:inline;fill:#ffffff;enable-background:new" /> + + inkscape:label="BA-9"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:7.17647;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 181.5,578 c -3.57273,0 -6.5,2.92727 -6.5,6.5 0,1.60752 0.59683,3.08042 1.57422,4.21875 l -2.42774,2.42773 a 0.50005,0.50005 0 1 0 0.70704,0.70704 l 2.42578,-2.42579 c 1.13822,0.977 2.61355,1.57227 4.2207,1.57227 3.57273,0 6.5,-2.92728 6.5,-6.5 0,-3.57273 -2.92727,-6.5 -6.5,-6.5 z m 0,1 c 2.85852,0 5.21992,2.20548 5.47461,5 h -6.26758 l 2.14649,-2.14648 c 0.32529,-0.31801 0.0914,-0.86992 -0.36329,-0.85743 -0.12976,0.004 -0.25303,0.0575 -0.34375,0.15039 l -2.95703,2.95704 c -0.26095,0.19951 -0.26189,0.59214 -0.002,0.79296 0.002,10e-4 0.004,0.003 0.006,0.004 l 2.95312,2.95313 c 0.47127,0.49023 1.19727,-0.23577 0.70704,-0.70704 L 180.70703,585 h 6.26758 c -0.25469,2.79451 -2.61609,5 -5.47461,5 -3.02727,0 -5.5,-2.47273 -5.5,-5.5 0,-3.02728 2.47273,-5.5 5.5,-5.5 z" + id="path9890-4" + inkscape:connector-curvature="0" /> + + + id="g5759" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="BA-8"> + + style="display:inline;fill:#ffffff;enable-background:new" + id="g10061" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="BA-7"> - + id="g9978" + transform="translate(159,3)" + style="display:inline;fill:#ffffff;enable-background:new"> + + + + style="display:inline;opacity:0.6;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:new" + transform="translate(-21,84.9994)" + id="g10042"> + inkscape:export-ydpi="96" + inkscape:label="BA-6"> + + + + + + + + transform="translate(640,-112)" + id="g7753-3"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m -539.42969,690.01758 c -0.79787,-0.038 -1.57177,0.27684 -2.16015,0.86523 l -0.48633,0.48828 c -0.61577,0.57533 -0.93433,1.35407 -0.89258,2.1543 a 0.50005,0.50005 0 1 0 0.99805,-0.0508 c -0.0269,-0.51495 0.15566,-0.98016 0.57617,-1.37305 a 0.50005,0.50005 0 0 0 0.0117,-0.0117 l 0.5,-0.5 c 0.41161,-0.41161 0.88966,-0.59872 1.40429,-0.57422 0.51464,0.0245 1.08439,0.26993 1.63868,0.82422 0.55479,0.5548 0.80954,1.13546 0.83789,1.65235 0.0283,0.51688 -0.15241,0.9848 -0.57422,1.3789 a 0.50005,0.50005 0 0 0 -0.0137,0.0117 l -0.5,0.5 c -0.41213,0.41213 -0.87694,0.60352 -1.39649,0.60352 a 0.50005,0.50005 0 1 0 0,1 c 0.78067,0 1.52491,-0.31788 2.10352,-0.89649 l 0.48828,-0.48828 c 0.61768,-0.57711 0.9366,-1.36125 0.89258,-2.16406 -0.044,-0.80281 -0.43566,-1.60948 -1.13086,-2.30469 -0.69571,-0.69571 -1.49901,-1.07724 -2.29688,-1.11523 z m -7.00976,7 c -0.80753,-0.0482 -1.5954,0.26944 -2.17578,0.89062 l -0.48829,0.48828 c -0.58838,0.58839 -0.90322,1.36034 -0.86523,2.15821 0.038,0.79787 0.41952,1.60311 1.11523,2.29883 0.69521,0.6952 1.50384,1.08487 2.30664,1.1289 0.80281,0.044 1.585,-0.27294 2.16211,-0.89062 l 0.48829,-0.48828 C -543.31787,702.0249 -543,701.28067 -543,700.5 a 0.50005,0.50005 0 1 0 -1,0 c 0,0.51955 -0.19139,0.98436 -0.60352,1.39648 l -0.5,0.5 a 0.50005,0.50005 0 0 0 -0.0117,0.0117 c -0.39411,0.42182 -0.86007,0.60452 -1.37696,0.57618 -0.51688,-0.0283 -1.0995,-0.2831 -1.65429,-0.8379 -0.55429,-0.55428 -0.79776,-1.12404 -0.82227,-1.63867 -0.0245,-0.51463 0.16065,-0.99268 0.57227,-1.40429 l 0.5,-0.5 a 0.50005,0.50005 0 0 0 0.0117,-0.0117 c 0.39634,-0.4242 0.86629,-0.60725 1.38672,-0.57618 a 0.50005,0.50005 0 1 0 0.0586,-0.99804 z" + id="path12469-4" + inkscape:connector-curvature="0" /> + inkscape:label="BA-3"> + inkscape:connector-curvature="0" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 48.5,578 c -0.276131,3e-5 -0.499972,0.22387 -0.5,0.5 L 47.9922,589 c 0,1.51667 1.219299,3 2.984374,3 1.765077,0 3.015625,-1.475 3.015626,-3 L 54,578.5 c -2.8e-5,-0.27613 -0.223869,-0.49997 -0.5,-0.5 z m 2.5,10 c 0.546362,0 1,0.45364 1,1 0,0.54636 -0.453638,1 -1,1 -0.546362,0 -1,-0.45364 -1,-1 0,-0.54636 0.453638,-1 1,-1 z" + transform="translate(-0.99998)" + id="path13694" /> + sodipodi:nodetypes="ccccccccccc" /> + - + id="g21044" + inkscape:label="BA-2" + style="display:inline;enable-background:new"> + inkscape:export-ydpi="96" + inkscape:label="BA-1"> + id="g24378" + inkscape:label="AA-26" + style="display:inline;enable-background:new"> + id="g24375" + inkscape:label="AA-25" + style="display:inline;enable-background:new"> - - - - + id="g10896" + inkscape:label="AA-18" + style="display:inline;enable-background:new"> + - + id="g24381" + inkscape:label="AA-17" + style="display:inline;enable-background:new"> + sodipodi:nodetypes="cccccccccccccccccccccc" /> - - + transform="translate(-390.004,-305.996)" + style="display:inline;fill:#ffffff;enable-background:new" + id="g15818-1" + inkscape:label="AA-16"> + + transform="translate(42)" + id="g15380-9" + style="opacity:1;fill:#ffffff"> + + + - - - - + style="display:inline;fill:#ffffff;enable-background:new" + id="g13069" + transform="translate(-21,42)" + inkscape:label="AA-13"> + + + + + + inkscape:export-ydpi="96" + inkscape:label="AA-11"> + + id="g23031" + style="opacity:0.7;fill:#ffffff"> + + + + + + style="display:inline;fill:#ffffff;enable-background:new" + id="g25445" + transform="translate(-218,-111)"> + - - - + id="g22484" + transform="translate(-220,-220)" + inkscape:label="AA-6"> + + - + transform="translate(462.005,-96.0051)" + inkscape:label="AA-5"> - + transform="matrix(-1,0,0,1,384.832,63)" + style="display:inline;fill:#ffffff;enable-background:new" + id="g23034" + inkscape:label="AA-4"> + inkscape:label="AA-3"> + inkscape:connector-curvature="0" + style="display:inline;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;enable-background:new" + d="m 49,558 v 3 h 3 v -3 z m 3,3 v 3 h 3 v -3 z m 3,0 h 3 v -3 h -3 z m 3,0 v 3 h 3 v -3 z m 0,3 h -3 v 3 h 3 z m 0,3 v 3 h 3 v -3 z m -3,0 h -3 v 3 h 3 z m -3,0 v -3 h -3 v 3 z" + id="path10767" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" /> - - + inkscape:export-ydpi="96" + inkscape:label="AA-2"> + + + + + + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 1025.4902,1561.0059 a 0.50005,0.50005 0 0 0 -0.3437,0.8476 l 0.8535,0.8535 v 1.543 a 0.50005,0.50005 0 1 0 1,0 V 1563 h 2 v 1.25 a 0.50005,0.50005 0 1 0 1,0 v -1.543 l 0.8535,-0.8535 a 0.50005,0.50005 0 0 0 -0.707,-0.707 L 1029.293,1562 h -2.586 l -0.8535,-0.8535 a 0.50005,0.50005 0 0 0 -0.3633,-0.1406 z" + id="path24817" + inkscape:connector-curvature="0" /> + id="g27728" + inkscape:label="Z-25" + style="display:inline;enable-background:new"> + sodipodi:nodetypes="cccccccccc" + inkscape:connector-curvature="0" + d="m 516,545 h 2 v -2 h -2 z m 0,-4 h 2 v -2 h -2 z" + style="display:inline;opacity:0.8;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;enable-background:new" + id="path19014-3-8" /> + inkscape:export-ydpi="96" + inkscape:label="Z-24"> + width="16" + id="rect17483" + style="display:inline;overflow:visible;visibility:visible;opacity:0;fill:#ffffff;stroke:none;stroke-width:3.42857;marker:none;enable-background:accumulate" /> + + + id="path17742-5" + d="m 468.50781,535.99219 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 13 a 0.50005,0.50005 0 0 0 0.5,0.5 h 13 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -13 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z m 0.5,1 h 12 v 5.00586 l -1,-0.006 v 2 l 1,0.006 V 546 h -1.00586 l 0.006,-2.00781 h -2 L 478.00195,546 h -2 l 0.006,-2.00781 h -2 L 474.00195,546 h -2 l 0.006,-2.00781 h -2 L 470.00195,546 h -0.99414 v -2.00391 l 1,-0.006 v -2 l -1,0.006 z m 3,7 h 2 v -2 h -2 z m 4,0 h 2 v -2 h -2 z" + style="display:inline;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;enable-background:new" /> + - - - + inkscape:export-ydpi="96" + inkscape:label="Z-22"> + + - - - + id="g27716" + inkscape:label="Z-21" + style="display:inline;enable-background:new"> + sodipodi:nodetypes="cccccccccccccccccccccccccccccccccc" /> + + + + inkscape:export-ydpi="96" + inkscape:label="Z-18"> + style="display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.8;marker:none;enable-background:accumulate" + d="m 363.5,536 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 13 a 0.50005,0.50005 0 0 0 0.5,0.5 h 13 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -13 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z m 0.5,1 h 12 v 12 h -12 z m 2,2 v 1 h 1 v -1 z m 1,1 v 1 h 1 v -1 z m 1,1 v 1 h 1 v -1 z m 0,1 h -1 v 1 h 1 z m -1,1 h -1 v 1 h 1 z m 2,0 v 1 h 3 v -1 z" + transform="translate(63,-42)" + id="rect40533-7-0" + inkscape:connector-curvature="0" /> + + + id="g7978"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.928338;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 373.21906,563.36205 c -2.04533,0 -3.71335,1.66802 -3.71335,3.71335 0,2.04534 1.66802,3.71336 3.71335,3.71336 2.04534,0 3.71336,-1.66802 3.71336,-3.71336 0,-2.04533 -1.66802,-3.71335 -3.71336,-3.71335 z" + id="path7726" + inkscape:connector-curvature="0" + sodipodi:nodetypes="sssss" /> + + + + + + + transform="translate(0,231)" + style="opacity:1;fill:#ffffff" + id="g12112"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.7;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 332.5,307 v 1 c 0.55917,0 0.9084,0.14278 1.13281,0.36719 C 333.85722,308.5916 334,308.94083 334,309.5 v 5 c 0,0.55917 -0.14278,0.9084 -0.36719,1.13281 C 333.4084,315.85722 333.05917,316 332.5,316 h -2 v 1 h 2 c 0.73927,0 1.38886,-0.20918 1.83984,-0.66016 C 334.79082,315.88886 335,315.23927 335,314.5 v -5 c 0,-0.73927 -0.20918,-1.38886 -0.66016,-1.83984 C 333.88886,307.20918 333.23927,307 332.5,307 Z" + id="path12102" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccsssssccsssssc" /> - + inkscape:export-ydpi="96" + inkscape:label="Z-15"> + + + - + inkscape:label="Z-13"> + + + + + inkscape:label="Z-12"> + - - - + inkscape:export-ydpi="96" + inkscape:label="Z-11"> + transform="translate(-1602.05,188)" + style="display:inline;enable-background:new" + id="g26614" + inkscape:label="Z-10"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.7;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 1796.5,348 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 5.5 h 1 v -5 h 2 v -1 z m 7.5,0 v 1 h 7 v 5 h 1 v -5.5 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z m -8,11 v 2.5 a 0.50005,0.50005 0 0 0 0.5,0.5 h 2.5 v -1 h -2 v -2 z m 15,0 v 2 h -7 v 1 h 7.5 a 0.50005,0.50005 0 0 0 0.5,-0.5 V 359 Z" + id="path33581" + inkscape:connector-curvature="0" /> + sodipodi:nodetypes="cccccccccc" + id="path33583" + style="display:inline;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;enable-background:new" + d="m 1800,348 h 3 v 14 h -3 z m -4,7 h 16 v 3 h -16 z" + inkscape:connector-curvature="0" /> + transform="translate(-1141,-748)" + id="g17971-7" + inkscape:label="Z-9"> + + + + + + id="path17404-8-7" + transform="translate(1162,873.994)" + d="m 164.48438,535.00586 a 0.50005,0.50005 0 0 0 -0.1211,0.0195 l -1.36328,0.3887 0.01,2.03906 1.63085,-0.46679 A 0.50005,0.50005 0 0 0 165,536.50586 v -1 a 0.50005,0.50005 0 0 0 -0.51562,-0.5 z M 162,535.70117 l -2,0.57031 0.01,2.03907 2,-0.57227 z m -3,0.85547 -1.63672,0.46875 A 0.50005,0.50005 0 0 0 157,537.50586 v 1 a 0.50005,0.50005 0 0 0 0.63672,0.48047 l 1.36914,-0.39063 z M 157,542 v 1 l 8.5,0.01 c 0.276,-5e-4 0.4995,-0.224 0.5,-0.5 -5e-4,-0.276 -0.224,-0.4995 -0.5,-0.5 z m -3,2.00586 v 5.49219 c 0,0.2761 0.2239,0.5 0.5,0.5 h 11 c 0.2761,0 0.5,-0.2239 0.5,-0.5 v -4.99219 c 0,-0.2761 -0.2239,-0.5 -0.5,-0.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + + id="g27719" + inkscape:label="Z-7" + style="display:inline;enable-background:new"> + inkscape:connector-curvature="0" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 134.49999,536.00009 c -1.3764,0 -2.5,1.1236 -2.5,2.5 v 7 c 0,1.3764 1.1236,2.5 2.5,2.5 h 0.5 v 2.5 c -8e-4,0.4686 0.5855,0.6809 0.8848,0.3203 l 2.3496,-2.8203 h 5.2656 c 1.3764,0 2.5,-1.1236 2.5,-2.5 v -7 c 0,-1.3764 -1.1236,-2.5 -2.5,-2.5 z m 3.5,2 h 2 v 2 h -2 z m -1,3 h 3 v 4 h 1 v 1 h -4 v -1 h 1 v -3 h -1 z" + id="path17991-2" /> + inkscape:export-ydpi="96" + inkscape:label="Z-6"> + id="path12191-1" + d="m 136.5,557 c -0.25,0 -0.5,0.25 -0.5,0.5 v 5.5 c 0,0.5 0.25,1 1,1 0.75,0 1,-0.5 1,-1 v -1 c 0,-0.5 0.53412,-1 1,-1 0.55229,0 1,0.44772 1,1 v 3.5 c 0,0.82843 0.67157,1.5 1.5,1.5 0.82843,0 1.5,-0.67157 1.5,-1.5 V 562 c 0,-0.55228 0.44772,-1 1,-1 h 0.5 c 0.85547,0 1.5,-0.66406 1.5,-1.5 v -2 c 0,-0.25 -0.25,-0.5 -0.5,-0.5 z" + style="display:inline;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;enable-background:new" /> - - + style="display:inline;fill:#ffffff;enable-background:new" + transform="translate(-273,442)" + id="g28092-0-4" + inkscape:label="Z-5"> + sodipodi:nodetypes="cccccccccc" + inkscape:connector-curvature="0" + id="path28072-2-7" + d="m 363.5,95 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 V 99 h 14 v -1.5 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 H 368 v -1.5 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + + id="g27725" + inkscape:label="Z-4" + style="display:inline;enable-background:new"> + inkscape:connector-curvature="0" + d="m 71.5,536 c -1.3819,0 -2.5,1.1181 -2.5,2.5 v 1 c 0,1.3818 1.118,2.5 2.5,2.5 h 9 c 1.382,0 2.5,-1.1182 2.5,-2.5 v -1 c 0,-1.3857 -1.13452,-2.48443 -2.51172,-2.48633 z m 3.5,1.00586 5.48633,0.008 c 0.8628,0 1.51367,0.63203 1.51367,1.48633 v 1 c 0,0.8578 -0.642,1.5 -1.5,1.5 H 75 Z M 71.5,544 c -1.3819,0 -2.5,1.1181 -2.5,2.5 v 1 c 0,1.3818 1.118,2.5 2.5,2.5 h 9 c 1.382,0 2.5,-1.1182 2.5,-2.5 v -1 c 0,-1.3857 -1.13452,-2.48443 -2.51172,-2.48633 z m 6.50977,1.00977 2.47656,0.004 c 0.8628,0 1.51367,0.63203 1.51367,1.48633 v 1 c 0,0.8578 -0.642,1.5 -1.5,1.5 H 78 Z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + id="path18055-4" /> + id="g7579" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="Z-3"> + + inkscape:export-ydpi="96" + inkscape:label="Z-2"> + + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 81.75,32.099609 c -1.433114,0 -2.573994,0.599965 -3.328125,1.414063 -0.754131,0.814098 -1.193245,1.779187 -1.615234,2.65039 -0.42199,0.871204 -0.825206,1.64712 -1.308594,2.146485 -0.483388,0.499365 -0.99066,0.789062 -1.998047,0.789062 H 69 v 2 h 4.5 c 1.476988,0 2.648427,-0.585302 3.435547,-1.398437 0.78712,-0.813135 1.246208,-1.787219 1.671875,-2.666016 0.425667,-0.878796 0.819561,-1.663707 1.28125,-2.162109 0.461689,-0.498402 0.919442,-0.773438 1.861328,-0.773438 H 83 v -2 z" + id="path4336" + inkscape:connector-curvature="0" /> + id="g23052" + style="opacity:0.7;fill:#ffffff"> + inkscape:export-ydpi="96" + inkscape:label="Z-1"> + + + - - - - - - + id="g14371" + transform="matrix(1,0,0,-1,336,494)" + inkscape:label="Y-25"> + + + + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 176.5,-32 c 0.8225,0 1.5,-0.677495 1.5,-1.5 0,-0.822505 -0.6775,-1.5 -1.5,-1.5 -0.8225,0 -1.5,0.677495 -1.5,1.5 0,0.822505 0.6775,1.5 1.5,1.5 z" + id="circle14357" + inkscape:connector-curvature="0" + sodipodi:nodetypes="sssss" /> - + transform="matrix(-1,0,0,1,677,550)" + id="g14389" + inkscape:label="Y-24"> + + + + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 176.5,-32 c 0.8225,0 1.5,-0.677495 1.5,-1.5 0,-0.822505 -0.6775,-1.5 -1.5,-1.5 -0.8225,0 -1.5,0.677495 -1.5,1.5 0,0.822505 0.6775,1.5 1.5,1.5 z" + id="circle14385" + inkscape:connector-curvature="0" + sodipodi:nodetypes="sssss" /> - + style="display:inline;fill:#ffffff;enable-background:new" + id="g14048" + transform="translate(294,571)" + inkscape:label="Y-23"> + + + + + id="g14223" + transform="rotate(180,317.495,236.5015)" + inkscape:label="Y-22"> + + + + + - - + id="g31068" + inkscape:label="Y-20" + style="display:inline;enable-background:new"> + style="display:inline;fill:#ffffff;enable-background:new" + id="g14377" + transform="translate(210,592)" + inkscape:label="Y-19"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 184.49414,-76.005859 c -0.4494,8.8e-5 -0.67059,0.546839 -0.34766,0.859375 l 2.64649,2.646484 -2.64649,2.646484 c -0.4905,0.471264 0.23578,1.197538 0.70704,0.707032 l 3,-3 c 0.19518,-0.195265 0.19518,-0.511767 0,-0.707032 l -3,-3 c -0.0942,-0.09737 -0.2239,-0.152345 -0.35938,-0.152343 z M 175.49219,-73 c -0.27615,0.0043 -0.49651,0.231666 -0.49219,0.507812 v 1 c -0.01,0.676161 1.00956,0.676161 1,0 v -1 c 0.004,-0.282265 -0.22554,-0.512227 -0.50781,-0.507812 z m 5.00781,0 c -0.67616,-0.0096 -0.67616,1.009563 0,1 h 1 c 0.67616,0.0096 0.67616,-1.009563 0,-1 z m 3,0 c -0.67616,-0.0096 -0.67616,1.009563 0,1 h 1 c 0.67616,0.0096 0.67616,-1.009563 0,-1 z m -6,0.0078 c -0.67616,-0.0096 -0.67616,1.009563 0,1 h 1 c 0.67616,0.0096 0.67616,-1.009563 0,-1 z M 175.49219,-70 c -0.27615,0.0043 -0.49651,0.231666 -0.49219,0.507812 v 1 c -0.01,0.676161 1.00956,0.676161 1,0 v -1 c 0.004,-0.282265 -0.22554,-0.512227 -0.50781,-0.507812 z" + id="path14373" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccccccccccccccccccccccccccccccc" /> + sodipodi:nodetypes="sssss" /> + transform="rotate(180,275.5,225.9975)" + id="g14033" + inkscape:label="Y-18"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 184.49414,-76.005859 c -0.4494,8.8e-5 -0.67059,0.546839 -0.34766,0.859375 l 2.64649,2.646484 -2.64649,2.646484 c -0.4905,0.471264 0.23578,1.197538 0.70704,0.707032 l 3,-3 c 0.19518,-0.195265 0.19518,-0.511767 0,-0.707032 l -3,-3 c -0.0942,-0.09737 -0.2239,-0.152345 -0.35938,-0.152343 z M 175.49219,-73 c -0.27615,0.0043 -0.49651,0.231666 -0.49219,0.507812 v 1 c -0.01,0.676161 1.00956,0.676161 1,0 v -1 c 0.004,-0.282265 -0.22554,-0.512227 -0.50781,-0.507812 z m 5.00781,0 c -0.67616,-0.0096 -0.67616,1.009563 0,1 h 1 c 0.67616,0.0096 0.67616,-1.009563 0,-1 z m 3,0 c -0.67616,-0.0096 -0.67616,1.009563 0,1 h 1 c 0.67616,0.0096 0.67616,-1.009563 0,-1 z m -6,0.0078 c -0.67616,-0.0096 -0.67616,1.009563 0,1 h 1 c 0.67616,0.0096 0.67616,-1.009563 0,-1 z M 175.49219,-70 c -0.27615,0.0043 -0.49651,0.231666 -0.49219,0.507812 v 1 c -0.01,0.676161 1.00956,0.676161 1,0 v -1 c 0.004,-0.282265 -0.22554,-0.512227 -0.50781,-0.507812 z" + id="path14029" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccccccccccccccccccccccccccccccc" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 175.5,-67 c -0.8225,0 -1.5,0.677495 -1.5,1.5 0,0.822505 0.6775,1.5 1.5,1.5 0.8225,0 1.5,-0.677495 1.5,-1.5 0,-0.822505 -0.6775,-1.5 -1.5,-1.5 z" + id="circle14031" + inkscape:connector-curvature="0" + sodipodi:nodetypes="sssss" /> - - - - + id="g15557" + transform="translate(42)" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="Y-17"> + + + + - - + transform="translate(-0.0299934)" + style="display:inline;fill:#ffffff;enable-background:new" + id="g14346" + inkscape:label="Y-9"> + + + id="path21020" + d="m 342.5,348 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 3 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 3 c 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 v -3 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + sodipodi:nodetypes="ccccccccc" /> + + + + + + - - + id="g22292" + transform="translate(-88,-170)" + inkscape:label="Y-6"> - + transform="translate(20,10)" + id="g22287" + style="fill:#ffffff"> + inkscape:connector-curvature="0" + id="path22290" + d="m 203,691 v 5 c 0,0.5 0.25,1 1,1 0.75,0 1,-0.5 1,-1 v -1 c 0,-0.5 0.53412,-1 1,-1 0.55229,0 1,0.44772 1,1 v 2.5 c 0,0.82843 0.67157,1.5 1.5,1.5 0.82843,0 1.5,-0.67157 1.5,-1.5 V 697 c 0,-0.55228 0.44772,-1 1,-1 0.55229,0 1,-0.44771 1,-1 v -1 h -3 v -3 z" + style="display:inline;opacity:0.6;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;enable-background:new" + sodipodi:nodetypes="csssssssssssscccc" /> + id="g13475" + transform="translate(-21)" + inkscape:label="Y-5"> + transform="translate(63,-231)" + id="g12189" + style="opacity:0.6;fill:#ffffff"> + sodipodi:nodetypes="csssssssssssscccccccccc" /> + transform="matrix(-1,0,0,1,89,-231)" + id="g12197" + inkscape:label="Y-4"> + id="path12191" + transform="matrix(-1,0,0,1,90,231)" + d="m 75,519 v 7 c 0,0.5 0.25,1 1,1 0.75,0 1,-0.5 1,-1 v -1 c 0,-0.5 0.53412,-1 1,-1 0.55229,0 1,0.44772 1,1 v 2.5 c 0,0.82843 0.67157,1.5 1.5,1.5 0.82843,0 1.5,-0.67157 1.5,-1.5 V 526 c 0,-0.55228 0.44772,-1 1,-1 0.55229,0 1,-0.44771 1,-1 v -3.08789 A 1.50015,1.50015 0 0 1 83.5,521 h -3 A 1.50015,1.50015 0 0 1 79,519.5 V 519 Z" + style="display:inline;opacity:0.6;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;enable-background:new" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 16.5,746 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 1.5 h -6 v -0.5 A 0.50005,0.50005 0 0 0 9.5,747 h -3 A 0.50005,0.50005 0 0 0 6,747.5 v 3 a 0.50005,0.50005 0 0 0 0.5,0.5 h 3 A 0.50005,0.50005 0 0 0 10,750.5 V 749 h 6 v 0.5 a 0.50005,0.50005 0 0 0 0.5,0.5 h 3 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -3 A 0.50005,0.50005 0 0 0 19.5,746 Z m 0.5,1 h 2 v 2 h -2 z m -10,1 h 2 v 2 H 7 Z" + id="path12195" + inkscape:connector-curvature="0" /> - - - - - - - - + id="g12761" + transform="translate(72)" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="Y-3"> + + + + style="display:inline;opacity:1;fill:#ffffff;enable-background:new" + transform="rotate(90,-175.5,-141.5)" + id="g12255" + mask="none"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 237.5,284 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 4 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 4 c 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 v -4 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 z" + id="path12253" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccccccc" /> + inkscape:label="Y-1"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 27.5,347 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 3.5 h 1 v -3 h 3 v -1 z m 9.5,0 v 1 h 3 v 3 h 1 v -3.5 A 0.50005,0.50005 0 0 0 40.5,347 Z m -10,10 v 3.5 a 0.50005,0.50005 0 0 0 0.5,0.5 H 31 v -1 h -3 v -3 z m 13,0 v 3 h -3 v 1 h 3.5 A 0.50005,0.50005 0 0 0 41,360.5 V 357 Z" + id="path15947-7" + inkscape:connector-curvature="0" /> + sodipodi:nodetypes="ccccccccc" /> - - - + id="g31077" + inkscape:label="X-26" + style="display:inline;enable-background:new"> + id="g20181-9" + style="display:inline;opacity:0.6;fill:#ffffff;enable-background:new" + transform="translate(-288.996,63.999)" + inkscape:label="X-25"> + + + + id="path8055" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 823.98994,430.00453 v 2 h 5 v 3.7086 l 1.8538,-1.85485 c 0.0937,-0.0939 0.14631,-0.22111 0.1462,-0.35375 0.0114,-0.97402 0,-3.0415 0,-3.5 z m -3,3 v 4 h 7 l -4e-5,-4 z m 11.48438,1.125 c -0.12717,0.004 -0.24801,0.0564 -0.3379,0.14648 l -3.72851,3.72852 h -8.91211 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 5 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 8.49414 v -4.5 a 0.50005,0.50005 0 0 1 0.49219,-0.50586 0.50005,0.50005 0 0 1 0.50781,0.50586 v 4.33203 l 3.85352,-3.85351 c 0.0938,-0.0938 0.14645,-0.22092 0.14648,-0.35352 v -5 c 1.1e-4,-0.28235 -0.23342,-0.50879 -0.51562,-0.5 z" + inkscape:connector-curvature="0" /> + inkscape:label="X-23"> + id="path8180-3" + d="m 470.66016,493.97656 c -0.37961,-0.006 -0.76447,0.0652 -1.1211,0.21289 -0.93286,0.38641 -1.54492,1.30084 -1.54492,2.31055 v 3.5 h 1 v -3.5 c 0,-0.60813 0.3659,-1.154 0.92774,-1.38672 0.5437,-0.22521 1.42162,-0.0569 1.71874,0.24024 l 0.5,0.5 c 0.47128,0.49023 1.19727,-0.23577 0.70704,-0.70704 l -0.5,-0.5 c -0.35144,-0.35143 -0.81472,-0.56325 -1.31055,-0.63867 -0.12396,-0.0188 -0.25042,-0.0294 -0.37695,-0.0312 z m 8.67382,0 c -0.12653,0.002 -0.25299,0.0124 -0.37695,0.0312 -0.49583,0.0754 -0.95911,0.28724 -1.31055,0.63867 l -0.5,0.5 c -0.49023,0.47127 0.23577,1.19727 0.70704,0.70704 l 0.5,-0.5 c 0.29713,-0.29714 1.17505,-0.46545 1.71875,-0.24024 C 480.63411,495.346 481,495.89187 481,496.5 v 3.5 h 1 v -3.5 c 0,-1.00971 -0.61206,-1.92414 -1.54492,-2.31055 -0.35663,-0.1477 -0.74149,-0.21856 -1.1211,-0.21289 z M 468.49414,501 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 6 a 0.50005,0.50005 0 0 0 0.5,0.5 h 1 3.00586 a 0.50005,0.50005 0 0 0 0.41602,-0.22266 L 474.76758,505 h 0.46484 l 1.85156,2.77734 A 0.50005,0.50005 0 0 0 477.5,508 h 2.99414 1 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -6 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z M 474,502 v 2.34766 L 472.23242,507 h -2.23828 -1 L 469,502.00195 Z m 1.99414,0 5,0.002 V 507 h -0.5 H 480 477.76758 L 476,504.34766 Z" + style="fill:#ffffff;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" + inkscape:connector-curvature="0" /> + id="path8043-2" + d="m 470.49414,503 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 2 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 H 471.5 c 0.16717,-10e-6 0.32328,-0.0836 0.41602,-0.22266 l 1,-1.5 c 0.0559,-0.0838 0.0852,-0.18249 0.084,-0.2832 l -0.006,-0.5 c -0.003,-0.27384 -0.22614,-0.49413 -0.5,-0.49414 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.3;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> - - - + id="g13344" + transform="translate(-42,21)" + inkscape:label="X-22"> + d="m 449.5,494 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 1 c 0,-0.0417 0.006,-0.0117 -0.0469,0.0547 -0.0531,0.0664 -0.15023,0.15467 -0.2539,0.23242 -0.16609,0.12457 -0.2761,0.17993 -0.33789,0.21289 H 447.5 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 1 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 1.77539 c 0.40889,-0.49501 0.89786,-0.93068 1.47461,-1.26367 l 0.42578,-0.26758 L 451,496.29297 V 494.5 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 z m 8.28125,0.002 c -0.50453,0.0148 -1.01019,0.15125 -1.46875,0.41602 a 0.50005,0.50005 0 0 0 -0.0156,0.01 l -5.04688,3.17579 -0.0156,0.01 a 0.50005,0.50005 0 0 0 -0.19532,0.2207 c -1.45784,0.9884 -2.27996,2.6934 -1.9707,4.44727 0.32821,1.86134 1.78904,3.32218 3.65039,3.65039 0.641,0.11303 1.28349,0.0836 1.88867,-0.0703 a 0.50005,0.50005 0 1 0 -0.24609,-0.96875 c -0.47064,0.11969 -0.96915,0.14277 -1.46875,0.0547 -1.45073,-0.25581 -2.58404,-1.38913 -2.83985,-2.83985 -0.2558,-1.45072 0.42152,-2.90212 1.69727,-3.63867 a 0.50005,0.50005 0 0 0 0.0156,-0.01 l 0.0274,-0.0156 a 0.50005,0.50005 0 0 0 0.01,-0.008 l 5.00977,-3.15039 c 0.83503,-0.4821 1.88265,-0.34391 2.56445,0.33789 0.6818,0.68178 0.81999,1.72943 0.33789,2.56445 a 0.50005,0.50005 0 0 0 -0.006,0.0137 0.50005,0.50005 0 0 0 -0.0195,0.0371 l -1.6289,3.02539 a 0.50005,0.50005 0 1 0 0.8789,0.47266 l 1.63477,-3.03711 0.008,-0.0117 c 6.7e-4,-10e-4 -6.7e-4,-0.003 0,-0.004 l 0.006,-0.01 a 0.50005,0.50005 0 0 0 0.0586,-0.3125 c 0.51014,-1.16591 0.35331,-2.52952 -0.5625,-3.44531 -0.49921,-0.49921 -1.13538,-0.80107 -1.80078,-0.88868 -0.16635,-0.0219 -0.33377,-0.0303 -0.50195,-0.0254 z M 458,496 a 1,1 0 0 0 -1,1 1,1 0 0 0 1,1 1,1 0 0 0 1,-1 1,1 0 0 0 -1,-1 z m -4.65039,2.64258 -1.05078,0.66211 a 1.50015,1.50015 0 0 1 -0.0488,0.0293 c -0.34485,0.1991 -0.62663,0.47142 -0.83594,0.78711 l 4.22461,4.22657 1.3613,1.35936 V 507.5 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 1 c 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 v -1 c 0,-0.0833 0.0571,-0.22505 0.16602,-0.33398 C 459.27495,506.05708 459.41667,506 459.5,506 h 1 c 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 v -1 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 h -1.79297 z" + transform="translate(42,-21)" + id="path13315" + inkscape:connector-curvature="0" /> - - - - - - - + style="display:inline;fill:#ffffff;enable-background:new" + id="g20455" + transform="rotate(180,359.4965,417.0035)" + inkscape:label="X-21"> + sodipodi:nodetypes="ccccccccccccc" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 284.25,326 a 0.50005,0.50005 0 0 0 -0.35352,0.14648 l -4.75,4.75 A 0.50005,0.50005 0 0 0 279,331.25 l -0.008,8.25586 a 0.50005,0.50005 0 0 0 0.50195,0.50195 L 287.75,340 a 0.50005,0.50005 0 0 0 0.35352,-0.14648 l 4.75,-4.75 A 0.50005,0.50005 0 0 0 293,334.75 l -0.008,-8.24414 a 0.50005,0.50005 0 0 0 -0.49805,-0.49805 z m 0.20703,1 7.53711,0.006 0.006,7.53711 -4.45703,4.45703 -7.55078,0.008 0.008,-7.55078 z" + id="path20449" + inkscape:connector-curvature="0" /> - + id="g12222" + transform="rotate(180,422.506,501)" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="X-20"> - - + id="g23057" + style="opacity:1;fill:#ffffff"> - + id="g31074" + inkscape:label="X-19" + style="display:inline;enable-background:new"> - + d="m 388.50781,494 c -0.82415,0 -1.5,0.67974 -1.5,1.5 v 1 0.5 h -0.5 -1 c -0.82026,0 -1.5,0.67584 -1.5,1.5 0,0.82416 0.67974,1.5 1.5,1.5 h 1 c 0.42622,0 0.76643,-0.21374 1.03516,-0.50195 l 4.96289,4.96484 C 392.21502,504.73149 392,505.07159 392,505.5 v 1 c 0,0.82025 0.67584,1.5 1.5,1.5 0.82416,0 1.5,-0.67974 1.5,-1.5 v -1 -0.5 h 0.5 1 c 0.82026,0 1.5,-0.67584 1.5,-1.5 0,-0.82416 -0.67974,-1.5 -1.5,-1.5 h -1 c -0.42622,0 -0.76642,0.21375 -1.03516,0.50195 l -4.96289,-4.96484 c 0.29085,-0.2686 0.50586,-0.60869 0.50586,-1.03711 v -1 c 0,-0.82026 -0.67584,-1.5 -1.5,-1.5 z" + id="path12213" + inkscape:connector-curvature="0" + sodipodi:nodetypes="sscccssssccsssscccssssccsss" /> + id="g16377" + transform="translate(-217,44)" + inkscape:label="X-18"> + sodipodi:nodetypes="ssccsssssscccccccsccccsccc" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 584,453 c -1.09935,0 -2,0.90065 -2,2 v 1 c 0,1.09935 0.90065,2 2,2 h 1 c 1.09935,0 2,-0.90065 2,-2 v -1 c 0,-1.09935 -0.90065,-2 -2,-2 z m -1.51562,6 c -0.12717,0.004 -0.248,0.0564 -0.3379,0.14648 -0.47053,0.47054 -0.96071,0.83572 -1.375,1.25 C 580.3572,460.81077 580,461.33333 580,462 v 1.5 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 8 c 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 V 462 c 0,-0.66667 -0.3572,-1.18923 -0.77148,-1.60352 -0.41429,-0.41428 -0.90447,-0.77946 -1.375,-1.25 -0.32034,-0.32057 -0.86773,-0.0838 -0.85352,0.36914 8.5e-4,0.0269 -0.0479,0.18374 -0.16406,0.30274 C 585.71981,459.93736 585.56612,460 585.5,460 h -2 c -0.0803,0 -0.22193,-0.0571 -0.33203,-0.16797 -0.1101,-0.11083 -0.16786,-0.25596 -0.16797,-0.33203 1.1e-4,-0.28235 -0.23341,-0.50879 -0.51562,-0.5 z" + id="path16332" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ssssssssscccsccccsccccssccc" /> - - - + id="g31092" + inkscape:label="X-17" + style="display:inline;enable-background:new"> - - - - - - + id="g23520-0" + inkscape:label="X-16"> + inkscape:connector-curvature="0" + d="m 1461,518 c -1.3523,-0.0191 -1.3523,2.01913 0,2 h 2 c 1.3523,0.0191 1.3523,-2.01913 0,-2 z m -5,-1 v 7.5 c 0,0.27613 0.2239,0.49997 0.5,0.5 h 11 c 0.2761,-3e-5 0.5,-0.22387 0.5,-0.5 V 517 h -1 v 7 h -10 v -7 z m 12.5,-1 c 0.2761,-3e-5 0.5,-0.22387 0.5,-0.5 v -3 c 0,-0.27613 -0.2239,-0.49997 -0.5,-0.5 h -13 c -0.2761,3e-5 -0.5,0.22387 -0.5,0.5 v 3 c 0,0.27613 0.2239,0.49997 0.5,0.5 z m -12.5,-3 h 12 v 2 h -12 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + id="path23618-7" + sodipodi:nodetypes="cccccccccccccccccccccccccccccc" /> - + id="g31086" + inkscape:label="X-15" + style="display:inline;enable-background:new"> + inkscape:connector-curvature="0" + id="path6351" + d="m 310.57031,494.01758 c -0.79787,-0.038 -1.57177,0.27684 -2.16015,0.86523 l -1.48633,1.48828 c -0.61577,0.57533 -0.93433,1.35407 -0.89258,2.1543 a 0.50005,0.50005 0 1 0 0.99805,-0.0508 c -0.0269,-0.51495 0.15566,-0.98016 0.57617,-1.37305 a 0.50005,0.50005 0 0 0 0.0117,-0.0117 l 1.5,-1.5 c 0.41161,-0.41161 0.88966,-0.59872 1.40429,-0.57422 0.51464,0.0245 1.08439,0.26993 1.63868,0.82422 0.55479,0.5548 0.80954,1.13546 0.83789,1.65235 0.0283,0.51688 -0.15241,0.9848 -0.57422,1.3789 a 0.50005,0.50005 0 0 0 -0.0137,0.0117 l -1.5,1.5 c -0.41213,0.41213 -0.87694,0.60352 -1.39649,0.60352 a 0.50005,0.50005 0 1 0 0,1 c 0.78067,0 1.52491,-0.31788 2.10352,-0.89649 l 1.48828,-1.48828 c 0.61768,-0.57711 0.9366,-1.36125 0.89258,-2.16406 -0.044,-0.80281 -0.43566,-1.60948 -1.13086,-2.30469 -0.69571,-0.69571 -1.49901,-1.07724 -2.29688,-1.11523 z m -1.08008,3.97851 a 0.50005,0.50005 0 0 0 -0.34375,0.15039 l -5,5 a 0.50005,0.50005 0 1 0 0.70704,0.70704 l 5,-5 a 0.50005,0.50005 0 0 0 -0.36329,-0.85743 z m -4.92968,2.02149 c -0.80753,-0.0482 -1.5954,0.26944 -2.17578,0.89062 l -1.48829,1.48828 c -0.58838,0.58839 -0.90322,1.36034 -0.86523,2.15821 0.038,0.79787 0.41952,1.60311 1.11523,2.29883 0.69521,0.6952 1.50384,1.08487 2.30664,1.1289 0.80281,0.044 1.585,-0.27294 2.16211,-0.89062 l 1.48829,-1.48828 C 307.68213,505.0249 308,504.28067 308,503.5 a 0.50005,0.50005 0 1 0 -1,0 c 0,0.51955 -0.19139,0.98436 -0.60352,1.39648 l -1.5,1.5 a 0.50005,0.50005 0 0 0 -0.0117,0.0117 c -0.39411,0.42182 -0.86007,0.60452 -1.37696,0.57618 -0.51688,-0.0283 -1.0995,-0.2831 -1.65429,-0.8379 -0.55429,-0.55428 -0.79776,-1.12404 -0.82227,-1.63867 -0.0245,-0.51463 0.16065,-0.99268 0.57227,-1.40429 l 1.5,-1.5 a 0.50005,0.50005 0 0 0 0.0117,-0.0117 c 0.39634,-0.4242 0.86629,-0.60725 1.38672,-0.57618 a 0.50005,0.50005 0 1 0 0.0586,-0.99804 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> - + id="g6359" + transform="rotate(90,306.5,333.5)" + inkscape:label="X-14"> + sodipodi:nodetypes="sssss" + inkscape:connector-curvature="0" + id="path6353" + d="m 469.5,356 c -1.37478,0 -2.5,1.12522 -2.5,2.5 0,1.37478 1.12522,2.5 2.5,2.5 1.37478,0 2.5,-1.12522 2.5,-2.5 0,-1.37478 -1.12522,-2.5 -2.5,-2.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + + + + id="g24304" + inkscape:label="X-13"> + id="path24300" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.5;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m -357.5,1856.0041 h 0.49805 c 3.5943,0 5.00195,-2.5001 5.00195,-4.5001 v 0 c 0,-2.4794 -2.02064,-4.5 -4.5,-4.5 -1.88915,0 -3.45662,1.2594 -4.10547,3 h -0.51758 c -0.64602,-0.6132 -1.47755,-0.9979 -2.375,-1 h -0.002 c -1.92707,0 -3.5,1.5729 -3.5,3.5 0,1.9271 1.57293,3.5 3.5,3.5 z m -6,-1.0041 c -1.38663,0 -2.5,-1.1134 -2.5,-2.5 0,-1.386 1.11233,-2.4989 2.49805,-2.5 0.71036,0 1.38608,0.3048 1.85937,0.834 0.0951,0.1059 0.23074,0.1663 0.37305,0.166 h 0.90234 c 0.22809,10e-5 0.42734,-0.1542 0.48438,-0.375 0.39914,-1.5459 1.78609,-2.6224 3.38281,-2.625 1.93892,0 3.5,1.5611 3.5,3.5 0,1.9279 -1.54523,3.4765 -3.46875,3.4941 -0.0392,0 -0.0785,-6e-4 -0.11719,0.01 H -357.5 Z m -1.00781,1.9922 c -0.27614,0 -0.4965,0.2317 -0.49219,0.5078 v 1 1 c 0.009,0.6573 0.9907,0.6573 1,0 v -0.5 h 1 v 1.5 c 3e-5,0.1326 0.0527,0.2597 0.14648,0.3535 l 1,1 c 0.0937,0.094 0.22092,0.1465 0.35352,0.1465 h 5 c 0.27613,0 0.49997,-0.2239 0.5,-0.5 v -1.5039 h 0.33594 l 2.61914,1.9043 c 0.0852,0.064 0.18852,0.099 0.29492,0.1 h 0.25 c 0.27613,0 0.49997,-0.2239 0.5,-0.5 v -4 c -3e-5,-0.2761 -0.22387,-0.5 -0.5,-0.5 h -0.25 c -0.106,-10e-5 -0.20927,0.034 -0.29492,0.096 L -355.66211,1859 H -356 v -2 h -1 v 4 h -4.29297 L -362,1860.293 V 1857 h -1 v 1 h -1 v -0.5 c 0.004,-0.2823 -0.22555,-0.5122 -0.50781,-0.5078 z M -353,1858.3008 v 2.3984 l -1.65039,-1.1992 z" + inkscape:connector-curvature="0" + sodipodi:nodetypes="csssscccsscccsccccccscccccccccccccccccccccsccsccccccccccccccccccc" /> + + transform="matrix(-1,0,0,1,552.832,1.5e-5)" + inkscape:label="X-12"> - + id="path5994" + d="m 310.58203,494 c -1.1875,0 -2.13583,0.69085 -2.52344,1.62109 -0.3876,0.93025 -0.20775,2.10475 0.66993,2.98243 l 2.5,2.5 c 0.62232,0.62232 0.69247,1.32282 0.45507,1.89257 C 311.4462,503.56585 310.89453,504 310.08203,504 l -6.53515,-0.008 2.13867,-2.13867 a 0.50005,0.50005 0 1 0 -0.70703,-0.70704 l -2.9336,2.93555 -0.01,0.008 a 0.50005,0.50005 0 0 0 -0.20704,0.41602 0.50005,0.50005 0 0 0 0,0.008 0.50005,0.50005 0 0 0 0.004,0.0469 0.50005,0.50005 0 0 0 0.008,0.0488 0.50005,0.50005 0 0 0 0.19336,0.29882 l 2.94532,2.94532 a 0.50005,0.50005 0 1 0 0.70703,-0.70704 l -2.1543,-2.15429 6.55078,0.008 c 1.1875,0 2.13584,-0.69085 2.52344,-1.62109 0.3876,-0.93025 0.20775,-2.10475 -0.66992,-2.98243 l -2.5,-2.5 c -0.62233,-0.62232 -0.69248,-1.32282 -0.45508,-1.89257 0.2374,-0.56976 0.78906,-1.00391 1.60156,-1.00391 h 4.75 a 0.50005,0.50005 0 1 0 0,-1 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:7.4;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> - - - + id="g5920" + transform="translate(168,-63)" + inkscape:label="X-11"> + inkscape:connector-curvature="0" + id="path5918" + d="m 48.5,557 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 13 a 0.50005,0.50005 0 0 0 0.5,0.5 h 13 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -13 A 0.50005,0.50005 0 0 0 61.5,557 Z m 0.5,1 h 12 v 12 H 49 Z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + inkscape:export-ydpi="96" + inkscape:export-xdpi="96" + inkscape:export-filename="blender_icons.png" + transform="matrix(0.866668,0,0,0.866668,-9.46699,121.399)" + id="g5914" + style="display:inline;fill:#ffffff;stroke-width:1.15384;enable-background:new" + inkscape:label="X-10"> - - - - + id="g5912" + style="fill:#ffffff;stroke-width:1.15384"> + - + inkscape:label="X-9"> + inkscape:connector-curvature="0" + id="path24809" + d="m 1028,1556 c -3.2833,0 -6,2.4414 -6,5.5 0,1.7056 0.9046,3.146 2.1523,4.3594 l 0.9942,0.9941 a 0.50004997,0.50004997 0 0 0 0.3027,0.1485 0.50004997,0.50004997 0 0 0 0.051,0.998 h 5 a 0.50004997,0.50004997 0 0 0 0.045,-0.998 0.50004997,0.50004997 0 0 0 0.3086,-0.1485 l 0.9981,-0.998 v 0 c 1.2122,-1.1985 2.1465,-2.6518 2.1465,-4.3535 0,-3.0586 -2.7167,-5.5 -6,-5.5 z m 0,1 c 2.7919,0 5,2.0358 5,4.5 0,1.3342 -0.7428,2.5488 -1.8516,3.6445 l -1,1 a 0.50004997,0.50004997 0 0 0 0.2793,0.8535 h -4.8594 a 0.50004997,0.50004997 0 0 0 0.2871,-0.8535 l -1,-1 a 0.50004997,0.50004997 0 0 0 -0.01,-0.01 c -1.1397,-1.1082 -1.8477,-2.2864 -1.8477,-3.6406 0,-2.4642 2.2081,-4.5 5,-4.5 z m -2.5,12 a 0.50004997,0.50004997 0 1 0 0,1 h 1.5 v 0.5 a 0.50004997,0.50004997 0 0 0 0.5,0.5 h 1 a 0.50004997,0.50004997 0 0 0 0.5,-0.5 v -0.5 h 1.5 a 0.50004997,0.50004997 0 1 0 0,-1 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + inkscape:connector-curvature="0" + id="path24811" + d="m 1025.4902,1561.0059 a 0.50005,0.50005 0 0 0 -0.3437,0.8476 l 0.8535,0.8535 v 1.543 a 0.50005,0.50005 0 1 0 1,0 V 1563 h 2 v 1.25 a 0.50005,0.50005 0 1 0 1,0 v -1.543 l 0.8535,-0.8535 a 0.50005,0.50005 0 0 0 -0.707,-0.707 L 1029.293,1562 h -2.586 l -0.8535,-0.8535 a 0.50005,0.50005 0 0 0 -0.3633,-0.1406 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + + + id="path20392" + d="m 154.49904,493.99904 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 3 a 0.50005,0.50005 0 0 0 0.5,0.5 h 0.86133 l -0.82812,3.30664 a 0.50005,0.50005 0 0 0 -0.002,0.006 0.50005,0.50005 0 0 0 -0.0352,0.14258 0.50005,0.50005 0 0 0 0,0.004 l -1.48047,5.91993 a 0.50005,0.50005 0 0 0 0.48437,0.62304 h 4.92383 a 0.50005,0.50005 0 0 0 0.14453,0 h 4.90821 a 0.50005,0.50005 0 0 0 0.48437,-0.3789 l 3.03711,-11.99024 a 0.50005,0.50005 0 0 0 -0.48437,-0.62109 l -4.91016,-0.006 a 0.50005,0.50005 0 0 0 -0.20117,0 l -3.40235,-0.006 v -0.5 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z m 0.5,1 h 2 v 2 h -0.92187 a 0.50005,0.50005 0 0 0 -0.14453,0 h -0.9336 z m 3,1 2.85938,0.004 -1.24805,4.99609 h -3.96875 l 0.75,-3 h 1.10742 a 0.50005,0.50005 0 0 0 0.5,-0.5 z m 3.89063,0.006 3.98047,0.006 -1.26367,4.98828 h -3.96485 z m -6.49805,5.99414 h 3.96875 l -1.25195,5.00195 h -3.9668 z m 5,0 h 3.96094 l -1.26758,5.00195 h -3.94336 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> - + inkscape:label="X-7"> - - - + id="g31083" + inkscape:label="X-6" + style="display:inline;enable-background:new"> - - - + inkscape:connector-curvature="0" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 115.5,494 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 3 a 0.50005,0.50005 0 0 0 0.5,0.5 h 3 a 0.50005,0.50005 0 0 0 0.5,-0.5 V 496 h 0.5 c 1.29307,0 2.42587,0.35206 3.21875,1.00977 C 123.51163,497.66747 124,498.62406 124,500 c 0,1.58333 -0.78109,3.05511 -2.24023,4.16406 C 120.30062,505.27301 118.15909,506 115.5,506 H 115 v -1.5 a 0.50005,0.50005 0 0 0 -0.5,-0.5 h -3 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 3 a 0.50005,0.50005 0 0 0 0.5,0.5 h 3 a 0.50005,0.50005 0 0 0 0.5,-0.5 V 507 h 0.5 c 2.84091,0 5.19938,-0.77301 6.86523,-2.03906 C 124.03109,503.69489 125,501.91667 125,500 c 0,-1.62406 -0.62718,-2.91747 -1.64258,-3.75977 C 122.34202,495.39794 120.97378,495 119.5,495 H 119 v -0.5 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z m 0.5,1 h 2 v 2 h -2 z m -4,10 h 2 v 2 h -2 z" + id="path6006" /> + id="g31080" + inkscape:label="X-5" + style="display:inline;enable-background:new"> - - - + inkscape:connector-curvature="0" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 90.5,494 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 3 a 0.50005,0.50005 0 0 0 0.5,0.5 h 0.949219 L 95,504.62695 V 507.5 a 0.50005,0.50005 0 0 0 0.5,0.5 h 3 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -2.87305 L 102.55078,498 H 103.5 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -3 a 0.50005,0.50005 0 0 0 -0.5,-0.5 h -3 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 0.5 h -6 v -0.5 A 0.50005,0.50005 0 0 0 93.5,494 Z m 0.5,1 h 2 v 2 h -2 z m 10,0 h 2 v 2 h -2 z m -7,1 h 6 v 1.5 a 0.50005,0.50005 0 0 0 0.5,0.5 h 0.91406 l -3.214841,6 h -2.398438 l -3.214843,-6 H 93.5 a 0.50005,0.50005 0 0 0 0.5,-0.5 z m 2,9 h 2 v 2 h -2 z" + id="path6000" /> + id="g6348" + transform="translate(42,147)" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="X-4"> + inkscape:connector-curvature="0" + id="path6344" + d="m 27.5,347 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 3.5 h 1 v -3 h 3 v -1 z m 9.5,0 v 1 h 3 v 3 h 1 v -3.5 A 0.50005,0.50005 0 0 0 40.5,347 Z m -10,10 v 3.5 a 0.50005,0.50005 0 0 0 0.5,0.5 H 31 v -1 h -3 v -3 z m 13,0 v 3 h -3 v 1 h 3.5 A 0.50005,0.50005 0 0 0 41,360.5 V 357 Z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + sodipodi:nodetypes="ccccccccc" + inkscape:connector-curvature="0" + id="path6346" + d="m 30.5,350 c -0.276131,3e-5 -0.499972,0.22387 -0.5,0.5 v 7 c 2.8e-5,0.27613 0.223869,0.49997 0.5,0.5 h 7 c 0.276131,-3e-5 0.499972,-0.22387 0.5,-0.5 v -7 c -2.8e-5,-0.27613 -0.223869,-0.49997 -0.5,-0.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + id="g5937" + inkscape:label="X-3"> - - - + id="path5935" + transform="translate(-462.005,96.0051)" + d="m 103.49609,556.99023 a 0.50005,0.50005 0 0 0 -0.34375,0.15039 l -1.57031,1.57032 c -1.22858,-1.06562 -2.825838,-1.7168 -4.576171,-1.7168 -3.86007,0 -7,3.13993 -7,7 0,1.75033 0.649231,3.34954 1.714844,4.57813 l -1.568359,1.56835 a 0.50005,0.50005 0 1 0 0.707031,0.70704 l 1.568359,-1.56836 c 1.228585,1.06561 2.827793,1.71484 4.578125,1.71484 3.860071,0 7.000001,-3.13993 7.000001,-7 0,-1.75033 -0.65118,-3.34759 -1.7168,-4.57617 l 1.57032,-1.57031 a 0.50005,0.50005 0 0 0 -0.36329,-0.85743 z m -6.490231,1.00391 c 3.319631,0 6.000001,2.68037 6.000001,6 0,3.31963 -2.68037,6 -6.000001,6 -3.31963,0 -6,-2.68037 -6,-6 0,-0.88092 0.193846,-1.71405 0.533203,-2.4668 l 0.466797,0.4668 1,1 v 2 h 2 v 1 l 1,1 v 2 h 0.75 1.25 l 1,-1 1.000001,-1 v -1 l -1.000001,-1 h -2 -1 l -1,-1 h -1 l 1,-1 h 1 l 2,-2 -1,-1 h -1 l -1,-1 0.894532,-0.89453 c 0.358617,-0.0666 0.727234,-0.10547 1.105468,-0.10547 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.99;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + inkscape:connector-curvature="0" /> + inkscape:label="X-2"> + + id="path8807" + d="M 29.492188,495.99219 A 0.50005,0.50005 0 0 0 29,496.5 v 9 a 0.50005,0.50005 0 0 0 0.5,0.5 h 9 a 0.50005,0.50005 0 1 0 0,-1 H 30 v -8.5 a 0.50005,0.50005 0 0 0 -0.507812,-0.50781 z m -2,2 A 0.50005,0.50005 0 0 0 27,498.5 v 9 a 0.50005,0.50005 0 0 0 0.5,0.5 h 9 a 0.50005,0.50005 0 1 0 0,-1 H 28 v -8.5 a 0.50005,0.50005 0 0 0 -0.507812,-0.50781 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.693;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + id="g23466" + transform="translate(-1.85367e-6,-0.999995)" + inkscape:label="X-1"> - + inkscape:label="W-26"> + + + + + + - + id="path13640-8-2" + d="m 540.5,472.25 c -1.933,0 -3.5,1.567 -3.5,3.5 0,1.933 1.567,3.5 3.5,3.5 1.933,0 3.5,-1.567 3.5,-3.5 0,-1.933 -1.567,-3.5 -3.5,-3.5 z m -0.5,1 h 1 v 2 h 2 v 1 h -2 v 2 h -1 v -2 h -2 v -1 h 2 z" + style="display:inline;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:new" /> + id="g5506-8" + transform="translate(-44,63)" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="W-25"> + + inkscape:label="W-24"> + + + + - - - - + id="g19103-5" + transform="rotate(-90,394.5,1587.5)" + inkscape:label="W-22"> - - - + id="g9998" + transform="matrix(-1,0,0,1,908,4.69997e-6)" + inkscape:label="W-21"> + - - + - - - - - - - + inkscape:label="W-20"> + id="path17287" + d="m 405.5,476.00781 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 3 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 0.99999,-1e-5 1.99999,10e-6 3,0 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 v -3 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 z m 5,1.99219 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 3 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 1,0 1.99999,10e-6 3,0 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 v -3 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 -1,0 -1.99999,-10e-6 -3,0 z m -5,3.00781 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 3 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 0.99999,-1e-5 1.99999,10e-6 3,0 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 v -3 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 z m 5,1.99219 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 3 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 0.99999,-10e-6 1.99999,10e-6 3,0 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 v -3 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:7.4;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + + + - + id="g12585" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + transform="matrix(-1,0,0,1,782,4.49997e-6)" + inkscape:label="W-19"> - - - + d="m 393.5,473 c -0.82416,0 -1.5,0.67974 -1.5,1.5 v 1 c 0,0.42842 0.21502,0.76851 0.50586,1.03711 l -4.96289,4.96484 C 387.27424,481.21375 386.93403,481 386.50781,481 h -1 c -0.82026,0 -1.5,0.67584 -1.5,1.5 0,0.82416 0.67974,1.5 1.5,1.5 h 1 0.5 v 0.5 1 c 0,0.82026 0.67585,1.5 1.5,1.5 0.82416,0 1.5,-0.67975 1.5,-1.5 v -1 c 0,-0.42841 -0.21502,-0.76851 -0.50586,-1.03711 l 4.96289,-4.96484 C 394.73357,478.78626 395.07378,479 395.5,479 h 1 c 0.82026,0 1.5,-0.67584 1.5,-1.5 0,-0.82416 -0.67974,-1.5 -1.5,-1.5 h -1 -0.5 v -0.5 -1 c 0,-0.82026 -0.67584,-1.5 -1.5,-1.5 z" + id="path12208" + inkscape:connector-curvature="0" + sodipodi:nodetypes="sssccsssscccssssccsssscccss" /> + id="g23341" + transform="translate(63,-42)" + inkscape:label="W-18"> + transform="translate(231,-63)" + id="g23324" + style="opacity:0.6;fill:#ffffff"> + + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 79.570312,578.01758 c -0.797869,-0.038 -1.571767,0.27684 -2.160156,0.86523 l -0.486328,0.48828 c -0.615772,0.57533 -0.93433,1.35407 -0.892578,2.1543 a 0.50005,0.50005 0 1 0 0.998047,-0.0508 c -0.02687,-0.51495 0.155664,-0.98016 0.576172,-1.37305 a 0.50005,0.50005 0 0 0 0.01172,-0.0117 l 0.5,-0.5 c 0.411611,-0.41161 0.889666,-0.59872 1.404296,-0.57422 0.514631,0.0245 1.084383,0.26993 1.638672,0.82422 0.554795,0.5548 0.809544,1.13546 0.837891,1.65235 0.02835,0.51688 -0.152405,0.9848 -0.574219,1.3789 a 0.50005,0.50005 0 0 0 -0.01367,0.0117 l -0.5,0.5 c -0.412129,0.41213 -0.876932,0.60352 -1.396484,0.60352 a 0.50005,0.50005 0 1 0 0,1 c 0.780667,0 1.524905,-0.31788 2.103516,-0.89649 l 0.488281,-0.48828 c 0.617686,-0.57711 0.936606,-1.36125 0.892578,-2.16406 -0.04403,-0.80281 -0.435654,-1.60948 -1.130859,-2.30469 -0.695711,-0.69571 -1.499006,-1.07724 -2.296876,-1.11523 z m -0.830078,3.72851 a 0.50005,0.50005 0 0 0 -0.34375,0.15039 l -1,1 a 0.50005,0.50005 0 1 0 0.707032,0.70704 l 1,-1 a 0.50005,0.50005 0 0 0 -0.363282,-0.85743 z m -6.179687,3.27149 c -0.807524,-0.0482 -1.595401,0.26944 -2.175781,0.89062 l -0.488282,0.48828 c -0.588388,0.58839 -0.903228,1.36034 -0.865234,2.15821 0.03799,0.79787 0.419524,1.60311 1.115234,2.29883 0.695206,0.6952 1.503832,1.08487 2.306641,1.1289 0.802809,0.044 1.584996,-0.27294 2.162109,-0.89062 l 0.488282,-0.48828 C 75.682126,590.0249 76,589.28067 76,588.5 a 0.50005,0.50005 0 1 0 -1,0 c 0,0.51955 -0.191386,0.98436 -0.603516,1.39648 l -0.5,0.5 a 0.50005,0.50005 0 0 0 -0.01172,0.0117 c -0.394107,0.42182 -0.860068,0.60452 -1.376954,0.57618 -0.516885,-0.0283 -1.099502,-0.2831 -1.654296,-0.8379 -0.55429,-0.55428 -0.79776,-1.12404 -0.822266,-1.63867 -0.02451,-0.51463 0.160654,-0.99268 0.572266,-1.40429 l 0.5,-0.5 a 0.50005,0.50005 0 0 0 0.01172,-0.0117 c 0.39634,-0.4242 0.866283,-0.60725 1.386719,-0.57618 a 0.50005,0.50005 0 1 0 0.05859,-0.99804 z m 1.679687,1.22851 a 0.50005,0.50005 0 0 0 -0.34375,0.15039 l -1,1 a 0.50005,0.50005 0 1 0 0.707032,0.70704 l 1,-1 a 0.50005,0.50005 0 0 0 -0.363282,-0.85743 z" + id="path23322" + inkscape:connector-curvature="0" /> - + id="g37788" + inkscape:label="W-17" + style="display:inline;enable-background:new"> - - - - - + transform="translate(-21)" + id="g12839" + inkscape:label="W-16"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 353.13867,473.0332 c -0.49613,-0.0451 -1.03862,0.15972 -1.49219,0.61328 l -0.75,0.75 c -0.19519,0.19527 -0.19519,0.51177 0,0.70704 l 3,3 c 0.19527,0.19519 0.51177,0.19519 0.70704,0 l 0.75,-0.75 c 0.45356,-0.45357 0.65838,-0.99606 0.61328,-1.49219 -0.0451,-0.49613 -0.30436,-0.90592 -0.61328,-1.21485 l -1,-1 c -0.30893,-0.30892 -0.71872,-0.56817 -1.21485,-0.61328 z m -3.39644,2.7168 c -0.12989,0.002 -0.25387,0.0546 -0.34571,0.14648 l -6.25,6.25 c -0.0639,0.0642 -0.10909,0.14453 -0.13086,0.23243 l -1,4 c -0.0904,0.36537 0.2401,0.69582 0.60547,0.60547 l 4,-1 c 0.0879,-0.0218 0.16823,-0.067 0.23243,-0.13086 l 6.25,-6.25 c 0.19519,-0.19527 0.19519,-0.51177 0,-0.70704 l -3,-3 c -0.0957,-0.0957 -0.22605,-0.14856 -0.36137,-0.14648 z" + id="path12837" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccscccccccccccccccc" /> - + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="W-14"> - - - - + x="437" + y="710" /> + + + + - + transform="translate(-1.85367e-6,21)" + inkscape:label="W-12"> - + id="g37779" + inkscape:label="W-11" + style="display:inline;enable-background:new"> - - - - - - + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="W-10"> + - - - - - - - - - - + transform="translate(539.993,-999)" + id="g36643-9" + style="display:inline;enable-background:new" + inkscape:label="W-9"> + + - + id="g37791" + inkscape:label="W-8" + style="display:inline;enable-background:new"> - - + id="g37785" + inkscape:label="W-7" + style="display:inline;enable-background:new"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 134.50977,472.99414 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 3 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 1.5039 c 0.0205,0.58311 0.0403,1.03618 -0.0488,1.24805 -0.22838,0.54301 -0.5609,0.82668 -1.00781,1.10937 -0.44691,0.28269 -1.00733,0.53131 -1.53711,0.97461 -0.52977,0.4433 -1.00108,1.10059 -1.22656,2.08789 -0.22548,0.9873 -0.2332,2.29903 0.0625,4.16406 0.0385,0.24311 0.24801,0.42203 0.49414,0.42188 h 8.75586 c 0.30739,1.4e-4 0.54213,-0.2745 0.49414,-0.57812 -0.36883,-2.32606 -0.21493,-3.6368 0.12305,-4.40821 0.33798,-0.7714 0.85304,-1.08606 1.49804,-1.45508 0.645,-0.36901 1.43565,-0.7809 1.90821,-1.70703 0.47255,-0.92613 0.59571,-2.25921 0.21289,-4.4375 C 145.70043,474.17484 145.49284,474.0002 145.25,474 h -7.24023 v -0.50586 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 z m 0.5,1 h 2 v 2 h -2 z m 3,1.00586 h 6.78125 c 0.25456,1.74951 0.14487,2.81594 -0.15235,3.39844 -0.3247,0.63637 -0.84774,0.91198 -1.51367,1.29297 -0.66593,0.38098 -1.46183,0.87882 -1.91797,1.91992 -0.41222,0.94085 -0.49675,2.35477 -0.23633,4.38867 h -7.73437 c -0.19264,-1.47462 -0.23076,-2.65205 -0.0684,-3.36328 0.18281,-0.80045 0.49635,-1.21142 0.89258,-1.54297 0.39622,-0.33155 0.90291,-0.56523 1.42968,-0.89844 0.52678,-0.33321 1.07351,-0.7985 1.39649,-1.5664 0.22021,-0.52359 0.17686,-1.05487 0.13867,-1.63477 h 0.48438 c 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 z" + id="path13566" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccccscsccccccccccccccccccccccccccccccccc" /> - - - - + id="g6505" + transform="translate(84,-21)" + inkscape:label="W-6"> + + - - - + id="g37782" + inkscape:label="W-5" + style="display:inline;enable-background:new"> - - - - - - - - - + id="g13097" + inkscape:label="W-4"> + - + inkscape:export-ydpi="96" + inkscape:label="W-3"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 158.48048,492.99995 c -0.15153,0.004 -0.29304,0.0766 -0.38477,0.19727 l -4.94922,4.94921 c -0.31479,0.315 -0.0918,0.85335 0.35352,0.85352 h 5 c 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 v -4.5 h 5 v 12 h -10 v -6 h -1 v 6.5 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 11 c 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 v -13 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 h -6 c -0.005,-6e-5 -0.009,-6e-5 -0.0137,0 -6.7e-4,2e-5 -0.001,-2e-5 -0.002,0 -0.001,4e-5 -0.003,-5e-5 -0.004,0 z" + id="path8730-7" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccccccccccccccccccccc" /> + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="W-2"> + + + d="M 27.5,11 A 0.50005,0.50005 0 0 0 27,11.5 v 10.851562 l 1,-1.75 V 12 h 12 v 12 h -3.099609 c 0.122248,0.339531 0.117057,0.686553 0.0039,1 H 40.5 A 0.50005,0.50005 0 0 0 41,24.5 v -13 A 0.50005,0.50005 0 0 0 40.5,11 Z" + transform="matrix(-1,0,0,1,1290,-303)" + id="path8528-7-3-1" /> - + id="g13176" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="W-1"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 14,472 c -0.486111,0 -0.97894,0.16032 -1.363281,0.50195 C 12.252378,472.84359 12,473.375 12,474 v 4 H 8.5 c -0.2761309,3e-5 -0.4999724,0.22387 -0.5,0.5 v 4 c 0,0.33333 -0.182083,0.72505 -0.4785156,1.02148 C 7.2250518,483.81792 6.8333333,484 6.5,484 c -0.2761309,3e-5 -0.4999724,0.22387 -0.5,0.5 v 2 c 2.76e-5,0.27613 0.2238691,0.49997 0.5,0.5 h 3 c 0.1325988,-2e-5 0.259759,-0.0527 0.3535156,-0.14648 L 11,485.70703 V 486.5 c 2.8e-5,0.27613 0.223869,0.49997 0.5,0.5 h 5 c 1.777778,0 3.5,-1.46429 3.5,-3.5 v -5 c -2.8e-5,-0.27613 -0.223869,-0.49997 -0.5,-0.5 H 16 v -4 c 0,-0.625 -0.252378,-1.15641 -0.636719,-1.49805 C 14.97894,472.16032 14.486111,472 14,472 Z m 0,1 c 0.263889,0 0.52106,0.0897 0.699219,0.24805 C 14.877378,473.40641 15,473.625 15,474 v 4 h -0.5 c -0.676161,-0.01 -0.676161,1.00956 0,1 H 19 v 1 H 9 v -1 h 3.5 c 0.276131,-3e-5 0.499972,-0.22387 0.5,-0.5 V 474 c 0,-0.375 0.122622,-0.59359 0.300781,-0.75195 C 13.47894,473.08968 13.736111,473 14,473 Z m -5,8 h 10 v 2.5 c 0,1.46429 -1.277778,2.5 -2.5,2.5 H 16 v -1.5 c 0.0044,-0.28227 -0.225547,-0.51223 -0.507812,-0.50781 C 15.216044,483.9965 14.995681,484.22386 15,484.5 v 1.5 h -3 v -1.5 c -1.7e-4,-0.44532 -0.538517,-0.6683 -0.853516,-0.35352 L 9.2929688,486 H 7 v -1.22266 C 7.4441801,484.6575 7.9061001,484.55093 8.2285156,484.22852 8.682083,483.77495 9,483.16667 9,482.5 Z" + id="path5504-6" + inkscape:connector-curvature="0" + sodipodi:nodetypes="scsccccccccccccccsccccscsscscccccccccscsccssccccccccccccsc" /> + + + inkscape:export-ydpi="96" + inkscape:export-xdpi="96" + inkscape:export-filename="blender_icons.png" + id="g12901" + transform="matrix(-0.799288,0,0,0.799288,828.432,282.268)" + style="display:inline;opacity:0.7;fill:#ffffff;stroke-width:1.25111;enable-background:new"> - - - + inkscape:export-ydpi="96" + inkscape:export-xdpi="96" + inkscape:export-filename="blender_icons.png" + style="display:inline;opacity:1;fill:#ffffff;stroke-width:1.25111;enable-background:new" + transform="matrix(-0.799288,0,0,0.799288,833.432,275.268)" + id="g12909"> + id="g38026" + inkscape:label="V-25" + style="display:inline;enable-background:new"> - + inkscape:connector-curvature="0" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 511.5,452 c -0.8225,0 -1.5,0.6775 -1.5,1.5 0,0.82251 0.6775,1.5 1.5,1.5 0.64684,0 1.19777,-0.42097 1.40625,-1 h 1.1875 c 0.20848,0.57903 0.75941,1 1.40625,1 0.8225,0 1.5,-0.67749 1.5,-1.5 0,-0.8225 -0.6775,-1.5 -1.5,-1.5 -0.64637,0 -1.19742,0.42162 -1.40625,1 h -1.1875 c -0.20883,-0.57838 -0.75988,-1 -1.40625,-1 z m 11,0 c -0.64637,0 -1.19742,0.42162 -1.40625,1 H 520.5 a 0.50005,0.50005 0 0 0 -0.35352,0.14648 L 516.29297,457 h -3.38672 c -0.20883,-0.57838 -0.75988,-1 -1.40625,-1 -0.8225,0 -1.5,0.6775 -1.5,1.5 0,0.82251 0.6775,1.5 1.5,1.5 0.64684,0 1.19777,-0.42097 1.40625,-1 H 516.5 a 0.50005,0.50005 0 0 0 0.35352,-0.14648 L 520.70703,454 h 0.38672 c 0.20848,0.57903 0.75941,1 1.40625,1 0.8225,0 1.5,-0.67749 1.5,-1.5 0,-0.8225 -0.6775,-1.5 -1.5,-1.5 z m -11,1 c 0.27091,0 0.47782,0.20294 0.49414,0.46875 a 0.50005,0.50005 0 0 0 0,0.0625 C 511.97782,453.79707 511.77091,454 511.5,454 c -0.28206,0 -0.5,-0.21793 -0.5,-0.5 0,-0.28206 0.21794,-0.5 0.5,-0.5 z m 4,0 c 0.28206,0 0.5,0.21794 0.5,0.5 0,0.28207 -0.21794,0.5 -0.5,0.5 -0.27091,0 -0.47782,-0.20293 -0.49414,-0.46875 a 0.50005,0.50005 0 0 0 0,-0.0625 C 515.02218,453.20294 515.22909,453 515.5,453 Z m 7,0 c 0.28206,0 0.5,0.21794 0.5,0.5 0,0.28207 -0.21794,0.5 -0.5,0.5 -0.27091,0 -0.47782,-0.20293 -0.49414,-0.46875 a 0.50005,0.50005 0 0 0 0,-0.0625 C 522.02218,453.20294 522.22909,453 522.5,453 Z m 0,3 c -0.64637,0 -1.19742,0.42162 -1.40625,1 H 520.5 a 0.50005,0.50005 0 0 0 -0.35352,0.14648 l -5,5 A 0.50005,0.50005 0 0 0 515,462.5 v 0.59375 c -0.57903,0.20848 -1,0.75941 -1,1.40625 0,0.82251 0.6775,1.5 1.5,1.5 0.8225,0 1.5,-0.67749 1.5,-1.5 0,-0.64684 -0.42097,-1.19777 -1,-1.40625 v -0.38672 L 520.70703,458 h 0.38672 c 0.20848,0.57903 0.75941,1 1.40625,1 0.8225,0 1.5,-0.67749 1.5,-1.5 0,-0.8225 -0.6775,-1.5 -1.5,-1.5 z m -11,1 c 0.28206,0 0.5,0.21794 0.5,0.5 0,0.28207 -0.21794,0.5 -0.5,0.5 -0.28206,0 -0.5,-0.21793 -0.5,-0.5 0,-0.28206 0.21794,-0.5 0.5,-0.5 z m 11,0 c 0.28206,0 0.5,0.21794 0.5,0.5 0,0.28207 -0.21794,0.5 -0.5,0.5 -0.27091,0 -0.47782,-0.20293 -0.49414,-0.46875 a 0.50005,0.50005 0 0 0 0,-0.0625 C 522.02218,457.20294 522.22909,457 522.5,457 Z m -11,3 c -0.8225,0 -1.5,0.6775 -1.5,1.5 0,0.82251 0.6775,1.5 1.5,1.5 0.8225,0 1.5,-0.67749 1.5,-1.5 0,-0.8225 -0.6775,-1.5 -1.5,-1.5 z m 11,0 c -0.8225,0 -1.5,0.6775 -1.5,1.5 0,0.23017 0.0579,0.44679 0.15234,0.64258 a 0.50005,0.50005 0 0 0 -0.006,0.004 l -1,1 a 0.50005,0.50005 0 0 0 -0.004,0.006 C 519.94679,463.05791 519.73016,463 519.5,463 c -0.8225,0 -1.5,0.6775 -1.5,1.5 0,0.82251 0.6775,1.5 1.5,1.5 0.8225,0 1.5,-0.67749 1.5,-1.5 0,-0.23002 -0.058,-0.44493 -0.15234,-0.64062 a 0.50005,0.50005 0 0 0 0.006,-0.006 l 1,-1 a 0.50005,0.50005 0 0 0 0.004,-0.004 c 0.1955,0.0946 0.41225,0.15062 0.64234,0.15062 0.8225,0 1.5,-0.67749 1.5,-1.5 0,-0.8225 -0.6775,-1.5 -1.5,-1.5 z m -11,1 c 0.28206,0 0.5,0.21794 0.5,0.5 0,0.28207 -0.21794,0.5 -0.5,0.5 -0.28206,0 -0.5,-0.21793 -0.5,-0.5 0,-0.28206 0.21794,-0.5 0.5,-0.5 z m 11,0 c 0.28206,0 0.5,0.21794 0.5,0.5 0,0.28207 -0.21794,0.5 -0.5,0.5 -0.28206,0 -0.5,-0.21793 -0.5,-0.5 0,-0.28206 0.21794,-0.5 0.5,-0.5 z m -3,3 c 0.28206,0 0.5,0.21794 0.5,0.5 0,0.28207 -0.21794,0.5 -0.5,0.5 -0.28206,0 -0.5,-0.21793 -0.5,-0.5 0,-0.28206 0.21794,-0.5 0.5,-0.5 z m -4.0293,0.006 a 0.50005,0.50005 0 0 0 0.0371,0 0.50005,0.50005 0 0 0 0.0176,0 c 0.26874,0.0134 0.4746,0.22107 0.4746,0.494 0,0.28207 -0.21794,0.5 -0.5,0.5 -0.28206,0 -0.5,-0.21793 -0.5,-0.5 0,-0.27158 0.20391,-0.47876 0.4707,-0.49414 z" + id="circle28282" /> + + + id="g38053" + inkscape:label="V-23" + style="display:inline;enable-background:new"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="M 471.32227,452.0625 C 469.61213,452.13527 468,453.5463 468,456 c 0,1.29326 0.43161,2.55298 1.07227,3.72852 0.16015,0.29312 0.55714,0.34983 0.79296,0.11328 l 3.63477,-3.63477 1.64648,1.64649 c 0.19527,0.19519 0.51177,0.19519 0.70704,0 l 2.64648,-2.64649 2.48242,2.48242 c 0.27743,0.27645 0.75058,0.14111 0.83985,-0.24023 C 481.93311,456.97518 482,456.49157 482,456 c 0,-2.4537 -1.61213,-3.86473 -3.32227,-3.9375 -1.52139,-0.0647 -3.0532,0.927 -3.67773,2.69727 -0.62453,-1.77027 -2.15634,-2.76201 -3.67773,-2.69727 z M 478.49219,458 c -0.12989,0.002 -0.25387,0.0546 -0.34571,0.14648 l -2.64648,2.64649 -1.64648,-1.64649 c -0.19527,-0.19519 -0.51177,-0.19519 -0.70704,0 l -2.34765,2.34766 c -0.18257,0.18194 -0.19694,0.47283 -0.0332,0.67188 1.2321,1.49847 2.62304,2.79208 3.65235,3.70703 0.0914,0.0816 0.20953,0.12673 0.33202,0.12695 h 0.5 c 0.12249,-2.2e-4 0.24064,-0.0454 0.33203,-0.12695 1.41546,-1.2582 3.54029,-3.21344 4.96094,-5.48828 0.12374,-0.19754 0.0946,-0.45438 -0.0703,-0.61915 l -1.61914,-1.61914 c -0.0957,-0.0957 -0.22604,-0.14856 -0.36134,-0.14648 z" + id="path6086" + inkscape:connector-curvature="0" + sodipodi:nodetypes="csccccccccscccccccccccccccccc" /> + + - - - + id="g38068" + inkscape:label="V-21" + style="display:inline;enable-background:new"> - - + id="g38035" + inkscape:label="V-20" + style="display:inline;enable-background:new"> + id="path4106" + d="m 408.48148,452.0625 c -1.71014,0.0728 -3.32227,1.4838 -3.32227,3.9375 0,2.05278 1.07076,4.01178 2.38672,5.71289 1.31595,1.70111 2.90024,3.15481 4.03125,4.16016 0.0914,0.0816 0.20954,0.12673 0.33203,0.12695 h 0.5 c 0.12249,-2.2e-4 0.24064,-0.0454 0.33203,-0.12695 1.13101,-1.00535 2.7153,-2.45905 4.03125,-4.16016 1.31596,-1.70111 2.38672,-3.66011 2.38672,-5.71289 0,-2.4537 -1.61213,-3.86473 -3.32227,-3.9375 -1.52139,-0.0647 -3.0532,0.927 -3.67773,2.69727 -0.62453,-1.77027 -2.15634,-2.76201 -3.67773,-2.69727 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + sodipodi:nodetypes="csccccccsccc" /> - + style="display:inline;fill:#ffffff;enable-background:new" + id="g23545-6" + transform="rotate(-90,318.416,448.416)" + inkscape:label="V-19"> + sodipodi:nodetypes="cccccccccccccccssccsscc" /> + transform="translate(696,793)" + inkscape:label="V-18"> + + + + + - + inkscape:label="V-16"> + + + + + + + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 310.26367,452.0293 c -0.67255,-0.0123 -1.33821,0.3 -1.8789,0.8789 l -0.23829,0.23828 a 0.50004997,0.50004997 0 0 0 -0.084,0.10938 0.50004997,0.50004997 0 0 0 -0.0879,0.0723 l -1.26563,1.25196 c -0.23797,0.23547 -0.44036,0.48356 -0.58984,0.75781 a 0.50004997,0.50004997 0 1 0 0.87695,0.47852 c 0.0885,-0.16243 0.22753,-0.33889 0.41602,-0.52539 l 1.26562,-1.25391 a 0.50004997,0.50004997 0 0 0 0.0899,-0.11523 0.50004997,0.50004997 0 0 0 0.002,-0.002 0.50004997,0.50004997 0 0 0 0.084,-0.0664 l 0.25,-0.25 a 0.50004997,0.50004997 0 0 0 0.0117,-0.0117 c 0.39332,-0.4211 0.76594,-0.57109 1.12891,-0.56446 0.36298,0.007 0.75841,0.18208 1.15234,0.57618 0.39504,0.3952 0.56757,0.78617 0.57422,1.14648 0.007,0.36031 -0.14053,0.73397 -0.56445,1.13672 l -0.26758,0.2539 a 0.50004997,0.50004997 0 0 0 -0.0957,0.12305 0.50004997,0.50004997 0 0 0 -0.0859,0.0684 l -1.25,1.25 a 0.50004997,0.50004997 0 0 0 -0.0137,0.0117 c -0.16233,0.1738 -0.32346,0.30196 -0.48047,0.39063 a 0.50026213,0.50026213 0 1 0 0.49219,0.87109 c 0.25644,-0.14482 0.49753,-0.33919 0.7207,-0.57813 l 1.23828,-1.23828 a 0.50004997,0.50004997 0 0 0 0.0859,-0.11133 0.50004997,0.50004997 0 0 0 0.0781,-0.0625 l 0.26563,-0.25195 c 0.57608,-0.54732 0.88934,-1.2117 0.87695,-1.88281 -0.0124,-0.67111 -0.33835,-1.30493 -0.86718,-1.83399 -0.52994,-0.53015 -1.16729,-0.85488 -1.83985,-0.86718 z m -1.77344,3.96679 a 0.50004997,0.50004997 0 0 0 -0.34375,0.15039 l -4.01367,4.01563 a 0.50004997,0.50004997 0 1 0 0.70703,0.70703 l 4.01368,-4.01562 a 0.50004997,0.50004997 0 0 0 -0.36329,-0.85743 z m -4.94921,2.1836 a 0.50004997,0.50004997 0 0 0 -0.22852,0.0469 c -0.33394,0.14944 -0.64532,0.37928 -0.92773,0.68164 l -1.23829,1.23828 a 0.50004997,0.50004997 0 0 0 -0.0547,0.0645 l -0.18555,0.17578 c -0.57608,0.54731 -0.88934,1.2117 -0.87695,1.88281 0.0124,0.67111 0.33835,1.30494 0.86718,1.83399 0.52994,0.53016 1.16729,0.85489 1.83985,0.86718 0.67255,0.0123 1.33821,-0.3 1.8789,-0.8789 l 0.23829,-0.23828 a 0.50004997,0.50004997 0 0 0 0.0605,-0.0723 0.50004997,0.50004997 0 0 0 0.004,-0.004 l 1.18359,-1.17187 c 0.27971,-0.27677 0.50962,-0.57119 0.66211,-0.9043 a 0.50004997,0.50004997 0 1 0 -0.9082,-0.41601 c -0.0851,0.18591 -0.2355,0.39018 -0.45703,0.60937 l -1.26563,1.25391 a 0.50004997,0.50004997 0 0 0 -0.0664,0.0762 0.50004997,0.50004997 0 0 0 -0.004,0.006 l -0.16602,0.16601 a 0.50004997,0.50004997 0 0 0 -0.0117,0.0117 c -0.39332,0.4211 -0.76594,0.57109 -1.12891,0.56446 -0.36298,-0.007 -0.75841,-0.18208 -1.15234,-0.57618 -0.39505,-0.3952 -0.56758,-0.78617 -0.57422,-1.14648 -0.007,-0.36031 0.14053,-0.73396 0.56445,-1.13672 l 0.26758,-0.2539 a 0.50004997,0.50004997 0 0 0 0.0742,-0.0879 l 1.16797,-1.16796 a 0.50004997,0.50004997 0 0 0 0.0117,-0.0117 c 0.20545,-0.21996 0.40997,-0.36564 0.60547,-0.45313 a 0.50004997,0.50004997 0 0 0 -0.17968,-0.95898 z" + id="path23240-3" + inkscape:connector-curvature="0" /> - + id="g24182" + inkscape:label="V-14"> + id="g38041" + inkscape:label="V-13" + style="display:inline;enable-background:new"> - - + id="g38044" + inkscape:label="V-12" + style="display:inline;enable-background:new"> + sodipodi:nodetypes="cccccccccc" /> + + + transform="translate(112.055,339.927)" + id="g44391-4" + style="display:inline;opacity:0.7;fill:#ffffff;enable-background:new"> + + + + inkscape:label="V-10"> + id="path13014" + d="m 176.5,410 a 0.50005,0.50005 0 1 0 0,1 h 0.79297 L 178,411.70703 v 10.58594 L 177.29297,423 H 176.5 a 0.50005,0.50005 0 1 0 0,1 h 1 a 0.50005,0.50005 0 0 0 0.35352,-0.14648 l 0.64648,-0.64649 0.64648,0.64649 A 0.50005,0.50005 0 0 0 179.5,424 h 1 a 0.50005,0.50005 0 1 0 0,-1 h -0.79297 L 179,422.29297 V 411.70703 L 179.70703,411 H 180.5 a 0.50005,0.50005 0 1 0 0,-1 h -1 a 0.50005,0.50005 0 0 0 -0.35352,0.14648 l -0.64648,0.64649 -0.64648,-0.64649 A 0.50005,0.50005 0 0 0 177.5,410 Z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + + id="g15185-9" + inkscape:label="V-9"> + sodipodi:nodetypes="sssssssssssssssccccccccccccccc" /> + + id="g28614" + transform="translate(-147,357)" + style="display:inline;enable-background:new" + inkscape:label="V-8"> + inkscape:connector-curvature="0" + id="path28606" + transform="translate(42)" + d="m 263.50391,94.996094 c -0.25245,0 -0.505,0.169732 -0.5,0.507812 L 263,97 h -2.5 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 2.5 l -1.49414,-0.002 c -0.67616,-0.0096 -0.67616,1.01 0,1 L 260,101 v 2 l -1.49414,-0.002 c -0.67616,-0.01 -0.67616,1.01 0,1 L 260,104 v 2.5 a 0.50005,0.50005 0 0 0 0.5,0.5 h 2.5 l 0.004,1.49609 c -0.01,0.67616 1.00956,0.67616 1,0 L 264,107 h 2 l 0.004,1.49609 c -0.01,0.67616 1.00956,0.67616 1,0 L 267,107 h 2.5 a 0.50005,0.50005 0 0 0 0.5,-0.5 V 104 l 1.49805,-0.002 c 0.67616,0.01 0.67616,-1.00956 0,-1 L 270,103 v -2 l 1.49805,-0.002 c 0.67616,0.01 0.67616,-1.009563 0,-1.000003 L 270,100 V 97.5 A 0.50005,0.50005 0 0 0 269.5,97 H 267 l 0.004,-1.496094 c 0.01,-0.67616 -1.01,-0.67616 -1,0 L 266,97 h -2 l 0.004,-1.496094 c 0.005,-0.33808 -0.24756,-0.507812 -0.5,-0.507812 z M 261,98 h 8 v 8 h -8 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + + + + + id="g38062" + inkscape:label="V-6" + style="display:inline;enable-background:new"> - - - + id="g38059" + inkscape:label="V-5" + style="display:inline;enable-background:new"> - - - - - - - - - - - - - - - - - - + inkscape:export-ydpi="96" + inkscape:label="V-4"> + + id="g38056" + inkscape:label="V-3" + style="display:inline;enable-background:new"> + + + inkscape:connector-curvature="0" + id="rect10947" + d="M 31.484375,452 C 29.563145,452 28,453.56315 28,455.48438 v 8.03124 C 28,465.43685 29.563145,467 31.484375,467 h 4.03125 C 37.436855,467 39,465.43685 39,463.51562 v -8.03124 C 39,453.56315 37.436855,452 35.515625,452 Z m 0,1 h 4.03125 C 36.900155,453 38,454.09985 38,455.48438 v 8.03124 C 38,464.90015 36.900155,466 35.515625,466 h -4.03125 C 30.099845,466 29,464.90015 29,463.51562 v -8.03124 C 29,454.09985 30.099845,453 31.484375,453 Z m 1.5625,1 C 32.475545,454 32,454.47555 32,455.04688 v 3.90624 C 32,459.52445 32.475545,460 33.046875,460 h 0.90625 C 34.524455,460 35,459.52445 35,458.95312 v -3.90624 C 35,454.47555 34.524455,454 33.953125,454 Z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + id="g38029" + inkscape:label="V-1" + style="display:inline;enable-background:new"> + inkscape:connector-curvature="0" + id="path10927" + d="m 10.484375,452 c -0.752,0 -1.4538175,0.239 -2.0234375,0.64453 C 7.5752675,453.27508 7,454.31514 7,455.48438 V 458.5 c 3e-5,0.27537 0.2226769,0.4989 0.4980469,0.5 l 4.0097651,0.008 c 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 L 12,452.53516 v -0.002 -0.0352 C 11.999,452.22272 11.77534,452.00003 11.5,452 Z M 13,452 v 1 h 1.515625 C 15.900145,453 17,454.09985 17,455.48438 v 8.03124 C 17,464.90014 15.900145,466 14.515625,466 h -4.03125 C 9.099855,466 8,464.90014 8,463.51562 V 460 H 7 v 3.51562 C 7,465.43685 8.563145,467 10.484375,467 h 4.03125 C 16.436855,467 18,465.43686 18,463.51562 v -8.03124 C 18,453.56315 16.436855,452 14.515625,452 Z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + + + id="path19349-4" + style="display:inline;enable-background:accumulate;color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto" + d="m 534.23047,436.74023 a 1.0001,1.0001 0 0 0 -0.6875,0.30274 l -1.47656,1.47656 c -0.71563,0.66862 -1.1007,1.61087 -1.04883,2.55664 0.0519,0.94577 0.50998,1.86545 1.27539,2.63086 0.76642,0.76642 1.68942,1.21503 2.6289,1.25977 0.93949,0.0447 1.85838,-0.33299 2.53516,-1.00977 l 1.5,-1.5 a 1.0001,1.0001 0 1 0 -1.41406,-1.41406 l -1.5,1.5 c -0.32323,0.32322 -0.65433,0.4455 -1.02735,0.42773 -0.37301,-0.0178 -0.82501,-0.19415 -1.30859,-0.67773 -0.48459,-0.48459 -0.6709,-0.9542 -0.69141,-1.32813 -0.0205,-0.37392 0.0941,-0.68177 0.41797,-0.98437 a 1.0001,1.0001 0 0 0 0.0234,-0.0234 l 1.5,-1.5 a 1.0001,1.0001 0 0 0 -0.72656,-1.7168 z m 6.84765,-5.70703 c -0.93948,-0.0447 -1.85838,0.33299 -2.53515,1.00977 l -1.5,1.5 a 1.0001,1.0001 0 1 0 1.41406,1.41406 l 1.5,-1.5 c 0.32322,-0.32322 0.65433,-0.4455 1.02734,-0.42773 0.37302,0.0178 0.82502,0.19415 1.3086,0.67773 0.48459,0.48459 0.6709,0.9542 0.6914,1.32813 0.0205,0.37392 -0.0941,0.68177 -0.41796,0.98437 a 1.0001,1.0001 0 0 0 -0.0234,0.0234 l -1.5,1.5 a 1.0001,1.0001 0 1 0 1.41406,1.41406 l 1.47656,-1.47656 c 0.71562,-0.66862 1.1007,-1.61087 1.04883,-2.55664 -0.0519,-0.94577 -0.50998,-1.86545 -1.27539,-2.63086 -0.76642,-0.76642 -1.68942,-1.21503 -2.62891,-1.25977 z m -1.09765,3.95703 a 1.0001,1.0001 0 0 0 -0.6875,0.30274 l -4,4 a 1.0001,1.0001 0 1 0 1.41406,1.41406 l 4,-4 a 1.0001,1.0001 0 0 0 -0.72656,-1.7168 z" + inkscape:connector-curvature="0" + transform="matrix(1,0,0,-1,0,876)" /> - + id="g76979" + style="display:inline;enable-background:new" + inkscape:label="U-25"> + id="g76982" + style="display:inline;enable-background:new" + inkscape:label="U-24"> + inkscape:connector-curvature="0" + id="path22635-0-9" + d="m 494.5,432 a 0.50005,0.50005 0 0 0 -0.35352,0.14648 L 492.29297,434 H 489.5 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 8 a 0.50005,0.50005 0 0 0 0.5,0.5 h 12 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -8 a 0.50005,0.50005 0 0 0 -0.5,-0.5 h -1.79297 l -1.85351,-1.85352 A 0.50005,0.50005 0 0 0 497.5,432 Z m 0.20703,1 h 2.58594 l 1.85351,1.85352 A 0.50005,0.50005 0 0 0 499.5,435 h 1.5 v 7 h -11 v -7 h 2.5 a 0.50005,0.50005 0 0 0 0.35352,-0.14648 z m -0.46094,2.74414 a 0.50005,0.50005 0 0 0 -0.34961,0.85938 l 1.39649,1.39648 -1.39649,1.39648 a 0.50005,0.50005 0 1 0 0.70704,0.70704 L 496,438.70703 l 1.39648,1.39649 a 0.50005,0.50005 0 1 0 0.70704,-0.70704 L 496.70703,438 l 1.39649,-1.39648 a 0.50005,0.50005 0 1 0 -0.70704,-0.70704 L 496,437.29297 l -1.39648,-1.39649 a 0.50005,0.50005 0 0 0 -0.35743,-0.15234 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + + + sodipodi:nodetypes="ccccccccc" + inkscape:connector-curvature="0" + id="path19969-0" + d="m 470.49125,433 c -0.3497,0.006 -0.58488,0.36077 -0.45507,0.68555 l 4,10.00195 c 0.1779,0.45034 0.82806,0.4102 0.94922,-0.0586 l 1.17578,-4.46875 4.46679,-1.17578 c 0.46499,-0.12321 0.50486,-0.76769 0.0586,-0.94727 l -10,-4.00195 c -0.0621,-0.0247 -0.12852,-0.0366 -0.19532,-0.0351 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> - - - + id="g76988" + style="display:inline;enable-background:new" + inkscape:label="U-22"> + inkscape:connector-curvature="0" + id="path19971-4" + d="m 449.49078,433 a 0.50005,0.50005 0 0 0 -0.45507,0.68555 l 4,10.00195 a 0.50050292,0.50050292 0 1 0 0.92968,-0.37109 l -3.5664,-8.91797 8.91601,3.5664 a 0.50005,0.50005 0 1 0 0.3711,-0.92773 l -10,-4.00195 A 0.50005,0.50005 0 0 0 449.49078,433 Z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> - - - - - - + id="g76952" + style="display:inline;enable-background:new" + inkscape:label="U-21"> + + id="g76991" + style="display:inline;enable-background:new" + inkscape:label="U-20"> + inkscape:connector-curvature="0" + id="path13646-9" + d="m 405.50174,437 a 0.50005,0.50005 0 0 0 -0.33203,0.84375 c 1.3239,1.43817 3.0824,4.1582 6.3457,4.1582 3.26331,0 5.02376,-2.72003 6.34766,-4.1582 a 0.50037481,0.50037481 0 1 0 -0.73633,-0.67773 c -1.43556,1.55946 -2.90607,3.83593 -5.61133,3.83593 -2.70525,0 -4.17576,-2.27647 -5.61132,-3.83593 A 0.50005,0.50005 0 0 0 405.50174,437 Z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + inkscape:label="U-19"> + sodipodi:nodetypes="ccccccccsssss" + inkscape:connector-curvature="0" + id="path12959" + d="m 473.49999,411 -2.99609,0.006 c -0.2754,6.1e-4 -0.49976,0.22265 -0.5,0.49805 l -0.004,8.45884 c 0,1.02328 0.80629,2.03747 2,2.03711 1.19534,0 1.99609,-1.02155 1.99609,-2.04297 l 0.004,-8.45703 c -1.8e-4,-0.27638 -0.22362,-0.50048 -0.5,-0.5 z m -1.5,8 c 0.55228,0 1,0.44772 1,1 0,0.55228 -0.44772,1 -1,1 -0.55228,0 -1,-0.44772 -1,-1 0,-0.55228 0.44772,-1 1,-1 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + sodipodi:nodetypes="cccccccccc" + inkscape:connector-curvature="0" + id="path12972" + d="m 478.14648,418.14648 -3,3 c -0.31399,0.31533 -0.0915,0.85354 0.35352,0.85352 h 3 2 c 0.27591,-1.9e-4 0.49982,-0.22409 0.5,-0.5 v -3 c -7e-5,-0.28163 -0.23215,-0.50794 -0.51367,-0.5 h -2 c -0.12731,0.004 -0.24956,0.0566 -0.33985,0.14648 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.5;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + + id="g20019-5" + style="display:inline;opacity:0.6;fill:#ffffff;enable-background:new" + inkscape:label="U-18"> + inkscape:connector-curvature="0" + id="path20013-1" + d="m 365.5,433 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 8.5 c 0,1.09865 0.90135,2 2,2 1.09865,0 2,-0.90135 2,-2 v -8.5 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z m 0.5,1 h 2 v 8 c 0,0.55821 -0.44179,1 -1,1 -0.55821,0 -1,-0.44179 -1,-1 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + + id="g23658-7-6" + transform="translate(-1113,-80)" + inkscape:label="U-17"> + inkscape:export-ydpi="96" + inkscape:export-xdpi="96" + inkscape:export-filename="blender_icons.png" + id="g10909" + style="display:inline;opacity:0.99;fill:#ffffff;enable-background:new" + transform="matrix(-1,0,0,1,1290,263)" + inkscape:label="U-16"> - + id="g76946" + style="display:inline;enable-background:new" + inkscape:label="U-15"> + + - - - + inkscape:label="U-13"> + sodipodi:nodetypes="cssccsscccsccsccccccc" /> + - - - + id="g13834" + transform="rotate(90,244,438)" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="U-12"> + id="g13861" + transform="rotate(-90,244.008,459)" + style="fill:#ffffff;stroke:#ffffff"> - + id="g12310-0" + transform="translate(63,210)" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="U-11"> + + + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 199.50586,431 c -0.31034,2.7e-4 -0.5455,0.28021 -0.49219,0.58594 0.36378,2.06995 0.21088,3.24033 -0.13086,3.98828 -0.34174,0.74794 -0.89741,1.14621 -1.5625,1.65234 -0.66509,0.50614 -1.42742,1.12518 -1.87304,2.24414 -0.44562,1.11896 -0.57595,2.68203 -0.19141,5.10742 0.0385,0.24306 0.24806,0.422 0.49414,0.42188 h 8.75586 c 0.30739,1.4e-4 0.54213,-0.27449 0.49414,-0.57812 -0.36857,-2.32471 -0.21903,-3.70089 0.13086,-4.58204 0.34989,-0.88114 0.89751,-1.32255 1.54492,-1.8164 0.64741,-0.49385 1.4068,-1.03318 1.86328,-2.03516 0.45648,-1.00198 0.58623,-2.39428 0.20313,-4.57422 C 208.70043,431.17484 208.49284,431.0002 208.25,431 Z" + id="path8644-7" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccscccccssccc" /> + id="g76976" + style="display:inline;enable-background:new" + inkscape:label="U-9"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 180.25977,430.99023 c -1.56005,0 -2.83737,0.68503 -4,1.32227 -0.15986,0.0876 -0.25939,0.25522 -0.25977,0.4375 v 1.75 c -0.002,0.58015 0.81953,0.69801 0.98047,0.14062 0.5477,-1.86218 2.26129,-2.69946 3.87109,-2.56445 0.056,0.003 0.0879,0.0161 0.14258,0.0195 C 182.58833,432.2915 184,433.38188 184,435.75 V 437 h -4.17383 c -1.71873,0 -2.95078,0.48602 -3.73828,1.26172 -0.7875,0.77569 -1.09766,1.80326 -1.09766,2.77539 0,1.37482 0.63393,2.43409 1.58008,3.07031 C 177.51646,444.74365 178.73744,445 180,445 c 1.61266,0 3.42658,-0.64151 4.45508,-1.56836 0.43191,0.42904 0.80166,0.78819 1.11328,1.03906 0.38759,0.31203 0.75096,0.5293 1.18164,0.5293 h 0.75 c 0.31531,0 0.5,-0.25 0.5,-0.5 0,-0.25 -0.16406,-0.5 -0.5,-0.5 -0.0833,0 -0.22505,-0.0571 -0.33398,-0.16602 C 187.05708,443.72508 187,443.58333 187,443.5 v -7 c 0,-1.04167 -0.0843,-2.5 -1.08398,-3.57617 -1.37045,-1.47528 -2.91602,-1.9336 -5.65625,-1.9336 z M 181.5,438 h 2.5 v 4.49023 c -0.8549,0.62028 -1.60427,0.95721 -2.2793,1.14844 -0.1561,0.0477 -0.30873,0.061 -0.46679,0.0977 -0.63626,0.10402 -1.2211,0.10508 -1.65821,-0.0742 C 178.58677,443.24823 178,442.20833 178,441 c 0,-0.63889 0.21654,-1.3962 0.74414,-1.97656 C 179.27174,438.44308 180.11111,438 181.5,438 Z" + id="path7432" + inkscape:connector-curvature="0" + sodipodi:nodetypes="sccccccscccscscssssscsscsscccccsss" /> + + + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 160.5,431 a 1.5,1.5 0 0 0 -1.5,1.5 1.5,1.5 0 0 0 0.0879,0.5 h -4.56836 a 1.50015,1.50015 0 1 0 0,3 H 158 v 1.94922 c -1.31983,1.3363 -2.47353,2.76999 -2.9707,5.25586 a 1.50015,1.50015 0 1 0 2.9414,0.58984 c 0.37009,-1.85045 0.99513,-2.60285 2.0293,-3.6289 1.03417,1.02605 1.65921,1.77845 2.0293,3.6289 a 1.50015,1.50015 0 1 0 2.9414,-0.58984 c -0.49717,-2.48587 -1.65087,-3.91956 -2.9707,-5.25586 V 436 h 3.48047 a 1.50015,1.50015 0 1 0 0,-3 h -2.53711 c -0.0387,0.18925 -0.094,0.37697 -0.17774,0.55664 C 162.35553,434.4361 161.47038,435 160.5,435 a 0.50005,0.50005 0 1 1 0,-1 1.5,1.5 0 0 0 0.41406,-0.0586 c 0.009,-0.002 0.0169,-0.005 0.0254,-0.008 a 1.5,1.5 0 0 0 0.35157,-0.16211 c 0.006,-0.004 0.0133,-0.006 0.0195,-0.01 a 1.5,1.5 0 0 0 0.004,-0.002 1.5,1.5 0 0 0 0.29102,-0.25 c 0.009,-0.0103 0.0202,-0.0187 0.0293,-0.0293 a 1.5,1.5 0 0 0 0.21484,-0.33008 c 0.003,-0.006 0.007,-0.01 0.01,-0.0156 0.002,-0.005 0.004,-0.0104 0.006,-0.0156 A 1.5,1.5 0 0 0 162,432.5 1.5,1.5 0 0 0 160.5,431 Z" + id="path13634" + inkscape:connector-curvature="0" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" /> + transform="translate(477,-1375)" + id="g20978-8" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="U-7"> - + id="g76949" + style="display:inline;enable-background:new" + inkscape:label="U-6"> + inkscape:connector-curvature="0" + id="path21010-5" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 115.5,442 a 0.50004997,0.50004997 0 1 0 0,1 h 5 a 0.50004997,0.50004997 0 1 0 0,-1 z m 0,2 a 0.50004997,0.50004997 0 1 0 0,1 h 1.5 v 0.5 a 0.50004997,0.50004997 0 0 0 0.5,0.5 h 1 a 0.50004997,0.50004997 0 0 0 0.5,-0.5 V 445 h 1.5 a 0.50004997,0.50004997 0 1 0 0,-1 z m 2.5,-13 c -3.2833,0 -6,2.4414 -6,5.5 0,1.7222 1.0876,3.2946 2.1465,4.3535 0.094,0.094 0.2209,0.1465 0.3535,0.1465 h 7 c 0.1326,0 0.2598,-0.053 0.3535,-0.1465 0.7109,-0.7108 1.4222,-1.6513 1.8262,-2.7148 C 123.8776,437.6179 124,437.066 124,436.5 c 0,-3.0586 -2.7167,-5.5 -6,-5.5 z m -2.5098,5.0059 c 0.1351,0 0.2662,0.047 0.3633,0.1406 L 116.707,437 h 2.586 l 0.8535,-0.8535 c 0.4712,-0.4506 1.1576,0.2358 0.707,0.707 L 120,437.707 v 1.793 0.5 h -1 v -0.5 -1.5 h -2 v 1.5 0.5 h -1 v -0.5 -1.793 l -0.8535,-0.8535 c -0.302,-0.3119 -0.09,-0.8341 0.3437,-0.8476 z" /> + + + id="path13682" + d="m 100.45117,431 c -0.36065,0.005 -0.725199,0.0666 -1.080078,0.1875 -1.419514,0.48381 -2.371094,1.8128 -2.371094,3.3125 a 0.50005006,0.50005006 0 1 1 -1,0 c 0,-0.53249 0.102694,-1.04562 0.277344,-1.52734 -0.0921,0.0103 -0.173784,0.0274 -0.277344,0.0273 -3.307786,0 -6,2.69221 -6,6 0,3.30779 2.692214,6 6,6 3.307786,0 6.000002,-2.69221 6.000002,-6 v -0.002 c -4.7e-4,-0.11093 0.014,-0.19694 0.0332,-0.27344 -0.83987,0.3046 -1.77022,0.36483 -2.67578,0.12695 a 0.50005006,0.50005006 0 1 1 0.253906,-0.96679 c 1.450494,0.38101 2.978064,-0.20116 3.806644,-1.45118 0.82858,-1.25001 0.7694,-2.88277 -0.14649,-4.07031 -0.68691,-0.89068 -1.73836,-1.37897 -2.82031,-1.36328 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.5;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + id="g14020" + transform="translate(153,38)" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="U-4"> + sodipodi:nodetypes="cccccccccccccccccccccccccccccccccccc" /> + + - + id="g76994" + style="display:inline;enable-background:new" + inkscape:label="U-2"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 28.5,431 a 1.50015,1.50015 0 0 0 -1.341797,2.16992 l 5.5,11 a 1.50015,1.50015 0 0 0 2.683594,0 l 5.5,-11 A 1.50015,1.50015 0 0 0 39.5,431 Z m 2.425781,3 h 6.148438 L 34,440.14648 Z" + id="path14201" + inkscape:connector-curvature="0" /> + id="g76964" + style="display:inline;enable-background:new" + inkscape:label="U-1"> - - - - - + id="g84220" + style="display:inline;enable-background:new" + inkscape:label="T-26"> + - - - + id="g84229" + style="display:inline;enable-background:new" + inkscape:label="T-25"> + d="m 512,414.00018 h 9 v 5 h -9 z m -1.5,-2 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 8 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 5.5 v 1 h -2.5 c -0.67616,-0.01 -0.67616,1.00956 0,1 h 6 c 0.6573,-0.009 0.6573,-0.9907 0,-1 H 517 v -1 h 5.5 c 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 v -8 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 z m 0.5,1 h 11 v 7 h -11 z" + style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke" + id="rect22324-2" /> - - - - + id="g84232" + style="display:inline;enable-background:new" + inkscape:label="T-24"> + + transform="translate(42)" + style="display:inline;fill:#ffffff;enable-background:new" + id="g6098" + inkscape:label="T-23"> - - - - + inkscape:connector-curvature="0" + id="circle19345" + d="m 435.5,411 c -2.47344,0 -4.5,2.02656 -4.5,4.5 0,2.47344 2.02656,4.5 4.5,4.5 2.47344,0 4.5,-2.02656 4.5,-4.5 0,-2.47344 -2.02656,-4.5 -4.5,-4.5 z m 0,2 c 1.39256,0 2.5,1.10744 2.5,2.5 0,1.39256 -1.10744,2.5 -2.5,2.5 -1.39256,0 -2.5,-1.10744 -2.5,-2.5 0,-1.39256 1.10744,-2.5 2.5,-2.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> - + style="display:inline;opacity:0.6;fill:#ffffff;enable-background:new" + id="g22801-1" + transform="translate(696,857.997)" + inkscape:label="T-22"> - - - + id="g84214" + style="display:inline;enable-background:new" + inkscape:label="T-21"> + sodipodi:nodetypes="cccccscccccccccccccccc" + inkscape:connector-curvature="0" + id="path17971-6" + d="m 433.60156,411.10938 c -0.0348,2.9e-4 -0.0695,0.004 -0.10351,0.0117 -2.03912,0.46668 -3.49042,2.28707 -3.49219,4.37891 9e-4,0.50625 0.15089,0.99318 0.31836,1.46875 l -2.42774,2.42773 c -1.00968,1.00969 -1.01874,2.3133 -0.3125,3.01954 0.70624,0.70624 2.00985,0.69718 3.01954,-0.3125 l 2.42968,-2.42969 c 0.47476,0.16775 0.9595,0.31841 1.46485,0.32031 6.7e-4,0 0.001,0 0.002,0 2.10425,-9.3e-4 3.93168,-1.46897 4.38672,-3.52344 0.0316,-0.14237 -4.7e-4,-0.29146 -0.0879,-0.4082 l -0.64844,-0.86328 c -0.17598,-0.23372 -0.51414,-0.2671 -0.73242,-0.0723 L 435.31055,417 h -1.08008 L 433,415.56445 v -0.85742 l 1.85352,-1.85351 c 0.19519,-0.19527 0.19519,-0.51177 0,-0.70704 l -0.89063,-0.89062 c -0.0957,-0.0957 -0.22603,-0.14855 -0.36133,-0.14648 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> - + id="g84217" + style="display:inline;enable-background:new" + inkscape:label="T-20"> + inkscape:connector-curvature="0" + id="path18042-5" + d="m 413.49609,411 c -1.31297,0 -2.2906,0.33758 -3.34961,1.39648 -1.07833,1.0783 -1.34444,2.83666 -0.73437,4.48438 l -2.51563,2.51562 c -1.00968,1.00969 -1.01874,2.3133 -0.3125,3.01954 0.70624,0.70624 2.00985,0.69718 3.01954,-0.3125 l 2.51757,-2.51758 c 1.64574,0.60684 3.4036,0.3464 4.48243,-0.73242 1.05892,-1.05893 1.39668,-2.03818 1.39257,-3.35547 a 0.50005,0.50005 0 0 0 -0.14648,-0.35157 l -1,-1 a 0.50005,0.50005 0 0 0 -0.70703,0 L 414.28906,416 h -0.58594 l -0.70703,-0.70703 v -0.58594 l 1.85352,-1.85351 a 0.50005,0.50005 0 0 0 0,-0.70704 l -1,-1 A 0.50005,0.50005 0 0 0 413.49609,411 Z m -0.18164,1.02539 0.47461,0.47461 -1.64648,1.64648 A 0.50005,0.50005 0 0 0 411.99609,414.5 v 1 a 0.50005,0.50005 0 0 0 0.14649,0.35352 l 1,1 A 0.50005,0.50005 0 0 0 413.49609,417 h 1 a 0.50005,0.50005 0 0 0 0.35352,-0.14648 l 1.64648,-1.64649 0.47461,0.47461 c -0.0187,1.03267 -0.19304,1.58367 -1.07422,2.46484 -0.83209,0.8321 -2.35353,1.13617 -3.75195,0.47266 a 0.50005,0.50005 0 0 0 -0.56836,0.0977 l -2.67969,2.67968 c -0.74031,0.74032 -1.3117,0.60626 -1.60546,0.3125 -0.29376,-0.29376 -0.42782,-0.86515 0.3125,-1.60546 l 2.67773,-2.67774 a 0.50005,0.50005 0 0 0 0.0977,-0.56836 c -0.66677,-1.39852 -0.3581,-2.92122 0.47461,-3.7539 0.88058,-0.8805 1.43264,-1.05616 2.46093,-1.07813 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + id="g19394" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="T-19"> + d="m 390.5,416 c -0.98118,0 -1.79306,0.19786 -2.45312,0.51562 -0.008,0.17574 -0.0332,0.34151 -0.0332,0.52149 0,4.06772 2.58548,6.61176 5.30078,6.95117 2.02213,0.29647 2.40413,-2.76745 0.3711,-2.97656 -1.2847,-0.16059 -2.67188,-1.04233 -2.67188,-3.97461 0,-0.37546 0.0235,-0.72058 0.0664,-1.03711 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + id="path19340" + sodipodi:nodetypes="scsccscs" /> - - - - - - - - - - - + transform="rotate(180,349.0055,417)" + id="g22180" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="T-18"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 331.50586,410 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 3 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 3 c 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 v -3 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 z m -10,10 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 3 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 3 c 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 v -3 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 z" + id="path22182" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccccccccccccc" /> - - - - - + id="g20665" + inkscape:label="T-17"> + inkscape:connector-curvature="0" + id="path20592" + d="m 354.51367,409.98828 a 1.50015,1.50015 0 0 0 -0.80859,0.24024 c -4.9147,3.07168 -4.9147,10.47128 0,13.54296 a 1.50015,1.50015 0 1 0 1.58984,-2.54296 c -3.0853,-1.92832 -3.0853,-6.52872 0,-8.45704 a 1.50015,1.50015 0 0 0 -0.78125,-2.7832 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + id="g10007" + inkscape:label="T-16" + style="display:inline;enable-background:new"> - - - - - - - - + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 331,410 c -2.22355,0 -4.0767,1.3002 -5.12305,3.18945 0.93682,-0.21724 1.92473,-0.28964 2.92578,-0.17187 0.40221,0.0473 0.78928,0.12403 1.16797,0.21484 C 330.29795,413.09146 330.64197,413 331,413 h 0.5 c 2.02848,0.0287 2.02848,-3.02869 0,-3 z m -3.05859,3.95898 c -3.69856,-0.0756 -6.93164,2.52898 -6.93164,6.04102 v 1.5 c -0.0287,2.02848 3.02869,2.02848 3,0 V 420 c 0,-1.75382 1.80351,-3.30654 4.32617,-3.00977 0.37314,0.0439 0.71883,0.11732 1.03711,0.21485 1.3077,0.40071 2.14372,1.21003 2.47656,1.91211 0.22905,0.48316 0.34095,1.19341 -0.0996,1.63281 -0.4946,0.4933 -1.25,0.34848 -1.89258,-0.0527 -0.70436,-0.44 -1.43726,-1.36047 -1.75195,-2.71875 -0.21041,-0.0194 -0.41646,-0.0328 -0.60742,-0.0234 -1.06865,0.0527 -1.81638,0.53365 -2.19727,1.13086 0.52848,1.85114 1.6077,3.30531 2.9668,4.15429 1.61509,1.00892 3.82413,1.05951 5.33594,-0.2539 0.0754,-0.0514 0.14605,-0.10959 0.21093,-0.17383 0.0562,-0.057 0.10785,-0.11837 0.1543,-0.18359 1.19162,-1.34198 1.31147,-3.2788 0.5918,-4.79688 -0.75194,-1.58612 -2.28118,-2.87289 -4.3086,-3.49414 -0.49344,-0.15121 -1.01745,-0.26354 -1.5664,-0.32812 -0.2499,-0.0294 -0.49757,-0.0457 -0.74414,-0.0508 z" + id="path20562" + inkscape:connector-curvature="0" + sodipodi:nodetypes="scccsccscsccsccccccccccccccccc" /> + inkscape:export-ydpi="96" + inkscape:export-xdpi="96" + inkscape:export-filename="blender_icons.png" + id="g20538" + transform="rotate(180,317.5055,417)" + inkscape:label="T-15"> + sodipodi:nodetypes="ccccccccc" /> + transform="rotate(180,307.0055,417)" + id="g13597" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="T-14"> - - - + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 289.50391,410 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 1.5 h -0.99414 c -1.17529,0 -2.25213,0.41841 -2.88477,1.18164 -0.28029,0.32895 -0.0395,0.83456 0.39258,0.82422 0.14712,-0.004 0.28501,-0.0726 0.37695,-0.1875 C 286.27001,413.36537 287.08306,413 288.00977,413 h 0.99414 v 0.5 a 0.50005,0.50005 0 0 0 0.5,0.5 h 3 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -3 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z m 0.5,1 h 2 v 2 h -2 z m -4.58594,3.95898 c -1.10666,0.0478 -2.07229,0.36866 -2.82031,0.91211 -0.99737,0.72461 -1.58789,1.85424 -1.58789,3.12891 v 1 h -1.50586 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 3 a 0.50005,0.50005 0 0 0 0.5,0.5 h 3 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -3 a 0.50005,0.50005 0 0 0 -0.5,-0.5 h -0.49414 v -1 c 0,-0.97533 0.41802,-1.76979 1.17578,-2.32031 0.75777,-0.55053 1.87662,-0.84701 3.26562,-0.6836 1.98998,0.23412 3.41358,1.22814 4.10352,2.33203 0.68994,1.10391 0.68215,2.23778 -0.14844,3.06836 -0.83795,0.83796 -1.98844,0.89361 -3.08398,0.26368 -1.09554,-0.62994 -2.08242,-1.9799 -2.31641,-3.96875 -0.0504,-0.69579 -1.10528,-0.57121 -0.99219,0.11718 0.26601,2.26115 1.40414,3.91119 2.8086,4.71875 0.70223,0.40379 1.48456,0.58895 2.23632,0.52735 0.75176,-0.0616 1.47366,-0.37015 2.05469,-0.95117 1.16941,-1.16942 1.16162,-2.91055 0.28906,-4.30664 -0.87256,-1.3961 -2.57396,-2.52709 -4.83398,-2.79297 -0.39761,-0.0468 -0.78151,-0.0608 -1.15039,-0.0449 z M 280.00391,421 h 2 v 2 h -2 z" + transform="rotate(180,307.0055,417)" + id="path13595" + inkscape:connector-curvature="0" /> - + inkscape:export-ydpi="96" + inkscape:export-xdpi="96" + inkscape:export-filename="blender_icons.png" + transform="translate(-84,-63)" + id="g21409" + inkscape:label="T-13"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 269.13867,410.0332 c -0.49613,-0.0451 -1.03862,0.15972 -1.49219,0.61328 l -8.5,8.5 c -0.0639,0.0642 -0.10909,0.14453 -0.13086,0.23243 l -1,4 c -0.0904,0.36537 0.2401,0.69582 0.60547,0.60547 l 4,-1 c 0.0879,-0.0218 0.16823,-0.067 0.23243,-0.13086 l 8.5,-8.5 c 0.45356,-0.45357 0.65838,-0.99606 0.61328,-1.49219 -0.0451,-0.49613 -0.30436,-0.90592 -0.61328,-1.21485 l -1,-1 c -0.30893,-0.30892 -0.71872,-0.56817 -1.21485,-0.61328 z m -1.88867,2.42383 2.29297,2.29297 -7.29883,7.29883 -3.05859,0.76562 0.76562,-3.05859 z" + transform="translate(84,63)" + id="path21405" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccccsccccccccc" /> - + id="g84226" + style="display:inline;enable-background:new" + inkscape:label="T-12"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="115" + inkscape:export-ydpi="115" /> + inkscape:export-ydpi="96" + inkscape:label="T-11"> + + + id="path5256" + d="m 197.50977,409.99414 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 3 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 1.5039 c 0.0205,0.58311 0.0403,1.03618 -0.0488,1.24805 -0.22838,0.54301 -0.5609,0.82668 -1.00781,1.10937 -0.44691,0.28269 -1.00733,0.53131 -1.53711,0.97461 -0.52977,0.4433 -1.00108,1.10059 -1.22656,2.08789 -0.22548,0.9873 -0.2332,2.29903 0.0625,4.16406 0.0385,0.24311 0.24801,0.42203 0.49414,0.42188 h 8.75586 c 0.30739,1.4e-4 0.54213,-0.2745 0.49414,-0.57812 -0.36883,-2.32606 -0.21493,-3.6368 0.12305,-4.40821 0.33798,-0.7714 0.85304,-1.08606 1.49804,-1.45508 0.645,-0.36901 1.43565,-0.7809 1.90821,-1.70703 0.47255,-0.92613 0.59571,-2.25921 0.21289,-4.4375 C 208.70043,411.17484 208.49284,411.0002 208.25,411 h -7.24023 v -0.50586 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 z m 0.5,1 h 2 v 2 h -2 z m 3,1.00586 h 6.78125 c 0.25456,1.74951 0.14487,2.81594 -0.15235,3.39844 -0.3247,0.63637 -0.84774,0.91198 -1.51367,1.29297 -0.66593,0.38098 -1.46183,0.87882 -1.91797,1.91992 -0.41222,0.94085 -0.49675,2.35477 -0.23633,4.38867 h -7.73437 c -0.19264,-1.47462 -0.23076,-2.65205 -0.0684,-3.36328 0.18281,-0.80045 0.49635,-1.21142 0.89258,-1.54297 0.39622,-0.33155 0.90291,-0.56523 1.42968,-0.89844 0.52678,-0.33321 1.07351,-0.7985 1.39649,-1.5664 0.22021,-0.52359 0.17686,-1.05487 0.13867,-1.63477 h 0.48438 c 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> - + id="g84238" + style="display:inline;enable-background:new" + inkscape:label="T-9"> + id="path5262" + d="m 180.5,410 c -2.08712,0 -3.47816,1.04607 -4.39062,2.1875 a 0.50024408,0.50024408 0 0 0 0.78125,0.625 C 177.69049,411.81193 178.73439,411 180.5,411 c 2.09839,0 3.13411,0.87695 3.75195,1.94531 C 184.8698,414.01367 185,415.31651 185,416 h -5.25 c -1.27344,0 -2.44324,0.36418 -3.31445,1.05469 -0.87121,0.69051 -1.42774,1.72791 -1.42774,2.94531 0,1.2174 0.55653,2.2548 1.42774,2.94531 C 177.30676,423.63582 178.47656,424 179.75,424 h 0.75 c 1.75558,0 3.38771,-0.96684 4.53711,-2.0332 0.0781,0.53303 0.28951,1.00434 0.64062,1.35547 C 186.12862,423.77315 186.775,424 187.5,424 a 0.50005,0.50005 0 1 0 0,-1 c -0.525,0 -0.87862,-0.14815 -1.11523,-0.38477 C 186.14815,422.37862 186,422.025 186,421.5 V 416 c 0,-0.79721 -0.12242,-2.24322 -0.88086,-3.55469 C 184.3607,411.13385 182.89646,410 180.5,410 Z m -0.75,7 H 185 v 3.56055 C 184.04582,421.68473 182.18875,423 180.5,423 h -0.75 c -1.08101,0 -2.03187,-0.31555 -2.69336,-0.83984 -0.66149,-0.52429 -1.04883,-1.23676 -1.04883,-2.16016 0,-0.9234 0.38734,-1.63587 1.04883,-2.16016 C 177.71813,417.31555 178.66899,417 179.75,417 Z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> - - - - - - - - - + id="g20896" + transform="translate(-189,63)" + inkscape:label="T-8"> - - - - - - - - - - - - - - + transform="translate(498,-1438)" + inkscape:label="T-7"> - + id="path21106-9" + d="m -356.5,1850 c -0.8225,0 -1.5,0.6775 -1.5,1.5 0,0.8225 0.6775,1.5 1.5,1.5 0.8225,0 1.5,-0.6775 1.5,-1.5 0,-0.8225 -0.6775,-1.5 -1.5,-1.5 z m 0,1 c 0.28206,0 0.5,0.2179 0.5,0.5 0,0.2821 -0.21794,0.5 -0.5,0.5 -0.28206,0 -0.5,-0.2179 -0.5,-0.5 0,-0.2821 0.21794,-0.5 0.5,-0.5 z m -7,1 a 0.5,0.5 0 0 0 -0.5,0.5 0.5,0.5 0 0 0 0.5,0.5 0.5,0.5 0 0 0 0.5,-0.5 0.5,0.5 0 0 0 -0.5,-0.5 z" + style="display:inline;opacity:0.7;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.7;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:new" /> + d="m -357.5,1856.0041 h 0.49805 c 3.5943,0 5.00195,-2.5001 5.00195,-4.5001 v 0 c 0,-2.4794 -2.02064,-4.5 -4.5,-4.5 -1.88915,0 -3.45662,1.2594 -4.10547,3 h -0.51758 c -0.64602,-0.6132 -1.47755,-0.9979 -2.375,-1 h -0.002 c -1.92707,0 -3.5,1.5729 -3.5,3.5 0,1.9271 1.57293,3.5 3.5,3.5 z m -6,-1.0041 c -1.38663,0 -2.5,-1.1134 -2.5,-2.5 0,-1.386 1.11233,-2.4989 2.49805,-2.5 0.71036,0 1.38608,0.3048 1.85937,0.834 0.0951,0.1059 0.23074,0.1663 0.37305,0.166 h 0.90234 c 0.22809,10e-5 0.42734,-0.1542 0.48438,-0.375 0.39914,-1.5459 1.78609,-2.6224 3.38281,-2.625 1.93892,0 3.5,1.5611 3.5,3.5 0,1.9279 -1.54523,3.4765 -3.46875,3.4941 -0.0392,0 -0.0785,-6e-4 -0.11719,0.01 H -357.5 Z m -1.00781,1.9922 c -0.27614,0 -0.4965,0.2317 -0.49219,0.5078 v 1 1 c 0.009,0.6573 0.9907,0.6573 1,0 v -0.5 h 1 v 1.5 c 3e-5,0.1326 0.0527,0.2597 0.14648,0.3535 l 1,1 c 0.0937,0.094 0.22092,0.1465 0.35352,0.1465 h 5 c 0.27613,0 0.49997,-0.2239 0.5,-0.5 v -1.5039 h 0.33594 l 2.61914,1.9043 c 0.0852,0.064 0.18852,0.099 0.29492,0.1 h 0.25 c 0.27613,0 0.49997,-0.2239 0.5,-0.5 v -4 c -3e-5,-0.2761 -0.22387,-0.5 -0.5,-0.5 h -0.25 c -0.106,-10e-5 -0.20927,0.034 -0.29492,0.096 L -355.66211,1859 H -356 v -2 h -1 v 4 h -4.29297 L -362,1860.293 V 1857 h -1 v 1 h -1 v -0.5 c 0.004,-0.2823 -0.22555,-0.5122 -0.50781,-0.5078 z M -353,1858.3008 v 2.3984 l -1.65039,-1.1992 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.5;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + id="path21108-3" /> + + id="g24264-7" + transform="translate(-910,-1146)" + inkscape:label="T-6"> - - - - - - - - - - - - - - + id="g14534" + inkscape:label="T-5"> + id="g84223" + style="display:inline;enable-background:new" + inkscape:label="T-4"> + id="g84211" + style="display:inline;enable-background:new" + inkscape:label="T-3"> + inkscape:connector-curvature="0" + id="path22951-5" + d="m 52.5,410 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 3 a 0.50005,0.50005 0 0 0 0.5,0.5 h 3 A 0.50005,0.50005 0 0 0 56,413.5 V 412 h 0.5 c 1.293073,0 2.425866,0.35206 3.21875,1.00977 C 60.511634,413.66747 61,414.62406 61,416 c 0,1.58333 -0.78109,3.05511 -2.240234,4.16406 C 57.300621,421.27301 55.159091,422 52.5,422 H 52 v -1.5 A 0.50005,0.50005 0 0 0 51.5,420 h -3 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 3 a 0.50005,0.50005 0 0 0 0.5,0.5 h 3 A 0.50005,0.50005 0 0 0 52,423.5 V 423 h 0.5 c 2.840909,0 5.199379,-0.77301 6.865234,-2.03906 C 61.03109,419.69489 62,417.91667 62,416 62,414.37594 61.372824,413.08253 60.357422,412.24023 59.34202,411.39794 57.973784,411 56.5,411 H 56 v -0.5 A 0.50005,0.50005 0 0 0 55.5,410 Z m 0.5,1 h 2 v 2 h -2 z m -4,10 h 2 v 2 h -2 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> - - + id="g84208" + style="display:inline;enable-background:new" + inkscape:label="T-2"> + id="path17434" + d="m 27.5,410 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 3 a 0.50005,0.50005 0 0 0 0.5,0.5 h 0.949219 L 32,420.62695 V 423.5 a 0.50005,0.50005 0 0 0 0.5,0.5 h 3 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -2.87305 L 39.550781,414 H 40.5 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -3 A 0.50005,0.50005 0 0 0 40.5,410 h -3 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 0.5 h -6 v -0.5 A 0.50005,0.50005 0 0 0 30.5,410 Z m 0.5,1 h 2 v 2 h -2 z m 10,0 h 2 v 2 h -2 z m -7,1 h 6 v 1.5 a 0.50005,0.50005 0 0 0 0.5,0.5 h 0.914062 l -3.214843,6 h -2.398438 l -3.214843,-6 H 30.5 a 0.50005,0.50005 0 0 0 0.5,-0.5 z m 2,9 h 2 v 2 h -2 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="T-1"> - - - - - + id="g6275" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="S-25"> + inkscape:connector-curvature="0" + id="path14559-8" + d="m 519.02539,389.05664 c -1.23014,0.0645 -2.42356,0.6345 -3.37891,1.58984 l -4,4 c -0.95534,0.95535 -1.53001,2.15146 -1.59765,3.38477 -0.0676,1.23331 0.38786,2.49571 1.41406,3.50977 1.02392,1.0118 2.28158,1.46686 3.51172,1.40234 1.23014,-0.0645 2.42356,-0.6345 3.37891,-1.58984 l 4,-4 c 0.95534,-0.95535 1.53001,-2.15146 1.59765,-3.38477 0.0676,-1.23331 -0.38786,-2.4957 -1.41406,-3.50977 -1.02392,-1.0118 -2.28158,-1.46686 -3.51172,-1.40234 z m 0.0527,0.99805 c 0.95617,-0.0502 1.91199,0.28134 2.75586,1.11523 0.8416,0.83165 1.17171,1.78562 1.11914,2.74414 -0.0526,0.95853 -0.50462,1.93042 -1.30664,2.73242 l -4,4 c -0.80201,0.80202 -1.76844,1.24868 -2.7246,1.29883 -0.95617,0.0502 -1.91199,-0.28134 -2.75586,-1.11523 -0.8416,-0.83164 -1.17171,-1.78562 -1.11914,-2.74414 0.0526,-0.95853 0.50462,-1.93041 1.30664,-2.73242 l 4,-4 c 0.80201,-0.80202 1.76844,-1.24868 2.7246,-1.29883 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + + id="g6267" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="S-24"> + id="path14468-2" + d="m 499.91406,389.05664 c -0.77439,-0.15321 -1.63518,-0.1241 -2.51367,0.0664 -1.75698,0.38102 -3.63558,1.4051 -5.25391,3.02343 -1.61833,1.61833 -2.64241,3.49693 -3.02343,5.25391 -0.38103,1.75698 -0.11706,3.43764 0.96093,4.51563 1.07799,1.07798 2.75865,1.34196 4.51563,0.96093 1.75698,-0.38102 3.63558,-1.40511 5.25391,-3.02343 1.61832,-1.61833 2.64241,-3.49693 3.02343,-5.25391 0.38102,-1.75698 0.11705,-3.43764 -0.96093,-4.51563 -0.539,-0.53899 -1.22757,-0.87413 -2.00196,-1.02734 z m -0.21289,0.97656 c 0.61146,0.11888 1.12564,0.37564 1.50781,0.75782 0.76435,0.76434 1.0245,2.05973 0.69141,3.5957 -0.33309,1.53597 -1.26116,3.26702 -2.75391,4.75976 -1.49274,1.49275 -3.22379,2.42081 -4.75976,2.75391 -1.53597,0.3331 -2.83136,0.0729 -3.5957,-0.69141 -0.76435,-0.76434 -1.02451,-2.05973 -0.69141,-3.5957 0.33309,-1.53597 1.26117,-3.26702 2.75391,-4.75976 1.49274,-1.49275 3.22379,-2.42082 4.75976,-2.75391 0.76799,-0.16655 1.47643,-0.18529 2.08789,-0.0664 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + inkscape:connector-curvature="0" + id="path14550" + d="m 493.47266,393.99414 a 0.50005,0.50005 0 0 0 -0.45899,0.62305 c 0.22122,0.92011 0.78151,1.92168 1.64063,2.74414 0.81151,0.77689 1.74924,1.41197 2.74023,1.62695 a 0.50005,0.50005 0 1 0 0.21094,-0.97656 c -0.70573,-0.1531 -1.535,-0.67922 -2.25977,-1.37305 -0.71878,-0.6881 -1.19119,-1.55637 -1.35937,-2.25586 a 0.50005,0.50005 0 0 0 -0.51367,-0.38867 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + id="g6259" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="S-23"> + inkscape:connector-curvature="0" + id="path14478-2" + d="m 475,388.9375 c -3.89459,0 -7.0625,3.1679 -7.0625,7.0625 0,3.89459 3.16791,7.0625 7.0625,7.0625 3.89459,0 7.0625,-3.16791 7.0625,-7.0625 0,-3.8946 -3.16791,-7.0625 -7.0625,-7.0625 z m 0,1 c 3.35415,0 6.0625,2.70834 6.0625,6.0625 0,3.35415 -2.70835,6.0625 -6.0625,6.0625 -3.35415,0 -6.0625,-2.70835 -6.0625,-6.0625 0,-3.35416 2.70835,-6.0625 6.0625,-6.0625 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + - - + id="g6251" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="S-22"> + + + + + + id="g16413-1" + inkscape:label="S-20"> + + + + + + inkscape:label="S-18"> + inkscape:export-ydpi="96" + inkscape:label="S-17"> + + + + + + - - - - - - + inkscape:label="S-13"> + style="display:inline;fill:#ffffff;enable-background:new" + transform="translate(-84,42)" + id="g19210"> - - - + d="m 348.48047,347 c -3.16361,0.009 -5.86504,2.31609 -6.39063,5.45117 -0.52558,3.13508 1.27477,6.20467 4.25977,7.25781 a 0.50005,0.50005 0 1 0 0.33203,-0.94336 c -2.52985,-0.89255 -4.05213,-3.48414 -3.60547,-6.14843 0.44666,-2.66429 2.72792,-4.6094 5.40821,-4.61719 2.68028,-0.008 4.97166,1.92234 5.43359,4.58398 0.46193,2.66165 -1.0456,5.26268 -3.57031,6.16993 a 0.50043111,0.50043111 0 1 0 0.33984,0.9414 c 2.9789,-1.07046 4.75841,-4.14926 4.21484,-7.28125 -0.54356,-3.13198 -3.25826,-5.42325 -6.42187,-5.41406 z m 0.0195,4.99999 c -0.8225,0 -1.5,0.6775 -1.5,1.5 0,0.64684 0.42097,1.19777 1,1.40625 v 0.59375 c -0.01,0.67616 1.00956,0.67616 1,0 v -0.59375 c 0.57902,-0.20848 1,-0.75941 1,-1.40625 0,-0.8225 -0.67751,-1.5 -1.5,-1.5 z m 0,1 c 0.28206,0 0.5,0.21794 0.5,0.5 0,0.28206 -0.21794,0.5 -0.5,0.5 -0.28207,0 -0.5,-0.21794 -0.5,-0.5 0,-0.28206 0.21793,-0.5 0.5,-0.5 z m -0.002,3.98438 c -0.27614,0.004 -0.49651,0.23167 -0.49219,0.50781 v 1 c -0.01,0.67616 1.00957,0.67616 1,0 v -1 c 0.004,-0.28226 -0.22555,-0.51223 -0.50781,-0.50781 z m 0,3 c -0.27614,0.004 -0.49651,0.23167 -0.49219,0.50781 v 1.00195 c -0.01,0.67616 1.00957,0.67616 1,0 v -1.00195 c 0.004,-0.28226 -0.22555,-0.51223 -0.50781,-0.50781 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + id="circle19208" /> - - - - + id="g23051-0" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + transform="translate(210,21)" + inkscape:label="S-12"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 243.49219,389 c -0.12989,0.002 -0.25387,0.0546 -0.34571,0.14648 l -3,3 c -0.49023,0.47127 0.23577,1.19727 0.70704,0.70704 L 243,390.70703 V 402 h -1.5 c -0.67616,-0.01 -0.67616,1.00956 0,1 h 4 c 0.67616,0.01 0.67616,-1.00956 0,-1 H 244 v -11.29297 l 2.14648,2.14649 c 0.47127,0.49023 1.19727,-0.23577 0.70704,-0.70704 l -3,-3 c -0.0957,-0.0957 -0.22603,-0.14855 -0.36133,-0.14648 z" + transform="translate(-210,-21)" + id="path23043-3" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccccccccccc" /> - + id="g91498" + style="display:inline;enable-background:new" + inkscape:label="S-11"> - + id="g91510" + style="display:inline;enable-background:new" + inkscape:label="S-10"> - - - - - + id="g6344" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="S-9"> - - - - - - - - + inkscape:connector-curvature="0" + id="path17355-2" + d="m 181,390 c -2.19521,0 -4.16987,0.64081 -5.61914,1.70898 -1.44928,1.06818 -2.38087,2.59156 -2.38086,4.29102 0,1.69945 0.93159,3.22284 2.38086,4.29102 C 176.83013,401.35919 178.80479,402 181,402 c 2.19521,0 4.16987,-0.64081 5.61914,-1.70898 C 188.06841,399.22284 189,397.69945 189,396 c 0,-1.69945 -0.93159,-3.22284 -2.38086,-4.29102 C 185.16987,390.64081 183.19522,390 181,390 Z m 0,1 c 1.99941,0 3.77332,0.59085 5.02539,1.51367 C 187.27746,393.4365 188,394.66345 188,396 c 0,1.33655 -0.72254,2.5635 -1.97461,3.48633 C 184.77332,400.40915 182.99941,401 181,401 c -1.99941,0 -3.77332,-0.59085 -5.02539,-1.51367 C 174.72254,398.5635 174,397.33655 174,396 c 0,-1.33654 0.72254,-2.5635 1.97461,-3.48633 C 177.22668,391.59085 179.00059,391 181,391 Z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + id="path17357" + d="M 178.49219,394.49219 A 0.50005,0.50005 0 0 0 178,395.00195 v 0.25 c 5.5e-4,0.26762 0.10104,0.43267 0.23438,0.61719 0.13369,0.18504 0.32089,0.36854 0.5664,0.53516 0.49103,0.33322 1.21831,0.59296 2.19727,0.5957 0.98079,0.003 1.71165,-0.25661 2.20312,-0.5918 0.24574,-0.16759 0.43113,-0.35326 0.56445,-0.53906 0.13296,-0.18529 0.23383,-0.34989 0.23438,-0.61719 v -0.002 -0.24805 a 0.50005,0.50005 0 1 0 -1,-0.004 v 0.2521 c 0,-0.11242 0.009,-0.0414 -0.0469,0.0371 -0.0564,0.0786 -0.16044,0.18856 -0.3164,0.29492 -0.31192,0.21273 -0.83026,0.42022 -1.63477,0.41797 -0.80517,-0.002 -1.32795,-0.21164 -1.64062,-0.42383 -0.15634,-0.10609 -0.26023,-0.21522 -0.31641,-0.29297 C 178.98874,395.20545 179,395.13726 179,395.25 v -0.25195 a 0.50005,0.50005 0 0 0 -0.50781,-0.50586 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + inkscape:label="S-8"> + inkscape:connector-curvature="0" + id="path17349-5" + d="m 156.49609,393.24609 a 0.50005,0.50005 0 0 0 -0.27343,0.91993 c 0.927,0.618 2.37713,0.83398 3.77734,0.83398 1.39708,0 2.84978,-0.21561 3.77734,-0.83398 a 0.50005,0.50005 0 1 0 -0.55468,-0.83204 C 162.65022,393.71561 161.2722,394 160,394 c -1.27563,0 -2.64966,-0.28402 -3.22266,-0.66602 a 0.50005,0.50005 0 0 0 -0.28125,-0.0879 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + - - - - - + id="g6328" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="S-7"> + + - - - - + inkscape:label="S-6"> + + + inkscape:label="S-5"> - - - + id="path17390-7" + mask="none" + d="m 96,389 a 0.50005,0.50005 0 0 0 -0.240234,0.0605 l -5.5,3 A 0.50005,0.50005 0 0 0 90,392.5 v 7 a 0.50005,0.50005 0 0 0 0.259766,0.43945 l 5.5,3 A 0.50005,0.50005 0 0 0 96,403 h 1 a 0.50005,0.50005 0 0 0 0.240234,-0.0605 l 5.499996,-3 A 0.50005,0.50005 0 0 0 103,399.5 v -7 a 0.50005,0.50005 0 0 0 -0.25977,-0.43945 l -5.499996,-3 A 0.50005,0.50005 0 0 0 97,389 Z m 0.126953,1 h 0.746094 L 102,392.79688 v 6.40624 L 96.873047,402 H 96.126953 L 91,399.20312 v -6.40624 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + + inkscape:label="S-4"> + id="path17418-8" + d="m 76,388.9375 c -3.894591,0 -7.0625,3.1679 -7.0625,7.0625 0,3.89459 3.167908,7.0625 7.0625,7.0625 3.894592,0 7.0625,-3.16791 7.0625,-7.0625 0,-3.8946 -3.167909,-7.0625 -7.0625,-7.0625 z m 0,1 c 3.354153,0 6.0625,2.70834 6.0625,6.0625 0,3.35415 -2.708348,6.0625 -6.0625,6.0625 -3.354152,0 -6.0625,-2.70835 -6.0625,-6.0625 0,-3.35416 2.708347,-6.0625 6.0625,-6.0625 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + id="path17424" + d="m 75.476562,390.74414 a 0.50005,0.50005 0 0 0 -0.40039,0.24024 C 74.184416,392.40936 74,393.97991 74,396 c 0,0.33476 0.05553,0.60654 0.06836,0.92188 -1.166939,-0.0798 -2.112698,-0.42664 -3.201171,-1.52344 a 0.50005,0.50005 0 1 0 -0.710938,0.70312 c 1.257685,1.26731 2.545083,1.75219 3.986328,1.83008 0.125352,1.11893 0.352857,2.1649 0.933594,3.08594 a 0.50122772,0.50122772 0 1 0 0.847656,-0.53516 C 75.471077,399.76437 75.263567,398.92779 75.142578,398 H 76 c 2.575016,3.5e-4 3.847653,-0.61523 4.722656,-1.05273 a 0.50005,0.50005 0 1 0 -0.445312,-0.89454 C 79.402347,396.49023 78.424978,397.00033 76,397 H 75.072266 C 75.055487,396.65897 75,396.38014 75,396 c 0,-1.96025 0.172858,-3.28436 0.923828,-4.48438 a 0.50005,0.50005 0 0 0 -0.447266,-0.77148 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> - + id="g91507" + style="display:inline;enable-background:new" + inkscape:label="S-3"> + inkscape:label="S-2"> + id="path8006-3-4" + d="m 30.5,389 a 0.50005,0.50005 0 0 0 -0.353516,0.14648 l -3,3 A 0.50005,0.50005 0 0 0 27,392.5 v 10 a 0.50005,0.50005 0 0 0 0.5,0.5 h 10 a 0.50005,0.50005 0 0 0 0.353516,-0.14648 l 3,-3 A 0.50005,0.50005 0 0 0 41,399.5 v -10 A 0.50005,0.50005 0 0 0 40.5,389 Z m 0.207031,1 H 40 v 9.29297 L 37.292969,402 H 28 v -9.29297 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + id="path8009-8" + d="m 39.490234,389.98828 a 0.50005,0.50005 0 0 0 -0.34375,0.15234 L 37.292969,392 H 29.5 a 0.50005,0.50005 0 1 0 0,1 H 37 v 7.5 a 0.50005,0.50005 0 1 0 1,0 v -7.79297 l 1.853516,-1.86133 a 0.50005,0.50005 0 0 0 -0.363282,-0.85742 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> - - + id="g91504" + style="display:inline;enable-background:new" + inkscape:label="S-1"> + id="g98816" + style="display:inline;enable-background:new" + inkscape:label="R-26" + transform="translate(-0.35799351,-0.56441723)"> - - - - - - - - - + id="path9260-6" + style="display:inline;enable-background:accumulate;color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto" + d="m 542,379.49414 c 0,0.27842 -0.2216,0.50585 -0.5,0.50586 h -7 c -0.4051,6.1e-4 -0.6427,-0.45544 -0.4102,-0.78711 l 3.5,-5 c 0.199,-0.28542 0.6214,-0.28542 0.8204,0 l 3.5,5 c 0.058,0.0826 0.089,0.1806 0.09,0.28125 z M 542,373 h -8 v -2 h 8 z" + sodipodi:nodetypes="cccccccccccccc" + transform="translate(0.35799351,0.56441723)" /> + id="g98819" + style="display:inline;enable-background:new" + inkscape:label="R-25"> + id="g98813" + style="display:inline;enable-background:new" + inkscape:label="R-24"> - + id="g98810" + style="display:inline;enable-background:new" + inkscape:label="R-23"> + id="path27544-8" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 471.50586,371 c -0.27842,0 -0.50585,0.2216 -0.50586,0.5 v 7 c -6.1e-4,0.4051 0.45544,0.6427 0.78711,0.4102 l 5,-3.5 c 0.28542,-0.199 0.28542,-0.6214 0,-0.8204 l -5,-3.5 c -0.0826,-0.058 -0.1806,-0.089 -0.28125,-0.09 z M 478,371 v 8 h 2 v -8 z" + sodipodi:nodetypes="cccccccccccccc" /> + id="g98798" + style="display:inline;enable-background:new" + inkscape:label="R-22"> - + sodipodi:nodetypes="ccccscsccscsccscscscccccscscscc" /> + + + id="g98801" + style="display:inline;enable-background:new" + inkscape:label="R-20"> + + + id="g19186-9-4" + transform="translate(-146.994,85.0365)" + inkscape:label="R-16"> - + id="g6286" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="R-15"> + inkscape:connector-curvature="0" + id="path8009-8-5-8-9-1-5" + d="M 312.14648,369.14648 309.29297,372 H 300.5 v 1 h 8.5 l 0.006,8.53711 h 1 l -0.006,-8.83008 2.85352,-2.85351 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.7;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> - + id="g98792" + style="display:inline;enable-background:new" + inkscape:label="R-14"> + + + - - - + id="g14406" + transform="translate(-1.85367e-6,42)" + inkscape:label="R-13"> + id="g14414" + transform="translate(-1.85367e-6,42)" + inkscape:label="R-12"> + style="opacity:0.6;fill:#ffffff" + id="g13459"> + id="g14422" + transform="translate(-1.85367e-6,42)" + inkscape:label="R-11"> + + + + + + + + + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="R-9"> - + id="g98807" + style="display:inline;enable-background:new" + inkscape:label="R-8"> + style="display:inline;opacity:0.99;fill:#ffffff;enable-background:new" + transform="translate(-357,168)" + id="g23559" + inkscape:label="R-7"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="M 495.49219,199.99219 A 0.50005,0.50005 0 0 0 495,200.5 v 6.79297 l -3.85352,3.85351 a 0.50005,0.50005 0 1 0 0.70704,0.70704 L 495.70703,208 H 502.5 a 0.50005,0.50005 0 1 0 0,-1 H 496 v -6.5 a 0.50005,0.50005 0 0 0 -0.50781,-0.50781 z" + id="path23553" + inkscape:connector-curvature="0" /> + style="display:inline;fill:#ffffff;enable-background:new" + id="g14344" + transform="translate(-168,63)" + inkscape:label="R-6"> - - - + d="m 286,307 c -1.846,0 -3.56208,0.52582 -4.8457,1.40625 C 279.87068,309.28668 279,310.55671 279,312 c 0,1.44329 0.87068,2.71332 2.1543,3.59375 C 282.43792,316.47418 284.154,317 286,317 c 1.846,0 3.56208,-0.52582 4.8457,-1.40625 C 292.12932,314.71332 293,313.44329 293,312 c 0,-1.44329 -0.87068,-2.71332 -2.1543,-3.59375 C 289.56208,307.52582 287.846,307 286,307 Z m 2.49219,3.61719 A 0.50005,0.50005 0 0 1 289,311.125 c 0,0.62131 -0.43923,1.10886 -0.98438,1.41016 C 287.47049,312.83646 286.77233,313 286,313 c -0.77233,0 -1.47049,-0.16354 -2.01562,-0.46484 C 283.43923,312.23386 283,311.74631 283,311.125 a 0.50005,0.50005 0 0 1 0.49414,-0.50586 A 0.50005,0.50005 0 0 1 284,311.125 c 0,0.13737 0.11369,0.33891 0.46875,0.53516 C 284.82381,311.85641 285.37451,312 286,312 c 0.62549,0 1.17619,-0.14359 1.53125,-0.33984 C 287.88631,311.46391 288,311.26237 288,311.125 a 0.50005,0.50005 0 0 1 0.49219,-0.50781 z" + id="path13633" + inkscape:connector-curvature="0" /> + + style="display:inline;fill:#ffffff;enable-background:new" + id="g14350" + transform="translate(-210,63)" + inkscape:label="R-5"> + + - + id="g14390" + transform="translate(-294,63)" + inkscape:label="R-3"> + + + + + + sodipodi:nodetypes="sssss" /> + + + sodipodi:nodetypes="ccscccccccc" /> + + + - - + transform="translate(-222,478)" + inkscape:label="Q-26"> @@ -13363,66 +12321,33 @@ inkscape:connector-curvature="0" /> + style="display:inline;opacity:0.99;fill:#ffffff;enable-background:new"> - - - - - - - - - + transform="translate(-285,478)" + inkscape:label="Q-25"> + transform="matrix(-1,0,0,1,1460,228)"> + id="g16449" + transform="translate(-285,478)" + inkscape:label="Q-24"> - + style="display:inline;opacity:1;fill:#ffffff;enable-background:new" + id="g15951-6-3" + transform="translate(747,-478)" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96"> + transform="matrix(-1,0,0,1,1439,230)" + id="g8805-98" + style="display:inline;opacity:0.99;fill:#ffffff;enable-background:new"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 656,-357 c -0.54636,0 -1,0.45364 -1,1 0,0.54636 0.45364,1 1,1 0.54636,0 1,-0.45364 1,-1 0,-0.54636 -0.45364,-1 -1,-1 z m 3.50391,1 c -0.18479,-10e-4 -0.35529,0.0993 -0.44336,0.26172 l -3,5.5 c -0.1805,0.33311 0.0606,0.73812 0.43945,0.73828 h 5.75 c 0.37101,-3.7e-4 0.61244,-0.39044 0.44727,-0.72266 l -2.75,-5.5 c -0.0838,-0.16852 -0.25516,-0.2757 -0.44336,-0.27734 z" + id="circle8761-3" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ssssscccccccc" /> + id="g5375" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="Q-17"> + id="path9106-6-2" + d="m 346.49261,354.99229 a 0.50005,0.50005 0 0 0 -0.49219,0.50781 v 3 c 0,0.725 0.22685,1.37138 0.67773,1.82226 0.45089,0.45089 1.09727,0.67774 1.82227,0.67774 0.725,0 1.37138,-0.22685 1.82226,-0.67774 0.45089,-0.45088 0.67774,-1.09726 0.67774,-1.82226 v -3 a 0.50005,0.50005 0 1 0 -1,0 v 3 c 0,0.525 -0.14815,0.87862 -0.38477,1.11523 -0.23661,0.23661 -0.59023,0.38477 -1.11523,0.38477 -0.525,0 -0.87862,-0.14816 -1.11524,-0.38477 -0.23661,-0.23661 -0.38476,-0.59023 -0.38476,-1.11523 v -3 a 0.50005,0.50005 0 0 0 -0.50781,-0.50781 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.75;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers stroke fill;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + id="g5381" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="Q-16"> + + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.03999px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="M 321.49219,347.97266 A 0.52004817,0.52004817 0 0 0 320.97852,348.5 v 11 a 0.520505,0.520505 0 0 0 1.04101,0 V 358 h 11.95899 v 1.5 a 0.520505,0.520505 0 0 0 1.04101,0 v -11 a 0.52004817,0.52004817 0 0 0 -0.52734,-0.52734 0.52004817,0.52004817 0 0 0 -0.51367,0.52734 v 1.5 h -11.95899 v -1.5 a 0.52004817,0.52004817 0 0 0 -0.52734,-0.52734 z M 322.01953,351 h 11.95899 v 6 h -11.95899 z" + id="path4902" /> - - - - + id="g24475" + transform="matrix(-1,0,0,1,551,4.49997e-6)" + inkscape:label="Q-13"> + + + + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 290.99609,357 c -1.09865,0 -2,0.90134 -2,2 0,1.09866 0.90135,2 2,2 1.09866,0 2,-0.90134 2,-2 0,-1.09866 -0.90134,-2 -2,-2 z" + id="circle24448" + inkscape:connector-curvature="0" + sodipodi:nodetypes="sssss" /> + id="g22594" + transform="translate(-1.85367e-6,21)" + inkscape:label="Q-12"> + sodipodi:nodetypes="sssss" /> + + + + + + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.7;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 223.49219,347 a 0.50005,0.50005 0 0 0 -0.34571,0.14648 l -2,2 A 0.50005,0.50005 0 0 0 221,349.5 v 0.79297 L 220.29297,351 H 218.5 a 0.50005,0.50005 0 0 0 -0.35352,0.14648 l -1,1 a 0.50005,0.50005 0 0 0 0,0.70704 L 218,353.70703 v 2.58594 l -1.85352,1.85351 A 0.50005,0.50005 0 0 0 216,358.5 v 2 a 0.50005,0.50005 0 1 0 1,0 v -1.79297 l 1.85352,-1.85351 A 0.50005,0.50005 0 0 0 219,356.5 v -3 a 0.50005,0.50005 0 0 0 -0.14648,-0.35352 l -0.64649,-0.64648 0.5,-0.5 H 220.5 a 0.50005,0.50005 0 0 0 0.35352,-0.14648 l 1,-1 A 0.50005,0.50005 0 0 0 222,350.5 v -0.79297 l 1.5,-1.5 0.64648,0.64649 A 0.50005,0.50005 0 0 0 224.5,349 h 3 a 0.50005,0.50005 0 0 0 0.5,-0.5 V 348 h 1.5 a 0.50005,0.50005 0 1 0 0,-1 h -2 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 0.5 h -2.29297 l -0.85351,-0.85352 A 0.50005,0.50005 0 0 0 223.49219,347 Z" + id="path42222-9" + inkscape:connector-curvature="0" /> + + transform="matrix(-1,0,0,1,382.999,-16)" + style="display:inline;fill:#ffffff;enable-background:new" + id="g24066" + inkscape:label="Q-10"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 198.03906,347.00195 c -0.16252,-0.007 -0.32108,0.006 -0.47461,0.0371 -1.22821,0.25238 -1.95915,1.36682 -2.45507,2.09766 a 0.60006002,0.60006002 0 1 0 0.99218,0.67383 c 0.46736,-0.68874 1.16385,-1.48449 1.70508,-1.59571 0.27061,-0.0556 0.53217,-0.0252 0.94727,0.3125 0.41509,0.33772 0.93861,1.01123 1.49804,2.15625 a 0.60006002,0.60006002 0 0 0 0.53907,0.33594 h 0.41406 a 0.60006002,0.60006002 0 0 0 0.54101,-0.33789 c 0.54756,-1.13491 1.06338,-1.80121 1.47657,-2.13476 0.41318,-0.3336 0.68072,-0.3648 0.95898,-0.3086 0.55653,0.1125 1.2624,0.90264 1.7168,1.57227 a 0.60006002,0.60006002 0 1 0 0.99218,-0.67383 c -0.49155,-0.72441 -1.2401,-1.82545 -2.4707,-2.07422 -0.6153,-0.12438 -1.32524,0.0455 -1.95117,0.55078 -0.51038,0.41204 -0.98784,1.19591 -1.4707,2.07617 -0.49098,-0.88906 -0.97541,-1.67806 -1.48633,-2.09375 -0.46868,-0.38133 -0.98509,-0.5741 -1.47266,-0.59375 z M 202.23047,353 c -0.77376,-7.6e-4 -1.47981,0.46475 -2.07031,1.12109 a 0.60022344,0.60022344 0 1 0 0.89257,0.80274 c 0.54185,-0.60226 0.93231,-0.77859 1.30469,-0.71289 0.37239,0.0657 0.94999,0.47591 1.63867,1.53906 a 0.60006002,0.60006002 0 0 0 1.00782,0 c 0.68868,-1.06315 1.26628,-1.47336 1.63867,-1.53906 0.37238,-0.0657 0.76284,0.11063 1.30469,0.71289 a 0.60022344,0.60022344 0 1 0 0.89257,-0.80274 c -0.67486,-0.75011 -1.50108,-1.25149 -2.40625,-1.09179 -0.70977,0.12522 -1.32665,0.68468 -1.93359,1.45508 -0.60694,-0.7704 -1.22382,-1.32986 -1.93359,-1.45508 -0.11315,-0.02 -0.2254,-0.0292 -0.33594,-0.0293 z" + transform="matrix(-1,0,0,1,382.999,16)" + id="path23960" + inkscape:connector-curvature="0" /> + sodipodi:nodetypes="sssss" /> - - - + id="g24237" + transform="translate(-1.85367e-6,21)" + inkscape:label="Q-9"> + + id="g24286" + transform="matrix(-1,0,0,1,319.997,63)" + inkscape:label="Q-8"> + sodipodi:nodetypes="cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + style="display:inline;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;enable-background:new" + d="m 153,284 v 2 h 2 v -2 z m 2,2 v 2 h 2 v -2 z m 2,0 h 2 v -2 h -2 z m 2,0 v 2 h 2 v -2 z m 2,0 h 2 v -2 h -2 z m -6,8 v -2 h -2 v 2 z m 0,-2 h 2 v -2 h -2 z m 0,-2 v -2 h -2 v 2 z m 2,0 h 2 v -2 h -2 z m 2,0 v 2 h 2 v -2 z m 2,0 h 2 v -2 h -2 z m -2,2 h -2 v 2 h 2 z" + id="path24240" + inkscape:connector-curvature="0" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" /> + sodipodi:nodetypes="sssss" /> + id="g24513" + transform="matrix(0,1,1,0,-152.003,47.0064)" + inkscape:label="Q-7"> + id="g24612" + style="fill:#ffffff"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 142.49609,347.00195 c -1.17351,0 -2.15665,0.82286 -2.42187,1.91797 1.14862,0.70733 2.04128,1.7894 2.50586,3.07422 1.33547,-0.0457 2.41601,-1.14613 2.41601,-2.49219 0,-1.37479 -1.12521,-2.5 -2.5,-2.5 z m 0.50196,8 c -1.65181,0 -3.00196,1.35015 -3.00196,3.00196 0,1.65181 1.35015,3.00195 3.00196,3.00195 1.65181,0 3.00195,-1.35014 3.00195,-3.00195 0,-1.65181 -1.35014,-3.00196 -3.00195,-3.00196 z" + transform="matrix(0,1,1,0,-47.0064,152.003)" + id="circle24338" + inkscape:connector-curvature="0" /> + - - + id="g106133" + style="display:inline;enable-background:new" + inkscape:label="Q-6"> - - + style="display:inline;opacity:0.6;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.8;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;enable-background:new" + d="m 121.47461,347 a 3.4883757,3.4883673 0 0 0 -3.48828,3.48828 3.4883757,3.4883673 0 0 0 3.48828,3.48828 3.4883757,3.4883673 0 0 0 3.48828,-3.48828 A 3.4883757,3.4883673 0 0 0 121.47461,347 Z m -2.48047,3 H 124 v 1.01367 h -5.00586 z" + id="circle23728" + inkscape:connector-curvature="0" /> - - - + id="g106130" + style="display:inline;enable-background:new"> + style="display:inline;overflow:visible;visibility:visible;opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.8;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + d="m 114.49609,354 a 3.4824922,3.4824822 0 0 0 -3.48242,3.48242 3.4824922,3.4824822 0 0 0 3.48242,3.48242 3.4824922,3.4824822 0 0 0 3.48243,-3.48242 A 3.4824922,3.4824822 0 0 0 114.49609,354 Z m -0.50586,0.98633 h 1.01368 v 1.98633 H 117 v 1.01367 h -1.99609 v 1.98633 h -1.01368 v -1.98633 h -1.99609 v -1.01367 h 1.99609 z" + id="circle23722" + inkscape:connector-curvature="0" /> + style="display:inline;fill:#ffffff;enable-background:new" + id="g22663" + transform="translate(-126,21)" + inkscape:label="Q-5"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 219.77539,326 c -0.5582,0 -1.07768,0.24943 -1.49219,0.64258 -0.41451,0.39314 -0.75014,0.92623 -1.04101,1.57422 -0.58174,1.29596 -0.98621,3.07228 -1.23828,5.21484 a 0.55005495,0.55005495 0 1 0 1.09179,0.12891 c 0.24409,-2.07475 0.64639,-3.77175 1.15039,-4.89453 0.252,-0.5614 0.52906,-0.97431 0.79297,-1.22461 0.26391,-0.25031 0.48846,-0.3418 0.73633,-0.3418 0.24788,0 0.46416,0.0907 0.7207,0.33398 0.25655,0.24329 0.52547,0.64294 0.77149,1.17579 0.49204,1.06569 0.89452,2.64518 1.18945,4.47851 0.3037,1.88782 0.70794,3.53465 1.27539,4.76367 0.28373,0.61451 0.60805,1.12901 1.01367,1.51367 0.40563,0.38466 0.92032,0.63477 1.47852,0.63477 1.08819,0 1.93098,-0.8296 2.51953,-1.97266 0.58855,-1.14305 0.99634,-2.67751 1.25,-4.45312 a 0.55005495,0.55005495 0 1 0 -1.08789,-0.15625 c -0.2425,1.69747 -0.64149,3.13986 -1.13867,4.10547 -0.49719,0.9656 -1.01901,1.37695 -1.54297,1.37695 -0.24788,0 -0.46416,-0.0907 -0.7207,-0.33398 -0.25655,-0.24329 -0.52547,-0.64294 -0.77149,-1.17579 -0.49204,-1.06569 -0.89451,-2.64518 -1.18945,-4.47851 -0.30369,-1.88783 -0.70794,-3.53465 -1.27539,-4.76367 -0.28373,-0.61451 -0.60805,-1.12901 -1.01367,-1.51367 C 220.84828,326.25011 220.33359,326 219.77539,326 Z" + id="use22627" + inkscape:connector-curvature="0" /> - - + id="g56772" + inkscape:label="Q-4"> + + + + + + + id="g106121" + style="display:inline;enable-background:new" + inkscape:label="Q-3"> - - - - + style="display:inline;fill:#ffffff;enable-background:new" + id="g24220" + transform="translate(-1.85367e-6,63)" + inkscape:label="Q-2"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 32.855469,347.00586 c -0.706567,0.052 -1.35787,0.479 -1.669922,1.15234 a 0.50005,0.50005 0 1 0 0.90625,0.42188 c 0.210359,-0.45391 0.713864,-0.68179 1.193359,-0.53906 0.479496,0.14272 0.777196,0.60845 0.705078,1.10351 C 33.918117,349.63959 33.500288,350 33,350 h -5.5 c -0.676161,-0.01 -0.676161,1.00956 0,1 H 33 c 0.98952,0 1.835874,-0.73175 1.978516,-1.71094 0.142642,-0.97918 -0.459806,-1.92277 -1.408204,-2.20508 -0.237099,-0.0706 -0.479321,-0.0955 -0.714843,-0.0781 z M 38.488281,349 c -0.867889,0.005 -1.700579,0.46414 -2.154297,1.25 a 0.5005035,0.5005035 0 1 0 0.867188,0.5 c 0.36563,-0.63329 1.125339,-0.91026 1.8125,-0.66016 0.68716,0.25011 1.089874,0.94977 0.96289,1.66993 C 39.849581,352.47992 39.231261,353 38.5,353 h -11 c -0.676161,-0.01 -0.676161,1.00956 0,1 h 11 c 1.209914,0 2.252791,-0.87487 2.462891,-2.06641 0.210099,-1.19153 -0.470475,-2.36938 -1.607422,-2.7832 C 39.071232,349.04694 38.777578,348.99817 38.488281,349 Z M 27.5,356 c -0.676161,-0.01 -0.676161,1.00956 0,1 H 35.464844 35.5 c 0.74205,0 1.3672,0.53453 1.482422,1.26758 0.115221,0.73305 -0.315216,1.43251 -1.021484,1.66015 -0.706269,0.22765 -1.466229,-0.0896 -1.800782,-0.75195 a 0.50006246,0.50006246 0 1 0 -0.892578,0.45117 c 0.553327,1.09549 1.831882,1.62847 3,1.25196 1.168118,-0.37652 1.891741,-1.55517 1.701172,-2.76758 C 37.778181,356.89891 36.727299,356 35.5,356 Z" + transform="translate(1.85367e-6,-63)" + id="path24132" + inkscape:connector-curvature="0" /> + + - - + transform="rotate(90,171,281)" + id="g24322" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="Q-1"> + style="fill:#ffffff;stroke:#ffffff" + transform="rotate(-90,244.008,459)" + id="g24320"> + + - + id="g113469" + style="display:inline;enable-background:new" + inkscape:label="P-26"> + sodipodi:nodetypes="cccccccccccccccccccccccc" /> - - - - + id="g25828" + transform="translate(-1)" + inkscape:label="P-25"> + sodipodi:nodetypes="ccccccccc" /> + + + - - - - + style="display:inline;fill:#ffffff;enable-background:new" + id="g25821" + inkscape:label="P-24"> + + + + sodipodi:nodetypes="cccccccccccc" /> - + id="g113472" + style="display:inline;enable-background:new" + inkscape:label="P-23"> + sodipodi:nodetypes="ccccccccccccccc" /> - - + style="display:inline;fill:#ffffff;enable-background:new" + id="g25815" + inkscape:label="P-22"> + transform="translate(-230.996,-1801)" + id="g24435-3" + style="display:inline;fill:#ffffff;enable-background:new"> + sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccacccaccccc" + inkscape:connector-curvature="0" + id="rect24419-6" + transform="translate(230.996,1801)" + d="m 447.00391,325.99805 v 2 h 1 v -1 h 1 v -1 z m 4,0 v 1 h 2 v -1 z m 4,0 v 1 h 2 v -1 z m 4,0 v 1 h 1 v 1 h 1 v -2 z m -12,4 v 2 h 1 v -2 z m 13,0 v 2 h 1 v -2 z m -13,4 v 2 h 1 v -2 z m 13,0 v 2 h 1 v -2 z m -13,4 v 2 h 2 v -1 h -1 v -1 z m 13,0 v 1 h -1 v 1 h 2 v -2 z m -9,1 v 1 h 2 v -1 z m 4,0 v 1 h 2 v -1 z" + style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none" /> - - + id="g25466" + style="display:inline;fill:#ffffff;enable-background:new" + transform="translate(624,-1280)" + inkscape:label="P-13"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.7;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m -365.49219,1608 c -0.67616,-0.01 -0.67616,1.0096 0,1 H -352.5 c 0.67616,0.01 0.67616,-1.0096 0,-1 z m 0,3 c -0.67616,-0.01 -0.67616,1.0096 0,1 h 2.63867 c 0.0948,-0.3549 0.23771,-0.6892 0.42188,-1 z m 9.92383,0 c 0.18417,0.3108 0.32709,0.6451 0.42188,1 H -352.5 c 0.67616,0.01 0.67616,-1.0096 0,-1 z m -9.92383,3 c -0.67616,-0.01 -0.67616,1.0096 0,1 h 3.06055 c -0.18417,-0.3108 -0.32709,-0.6451 -0.42188,-1 z m 10.34571,0 c -0.0948,0.3549 -0.23771,0.6892 -0.42188,1 H -352.5 c 0.67616,0.01 0.67616,-1.0096 0,-1 z m -10.34571,3 c -0.67616,-0.01 -0.67616,1.0096 0,1 H -352.5 c 0.67616,0.01 0.67616,-1.0096 0,-1 z" + id="path25334" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccccccccccccccccccccccccc" /> + + transform="matrix(-1,0,0,1,-157.001,-1280)" + id="g25455" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="P-12"> + id="path25451" + d="m -406.5,1608 c -0.67616,-0.01 -0.67616,1.0096 0,1 h 11.99219 c 0.67616,0.01 0.67616,-1.0096 0,-1 z m 3.93164,3 c 0.18417,0.3108 0.32709,0.6451 0.42188,1 h 7.63867 c 0.67616,0.01 0.67616,-1.0096 0,-1 z m 0.42188,3 c -0.0948,0.3549 -0.23771,0.6892 -0.42188,1 h 8.06055 c 0.67616,0.01 0.67616,-1.0096 0,-1 z m -4.35352,3 c -0.67616,-0.01 -0.67616,1.0096 0,1 h 11.99219 c 0.67616,0.01 0.67616,-1.0096 0,-1 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.7;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + - - - - - + id="g25449" + style="display:inline;fill:#ffffff;enable-background:new" + transform="translate(624,-1280)" + inkscape:label="P-11"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.7;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m -406.5,1608 c -0.67616,-0.01 -0.67616,1.0096 0,1 h 11.99219 c 0.67616,0.01 0.67616,-1.0096 0,-1 z m 3.93164,3 c 0.18417,0.3108 0.32709,0.6451 0.42188,1 h 7.63867 c 0.67616,0.01 0.67616,-1.0096 0,-1 z m 0.42188,3 c -0.0948,0.3549 -0.23771,0.6892 -0.42188,1 h 8.06055 c 0.67616,0.01 0.67616,-1.0096 0,-1 z m -4.35352,3 c -0.67616,-0.01 -0.67616,1.0096 0,1 h 11.99219 c 0.67616,0.01 0.67616,-1.0096 0,-1 z" + id="path25345" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccccccccccccccc" /> + - + inkscape:label="P-10"> - + id="path25435" + d="m -449.5,1609 c -0.67616,-0.01 -0.67616,1.0096 0,1 h 3.06836 c -0.18417,-0.3108 -0.32709,-0.6451 -0.42188,-1 z m 10.35352,0 c -0.0948,0.3549 -0.23771,0.6892 -0.42188,1 h 3.06055 c 0.67616,0.01 0.67616,-1.0096 0,-1 z m -10.35352,3 c -0.67616,-0.01 -0.67616,1.0096 0,1 h 12.99219 c 0.67616,0.01 0.67616,-1.0096 0,-1 z m 0,3 c -0.67616,-0.01 -0.67616,1.0096 0,1 h 12.99219 c 0.67616,0.01 0.67616,-1.0096 0,-1 z m 0,3 c -0.67616,-0.01 -0.67616,1.0096 0,1 h 12.99219 c 0.67616,0.01 0.67616,-1.0096 0,-1 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.7;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + - + id="g25433" + style="display:inline;fill:#ffffff;enable-background:new" + transform="translate(624,-1280)" + inkscape:label="P-9"> + sodipodi:nodetypes="ccccccccccccccccccccccccc" /> + - - + transform="rotate(180,670.505,476.5)" + style="display:inline;opacity:0.99;fill:#ffffff;enable-background:new" + id="g9316-9" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="P-8"> + + + + + - + + + + + + + + + + + + + + id="g12793" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="P-5"> + - - + inkscape:export-ydpi="96" + inkscape:export-xdpi="96" + inkscape:export-filename="blender_icons.png" + id="g14256" + transform="translate(-21)" + style="display:inline;opacity:0.4;fill:#ffffff;enable-background:new" + inkscape:label="P-4"> + + + + + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 541.77363,306 a 1.0001,1.0001 0 0 0 -0.41211,0.0781 l -4.74024,2 a 1.0004654,1.0004654 0 1 0 0.77735,1.84376 l 1.54883,-0.65235 -2.69532,5.375 -2.78906,-2.30078 a 1.50015,1.50015 0 1 0 -1.9082,2.3125 l 4.24023,3.5 a 1.50015,1.50015 0 0 0 2.29688,-0.48437 l 3.53711,-7.0586 0.41015,1.63086 a 1.0001,1.0001 0 1 0 1.93946,-0.48828 l -1.25977,-5 A 1.0001,1.0001 0 0 0 541.77363,306 Z" + id="path25427-8" /> + + + id="g120743" + style="display:inline;enable-background:new" + inkscape:label="O-24"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 490,307 c -0.55226,6e-5 -0.99994,0.44774 -1,1 v 8 c 6e-5,0.55226 0.44774,0.99994 1,1 h 12 c 0.55226,-6e-5 0.99994,-0.44774 1,-1 v -8 c -6e-5,-0.55226 -0.44774,-0.99994 -1,-1 z m 6,1 a 4,4 0 0 1 4,4 4,4 0 0 1 -4,4 4,4 0 0 1 -4,-4 4,4 0 0 1 4,-4 z" + id="path25011-0" /> + transform="translate(1055,731)" + style="display:inline;opacity:0.6;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;enable-background:new" + id="g25234-0" + inkscape:label="O-23"> + transform="translate(230.768,210.171)" + style="display:inline;enable-background:new" + id="g4087_GP_lineart" + inkscape:label="O-21"> + id="g4082"> + id="path12456-6" + mask="none" + d="m 198.0253,98.27163 v 1.5 h 1 v -1.5 z m 0,2.5 v 2 h 1 v -2 z m 0,3 v 1.2793 l -2.58594,2.35156 0.67188,0.73828 2.60351,-2.36719 0.49027,-0.002 c 0.82475,0 0.82408,-1 0,-1 h -0.17972 v -1 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + + - - - - + id="g3380" + inkscape:label="O-20" + style="display:inline;enable-background:new"> + sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + d="m 417.92349,304.73964 c -0.7818,-0.0644 -0.86293,1.09626 -0.0796,1.1383 l 0.41758,0.0202 c 0.78182,0.0644 0.86296,-1.09626 0.0796,-1.13831 z m -7.87437,1.29265 c -0.65325,0.42724 0.0163,1.38626 0.65667,0.94062 l 0.34001,-0.23929 c 0.65327,-0.42727 -0.0163,-1.38631 -0.65662,-0.94061 z m 5.26412,-0.10772 c 0.785,-0.0185 0.73895,-1.18175 -0.0451,-1.14009 -0.6811,-0.0652 -1.43225,-0.0213 -2.22341,0.0851 -0.785,0.0185 -0.73896,1.18176 0.0451,1.14011 0.8585,-0.10954 1.60282,-0.14009 2.22342,-0.0852 z m -5.74172,5.34858 c -0.17789,-0.75187 -1.32618,-0.47161 -1.12597,0.27482 -0.008,0.72815 0.18352,1.43475 0.53595,2.12392 0.17789,0.75187 1.32617,0.47159 1.12598,-0.27483 -0.40688,-0.70818 -0.47775,-1.41605 -0.53596,-2.12391 z m 1.14987,4.81425 c 0.55238,0.5479 1.3799,-0.2833 0.81165,-0.81524 l -0.30437,-0.28193 c -0.55238,-0.54789 -1.37991,0.2833 -0.81163,0.81524 z m 2.55883,0.11471 c -0.78112,0.0716 -0.65484,1.22767 0.12391,1.13446 0.79706,0.0708 1.5429,0.0136 2.2124,-0.23372 0.7811,-0.0716 0.65482,-1.22768 -0.12391,-1.13445 -0.66955,0.35373 -1.42049,0.37687 -2.2124,0.23371 z m 4.35036,-1.24066 c 0.39775,-0.66505 -0.63058,-1.23994 -1.00859,-0.56384 l -0.19953,0.36135 c -0.39776,0.66506 0.63057,1.23995 1.00857,0.56383 z m -1.53457,-4.82813 c -0.44444,-0.63566 -1.409,0.0364 -0.94666,0.65956 0.53116,0.53126 0.99257,1.10609 1.28624,1.78569 0.44445,0.63565 1.40902,-0.0364 0.94667,-0.65956 -0.24301,-0.74231 -0.69323,-1.32054 -1.28625,-1.78569 z m -2.73483,-1.49223 c -0.72218,-0.30138 -1.16808,0.7761 -0.43732,1.05681 l 0.39025,0.14758 c 0.7222,0.30141 1.1681,-0.7761 0.43732,-1.0568 z m -7.60223,1.91562 c -0.52109,0.57678 0.37464,1.33651 0.87855,0.74515 l 0.26685,-0.31654 c 0.52111,-0.57679 -0.37465,-1.33654 -0.87854,-0.74516 z m 1.15912,7.09355 c -0.1906,-0.74845 -1.33363,-0.44917 -1.12109,0.29354 l 0.11543,0.39523 c 0.19062,0.74845 1.33365,0.44917 1.12109,-0.29354 z m -0.68592,-4.36328 c -0.0858,-0.76698 -1.25912,-0.62352 -1.15127,0.14077 -0.065,0.75431 -0.008,1.50847 0.28594,2.26232 0.0859,0.76696 1.25912,0.62352 1.15129,-0.14076 -0.28468,-0.81162 -0.29126,-1.53878 -0.28596,-2.26233 z m 1.97398,-4.7241 c -0.77314,0.13162 -0.55483,1.27463 0.21417,1.12135 0.7762,-0.30633 1.5005,-0.42412 2.18687,-0.40397 0.77313,-0.13163 0.55482,-1.27462 -0.21418,-1.12137 -0.74152,0.0229 -1.4733,0.13255 -2.18686,0.40399 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.15052;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:2.2;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + id="path4101-2-6-9-1_GP_dotdash" /> + transform="translate(167.426,209.695)" + style="display:inline;enable-background:new" + id="g7880_GP_lenght" + inkscape:label="O-19"> + sodipodi:nodetypes="cccccc" /> + id="path15289-7-6-5" + d="m 225.6621,95.349988 c -0.67621,-0.0096 -0.67621,1.009611 0,1 h 2.79493 c -1.0479,1.117288 -1.7641,1.668027 -2.82812,2.732043 -0.62065,0.56444 0.28321,1.468319 0.84765,0.847657 1.06063,-1.101282 1.59202,-1.777197 2.68554,-2.870716 v 2.791016 c -0.01,0.676162 1.00956,0.676162 1,0 v -4 c -3e-5,-0.276131 -0.22387,-0.499973 -0.5,-0.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + id="path15289-7-6-5-2" + d="m 221.03217,109.33958 c 0.67621,0.01 0.67621,-1.00961 0,-1 h -2.79493 c 1.0479,-1.11729 1.7641,-1.66802 2.82812,-2.73204 0.62065,-0.56444 -0.28321,-1.46832 -0.84765,-0.84766 -1.06063,1.10128 -1.59202,1.7772 -2.68554,2.87072 v -2.79102 c 0.01,-0.67616 -1.00956,-0.67616 -1,0 v 4 c 3e-5,0.27613 0.22387,0.49998 0.5,0.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> - - - + style="display:inline;enable-background:new" + id="g28812" + transform="rotate(180,328,312.5)" + inkscape:label="O-15"> - - + inkscape:label="O-12"> + id="g120719" + style="display:inline;enable-background:new" + inkscape:label="O-11"> + + + id="g120740" + style="display:inline;enable-background:new" + inkscape:label="O-9"> + id="g60277" + inkscape:label="O-8"> + id="g120737" + style="display:inline;enable-background:new"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 155.49219,314 a 0.50005,0.50005 0 1 0 0,1 h 8.99414 a 0.50005,0.50005 0 1 0 0,-1 z m 0,2 a 0.50005,0.50005 0 1 0 0,1 h 8.99414 a 0.50005,0.50005 0 1 0 0,-1 z" + id="path11052" + inkscape:connector-curvature="0" /> + + + + + + + + + + + + + + + + + + + + + + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:2.2;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 552.5,621 a 0.50005,0.50005 0 1 0 0,1 h 1.99219 a 0.50005,0.50005 0 1 0 0,-1 z m 4.02344,0 a 0.50005,0.50005 0 1 0 0,1 h 1.91601 a 0.50005,0.50005 0 1 0 0,-1 z m 3.9707,0 a 0.50005,0.50005 0 1 0 0,1 h 1.99805 a 0.50005,0.50005 0 1 0 0,-1 z m 3.99805,0 a 0.50005,0.50005 0 1 0 0,1 h 1 a 0.50005,0.50005 0 1 0 0,-1 z M 552.5,624 a 0.50005,0.50005 0 1 0 0,1 h 1 a 0.50005,0.50005 0 1 0 0,-1 z m 3,0 a 0.50005,0.50005 0 1 0 0,1 h 1.99805 a 0.50005,0.50005 0 1 0 0,-1 z m 4.05273,0 a 0.50005,0.50005 0 1 0 0,1 h 1.91797 a 0.50005,0.50005 0 1 0 0,-1 z m 3.94727,0 a 0.50005,0.50005 0 1 0 0,1 h 1.99219 a 0.50005,0.50005 0 1 0 0,-1 z m -11,3 a 0.50005,0.50005 0 1 0 0,1 h 1.99219 a 0.50005,0.50005 0 1 0 0,-1 z m 4.02344,0 a 0.50005,0.50005 0 1 0 0,1 h 1.91601 a 0.50005,0.50005 0 1 0 0,-1 z m 3.9707,0 a 0.50005,0.50005 0 1 0 0,1 h 1.99805 a 0.50005,0.50005 0 1 0 0,-1 z m 3.99805,0 a 0.50005,0.50005 0 1 0 0,1 h 1 a 0.50005,0.50005 0 1 0 0,-1 z M 552.5,630 a 0.50005,0.50005 0 1 0 0,1 h 1 a 0.50005,0.50005 0 1 0 0,-1 z m 3,0 a 0.50005,0.50005 0 1 0 0,1 h 1.99805 a 0.50005,0.50005 0 1 0 0,-1 z m 4.05273,0 a 0.50005,0.50005 0 1 0 0,1 h 1.91797 a 0.50005,0.50005 0 1 0 0,-1 z m 3.94727,0 a 0.50005,0.50005 0 1 0 0,1 h 1.99219 a 0.50005,0.50005 0 1 0 0,-1 z m -11,3 a 0.50005,0.50005 0 1 0 0,1 h 1.99219 a 0.50005,0.50005 0 1 0 0,-1 z m 4.02344,0 a 0.50005,0.50005 0 1 0 0,1 h 1.91601 a 0.50005,0.50005 0 1 0 0,-1 z m 3.9707,0 a 0.50005,0.50005 0 1 0 0,1 h 1.99805 a 0.50005,0.50005 0 1 0 0,-1 z m 3.99805,0 a 0.50005,0.50005 0 1 0 0,1 h 1 a 0.50005,0.50005 0 1 0 0,-1 z" + id="path10870" + inkscape:connector-curvature="0" /> + id="g120704" + style="display:inline;enable-background:new" + inkscape:label="O-4"> + + + + + + + + + + + + + + + + + id="path10375" + d="m 471.5,221 a 0.50005,0.50005 0 0 0 -0.35352,0.14648 l -3,3 A 0.50005,0.50005 0 0 0 468,224.5 v 10 a 0.50005,0.50005 0 0 0 0.5,0.5 h 8 a 0.50005,0.50005 0 0 0 0.35352,-0.14648 l 3,-3 A 0.50005,0.50005 0 0 0 480,231.5 v -10 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z m 0.20703,1 H 479 v 9.29297 L 476.29297,234 H 469 v -9.29297 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.7;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + id="circle10377" + d="m 474,225.99805 c -1.09907,0 -2,0.90288 -2,2.00195 0,1.09907 0.90093,2 2,2 1.09907,0 2.00195,-0.90093 2.00195,-2 0,-1.09907 -0.90288,-2.00195 -2.00195,-2.00195 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.00157;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers stroke fill;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + transform="translate(21,615)" + id="g10447" + style="display:inline;enable-background:new" + inkscape:label="N-24"> + id="path10443" + d="m 478.49609,-323.00781 a 0.50005,0.50005 0 0 0 -0.34765,0.85937 l 2.64648,2.64649 -2.64648,2.64648 a 0.50005,0.50005 0 1 0 0.70703,0.70703 l 3,-3 a 0.50005,0.50005 0 0 0 0,-0.70703 l -3,-3 a 0.50005,0.50005 0 0 0 -0.35938,-0.15234 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.7;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:7.4;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + id="path10445" + d="m 473.4375,-330.97461 c -0.67078,0.0637 -1.33815,0.24001 -1.97461,0.53711 -2.54627,1.1886 -3.92907,3.99498 -3.32031,6.73828 0.60875,2.7433 3.04739,4.69922 5.85742,4.69922 h 2.29297 l -2.14453,2.14453 a 0.50005,0.50005 0 1 0 0.70703,0.70703 l 3,-3 a 0.50005,0.50005 0 0 0 0,-0.70703 l -3,-3 a 0.50005,0.50005 0 1 0 -0.70703,0.70703 L 476.29688,-320 H 474 c -2.34689,0 -4.37244,-1.62485 -4.88086,-3.91602 -0.50842,-2.29116 0.63902,-4.62253 2.76563,-5.61523 2.12705,-0.99291 4.66398,-0.38512 6.07812,1.48242 l 0.74023,0.97656 C 479.25532,-326.34303 480.1366,-326 481,-326 h 0.5 a 0.50005,0.50005 0 1 0 0,-1 H 481 c -0.59704,0 -1.17055,-0.2407 -1.5,-0.67578 l -0.74023,-0.97656 c -1.27424,-1.68278 -3.30992,-2.51346 -5.32227,-2.32227 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:7.4;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> - - + transform="translate(0,636)" + id="g28776" + style="display:inline;enable-background:new" + inkscape:label="N-23"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 447.50977,297.01172 a 0.50005,0.50005 0 1 0 0,1 h 6.2832 c -0.26437,-0.28877 -0.47061,-0.62726 -0.60742,-1 z m 11.29882,0 c -0.13697,0.37274 -0.34281,0.71123 -0.60742,1 h 2.3086 a 0.50005,0.50005 0 1 0 0,-1 z" + transform="translate(21,-636)" + id="path10435" /> + sodipodi:nodetypes="sssss" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 472.49805,-351.99805 c 1.37513,0 2.50195,1.12557 2.50195,2.5 0,1.37444 -1.12682,2.49805 -2.50195,2.49805 -1.37514,0 -2.5,-1.12361 -2.5,-2.49805 0,-1.37443 1.12486,-2.5 2.5,-2.5 z m 0,1 c -0.83537,0 -1.5,0.66668 -1.5,1.5 0,0.83332 0.66463,1.49805 1.5,1.49805 0.83537,0 1.50195,-0.66473 1.50195,-1.49805 0,-0.83332 -0.66658,-1.5 -1.50195,-1.5 z" + id="ellipse10441" + inkscape:connector-curvature="0" /> + + transform="translate(105,2.4e-4)" + id="g10345" + style="display:inline;enable-background:new" + inkscape:label="N-22"> + + + + + id="rect10343" + d="m 342.5,284 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 4 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 4 c 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 v -4 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + + + id="path10453" + transform="translate(0,-636)" + d="m 554.5,284 c -1.37479,0 -2.5,1.12521 -2.5,2.5 v 2.00781 c 0,1.37479 1.12521,2.5 2.5,2.5 1.37479,0 2.5,-1.12521 2.5,-2.5 v -0.5 h 1.51172 0.49805 l -0.0137,3.28711 c -1.65939,1.40974 -3.24134,2.62162 -3.98828,6.10742 -0.17787,0.67446 0.86019,0.89868 0.97657,0.21094 0.67528,-3.15139 1.9234,-4.08819 3.51757,-5.42773 1.58398,1.34059 2.83152,2.27746 3.51953,5.42968 0.13318,0.66658 1.13544,0.44609 0.97657,-0.21484 -0.76273,-3.49457 -2.35105,-4.70351 -4.00196,-6.11719 l 0.0137,-3.27539 h 2.21679 3.28516 c 0.6573,-0.009 0.6573,-0.9907 0,-1 h -3.04297 C 562.0093,287.6044 561.2975,288 560.5,288 c -0.79751,0 -1.50931,-0.3956 -1.96875,-0.99219 H 558.51172 557 V 286.5 c 0,-1.37479 -1.12521,-2.5 -2.5,-2.5 z m 6,0.008 c -0.82054,0 -1.49609,0.67557 -1.49609,1.4961 0,0.82054 0.67555,1.49609 1.49609,1.49609 0.82053,0 1.49609,-0.67555 1.49609,-1.49609 0,-0.82053 -0.67556,-1.4961 -1.49609,-1.4961 z m -6,0.992 c 0.83435,0 1.5,0.66565 1.5,1.5 v 2.00781 c 0,0.83435 -0.66565,1.5 -1.5,1.5 -0.83435,0 -1.5,-0.66565 -1.5,-1.5 V 286.5 c 0,-0.83435 0.66565,-1.5 1.5,-1.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:7.4;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + transform="translate(-147,636)" + id="g10393" + style="display:inline;enable-background:new" + inkscape:label="N-20"> + id="ellipse10386" + d="m 560.99219,-349.49609 c 0,-1.37714 1.12676,-2.50391 2.5039,-2.50391 1.37715,0 2.50391,1.12677 2.50391,2.50391 0,1.37713 -1.12676,2.5039 -2.50391,2.5039 -1.37714,0 -2.5039,-1.12677 -2.5039,-2.5039 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + id="ellipse10388" + d="m 552,-339.99219 c 0,-1.09609 0.89673,-1.99609 1.99219,-1.99609 1.09546,0 1.99414,0.9 1.99414,1.99609 0,1.09609 -0.89868,1.99805 -1.99414,1.99805 -1.09546,0 -1.99219,-0.90196 -1.99219,-1.99805 z m 1,0 c 0,0.55685 0.43837,0.99805 0.99219,0.99805 0.55382,0 0.99414,-0.4412 0.99414,-0.99805 0,-0.55685 -0.44032,-0.99609 -0.99414,-0.99609 -0.55382,0 -0.99219,0.43924 -0.99219,0.99609 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + + + + id="ellipse10411" + d="m 350.99219,-370.49609 c 0,-1.37714 1.12676,-2.50391 2.5039,-2.50391 1.37715,0 2.50391,1.12677 2.50391,2.50391 0,1.37713 -1.12676,2.5039 -2.50391,2.5039 -1.37714,0 -2.5039,-1.12677 -2.5039,-2.5039 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + - - + transform="rotate(180,380.4965,-26.99765)" + id="g10433" + style="display:inline;enable-background:new" + inkscape:label="N-18"> + transform="translate(1)" + id="g10429"> + id="path10423-5" + transform="rotate(180,390.4965,-26.99765)" + d="m 393.49219,289.00391 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 1.73047 l -2.76953,2.76953 h -1.73047 c -0.27613,2e-5 -0.49997,0.22386 -0.5,0.5 v 1.02929 c 3e-5,0.27613 0.22387,0.47068 0.5,0.47071 h 1 c 0.2725,0 0.5,0.17071 0.5,0.5 v 1 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 1 c 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 v -1.73243 l 2.76758,-2.76757 h 1.73242 c 0.27613,-2e-5 0.49997,-0.22386 0.5,-0.5 v -1.02735 c -3e-5,-0.27613 -0.22387,-0.47262 -0.5,-0.47265 h -1 c -0.2725,0 -0.5,-0.17071 -0.5,-0.5 v -1 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + + transform="translate(-210,636)" + id="g10409" + style="display:inline;enable-background:new" + inkscape:label="N-17"> - - - - - + id="ellipse10403" + d="m 560.99219,-349.49609 c 0,-1.37714 1.12676,-2.50391 2.5039,-2.50391 1.37715,0 2.50391,1.12677 2.50391,2.50391 0,1.37713 -1.12676,2.5039 -2.50391,2.5039 -1.37714,0 -2.5039,-1.12677 -2.5039,-2.5039 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + + - - - - - + transform="translate(62.9759,-41.0395)" + id="g10359" + style="display:inline;enable-background:new" + inkscape:label="N-16"> + transform="translate(0,21)" + id="g10357"> + + id="circle10349" + d="m 269.02344,314.03906 c -1.09865,0 -2,0.90136 -2,2 0,1.09865 0.90135,2 2,2 1.09864,0 2,-0.90135 2,-2 0,-1.09864 -0.90136,-2 -2,-2 z m 0,1 c 0.5582,0 1,0.4418 1,1 0,0.55821 -0.4418,1 -1,1 -0.55821,0 -1,-0.44179 -1,-1 0,-0.5582 0.44179,-1 1,-1 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers stroke fill;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + id="path10351-7" + d="m 258.53125,304.03516 a 0.50005,0.50005 0 1 0 0,1 h 12 a 0.50005,0.50005 0 1 0 0,-1 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers stroke fill;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + + transform="translate(0,636)" + id="g9298" + style="display:inline;enable-background:new" + inkscape:label="N-15"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 306.49219,-348.01172 a 0.50005,0.50005 0 0 0 -0.38867,0.19531 l -1.95704,1.95703 a 0.50005,0.50005 0 1 0 0.70704,0.70704 L 306,-346.29883 v 5.79297 a 0.50005,0.50005 0 0 0 0.5,0.5 h 5.79297 l -1.14649,1.14648 a 0.50005,0.50005 0 1 0 0.70704,0.70704 l 1.95117,-1.94922 a 0.50005,0.50005 0 0 0 0.008,-0.79883 l -1.95898,-1.95899 a 0.50005,0.50005 0 1 0 -0.70704,0.70704 l 1.14649,1.14648 H 307 v -5.29297 l 1.14648,1.14649 a 0.50005,0.50005 0 1 0 0.70704,-0.70704 l -1.96094,-1.96093 -0.006,-0.008 a 0.50005,0.50005 0 0 0 -0.39453,-0.1836 z" + id="path10419-4" + inkscape:connector-curvature="0" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 306.44531,-351.99805 c -1.16966,-0.043 -2.35048,0.23548 -3.40039,0.84766 -2.09911,1.22396 -3.28024,3.5522 -3.00586,5.94141 0.27491,2.39372 1.98225,4.44954 4.31055,5.18554 a 0.50004997,0.50004997 0 1 0 0.30078,-0.95312 c -1.94954,-0.61628 -3.38871,-2.3582 -3.61719,-4.34766 -0.22899,-1.99397 0.75092,-3.93196 2.51563,-4.96094 1.76401,-1.02856 3.9738,-0.9382 5.67187,0.20118 0.93801,0.62937 1.92387,1.08398 3.2793,1.08398 h 1 a 0.50004997,0.50004997 0 1 0 0,-1 h -1 c -1.14457,0 -1.85649,-0.33615 -2.7207,-0.91602 -1.00565,-0.67477 -2.16433,-1.03898 -3.33399,-1.08203 z" + id="path10421-9" + inkscape:connector-curvature="0" /> + transform="translate(11,-136)" + id="g10373" + style="display:inline;enable-background:new" + inkscape:label="N-14"> + id="path10361" + d="m 275.4707,427 c -0.42985,0.004 -0.62994,0.5874 -0.31869,0.87125 0.40897,0.54487 0.81793,1.08974 1.2269,1.63461 -0.43981,0.59662 -0.90116,1.17907 -1.32716,1.78476 -0.21053,0.38613 0.24226,0.86663 0.6402,0.67938 0.28682,-0.15751 0.42097,-0.48416 0.63231,-0.72356 0.22655,-0.3022 0.4531,-0.6044 0.67965,-0.9066 0.3916,0.51133 0.76296,1.03993 1.16735,1.54035 0.31222,0.30974 0.89981,0.009 0.83079,-0.42566 -0.0715,-0.31923 -0.34727,-0.53953 -0.51804,-0.80941 -0.28503,-0.37975 -0.57007,-0.75951 -0.8551,-1.13926 0.43981,-0.59662 0.90116,-1.17907 1.32716,-1.78476 0.21053,-0.38613 -0.24226,-0.86663 -0.6402,-0.67938 -0.28682,0.15751 -0.42097,0.48416 -0.63231,0.72356 -0.22655,0.3022 -0.4531,0.6044 -0.67965,0.9066 -0.39082,-0.51197 -0.76442,-1.03847 -1.1661,-1.54128 -0.0981,-0.0907 -0.23371,-0.13896 -0.36711,-0.1306 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + inkscape:export-xdpi="96" + inkscape:export-ydpi="96"> + + + + + + + + id="g10331"> + id="path10321-5" + transform="matrix(-0.942809,-0.942809,-0.942809,0.942809,832.145,223.544)" + d="m 262.58789,287.02734 a 0.60006001,0.60006001 0 0 0 -0.47266,0.95899 l 2.14844,3.02929 -2.14844,3.0293 a 0.60024527,0.60024527 0 1 0 0.97852,0.69531 l 1.90625,-2.6875 1.90625,2.6875 a 0.60024527,0.60024527 0 1 0 0.97852,-0.69531 l -2.14844,-3.0293 2.14844,-3.02929 a 0.60005997,0.60005997 0 0 0 -0.041,-0.75781 0.60005997,0.60005997 0 0 0 -0.9375,0.0625 l -1.90625,2.6875 -1.90625,-2.6875 a 0.60006001,0.60006001 0 0 0 -0.0645,-0.0781 0.60006001,0.60006001 0 0 0 -0.44141,-0.18555 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + + transform="translate(21,573)" + id="g10534" + style="display:inline;enable-background:new" + inkscape:label="N-12"> + id="path10530" + d="m 216.5,-282 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 6 a 0.50005,0.50005 0 0 0 0.5,0.5 h 7 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -6 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z m 0.5,1 h 6 v 5 h -6 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.7;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers stroke fill;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + id="path10532" + d="m 226.5,-289 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 13 a 0.50005,0.50005 0 0 0 0.5,0.5 h 3 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -13 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z m 0.5,1 h 2 v 12 h -2 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers stroke fill;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + transform="translate(-21,615)" + style="display:inline;opacity:0.8;enable-background:new" + id="g10464" + inkscape:label="N-11"> + id="path10460" + d="m 237.5,-323 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 4 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 4 c 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 v -4 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + id="path10462" + d="m 237.52148,-329 a 0.50005,0.50005 0 0 0 -0.5,0.49805 l -0.0215,4.5 1,0.004 0.0195,-4.00195 H 247 v 9 h -4 v 1 h 4.5 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -10 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + - - - + id="g128040" + style="display:inline;enable-background:new" + inkscape:label="N-10"> + + id="path10488" + style="display:inline;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:new" + d="m 209,289 v -2 h -1 v 2 z m 0,-3 v -2 h -1 v 2 z m 0,12 v -2 h -1 v 2 z m 0,-3 v -2 h -1 v 2 z m 0,-3 v -2 h -1 v 2 z" /> - + transform="translate(125,-1)" + id="g10480" + style="display:inline;opacity:0.8;enable-background:new" + inkscape:label="N-9"> + id="path10478" + d="m 51.492188,285.99219 a 0.50005,0.50005 0 0 0 -0.388672,0.19531 0.50005,0.50005 0 0 0 -0.0039,0.006 l -1.953125,1.95312 a 0.50005,0.50005 0 1 0 0.707032,0.70704 L 51,287.70703 V 295.5 a 0.50005,0.50005 0 0 0 0.5,0.5 h 7.792969 l -1.146485,1.14648 a 0.50005,0.50005 0 1 0 0.707032,0.70704 l 1.957031,-1.95704 a 0.50005,0.50005 0 0 0 0.002,-0.79296 0.50005,0.50005 0 0 0 -0.0059,-0.004 l -1.953125,-1.95313 a 0.50005,0.50005 0 1 0 -0.707032,0.70704 L 59.292969,295 H 52 v -7.29297 l 1.146484,1.14649 a 0.50005,0.50005 0 1 0 0.707032,-0.70704 l -1.957032,-1.95703 a 0.50005,0.50005 0 0 0 -0.404296,-0.19726 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + d="m 63,290 v -2 h -1 v 2 z m 0,-3 v -2 h -1 v 2 z m 0,12 v -2 h -1 v 2 z m 0,-3 v -2 h -1 v 2 z m 0,-3 v -2 h -1 v 2 z" + style="display:inline;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:new" + id="path10486" + inkscape:connector-curvature="0" /> - - - - + id="g128044" + style="display:inline;enable-background:new" + inkscape:label="N-8"> + id="path10484" + style="display:inline;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:new" + d="m 167,289 v -2 h -1 v 2 z m 0,-3 v -2 h -1 v 2 z m 0,12 v -2 h -1 v 2 z m 0,-3 v -2 h -1 v 2 z m 0,-3 v -2 h -1 v 2 z" + inkscape:connector-curvature="0" /> + id="path10490" + d="m 160.5,285.99023 a 0.50005,0.50005 0 0 0 -0.34766,0.85743 l 1.14649,1.14648 -6.29297,6.29297 -1.14648,-1.14649 a 0.50005,0.50005 0 1 0 -0.70704,0.70704 l 3,3 a 0.50005,0.50005 0 1 0 0.70704,-0.70704 l -1.14649,-1.14648 6.29297,-6.29297 1.14648,1.14649 a 0.50005,0.50005 0 1 0 0.70704,-0.70704 l -3,-3 A 0.50005,0.50005 0 0 0 160.5,285.99023 Z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.85;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + id="g10502" + transform="rotate(180,170.503,427.501)" + style="display:inline;enable-background:new" + inkscape:label="N-7"> + + id="path10496" + d="m 201.60938,559.02148 a 0.60006002,0.60006002 0 0 0 -0.47071,0.98243 l 1.59766,2.00195 -1.59766,2.00195 a 0.6002929,0.6002929 0 1 0 0.9375,0.75 l 1.42774,-1.78906 1.42773,1.78906 a 0.6002929,0.6002929 0 1 0 0.9375,-0.75 l -1.59766,-2.00195 1.59766,-2.00195 a 0.6002929,0.6002929 0 1 0 -0.9375,-0.75 l -1.42773,1.78906 -1.42774,-1.78906 a 0.60006002,0.60006002 0 0 0 -0.46679,-0.23243 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> - - - + id="path10498" + d="m 196.50391,561 a 0.50004997,0.50004997 0 0 0 -0.22657,0.0547 c 0,0 -0.3567,0.1843 -0.66796,0.57226 C 195.29811,562.01491 195,562.63889 195,563.5 v 5 c 0,0.86111 0.29839,1.4864 0.60938,1.875 0.31098,0.3886 0.66796,0.57227 0.66796,0.57227 a 0.50004997,0.50004997 0 1 0 0.44532,-0.89454 c 0,0 -0.14302,-0.0665 -0.33204,-0.30273 C 196.20161,569.5138 196,569.13889 196,568.5 v -5 c 0,-0.63889 0.20189,-1.01282 0.39062,-1.24805 0.18874,-0.23523 0.33204,-0.30078 0.33204,-0.30078 A 0.50004997,0.50004997 0 0 0 196.50391,561 Z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.7;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + id="path10500" + d="m 207.48828,557 a 0.50004997,0.50004997 0 0 0 -0.20508,0.95117 c 0,0 0.14526,0.0655 0.33399,0.30078 0.18873,0.23523 0.38867,0.60916 0.38867,1.24805 v 5 c 0,0.63889 -0.20161,1.0138 -0.39063,1.25 -0.18901,0.2362 -0.33203,0.30273 -0.33203,0.30273 a 0.50006306,0.50006306 0 1 0 0.44727,0.89454 c 0,0 0.35503,-0.18367 0.66601,-0.57227 0.31099,-0.3886 0.60938,-1.01389 0.60938,-1.875 v -5 c 0,-0.86111 -0.29811,-1.48509 -0.60938,-1.87305 -0.31127,-0.38796 -0.66796,-0.57226 -0.66796,-0.57226 A 0.50004997,0.50004997 0 0 0 207.48828,557 Z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + transform="translate(-25,619)" + id="g10474" + style="display:inline;enable-background:new" + inkscape:label="N-6"> + id="path10468" + d="m 137.5,-326 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 3 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 3 c 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 v -3 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + inkscape:connector-curvature="0" + id="path10470" + d="m 137.52148,-331 a 0.50005,0.50005 0 0 0 -0.5,0.49609 l -0.0215,3.5 1,0.008 0.0176,-3.00391 H 145 v 7 h -3 v 1 h 3.5 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -8 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + id="path10472" + d="m 141.52148,-335 a 0.50005,0.50005 0 0 0 -0.5,0.49609 l -0.0215,2.5 1,0.008 0.0176,-2.00391 H 149 v 7 h -2 v 1 h 2.5 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -8 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> - + id="g63797" + inkscape:label="N-5"> + + + + + + + id="g10528" + transform="translate(-21,384)" + style="display:inline;enable-background:new" + inkscape:label="N-4"> + + + + + + + + id="g128007" + style="display:inline;enable-background:new" + inkscape:label="N-3"> + id="path10295" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 61,288 v -2.5 c -2.8e-5,-0.27613 -0.223869,-0.49997 -0.5,-0.5 H 58 v 1 h 2 v 2 z m -13,0 v -2.5 c 2.8e-5,-0.27613 0.223869,-0.49997 0.5,-0.5 H 51 v 1 h -2 v 2 z m 13,7 v 2.5 c -2.8e-5,0.27613 -0.223869,0.49997 -0.5,0.5 H 58 v -1 h 2 v -2 z m -13,0 v 2.5 c 2.8e-5,0.27613 0.223869,0.49997 0.5,0.5 H 51 v -1 h -2 v -2 z" + inkscape:connector-curvature="0" /> + id="path10315" + d="m 54,285 v 2 h 1 v -2 z m -2.5,3 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 6 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 6 c 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 v -6 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 z m 0.5,1 h 5.027344 v 5 H 52 Z m -4,2 v 1 h 2 v -1 z m 11,0 v 1 h 2 v -1 z m -5,5 v 2 h 1 v -2 z" + style="display:inline;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;enable-background:new" + inkscape:connector-curvature="0" /> - + id="g10303" + transform="matrix(-1,0,0,1,68.0071,83.9999)" + style="display:inline;enable-background:new" + inkscape:label="N-2"> + + + + + + d="m 29,207 v 1 h -2 v -1 z m 4,4 h 1 v 3 h -1 z m 0,-10 h 1 v 3 h -1 z m -2.5,4 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 4 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 6 c 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 V 208 h 0.335938 l 2.61914,1.9043 c 0.08568,0.0623 0.188958,0.0958 0.294922,0.0957 h 0.25 c 0.276131,-3e-5 0.499972,-0.22387 0.5,-0.5 v -4 c -2.8e-5,-0.27613 -0.223869,-0.49997 -0.5,-0.5 h -0.25 c -0.105964,-1.5e-4 -0.209238,0.0334 -0.294922,0.0957 L 37.335938,207 H 37 v -1.5 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 z m 0.5,1 h 5.027344 v 3 H 31 Z m 9,0.30078 v 2.39844 L 38.349609,207.5 Z" + style="display:inline;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;enable-background:new" + id="path10313" /> - + id="g12836" + style="display:inline;fill:#ffffff;enable-background:new" + transform="translate(-123,459)" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="M-26"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 540.5,263 c -2.47936,0 -4.5,2.02064 -4.5,4.5 0,2.47936 2.02064,4.5 4.5,4.5 2.47936,0 4.5,-2.02064 4.5,-4.5 0,-2.47936 -2.02064,-4.5 -4.5,-4.5 z m 0,1 c 1.93892,0 3.5,1.56108 3.5,3.5 0,1.40621 -0.82672,2.60476 -2.01758,3.16211 -0.16784,-2.48561 -2.15892,-4.47669 -4.64453,-4.64453 C 537.89524,264.82672 539.09379,264 540.5,264 Z" + transform="translate(123,-459)" + id="path12830" + inkscape:connector-curvature="0" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.7;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 657.50195,-192.93359 a 0.50004997,0.50004997 0 0 0 -0.22656,0.0508 c -2.27231,1.07327 -3.57921,3.51034 -3.21484,5.99609 0.36436,2.48575 2.31598,4.44563 4.80078,4.82227 2.48479,0.37663 4.92837,-0.91763 6.01367,-3.1836 a 0.50013262,0.50013262 0 1 0 -0.90234,-0.43164 c -0.89704,1.8729 -2.90867,2.93833 -4.96289,2.62696 -2.05422,-0.31138 -3.6598,-1.92406 -3.96094,-3.97852 -0.30115,-2.05446 0.77387,-4.06001 2.65234,-4.94727 a 0.50004997,0.50004997 0 0 0 -0.19922,-0.95507 z" + id="path12834" + inkscape:connector-curvature="0" /> + style="display:inline;fill:#ffffff;enable-background:new" + id="g22255" + transform="translate(-1.85367e-6,-21)" + inkscape:label="M-25"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:7.40019;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 521.5,287 c -5.3,0 -9.5,4.7037 -9.5,9.5 v 1 a 0.50005,0.50005 0 0 0 0.5,0.5 h 3 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -1 c 0,-1.05 0.68155,-2.47832 1.73828,-3.59375 C 518.79501,291.79082 520.19444,291 521.5,291 h 1 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -3 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z m 0,1 h 0.5 v 2 h -0.5 c -1.69444,0 -3.29501,0.95918 -4.48828,2.21875 C 515.81845,293.47832 515,295.05 515,296.5 v 0.5 h -2 v -0.5 c 0,-4.2037 3.8,-8.5 8.5,-8.5 z" + id="path21998" + inkscape:connector-curvature="0" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.5;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 512.5,263 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 6.60938 c 0.29756,-0.51189 0.62592,-1.00687 1,-1.46876 V 264 h 2 v 2.71875 c 0.32103,-0.24305 0.65292,-0.47168 1,-0.67969 V 263.5 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 z" + transform="translate(1.85367e-6,21)" + id="path22786" + inkscape:connector-curvature="0" /> - + style="display:inline;fill:#ffffff;enable-background:new" + id="g21627" + transform="translate(2.81463e-5,-42)" + inkscape:label="M-24"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.5;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 495.5,305 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 1.5 h -3.5 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 3.5 h -1.5 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 7 a 0.50005,0.50005 0 1 0 1,0 V 312 h 1.5 a 0.50005,0.50005 0 0 0 0.5,-0.5 V 308 h 3.5 a 0.50005,0.50005 0 0 0 0.5,-0.5 V 306 h 6.5 a 0.50005,0.50005 0 1 0 0,-1 z" + id="path21618" + inkscape:connector-curvature="0" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 502.5,308 c -5.79307,0 -10.5,4.70693 -10.5,10.5 a 0.50004997,0.50004997 0 1 0 1,0 c 0,-5.25263 4.24737,-9.5 9.5,-9.5 a 0.50004997,0.50004997 0 1 0 0,-1 z" + id="circle21622" + inkscape:connector-curvature="0" /> + style="display:inline;fill:#ffffff;enable-background:new" + id="g26277" + transform="translate(273,-21)" + inkscape:label="M-23"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 203.5,284 a 0.50005,0.50005 0 0 0 -0.35352,0.14648 l -8,8 A 0.50005,0.50005 0 0 0 195,292.5 l -0.008,5.00586 a 0.50005,0.50005 0 0 0 0.5,0.50195 L 208.5,298 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -13 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z m 0.20703,1 H 208 v 12 l -12.00781,0.008 0.008,-4.30078 z" + id="path23052" + inkscape:connector-curvature="0" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.5;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 200.5,284 -5.00781,0.008 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 5 a 0.50005,0.50005 0 1 0 1,0 v -4.50195 L 200.5,285 a 0.50005,0.50005 0 1 0 0,-1 z" + id="path23056" + inkscape:connector-curvature="0" /> - + style="display:inline;fill:#ffffff;enable-background:new" + id="g22510" + transform="translate(-1.85367e-6,21)" + inkscape:label="M-22"> + + + + d="m 451.5,246 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 5 a 0.50005,0.50005 0 0 0 0.5,0.5 h 5 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -5 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z m 0.5,1 h 4 v 4 h -4 z" + id="rect22466" + inkscape:connector-curvature="0" /> - + style="display:inline;fill:#ffffff;enable-background:new" + id="g22386" + transform="translate(-886,80.0001)" + inkscape:label="M-21"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 433,263 c -3.84758,0 -6.97776,3.12003 -6.99805,6.96289 -6e-5,0.0125 -0.002,0.0246 -0.002,0.0371 a 0.50004994,0.50004994 0 0 0 0.002,0.0488 0.50004994,0.50004994 0 0 0 0,0.002 l -0.01,6.45508 a 0.50005,0.50005 0 0 0 0.50195,0.50195 L 432.96484,277 A 0.50004994,0.50004994 0 0 0 433,277 c 0.0171,0 0.0337,-0.002 0.0508,-0.002 a 0.50005,0.50005 0 0 0 0.008,0 C 436.89154,276.96613 440,273.84031 440,270 c 0,-3.86007 -3.1399,-7 -7,-7 z m 0,1 c 3.3196,0 6,2.68037 6,6 0,3.31963 -2.6804,6 -6,6 l -6.00781,0.008 0.01,-6.00781 a 0.50005,0.50005 0 0 0 0,-0.0215 C 427.01364,266.66895 429.68766,264 433,264 Z" + transform="translate(886,-80.0001)" + id="path22377" + inkscape:connector-curvature="0" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 1314.5,189 a 0.50005,0.50005 0 1 0 0,1 h 4.5 v 4.5 a 0.50005,0.50005 0 1 0 1,0 v -5 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z" + id="path22381" + inkscape:connector-curvature="0" /> + style="display:inline;fill:#ffffff;enable-background:new" + id="g22840" + transform="translate(-1.85367e-6,-20)" + inkscape:label="M-20"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 412,286 c 1.65601,0 3,1.37486 3,3.04688 0,1.67201 -1.34399,3.04687 -3,3.04687 -1.65601,0 -3,-1.37486 -3,-3.04687 C 409,287.37486 410.34399,286 412,286 Z m 0,1 c -1.10534,0 -2,0.90526 -2,2.04688 0,1.14161 0.89466,2.04687 2,2.04687 1.10534,0 2,-0.90526 2,-2.04687 C 414,287.90526 413.10534,287 412,287 Z" + id="circle22836" + inkscape:connector-curvature="0" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 412,283 c -3.5093,0 -6,2.77253 -6,5.91992 V 296 h -0.96094 a 0.50004991,0.50004991 0 1 0 0,1 H 406.5 a 0.50004991,0.50004991 0 0 0 0.5,-0.5 v -7.58008 C 407,286.29196 409.01002,284 412,284 c 2.99076,0 5,2.29197 5,4.91992 V 296.5 a 0.50004991,0.50004991 0 0 0 0.5,0.5 h 1.46094 a 0.50004991,0.50004991 0 1 0 0,-1 H 418 v -7.08008 C 418,285.77251 415.50996,283 412,283 Z" + id="path22838" + inkscape:connector-curvature="0" /> - + id="g135607" + style="display:inline;enable-background:new" + inkscape:label="M-19"> + id="path10449" + d="m 391.5,263.00781 c -0.82054,0 -1.49609,0.67557 -1.49609,1.4961 0,0.82054 0.67555,1.49609 1.49609,1.49609 0.82053,0 1.49609,-0.67555 1.49609,-1.49609 0,-0.82053 -0.67556,-1.4961 -1.49609,-1.4961 z M 397,264 c -0.71373,0 -1.37556,0.3819 -1.73242,1 -0.19053,0.33001 -0.2484,0.68266 -0.25586,0.99609 v 0.0117 h -1.54297 C 393.0093,266.6044 392.2975,267 391.5,267 c -0.79751,0 -1.50931,-0.3956 -1.96875,-0.99219 h -0.0195 -5.01563 c -0.67616,-0.01 -0.67616,1.00956 0,1 h 5.01563 0.49805 l -0.0137,3.28711 c -1.65939,1.40974 -3.24134,2.62162 -3.98828,6.10742 -0.17787,0.67446 0.8602,0.89868 0.97657,0.21094 0.67528,-3.15139 1.9234,-4.08819 3.51757,-5.42773 1.58398,1.34059 2.83152,2.27746 3.51953,5.42968 0.13318,0.66658 1.13544,0.44609 0.97657,-0.21484 -0.76273,-3.49457 -2.35105,-4.70351 -4.00196,-6.11719 l 0.0137,-3.27539 h 2.21679 1.78516 v 0.004 c 0.007,0.31344 0.0653,0.66608 0.25586,0.99609 0.35686,0.61811 1.01869,1 1.73242,1 h 0.5 c 0.67616,0.01 0.67616,-1.00956 0,-1 H 397 c -0.35807,0 -0.6862,-0.1899 -0.86523,-0.5 -0.0738,-0.12789 -0.11835,-0.32209 -0.12305,-0.51953 v -0.96875 c 0.005,-0.19744 0.0492,-0.39164 0.12305,-0.51953 0.17903,-0.3101 0.50716,-0.5 0.86523,-0.5 h 0.5 c 0.67616,0.01 0.67616,-1.00956 0,-1 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> - - - + style="display:inline;fill:#ffffff;enable-background:new" + id="g13692-5-0" + transform="translate(316.007,-315)" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="M-18"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="M 47.5,578 A 0.50005,0.50005 0 0 0 47,578.5 L 46.9922,589 c 0,1.51667 1.219298,3 2.984374,3 1.765077,0 3.015625,-1.475 3.015626,-3 L 53,578.5 A 0.50005,0.50005 0 0 0 52.5,578 Z m 0.5,1 h 4 l -0.0078,10 c 0,0.975 -0.811952,2 -2.015626,2 -1.203673,0 -1.984375,-1.01667 -1.984374,-2 z" + id="path13679-7-1" + inkscape:connector-curvature="0" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 58.492188,586 a 0.50005,0.50005 0 1 0 0,1 H 60 v 4 h -6.257812 a 0.50005,0.50005 0 1 0 0,1 H 60.5 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -5 A 0.50005,0.50005 0 0 0 60.5,586 Z" + id="path13681-7-8" + inkscape:connector-curvature="0" /> + + - + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:export-ydpi="96" + inkscape:export-xdpi="96" + inkscape:export-filename="blender_icons.png" + transform="translate(422.009,-131.007)" + id="g22132" + inkscape:label="M-17"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m -76.509766,394.00586 a 0.50005,0.50005 0 0 0 -0.486328,0.38867 l -3.011718,12.98828 a 0.50005,0.50005 0 0 0 0.486328,0.61328 h 9.974609 a 0.50005,0.50005 0 0 0 0.488281,-0.38476 l 3.03711,-12.99024 a 0.50005,0.50005 0 0 0 -0.488282,-0.61523 z m 0.398438,1 h 3.974609 l -1.154297,5 h -3.978515 z m 5,0 h 3.972656 l -1.169922,5 h -3.955078 z m -6.390625,6 h 3.980469 l -1.382813,5.99023 h -3.986328 z m 5.005859,0 h 3.953125 l -1.40039,5.99023 h -3.933594 z" + id="path22130" + inkscape:connector-curvature="0" /> + + - + style="display:inline;fill:#ffffff;enable-background:new" + id="g22457" + transform="translate(21,-21)" + inkscape:label="M-15"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.5;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 280.5,284 a 0.50004976,0.50004976 0 0 0 -0.5,0.5 v 0.75 7.25 a 0.50004976,0.50004976 0 0 0 0.5,0.5 h 0.5 a 0.50004976,0.50004976 0 0 0 0.2832,-0.0879 l 10.5,-7.25 A 0.50004976,0.50004976 0 0 0 292,285.25 v -0.75 a 0.50004976,0.50004976 0 0 0 -0.5,-0.5 z m 0.5,1 h 9.98242 L 281,291.89258 V 285.25 Z" + id="path22322" + inkscape:connector-curvature="0" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.5;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 285.25,292 a 0.50004976,0.50004976 0 1 0 0,1 H 291 v 0.15625 l -10,3.80859 V 295.25 a 0.50004976,0.50004976 0 1 0 -1,0 v 2.25 a 0.50004976,0.50004976 0 0 0 0.5,0.5 h 0.5 a 0.50004976,0.50004976 0 0 0 0.17773,-0.0332 l 10.5,-4 A 0.50004976,0.50004976 0 0 0 292,293.5 v -1 a 0.50004976,0.50004976 0 0 0 -0.5,-0.5 z" + id="path22334" + inkscape:connector-curvature="0" /> + style="display:inline;fill:#ffffff;enable-background:new" + id="g22444" + transform="translate(-1.85367e-6,-42)" + inkscape:label="M-14"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.5;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 292.5,305 -9.00781,0.008 a 0.50005,0.50005 0 0 0 -0.5,0.50781 l 0.0391,2.49219 1,-0.0156 -0.0312,-1.98438 8,-0.008 -0.006,8.01172 -2,0.0195 0.01,1 2.49414,-0.0234 a 0.50005,0.50005 0 0 0 0.49414,-0.5 L 293,305.5 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z" + id="path22408" + inkscape:connector-curvature="0" /> + id="path22122" + d="m 279,309 v 2 h 2 v -2 z m 2,2 v 2 h 2 v -2 z m 2,0 h 2 v -2 h -2 z m 2,0 v 2 h 2 v -2 z m 2,0 h 2 v -2 h -2 z m 0,2 v 2 h 2 v -2 z m 0,2 h -2 v 2 h 2 z m 0,2 v 2 h 2 v -2 z m -2,0 h -2 v 2 h 2 z m -2,0 v -2 h -2 v 2 z m -2,0 h -2 v 2 h 2 z m 0,-2 v -2 h -2 v 2 z m 2,0 h 2 v -2 h -2 z" + style="display:inline;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;enable-background:new" + inkscape:connector-curvature="0" /> + style="display:inline;fill:#ffffff;enable-background:new" + id="g22406" + transform="translate(21,-21)" + inkscape:label="M-13"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.5;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 240.49805,288 -3.00586,0.008 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 9 a 0.50005,0.50005 0 0 0 0.5,0.5 h 3 a 0.50005,0.50005 0 0 0 0.5,-0.5 L 241,288.5 A 0.50005,0.50005 0 0 0 240.49805,288 Z M 240,289.00195 l -0.008,8.00586 h -2 v -8.00195 z" + id="path22383" + inkscape:connector-curvature="0" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 250.49805,284 -3.00586,0.008 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 9 a 0.50005,0.50005 0 0 0 0.5,0.5 h 3 a 0.50005,0.50005 0 0 0 0.5,-0.5 L 251,284.5 A 0.50005,0.50005 0 0 0 250.49805,284 Z M 250,285.00195 l -0.008,8.00586 h -2 v -8.00195 z M 245.49805,286 l -3.00586,0.008 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 9 a 0.50005,0.50005 0 0 0 0.5,0.5 h 3 a 0.50005,0.50005 0 0 0 0.5,-0.5 L 246,286.5 A 0.50005,0.50005 0 0 0 245.49805,286 Z M 245,287.00195 l -0.008,8.00586 h -2 v -8.00195 z" + id="path22387" + inkscape:connector-curvature="0" /> + style="display:inline;fill:#ffffff;enable-background:new" + transform="matrix(-1,0,0,1,550.993,-21)" + id="g22139" + inkscape:label="M-12"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.5;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 310.5,287 -10.00781,0.008 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 10 a 0.50005,0.50005 0 0 0 0.5,0.5 h 10 a 0.50005,0.50005 0 0 0 0.5,-0.5 L 311,287.5 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z m -0.5,1 -0.008,9.00781 h -9 v -9.00195 z" + id="path22135" + inkscape:connector-curvature="0" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 300.5,284 a 0.50005,0.50005 0 1 0 0,1 h 10 a 0.50005,0.50005 0 1 0 0,-1 z m 12.99219,2.99219 A 0.50005,0.50005 0 0 0 313,287.5 l -0.008,10.00586 a 0.50005,0.50005 0 1 0 1,0.002 L 314,287.5 a 0.50005,0.50005 0 0 0 -0.50781,-0.50781 z" + id="path22141" + inkscape:connector-curvature="0" /> - + transform="translate(-2.85367e-6,-21)" + id="g22228" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="M-11"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.5;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 222.5,284 a 0.50005,0.50005 0 0 1 0.5,0.5 v 6 a 0.50005,0.50005 0 0 1 -0.5,0.5 h -6 a 0.50005,0.50005 0 0 1 -0.5,-0.5 v -6 a 0.50005,0.50005 0 0 1 0.5,-0.5 z m -0.5,1 h -5 v 5 h 5 z" + id="rect22230" + inkscape:connector-curvature="0" /> - - - - - + id="g16957-2" + transform="rotate(90,380.0015,176.0065)" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="M-10"> + + + sodipodi:nodetypes="cccccccccccccccccccccccccc" /> + + - - - - - + id="g21555" + transform="translate(-1.85367e-6,-21)" + inkscape:label="M-8"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 160.50391,263.00391 c -0.82069,0 -1.4961,0.67734 -1.4961,1.49804 1e-5,0.64713 0.422,1.19917 1.00196,1.40625 -0.004,0.17741 -0.01,0.3625 -0.01,0.58594 0,0.8575 0.30572,1.55064 0.73242,2.08984 0.42671,0.53921 0.958,0.94374 1.45313,1.34375 0.99025,0.80003 1.81445,1.48075 1.81445,3.0293 0,1.03964 -0.41467,1.75679 -1.05859,2.26367 C 162.29747,275.72759 161.4016,276 160.5,276 c -0.9016,0 -1.79749,-0.27241 -2.44141,-0.7793 C 157.41467,274.71382 157,273.99667 157,272.95703 v -1.25 l 1.14648,1.14649 c 0.47127,0.49023 1.19727,-0.23577 0.70704,-0.70704 l -2,-2 c -0.315,-0.31479 -0.85335,-0.0918 -0.85352,0.35352 v 2.45703 c 0,1.32194 0.58533,2.37688 1.44141,3.05078 0.85608,0.6739 1.96019,0.99219 3.05859,0.99219 1.0984,0 2.20251,-0.31829 3.05859,-0.99219 0.85608,-0.6739 1.44141,-1.72884 1.44141,-3.05078 0,-1.93167 -1.1758,-2.99085 -2.18555,-3.80664 -0.50487,-0.40789 -0.97358,-0.77897 -1.29687,-1.1875 C 161.19428,267.55436 161,267.12816 161,266.49414 c 0,-0.20716 0.005,-0.40692 0.01,-0.58984 0.57393,-0.21035 0.99023,-0.75955 0.99023,-1.40235 0,-0.8207 -0.67541,-1.49804 -1.49609,-1.49804 z m 0,1 c 0.27921,0 0.49609,0.21676 0.49609,0.49804 0,0.28129 -0.21688,0.49805 -0.49609,0.49805 -0.27922,0 -0.4961,-0.21676 -0.4961,-0.49805 0,-0.28128 0.21688,-0.49804 0.4961,-0.49804 z" + transform="translate(1.85367e-6,21)" + id="path16949-6-5" + inkscape:connector-curvature="0" /> - + style="display:inline;fill:#ffffff;enable-background:new" + id="g22599" + transform="translate(-1.85367e-6,-22)" + inkscape:label="M-7"> - - - + style="display:inline;fill:#ffffff;enable-background:new" + id="g22589" + inkscape:label="M-6"> - - - - + transform="translate(-336,-84)" + id="g28475" + style="display:inline;enable-background:new" + inkscape:label="M-5"> + + - - + id="g26390" + inkscape:label="M-4"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.5;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 74.492188,262.99219 a 0.50005,0.50005 0 0 0 -0.09961,0.0117 l -4.90039,0.004 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 5 5 a 0.50005,0.50005 0 1 0 1,0 V 269 H 74.5 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -4.49805 L 79.5,264 a 0.50005,0.50005 0 1 0 0,-1 l -4.892578,0.004 a 0.50005,0.50005 0 0 0 -0.115234,-0.0117 z M 74,264.00391 V 268 h -4.007812 v -3.99414 z" + transform="translate(168,21)" + id="path23021" + inkscape:connector-curvature="0" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 250.48438,284 a 0.50005,0.50005 0 0 0 -0.3379,0.14648 l -13.00781,13.00782 a 0.50005,0.50005 0 0 0 0.35352,0.85351 L 250.5,298 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -13 A 0.50005,0.50005 0 0 0 250.48438,284 Z M 250,285.70703 V 297 l -11.30078,0.008 z" + id="path23044" + inkscape:connector-curvature="0" /> + id="g22221" + transform="translate(21,-21)" + inkscape:label="M-3"> + sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccc" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.5;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 40.5,284 a 0.50005,0.50005 0 0 1 0.5,0.5 v 3 a 0.50005,0.50005 0 0 1 -0.5,0.5 h -5 A 0.50005,0.50005 0 0 1 35,287.5 v -3 a 0.50005,0.50005 0 0 1 0.5,-0.5 z m -0.5,1 h -4 v 2 h 4 z" + id="rect22200" + inkscape:connector-curvature="0" /> - - - + id="g21947" + transform="matrix(-1,0,0,1,67.9866,-21)" + inkscape:label="M-2"> + id="circle21927" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 27.460938,284.01172 a 0.50005,0.50005 0 1 0 0.04297,0.99805 c 3.348416,-0.1388 6.600566,1.12874 8.972656,3.49609 2.372091,2.36735 3.645762,5.61811 3.513672,8.9668 a 0.50038206,0.50038206 0 1 0 1,0.0391 c 0.143077,-3.62723 -1.237239,-7.14863 -3.80664,-9.71289 -2.569402,-2.56427 -6.09572,-3.93745 -9.722656,-3.78711 z m -0.0332,4.00586 a 0.50005,0.50005 0 1 0 0.0625,0.99804 c 2.281784,-0.14542 4.516597,0.69332 6.140625,2.30274 1.62403,1.60942 2.482218,3.83613 2.357422,6.11914 a 0.50005,0.50005 0 1 0 0.998047,0.0547 c 0.140364,-2.5678 -0.823774,-5.07459 -2.65039,-6.88477 -1.826617,-1.81018 -4.341781,-2.75341 -6.908204,-2.58984 z m 0.03711,4.01172 a 0.50005,0.50005 0 1 0 0.107422,0.99414 c 1.199607,-0.12917 2.392346,0.29002 3.248046,1.14062 0.855701,0.8506 1.282148,2.03988 1.160157,3.24024 a 0.50005,0.50005 0 1 0 0.99414,0.10156 c 0.152408,-1.49965 -0.380162,-2.99006 -1.449218,-4.05274 -1.069056,-1.06268 -2.561836,-1.5852 -4.060547,-1.42382 z M 28,296 c -0.546362,0 -1,0.45364 -1,1 0,0.54636 0.453638,1 1,1 0.546362,0 1,-0.45364 1,-1 0,-0.54636 -0.453638,-1 -1,-1 z" + inkscape:connector-curvature="0" /> - + id="g13852" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + style="display:inline;fill:#ffffff;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;enable-background:new" + inkscape:label="M-1"> + + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 14.505859,263.01562 a 0.55005501,0.55005501 0 0 0 -0.05664,0.004 c -1.480032,0.16258 -2.81124,0.9768 -3.632813,2.21875 -0.754587,1.14068 -0.9438173,2.54615 -0.65039,3.875 l -3.5234379,3.52149 c -0.4164576,0.37981 -0.6354253,0.95498 -0.6601562,1.61328 -0.024627,0.65553 0.1896767,1.40646 0.7832031,2 0.5945619,0.59455 1.3481437,0.80114 1.9980469,0.76953 0.6519363,-0.0317 1.2112985,-0.25543 1.5917971,-0.63672 l 3.533203,-3.54297 c 1.328363,0.29148 2.734045,0.0991 3.873047,-0.6543 1.240498,-0.82051 2.05459,-2.151 2.21875,-3.6289 a 0.55005501,0.55005501 0 0 0 -0.160157,-0.45117 l -0.943359,-0.92969 a 0.55005501,0.55005501 0 0 0 -0.775391,0.002 l -1.80664,1.79883 h -0.589844 l -1.679687,-1.67969 v -0.58984 l 1.804687,-1.80078 a 0.55005501,0.55005501 0 0 0 0.0039,-0.77539 l -0.93164,-0.94727 a 0.55005501,0.55005501 0 0 0 -0.396485,-0.16602 z m -0.15625,1.17383 0.316407,0.32227 -1.580078,1.57617 a 0.55005501,0.55005501 0 0 0 -0.16211,0.39063 l 0.002,1.04687 a 0.55005501,0.55005501 0 0 0 0.16211,0.38672 l 2.001953,2.00195 a 0.55005501,0.55005501 0 0 0 0.388672,0.16211 l 1.046875,-0.002 a 0.55005501,0.55005501 0 0 0 0.386718,-0.16016 l 1.580079,-1.57422 0.318359,0.31446 c -0.183326,1.05859 -0.751902,2.01444 -1.654297,2.61132 -0.970627,0.64202 -2.219467,0.82961 -3.330078,0.49219 a 0.55005501,0.55005501 0 0 0 -0.548828,0.13672 l -3.699219,3.70899 c -0.1021327,0.10234 -0.4754005,0.29532 -0.8691406,0.31445 -0.395773,0.0192 -0.8021096,-0.0834 -1.1660156,-0.44727 -0.3650748,-0.36508 -0.4778317,-0.78393 -0.4628907,-1.18164 0.014838,-0.39496 0.2099663,-0.75541 0.3046875,-0.84179 a 0.55005501,0.55005501 0 0 0 0.017578,-0.0176 l 3.7070314,-3.70508 a 0.55005501,0.55005501 0 0 0 0.136719,-0.54883 c -0.339386,-1.11269 -0.154519,-2.36033 0.488281,-3.33203 0.59791,-0.90384 1.555081,-1.47203 2.615234,-1.6543 z" + id="path11179" + inkscape:connector-curvature="0" /> + id="g24393-7-4" + transform="matrix(-1,0,0,1,1912.004,-92)" + inkscape:label="L-26"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 1367.5039,334.00195 a 0.50004997,0.50004997 0 0 0 -0.5,0.5 v 9.99219 a 0.50004997,0.50004997 0 0 0 0.5,0.5 h 9.9961 a 0.50004997,0.50004997 0 0 0 0.5,-0.5 v -9.99219 a 0.50004997,0.50004997 0 0 0 -0.5,-0.5 z m 0.5,1 H 1377 v 8.99219 h -8.9961 z" + id="path24368-5-5" + inkscape:connector-curvature="0" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.7;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 1376,338 -4.4961,0.002 a 0.50004997,0.50004997 0 0 0 -0.5,0.5 V 343 h 1 v -3.99805 L 1376,339 Z m 3,0 v 1 l 1,0.002 v 7.99024 h -7.9961 V 346 h -1 v 1.49219 a 0.50004997,0.50004997 0 0 0 0.5,0.5 h 8.9961 a 0.50004997,0.50004997 0 0 0 0.5,-0.5 v -8.99024 a 0.50004997,0.50004997 0 0 0 -0.5,-0.5 z" + id="path24388-3-2" + inkscape:connector-curvature="0" /> - - - + id="g18316" + transform="translate(21,-63)" + inkscape:label="L-25"> - - + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 496,305 c -3.86007,0 -7,3.13993 -7,7 0,3.86007 3.13993,7 7,7 3.86007,0 7,-3.13993 7,-7 0,-3.86007 -3.13993,-7 -7,-7 z m 0,1 c 3.31963,0 6,2.68037 6,6 0,3.31963 -2.68037,6 -6,6 -3.31963,0 -6,-2.68037 -6,-6 0,-3.31963 2.68037,-6 6,-6 z" + id="circle17690" + inkscape:connector-curvature="0" /> + + id="g23689-9" + transform="rotate(180,958,414.9975)" + inkscape:label="L-24"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 1419.4941,588.00195 c 4.1402,0 7.5079,-3.36769 7.5079,-7.50781 a 0.50004997,0.50004997 0 1 0 -1,0 c 0,3.59968 -2.9082,6.50781 -6.5079,6.50781 a 0.50004997,0.50004997 0 1 0 0,1 z" + id="path23647-2" + inkscape:connector-curvature="0" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 1416.4941,585.10156 c 4.1942,0 7.6075,-3.41326 7.6075,-7.60742 a 0.6006,0.6006 0 1 0 -1.2012,0 c 0,3.54564 -2.8606,6.40625 -6.4063,6.40625 a 0.600585,0.600585 0 1 0 0,1.20117 z" + id="path23668-9" + inkscape:connector-curvature="0" /> + id="g23935-0" + transform="translate(-924,-332)" + inkscape:label="L-23"> - + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 1405.498,573.99805 c -7.4498,0 -13.5,6.0501 -13.5,13.5 a 0.50004997,0.50004997 0 1 0 1,0 c 0,-6.90946 5.5906,-12.5 12.5,-12.5 a 0.50004997,0.50004997 0 1 0 0,-1 z" + id="path23706-8" + inkscape:connector-curvature="0" /> - + style="display:inline;fill:#ffffff;enable-background:new" + transform="rotate(180,947.5,425.5)" + id="g24104-2" + inkscape:label="L-22"> + id="g24094-5" + style="opacity:0.5;fill:#ffffff"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 449,242 c -1.0986,0 -2,0.90135 -2,2 0,0.73315 0.40645,1.37054 1,1.71875 V 250 h 1 v -4 c 1.0986,0 2,-0.90135 2,-2 h 4 v -1 h -4.28125 c -0.34822,-0.59355 -0.98563,-1 -1.71875,-1 z m 0,1 c 0.5582,0 1,0.44179 1,1 0,0.55821 -0.4418,1 -1,1 -0.5582,0 -1,-0.44179 -1,-1 0,-0.55821 0.4418,-1 1,-1 z" + transform="rotate(180,947.5,425.5)" + id="circle24085-2" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ssccccccccssssss" /> + + - + id="g23993-7" + transform="matrix(1,0,0,-1,-1008,851.007)" + inkscape:label="L-21"> + + + - - - - - + id="g143023" + style="display:inline;enable-background:new" + inkscape:label="L-20"> + id="g10865-1-4" + transform="translate(209.993,-231)" + inkscape:label="L-19"> + + + - - + id="g22949" + transform="matrix(1,0,0,-1,168,540.009)" + inkscape:label="L-18"> + + + + + + + + + + + + + + + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.5;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 347.75,247 -5.25781,0.002 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 8 a 0.50005,0.50005 0 0 0 0.5,0.5 h 8 a 0.50005,0.50005 0 0 0 0.5,-0.49804 L 351,251.50195 a 0.50005,0.50005 0 1 0 -1,-0.004 l -0.008,3.5039 h -7 v -7 L 347.75,248 a 0.50005,0.50005 0 1 0 0,-1 z" + id="path22405" + inkscape:connector-curvature="0" /> + transform="translate(22,18)" + id="g22745" + inkscape:label="L-16"> - + id="g22756" + transform="translate(20,-60)" + style="fill:#ffffff"> + + + + - - + id="g22891" + transform="translate(444,-274)" + inkscape:label="L-15"> + sodipodi:nodetypes="cccccccccccccccccccccccccccccc" /> + + + + transform="matrix(-1,0,0,1,508.993,-42.0001)" + id="g23159" + style="fill:#ffffff"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 286.49219,242 a 0.50005,0.50005 0 0 0 -0.5,0.5 L 286,255.50781 a 0.50005,0.50005 0 0 0 0.5,0.5 h 6 a 0.50005,0.50005 0 0 0 0.5,-0.5 V 242.5 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z m 0.5,1 h 4.72656 l -4.7207,10.22852 z M 292,244.77344 v 10.23437 h -4.72266 z" + transform="matrix(-1,0,0,1,487.993,42.0001)" + id="path23171" + inkscape:connector-curvature="0" /> + - + id="g22579" + transform="matrix(-1,0,0,1,572,-63)" + inkscape:label="L-13"> + style="opacity:1;fill:#ffffff" + id="g22529"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 268.5,242 c -1.88693,0 -3.42464,1.51084 -3.48828,3.38281 -0.009,0.0385 -0.0124,0.0778 -0.0117,0.11719 -4.1e-4,0.0117 -4.1e-4,0.0234 0,0.0352 V 249 h -3.5 c -0.0381,-4.2e-4 -0.0761,0.004 -0.11328,0.0117 C 259.5129,249.07334 258,250.61174 258,252.5 c 0,1.88693 1.51084,3.42464 3.38281,3.48828 0.0385,0.009 0.0778,0.0124 0.11719,0.0117 h 7 c 0.92807,0 1.81837,-0.36915 2.47461,-1.02539 C 271.63085,254.31837 272,253.42807 272,252.5 v -7 c 7.2e-4,-0.0394 -0.003,-0.0787 -0.0117,-0.11719 C 271.92462,243.51084 270.38693,242 268.5,242 Z m 0,1 c 1.38663,0 2.5,1.11337 2.5,2.5 -4.1e-4,0.0117 -4.1e-4,0.0234 0,0.0352 V 252.5 c 0,0.66323 -0.26345,1.2986 -0.73242,1.76758 C 269.7986,254.73655 269.16323,255 268.5,255 h -7 c -1.38663,0 -2.5,-1.11337 -2.5,-2.5 0,-1.38663 1.11337,-2.5 2.5,-2.5 h 3.75 c 0.1326,-2e-5 0.25976,-0.0527 0.35352,-0.14648 l 0.25,-0.25 c 0.0938,-0.0938 0.14646,-0.22092 0.14648,-0.35352 v -3.75 c 0,-1.38663 1.11337,-2.5 2.5,-2.5 z" + transform="matrix(-1,0,0,1,572,63)" + id="path22488" + inkscape:connector-curvature="0" /> + + inkscape:label="L-12"> + + + - - - - + id="g22083" + transform="translate(-1.85367e-6,-42.0001)" + inkscape:label="L-10"> + + + + + + + + + id="g22339" + transform="translate(0,10)" + style="fill:#ffffff"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 154,242 v 2 h -1 v 4 h 1 v 2 h 2 v -8 z m 10,0 v 8 h 2 v -2 h 1 v -4 h -1 v -2 z m -7,3 v 2 h 6 v -2 z" + transform="translate(26,433)" + id="path22321" + inkscape:connector-curvature="0" /> + + - - + style="display:inline;fill:#ffffff;enable-background:new" + id="g21692" + transform="translate(-1.85367e-6,-42)" + inkscape:label="L-7"> + + + + + + + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.5;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="M 153.49219,289.73828 A 0.50005,0.50005 0 0 0 153,290.24414 l -0.008,7.26172 a 0.50005,0.50005 0 0 0 0.50195,0.50195 l 7.25586,-0.0137 a 0.50005,0.50005 0 1 0 0,-1 l -6.75781,0.0137 0.008,-6.76172 a 0.50005,0.50005 0 0 0 -0.50781,-0.50781 z" + id="path21430-0" + inkscape:connector-curvature="0" /> + + + + + + + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 300.5,326 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 13 a 0.50005,0.50005 0 0 0 0.5,0.5 h 13 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -13 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z m 0.5,1 h 12 v 12 h -12 z" + id="path22548" + inkscape:connector-curvature="0" /> + - + style="display:inline;fill:#ffffff;enable-background:new" + id="g22284" + transform="translate(-1.85367e-6,-42)" + inkscape:label="L-3"> - - + style="display:inline;fill:#ffffff;enable-background:new" + id="g22356" + transform="translate(-336,-63)" + inkscape:label="L-2"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 366.5,305 a 0.50005,0.50005 0 0 0 -0.35352,0.14648 l -3,3 A 0.50005,0.50005 0 0 0 363.5,309 h 6 a 0.50005,0.50005 0 0 0 0.35352,-0.14648 l 3,-3 A 0.50005,0.50005 0 0 0 372.5,305 Z m 0.20703,1 h 4.58594 l -2,2 h -4.58594 z m 9.77735,3 a 0.50005,0.50005 0 0 0 -0.3379,0.14648 l -3,3 A 0.50005,0.50005 0 0 0 373,312.5 v 6 a 0.50005,0.50005 0 0 0 0.85352,0.35352 l 3,-3 A 0.50005,0.50005 0 0 0 377,315.5 v -6 A 0.50005,0.50005 0 0 0 376.48438,309 Z M 376,310.70703 v 4.58594 l -2,2 v -4.58594 z M 369.5,312 l -6.00781,0.008 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 6 a 0.50005,0.50005 0 0 0 0.5,0.5 h 6 a 0.50005,0.50005 0 0 0 0.5,-0.5 L 370,312.5 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z m -0.5,1 -0.008,5.00781 h -5 v -5.00195 z" + id="path22324" + inkscape:connector-curvature="0" /> + style="display:inline;fill:#ffffff;enable-background:new" + id="g23463" + inkscape:label="L-1"> + + + style="display:inline;fill:#ffffff;enable-background:new" + id="g18539-5" + transform="translate(-699.95,-1104.95)" + inkscape:label="K-26"> + sodipodi:nodetypes="ccccc" /> + - + inkscape:export-ydpi="96" + inkscape:export-xdpi="96" + inkscape:export-filename="blender_icons.png" + transform="matrix(-1,0,0,1,970.039,-190)" + id="g19901" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="K-25"> + inkscape:connector-curvature="0" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 516.5,223 c -3.27784,-9.4e-4 -5.036,2.7211 -6.36328,4.16211 -0.17644,0.19146 -0.17644,0.48627 0,0.67773 1.32749,1.44124 3.08593,4.16011 6.36328,4.16211 3.27801,0.002 5.03607,-2.72118 6.36328,-4.16211 0.17644,-0.19146 0.17644,-0.48627 0,-0.67773 C 521.53587,225.72096 519.77754,223.00094 516.5,223 Z m 0,1 a 3.4999952,3.4999933 0 0 1 3.5,3.5 3.4999952,3.4999933 0 0 1 -3.5,3.5 3.4999952,3.4999933 0 0 1 -3.5,-3.5 3.4999952,3.4999933 0 0 1 3.5,-3.5 z m 0,2 a 1.4999952,1.4999944 0 0 0 -1.5,1.5 1.4999952,1.4999944 0 0 0 1.5,1.5 1.4999952,1.4999944 0 0 0 1.5,-1.5 1.4999952,1.4999944 0 0 0 -1.5,-1.5 z" + transform="matrix(-1,0,0,1,970.039,190)" + id="path19890" /> - + id="g150601" + style="display:inline;enable-background:new" + inkscape:label="K-24"> + id="path19886" + d="m 489.50195,227 a 0.50005,0.50005 0 0 0 -0.33203,0.84375 c 1.3239,1.43817 3.0824,4.1582 6.3457,4.1582 3.26331,0 5.02376,-2.72003 6.34766,-4.1582 a 0.50037481,0.50037481 0 1 0 -0.73633,-0.67773 c -1.43556,1.55946 -2.90607,3.83593 -5.61133,3.83593 -2.70525,0 -4.17576,-2.27647 -5.61132,-3.83593 A 0.50005,0.50005 0 0 0 489.50195,227 Z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + + + d="m 164.50781,224 a 0.50004997,0.50004997 0 0 1 0.43164,0.27148 c 1.44011,2.71955 1.43779,6.74164 -0.008,9.45899 a 0.50022794,0.50022794 0 1 1 -0.88281,-0.4707 c 1.24398,-2.33836 1.24519,-6.17912 0.006,-8.51954 A 0.50004997,0.50004997 0 0 1 164.50781,224 Z m -1.99609,1.99805 a 0.50004997,0.50004997 0 0 1 0.49219,0.50586 v 4.99218 a 0.50004997,0.50004997 0 1 1 -1,0 v -4.99218 a 0.50004997,0.50004997 0 0 1 0.50781,-0.50586 z M 159.25,223 c -0.1326,2e-5 -0.25976,0.0527 -0.35352,0.14648 L 156.04297,226 H 154.5 c -0.27613,3e-5 -0.49997,0.22387 -0.5,0.5 v 5 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 1.54297 l 2.85351,2.85352 c 0.0938,0.0938 0.22092,0.14646 0.35352,0.14648 h 0.25 c 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 V 223.78125 223.5 c -3e-5,-0.27613 -0.22387,-0.49997 -0.5,-0.5 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + id="path13628-2" /> + + + + transform="translate(84,-84)" + inkscape:label="K-21"> + transform="translate(-18,-64)" + inkscape:label="K-20"> + style="display:inline;fill:#ffffff;enable-background:new" + id="g5270-3" + transform="translate(497.995,-311.993)" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="K-19"> - - + sodipodi:nodetypes="ccccc" /> + + + + + + - - + id="g150574" + style="display:inline;enable-background:new;stroke-width:1.00038044;stroke-dasharray:none" + transform="matrix(0.77770413,0,0,0.77770413,82.359643,50.7947)" + inkscape:label="K-18"> + transform="matrix(1.285836,0,0,1.285836,-105.901,-65.313656)" /> - - - - - - - - - - - - - - - - - + id="g150577" + style="display:inline;enable-background:new" + inkscape:label="K-17"> + sodipodi:nodetypes="cccccccccc" /> - + id="g150580" + style="display:inline;enable-background:new" + inkscape:label="K-16"> - + inkscape:label="K-15"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 307.48242,226.75 c -0.15739,0.006 -0.30286,0.0854 -0.39258,0.21484 l -4,5.75 c -0.0583,0.0837 -0.0897,0.18317 -0.0898,0.28516 v 0.5 c 3e-5,0.27613 0.22387,0.49997 0.5,0.5 h 8 c 0.27999,2e-5 0.50544,-0.22983 0.5,-0.50977 l -0.01,-0.47461 c -0.002,-0.0985 -0.0321,-0.19425 -0.0879,-0.27539 l -3.99023,-5.77539 c -0.0971,-0.14016 -0.25903,-0.22114 -0.42945,-0.21484 z" + id="path23382-9" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccccccc" /> - + inkscape:label="K-14"> - - - - + id="g150589" + style="display:inline;enable-background:new" + inkscape:label="K-13"> + + id="g150586" + style="display:inline;enable-background:new" + inkscape:label="K-12"> - + sodipodi:nodetypes="cccccccccc" /> + id="g150672" + style="display:inline;enable-background:new" + inkscape:label="K-11"> + + inkscape:export-ydpi="96" + inkscape:export-xdpi="96" + inkscape:export-filename="blender_icons.png" + id="g13384" + transform="translate(-21,-105)" + style="display:inline;opacity:0.6;fill:#ffffff;enable-background:new"> + id="g13382" + transform="translate(1)" + style="fill:#ffffff"> + sodipodi:nodetypes="cccccccccccccccccccc" /> + sodipodi:nodetypes="ccccccccc" /> - - - - + id="g6085" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="K-10"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 199.5,221.00391 c -0.67616,-0.01 -0.67616,1.00956 0,1 h 1.5 v 1.08789 c -2.83241,0.47934 -5.00001,2.94243 -5,5.9082 0,3.30787 2.69334,6 6.00195,6 3.30861,0 6.00196,-2.69214 6.00196,-6 0,-1.22417 -0.37149,-2.36223 -1.00391,-3.3125 a 0.50005,0.50005 0 0 0 0.10352,-0.0801 l 1.75,-1.75 a 0.50005,0.50005 0 0 0 -0.36329,-0.85937 0.50005,0.50005 0 0 0 -0.34375,0.15234 l -1.75,1.75 a 0.50005,0.50005 0 0 0 -0.0117,0.0137 C 205.28833,223.73959 203.73131,223 202.00195,223 c -6.5e-4,0 -0.001,0 -0.002,0 v -0.99609 h 1.5 c 0.67616,0.01 0.67616,-1.00956 0,-1 z M 202.00195,224 c 2.76833,0 5.00196,2.23273 5.00196,5 0,2.76726 -2.23363,5 -5.00196,5 C 199.23362,234 197,231.76727 197,229 c -1e-5,-2.76727 2.23362,-5 5.00195,-5 z" + id="path17270" + inkscape:connector-curvature="0" /> + - - - - - - + id="g150607" + style="display:inline;enable-background:new" + inkscape:label="K-9"> + + style="display:inline;fill:#ffffff;enable-background:new" + id="g12135" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="K-8"> - - - + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 158.5,222 a 0.50005,0.50005 0 0 0 -0.35352,0.14648 L 155.29297,225 H 153.5 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 5 a 0.50005,0.50005 0 0 0 0.5,0.5 h 1.79297 l 2.85351,2.85352 A 0.50005,0.50005 0 0 0 158.5,234 h 1 a 0.50005,0.50005 0 0 0 0.5,-0.5 V 222.78125 222.5 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z m 0.20703,1 H 159 v 10 h -0.29297 l -2.85351,-2.85352 A 0.50005,0.50005 0 0 0 155.5,230 H 154 v -4 h 1.5 a 0.50005,0.50005 0 0 0 0.35352,-0.14648 z" + id="path12129" + inkscape:connector-curvature="0" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 165.4668,223.01562 a 0.50005,0.50005 0 0 0 -0.42188,0.72071 c 1.27607,2.70648 1.27309,5.84072 -0.008,8.54492 a 0.50017783,0.50017783 0 1 0 0.9043,0.42773 c 1.40867,-2.97397 1.41118,-6.42391 0.008,-9.40039 a 0.50005,0.50005 0 0 0 -0.48242,-0.29297 z m -2.86133,0.9375 a 0.50005,0.50005 0 0 0 -0.43359,0.74219 c 1.10287,2.06013 1.10465,4.5334 0.006,6.59571 a 0.50023236,0.50023236 0 1 0 0.88282,0.4707 c 1.25518,-2.35583 1.25203,-5.18378 -0.008,-7.53711 a 0.50005,0.50005 0 0 0 -0.44726,-0.27149 z" + id="path12133" + inkscape:connector-curvature="0" /> + + + id="path27528" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 132.5104,224 c -0.27994,-0.01 -0.50979,0.22 -0.50977,0.5 v 7 c -0.001,0.4307 0.5065,0.6613 0.83008,0.377 l 4,-3.5 c 0.22872,-0.1993 0.22872,-0.5547 0,-0.754 l -4,-3.5 c -0.0889,-0.078 -0.20238,-0.1211 -0.32031,-0.123 z m 9.96289,-1 c -0.13302,0.01 -0.25746,0.068 -0.34571,0.168 l -4,4.5 c -0.16816,0.1894 -0.16816,0.4746 0,0.664 l 4,4.5 c 0.19883,0.2227 0.54727,0.2227 0.7461,0 l 4,-4.5 c 0.16816,-0.1894 0.16816,-0.4746 0,-0.664 l -4,-4.5 c -0.10092,-0.114 -0.24831,-0.1759 -0.40039,-0.168 z" /> + id="g150616" + style="display:inline;enable-background:new" + inkscape:label="K-6"> + d="m 124.49003,224 c 0.27994,-0.01 0.50979,0.22 0.50977,0.5 v 7 c 0.001,0.4307 -0.5065,0.6613 -0.83008,0.377 l -4,-3.5 c -0.22872,-0.1993 -0.22872,-0.5547 0,-0.754 l 4,-3.5 c 0.0889,-0.078 0.20238,-0.1211 0.32031,-0.123 z m -9.96289,-1 c 0.13302,0.01 0.25746,0.068 0.34571,0.168 l 4,4.5 c 0.16816,0.1894 0.16816,0.4746 0,0.664 l -4,4.5 c -0.19883,0.2227 -0.54727,0.2227 -0.7461,0 l -4,-4.5 c -0.16816,-0.1894 -0.16816,-0.4746 0,-0.664 l 4,-4.5 c 0.10092,-0.114 0.24831,-0.1759 0.40039,-0.168 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + id="path9201" + inkscape:connector-curvature="0" /> + + + d="m -1207,502.79592 v 14.44898 h 2 v -14.44898 z m 6,0 v 14.44898 h 2 v -14.44898 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.19461;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + id="path8013-3" /> - + id="g150619" + style="display:inline;enable-background:new" + inkscape:label="K-4"> + id="g150613" + style="display:inline;enable-background:new" + inkscape:label="K-3"> + id="path27544" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 51.506493,224 c -0.27842,0 -0.50585,0.2216 -0.50586,0.5 v 7 c -6.1e-4,0.4051 0.45544,0.6427 0.78711,0.4102 l 5,-3.5 c 0.28542,-0.199 0.28542,-0.6214 0,-0.8204 l -5,-3.5 c -0.0826,-0.058 -0.1806,-0.089 -0.28125,-0.09 z m 6.49414,-1 v 10 h 2 v -10 z" /> + + + id="path27639" + d="m 29.959614,221.99997 c -0.53618,0.022 -0.95938,0.4634 -0.95898,1 v 10 c 7.2e-4,0.7847 0.8635,1.2629 1.5293,0.8477 l 8,-5 c 0.625909,-0.3918 0.625909,-1.3036 0,-1.6954 l -8,-5 c -0.17045,-0.107 -0.36922,-0.1601 -0.57032,-0.1523 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + + - + style="display:inline;fill:#ffffff;enable-background:new" + id="g14253" + inkscape:label="J-22"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 447.5,200 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 4 a 0.50005,0.50005 0 1 0 1,0 V 201 h 3.5 a 0.50005,0.50005 0 1 0 0,-1 z m 9.00781,0 a 0.50005,0.50005 0 1 0 0,1 h 3.5 v 3.5 a 0.50005,0.50005 0 1 0 1,0 v -4 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z m -9.01562,9 A 0.50005,0.50005 0 0 0 447,209.50781 v 4 a 0.50005,0.50005 0 0 0 0.5,0.5 h 4 a 0.50005,0.50005 0 1 0 0,-1 H 448 v -3.5 A 0.50005,0.50005 0 0 0 447.49219,209 Z M 460.5,209 a 0.50005,0.50005 0 0 0 -0.49219,0.50781 v 3.5 h -3.5 a 0.50005,0.50005 0 1 0 0,1 h 4 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -4 A 0.50005,0.50005 0 0 0 460.5,209 Z" + id="path13927-5" + inkscape:connector-curvature="0" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 449.5,203 a 0.50005,0.50005 0 1 0 0,1 h 1 c 0.39167,0 0.74862,0.27445 1.13672,0.86719 0.3881,0.59273 0.74407,1.4462 1.08594,2.3164 0.34186,0.8702 0.67067,1.75605 1.06836,2.4668 0.19884,0.35538 0.41332,0.66981 0.68554,0.91797 0.27222,0.24816 0.62761,0.43164 1.02344,0.43164 0.625,0 1.13349,-0.27613 1.44727,-0.64648 0.31377,-0.37036 0.47247,-0.79704 0.61523,-1.17774 0.14276,-0.3807 0.2723,-0.71757 0.41406,-0.91016 C 458.11833,208.07304 458.20536,208 458.5,208 a 0.50005,0.50005 0 1 0 0,-1 c -0.58036,0 -1.05583,0.30196 -1.32812,0.67188 -0.2723,0.36991 -0.40839,0.78304 -0.54688,1.15234 -0.13849,0.3693 -0.27667,0.69262 -0.43945,0.88476 C 456.02276,209.90113 455.875,210 455.5,210 c -0.10417,0 -0.20191,-0.0353 -0.34961,-0.16992 -0.1477,-0.13465 -0.31994,-0.3671 -0.48828,-0.66797 -0.33669,-0.60175 -0.66413,-1.4659 -1.00977,-2.3457 -0.34563,-0.8798 -0.70841,-1.77633 -1.17968,-2.4961 C 452.00138,203.60055 451.35833,203 450.5,203 Z" + id="path14224" + inkscape:connector-curvature="0" /> - + id="g158071" + style="display:inline;enable-background:new" + inkscape:label="J-21"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 439,201 c -1.59692,0 -2.85915,0.78757 -3.86133,1.88086 -1.00218,1.09329 -1.79297,2.49805 -2.57422,3.87305 -0.78125,1.375 -1.55296,2.72024 -2.4414,3.68945 C 429.2346,211.41257 428.27808,212 427,212 h -0.5 a 0.50005,0.50005 0 1 0 0,1 h 0.5 c 1.59692,0 2.85915,-0.78757 3.86133,-1.88086 1.00218,-1.09329 1.79297,-2.49805 2.57422,-3.87305 0.78125,-1.375 1.55296,-2.72024 2.4414,-3.68945 C 436.7654,202.58743 437.72192,202 439,202 h 0.5 a 0.50005,0.50005 0 1 0 0,-1 z" + id="path14305" + inkscape:connector-curvature="0" /> + id="g158086" + style="display:inline;enable-background:new" + inkscape:label="J-20"> + + - - - + id="g158080" + style="display:inline;enable-background:new" + inkscape:label="J-18"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 375.5,201 a 0.50005,0.50005 0 0 0 -0.47852,0.35547 c -0.97912,3.26329 -1.65795,6.01658 -2.41406,7.89453 -0.37805,0.93897 -0.77714,1.65307 -1.20117,2.10156 C 370.98222,211.80006 370.56872,212 370,212 c -0.81989,0 -1.77194,-0.85368 -2.74219,-1.84961 -0.48512,-0.49796 -0.97309,-1.01146 -1.49609,-1.41992 C 365.23871,208.32201 364.66174,208 364,208 h -0.5 a 0.50005,0.50005 0 1 0 0,1 h 0.5 c 0.32338,0 0.70916,0.17799 1.14648,0.51953 0.43732,0.34154 0.9074,0.82804 1.39649,1.33008 C 367.52115,211.85368 368.58382,213 370,213 c 0.83577,0 1.56694,-0.36242 2.13281,-0.96094 0.56587,-0.59851 1.00027,-1.41738 1.40235,-2.41601 0.7683,-1.90825 1.43134,-4.57357 2.33398,-7.62305 H 376.5 a 0.50005,0.50005 0 1 0 0,-1 z" + id="path16580-7" + inkscape:connector-curvature="0" /> - + id="g158077" + style="display:inline;enable-background:new" + inkscape:label="J-17"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 342.45117,201 a 0.50059574,0.50059574 0 0 0 0.0488,1 c 0.0215,2e-5 0.18225,-1.7e-4 0.33398,0 0.0105,0.42607 0.0502,2.52232 0.33594,5.05664 0.15671,1.39002 0.37504,2.78188 0.69727,3.86719 0.16111,0.54265 0.34457,1.00922 0.59179,1.38086 0.24723,0.37163 0.61429,0.69531 1.09375,0.69531 0.39174,0 0.74881,-0.20864 0.97657,-0.48047 0.22775,-0.27182 0.37091,-0.59987 0.48632,-0.96289 0.23084,-0.72603 0.34622,-1.61436 0.46876,-2.48828 0.12253,-0.87392 0.25275,-1.73314 0.44921,-2.31641 0.0982,-0.29163 0.21467,-0.50852 0.31446,-0.62109 C 348.34784,206.01829 348.39286,206 348.5,206 c 0.16824,0 0.2932,0.0848 0.48633,0.39062 0.19312,0.30582 0.37727,0.78401 0.5625,1.28321 0.18523,0.49919 0.37171,1.01992 0.64453,1.46289 0.27281,0.44297 0.71019,0.86328 1.30664,0.86328 0.41993,0 0.77613,-0.23568 0.99219,-0.49219 0.21606,-0.2565 0.34747,-0.53797 0.4707,-0.78906 0.12323,-0.25109 0.23849,-0.47074 0.33594,-0.58984 C 353.39628,208.0098 353.41651,208 353.5,208 c 0.0859,0 0.18647,0.0811 0.39844,0.32617 0.10598,0.12255 0.22449,0.26923 0.39453,0.40625 C 354.463,208.86945 354.71104,209 355,209 c 0.008,0 0.14542,-2.8e-4 0.27539,0 0.065,1.4e-4 0.13031,6e-5 0.18164,0 0.0513,-6e-5 0.0312,0.005 0.12109,0 a 0.50080138,0.50080138 0 1 0 -0.0566,-1 c 0.0748,-0.004 -0.0172,-5e-5 -0.0664,0 -0.0492,5e-5 -0.11331,1.4e-4 -0.17774,0 -0.12885,-2.8e-4 -0.25041,0 -0.27734,0 -0.0108,0 -0.0173,0.004 -0.0801,-0.0469 -0.0628,-0.0506 -0.15527,-0.15364 -0.26562,-0.28124 C 354.43358,207.41667 354.08387,207 353.5,207 c -0.4167,0 -0.76719,0.23824 -0.97656,0.49414 -0.20937,0.2559 -0.33683,0.53625 -0.45899,0.78516 -0.12215,0.24891 -0.23808,0.46744 -0.33789,0.58593 C 351.62675,208.98373 351.59835,209 351.5,209 c -0.14972,0 -0.26794,-0.0816 -0.45703,-0.38867 -0.18909,-0.30703 -0.37082,-0.78435 -0.55664,-1.28516 -0.18583,-0.5008 -0.37575,-1.02457 -0.65625,-1.46875 C 349.54958,205.41324 349.10136,205 348.5,205 c -0.39664,0 -0.76041,0.19848 -1,0.46875 -0.23959,0.27027 -0.39279,0.60017 -0.51562,0.96484 -0.24567,0.72934 -0.36739,1.61988 -0.49024,2.4961 -0.12285,0.87621 -0.24515,1.73764 -0.43164,2.32422 -0.0933,0.29328 -0.20581,0.51202 -0.29883,0.62304 -0.093,0.11102 -0.1237,0.12305 -0.21094,0.12305 -0.027,0 -0.10877,-0.0201 -0.26171,-0.25 -0.15295,-0.22991 -0.31979,-0.62276 -0.46485,-1.11133 -0.29011,-0.97714 -0.5088,-2.33541 -0.66211,-3.69531 -0.30662,-2.7198 -0.36328,-5.45313 -0.36328,-5.45313 a 0.50005,0.50005 0 0 0 -0.5,-0.49023 c -0.0468,0 -0.74734,5e-5 -0.80078,0 a 0.50005,0.50005 0 0 0 -0.0488,0 z" + id="path16468-1" + inkscape:connector-curvature="0" /> - - - + id="g158074" + style="display:inline;enable-background:new" + inkscape:label="J-16"> + + + id="g158065" + style="display:inline;enable-background:new" + inkscape:label="J-14"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 291.5,201 a 0.50005,0.50005 0 0 0 -0.5,0.5 c 0,2.47889 -0.15954,4.38104 -0.52734,5.8125 -0.36781,1.43146 -0.92559,2.38009 -1.74024,3.05273 C 287.10312,211.71053 284.21571,212 279.5,212 a 0.50005,0.50005 0 1 0 0,1 c 4.75093,0 7.86511,-0.21053 9.86914,-1.86523 1.00202,-0.82736 1.66924,-2.00373 2.07227,-3.57227 0.36978,-1.43916 0.48747,-3.33151 0.51171,-5.5625 H 292.5 a 0.50005,0.50005 0 1 0 0,-1 z" + id="path14276" + inkscape:connector-curvature="0" /> + style="display:inline;fill:#ffffff;enable-background:new" + id="g14249" + inkscape:label="J-13"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 270.5,201 a 0.50005,0.50005 0 0 0 -0.5,0.5 c 0,3.44341 -1.07548,6.03173 -3.02344,7.79102 C 265.02861,211.0503 262.16329,212 258.5,212 a 0.50005,0.50005 0 1 0 0,1 c 3.83671,0 6.97139,-1.00062 9.14844,-2.9668 2.06597,-1.86586 3.15029,-4.63736 3.26367,-8.0332 H 271.5 a 0.50005,0.50005 0 1 0 0,-1 z" + id="path14273" + inkscape:connector-curvature="0" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 258.5,199.99609 a 0.50005,0.50005 0 0 0 -0.5,0.5 V 202.5 a 0.50005,0.50005 0 0 0 0.5,0.5 h 1.5 c 0.41667,0 1,0.45 1,1.5 0,1.04998 -0.58056,1.4941 -1.00195,1.49609 -0.52634,0.003 -0.86013,-0.33871 -1.05078,-0.72265 a 0.50005,0.50005 0 1 0 -0.89454,0.44531 c 0.30945,0.62316 0.97566,1.28254 1.94922,1.27734 C 261.08054,206.99099 262,205.95002 262,204.5 c 0,-1.45 -0.91667,-2.5 -2,-2.5 h -1 v -1.00391 h 2.5 a 0.50005,0.50005 0 1 0 0,-1 z" + id="path14399" + inkscape:connector-curvature="0" /> - - + style="display:inline;fill:#ffffff;enable-background:new" + id="g14245" + inkscape:label="J-12"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 249.5,201 a 0.50005,0.50005 0 0 0 -0.49805,0.45508 c -0.24047,2.64524 -1.32572,5.28945 -3.23633,7.25976 C 243.85502,210.68516 241.12778,212 237.5,212 a 0.50005,0.50005 0 1 0 0,1 c 3.87222,0 6.89498,-1.43516 8.98438,-3.58984 1.97612,-2.03788 3.06007,-4.712 3.39843,-7.41016 H 250.5 a 0.50005,0.50005 0 1 0 0,-1 z" + id="path14271" + inkscape:connector-curvature="0" /> + sodipodi:nodetypes="ccccccccccccc" /> + style="display:inline;fill:#ffffff;enable-background:new" + id="g14241" + inkscape:label="J-11"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 228.5,201 a 0.50005,0.50005 0 0 0 -0.47461,0.3418 c -0.75176,2.25527 -1.61792,4.94183 -3.29101,7.0332 -1.6731,2.09137 -4.11183,3.625 -8.23438,3.625 a 0.50005,0.50005 0 1 0 0,1 c 4.37745,0 7.18872,-1.71637 9.01562,-4 1.74159,-2.17699 2.6089,-4.81175 3.33594,-7 H 229.5 a 0.50005,0.50005 0 1 0 0,-1 z" + id="path14266" + inkscape:connector-curvature="0" /> + - - - - - + style="display:inline;fill:#ffffff;enable-background:new" + id="g14237" + inkscape:label="J-10"> - - + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 197,199.99609 c -0.79172,0 -1.70536,0.42738 -1.98047,1.35938 a 0.50090117,0.50090117 0 1 0 0.96094,0.2832 c 0.12417,-0.42064 0.57767,-0.64258 1.01953,-0.64258 0.26308,0 0.50603,0.0889 0.67773,0.2461 0.17133,0.15683 0.29488,0.38005 0.29883,0.75976 0.001,0.2095 -0.10056,0.41777 -0.33789,0.6875 -0.23733,0.26973 -0.59584,0.56699 -0.97656,0.90235 -0.76143,0.67072 -1.65792,1.5571 -1.66211,2.90234 a 0.50005,0.50005 0 0 0 0.5,0.50195 h 3 a 0.50005,0.50005 0 1 0 0,-1 h -2.33398 c 0.18416,-0.62502 0.58645,-1.15066 1.1582,-1.65429 0.36326,-0.31999 0.74752,-0.62977 1.0664,-0.99219 0.31889,-0.36243 0.58851,-0.81103 0.58594,-1.35156 a 0.50005,0.50005 0 0 0 0,-0.002 c -0.006,-0.61869 -0.24656,-1.14755 -0.62304,-1.49218 -0.37648,-0.34464 -0.8666,-0.50782 -1.35352,-0.50782 z" + id="path12457-3" + inkscape:connector-curvature="0" /> + style="display:inline;fill:#ffffff;enable-background:new" + id="g14233" + inkscape:label="J-9"> + + style="display:inline;fill:#ffffff;enable-background:new" + id="g13432" + inkscape:label="J-8"> + - - - - - - - - - + id="g158062" + style="display:inline;enable-background:new" + inkscape:label="J-7"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 144.5,201 a 0.50005,0.50005 0 0 0 -0.35352,0.14648 L 133.29297,212 H 132.5 a 0.50005,0.50005 0 1 0 0,1 h 1 a 0.50005,0.50005 0 0 0 0.35352,-0.14648 L 144.70703,202 H 145.5 a 0.50005,0.50005 0 1 0 0,-1 z" + id="path13875" + inkscape:connector-curvature="0" /> + inkscape:label="J-6"> - - - - - - - - - - - + style="display:inline;fill:#ffffff;enable-background:new" + id="g13322" + transform="translate(-1.85367e-6,21)" + inkscape:label="J-5"> - + id="g14281" + transform="translate(84,200)" + style="opacity:0.6;fill:#ffffff"> + + + + - - - + sodipodi:nodetypes="ccccccc" /> - - - - - - - + id="g158089" + style="display:inline;enable-background:new" + inkscape:label="J-4"> + - + id="g158092" + style="display:inline;enable-background:new" + inkscape:label="J-3"> + sodipodi:nodetypes="cccccccccccccc" /> + + + sodipodi:nodetypes="cccccccccccccccccccccccccccccccc" /> + id="g158056" + style="display:inline;enable-background:new" + inkscape:label="J-1"> - + id="g6932" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="I-26"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 536.49609,179.00195 a 0.50005,0.50005 0 0 0 -0.35351,0.12891 c -4.08383,3.59037 -5.14329,7.80863 -5.13086,13.36328 a 0.50005,0.50005 0 1 0 1,-0.002 c -0.0122,-5.43221 0.92902,-9.21403 4.79101,-12.60938 a 0.50005,0.50005 0 0 0 -0.30664,-0.88086 z" + id="path7282" + inkscape:connector-curvature="0" /> - - - + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="I-25"> + + + sodipodi:nodetypes="cccccccccccccccccc" /> + transform="translate(919,-532)" + id="g6686" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="I-24"> + + sodipodi:nodetypes="ccccccccccccccccccccccccccc" /> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + inkscape:export-ydpi="90" + inkscape:export-xdpi="90" + inkscape:export-filename="blender_icons.png" + id="path29102" + d="m 460,120.50004 0.5,2e-5 v 9 h -9 L 451.49999,129" + style="display:inline;opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;enable-background:new" + sodipodi:nodetypes="ccccc" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="D-16"> + + + + + + + + + + + + + + id="g13282" + transform="translate(20,-21)" + style="fill:#ffffff"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 246.49805,74 c -0.13192,5.4e-4 -0.25829,0.0532 -0.35157,0.146484 C 245.43577,74.857204 244.09848,76 241.5,76 A 0.50005,0.50005 0 0 0 241,76.5 V 79 h -0.5 c -0.75833,0 -1.38671,0.318735 -1.90625,0.734375 -0.51954,0.41563 -0.962,0.926849 -1.44727,1.412109 -0.39648,0.39648 0.31056,1.103512 0.70704,0.707032 0.4558,-0.45581 0.79842,-0.643904 1.18164,-0.746094 C 239.41838,81.005232 239.875,81 240.5,81 h 0.5 v 4.5 c 0,0.75694 -0.56911,1.365513 -1.18359,1.476562 -0.30725,0.0555 -0.62032,0.004 -0.94141,-0.228515 -0.32109,-0.23272 -0.65388,-0.66673 -0.90039,-1.40625 A 0.50005,0.50005 0 0 0 237,85.5 v 1 a 0.50005,0.50005 0 0 0 0.14648,0.353516 C 238.22544,87.932473 239.1944,88.00968 240,88 c 0,0 0.002,0 0.002,0 9.9e-4,0 0.003,10e-7 0.004,0 0.001,-1.6e-5 0.003,1.8e-5 0.004,0 0.36494,-7.26e-4 0.98133,-0.05472 1.67773,-0.203125 0.19213,-0.0454 0.36061,-0.131526 0.54297,-0.197266 0.41443,-0.1248 0.82232,-0.201598 1.24219,-0.449218 C 244.82222,86.354491 246,84.75992 246,82 v -1 h 2.5 c 0.1326,-2e-5 0.25976,-0.0527 0.35352,-0.146484 l 1,-1 C 250.16831,79.538516 249.94532,79.00017 249.5,79 H 246 v -2.529297 c 0.26613,0.242116 0.53257,0.477435 0.83203,0.527344 0.63268,0.10544 1.23725,-0.0679 1.76563,-0.332031 1.05675,-0.52838 1.9005,-1.45715 2.25586,-1.8125 0.45304,-0.47127 -0.23577,-1.160072 -0.70704,-0.707032 -0.31826,0.31827 -0.84181,0.673592 -1.40039,0.763672 -0.55857,0.0901 -1.16912,-0.02339 -1.88867,-0.759765 -0.0945,-0.0967 -0.22417,-0.150901 -0.35937,-0.150391 z m -2.50977,3.013672 c 0.004,-1.62e-4 0.008,1.67e-4 0.0117,0 V 83 c 0,1.65476 -0.45037,2.602541 -1.13477,3.181641 -0.20404,0.172661 -0.46051,0.286861 -0.72656,0.390625 C 242.64927,85.748655 243,84.573733 243,83 v -5.982422 c 0.35078,0.0051 0.69957,0.0073 0.98828,-0.0039 z" + transform="translate(-20,42)" + id="path13240" + inkscape:connector-curvature="0" /> + + + sodipodi:nodetypes="cccccccccc" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + transform="translate(-1)" + inkscape:label="C-24"> + transform="translate(-664,-513)" + style="display:inline;opacity:0.99;fill:#ffffff;enable-background:new" + id="g8745-3" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + inkscape:label="C-23"> + + + + + + + + + - + id="g15520" + transform="translate(232,-398)" + style="display:inline;opacity:0.6;fill:#ffffff;enable-background:new" + inkscape:export-filename="blender_icons.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 430.48047,53 c -0.15153,0.004 -0.29304,0.07659 -0.38477,0.197266 l -3.94922,3.949218 C 425.83169,57.461484 426.05468,57.99983 426.5,58 h 4 c 0.27613,-3e-5 0.49997,-0.22387 0.5,-0.5 V 54 h 6 v 3.5 c -0.01,0.676161 1.00956,0.676161 1,0 v -4 c -3e-5,-0.276131 -0.22387,-0.499972 -0.5,-0.5 h -7 c -0.005,-6.2e-5 -0.009,-6.2e-5 -0.0137,0 -6.7e-4,1.8e-5 -0.001,-2.1e-5 -0.002,0 -0.001,4.1e-5 -0.003,-5e-5 -0.004,0 z M 426,59.000004 V 66.5 c 3e-5,0.276131 0.22387,0.499972 0.5,0.5 h 4.25 c 0.67616,0.0096 0.67616,-1.009563 0,-1 H 427 v -6.999996 z" + transform="translate(-274,440)" + id="path15514" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccccccccccccccccccccccc" /> + style="display:inline;opacity:1;fill:#ffffff;stroke-width:1.25111;enable-background:new" + transform="matrix(-0.799288,0,0,0.799288,686.435,-75.7359)" + id="g12909-3"> + style="display:inline;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:new" + d="m 435.50195,58 a 0.50005,0.50005 0 0 0 -0.50586,0.507812 L 435,59.847656 c -0.37235,0.07227 -0.71903,0.219786 -1.02148,0.427735 l -1.12696,-1.123047 a 0.50005,0.50005 0 0 0 -0.34375,-0.150391 0.50005,0.50005 0 0 0 -0.36133,0.859375 l 1.12696,1.123047 c -0.20773,0.30118 -0.3566,0.644813 -0.42969,1.015625 l -1.3457,0.002 a 0.5009775,0.5009775 0 0 0 0.002,1.001953 L 432.83789,63 c 0.0676,0.378442 0.20857,0.7312 0.41602,1.039062 l -1.10743,1.109376 a 0.50005,0.50005 0 1 0 0.70508,0.705078 l 1.10352,-1.101563 c 0.30785,0.216541 0.661,0.370975 1.04297,0.445313 l -0.002,1.300781 a 0.50098382,0.50098382 0 1 0 1.00196,0.0039 L 436,65.199219 c 0.38598,-0.06997 0.74484,-0.216649 1.05664,-0.431641 l 1.08203,1.085938 a 0.5000572,0.5000572 0 1 0 0.70899,-0.705078 l -1.08399,-1.087891 C 437.97884,63.747505 438.12621,63.387471 438.19531,63 H 439.5 a 0.50033424,0.50033424 0 1 0 0,-1 h -1.31055 c -0.0748,-0.379778 -0.22985,-0.730801 -0.44531,-1.037109 l 1.10156,-1.103516 A 0.50005,0.50005 0 0 0 438.49805,59 a 0.50005,0.50005 0 0 0 -0.35743,0.154297 l -1.10742,1.105469 C 436.72738,60.053691 436.37559,59.913865 436,59.845703 l -0.004,-1.339844 A 0.50005,0.50005 0 0 0 435.50195,58 Z m -0.002,3.300781 c 0.66274,0 1.19921,0.536481 1.19922,1.199219 0,0.662738 -0.53648,1.199219 -1.19922,1.199219 -0.66274,-8e-6 -1.19922,-0.536481 -1.19922,-1.199219 0,-0.66273 0.53649,-1.199211 1.19922,-1.199219 z" + transform="matrix(-1.25111,0,0,1.25111,911.356,147.301)" + id="path12903-0" + inkscape:connector-curvature="0" /> + + + id="g28589-6"> + d="m 411,-65 h 1 v 3 h -1 z m -1,3 h 1 v 3 h -1 z" + style="display:inline;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;enable-background:new" + id="path28575-1" /> + id="path28581-2" + d="m 408.50004,-53.446439 c 0,1.0803 -0.892,1.95 -2,1.95 -1.108,0 -2,-0.8697 -2,-1.95 l 10e-6,-2.05 h 4 z" + style="display:inline;opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;enable-background:new" /> - - + id="path28583-6" + d="m 404.50005,-58.496439 h 4 v 3 h -4 z" + style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke" /> + sodipodi:nodetypes="ccccccc" /> + + d="m 417,-65 h 1 v 3 h -1 z m -1,3 h 1 v 3 h -1 z" + style="display:inline;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;enable-background:new" + id="path28601-3" /> + + + + + + + + + + + + + + + id="g17552-8" + transform="matrix(-1,0,0,1,508,-7e-5)" + style="fill:#ffffff"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 201.49219,94.998047 a 0.50005,0.50005 0 0 0 -0.38867,0.195312 0.50005,0.50005 0 0 0 -0.006,0.0059 l -3.95118,3.953125 a 0.50005,0.50005 0 1 0 0.70704,0.707031 L 201,96.712891 v 8.792969 a 0.50005,0.50005 0 1 0 1,0 v -8.792969 l 3.14648,3.146484 a 0.50005,0.50005 0 1 0 0.70704,-0.707031 l -3.95508,-3.955078 a 0.50005,0.50005 0 0 0 -0.40625,-0.199219 z" + id="path14327-2" + inkscape:connector-curvature="0" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="M 195.49219,104.99219 A 0.50005,0.50005 0 0 0 195,105.5 v 3 a 0.50005,0.50005 0 0 0 0.5,0.5 h 12 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -3 a 0.50005,0.50005 0 1 0 -1,0 v 2.5 h -11 v -2.5 a 0.50005,0.50005 0 0 0 -0.50781,-0.50781 z" + id="rect17542-1" + inkscape:connector-curvature="0" /> + id="g17548-0" + transform="matrix(-1,0,0,1,508,-7e-5)" + style="fill:#ffffff"> - + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="M 222.49219,95.005859 A 0.50005,0.50005 0 0 0 222,95.511719 v 8.792971 l -3.14648,-3.14649 a 0.50005,0.50005 0 1 0 -0.70704,0.70703 l 3.95704,3.95704 a 0.50005,0.50005 0 0 0 0.79296,0.002 0.50005,0.50005 0 0 0 0.004,-0.006 l 3.95313,-3.95313 a 0.50005,0.50005 0 1 0 -0.70704,-0.70703 L 223,104.30469 v -8.792971 a 0.50005,0.50005 0 0 0 -0.50781,-0.50586 z" + id="path14329-2" + inkscape:connector-curvature="0" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.6;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="M 216.49219,104.99219 A 0.50005,0.50005 0 0 0 216,105.5 v 3 a 0.50005,0.50005 0 0 0 0.5,0.5 h 12 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -3 a 0.50005,0.50005 0 1 0 -1,0 v 2.5 h -11 v -2.5 a 0.50005,0.50005 0 0 0 -0.50781,-0.50781 z" + id="path17546-9" + inkscape:connector-curvature="0" /> + + + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 35.744141,52.003906 c -0.89409,0.01843 -1.828792,0.373713 -2.597657,1.142578 l -5,5 c -0.837869,0.83787 -1.178323,2.041781 -1.11914,3.226563 0.05918,1.184782 0.515289,2.376617 1.36914,3.230469 0.852133,0.852132 2.037527,1.314671 3.220704,1.376953 1.183176,0.06228 2.389387,-0.280013 3.236328,-1.126953 l 6,-6 a 0.50005,0.50005 0 1 0 -0.707032,-0.707032 l -6,6 c -0.60312,0.603121 -1.522254,0.886172 -2.476562,0.835938 -0.954308,-0.05023 -1.918539,-0.43807 -2.566406,-1.085938 -0.646149,-0.646148 -1.030262,-1.616046 -1.078125,-2.574218 -0.04786,-0.958173 0.23781,-1.878436 0.828125,-2.46875 l 5,-5 c 1.184971,-1.184974 2.75871,-1.004381 3.554687,-0.248047 0.796393,0.756731 0.863064,2.416231 -0.261719,3.541015 l -4.5,4.5 c -0.592865,0.592866 -1.094745,0.448224 -1.417968,0.125 -0.323224,-0.323223 -0.467866,-0.825103 0.125,-1.417968 l 3.5,-3.5 a 0.50005,0.50005 0 1 0 -0.707032,-0.707032 l -3.5,3.5 c -0.907134,0.907135 -0.801776,2.155255 -0.125,2.832032 0.676777,0.676776 1.924897,0.782134 2.832032,-0.125 l 4.5,-4.5 c 1.483397,-1.483398 1.519845,-3.762437 0.24414,-4.97461 -0.60598,-0.5758 -1.459426,-0.89343 -2.353515,-0.875 z" + id="path10693" + inkscape:connector-curvature="0" /> + + + + transform="translate(-189)" + id="g10709" + style="fill:#ffffff"> + - - - - + id="g10703" + style="fill:#ffffff" /> + + + + + + + + + + + - + transform="translate(0,-21)" + id="g28156"> - - - - - - - - - - + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 543.5,33 c -0.8225,0 -1.5,0.677492 -1.5,1.5 0,0.230167 0.0579,0.446791 0.15234,0.642578 a 0.50005,0.50005 0 0 0 -0.006,0.0039 L 540.29297,37 H 539.5 a 0.50005,0.50005 0 1 0 0,1 h 1 a 0.50005,0.50005 0 0 0 0.35352,-0.146484 l 2,-2 a 0.50005,0.50005 0 0 0 0.004,-0.0059 C 543.05321,35.942094 543.26984,36 543.5,36 c 0.8225,0 1.5,-0.677492 1.5,-1.5 0,-0.822508 -0.6775,-1.5 -1.5,-1.5 z m 0,1 c 0.28206,0 0.5,0.217938 0.5,0.5 0,0.282062 -0.21794,0.5 -0.5,0.5 -0.28206,0 -0.5,-0.217938 -0.5,-0.5 0,-0.282062 0.21794,-0.5 0.5,-0.5 z m 0,5 c -0.64684,0 -1.19777,0.420971 -1.40625,1 H 539.5 a 0.50005,0.50005 0 1 0 0,1 h 2.59375 c 0.20848,0.579029 0.75941,1 1.40625,1 0.8225,0 1.5,-0.677492 1.5,-1.5 0,-0.822508 -0.6775,-1.5 -1.5,-1.5 z m 0,1 c 0.28206,0 0.5,0.217938 0.5,0.5 0,0.282062 -0.21794,0.5 -0.5,0.5 -0.28206,0 -0.5,-0.217938 -0.5,-0.5 0,-0.282062 0.21794,-0.5 0.5,-0.5 z m -4,2.992188 a 0.50005,0.50005 0 1 0 0,1 h 0.79297 l 1.85351,1.853515 a 0.50005,0.50005 0 0 0 0.008,0.0078 C 542.05866,46.050309 542,46.268282 542,46.5 c 0,0.822508 0.6775,1.5 1.5,1.5 0.8225,0 1.5,-0.677492 1.5,-1.5 0,-0.822508 -0.6775,-1.5 -1.5,-1.5 -0.22767,0 -0.44256,0.05591 -0.63672,0.148438 a 0.50005,0.50005 0 0 0 -0.01,-0.0098 l -2,-2 A 0.50005,0.50005 0 0 0 540.5,42.992188 Z M 543.5,46 c 0.28206,0 0.5,0.217938 0.5,0.5 0,0.282062 -0.21794,0.5 -0.5,0.5 -0.28206,0 -0.5,-0.217938 -0.5,-0.5 0,-0.282062 0.21794,-0.5 0.5,-0.5 z" + transform="translate(123,41)" + id="circle28098" + inkscape:connector-curvature="0" /> + id="g28053" + transform="translate(21)"> + id="rect28051" + transform="translate(21,-21)" + style="display:inline;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;enable-background:new" + d="m 616,85 h 1 v 0.999999 h -1 z m -2,0 h 1 v 0.999999 h -1 z m -0.46289,-8 C 612.69436,77 612,77.694362 612,78.537109 v 7.925782 C 612,87.305638 612.69436,88 613.53711,88 h 5.92578 c 0.73741,0 1.36143,-0.531036 1.50586,-1.228516 0.0206,-0.09964 0.0312,-0.20325 0.0312,-0.308593 V 78.537109 C 621,77.694362 620.30564,77 619.46289,77 Z m 0,7 h 5.92578 C 619.76894,84 620,84.231065 620,84.537109 v 1.925782 C 620,86.768935 619.76894,87 619.46289,87 h -5.92578 C 613.23106,87 613,86.768935 613,86.462891 V 84.537109 C 613,84.231065 613.23106,84 613.53711,84 Z" + inkscape:connector-curvature="0" /> + + + style="display:inline;enable-background:new" + id="g28033" + transform="translate(21,-21)"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.00157;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="M 516.03711,33 C 514.91694,33 514,33.916938 514,35.037109 V 36 c -0.0409,0.706384 1.0409,0.706384 1,0 V 35.037109 C 515,34.454485 515.45448,34 516.03711,34 h 4.92578 C 521.54552,34 522,34.454485 522,35.037109 v 5.058594 c -0.58287,0.214917 -1,0.784943 -1,1.441406 v 1.925782 c 3e-5,0.276131 0.22387,0.499972 0.5,0.5 H 522 V 46 h 1 v -2.037109 h 0.5 c 0.27613,-2.8e-5 0.49997,-0.223869 0.5,-0.5 v -1.925782 c 0,-0.656467 -0.41713,-1.22649 -1,-1.441406 V 35.037109 C 523,33.916938 522.08306,33 520.96289,33 Z" + transform="translate(102,41)" + id="path28027" + inkscape:connector-curvature="0" /> - - - - - - - - - - + style="display:inline;enable-background:new" + id="g28053-2"> + id="rect28051-4" + transform="translate(21,-21)" + style="display:inline;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;enable-background:new" + d="m 616,85 h 1 v 0.999999 h -1 z m -2,0 h 1 v 0.999999 h -1 z m -0.46289,-8 C 612.69436,77 612,77.694362 612,78.537109 v 7.925782 C 612,87.305638 612.69436,88 613.53711,88 h 5.92578 c 0.73741,0 1.36143,-0.531036 1.50586,-1.228516 0.0206,-0.09964 0.0312,-0.20325 0.0312,-0.308593 V 78.537109 C 621,77.694362 620.30564,77 619.46289,77 Z m 0,7 h 5.92578 C 619.76894,84 620,84.231065 620,84.537109 v 1.925782 C 620,86.768935 619.76894,87 619.46289,87 h -5.92578 C 613.23106,87 613,86.768935 613,86.462891 V 84.537109 C 613,84.231065 613.23106,84 613.53711,84 Z" /> + + + + + + + + + + + + + + transform="translate(22)" + id="g12463" + style="fill:#ffffff"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 553.48438,620 a 0.50005,0.50005 0 0 0 -0.3379,0.14648 l -2,2 a 0.50005,0.50005 0 1 0 0.70704,0.70704 L 553,621.70703 V 625.5 a 0.50005,0.50005 0 1 0 1,0 v -5 A 0.50005,0.50005 0 0 0 553.48438,620 Z M 553,628 c -0.79172,0 -1.70536,0.42737 -1.98047,1.35938 a 0.50062501,0.50062501 0 1 0 0.96094,0.28124 C 552.10464,629.21999 552.55814,629 553,629 c 0.31942,0 0.59941,0.0632 0.75391,0.16602 0.1545,0.10279 0.25034,0.21211 0.2539,0.58789 5.6e-4,0.20689 -0.076,0.32805 -0.29687,0.52148 -0.22152,0.19398 -0.57835,0.40234 -0.9668,0.64453 -0.38845,0.24219 -0.81151,0.52364 -1.1543,0.93555 -0.34278,0.4119 -0.58774,0.96871 -0.58984,1.64258 A 0.50005,0.50005 0 0 0 551.5,634 h 3 a 0.50005,0.50005 0 1 0 0,-1 h -2.32227 c 0.0615,-0.1621 0.068,-0.36972 0.17969,-0.50391 0.22282,-0.26775 0.55267,-0.50197 0.91602,-0.72851 0.36335,-0.22654 0.7571,-0.44007 1.09765,-0.73828 0.34056,-0.29822 0.6393,-0.73918 0.63672,-1.28125 a 0.50005,0.50005 0 0 0 0,-0.002 c -0.006,-0.62417 -0.29003,-1.13985 -0.69922,-1.41211 C 553.89941,628.06173 553.43058,628 553,628 Z" + id="path12461" + inkscape:connector-curvature="0" /> + + - - - + transform="translate(21)" + id="g23190-0" + style="fill:#ffffff"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + inkscape:label="B-6"> + + + + + + + + + + + id="path12123-3" + d="m 51.5,32 a 0.50005,0.50005 0 0 0 -0.353516,0.146484 l -3,3 A 0.50005,0.50005 0 0 0 48,35.5 v 10 a 0.50005,0.50005 0 0 0 0.5,0.5 h 10 a 0.50005,0.50005 0 0 0 0.353516,-0.146484 l 3,-3 A 0.50005,0.50005 0 0 0 62,42.5 v -10 A 0.50005,0.50005 0 0 0 61.5,32 Z m 0.207031,1 H 61 v 9.292969 L 58.292969,45 H 49 v -9.292969 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + id="path12125-1" + d="m 60.490234,32.988281 a 0.50005,0.50005 0 0 0 -0.34375,0.152344 L 58.292969,35 H 50.5 a 0.50005,0.50005 0 1 0 0,1 H 58 v 7.5 a 0.50005,0.50005 0 1 0 1,0 v -7.792969 l 1.853516,-1.861328 a 0.50005,0.50005 0 0 0 -0.363282,-0.857422 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + id="g6375" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="B-2"> + id="path12109-5" + d="m 34,32 c -3.860074,0 -7,3.139926 -7,7 0,3.860083 3.139927,7 7,7 3.860082,0 7,-3.139918 7,-7 0,-3.860073 -3.139917,-7 -7,-7 z m 0,1 c 3.319643,0 6,2.680365 6,6 0,3.319644 -2.680356,6 -6,6 -3.319635,0 -6,-2.680357 -6,-6 0,-3.319634 2.680366,-6 6,-6 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + id="path12115-8" + d="M 28.507812,38.244141 A 0.50005,0.50005 0 0 0 28.15625,39.101562 C 29.521925,40.47768 30.901684,41.000009 32.5,41 H 34 c 2.575017,3.52e-4 3.847654,-0.615233 4.722656,-1.052734 A 0.50005,0.50005 0 1 0 38.277344,39.052734 C 37.402346,39.490233 36.424977,40.000332 34,40 h -1.5 c -1.401674,7e-6 -2.403186,-0.362534 -3.632812,-1.601562 a 0.50005,0.50005 0 0 0 -0.359376,-0.154297 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> - + id="g49134" + style="display:inline;enable-background:new" + inkscape:label="B-1"> - - - - - - - + id="g30732" + inkscape:label="A-26"> + id="path15447" + d="m 539.5,10.000005 c -3.5682,0 -6.5,2.931819 -6.5,6.499999 0,1.42599 0.4737,2.74606 1.2637,3.822271 l -3.9707,3.9707 a 1.0001,1.0001 0 1 0 1.414,1.41406 l 3.9707,-3.9707 c 1.0762,0.78998 2.3963,1.26367 3.8223,1.26367 3.5682,0 6.5,-2.93182 6.5,-6.500001 0,-3.56818 -2.9318,-6.499999 -6.5,-6.499999 z m 0,2.999999 c 0.051,0 0.098,0.0135 0.1484,0.0156 a 0.50005,0.50005 0 0 1 0.3516,0.4844 v 2.5 h 2.5 a 0.50005,0.50005 0 0 1 0.4863,0.35742 0.50005,0.50005 0 0 1 0,0.002 c 0,0.0477 0.014,0.0925 0.014,0.14062 0,0.0488 -0.012,0.0942 -0.014,0.14258 A 0.50005,0.50005 0 0 1 542.5,17.000004 H 540 v 2.5 a 0.50005,0.50005 0 0 1 -0.3574,0.486331 c -0.048,0.002 -0.092,0.0137 -0.1406,0.0137 -0.049,0 -0.094,-0.0117 -0.1426,-0.0137 A 0.50005,0.50005 0 0 1 539,19.500004 v -2.5 h -2.5 a 0.50005,0.50005 0 0 1 -0.4863,-0.35742 0.50005,0.50005 0 0 1 0,-0.002 c 0,-0.0476 -0.014,-0.0925 -0.014,-0.14062 0,-0.0488 0.012,-0.0942 0.014,-0.14258 a 0.50005,0.50005 0 0 1 0.4863,-0.35738 h 2.5 v -2.5 a 0.50005,0.50005 0 0 1 0.3516,-0.48438 c 0.05,-0.002 0.098,-0.0156 0.1484,-0.0156 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:7.17647;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + inkscape:connector-curvature="0" /> + id="g30744" + inkscape:label="A-25"> + id="path22769" + d="m 517.98438,9.9863345 c -0.55152,0.009 -0.99193,0.4621405 -0.98438,1.0136695 v 7 h -1.10156 l -0.91016,-6.14648 c -0.0643,-0.48249 -0.46671,-0.84859 -0.95312,-0.86719 -0.63083,-0.0229 -1.12488,0.53711 -1.02344,1.16015 L 514,18.816414 v 0.88671 1.296881 h -1 v -1.746091 l -2.58984,-1.16602 c -0.12175,-0.0548 -0.25324,-0.0847 -0.38672,-0.0879 -0.88449,-0.0197 -1.35703,1.03438 -0.75391,1.68164 l 3.5,3.750001 c 1.45044,1.55404 3.38812,2.64316 5.12305,2.55469 2.20226,-0.16055 4.09501,-1.6226 4.80664,-3.71289 l 2.25586,-6.269531 c 0.1808,-0.48082 -0.16432,-0.99701 -0.67774,-1.01368 -0.32862,-0.0101 -0.62553,0.19494 -0.73242,0.50586 l -0.90039,2.50392 h -0.74023 l 1.07812,-5.81836 c 0.12593,-0.63116 -0.36843,-1.215229 -1.01172,-1.19531 -0.47506,0.0156 -0.8735,0.36343 -0.95312,0.83203 l -1.14453,6.18164 H 519 v -7 c 0.008,-0.563769 -0.45189,-1.0224695 -1.01562,-1.0136695 z" + style="display:inline;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;enable-background:new" /> + + + id="path15245-5-5" + d="m 498.5,10 c -2.1397,0 -3.80728,1.4725 -4.33008,3.2441 C 493.52022,12.4964 492.6594,12 491.5,12 c -1.9271,0 -3.5,1.5729 -3.5,3.5 0,1.927 1.5729,3.5 3.5,3.5 h 7 c 2.4794,0 4.5,-2.0207 4.5,-4.5 0,-2.4794 -2.0206,-4.5 -4.5,-4.5 z m 0,3 c 0.8225,0 1.5,0.6774 1.5,1.5 0,0.8225 -0.6775,1.5 -1.5,1.5 -0.8225,0 -1.5,-0.6775 -1.5,-1.5 0,-0.8226 0.6775,-1.5 1.5,-1.5 z m -7,1 c 0.8225,0 1.5,0.6774 1.5,1.5 0,0.8225 -0.6775,1.5 -1.5,1.5 -0.8225,0 -1.5,-0.6775 -1.5,-1.5 0,-0.8226 0.6775,-1.5 1.5,-1.5 z m 7,0 c -0.2821,0 -0.5,0.2179 -0.5,0.5 0,0.282 0.2179,0.5 0.5,0.5 0.2821,0 0.5,-0.218 0.5,-0.5 0,-0.2821 -0.2179,-0.5 -0.5,-0.5 z m -7,1 c -0.2821,0 -0.5,0.2179 -0.5,0.5 0,0.282 0.2179,0.5 0.5,0.5 0.2821,0 0.5,-0.218 0.5,-0.5 0,-0.2821 -0.2179,-0.5 -0.5,-0.5 z m 1,5 c -0.2761,0 -0.5,0.2239 -0.5,0.5 v 1 0.5 h -1 v -0.5 c 0.004,-0.2823 -0.22555,-0.5122 -0.50781,-0.5078 -0.27615,0 -0.49651,0.2316 -0.49219,0.5078 v 1 1 c 0.009,0.6573 0.9907,0.6573 1,0 V 23 h 1 v 0.5 1 c 0,0.1326 0.053,0.2597 0.14648,0.3535 l 1,1 C 493.24048,25.9475 493.3674,26 493.5,26 h 5 c 0.2761,0 0.5,-0.2239 0.5,-0.5 v -5 c 0,-0.2761 -0.2239,-0.5 -0.5,-0.5 z m 10.98438,1 c -0.0871,0 -0.17191,0.028 -0.2461,0.074 l -3.25,2 c -0.31714,0.1953 -0.31714,0.6563 0,0.8516 l 3.25,2 c 0.33298,0.2045 0.76131,-0.035 0.76172,-0.4258 v -4 c 1.1e-4,-0.2823 -0.23341,-0.5088 -0.51562,-0.5 z" + style="display:inline;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.5;stroke-opacity:1;marker:none;enable-background:new" /> + + + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.5;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 467.5,10.000005 a 0.50005006,0.50005006 0 0 0 -0.5,0.5 v 15 a 0.50005006,0.50005006 0 0 0 0.5,0.5 h 15 a 0.50005006,0.50005006 0 0 0 0.5,-0.5 v -15 a 0.50005006,0.50005006 0 0 0 -0.5,-0.5 z m 0.5,0.999999 h 4 v 4 h -4 z m 5,0 h 4 v 4 h -4 z m 5,0 h 4 v 4 h -4 z m -10,5 h 4 v 4.000001 h -4 z m 5,0 h 4 v 4.000001 h -4 z m 5,0 h 4 v 4.000001 h -4 z m -10,5.000001 h 4 v 4 h -4 z m 5,0 h 4 v 4 h -4 z m 5,0 h 4 v 4 h -4 z" + id="rect15343" + inkscape:connector-curvature="0" /> + id="g30738" + inkscape:label="A-22"> + id="path15369" + d="m 449.75,12.000004 a 0.50005006,0.50005006 0 0 0 -0.4824,0.36914 l -3.25,12.000001 a 0.50005006,0.50005006 0 0 0 0.4824,0.63086 h 15 a 0.50005006,0.50005006 0 0 0 0.4824,-0.63086 l -3.25,-12.000001 a 0.50005006,0.50005006 0 0 0 -0.4824,-0.36914 z m 0.3828,1 h 2.086 l -0.2169,2 h -2.4121 z m 3.0938,0 h 1.5468 l 0.2168,2 h -1.9804 z m 2.5547,0 h 2.0859 l 0.541,2 h -2.4101 z m -6.461,3 h 2.5723 l -0.3262,3 h -3.0586 z m 3.5801,0 h 2.1992 l 0.3262,3 h -2.8516 z m 3.207,0 h 2.5723 l 0.8125,3 h -3.0586 z m -7.8711,4.000001 h 3.2207 l -0.4336,4 h -3.8711 z m 4.2305,0 h 3.0664 l 0.4356,4 h -3.9375 z m 4.0762,0 h 3.2207 l 1.084,4 h -3.8711 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.5;stroke-opacity:1;marker:none;paint-order:fill markers stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + + + d="m -503.5,1348.5 h -2 v 2 h 2 z" + style="display:inline;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;enable-background:new" + id="path25851-1" /> - - + id="path25853-8" + d="m -498.49506,1361.4978 -2.00459,-2.0055 c -2.16266,-2.1636 -3.0003,-5.1361 -3.0003,-7.9852 l 0.01,-1.0071" + style="opacity:0.7;vector-effect:none;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal" /> - - + sodipodi:nodetypes="csc" /> + id="g25861-1" + transform="translate(-47,-2)"> + - - - - - + sodipodi:nodetypes="cc" /> + transform="rotate(90,-479.5,1328.5)" + id="g25873-3"> - + d="m -457.50994,1359.5 h -2 v 2 h 2 z" + style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="path25869-0" + sodipodi:nodetypes="ccccc" /> + + transform="matrix(1,0,0,-1,812,1351.9941)" + id="g25919-9" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="A-17"> - + transform="translate(834,-1254)" + id="g26210-2" + style="display:inline;fill:#ffffff;enable-background:new" + inkscape:label="A-16"> - - - + inkscape:connector-curvature="0" + id="path24970-9" + d="m -511.49974,1278.4919 -2.6e-4,-2.1822 c 2e-5,-2.8698 1.14768,-5.4099 3,-6.8448 1.85234,-1.4349 4.14766,-1.4349 6,0 1.85233,1.4349 2.99998,4.0575 3,6.9272 v 2.0998" + style="display:inline;overflow:visible;visibility:visible;opacity:0.7;fill:none;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + sodipodi:nodetypes="ccsccc" /> + transform="translate(-729.00712,939.005)" + style="opacity:1" + id="g24984-6"> + sodipodi:nodetypes="cccccccccc" + id="path24980-2" + style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="m 228.50712,326.49495 h 2 v 2 h -2 z m -10.00994,0 h -2 v 2 h 2 z" + inkscape:connector-curvature="0" /> + id="path24982-2" + style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal" + d="m 228.5043,327.5 -2.49718,-0.005 M 218.5,327.5 l 2.50712,-0.005" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccc" /> + + + + + + + + + + sodipodi:nodetypes="ccsccc" + style="display:inline;overflow:visible;visibility:visible;opacity:0.7;fill:none;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + d="m -510.49974,1338.4932 -2.6e-4,-0.2387 c 0,-2.5685 1.14768,-5.2795 3,-6.6953 1.85233,-1.4159 4.14765,-1.4159 5.99999,0 1.85233,1.4158 3,4.1268 3,6.6953 v 0.2387" + id="path25297-7" + inkscape:connector-curvature="0" /> + + + + + + + + + + + + + + + + + inkscape:export-ydpi="96" + inkscape:export-xdpi="96" + inkscape:export-filename="blender_icons.png" + id="g13337" + style="display:inline;opacity:0.99;fill:#ffffff;enable-background:new" + transform="matrix(-1,0,0,1,1458,302.99979)" + inkscape:label="A-10"> + sodipodi:nodetypes="ccccc" /> + sodipodi:nodetypes="cccccccc" /> + sodipodi:nodetypes="sssss" /> + - + + + + inkscape:export-ydpi="96" + inkscape:export-xdpi="96" + inkscape:export-filename="blender_icons.png" + style="display:inline;opacity:1;enable-background:new" + id="g13408-5" + transform="translate(-357.00711,41.992878)"> - - + style="display:inline;opacity:0.99;enable-background:new" + transform="translate(378.99999,-439.99995)" + id="g12509-8" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 531.49219,52.992188 a 0.50005,0.50005 0 0 0 -0.31641,0.121093 0.50005,0.50005 0 0 0 -0.0195,0.01563 0.50005,0.50005 0 0 0 -0.0176,0.01758 0.50005,0.50005 0 0 0 -0.002,0.0039 0.50005,0.50005 0 0 0 -0.0312,0.0332 0.50005,0.50005 0 0 0 -0.002,0.0039 0.50005,0.50005 0 0 0 -0.0273,0.03711 0.50005,0.50005 0 0 0 -0.002,0.0039 A 0.50005,0.50005 0 0 0 531,53.582031 V 54.5 a 0.50005,0.50005 0 1 0 1,0 V 54 h 0.5 a 0.50005,0.50005 0 1 0 0,-1 h -0.91992 a 0.50005,0.50005 0 0 0 -0.0879,-0.0078 z m 13,0 A 0.50005,0.50005 0 0 0 544.41797,53 H 543.5 a 0.50005,0.50005 0 1 0 0,1 h 0.5 v 0.5 a 0.50005,0.50005 0 1 0 1,0 v -0.919922 a 0.50005,0.50005 0 0 0 -0.11328,-0.404297 0.50005,0.50005 0 0 0 -0.002,-0.0039 0.50005,0.50005 0 0 0 -0.0312,-0.0332 0.50005,0.50005 0 0 0 -0.004,-0.002 0.50005,0.50005 0 0 0 -0.0332,-0.03125 0.50005,0.50005 0 0 0 -0.004,-0.002 0.50005,0.50005 0 0 0 -0.32031,-0.111328 z M 534.5,53 a 0.50005,0.50005 0 1 0 0,1 h 1 a 0.50005,0.50005 0 1 0 0,-1 z m 3,0 a 0.50005,0.50005 0 1 0 0,1 h 1 a 0.50005,0.50005 0 1 0 0,-1 z m 3,0 a 0.50005,0.50005 0 1 0 0,1 h 1 a 0.50005,0.50005 0 1 0 0,-1 z m -9.00781,2.992188 A 0.50005,0.50005 0 0 0 531,56.5 v 1 a 0.50005,0.50005 0 1 0 1,0 v -1 a 0.50005,0.50005 0 0 0 -0.50781,-0.507812 z m 13,0 A 0.50005,0.50005 0 0 0 544,56.5 v 1 a 0.50005,0.50005 0 1 0 1,0 v -1 a 0.50005,0.50005 0 0 0 -0.50781,-0.507812 z m -13,3 A 0.50005,0.50005 0 0 0 531,59.5 v 1 a 0.50005,0.50005 0 1 0 1,0 v -1 a 0.50005,0.50005 0 0 0 -0.50781,-0.507812 z m 13,0 A 0.50005,0.50005 0 0 0 544,59.5 v 1 a 0.50005,0.50005 0 1 0 1,0 v -1 a 0.50005,0.50005 0 0 0 -0.50781,-0.507812 z m -13,3 A 0.50005,0.50005 0 0 0 531,62.5 v 1 a 0.50005,0.50005 0 1 0 1,0 v -1 a 0.50005,0.50005 0 0 0 -0.50781,-0.507812 z m 13,0 A 0.50005,0.50005 0 0 0 544,62.5 v 1 a 0.50005,0.50005 0 1 0 1,0 v -1 a 0.50005,0.50005 0 0 0 -0.50781,-0.507812 z m -13,3 A 0.50005,0.50005 0 0 0 531,65.5 v 0.919922 a 0.50005,0.50005 0 0 0 0.11328,0.404297 0.50005,0.50005 0 0 0 0.0156,0.01953 0.50005,0.50005 0 0 0 0.0176,0.01758 0.50005,0.50005 0 0 0 0.004,0.002 0.50005,0.50005 0 0 0 0.0332,0.03125 0.50005,0.50005 0 0 0 0.004,0.002 A 0.50005,0.50005 0 0 0 531.58203,67 H 532.5 a 0.50005,0.50005 0 1 0 0,-1 H 532 v -0.5 a 0.50005,0.50005 0 0 0 -0.50781,-0.507812 z m 13,0 A 0.50005,0.50005 0 0 0 544,65.5 V 66 h -0.5 a 0.50005,0.50005 0 1 0 0,1 h 0.91992 a 0.50005,0.50005 0 0 0 0.4043,-0.113281 0.50005,0.50005 0 0 0 0.004,-0.002 0.50005,0.50005 0 0 0 0.0332,-0.03125 0.50005,0.50005 0 0 0 0.002,-0.0039 0.50005,0.50005 0 0 0 0.0312,-0.0332 0.50005,0.50005 0 0 0 0.002,-0.0039 A 0.50005,0.50005 0 0 0 545,66.417969 V 65.5 a 0.50005,0.50005 0 0 0 -0.50781,-0.507812 z M 534.5,66 a 0.50005,0.50005 0 1 0 0,1 h 1 a 0.50005,0.50005 0 1 0 0,-1 z m 3,0 a 0.50005,0.50005 0 1 0 0,1 h 1 a 0.50005,0.50005 0 1 0 0,-1 z m 3,0 a 0.50005,0.50005 0 1 0 0,1 h 1 a 0.50005,0.50005 0 1 0 0,-1 z" + id="path27987" + inkscape:connector-curvature="0" /> - - - - - + transform="rotate(180,149.5,102.00001)" + id="g27919-7" + style="display:inline;enable-background:new"> + + style="display:inline;fill:#ffffff;enable-background:new" + id="g14588" + transform="translate(-42.000002,25.000005)" + inkscape:label="A-6"> + - - - - - - - - - - - + id="g14560" + transform="translate(147)" + style="fill:#ffffff"> + - - - + id="path14394" + d="m 98.999998,11.000004 v 4.000002 h -1 -1 v 3 h -1 v -2 h -1 v 5.000001 h -1 v -2.000001 h -1 v 3.560548 h -1 v -2.085938 h -1 v 2.525389 h -1 v 2 L 104,25.000007 v -2.421875 h -1 v -4.578126 h -1 v 2.000001 h -1 v -6.000003 h -1.000002 v -3 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" /> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 76,11 c -0.08389,-2.87e-4 -0.166503,0.02054 -0.240234,0.06055 l -5.5,3 C 70.099297,14.148474 69.999666,14.317023 70,14.5 v 7 c -3.35e-4,0.182978 0.0993,0.351528 0.259766,0.439453 l 5.5,3 C 75.833498,24.979467 75.916111,25.000288 76,25 h 1 c 0.08389,2.87e-4 0.166503,-0.02053 0.240234,-0.06055 l 5.5,-3 C 82.900703,21.851526 83.000334,21.682977 83,21.5 v -7 c 3.35e-4,-0.182978 -0.0993,-0.351528 -0.259766,-0.439453 l -5.5,-3 C 77.166502,11.020533 77.083889,10.999712 77,11 Z m 0.382812,2 h 0.240235 L 81,15.390625 v 5.21875 L 76.617188,23 H 76.382812 L 72,20.609375 v -5.21875 z" + id="path14426" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccccccccccccccccccccc" /> + + + + + + + + + + transform="matrix(1,0,0,-1,-294,368)" + id="g4806-1" + style="opacity:1;fill:#ffffff"> + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new" + d="m 300.5,375 a 0.50005,0.50005 0 0 0 -0.5,0.5 v 3 a 0.50005,0.50005 0 0 0 0.5,0.5 h 11 a 0.50005,0.50005 0 0 0 0.5,-0.5 v -3 a 0.50005,0.50005 0 0 0 -0.5,-0.5 z m 0.5,1 h 10 v 2 h -10 z" + id="path4801-8" + inkscape:connector-curvature="0" /> + + Date: Wed, 6 Jul 2022 11:53:18 -0500 Subject: Fix T99464: Curves sculpt add 3D brush symmetry broken The brush transform was not applied to the view direction. --- source/blender/editors/sculpt_paint/curves_sculpt_add.cc | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc index e5e6cfef8ae..401550b3b3a 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc @@ -353,17 +353,21 @@ struct AddOperationExecutor { view_ray_start_wo, view_ray_end_wo, true); - const float3 view_direction_su = math::normalize( - transforms_.world_to_surface * view_ray_end_wo - - transforms_.world_to_surface * view_ray_start_wo); + + const float3 view_ray_start_cu = transforms_.world_to_curves * view_ray_start_wo; + const float3 view_ray_end_cu = transforms_.world_to_curves * view_ray_end_wo; const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( eCurvesSymmetryType(curves_id_->symmetry)); for (const float4x4 &brush_transform : symmetry_brush_transforms) { const float4x4 transform = transforms_.curves_to_surface * brush_transform; + const float3 brush_pos_su = transform * brush_3d->position_cu; + const float3 view_direction_su = math::normalize(transform * view_ray_end_cu - + transform * view_ray_start_cu); const float brush_radius_su = transform_brush_radius( transform, brush_3d->position_cu, brush_3d->radius_cu); + this->sample_spherical( rng, r_added_points, brush_pos_su, brush_radius_su, view_direction_su); } -- cgit v1.2.3 From debb2337877ae333b6cf3e4dc3876a6ded4f08c3 Mon Sep 17 00:00:00 2001 From: Xavier Hallade Date: Wed, 6 Jul 2022 18:50:25 +0200 Subject: Cleanup: fix comments in oneAPI kernel.cpp --- intern/cycles/kernel/device/oneapi/kernel.cpp | 36 +++++++++++++-------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/intern/cycles/kernel/device/oneapi/kernel.cpp b/intern/cycles/kernel/device/oneapi/kernel.cpp index ec979db2455..329d8c032f7 100644 --- a/intern/cycles/kernel/device/oneapi/kernel.cpp +++ b/intern/cycles/kernel/device/oneapi/kernel.cpp @@ -104,11 +104,10 @@ bool oneapi_usm_memcpy(SyclQueue *queue_, void *dest, void *src, size_t num_byte oneapi_check_usm(queue_, dest, true); oneapi_check_usm(queue_, src, true); sycl::event mem_event = queue->memcpy(dest, src, num_bytes); -#ifdef WITH_CYCLES_DEBUG +# ifdef WITH_CYCLES_DEBUG try { - /* NOTE(@nsirgien) Waiting on memory operation may give more preciese error - * messages in case of the problems, but due to impact on occupancy - * make sense enable it only during cycles debugging + /* NOTE(@nsirgien) Waiting on memory operation may give more precise error + * messages. Due to impact on occupancy, it makes sense to enable it only during Cycles debug. */ mem_event.wait_and_throw(); return true; @@ -119,20 +118,20 @@ bool oneapi_usm_memcpy(SyclQueue *queue_, void *dest, void *src, size_t num_byte } return false; } -#else +# else sycl::usm::alloc dest_type = get_pointer_type(dest, queue->get_context()); sycl::usm::alloc src_type = get_pointer_type(src, queue->get_context()); - bool from_device_to_host - = dest_type == sycl::usm::alloc::host && src_type == sycl::usm::alloc::device; - bool host_or_device_memop_with_offset - = dest_type == sycl::usm::alloc::unknown || src_type == sycl::usm::alloc::unknown; - /* NOTE(@sirgienko) Host-side blocking wait on this operations is mandatory, host - * may don't wait until end of transfer before using the memory. + bool from_device_to_host = dest_type == sycl::usm::alloc::host && + src_type == sycl::usm::alloc::device; + bool host_or_device_memop_with_offset = dest_type == sycl::usm::alloc::unknown || + src_type == sycl::usm::alloc::unknown; + /* NOTE(@sirgienko) Host-side blocking wait on this operation is mandatory, otherwise the host + * may not wait until the end of the transfer before using the memory. */ - if(from_device_to_host || host_or_device_memop_with_offset) + if (from_device_to_host || host_or_device_memop_with_offset) mem_event.wait(); return true; -#endif +# endif } bool oneapi_usm_memset(SyclQueue *queue_, void *usm_ptr, unsigned char value, size_t num_bytes) @@ -141,11 +140,10 @@ bool oneapi_usm_memset(SyclQueue *queue_, void *usm_ptr, unsigned char value, si sycl::queue *queue = reinterpret_cast(queue_); oneapi_check_usm(queue_, usm_ptr, true); sycl::event mem_event = queue->memset(usm_ptr, value, num_bytes); -#ifdef WITH_CYCLES_DEBUG +# ifdef WITH_CYCLES_DEBUG try { - /* NOTE(@nsirgien) Waiting on memory operation may give more preciese error - * messages in case of the problems, but due to impact on occupancy - * make sense enable it only during cycles debugging + /* NOTE(@nsirgien) Waiting on memory operation may give more precise error + * messages. Due to impact on occupancy, it makes sense to enable it only during Cycles debug. */ mem_event.wait_and_throw(); return true; @@ -156,10 +154,10 @@ bool oneapi_usm_memset(SyclQueue *queue_, void *usm_ptr, unsigned char value, si } return false; } -#else +# else (void)mem_event; return true; -#endif +# endif } bool oneapi_queue_synchronize(SyclQueue *queue_) -- cgit v1.2.3 From 190ad735903f7e952629ddce9ceb12b5dcf8ecdc Mon Sep 17 00:00:00 2001 From: Xavier Hallade Date: Wed, 6 Jul 2022 18:39:47 +0200 Subject: Cycles oneAPI: Remove direct dependency on Level-Zero We used it only to access device id for explicitly allowing Arc GPUs. It made the backend require ze_loader.dll which could be problematic if we end up using direct linking. I've replaced filtering based on PCI device id by using other HW properties instead (EUs, threads per EU), that are now available through Level-Zero. --- intern/cycles/kernel/CMakeLists.txt | 3 --- intern/cycles/kernel/device/oneapi/device_id.h | 11 --------- intern/cycles/kernel/device/oneapi/kernel.cpp | 33 +++++++++++++------------- 3 files changed, 17 insertions(+), 30 deletions(-) delete mode 100644 intern/cycles/kernel/device/oneapi/device_id.h diff --git a/intern/cycles/kernel/CMakeLists.txt b/intern/cycles/kernel/CMakeLists.txt index ccd694dfdfd..b893ff6ef24 100644 --- a/intern/cycles/kernel/CMakeLists.txt +++ b/intern/cycles/kernel/CMakeLists.txt @@ -86,7 +86,6 @@ set(SRC_KERNEL_DEVICE_ONEAPI_HEADERS device/oneapi/compat.h device/oneapi/context_begin.h device/oneapi/context_end.h - device/oneapi/device_id.h device/oneapi/globals.h device/oneapi/image.h device/oneapi/kernel.h @@ -732,8 +731,6 @@ if(WITH_CYCLES_DEVICE_ONEAPI) -O2 -o ${cycles_kernel_oneapi_lib} -I${CMAKE_CURRENT_SOURCE_DIR}/.. - -I${LEVEL_ZERO_INCLUDE_DIR} - ${LEVEL_ZERO_LIBRARY} ${SYCL_CPP_FLAGS} ) diff --git a/intern/cycles/kernel/device/oneapi/device_id.h b/intern/cycles/kernel/device/oneapi/device_id.h deleted file mode 100644 index b4c94ac27a2..00000000000 --- a/intern/cycles/kernel/device/oneapi/device_id.h +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: Apache-2.0 - * Copyright 2021-2022 Intel Corporation */ - -#pragma once - -/* from public source : - * https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/include/pci_ids/iris_pci_ids.h */ -const static std::set intel_arc_alchemist_device_ids = { - 0x4f80, 0x4f81, 0x4f82, 0x4f83, 0x4f84, 0x4f87, 0x4f88, 0x5690, 0x5691, - 0x5692, 0x5693, 0x5694, 0x5695, 0x5696, 0x5697, 0x56a0, 0x56a1, 0x56a2, - 0x56a3, 0x56a4, 0x56a5, 0x56a6, 0x56b0, 0x56b1, 0x56b2, 0x56b3}; diff --git a/intern/cycles/kernel/device/oneapi/kernel.cpp b/intern/cycles/kernel/device/oneapi/kernel.cpp index 329d8c032f7..9b9c046de4c 100644 --- a/intern/cycles/kernel/device/oneapi/kernel.cpp +++ b/intern/cycles/kernel/device/oneapi/kernel.cpp @@ -9,12 +9,9 @@ # include # include -# include # include -# include # include "kernel/device/oneapi/compat.h" -# include "kernel/device/oneapi/device_id.h" # include "kernel/device/oneapi/globals.h" # include "kernel/device/oneapi/kernel_templates.h" @@ -752,21 +749,25 @@ static std::vector oneapi_available_devices() else { bool filter_out = false; - /* For now we support all Intel(R) Arc(TM) devices - * and any future GPU with more than 128 execution units - * official support can be broaden to older and smaller GPUs once ready. */ + /* For now we support all Intel(R) Arc(TM) devices and likely any future GPU, + * assuming they have either more than 96 Execution Units or not 7 threads per EU. + * Official support can be broaden to older and smaller GPUs once ready. */ if (device.is_gpu() && platform.get_backend() == sycl::backend::ext_oneapi_level_zero) { - ze_device_handle_t ze_device = sycl::get_native( - device); - ze_device_properties_t props = {ZE_STRUCTURE_TYPE_DEVICE_PROPERTIES}; - zeDeviceGetProperties(ze_device, &props); - bool is_dg2 = (intel_arc_alchemist_device_ids.find(props.deviceId) != - intel_arc_alchemist_device_ids.end()); - int number_of_eus = props.numEUsPerSubslice * props.numSubslicesPerSlice * - props.numSlices; - if (!is_dg2 || number_of_eus < 128) + /* Filtered-out defaults in-case these values aren't available through too old L0 + * runtime. */ + int number_of_eus = 96; + int threads_per_eu = 7; + if (device.has(sycl::aspect::ext_intel_gpu_eu_count)) { + number_of_eus = device.get_info(); + } + if (device.has(sycl::aspect::ext_intel_gpu_hw_threads_per_eu)) { + threads_per_eu = + device.get_info(); + } + /* This filters out all Level-Zero supported GPUs from older generation than Arc. */ + if (number_of_eus <= 96 && threads_per_eu == 7) { filter_out = true; - + } /* if not already filtered out, check driver version. */ if (!filter_out) { int driver_build_version = parse_driver_build_version(device); -- cgit v1.2.3 From 2d041fc46823810219280f34a46a9cedd7a1151d Mon Sep 17 00:00:00 2001 From: Aras Pranckevicius Date: Wed, 6 Jul 2022 21:30:35 +0300 Subject: Object: Speed up duplication of large selections by doing fewer collection syncs Previous code was doing N collection syncs when duplicating N objects. New code avoids all the intermediate syncs by using BKE_layer_collection_resync_forbid and BKE_layer_collection_resync_allow, and then does one BKE_main_collection_sync + BKE_main_collection_sync_remap for the whole operation. There is some complexity involved where the Base things of newly duplicated objects can't be found yet, without the sync, so some work on them (marking them selected, active, ...) has to be deferred until after the sync. Timings: scene with 10k cubes, each with unique mesh (Windows, VS2022 Release build, AMD Ryzen 5950X): - Shift+D duplicate: 13.6s -> 9.2s - Alt+D duplicate: 4.76s -> 1.53s Reviewed By: Bastien Montagne Differential Revision: https://developer.blender.org/D14150 --- source/blender/editors/object/object_add.cc | 85 ++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 25 deletions(-) diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc index 41aaede8d6f..64026f7b06b 100644 --- a/source/blender/editors/object/object_add.cc +++ b/source/blender/editors/object/object_add.cc @@ -36,6 +36,7 @@ #include "BLI_math.h" #include "BLI_string.h" #include "BLI_utildefines.h" +#include "BLI_vector.hh" #include "BLT_translation.h" @@ -3600,7 +3601,8 @@ static Base *object_add_duplicate_internal(Main *bmain, ViewLayer *view_layer, Object *ob, const eDupli_ID_Flags dupflag, - const eLibIDDuplicateFlags duplicate_options) + const eLibIDDuplicateFlags duplicate_options, + Object **r_ob_new) { Base *base, *basen = nullptr; Object *obn; @@ -3611,6 +3613,9 @@ static Base *object_add_duplicate_internal(Main *bmain, else { obn = static_cast( ID_NEW_SET(ob, BKE_object_duplicate(bmain, ob, dupflag, duplicate_options))); + if (r_ob_new) { + *r_ob_new = obn; + } DEG_id_tag_update(&obn->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); base = BKE_view_layer_base_find(view_layer, ob); @@ -3623,7 +3628,7 @@ static Base *object_add_duplicate_internal(Main *bmain, } basen = BKE_view_layer_base_find(view_layer, obn); - if (base != nullptr) { + if (base != nullptr && basen != nullptr) { basen->local_view_bits = base->local_view_bits; } @@ -3654,7 +3659,8 @@ Base *ED_object_add_duplicate( base->object, dupflag, LIB_ID_DUPLICATE_IS_SUBPROCESS | - LIB_ID_DUPLICATE_IS_ROOT_ID); + LIB_ID_DUPLICATE_IS_ROOT_ID, + nullptr); if (basen == nullptr) { return nullptr; } @@ -3687,45 +3693,73 @@ static int duplicate_exec(bContext *C, wmOperator *op) ViewLayer *view_layer = CTX_data_view_layer(C); const bool linked = RNA_boolean_get(op->ptr, "linked"); const eDupli_ID_Flags dupflag = (linked) ? (eDupli_ID_Flags)0 : (eDupli_ID_Flags)U.dupflag; - bool changed = false; /* We need to handle that here ourselves, because we may duplicate several objects, in which case * we also want to remap pointers between those... */ BKE_main_id_newptr_and_tag_clear(bmain); + /* Do not do collection re-syncs for each object; will do it once afterwards. + * However this means we can't get to new duplicated Base's immediately, will + * have to process them after the sync. */ + BKE_layer_collection_resync_forbid(); + + /* Duplicate the selected objects, remember data needed to process + * after the sync (the base of the original object, and the copy of the + * original object). */ + blender::Vector> source_bases_new_objects; + Object *ob_new_active = nullptr; + CTX_DATA_BEGIN (C, Base *, base, selected_bases) { - Base *basen = object_add_duplicate_internal(bmain, - scene, - view_layer, - base->object, - dupflag, - LIB_ID_DUPLICATE_IS_SUBPROCESS | - LIB_ID_DUPLICATE_IS_ROOT_ID); + Object *ob_new = NULL; + object_add_duplicate_internal(bmain, + scene, + view_layer, + base->object, + dupflag, + LIB_ID_DUPLICATE_IS_SUBPROCESS | LIB_ID_DUPLICATE_IS_ROOT_ID, + &ob_new); + if (ob_new == nullptr) { + continue; + } + source_bases_new_objects.append({base, ob_new}); /* note that this is safe to do with this context iterator, * the list is made in advance */ ED_object_base_select(base, BA_DESELECT); - ED_object_base_select(basen, BA_SELECT); - changed = true; - - if (basen == nullptr) { - continue; - } - /* new object becomes active */ + /* new object will become active */ if (BASACT(view_layer) == base) { - ED_object_base_activate(C, basen); - } - - if (basen->object->data) { - DEG_id_tag_update(static_cast(basen->object->data), 0); + ob_new_active = ob_new; } } CTX_DATA_END; - if (!changed) { + if (source_bases_new_objects.is_empty()) { return OPERATOR_CANCELLED; } + /* Sync the collection now, after everything is duplicated. */ + BKE_layer_collection_resync_allow(); + BKE_main_collection_sync(bmain); + + /* After sync we can get to the new Base data, process it here. */ + for (const auto &item : source_bases_new_objects) { + Object *ob_new = item.second; + Base *base_source = item.first; + Base *base_new = BKE_view_layer_base_find(view_layer, ob_new); + if (base_new == nullptr) { + continue; + } + ED_object_base_select(base_new, BA_SELECT); + if (ob_new == ob_new_active) { + ED_object_base_activate(C, base_new); + } + if (base_new->object->data) { + DEG_id_tag_update(static_cast(base_new->object->data), 0); + } + /* #object_add_duplicate_internal will not have done this, since + * before the collection sync it would not have found the new base yet. */ + base_new->local_view_bits = base_source->local_view_bits; + } /* Note that this will also clear newid pointers and tags. */ copy_object_set_idnew(C); @@ -3808,7 +3842,8 @@ static int object_add_named_exec(bContext *C, wmOperator *op) * the case here. So we have to do the new-ID relinking ourselves * (#copy_object_set_idnew()). */ - LIB_ID_DUPLICATE_IS_SUBPROCESS | LIB_ID_DUPLICATE_IS_ROOT_ID); + LIB_ID_DUPLICATE_IS_SUBPROCESS | LIB_ID_DUPLICATE_IS_ROOT_ID, + nullptr); if (basen == nullptr) { BKE_report(op->reports, RPT_ERROR, "Object could not be duplicated"); -- cgit v1.2.3 From 2a60b979cca272f01acf3a08a5517599b94a12f2 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 6 Jul 2022 15:03:21 -0500 Subject: UI: Adjust and fix shader node descriptions The tooltips added for shader nodes in D15309 are very helpful already. This commit makes a few tweaks to make them more consistent, concise, and includes some fixes. Note that this might move some descriptions further away from the wording in the manual. * Make wording more concise * Start fewer new sentences * Use "For Example" slightly less * Avoid repeating the node's name unnecessarily * Spelling/grammar fixes * Don't capitalize some words * Use consistent verb conjugation * Use ASCII characters/more common quote symbols * Corrections to information * Plural/singular corrections * "smoke domains" -> "volume grids" * Fix tooltip for separate and combine color nodes * Refer to color sockets as colors rather than "images" * Avoid "advice" in a few places, which should be left for the manual * Remove information for sockets and could be in their tooltips * Avoid referring to the locations of a property in the UI * Avoid manual newlines (mostly reserve for "Note:") * Leave UI code in control of wrapping, which is more consistent * Add some information * That the UV map and color attribute nodes use a default * That Voronoi is "based on the distance to random points" * Add "(Deprecated)" to old color combine and separate nodes Differential Revision: https://developer.blender.org/D15381 --- source/blender/nodes/NOD_static_types.h | 116 ++++++++++++++++---------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 609791ad091..302656b8a7c 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -17,111 +17,111 @@ /* WARNING! If you edit those strings, please do the same in relevant nodes files (under blender/nodes/...)! */ /* Tree type Node ID RNA def function Enum name Struct name UI Name UI Description */ -DefNode(Node, NODE_FRAME, def_frame, "FRAME", Frame, "Frame", "Collect related nodes together in a common area.\nFrames are useful when a node setup becomes large and confusing yet the re-usability of a Node Group is not required") +DefNode(Node, NODE_FRAME, def_frame, "FRAME", Frame, "Frame", "Collect related nodes together in a common area. Useful for organization when the re-usability of a node group is not required") DefNode(Node, NODE_GROUP, def_group, "GROUP", Group, "Group", "") -DefNode(Node, NODE_GROUP_INPUT, def_group_input, "GROUP_INPUT", GroupInput, "Group Input", "While inside a group, expose connected data as sockets in the node gruop interface") -DefNode(Node, NODE_GROUP_OUTPUT, def_group_output, "GROUP_OUTPUT", GroupOutput, "Group Output", "While inside a group, output connected data") -DefNode(Node, NODE_REROUTE, 0, "REROUTE", Reroute, "Reroute", "Used for organizing.\nReroute looks and behaves much like a socket on other nodes in that it supports one input connection while allowing multiple output connections") +DefNode(Node, NODE_GROUP_INPUT, def_group_input, "GROUP_INPUT", GroupInput, "Group Input", "Expose connected data from inside a node group as inputs to its interface") +DefNode(Node, NODE_GROUP_OUTPUT, def_group_output, "GROUP_OUTPUT", GroupOutput, "Group Output", "Output data from inside of a node group") +DefNode(Node, NODE_REROUTE, 0, "REROUTE", Reroute, "Reroute", "A single-socket organization tool that supports one input and multiple outputs") DefNode(ShaderNode, SH_NODE_RGB, 0, "RGB", RGB, "RGB", "A color picker") -DefNode(ShaderNode, SH_NODE_VALUE, 0, "VALUE", Value, "Value", "A node to input numerical values to other nodes in the tree") -DefNode(ShaderNode, SH_NODE_MIX_RGB, def_mix_rgb, "MIX_RGB", MixRGB, "MixRGB", "Mix colors by working on the individual and corresponding pixels of the two input colors") +DefNode(ShaderNode, SH_NODE_VALUE, 0, "VALUE", Value, "Value", "Used to Input numerical values to other nodes in the tree") +DefNode(ShaderNode, SH_NODE_MIX_RGB, def_mix_rgb, "MIX_RGB", MixRGB, "MixRGB", "Mix two input colors") DefNode(ShaderNode, SH_NODE_VALTORGB, def_colorramp, "VALTORGB", ValToRGB, "ColorRamp", "Map values to colors with the use of a gradient") -DefNode(ShaderNode, SH_NODE_RGBTOBW, 0, "RGBTOBW", RGBToBW, "RGB to BW", "Convert an RGB color image to a grayscale by the luminance") -DefNode(ShaderNode, SH_NODE_SHADERTORGB, 0, "SHADERTORGB", ShaderToRGB, "Shader to RGB", "Convert rendering effect (such as light and shadow) to color.\nTypically used for non-photorealistic rendering, to apply additional effects on the output of BSDFs.\nFor example, a color ramp on the output of a diffuse BSDF can be used to create a flexible toon shader.\nNote: only supported for Eevee") +DefNode(ShaderNode, SH_NODE_RGBTOBW, 0, "RGBTOBW", RGBToBW, "RGB to BW", "Convert a color's luminance to a grayscale value") +DefNode(ShaderNode, SH_NODE_SHADERTORGB, 0, "SHADERTORGB", ShaderToRGB, "Shader to RGB", "Convert rendering effect (such as light and shadow) to color. Typically used for non-photorealistic rendering, to apply additional effects on the output of BSDFs.\nNote: only supported for Eevee") DefNode(ShaderNode, SH_NODE_NORMAL, 0, "NORMAL", Normal, "Normal", "Generate a normal vector and a dot product") DefNode(ShaderNode, SH_NODE_GAMMA, 0, "GAMMA", Gamma, "Gamma", "Apply a gamma correction") DefNode(ShaderNode, SH_NODE_BRIGHTCONTRAST, 0, "BRIGHTCONTRAST", BrightContrast, "Bright Contrast", "Control the brightness and contrast of the input color") -DefNode(ShaderNode, SH_NODE_MAPPING, def_sh_mapping, "MAPPING", Mapping, "Mapping", "Transform the input vector by applying translation, rotation, and scaling") -DefNode(ShaderNode, SH_NODE_CURVE_VEC, def_vector_curve, "CURVE_VEC", VectorCurve, "Vector Curves", "Map an input vector component to a curve, Used to fine-tune the interpolation of the input") +DefNode(ShaderNode, SH_NODE_MAPPING, def_sh_mapping, "MAPPING", Mapping, "Mapping", "Transform the input vector by applying translation, rotation, and scale") +DefNode(ShaderNode, SH_NODE_CURVE_VEC, def_vector_curve, "CURVE_VEC", VectorCurve, "Vector Curves", "Map an input vectors to curves, used to fine-tune the interpolation of the input") DefNode(ShaderNode, SH_NODE_CURVE_RGB, def_rgb_curve, "CURVE_RGB", RGBCurve, "RGB Curves", "Apply color corrections for each color channel") -DefNode(ShaderNode, SH_NODE_CAMERA, 0, "CAMERA", CameraData, "Camera Data", "Use to get information about the position of the object relative to the camera.\nFor Example: To change the shading of objects further away from the camera, or make custom fog effects") +DefNode(ShaderNode, SH_NODE_CAMERA, 0, "CAMERA", CameraData, "Camera Data", "Retrieve information about the camera and how it relates to the current shading point's position") DefNode(ShaderNode, SH_NODE_MAP_RANGE, def_map_range, "MAP_RANGE", MapRange, "Map Range", "Remap a value from a range to a target range") DefNode(ShaderNode, SH_NODE_CLAMP, def_clamp, "CLAMP", Clamp, "Clamp", "Clamp a value between a minimum and a maximum") DefNode(ShaderNode, SH_NODE_MATH, def_math, "MATH", Math, "Math", "Perform math operations") DefNode(ShaderNode, SH_NODE_VECTOR_MATH, def_vector_math, "VECT_MATH", VectorMath, "Vector Math", "Perform vector math operation") DefNode(ShaderNode, SH_NODE_SQUEEZE, 0, "SQUEEZE", Squeeze, "Squeeze Value", "") -DefNode(ShaderNode, SH_NODE_INVERT, 0, "INVERT", Invert, "Invert", "Inverts a color, producing a negative") -DefNode(ShaderNode, SH_NODE_SEPRGB_LEGACY, 0, "SEPRGB", SeparateRGB, "Separate RGB", "Split an image into its red, green and blue channels") -DefNode(ShaderNode, SH_NODE_COMBRGB_LEGACY, 0, "COMBRGB", CombineRGB, "Combine RGB", "Combine an image from its red, green and blue channels") -DefNode(ShaderNode, SH_NODE_HUE_SAT, 0, "HUE_SAT", HueSaturation, "Hue Saturation Value","Apply a color transformation in the HSV Color Model") +DefNode(ShaderNode, SH_NODE_INVERT, 0, "INVERT", Invert, "Invert", "Invert a color, producing a negative") +DefNode(ShaderNode, SH_NODE_SEPRGB_LEGACY, 0, "SEPRGB", SeparateRGB, "Separate RGB", "Split a color into its red, green, and blue channels (Deprecated)") +DefNode(ShaderNode, SH_NODE_COMBRGB_LEGACY, 0, "COMBRGB", CombineRGB, "Combine RGB", "Generate a color from its red, green, and blue channels (Deprecated)") +DefNode(ShaderNode, SH_NODE_HUE_SAT, 0, "HUE_SAT", HueSaturation, "Hue Saturation Value","Apply a color transformation in the HSV color model") -DefNode(ShaderNode, SH_NODE_OUTPUT_MATERIAL, def_sh_output, "OUTPUT_MATERIAL", OutputMaterial, "Material Output", "Output surface material information to a surface object") -DefNode(ShaderNode, SH_NODE_EEVEE_SPECULAR, 0, "EEVEE_SPECULAR", EeveeSpecular, "Specular BSDF", "Similar to the Principled BSDF node but uses the specular workflow instead of the metallic.\nThe specular workflow functions by specifying the facing (along normal) reflection color The result may not be physically plausible because there is no energy conservation") +DefNode(ShaderNode, SH_NODE_OUTPUT_MATERIAL, def_sh_output, "OUTPUT_MATERIAL", OutputMaterial, "Material Output", "Output surface material information for use in rendering") +DefNode(ShaderNode, SH_NODE_EEVEE_SPECULAR, 0, "EEVEE_SPECULAR", EeveeSpecular, "Specular BSDF", "Similar to the Principled BSDF node but uses the specular workflow instead of metallic, which functions by specifying the facing (along normal) reflection color. Energy is not conserved, so the result may not be physically accurate") DefNode(ShaderNode, SH_NODE_OUTPUT_LIGHT, def_sh_output, "OUTPUT_LIGHT", OutputLight, "Light Output", "Output light information to a light object") -DefNode(ShaderNode, SH_NODE_OUTPUT_WORLD, def_sh_output, "OUTPUT_WORLD", OutputWorld, "World Output", "Output light color information to the scene’s World") +DefNode(ShaderNode, SH_NODE_OUTPUT_WORLD, def_sh_output, "OUTPUT_WORLD", OutputWorld, "World Output", "Output light color information to the scene's World") DefNode(ShaderNode, SH_NODE_OUTPUT_LINESTYLE, def_sh_output_linestyle,"OUTPUT_LINESTYLE", OutputLineStyle, "Line Style Output", "") -DefNode(ShaderNode, SH_NODE_FRESNEL, 0, "FRESNEL", Fresnel, "Fresnel", "Produce a blending factor depending on the angle between the surface normal and the viewing direction using Fresnel equations.\nFaces facing the viewer result in darker values, and perpendicular faces produce lighter values.\nTypically used for mixing realistic reflections at grazing angles") -DefNode(ShaderNode, SH_NODE_LAYER_WEIGHT, 0, "LAYER_WEIGHT", LayerWeight, "Layer Weight", "Produce a blending factor depending on the angle between the surface normal and the viewing direction.\nDirectly facing the viewer results in darker values and perpendicular faces will be lighter.\nTypically used for layering shaders with the Mix Shader node") -DefNode(ShaderNode, SH_NODE_MIX_SHADER, 0, "MIX_SHADER", MixShader, "Mix Shader", "Mix two shaders together.\nTypically used for material layering") +DefNode(ShaderNode, SH_NODE_FRESNEL, 0, "FRESNEL", Fresnel, "Fresnel", "Produce a blending factor depending on the angle between the surface normal and the view direction using Fresnel equations.\nTypically used for mixing reflections at grazing angles") +DefNode(ShaderNode, SH_NODE_LAYER_WEIGHT, 0, "LAYER_WEIGHT", LayerWeight, "Layer Weight", "Produce a blending factor depending on the angle between the surface normal and the view direction.\nTypically used for layering shaders with the Mix Shader node") +DefNode(ShaderNode, SH_NODE_MIX_SHADER, 0, "MIX_SHADER", MixShader, "Mix Shader", "Mix two shaders together. Typically used for material layering") DefNode(ShaderNode, SH_NODE_ADD_SHADER, 0, "ADD_SHADER", AddShader, "Add Shader", "Add two Shaders together") -DefNode(ShaderNode, SH_NODE_ATTRIBUTE, def_sh_attribute, "ATTRIBUTE", Attribute, "Attribute", "Retrieve attributes attached to an object or mesh") -DefNode(ShaderNode, SH_NODE_AMBIENT_OCCLUSION, def_sh_ambient_occlusion,"AMBIENT_OCCLUSION", AmbientOcclusion, "Ambient Occlusion", "Computes how much the hemisphere above the shading point is occluded.\nThis can be used for procedural texturing, for example, to add weathering effects to corners only.\nNote: For Cycle, This is an expensive shader and may slow down render significantly") +DefNode(ShaderNode, SH_NODE_ATTRIBUTE, def_sh_attribute, "ATTRIBUTE", Attribute, "Attribute", "Retrieve attributes attached to objects or geometry") +DefNode(ShaderNode, SH_NODE_AMBIENT_OCCLUSION, def_sh_ambient_occlusion,"AMBIENT_OCCLUSION", AmbientOcclusion, "Ambient Occlusion", "Compute how much the hemisphere above the shading point is occluded, for example to add weathering effects to corners.\nNote: For Cycles, this may slow down renders significantly") DefNode(ShaderNode, SH_NODE_BACKGROUND, 0, "BACKGROUND", Background, "Background", "Add background light emission.\nNote: This node should only be used for the world surface output") -DefNode(ShaderNode, SH_NODE_HOLDOUT, 0, "HOLDOUT", Holdout, "Holdout", "Create a “hole” in the image with zero alpha transparency, which is useful for compositing.\nNote: the holdout shader can only create alpha when: Properties ‣ Render ‣ Film ‣ Transparent is enabled") +DefNode(ShaderNode, SH_NODE_HOLDOUT, 0, "HOLDOUT", Holdout, "Holdout", "Create a \"hole\" in the image with zero alpha transparency, which is useful for compositing.\nNote: the holdout shader can only create alpha when transparency is enabled in the film settings") DefNode(ShaderNode, SH_NODE_BSDF_ANISOTROPIC, def_anisotropic, "BSDF_ANISOTROPIC", BsdfAnisotropic, "Anisotropic BSDF", "Glossy reflection with separate control over U and V direction roughness") DefNode(ShaderNode, SH_NODE_BSDF_DIFFUSE, 0, "BSDF_DIFFUSE", BsdfDiffuse, "Diffuse BSDF", "Lambertian and Oren-Nayar diffuse reflection") -DefNode(ShaderNode, SH_NODE_BSDF_PRINCIPLED, def_principled, "BSDF_PRINCIPLED", BsdfPrincipled, "Principled BSDF", "Physically-based, easy-to-use shader for rendering surface materials.\nIt is based on the Disney principled model also known as the “PBR” shader, making it compatible with other software.\nTextures painted or baked from other softwares may be directly linked to the corresponding parameters in this shader") +DefNode(ShaderNode, SH_NODE_BSDF_PRINCIPLED, def_principled, "BSDF_PRINCIPLED", BsdfPrincipled, "Principled BSDF", "Physically-based, easy-to-use shader for rendering surface materials, based on the Disney principled model also known as the \"PBR\" shader") DefNode(ShaderNode, SH_NODE_BSDF_GLOSSY, def_glossy, "BSDF_GLOSSY", BsdfGlossy, "Glossy BSDF", "Reflection with microfacet distribution, used for materials such as metal or mirrors") DefNode(ShaderNode, SH_NODE_BSDF_GLASS, def_glass, "BSDF_GLASS", BsdfGlass, "Glass BSDF", "Glass-like shader mixing refraction and reflection at grazing angles") -DefNode(ShaderNode, SH_NODE_BSDF_REFRACTION, def_refraction, "BSDF_REFRACTION", BsdfRefraction, "Refraction BSDF", "Glossy refraction with sharp or microfacet distribution,.\nTypically used for materials that transmit light") +DefNode(ShaderNode, SH_NODE_BSDF_REFRACTION, def_refraction, "BSDF_REFRACTION", BsdfRefraction, "Refraction BSDF", "Glossy refraction with sharp or microfacet distribution,. Typically used for materials that transmit light") DefNode(ShaderNode, SH_NODE_BSDF_TRANSLUCENT, 0, "BSDF_TRANSLUCENT", BsdfTranslucent, "Translucent BSDF", "Lambertian diffuse transmission") -DefNode(ShaderNode, SH_NODE_BSDF_TRANSPARENT, 0, "BSDF_TRANSPARENT", BsdfTransparent, "Transparent BSDF", "Transparency without refraction, passing straight through the surface as if there were no geometry there") -DefNode(ShaderNode, SH_NODE_BSDF_VELVET, 0, "BSDF_VELVET", BsdfVelvet, "Velvet BSDF", "Reflection for materials such as cloth.\nTypically used together with other shaders (such as a Diffuse Shader) and is not particularly useful on its own") +DefNode(ShaderNode, SH_NODE_BSDF_TRANSPARENT, 0, "BSDF_TRANSPARENT", BsdfTransparent, "Transparent BSDF", "Transparency without refraction, passing straight through the surface as if there were no geometry") +DefNode(ShaderNode, SH_NODE_BSDF_VELVET, 0, "BSDF_VELVET", BsdfVelvet, "Velvet BSDF", "Reflection for materials such as cloth.\nTypically mixed with other shaders (such as a Diffuse Shader) and is not particularly useful on its own") DefNode(ShaderNode, SH_NODE_BSDF_TOON, def_toon, "BSDF_TOON", BsdfToon, "Toon BSDF", "Diffuse and Glossy shaders with cartoon light effects") DefNode(ShaderNode, SH_NODE_BSDF_HAIR, def_hair, "BSDF_HAIR", BsdfHair, "Hair BSDF", "Reflection and transmission shaders optimized for hair rendering") DefNode(ShaderNode, SH_NODE_BSDF_HAIR_PRINCIPLED, def_hair_principled, "BSDF_HAIR_PRINCIPLED", BsdfHairPrincipled, "Principled Hair BSDF", "Physically-based, easy-to-use shader for rendering hair and fur") -DefNode(ShaderNode, SH_NODE_SUBSURFACE_SCATTERING, def_sh_subsurface, "SUBSURFACE_SCATTERING",SubsurfaceScattering,"Subsurface Scattering","Subsurface multiple scattering shader.\nRather than light being reflected directly off the surface, it will penetrate the surface and bounce around internally.\nTypically used for materials such as skin, wax, marble or milk") +DefNode(ShaderNode, SH_NODE_SUBSURFACE_SCATTERING, def_sh_subsurface, "SUBSURFACE_SCATTERING",SubsurfaceScattering,"Subsurface Scattering","Subsurface multiple scattering shader to simulate light entering the surface and bouncing internally.\nTypically used for materials such as skin, wax, marble or milk") DefNode(ShaderNode, SH_NODE_VOLUME_ABSORPTION, 0, "VOLUME_ABSORPTION", VolumeAbsorption, "Volume Absorption", "Absorb light as it passes through the volume") -DefNode(ShaderNode, SH_NODE_VOLUME_SCATTER, 0, "VOLUME_SCATTER", VolumeScatter, "Volume Scatter", "Scatter light as it passes through the volume.\nTypically usage would be to add fog to a scene") +DefNode(ShaderNode, SH_NODE_VOLUME_SCATTER, 0, "VOLUME_SCATTER", VolumeScatter, "Volume Scatter", "Scatter light as it passes through the volume, often used to add fog to a scene") DefNode(ShaderNode, SH_NODE_VOLUME_PRINCIPLED, 0, "PRINCIPLED_VOLUME", VolumePrincipled, "Principled Volume", "Combine all volume shading components into a single easy to use node") DefNode(ShaderNode, SH_NODE_EMISSION, 0, "EMISSION", Emission, "Emission", "Lambertian emission shader") -DefNode(ShaderNode, SH_NODE_NEW_GEOMETRY, 0, "NEW_GEOMETRY", NewGeometry, "Geometry", "Geometric information about the current shading point") -DefNode(ShaderNode, SH_NODE_LIGHT_PATH, 0, "LIGHT_PATH", LightPath, "Light Path", "Find out for which kind of incoming ray the shader is being executed.\nTypically used for non-physically-based tricks, For example: an invisible object that still affects parts of the scene") -DefNode(ShaderNode, SH_NODE_LIGHT_FALLOFF, 0, "LIGHT_FALLOFF", LightFalloff, "Light Falloff", "Manipulate how light intensity decreases over distance.\nTypically used for non-physically-based effects, in reality, the light will always fall off quadratically") +DefNode(ShaderNode, SH_NODE_NEW_GEOMETRY, 0, "NEW_GEOMETRY", NewGeometry, "Geometry", "Retrieve geometric information about the current shading point") +DefNode(ShaderNode, SH_NODE_LIGHT_PATH, 0, "LIGHT_PATH", LightPath, "Light Path", "Retrieve the type of incoming ray for which the shader is being executed.\nTypically used for non-physically-based tricks") +DefNode(ShaderNode, SH_NODE_LIGHT_FALLOFF, 0, "LIGHT_FALLOFF", LightFalloff, "Light Falloff", "Manipulate how light intensity decreases over distance. Typically used for non-physically-based effects; in reality light always falls off quadratically") DefNode(ShaderNode, SH_NODE_OBJECT_INFO, 0, "OBJECT_INFO", ObjectInfo, "Object Info", "Retrieve information about the object instance") -DefNode(ShaderNode, SH_NODE_PARTICLE_INFO, 0, "PARTICLE_INFO", ParticleInfo, "Particle Info", "Retrieve the data of the particle that spawned the object instance.\nIt can be useful to give some variation to a single material assigned to multiple instances of instancing object") +DefNode(ShaderNode, SH_NODE_PARTICLE_INFO, 0, "PARTICLE_INFO", ParticleInfo, "Particle Info", "Retrieve the data of the particle that spawned the object instance, for example to give variation to multiple instances of an object") DefNode(ShaderNode, SH_NODE_HAIR_INFO, 0, "HAIR_INFO", HairInfo, "Curves Info", "Retrieve hair curve information") DefNode(ShaderNode, SH_NODE_POINT_INFO, 0, "POINT_INFO", PointInfo, "Point Info", "Retrieve information about points in a point cloud") -DefNode(ShaderNode, SH_NODE_VOLUME_INFO, 0, "VOLUME_INFO", VolumeInfo, "Volume Info", "Read volume data attributes found from smoke domains and other volumes") -DefNode(ShaderNode, SH_NODE_WIREFRAME, def_sh_tex_wireframe, "WIREFRAME", Wireframe, "Wireframe", "Retrieve the edges of an object as it appears to Cycles.\nNote: as meshes are triangulated before being processed by Cycles, topology will always appear triangulated when viewed with the Wireframe node") +DefNode(ShaderNode, SH_NODE_VOLUME_INFO, 0, "VOLUME_INFO", VolumeInfo, "Volume Info", "Read volume data attributes from volume grids") +DefNode(ShaderNode, SH_NODE_WIREFRAME, def_sh_tex_wireframe, "WIREFRAME", Wireframe, "Wireframe", "Retrieve the edges of an object as it appears to Cycles.\nNote: as meshes are triangulated before being processed by Cycles, topology will always appear triangulated") DefNode(ShaderNode, SH_NODE_WAVELENGTH, 0, "WAVELENGTH", Wavelength, "Wavelength", "Convert a wavelength value to an RGB value") DefNode(ShaderNode, SH_NODE_BLACKBODY, 0, "BLACKBODY", Blackbody, "Blackbody", "Convert a blackbody temperature to an RGB value") -DefNode(ShaderNode, SH_NODE_BUMP, def_sh_bump, "BUMP", Bump, "Bump", "Generate a perturbed normal from a height texture, for bump mapping.\nTypically used for faking high detailed surfaces") -DefNode(ShaderNode, SH_NODE_NORMAL_MAP, def_sh_normal_map, "NORMAL_MAP", NormalMap, "Normal Map", "Generate a perturbed normal from an RGB normal map image.\nTypically used for faking high detailed surfaces") +DefNode(ShaderNode, SH_NODE_BUMP, def_sh_bump, "BUMP", Bump, "Bump", "Generate a perturbed normal from a height texture for bump mapping. Typically used for faking highly detailed surfaces") +DefNode(ShaderNode, SH_NODE_NORMAL_MAP, def_sh_normal_map, "NORMAL_MAP", NormalMap, "Normal Map", "Generate a perturbed normal from an RGB normal map image. Typically used for faking highly detailed surfaces") DefNode(ShaderNode, SH_NODE_TANGENT, def_sh_tangent, "TANGENT", Tangent, "Tangent", "Generate a tangent direction for the Anisotropic BSDF") -DefNode(ShaderNode, SH_NODE_SCRIPT, def_sh_script, "SCRIPT", Script, "Script", "Generate an OSL shader from a file or text block.\nNote: there is no support for running OSL code on the GPU") -DefNode(ShaderNode, SH_NODE_TEX_IMAGE, def_sh_tex_image, "TEX_IMAGE", TexImage, "Image Texture", "Add an image file as a texture") -DefNode(ShaderNode, SH_NODE_TEX_ENVIRONMENT, def_sh_tex_environment, "TEX_ENVIRONMENT", TexEnvironment, "Environment Texture","Add an image file as an environment texture.\nTypically used to light the scene with the background node") +DefNode(ShaderNode, SH_NODE_SCRIPT, def_sh_script, "SCRIPT", Script, "Script", "Generate an OSL shader from a file or text data-block.\nNote: OSL shaders are not supported on the GPU") +DefNode(ShaderNode, SH_NODE_TEX_IMAGE, def_sh_tex_image, "TEX_IMAGE", TexImage, "Image Texture", "Sample an image file as a texture") +DefNode(ShaderNode, SH_NODE_TEX_ENVIRONMENT, def_sh_tex_environment, "TEX_ENVIRONMENT", TexEnvironment, "Environment Texture","Sample an image file as an environment texture. Typically used to light the scene with the background node") DefNode(ShaderNode, SH_NODE_TEX_SKY, def_sh_tex_sky, "TEX_SKY", TexSky, "Sky Texture", "Generate a procedural sky texture") -DefNode(ShaderNode, SH_NODE_TEX_GRADIENT, def_sh_tex_gradient, "TEX_GRADIENT", TexGradient, "Gradient Texture", "Generate an interpolated color and intensity values based on the input vector") -DefNode(ShaderNode, SH_NODE_TEX_NOISE, def_sh_tex_noise, "TEX_NOISE", TexNoise, "Noise Texture", "Generate a fractal Perlin noise") +DefNode(ShaderNode, SH_NODE_TEX_GRADIENT, def_sh_tex_gradient, "TEX_GRADIENT", TexGradient, "Gradient Texture", "Generate interpolated color and intensity values based on the input vector") +DefNode(ShaderNode, SH_NODE_TEX_NOISE, def_sh_tex_noise, "TEX_NOISE", TexNoise, "Noise Texture", "Generate fractal Perlin noise") DefNode(ShaderNode, SH_NODE_TEX_MAGIC, def_sh_tex_magic, "TEX_MAGIC", TexMagic, "Magic Texture", "Generate a psychedelic color texture") DefNode(ShaderNode, SH_NODE_TEX_WAVE, def_sh_tex_wave, "TEX_WAVE", TexWave, "Wave Texture", "Generate procedural bands or rings with noise") -DefNode(ShaderNode, SH_NODE_TEX_MUSGRAVE, def_sh_tex_musgrave, "TEX_MUSGRAVE", TexMusgrave, "Musgrave Texture", "Generate a fractal Perlin noise.\nUnlike the Noise Texture, which is also a fractal Perlin noise, the Musgrave Texture allows greater control over how octaves are combined") -DefNode(ShaderNode, SH_NODE_TEX_VORONOI, def_sh_tex_voronoi, "TEX_VORONOI", TexVoronoi, "Voronoi Texture", "Generate a Worley noise.\nTypically used to generate textures such as stones, water, or biological cells") +DefNode(ShaderNode, SH_NODE_TEX_MUSGRAVE, def_sh_tex_musgrave, "TEX_MUSGRAVE", TexMusgrave, "Musgrave Texture", "Generate fractal Perlin noise. Allows for greater control over how octaves are combined than the Noise Texture node") +DefNode(ShaderNode, SH_NODE_TEX_VORONOI, def_sh_tex_voronoi, "TEX_VORONOI", TexVoronoi, "Voronoi Texture", "Generate Worley noise based on the distance to random points. Typically used to generate textures such as stones, water, or biological cells") DefNode(ShaderNode, SH_NODE_TEX_CHECKER, def_sh_tex_checker, "TEX_CHECKER", TexChecker, "Checker Texture", "Generate a checkerboard texture") DefNode(ShaderNode, SH_NODE_TEX_BRICK, def_sh_tex_brick, "TEX_BRICK", TexBrick, "Brick Texture", "Generate a procedural texture producing bricks") DefNode(ShaderNode, SH_NODE_TEX_POINTDENSITY, def_sh_tex_pointdensity,"TEX_POINTDENSITY", TexPointDensity, "Point Density", "Generate a volumetric point for each particle or vertex of another object") DefNode(ShaderNode, SH_NODE_TEX_COORD, def_sh_tex_coord, "TEX_COORD", TexCoord, "Texture Coordinate","Retrieve multiple types of texture coordinates.\nTypically used as inputs for texture nodes") DefNode(ShaderNode, SH_NODE_VECTOR_ROTATE, def_sh_vector_rotate, "VECTOR_ROTATE", VectorRotate, "Vector Rotate", "Rotate a vector around a pivot point (center)") DefNode(ShaderNode, SH_NODE_VECT_TRANSFORM, def_sh_vect_transform, "VECT_TRANSFORM", VectorTransform, "Vector Transform", "Convert a vector, point, or normal between world, camera, and object coordinate space") -DefNode(ShaderNode, SH_NODE_SEPHSV_LEGACY, 0, "SEPHSV", SeparateHSV, "Separate HSV", "Split an image into its hue, saturation, and value channels") -DefNode(ShaderNode, SH_NODE_COMBHSV_LEGACY, 0, "COMBHSV", CombineHSV, "Combine HSV", "Combine an image from its hue, saturation, and value channels") -DefNode(ShaderNode, SH_NODE_UVMAP, def_sh_uvmap, "UVMAP", UVMap, "UV Map", "Retrieve a specified UV map") -DefNode(ShaderNode, SH_NODE_VERTEX_COLOR, def_sh_vertex_color, "VERTEX_COLOR", VertexColor, "Color Attribute", "Retrieve a specified color attribute") +DefNode(ShaderNode, SH_NODE_SEPHSV_LEGACY, 0, "SEPHSV", SeparateHSV, "Separate HSV", "Split a color into its hue, saturation, and value channels") +DefNode(ShaderNode, SH_NODE_COMBHSV_LEGACY, 0, "COMBHSV", CombineHSV, "Combine HSV", "Create a color from its hue, saturation, and value channels") +DefNode(ShaderNode, SH_NODE_UVMAP, def_sh_uvmap, "UVMAP", UVMap, "UV Map", "Retrieve a UV map from the geometry, or the default fallback if none is specified") +DefNode(ShaderNode, SH_NODE_VERTEX_COLOR, def_sh_vertex_color, "VERTEX_COLOR", VertexColor, "Color Attribute", "Retrieve a color attribute, or the default fallback if none is specified") DefNode(ShaderNode, SH_NODE_UVALONGSTROKE, def_sh_uvalongstroke, "UVALONGSTROKE", UVAlongStroke, "UV Along Stroke", "") -DefNode(ShaderNode, SH_NODE_SEPXYZ, 0, "SEPXYZ", SeparateXYZ, "Separate XYZ", "Split an image into its X, Y, and Z channels") -DefNode(ShaderNode, SH_NODE_COMBXYZ, 0, "COMBXYZ", CombineXYZ, "Combine XYZ", "Combine an image from its X, Y, and Z channels") -DefNode(ShaderNode, SH_NODE_BEVEL, def_sh_bevel, "BEVEL", Bevel, "Bevel", "Generates normals with round corners.\nNote: this is only supported in Cycles, and is expensive") +DefNode(ShaderNode, SH_NODE_SEPXYZ, 0, "SEPXYZ", SeparateXYZ, "Separate XYZ", "Split a vector into its X, Y, and Z components") +DefNode(ShaderNode, SH_NODE_COMBXYZ, 0, "COMBXYZ", CombineXYZ, "Combine XYZ", "Create a vector from X, Y, and Z components") +DefNode(ShaderNode, SH_NODE_BEVEL, def_sh_bevel, "BEVEL", Bevel, "Bevel", "Generates normals with round corners.\nNote: only supported in Cycles, and may slow down renders") DefNode(ShaderNode, SH_NODE_DISPLACEMENT, def_sh_displacement, "DISPLACEMENT", Displacement, "Displacement", "Displace the surface along the surface normal") -DefNode(ShaderNode, SH_NODE_VECTOR_DISPLACEMENT,def_sh_vector_displacement,"VECTOR_DISPLACEMENT",VectorDisplacement,"Vector Displacement","Displace the surface along arbitrary directions") -DefNode(ShaderNode, SH_NODE_TEX_IES, def_sh_tex_ies, "TEX_IES", TexIES, "IES Texture", "Used to match real world lights based on IES files (IES).\nIES files store the directional intensity distribution of light sources") -DefNode(ShaderNode, SH_NODE_TEX_WHITE_NOISE, def_sh_tex_white_noise, "TEX_WHITE_NOISE", TexWhiteNoise, "White Noise", "Return a random number based on an input seed") -DefNode(ShaderNode, SH_NODE_OUTPUT_AOV, def_sh_output_aov, "OUTPUT_AOV", OutputAOV, "AOV Output", "Arbitrary Output Variables.\nProvide custom render passes for arbitrary shader node components") +DefNode(ShaderNode, SH_NODE_VECTOR_DISPLACEMENT,def_sh_vector_displacement,"VECTOR_DISPLACEMENT",VectorDisplacement,"Vector Displacement","Displace the surface along an arbitrary direction") +DefNode(ShaderNode, SH_NODE_TEX_IES, def_sh_tex_ies, "TEX_IES", TexIES, "IES Texture", "Used to match real world lights with IES files, which store the directional intensity distribution of light sources") +DefNode(ShaderNode, SH_NODE_TEX_WHITE_NOISE, def_sh_tex_white_noise, "TEX_WHITE_NOISE", TexWhiteNoise, "White Noise", "Return a random value or color based on an input seed") +DefNode(ShaderNode, SH_NODE_OUTPUT_AOV, def_sh_output_aov, "OUTPUT_AOV", OutputAOV, "AOV Output", "Arbitrary Output Variables.\nProvide custom render passes for arbitrary shader node outputs") DefNode(ShaderNode, SH_NODE_CURVE_FLOAT, def_float_curve, "CURVE_FLOAT", FloatCurve, "Float Curve", "Map an input float to a curve and outputs a float value") -DefNode(ShaderNode, SH_NODE_COMBINE_COLOR, def_sh_combsep_color, "COMBINE_COLOR", CombineColor, "Combine Color", "Combine an image from its red, green, and blue channels") -DefNode(ShaderNode, SH_NODE_SEPARATE_COLOR, def_sh_combsep_color, "SEPARATE_COLOR", SeparateColor, "Separate Color", "Split an image into its red, green, and blue channels") +DefNode(ShaderNode, SH_NODE_COMBINE_COLOR, def_sh_combsep_color, "COMBINE_COLOR", CombineColor, "Combine Color", "Create a color from individual components using multiple models") +DefNode(ShaderNode, SH_NODE_SEPARATE_COLOR, def_sh_combsep_color, "SEPARATE_COLOR", SeparateColor, "Separate Color", "Split a color into its individual components using multiple models") DefNode(CompositorNode, CMP_NODE_VIEWER, def_cmp_viewer, "VIEWER", Viewer, "Viewer", "" ) DefNode(CompositorNode, CMP_NODE_RGB, 0, "RGB", RGB, "RGB", "" ) -- cgit v1.2.3 From 3354ec3fb3f6c0785c7c1ad9d84bae1c58d1628f Mon Sep 17 00:00:00 2001 From: jon denning Date: Wed, 6 Jul 2022 16:21:56 -0400 Subject: Fix T99334: Ignore edit-related snap options in Object mode When in Object Mode, any of the active- and edit-related snapping options (Include Active, Include Edited, Include Non-Edited) should be ignored when in Object Mode, otherwise snapping could be effectively disabled. This commit forces snap code to ignore the active- and edit-related options when in Object Mode. Reviewed By: Germano Cavalcante (mano-wii) Differential Revision: https://developer.blender.org/D15366 --- .../editors/transform/transform_snap_object.cc | 38 +++++++++++----------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/source/blender/editors/transform/transform_snap_object.cc b/source/blender/editors/transform/transform_snap_object.cc index 817474f795e..479214ee2d3 100644 --- a/source/blender/editors/transform/transform_snap_object.cc +++ b/source/blender/editors/transform/transform_snap_object.cc @@ -492,39 +492,39 @@ static bool snap_object_is_snappable(const SnapObjectContext *sctx, return false; } - /* get base attributes */ + /* Get attributes of potential target. */ const bool is_active = (base_act == base); const bool is_selected = (base->flag & BASE_SELECTED) || (base->flag_legacy & BA_WAS_SEL); const bool is_edited = (base->object->mode == OB_MODE_EDIT); const bool is_selectable = (base->flag & BASE_SELECTABLE); + /* Get attributes of state. */ const bool is_in_object_mode = (base_act == NULL) || (base_act->object->mode == OB_MODE_OBJECT); - if (is_edited) { - if (is_active) { - if (snap_target_select & SCE_SNAP_TARGET_NOT_ACTIVE) { - return false; - } - } - else { - if (snap_target_select & SCE_SNAP_TARGET_NOT_EDITED) { - return false; - } + if (is_in_object_mode) { + /* Handle target selection options that make sense for object mode. */ + if ((snap_target_select & SCE_SNAP_TARGET_NOT_SELECTED) && is_selected) { + /* What is selectable or not is part of the object and depends on the mode. */ + return false; } } - - if ((snap_target_select & SCE_SNAP_TARGET_NOT_NONEDITED) && !is_edited) { - return false; + else { + /* Handle target selection options that make sense for edit/pose mode. */ + if ((snap_target_select & SCE_SNAP_TARGET_NOT_ACTIVE) && is_active) { + return false; + } + if ((snap_target_select & SCE_SNAP_TARGET_NOT_EDITED) && is_edited && !is_active) { + /* Base is edited, but not active. */ + return false; + } + if ((snap_target_select & SCE_SNAP_TARGET_NOT_NONEDITED) && !is_edited) { + return false; + } } if ((snap_target_select & SCE_SNAP_TARGET_ONLY_SELECTABLE) && !is_selectable) { return false; } - if ((snap_target_select & SCE_SNAP_TARGET_NOT_SELECTED) && is_in_object_mode && is_selected) { - /* What is selectable or not is part of the object and depends on the mode. */ - return false; - } - return true; } -- cgit v1.2.3 From 378f65f7d9843ea789a66623019163f935af141e Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 7 Jul 2022 12:30:40 +1000 Subject: Fix Py-driver byte code access with Python 3.11 Error in [0] which assumed the struct member was renamed however byte-code access from PyCodeObject now requires an API call. Thanks to @music for pointing this out. [0]: 780c0ea097444c3be60314dffd203c099720badb --- source/blender/python/intern/bpy_driver.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/source/blender/python/intern/bpy_driver.c b/source/blender/python/intern/bpy_driver.c index f71cf164e8c..07de5877d42 100644 --- a/source/blender/python/intern/bpy_driver.c +++ b/source/blender/python/intern/bpy_driver.c @@ -443,13 +443,19 @@ static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code, PyObject *d PyObject *co_code; # if PY_VERSION_HEX >= 0x030b0000 /* Python 3.11 & newer. */ - co_code = py_code->_co_code; + co_code = PyCode_GetCode(py_code); + if (UNLIKELY(!co_code)) { + PyErr_Print(); + PyErr_Clear(); + return false; + } # else co_code = py_code->co_code; # endif PyBytes_AsStringAndSize(co_code, (char **)&codestr, &code_len); code_len /= sizeof(*codestr); + bool ok = true; for (Py_ssize_t i = 0; i < code_len; i++) { const int opcode = _Py_OPCODE(codestr[i]); @@ -458,11 +464,17 @@ static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code, PyObject *d "\tBPY_driver_eval() - restricted access disallows opcode '%d', " "enable auto-execution to support\n", opcode); - return false; + ok = false; + break; } } -# undef CODESIZE +# if PY_VERSION_HEX >= 0x030b0000 /* Python 3.11 & newer. */ + Py_DECREF(co_code); +# endif + if (!ok) { + return false; + } } return true; -- cgit v1.2.3 From 709e620977faba2c29732722a536953fdf6bb665 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 7 Jul 2022 12:30:42 +1000 Subject: Cleanup: format --- source/blender/editors/space_outliner/outliner_intern.hh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/source/blender/editors/space_outliner/outliner_intern.hh b/source/blender/editors/space_outliner/outliner_intern.hh index 19090641173..18173b37123 100644 --- a/source/blender/editors/space_outliner/outliner_intern.hh +++ b/source/blender/editors/space_outliner/outliner_intern.hh @@ -438,12 +438,12 @@ void lib_reload_fn(struct bContext *C, void *user_data); void id_delete_tag_fn(struct bContext *C, - struct ReportList *reports, - struct Scene *scene, - struct TreeElement *te, - struct TreeStoreElem *tsep, - struct TreeStoreElem *tselem, - void *user_data); + struct ReportList *reports, + struct Scene *scene, + struct TreeElement *te, + struct TreeStoreElem *tsep, + struct TreeStoreElem *tselem, + void *user_data); void id_remap_fn(struct bContext *C, struct ReportList *reports, struct Scene *scene, -- cgit v1.2.3 From 5c790fd52b6b5c9ebd28c8eaac49a2ba6d7e9a7a Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 7 Jul 2022 12:30:44 +1000 Subject: Cleanup: use boolean types & early exit on failure for PyDriver Also use __func__ for printing the funciton name. --- source/blender/python/intern/bpy_driver.c | 42 +++++++++++++++++-------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/source/blender/python/intern/bpy_driver.c b/source/blender/python/intern/bpy_driver.c index 07de5877d42..aa627a42a2b 100644 --- a/source/blender/python/intern/bpy_driver.c +++ b/source/blender/python/intern/bpy_driver.c @@ -282,9 +282,9 @@ static void pydriver_error(ChannelDriver *driver) #ifdef USE_BYTECODE_WHITELIST -# define OK_OP(op) [op] = 1 +# define OK_OP(op) [op] = true -static const char secure_opcodes[255] = { +static const bool secure_opcodes[255] = { # if PY_VERSION_HEX >= 0x030b0000 /* Python 3.11 & newer. */ OK_OP(CACHE), @@ -408,7 +408,9 @@ static const char secure_opcodes[255] = { # undef OK_OP -static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code, PyObject *dict_arr[]) +static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code, + PyObject *dict_arr[], + const char *error_prefix) { PyCodeObject *py_code = (PyCodeObject *)expr_code; @@ -427,8 +429,9 @@ static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code, PyObject *d if (contains_name == false) { fprintf(stderr, - "\tBPY_driver_eval() - restricted access disallows name '%s', " + "\t%s: restricted access disallows name '%s', " "enable auto-execution to support\n", + error_prefix, PyUnicode_AsUTF8(name)); return false; } @@ -457,12 +460,14 @@ static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code, PyObject *d code_len /= sizeof(*codestr); bool ok = true; + /* Loop over op-code's, the op-code arguments are ignored. */ for (Py_ssize_t i = 0; i < code_len; i++) { const int opcode = _Py_OPCODE(codestr[i]); - if (secure_opcodes[opcode] == 0) { + if (secure_opcodes[opcode] == false) { fprintf(stderr, - "\tBPY_driver_eval() - restricted access disallows opcode '%d', " + "\t%s: restricted access disallows opcode '%d', " "enable auto-execution to support\n", + error_prefix, opcode); ok = false; break; @@ -512,7 +517,7 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna, DriverVar *dvar; double result = 0.0; /* Default return. */ const char *expr; - short targets_ok = 1; + bool targets_ok = true; int i; /* Get the python expression to be evaluated. */ @@ -547,7 +552,7 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna, /* Initialize global dictionary for Python driver evaluation settings. */ if (!bpy_pydriver_Dict) { if (bpy_pydriver_create_dict() != 0) { - fprintf(stderr, "PyDriver error: couldn't create Python dictionary\n"); + fprintf(stderr, "%s: couldn't create Python dictionary\n", __func__); if (use_gil) { PyGILState_Release(gilstate); } @@ -656,12 +661,11 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna, /* This target failed - bad name. */ if (targets_ok) { /* First one, print some extra info for easier identification. */ - fprintf(stderr, "\nBPY_driver_eval() - Error while evaluating PyDriver:\n"); - targets_ok = 0; + fprintf(stderr, "\n%s: Error while evaluating PyDriver:\n", __func__); + targets_ok = false; } - fprintf( - stderr, "\tBPY_driver_eval() - couldn't add variable '%s' to namespace\n", dvar->name); + fprintf(stderr, "\t%s: couldn't add variable '%s' to namespace\n", __func__, dvar->name); // BPy_errors_to_report(NULL); /* TODO: reports. */ PyErr_Print(); PyErr_Clear(); @@ -678,7 +682,8 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna, bpy_pydriver_Dict__whitelist, driver_vars, NULL, - })) { + }, + __func__)) { if (!(G.f & G_FLAG_SCRIPT_AUTOEXEC_FAIL_QUIET)) { G.f |= G_FLAG_SCRIPT_AUTOEXEC_FAIL; BLI_snprintf(G.autoexec_fail, sizeof(G.autoexec_fail), "Driver '%s'", expr); @@ -710,7 +715,7 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna, pydriver_error(driver); } else { - if ((result = PyFloat_AsDouble(retval)) == -1.0 && PyErr_Occurred()) { + if (UNLIKELY((result = PyFloat_AsDouble(retval)) == -1.0 && PyErr_Occurred())) { pydriver_error(driver); result = 0.0; } @@ -725,11 +730,10 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna, PyGILState_Release(gilstate); } - if (isfinite(result)) { - return (float)result; + if (UNLIKELY(!isfinite(result))) { + fprintf(stderr, "\t%s: driver '%s' evaluates to '%f'\n", __func__, driver->expression, result); + return 0.0f; } - fprintf( - stderr, "\tBPY_driver_eval() - driver '%s' evaluates to '%f'\n", driver->expression, result); - return 0.0f; + return (float)result; } -- cgit v1.2.3 From 83c0f6ac3740cc8d7fb17a8b837ac95a8956805c Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 7 Jul 2022 12:30:45 +1000 Subject: Python: clear Py-driver variables on exit These kinds of leaks are relatively harmless, it reduces the number of un-freed data reported by valgrind on exit. --- source/blender/python/BPY_extern.h | 5 +++++ source/blender/python/intern/bpy_driver.c | 22 ++++++++++++++-------- source/blender/python/intern/bpy_interface.c | 3 +++ 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/source/blender/python/BPY_extern.h b/source/blender/python/BPY_extern.h index 0ab26fde34f..8075e4ecd22 100644 --- a/source/blender/python/BPY_extern.h +++ b/source/blender/python/BPY_extern.h @@ -69,6 +69,11 @@ void BPY_modules_load_user(struct bContext *C); void BPY_app_handlers_reset(bool do_all); +/** + * Run on exit to free any cached data. + */ +void BPY_driver_exit(void); + /** * Update function, it gets rid of python-drivers global dictionary: `bpy.app.driver_namespace`, * forcing #BPY_driver_exec to recreate it. Use this when loading a new `.blend` file diff --git a/source/blender/python/intern/bpy_driver.c b/source/blender/python/intern/bpy_driver.c index aa627a42a2b..3134969d21c 100644 --- a/source/blender/python/intern/bpy_driver.c +++ b/source/blender/python/intern/bpy_driver.c @@ -233,15 +233,8 @@ static void bpy_pydriver_namespace_update_depsgraph(struct Depsgraph *depsgraph) } } -void BPY_driver_reset(void) +void BPY_driver_exit(void) { - PyGILState_STATE gilstate; - const bool use_gil = true; /* !PyC_IsInterpreterActive(); */ - - if (use_gil) { - gilstate = PyGILState_Ensure(); - } - if (bpy_pydriver_Dict) { /* Free the global dict used by python-drivers. */ PyDict_Clear(bpy_pydriver_Dict); Py_DECREF(bpy_pydriver_Dict); @@ -261,6 +254,19 @@ void BPY_driver_reset(void) /* Freed when clearing driver dictionary. */ g_pydriver_state_prev.self = NULL; g_pydriver_state_prev.depsgraph = NULL; +} + +void BPY_driver_reset(void) +{ + PyGILState_STATE gilstate; + const bool use_gil = true; /* !PyC_IsInterpreterActive(); */ + + if (use_gil) { + gilstate = PyGILState_Ensure(); + } + + /* Currently exit/reset are practically the same besides the GIL check. */ + BPY_driver_exit(); if (use_gil) { PyGILState_Release(gilstate); diff --git a/source/blender/python/intern/bpy_interface.c b/source/blender/python/intern/bpy_interface.c index ea64fa6c098..939fa475344 100644 --- a/source/blender/python/intern/bpy_interface.c +++ b/source/blender/python/intern/bpy_interface.c @@ -512,6 +512,9 @@ void BPY_python_end(void) /* finalizing, no need to grab the state, except when we are a module */ gilstate = PyGILState_Ensure(); + /* Frees the python-driver name-space & cached data. */ + BPY_driver_exit(); + /* Clear Python values in the context so freeing the context after Python exits doesn't crash. */ bpy_context_end(BPY_context_get()); -- cgit v1.2.3 From 3f657e7ef19a39d7dca64ed5fdee57336f445c62 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 7 Jul 2022 12:30:47 +1000 Subject: Python: show additional context for PyDriver errors in the stderr Showing the expression alone may not be enough to track down an error evaluating a py-driver. Show information about the target ID & property in the error message as well. --- source/blender/python/intern/bpy_driver.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/source/blender/python/intern/bpy_driver.c b/source/blender/python/intern/bpy_driver.c index 3134969d21c..9df3ea9b318 100644 --- a/source/blender/python/intern/bpy_driver.c +++ b/source/blender/python/intern/bpy_driver.c @@ -19,6 +19,7 @@ #include "BKE_animsys.h" #include "BKE_fcurve_driver.h" #include "BKE_global.h" +#include "BKE_idtype.h" #include "RNA_access.h" #include "RNA_prototypes.h" @@ -273,13 +274,27 @@ void BPY_driver_reset(void) } } -/** Error return function for #BPY_eval_pydriver. */ -static void pydriver_error(ChannelDriver *driver) +/** + * Error return function for #BPY_eval_pydriver. + * + * \param anim_rna: Used to show the target when printing the error to give additional context. + */ +static void pydriver_error(ChannelDriver *driver, const struct PathResolvedRNA *anim_rna) { driver->flag |= DRIVER_FLAG_INVALID; /* Python expression failed. */ + + const char *null_str = ""; + const ID *id = anim_rna->ptr.owner_id; fprintf(stderr, - "\nError in Driver: The following Python expression failed:\n\t'%s'\n\n", - driver->expression); + "\n" + "Error in PyDriver: expression failed: %s\n" + "For target: (type=%s, name=\"%s\", property=%s, property_index=%d)\n" + "\n", + driver->expression, + id ? BKE_idtype_idcode_to_name(GS(id->name)) : null_str, + id ? id->name + 2 : null_str, + anim_rna->prop ? RNA_property_identifier(anim_rna->prop) : null_str, + anim_rna->prop_index); // BPy_errors_to_report(NULL); /* TODO: reports. */ PyErr_Print(); @@ -718,11 +733,11 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna, /* Process the result. */ if (retval == NULL) { - pydriver_error(driver); + pydriver_error(driver, anim_rna); } else { if (UNLIKELY((result = PyFloat_AsDouble(retval)) == -1.0 && PyErr_Occurred())) { - pydriver_error(driver); + pydriver_error(driver, anim_rna); result = 0.0; } else { -- cgit v1.2.3 From 34c701abbdbfa6143c11e1b6f8d7797257c70237 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 7 Jul 2022 14:57:43 +1000 Subject: Fix T99270: bones using empties as custom shapes can't be selected Regression in [0] which didn't account for the bounds of empty objects. Add support support calculating bounds from empty draw-type to use in pose-bone culling. [0]: 3267c91b4d5caab7da8aef071a446dd2e86f86a9 --- source/blender/blenkernel/BKE_armature.h | 4 ++ source/blender/blenkernel/BKE_object.h | 4 ++ source/blender/blenkernel/intern/armature.c | 27 +++++++-- source/blender/blenkernel/intern/object.cc | 64 ++++++++++++++++++++++ .../draw/engines/overlay/overlay_armature.c | 2 +- 5 files changed, 95 insertions(+), 6 deletions(-) diff --git a/source/blender/blenkernel/BKE_armature.h b/source/blender/blenkernel/BKE_armature.h index 888de38dcc7..98f9f8e3588 100644 --- a/source/blender/blenkernel/BKE_armature.h +++ b/source/blender/blenkernel/BKE_armature.h @@ -158,9 +158,13 @@ struct BoundBox *BKE_armature_boundbox_get(struct Object *ob); * or the custom object's bounds (if the bone uses a custom object). * Visual elements such as the envelopes radius & bendy-bone spline segments are *not* included, * making this not so useful for viewport culling. + * + * \param use_empty_drawtype: When enabled, the draw type of empty custom-objects is tagen into + * account when calculating the bounds. */ void BKE_pchan_minmax(const struct Object *ob, const struct bPoseChannel *pchan, + const bool use_empty_drawtype, float r_min[3], float r_max[3]); /** diff --git a/source/blender/blenkernel/BKE_object.h b/source/blender/blenkernel/BKE_object.h index faf878dfc2a..6ed05bc267a 100644 --- a/source/blender/blenkernel/BKE_object.h +++ b/source/blender/blenkernel/BKE_object.h @@ -369,6 +369,9 @@ void BKE_object_empty_draw_type_set(struct Object *ob, int value); void BKE_object_boundbox_calc_from_mesh(struct Object *ob, const struct Mesh *me_eval); bool BKE_object_boundbox_calc_from_evaluated_geometry(struct Object *ob); +/** + * Calculate visual bounds from an empty objects draw-type. + */ void BKE_object_minmax(struct Object *ob, float r_min[3], float r_max[3], bool use_hidden); bool BKE_object_minmax_dupli(struct Depsgraph *depsgraph, struct Scene *scene, @@ -376,6 +379,7 @@ bool BKE_object_minmax_dupli(struct Depsgraph *depsgraph, float r_min[3], float r_max[3], bool use_hidden); +bool BKE_object_minmax_empty_drawtype(const struct Object *ob, float r_min[3], float r_max[3]); /** * Sometimes min-max isn't enough, we need to loop over each point. diff --git a/source/blender/blenkernel/intern/armature.c b/source/blender/blenkernel/intern/armature.c index dfe3d9cc310..622ecde6a91 100644 --- a/source/blender/blenkernel/intern/armature.c +++ b/source/blender/blenkernel/intern/armature.c @@ -2661,13 +2661,30 @@ BoundBox *BKE_armature_boundbox_get(Object *ob) return ob->runtime.bb; } -void BKE_pchan_minmax(const Object *ob, const bPoseChannel *pchan, float r_min[3], float r_max[3]) +void BKE_pchan_minmax(const Object *ob, + const bPoseChannel *pchan, + const bool use_empty_drawtype, + float r_min[3], + float r_max[3]) { const bArmature *arm = ob->data; const bPoseChannel *pchan_tx = (pchan->custom && pchan->custom_tx) ? pchan->custom_tx : pchan; - const BoundBox *bb_custom = ((pchan->custom) && !(arm->flag & ARM_NO_CUSTOM)) ? - BKE_object_boundbox_get(pchan->custom) : - NULL; + const BoundBox *bb_custom = NULL; + BoundBox bb_custom_buf; + + if ((pchan->custom) && !(arm->flag & ARM_NO_CUSTOM)) { + float min[3], max[3]; + if (use_empty_drawtype && (pchan->custom->type == OB_EMPTY) && + BKE_object_minmax_empty_drawtype(pchan->custom, min, max)) { + memset(&bb_custom_buf, 0x0, sizeof(bb_custom_buf)); + BKE_boundbox_init_from_minmax(&bb_custom_buf, min, max); + bb_custom = &bb_custom_buf; + } + else { + bb_custom = BKE_object_boundbox_get(pchan->custom); + } + } + if (bb_custom) { float mat[4][4], smat[4][4], rmat[4][4], tmp[4][4]; scale_m4_fl(smat, PCHAN_CUSTOM_BONE_LENGTH(pchan)); @@ -2704,7 +2721,7 @@ bool BKE_pose_minmax(Object *ob, float r_min[3], float r_max[3], bool use_hidden if (pchan->bone && (!((use_hidden == false) && (PBONE_VISIBLE(arm, pchan->bone) == false)) && !((use_select == true) && ((pchan->bone->flag & BONE_SELECTED) == 0)))) { - BKE_pchan_minmax(ob, pchan, r_min, r_max); + BKE_pchan_minmax(ob, pchan, false, r_min, r_max); changed = true; } } diff --git a/source/blender/blenkernel/intern/object.cc b/source/blender/blenkernel/intern/object.cc index e87b43c627c..5ed1ac98ddf 100644 --- a/source/blender/blenkernel/intern/object.cc +++ b/source/blender/blenkernel/intern/object.cc @@ -3985,6 +3985,70 @@ bool BKE_object_empty_image_data_is_visible_in_view3d(const Object *ob, const Re return true; } +bool BKE_object_minmax_empty_drawtype(const struct Object *ob, float r_min[3], float r_max[3]) +{ + BLI_assert(ob->type == OB_EMPTY); + float3 min(0), max(0); + + bool ok = false; + const float radius = ob->empty_drawsize; + + switch (ob->empty_drawtype) { + case OB_ARROWS: { + max = float3(radius); + ok = true; + break; + } + case OB_PLAINAXES: + case OB_CUBE: + case OB_EMPTY_SPHERE: { + min = float3(-radius); + max = float3(radius); + ok = true; + break; + } + case OB_CIRCLE: { + max[0] = max[2] = radius; + min[0] = min[2] = -radius; + ok = true; + break; + } + case OB_SINGLE_ARROW: { + max[2] = radius; + ok = true; + break; + } + case OB_EMPTY_CONE: { + min = float3(-radius, 0.0f, -radius); + max = float3(radius, radius * 2.0f, radius); + ok = true; + break; + } + case OB_EMPTY_IMAGE: { + const float *ofs = ob->ima_ofs; + /* NOTE: this is the best approximation that can be calculated without loading the image. */ + min[0] = ofs[0] * radius; + min[1] = ofs[1] * radius; + max[0] = radius + (ofs[0] * radius); + max[1] = radius + (ofs[1] * radius); + /* Since the image aspect can shrink the bounds towards the object origin, + * adjust the min/max to account for that. */ + for (int i = 0; i < 2; i++) { + CLAMP_MAX(min[i], 0.0f); + CLAMP_MIN(max[i], 0.0f); + } + ok = true; + break; + } + } + + if (ok) { + copy_v3_v3(r_min, min); + copy_v3_v3(r_max, max); + } + return ok; +} + bool BKE_object_minmax_dupli(Depsgraph *depsgraph, Scene *scene, Object *ob, diff --git a/source/blender/draw/engines/overlay/overlay_armature.c b/source/blender/draw/engines/overlay/overlay_armature.c index ea0c2f287a6..e38695c76ab 100644 --- a/source/blender/draw/engines/overlay/overlay_armature.c +++ b/source/blender/draw/engines/overlay/overlay_armature.c @@ -2102,7 +2102,7 @@ static void pchan_culling_calc_bsphere(const Object *ob, { float min[3], max[3]; INIT_MINMAX(min, max); - BKE_pchan_minmax(ob, pchan, min, max); + BKE_pchan_minmax(ob, pchan, true, min, max); mid_v3_v3v3(r_bsphere->center, min, max); r_bsphere->radius = len_v3v3(min, r_bsphere->center); } -- cgit v1.2.3 From 843ad51d18c80f4122baab47454d09383ae365c3 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 7 Jul 2022 16:29:19 +1000 Subject: Cleanup: use arguments for internal wayland cursor grabbing Pass in arguments for internal grab logic instead of accessing some values from the window and other values as arguments. While more verbose it's simpler to reason about. --- intern/ghost/intern/GHOST_SystemWayland.cpp | 32 ++++++++++------------------- intern/ghost/intern/GHOST_SystemWayland.h | 5 ++++- intern/ghost/intern/GHOST_WindowWayland.cpp | 16 ++++++++++++++- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 98526eb0bc6..164531f3847 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -3449,7 +3449,10 @@ void GHOST_SystemWayland::window_surface_unref(const wl_surface *surface) bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mode, const GHOST_TGrabCursorMode mode_current, int32_t init_grab_xy[2], - wl_surface *surface) + const GHOST_Rect *wrap_bounds, + const GHOST_TAxisFlag wrap_axis, + wl_surface *surface, + const int scale) { /* Ignore, if the required protocols are not supported. */ if (!d->relative_pointer_manager || !d->pointer_constraints) { @@ -3502,31 +3505,22 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod if (mode_current == GHOST_kGrabWrap) { /* Since this call is initiated by Blender, we can be sure the window wasn't closed * by logic outside this function - as the window was needed to make this call. */ - GHOST_WindowWayland *win = ghost_wl_surface_user_data(surface); - GHOST_ASSERT(win, "could not find window from surface when un-grabbing!"); - - GHOST_Rect bounds; int32_t xy_new[2] = {input->pointer.xy[0], input->pointer.xy[1]}; - /* Fallback to window bounds. */ - if (win->getCursorGrabBounds(bounds) == GHOST_kFailure) { - win->getClientBounds(bounds); - } - - const int scale = win->scale(); + GHOST_Rect bounds_scale; - bounds.m_l = wl_fixed_from_int(bounds.m_l) / scale; - bounds.m_t = wl_fixed_from_int(bounds.m_t) / scale; - bounds.m_r = wl_fixed_from_int(bounds.m_r) / scale; - bounds.m_b = wl_fixed_from_int(bounds.m_b) / scale; + bounds_scale.m_l = wl_fixed_from_int(wrap_bounds->m_l) / scale; + bounds_scale.m_t = wl_fixed_from_int(wrap_bounds->m_t) / scale; + bounds_scale.m_r = wl_fixed_from_int(wrap_bounds->m_r) / scale; + bounds_scale.m_b = wl_fixed_from_int(wrap_bounds->m_b) / scale; - bounds.wrapPoint(xy_new[0], xy_new[1], 0, win->getCursorGrabAxis()); + bounds_scale.wrapPoint(xy_new[0], xy_new[1], 0, wrap_axis); /* Push an event so the new location is registered. */ if ((xy_new[0] != input->pointer.xy[0]) || (xy_new[1] != input->pointer.xy[1])) { input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(), GHOST_kEventCursorMove, - win, + ghost_wl_surface_user_data(surface), wl_fixed_to_int(scale * xy_new[0]), wl_fixed_to_int(scale * xy_new[1]), GHOST_TABLET_DATA_NONE)); @@ -3541,8 +3535,6 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod else if (mode_current == GHOST_kGrabHide) { if ((init_grab_xy[0] != input->grab_lock_xy[0]) || (init_grab_xy[1] != input->grab_lock_xy[1])) { - GHOST_WindowWayland *win = ghost_wl_surface_user_data(surface); - const int scale = win->scale(); const wl_fixed_t xy_next[2] = { wl_fixed_from_int(init_grab_xy[0]) / scale, wl_fixed_from_int(init_grab_xy[1]) / scale, @@ -3595,8 +3587,6 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod if (mode == GHOST_kGrabHide) { /* Set the initial position to detect any changes when un-grabbing, * otherwise the unlocked cursor defaults to un-locking in-place. */ - GHOST_WindowWayland *win = ghost_wl_surface_user_data(surface); - const int scale = win->scale(); init_grab_xy[0] = wl_fixed_to_int(scale * input->pointer.xy[0]); init_grab_xy[1] = wl_fixed_to_int(scale * input->pointer.xy[1]); input->grab_lock_xy[0] = init_grab_xy[0]; diff --git a/intern/ghost/intern/GHOST_SystemWayland.h b/intern/ghost/intern/GHOST_SystemWayland.h index e40448ff20b..0d51759aa2f 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.h +++ b/intern/ghost/intern/GHOST_SystemWayland.h @@ -179,7 +179,10 @@ class GHOST_SystemWayland : public GHOST_System { bool window_cursor_grab_set(const GHOST_TGrabCursorMode mode, const GHOST_TGrabCursorMode mode_current, int32_t init_grab_xy[2], - wl_surface *surface); + const GHOST_Rect *wrap_bounds, + GHOST_TAxisFlag wrap_axis, + wl_surface *surface, + int scale); private: struct display_t *d; diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp index 77eddbb13a5..a04ff23a1a1 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cpp +++ b/intern/ghost/intern/GHOST_WindowWayland.cpp @@ -501,7 +501,21 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, GHOST_TSuccess GHOST_WindowWayland::setWindowCursorGrab(GHOST_TGrabCursorMode mode) { - if (m_system->window_cursor_grab_set(mode, m_cursorGrab, m_cursorGrabInitPos, w->wl_surface)) { + GHOST_Rect bounds_buf; + GHOST_Rect *bounds = nullptr; + if (m_cursorGrab == GHOST_kGrabWrap) { + if (getCursorGrabBounds(bounds_buf) == GHOST_kFailure) { + getClientBounds(bounds_buf); + } + bounds = &bounds_buf; + } + if (m_system->window_cursor_grab_set(mode, + m_cursorGrab, + m_cursorGrabInitPos, + bounds, + m_cursorGrabAxis, + w->wl_surface, + w->scale)) { return GHOST_kSuccess; } return GHOST_kFailure; -- cgit v1.2.3 From 28105caaa394b5a6c3d237fea7ddadecca7d0331 Mon Sep 17 00:00:00 2001 From: Antonio Vazquez Date: Thu, 7 Jul 2022 09:45:33 +0200 Subject: Fix T99342: GPencil multiframe falloff is scaling wrongly in rotation The falloff was applied to scale by error. Now, the falloff is only applied to the rotation. Differential Revision: https://developer.blender.org/D15364 . --- source/blender/editors/transform/transform_mode.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/source/blender/editors/transform/transform_mode.c b/source/blender/editors/transform/transform_mode.c index 83f1bd35f81..5ba0f08ee1c 100644 --- a/source/blender/editors/transform/transform_mode.c +++ b/source/blender/editors/transform/transform_mode.c @@ -549,18 +549,14 @@ void ElementRotation_ex(const TransInfo *t, mul_m3_m3m3(totmat, mat, td->mtx); mul_m3_m3m3(smat, td->smtx, totmat); - /* apply gpencil falloff */ + /* Apply gpencil falloff. */ if (t->options & CTX_GPENCIL_STROKES) { bGPDstroke *gps = (bGPDstroke *)td->extra; - float sx = smat[0][0]; - float sy = smat[1][1]; - float sz = smat[2][2]; - - mul_m3_fl(smat, gps->runtime.multi_frame_falloff); - /* fix scale */ - smat[0][0] = sx; - smat[1][1] = sy; - smat[2][2] = sz; + if (gps->runtime.multi_frame_falloff != 1.0f) { + float ident_mat[3][3]; + unit_m3(ident_mat); + interp_m3_m3m3(smat, ident_mat, smat, gps->runtime.multi_frame_falloff); + } } sub_v3_v3v3(vec, td->iloc, center); -- cgit v1.2.3 From bddcb89cdaa6d9b36b3e42547a95bff792d37610 Mon Sep 17 00:00:00 2001 From: Aras Pranckevicius Date: Thu, 7 Jul 2022 11:32:56 +0300 Subject: OBJ: always set eevee blend mode when material "d" is below 1.0 Fixes T97743: the import code was setting EEVEE blending mode whenever a transparency texture was present (map_d), or when the materials illum was saying "yo, transparency!". But if only the material's d was below 1.0, it was not setting the blend mode, which is different to user expectations. Differential Revision: https://developer.blender.org/D15383 --- source/blender/io/wavefront_obj/importer/obj_import_mtl.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/io/wavefront_obj/importer/obj_import_mtl.cc b/source/blender/io/wavefront_obj/importer/obj_import_mtl.cc index f39def0a4af..60e419728f3 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_mtl.cc +++ b/source/blender/io/wavefront_obj/importer/obj_import_mtl.cc @@ -320,7 +320,7 @@ void ShaderNodetreeWrap::set_bsdf_socket_values(Material *mat) if (alpha != -1) { set_property_of_socket(SOCK_FLOAT, "Alpha", {alpha}, bsdf_); } - if (do_tranparency) { + if (do_tranparency || (alpha >= 0.0f && alpha < 1.0f)) { mat->blend_method = MA_BM_BLEND; } } -- cgit v1.2.3 From 50f9c1c09ce331c7ce09115016ba3e4407691701 Mon Sep 17 00:00:00 2001 From: Aras Pranckevicius Date: Thu, 7 Jul 2022 11:34:04 +0300 Subject: OBJ: more robust .mtl texture offset/scale parsing (T89421) As pointed out in a comment on T89421, if a MTL file contained something like: `map_Ka -o 1 2.png` then it was parsed as having offset `1 2` and the texture filename just a `.png`. Make it so that mtl option numbers are parsed in a way where the number is only accepted only if it's followed by whitespace. Differential Revision: https://developer.blender.org/D15385 --- .../importer/obj_import_file_reader.cc | 6 +++--- .../importer/obj_import_string_utils.cc | 22 ++++++++++++++++++---- .../importer/obj_import_string_utils.hh | 18 ++++++++++++++---- .../tests/obj_import_string_utils_tests.cc | 12 ++++++++++-- .../io/wavefront_obj/tests/obj_mtl_parser_tests.cc | 2 +- 5 files changed, 46 insertions(+), 14 deletions(-) diff --git a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc index 3cc17e7d8e6..c2aa96713b1 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc +++ b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc @@ -626,15 +626,15 @@ static bool parse_texture_option(const char *&p, { p = drop_whitespace(p, end); if (parse_keyword(p, end, "-o")) { - p = parse_floats(p, end, 0.0f, tex_map.translation, 3); + p = parse_floats(p, end, 0.0f, tex_map.translation, 3, true); return true; } if (parse_keyword(p, end, "-s")) { - p = parse_floats(p, end, 1.0f, tex_map.scale, 3); + p = parse_floats(p, end, 1.0f, tex_map.scale, 3, true); return true; } if (parse_keyword(p, end, "-bm")) { - p = parse_float(p, end, 1.0f, material->map_Bump_strength); + p = parse_float(p, end, 1.0f, material->map_Bump_strength, true, true); return true; } if (parse_keyword(p, end, "-type")) { diff --git a/source/blender/io/wavefront_obj/importer/obj_import_string_utils.cc b/source/blender/io/wavefront_obj/importer/obj_import_string_utils.cc index c8eaa046e68..bc9006e1051 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_string_utils.cc +++ b/source/blender/io/wavefront_obj/importer/obj_import_string_utils.cc @@ -62,8 +62,12 @@ static const char *drop_plus(const char *p, const char *end) return p; } -const char *parse_float( - const char *p, const char *end, float fallback, float &dst, bool skip_space) +const char *parse_float(const char *p, + const char *end, + float fallback, + float &dst, + bool skip_space, + bool require_trailing_space) { if (skip_space) { p = drop_whitespace(p, end); @@ -73,13 +77,23 @@ const char *parse_float( if (res.ec == std::errc::invalid_argument || res.ec == std::errc::result_out_of_range) { dst = fallback; } + else if (require_trailing_space && res.ptr < end && !is_whitespace(*res.ptr)) { + /* If there are trailing non-space characters, do not eat up the number. */ + dst = fallback; + return p; + } return res.ptr; } -const char *parse_floats(const char *p, const char *end, float fallback, float *dst, int count) +const char *parse_floats(const char *p, + const char *end, + float fallback, + float *dst, + int count, + bool require_trailing_space) { for (int i = 0; i < count; ++i) { - p = parse_float(p, end, fallback, dst[i]); + p = parse_float(p, end, fallback, dst[i], true, require_trailing_space); } return p; } diff --git a/source/blender/io/wavefront_obj/importer/obj_import_string_utils.hh b/source/blender/io/wavefront_obj/importer/obj_import_string_utils.hh index 3f428b1ab5c..f6dd1a6b675 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_string_utils.hh +++ b/source/blender/io/wavefront_obj/importer/obj_import_string_utils.hh @@ -62,12 +62,17 @@ const char *parse_int( * The parsed result is stored in `dst`. The function skips * leading white-space unless `skip_space=false`. If the * number can't be parsed (invalid syntax, out of range), - * `fallback` value is stored instead. + * `fallback` value is stored instead. If `require_trailing_space` + * is true, the character after the number has to be whitespace. * * Returns the start of remainder of the input string after parsing. */ -const char *parse_float( - const char *p, const char *end, float fallback, float &dst, bool skip_space = true); +const char *parse_float(const char *p, + const char *end, + float fallback, + float &dst, + bool skip_space = true, + bool require_trailing_space = false); /** * Parse a number of white-space separated floats from an input string. @@ -77,6 +82,11 @@ const char *parse_float( * * Returns the start of remainder of the input string after parsing. */ -const char *parse_floats(const char *p, const char *end, float fallback, float *dst, int count); +const char *parse_floats(const char *p, + const char *end, + float fallback, + float *dst, + int count, + bool require_trailing_space = false); } // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/tests/obj_import_string_utils_tests.cc b/source/blender/io/wavefront_obj/tests/obj_import_string_utils_tests.cc index 46e093bb8a7..e9747b437cc 100644 --- a/source/blender/io/wavefront_obj/tests/obj_import_string_utils_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_import_string_utils_tests.cc @@ -29,9 +29,14 @@ static StringRef parse_int(StringRef s, int fallback, int &dst, bool skip_space { return StringRef(parse_int(s.begin(), s.end(), fallback, dst, skip_space), s.end()); } -static StringRef parse_float(StringRef s, float fallback, float &dst, bool skip_space = true) +static StringRef parse_float(StringRef s, + float fallback, + float &dst, + bool skip_space = true, + bool require_trailing_space = false) { - return StringRef(parse_float(s.begin(), s.end(), fallback, dst, skip_space), s.end()); + return StringRef( + parse_float(s.begin(), s.end(), fallback, dst, skip_space, require_trailing_space), s.end()); } TEST(obj_import_string_utils, drop_whitespace) @@ -126,6 +131,9 @@ TEST(obj_import_string_utils, parse_float_invalid) /* Has leading white-space when we don't expect it */ EXPECT_STRREF_EQ(" 1", parse_float(" 1", -4.0f, val, false)); EXPECT_EQ(val, -4.0f); + /* Has trailing non-number characters when we don't want them */ + EXPECT_STRREF_EQ("123.5.png", parse_float(" 123.5.png", -5.0f, val, true, true)); + EXPECT_EQ(val, -5.0f); } } // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/tests/obj_mtl_parser_tests.cc b/source/blender/io/wavefront_obj/tests/obj_mtl_parser_tests.cc index 5b909865d9b..08050ac34c9 100644 --- a/source/blender/io/wavefront_obj/tests/obj_mtl_parser_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_mtl_parser_tests.cc @@ -247,7 +247,7 @@ TEST_F(obj_mtl_parser_test, materials) ks.image_path = "ScaleOffsetBothTwovalues.png"; tex_map_XX &ns = mat[5].tex_map_of_type(eMTLSyntaxElement::map_Ns); ns.scale = {0.5f, 1.0f, 1.0f}; - ns.image_path = "ScaleOneValue.png"; + ns.image_path = "1.Value.png"; } check("materials.mtl", mat, ARRAY_SIZE(mat)); -- cgit v1.2.3 From f25620187620e75772adc18eaf121ef00416c791 Mon Sep 17 00:00:00 2001 From: Jesse Yurkovich Date: Thu, 7 Jul 2022 02:08:32 -0700 Subject: Fix T99388: Obey relative path option when saving UDIMs Ensure that the Image maintains the proper file path after saving all the individual tiles. The image_save_post function is unaware that the filepath it receives is only for a single tile, not the entire Image, and happily keeps setting ima->filepath to the concrete filepath for each tile. There were 2 problems with the code that attempted to correct the Image filepath back to the UDIM virtual form: - It would trample the "relative" directory that might have been set - It would do the wrong thing if no tiles could be saved at all The design is now as follows: Example of trying to save to a new PathB | | all tiles ok | any tile not ok| | -------------------------------- | ---------------- | ---------------| | ima->filepath is currently empty | set to new PathB | keep empty | | ima->filepath is currently PathA | set to new PathB | keep PathA | Differential Revision: https://developer.blender.org/D15384 --- source/blender/blenkernel/intern/image_save.cc | 40 +++++++++++++++++--------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/source/blender/blenkernel/intern/image_save.cc b/source/blender/blenkernel/intern/image_save.cc index 9dda3762553..07ffc35907a 100644 --- a/source/blender/blenkernel/intern/image_save.cc +++ b/source/blender/blenkernel/intern/image_save.cc @@ -253,6 +253,21 @@ void BKE_image_save_options_free(ImageSaveOptions *opts) BKE_image_format_free(&opts->im_format); } +static void image_save_update_filepath(Image *ima, + const char *filepath, + const ImageSaveOptions *opts) +{ + if (opts->do_newpath) { + BLI_strncpy(ima->filepath, filepath, sizeof(ima->filepath)); + + /* only image path, never ibuf */ + if (opts->relative) { + const char *relbase = ID_BLEND_PATH(opts->bmain, &ima->id); + BLI_path_rel(ima->filepath, relbase); /* only after saving */ + } + } +} + static void image_save_post(ReportList *reports, Image *ima, ImBuf *ibuf, @@ -273,13 +288,11 @@ static void image_save_post(ReportList *reports, if (opts->do_newpath) { BLI_strncpy(ibuf->name, filepath, sizeof(ibuf->name)); - BLI_strncpy(ima->filepath, filepath, sizeof(ima->filepath)); + } - /* only image path, never ibuf */ - if (opts->relative) { - const char *relbase = ID_BLEND_PATH(opts->bmain, &ima->id); - BLI_path_rel(ima->filepath, relbase); /* only after saving */ - } + /* The tiled image codepath must call this on its own. */ + if (ima->source != IMA_SRC_TILED) { + image_save_update_filepath(ima, filepath, opts); } ibuf->userflags &= ~IB_BITMAPDIRTY; @@ -640,22 +653,23 @@ bool BKE_image_save( ok = image_save_single(reports, ima, iuser, opts, &colorspace_changed); } else { - char filepath[FILE_MAX]; - BLI_strncpy(filepath, opts->filepath, sizeof(filepath)); - /* Save all the tiles. */ LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) { + ImageSaveOptions tile_opts = *opts; BKE_image_set_filepath_from_tile_number( - opts->filepath, udim_pattern, tile_format, tile->tile_number); + tile_opts.filepath, udim_pattern, tile_format, tile->tile_number); iuser->tile = tile->tile_number; - ok = image_save_single(reports, ima, iuser, opts, &colorspace_changed); + ok = image_save_single(reports, ima, iuser, &tile_opts, &colorspace_changed); if (!ok) { break; } } - BLI_strncpy(ima->filepath, filepath, sizeof(ima->filepath)); - BLI_strncpy(opts->filepath, filepath, sizeof(opts->filepath)); + + /* Set the image path only if all tiles were ok. */ + if (ok) { + image_save_update_filepath(ima, opts->filepath, opts); + } MEM_freeN(udim_pattern); } -- cgit v1.2.3 From 14980c9b3ad849340cad2cce5aa475228ff0c2b4 Mon Sep 17 00:00:00 2001 From: Jesse Yurkovich Date: Thu, 7 Jul 2022 02:36:54 -0700 Subject: Fix: Save modified images during file close Regressed in the following commit due to an inverted conditional: {rB1159b63a07fd2cbc7fc48e162d57721c9c85b3f6} Differential Revision: https://developer.blender.org/D15389 --- source/blender/editors/space_image/image_ops.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/editors/space_image/image_ops.c b/source/blender/editors/space_image/image_ops.c index b77bdc11ca5..49420e9a2a1 100644 --- a/source/blender/editors/space_image/image_ops.c +++ b/source/blender/editors/space_image/image_ops.c @@ -2391,7 +2391,7 @@ bool ED_image_save_all_modified(const bContext *C, ReportList *reports) if (image_has_valid_path(ima)) { ImageSaveOptions opts; Scene *scene = CTX_data_scene(C); - if (!BKE_image_save_options_init(&opts, bmain, scene, ima, NULL, false, false)) { + if (BKE_image_save_options_init(&opts, bmain, scene, ima, NULL, false, false)) { bool saved_successfully = BKE_image_save(reports, bmain, ima, NULL, &opts); ok = ok && saved_successfully; } -- cgit v1.2.3 From 051a341cf00ca1518b50b43bb95f95bc24bf2398 Mon Sep 17 00:00:00 2001 From: Falk David Date: Wed, 6 Jul 2022 17:09:00 +0200 Subject: Fix T99491: Crash when opening modifiers panel This crashed because in `get_active_fcurve_channel`, the filter did not filter out channels with no fcurve. The fix adds the filter `ANIMFILTER_FCURVESONLY`. See rB92d7f9ac56e0ff1e65c364487542dfb7c32a0a67 for the new filter. Maniphest Tasks: T99491 Differential Revision: https://developer.blender.org/D15386 --- source/blender/editors/space_graph/graph_utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/editors/space_graph/graph_utils.c b/source/blender/editors/space_graph/graph_utils.c index a813e6ae245..51af795893f 100644 --- a/source/blender/editors/space_graph/graph_utils.c +++ b/source/blender/editors/space_graph/graph_utils.c @@ -81,7 +81,7 @@ void ED_drivers_editor_init(bContext *C, ScrArea *area) bAnimListElem *get_active_fcurve_channel(bAnimContext *ac) { ListBase anim_data = {NULL, NULL}; - int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_ACTIVE); + int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_ACTIVE | ANIMFILTER_FCURVESONLY); size_t items = ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* We take the first F-Curve only, since some other ones may have had 'active' flag set -- cgit v1.2.3 From 97dd10707097536df24dd7ca7d2a45b9a6408711 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Thu, 7 Jul 2022 12:33:45 +0200 Subject: Fix T98029: Support isolated islands of IDs when purging unused ones. Cases were e.g. an object would use a material, and this material would use this object (e.g. through a driver), even if both those data-blocks are technically unused, they would remain forever since they were not detected as such. Now this is properly detected and purged as part of the 'recursive purge' operation. --- source/blender/blenkernel/intern/lib_query.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/source/blender/blenkernel/intern/lib_query.c b/source/blender/blenkernel/intern/lib_query.c index 5de8704e13b..38252a46b93 100644 --- a/source/blender/blenkernel/intern/lib_query.c +++ b/source/blender/blenkernel/intern/lib_query.c @@ -693,6 +693,13 @@ static void lib_query_unused_ids_tag_recurse(Main *bmain, * First recursively check all its valid users, if all of them can be tagged as * unused, then we can tag this ID as such too. */ bool has_valid_from_users = false; + /* Preemptively consider this ID as unused. That way if there is a loop of dependency leading + * back to it, it won't create a fake 'valid user' detection. + * NOTE: This can only only be done for a subset of IDs, some types are never 'indirectly + * unused', same for IDs with a fake user. */ + if ((id->flag & LIB_FAKEUSER) == 0 && !ELEM(GS(id->name), ID_SCE, ID_WM, ID_SCR, ID_WS, ID_LI)) { + id->tag |= tag; + } for (MainIDRelationsEntryItem *id_from_item = id_relations->from_ids; id_from_item != NULL; id_from_item = id_from_item->next) { if ((id_from_item->usage_flag & ignored_usages) != 0 || @@ -715,7 +722,11 @@ static void lib_query_unused_ids_tag_recurse(Main *bmain, break; } } - if (!has_valid_from_users) { + if (has_valid_from_users) { + /* This ID has 'valid' users, clear the 'tag as unused' preemptively set above. */ + id->tag &= ~tag; + } + else { /* This ID has no 'valid' users, tag it as unused. */ id->tag |= tag; if (r_num_tagged != NULL) { -- cgit v1.2.3 From a27024e36d879c3872cfb791f4c5ce805bf57857 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Thu, 7 Jul 2022 12:59:24 +0200 Subject: ID Management: Purge: Make outliner button use recursive purge. This change the 'Purge' button of the Outliner 'Orphaned' view to use recursive purge, i.e. it wil not only delete immediately unused IDs (as listed in the view) anymore, but also all their unused dependencies. --- release/scripts/startup/bl_ui/space_outliner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release/scripts/startup/bl_ui/space_outliner.py b/release/scripts/startup/bl_ui/space_outliner.py index fff252ade01..18057de6767 100644 --- a/release/scripts/startup/bl_ui/space_outliner.py +++ b/release/scripts/startup/bl_ui/space_outliner.py @@ -55,7 +55,7 @@ class OUTLINER_HT_header(Header): layout.operator("outliner.collection_new", text="", icon='COLLECTION_NEW').nested = True elif display_mode == 'ORPHAN_DATA': - layout.operator("outliner.orphans_purge", text="Purge") + layout.operator("outliner.orphans_purge", text="Purge").do_recursive=True elif space.display_mode == 'DATA_API': layout.separator() -- cgit v1.2.3 From 5d6e7df4a8b70aa98d86655b024a87ca3319c6be Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 7 Jul 2022 21:42:05 +1000 Subject: GHOST: initialize grab axis for windows While this didn't cause any user visible bugs, ASAN would report an error when passing it as an argument. --- intern/ghost/intern/GHOST_Window.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/intern/ghost/intern/GHOST_Window.cpp b/intern/ghost/intern/GHOST_Window.cpp index be5d593c401..db4d6c3bb71 100644 --- a/intern/ghost/intern/GHOST_Window.cpp +++ b/intern/ghost/intern/GHOST_Window.cpp @@ -23,6 +23,7 @@ GHOST_Window::GHOST_Window(uint32_t width, : m_drawingContextType(GHOST_kDrawingContextTypeNone), m_cursorVisible(true), m_cursorGrab(GHOST_kGrabDisable), + m_cursorGrabAxis(GHOST_kAxisNone), m_cursorShape(GHOST_kStandardCursorDefault), m_wantStereoVisual(wantStereoVisual), m_context(new GHOST_ContextNone(false)) -- cgit v1.2.3 From e3ef56ef91417519a3301746f4cfd244cd08ef85 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 7 Jul 2022 08:06:30 -0500 Subject: Curves: Add sculpt selection overlay This commit adds visualization to the selection in curves sculpt mode. Previously it was only possible to see the selection when it was connected to a material. In order to obstruct the users vision as little as possible, the selected areas of the curve are left as is, but a dark overlay is drawn over unselected areas. To make it work, the overlay requests the selection attribute and then ensures that the evaluation is complete for curves. Then it retrieves the evaluated selection GPU texture and passes that to the shader. This reuses the existing generic attribute extraction system because there currently wouldn't be any benefits to dealing with selection separately, and because it avoids duplication of the logic that extracts attributes from curves and evaluates them if necessary. Differential Revision: https://developer.blender.org/D15219 --- release/scripts/startup/bl_ui/space_view3d.py | 1 + source/blender/draw/CMakeLists.txt | 3 + .../blender/draw/engines/overlay/overlay_engine.c | 10 ++ .../blender/draw/engines/overlay/overlay_private.h | 7 ++ .../draw/engines/overlay/overlay_sculpt_curves.cc | 96 ++++++++++++++++ .../blender/draw/engines/overlay/overlay_shader.c | 13 +++ .../shaders/infos/overlay_sculpt_curves_info.hh | 21 ++++ .../overlay_sculpt_curves_selection_frag.glsl | 5 + .../overlay_sculpt_curves_selection_vert.glsl | 35 ++++++ source/blender/draw/intern/draw_cache_impl.h | 10 ++ .../blender/draw/intern/draw_cache_impl_curves.cc | 126 +++++++++++++++------ source/blender/draw/tests/shaders_test.cc | 1 + source/blender/editors/curves/intern/curves_ops.cc | 12 +- source/blender/gpu/CMakeLists.txt | 1 + 14 files changed, 298 insertions(+), 43 deletions(-) create mode 100644 source/blender/draw/engines/overlay/overlay_sculpt_curves.cc create mode 100644 source/blender/draw/engines/overlay/shaders/infos/overlay_sculpt_curves_info.hh create mode 100644 source/blender/draw/engines/overlay/shaders/overlay_sculpt_curves_selection_frag.glsl create mode 100644 source/blender/draw/engines/overlay/shaders/overlay_sculpt_curves_selection_vert.glsl diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 5a38c4175a8..91422a817f5 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -6669,6 +6669,7 @@ class VIEW3D_PT_overlay_sculpt_curves(Panel): overlay = view.overlay row = layout.row(align=True) + row.active = overlay.show_overlays row.prop(overlay, "sculpt_mode_mask_opacity", text="Selection Opacity") diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt index f4af9e242d3..81e4b00290a 100644 --- a/source/blender/draw/CMakeLists.txt +++ b/source/blender/draw/CMakeLists.txt @@ -194,6 +194,7 @@ set(SRC engines/overlay/overlay_paint.c engines/overlay/overlay_particle.c engines/overlay/overlay_sculpt.c + engines/overlay/overlay_sculpt_curves.cc engines/overlay/overlay_shader.c engines/overlay/overlay_volume.c engines/overlay/overlay_wireframe.c @@ -556,6 +557,8 @@ set(GLSL_SRC engines/overlay/shaders/overlay_particle_vert.glsl engines/overlay/shaders/overlay_point_varying_color_frag.glsl engines/overlay/shaders/overlay_point_varying_color_varying_outline_aa_frag.glsl + engines/overlay/shaders/overlay_sculpt_curves_selection_frag.glsl + engines/overlay/shaders/overlay_sculpt_curves_selection_vert.glsl engines/overlay/shaders/overlay_sculpt_mask_frag.glsl engines/overlay/shaders/overlay_sculpt_mask_vert.glsl engines/overlay/shaders/overlay_uniform_color_frag.glsl diff --git a/source/blender/draw/engines/overlay/overlay_engine.c b/source/blender/draw/engines/overlay/overlay_engine.c index f8c28394b16..9ec0398e5cb 100644 --- a/source/blender/draw/engines/overlay/overlay_engine.c +++ b/source/blender/draw/engines/overlay/overlay_engine.c @@ -192,6 +192,8 @@ static void OVERLAY_cache_init(void *vedata) OVERLAY_edit_curves_cache_init(vedata); break; case CTX_MODE_SCULPT_CURVES: + OVERLAY_sculpt_curves_cache_init(vedata); + break; case CTX_MODE_OBJECT: break; default: @@ -310,6 +312,8 @@ static void OVERLAY_cache_populate(void *vedata, Object *ob) (draw_ctx->object_mode & OB_MODE_ALL_PAINT); const bool in_sculpt_mode = (ob == draw_ctx->obact) && (ob->sculpt != NULL) && (ob->sculpt->mode_type == OB_MODE_SCULPT); + const bool in_curves_sculpt_mode = (ob == draw_ctx->obact) && + (ob->mode == OB_MODE_SCULPT_CURVES); const bool has_surface = ELEM(ob->type, OB_MESH, OB_CURVES_LEGACY, @@ -428,6 +432,9 @@ static void OVERLAY_cache_populate(void *vedata, Object *ob) if (in_sculpt_mode) { OVERLAY_sculpt_cache_populate(vedata, ob); } + else if (in_curves_sculpt_mode) { + OVERLAY_sculpt_curves_cache_populate(vedata, ob); + } if (draw_motion_paths) { OVERLAY_motion_path_cache_populate(vedata, ob); @@ -591,6 +598,9 @@ static void OVERLAY_draw_scene(void *vedata) case CTX_MODE_SCULPT: OVERLAY_sculpt_draw(vedata); break; + case CTX_MODE_SCULPT_CURVES: + OVERLAY_sculpt_curves_draw(vedata); + break; case CTX_MODE_EDIT_MESH: case CTX_MODE_POSE: case CTX_MODE_PAINT_WEIGHT: diff --git a/source/blender/draw/engines/overlay/overlay_private.h b/source/blender/draw/engines/overlay/overlay_private.h index 23c20a186a0..667e443932c 100644 --- a/source/blender/draw/engines/overlay/overlay_private.h +++ b/source/blender/draw/engines/overlay/overlay_private.h @@ -116,6 +116,7 @@ typedef struct OVERLAY_PassList { DRWPass *particle_ps; DRWPass *pointcloud_ps; DRWPass *sculpt_mask_ps; + DRWPass *sculpt_curves_selection_ps; DRWPass *volume_ps; DRWPass *wireframe_ps; DRWPass *wireframe_xray_ps; @@ -279,6 +280,7 @@ typedef struct OVERLAY_PrivateData { DRWShadingGroup *particle_shapes_grp; DRWShadingGroup *pointcloud_dots_grp; DRWShadingGroup *sculpt_mask_grp; + DRWShadingGroup *sculpt_curves_selection_grp; DRWShadingGroup *volume_selection_surface_grp; DRWShadingGroup *wires_grp[2][2]; /* With and without coloring. */ DRWShadingGroup *wires_all_grp[2][2]; /* With and without coloring. */ @@ -669,6 +671,10 @@ void OVERLAY_sculpt_cache_init(OVERLAY_Data *vedata); void OVERLAY_sculpt_cache_populate(OVERLAY_Data *vedata, Object *ob); void OVERLAY_sculpt_draw(OVERLAY_Data *vedata); +void OVERLAY_sculpt_curves_cache_init(OVERLAY_Data *vedata); +void OVERLAY_sculpt_curves_cache_populate(OVERLAY_Data *vedata, Object *ob); +void OVERLAY_sculpt_curves_draw(OVERLAY_Data *vedata); + void OVERLAY_wireframe_init(OVERLAY_Data *vedata); void OVERLAY_wireframe_cache_init(OVERLAY_Data *vedata); void OVERLAY_wireframe_cache_populate(OVERLAY_Data *vedata, @@ -750,6 +756,7 @@ GPUShader *OVERLAY_shader_paint_wire(void); GPUShader *OVERLAY_shader_particle_dot(void); GPUShader *OVERLAY_shader_particle_shape(void); GPUShader *OVERLAY_shader_sculpt_mask(void); +GPUShader *OVERLAY_shader_sculpt_curves_selection(void); GPUShader *OVERLAY_shader_volume_velocity(bool use_needle, bool use_mac); GPUShader *OVERLAY_shader_volume_gridlines(bool color_with_flags, bool color_range); GPUShader *OVERLAY_shader_wireframe(bool custom_bias); diff --git a/source/blender/draw/engines/overlay/overlay_sculpt_curves.cc b/source/blender/draw/engines/overlay/overlay_sculpt_curves.cc new file mode 100644 index 00000000000..b8021124f27 --- /dev/null +++ b/source/blender/draw/engines/overlay/overlay_sculpt_curves.cc @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. */ + +/** \file + * \ingroup draw_engine + */ + +#include "DRW_render.h" + +#include "draw_cache_impl.h" +#include "overlay_private.h" + +#include "BKE_curves.hh" + +void OVERLAY_sculpt_curves_cache_init(OVERLAY_Data *vedata) +{ + OVERLAY_PassList *psl = vedata->psl; + OVERLAY_PrivateData *pd = vedata->stl->pd; + + const DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_EQUAL | DRW_STATE_BLEND_ALPHA; + DRW_PASS_CREATE(psl->sculpt_curves_selection_ps, state | pd->clipping_state); + + GPUShader *sh = OVERLAY_shader_sculpt_curves_selection(); + pd->sculpt_curves_selection_grp = DRW_shgroup_create(sh, psl->sculpt_curves_selection_ps); + DRWShadingGroup *grp = pd->sculpt_curves_selection_grp; + + /* Reuse the same mask opacity from sculpt mode, since it wasn't worth it to add a different + * property yet. */ + DRW_shgroup_uniform_float_copy(grp, "selection_opacity", pd->overlay.sculpt_mode_mask_opacity); +} + +static bool everything_selected(const Curves &curves_id) +{ + if (!(curves_id.flag & CV_SCULPT_SELECTION_ENABLED)) { + /* When the selection is disabled, conceptually everything is selected. */ + return true; + } + const blender::bke::CurvesGeometry &curves = blender::bke::CurvesGeometry::wrap( + curves_id.geometry); + blender::VArray selection; + switch (curves_id.selection_domain) { + case ATTR_DOMAIN_POINT: + selection = curves.selection_point_float(); + break; + case ATTR_DOMAIN_CURVE: + selection = curves.selection_curve_float(); + break; + } + return selection.is_single() && selection.get_internal_single() == 1.0f; +} + +void OVERLAY_sculpt_curves_cache_populate(OVERLAY_Data *vedata, Object *object) +{ + OVERLAY_PrivateData *pd = vedata->stl->pd; + Curves *curves = static_cast(object->data); + + /* As an optimization, return early if everything is selected. */ + if (everything_selected(*curves)) { + return; + } + + /* Retrieve the location of the texture. */ + const char *name = curves->selection_domain == ATTR_DOMAIN_POINT ? ".selection_point_float" : + ".selection_curve_float"; + + bool is_point_domain; + GPUTexture **texture = DRW_curves_texture_for_evaluated_attribute( + curves, name, &is_point_domain); + if (texture == nullptr) { + return; + } + + /* Evaluate curves and their attributes if necessary. */ + DRWShadingGroup *grp = DRW_shgroup_curves_create_sub( + object, pd->sculpt_curves_selection_grp, nullptr); + if (*texture == nullptr) { + return; + } + + DRW_shgroup_uniform_bool_copy(grp, "is_point_domain", is_point_domain); + DRW_shgroup_uniform_texture(grp, "selection_tx", *texture); +} + +void OVERLAY_sculpt_curves_draw(OVERLAY_Data *vedata) +{ + OVERLAY_PassList *psl = vedata->psl; + OVERLAY_PrivateData *pd = vedata->stl->pd; + OVERLAY_FramebufferList *fbl = vedata->fbl; + + if (DRW_state_is_fbo()) { + GPU_framebuffer_bind(pd->painting.in_front ? fbl->overlay_in_front_fb : + fbl->overlay_default_fb); + } + + DRW_draw_pass(psl->sculpt_curves_selection_ps); +} diff --git a/source/blender/draw/engines/overlay/overlay_shader.c b/source/blender/draw/engines/overlay/overlay_shader.c index 48146fbddfb..7b7e84c307b 100644 --- a/source/blender/draw/engines/overlay/overlay_shader.c +++ b/source/blender/draw/engines/overlay/overlay_shader.c @@ -90,6 +90,7 @@ typedef struct OVERLAY_Shaders { GPUShader *particle_shape; GPUShader *pointcloud_dot; GPUShader *sculpt_mask; + GPUShader *sculpt_curves_selection; GPUShader *uniform_color; GPUShader *volume_velocity_needle_sh; GPUShader *volume_velocity_mac_sh; @@ -792,6 +793,18 @@ GPUShader *OVERLAY_shader_sculpt_mask(void) return sh_data->sculpt_mask; } +GPUShader *OVERLAY_shader_sculpt_curves_selection(void) +{ + const DRWContextState *draw_ctx = DRW_context_state_get(); + OVERLAY_Shaders *sh_data = &e_data.sh_data[draw_ctx->sh_cfg]; + if (!sh_data->sculpt_curves_selection) { + sh_data->sculpt_curves_selection = GPU_shader_create_from_info_name( + draw_ctx->sh_cfg == GPU_SHADER_CFG_CLIPPED ? "overlay_sculpt_curves_selection_clipped" : + "overlay_sculpt_curves_selection"); + } + return sh_data->sculpt_curves_selection; +} + struct GPUShader *OVERLAY_shader_uniform_color(void) { const DRWContextState *draw_ctx = DRW_context_state_get(); diff --git a/source/blender/draw/engines/overlay/shaders/infos/overlay_sculpt_curves_info.hh b/source/blender/draw/engines/overlay/shaders/infos/overlay_sculpt_curves_info.hh new file mode 100644 index 00000000000..46e3943b293 --- /dev/null +++ b/source/blender/draw/engines/overlay/shaders/infos/overlay_sculpt_curves_info.hh @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_INTERFACE_INFO(overlay_sculpt_curves_selection_iface, "") + .smooth(Type::FLOAT, "mask_weight"); + +GPU_SHADER_CREATE_INFO(overlay_sculpt_curves_selection) + .do_static_compilation(true) + .push_constant(Type::BOOL, "is_point_domain") + .push_constant(Type::FLOAT, "selection_opacity") + .sampler(0, ImageType::FLOAT_BUFFER, "selection_tx") + .vertex_out(overlay_sculpt_curves_selection_iface) + .vertex_source("overlay_sculpt_curves_selection_vert.glsl") + .fragment_source("overlay_sculpt_curves_selection_frag.glsl") + .fragment_out(0, Type::VEC4, "out_color") + .additional_info("draw_hair", "draw_globals"); + +GPU_SHADER_CREATE_INFO(overlay_sculpt_curves_selection_clipped) + .do_static_compilation(true) + .additional_info("overlay_sculpt_curves_selection", "drw_clipped"); diff --git a/source/blender/draw/engines/overlay/shaders/overlay_sculpt_curves_selection_frag.glsl b/source/blender/draw/engines/overlay/shaders/overlay_sculpt_curves_selection_frag.glsl new file mode 100644 index 00000000000..7af6bdb9fdb --- /dev/null +++ b/source/blender/draw/engines/overlay/shaders/overlay_sculpt_curves_selection_frag.glsl @@ -0,0 +1,5 @@ + +void main() +{ + out_color = vec4(vec3(0.0), 1.0 - mask_weight); +} diff --git a/source/blender/draw/engines/overlay/shaders/overlay_sculpt_curves_selection_vert.glsl b/source/blender/draw/engines/overlay/shaders/overlay_sculpt_curves_selection_vert.glsl new file mode 100644 index 00000000000..9ed959b5ea1 --- /dev/null +++ b/source/blender/draw/engines/overlay/shaders/overlay_sculpt_curves_selection_vert.glsl @@ -0,0 +1,35 @@ +#pragma BLENDER_REQUIRE(common_hair_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_clipping_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) + + +float retrieve_selection() +{ + if (is_point_domain) { + return texelFetch(selection_tx, hair_get_base_id()).r; + } + return texelFetch(selection_tx, hair_get_strand_id()).r; +} + +void main() +{ + bool is_persp = (ProjectionMatrix[3][3] == 0.0); + float time, thick_time, thickness; + vec3 world_pos, tan, binor; + hair_get_pos_tan_binor_time(is_persp, + ModelMatrixInverse, + ViewMatrixInverse[3].xyz, + ViewMatrixInverse[2].xyz, + world_pos, + tan, + binor, + time, + thickness, + thick_time); + + gl_Position = point_world_to_ndc(world_pos); + + mask_weight = 1.0 - (selection_opacity - retrieve_selection() * selection_opacity); + + view_clipping_distances(world_pos); +} diff --git a/source/blender/draw/intern/draw_cache_impl.h b/source/blender/draw/intern/draw_cache_impl.h index 4fa5813d476..91dbef8d7b1 100644 --- a/source/blender/draw/intern/draw_cache_impl.h +++ b/source/blender/draw/intern/draw_cache_impl.h @@ -161,6 +161,16 @@ struct GPUBatch *DRW_lattice_batch_cache_get_edit_verts(struct Lattice *lt); int DRW_curves_material_count_get(struct Curves *curves); +/** + * Provide GPU access to a specific evaluated attribute on curves. + * + * \return A pointer to location where the texture will be + * stored, which will be filled by #DRW_shgroup_curves_create_sub. + */ +struct GPUTexture **DRW_curves_texture_for_evaluated_attribute(struct Curves *curves, + const char *name, + bool *r_is_point_domain); + struct GPUBatch *DRW_curves_batch_cache_get_edit_points(struct Curves *curves); void DRW_curves_batch_cache_create_requested(struct Object *ob); diff --git a/source/blender/draw/intern/draw_cache_impl_curves.cc b/source/blender/draw/intern/draw_cache_impl_curves.cc index 6e098e0310d..2460482fbbf 100644 --- a/source/blender/draw/intern/draw_cache_impl_curves.cc +++ b/source/blender/draw/intern/draw_cache_impl_curves.cc @@ -512,40 +512,41 @@ static bool curves_ensure_attributes(const Curves &curves, ThreadMutex *render_mutex = &cache.render_mutex; const CustomData *cd_curve = &curves.geometry.curve_data; const CustomData *cd_point = &curves.geometry.point_data; + CurvesEvalFinalCache &final_cache = cache.curves_cache.final[subdiv]; - DRW_Attributes attrs_needed; - drw_attributes_clear(&attrs_needed); - ListBase gpu_attrs = GPU_material_attributes(gpu_material); - LISTBASE_FOREACH (GPUMaterialAttribute *, gpu_attr, &gpu_attrs) { - const char *name = gpu_attr->name; - - int layer_index; - eCustomDataType type; - eAttrDomain domain; - if (drw_custom_data_match_attribute(cd_curve, name, &layer_index, &type)) { - domain = ATTR_DOMAIN_CURVE; - } - else if (drw_custom_data_match_attribute(cd_point, name, &layer_index, &type)) { - domain = ATTR_DOMAIN_POINT; - } - else { - continue; - } - - drw_attributes_add_request(&attrs_needed, name, type, layer_index, domain); - } + if (gpu_material) { + DRW_Attributes attrs_needed; + drw_attributes_clear(&attrs_needed); + ListBase gpu_attrs = GPU_material_attributes(gpu_material); + LISTBASE_FOREACH (GPUMaterialAttribute *, gpu_attr, &gpu_attrs) { + const char *name = gpu_attr->name; + + int layer_index; + eCustomDataType type; + eAttrDomain domain; + if (drw_custom_data_match_attribute(cd_curve, name, &layer_index, &type)) { + domain = ATTR_DOMAIN_CURVE; + } + else if (drw_custom_data_match_attribute(cd_point, name, &layer_index, &type)) { + domain = ATTR_DOMAIN_POINT; + } + else { + continue; + } - CurvesEvalFinalCache &final_cache = cache.curves_cache.final[subdiv]; + drw_attributes_add_request(&attrs_needed, name, type, layer_index, domain); + } - if (!drw_attributes_overlap(&final_cache.attr_used, &attrs_needed)) { - /* Some new attributes have been added, free all and start over. */ - for (const int i : IndexRange(GPU_MAX_ATTR)) { - GPU_VERTBUF_DISCARD_SAFE(cache.curves_cache.proc_attributes_buf[i]); - DRW_TEXTURE_FREE_SAFE(cache.curves_cache.proc_attributes_tex[i]); + if (!drw_attributes_overlap(&final_cache.attr_used, &attrs_needed)) { + /* Some new attributes have been added, free all and start over. */ + for (const int i : IndexRange(GPU_MAX_ATTR)) { + GPU_VERTBUF_DISCARD_SAFE(cache.curves_cache.proc_attributes_buf[i]); + DRW_TEXTURE_FREE_SAFE(cache.curves_cache.proc_attributes_tex[i]); + } + drw_attributes_merge(&final_cache.attr_used, &attrs_needed, render_mutex); } - drw_attributes_merge(&final_cache.attr_used, &attrs_needed, render_mutex); + drw_attributes_merge(&final_cache.attr_used_over_time, &attrs_needed, render_mutex); } - drw_attributes_merge(&final_cache.attr_used_over_time, &attrs_needed, render_mutex); bool need_tf_update = false; @@ -602,9 +603,7 @@ bool curves_ensure_procedural_data(Curves *curves, *curves, cache.curves_cache, thickness_res, subdiv); } - if (gpu_material) { - need_ft_update |= curves_ensure_attributes(*curves, cache, gpu_material, subdiv); - } + need_ft_update |= curves_ensure_attributes(*curves, cache, gpu_material, subdiv); return need_ft_update; } @@ -620,6 +619,69 @@ GPUBatch *DRW_curves_batch_cache_get_edit_points(Curves *curves) return DRW_batch_request(&cache.edit_points); } +static void request_attribute(Curves &curves, const char *name) +{ + CurvesBatchCache &cache = curves_batch_cache_get(curves); + const DRWContextState *draw_ctx = DRW_context_state_get(); + const Scene *scene = draw_ctx->scene; + const int subdiv = scene->r.hair_subdiv; + CurvesEvalFinalCache &final_cache = cache.curves_cache.final[subdiv]; + + DRW_Attributes attributes{}; + + CurveComponent component; + component.replace(&curves, GeometryOwnershipType::ReadOnly); + std::optional meta_data = component.attribute_get_meta_data(name); + if (!meta_data) { + return; + } + const eAttrDomain domain = meta_data->domain; + const eCustomDataType type = meta_data->data_type; + const CustomData &custom_data = domain == ATTR_DOMAIN_POINT ? curves.geometry.point_data : + curves.geometry.curve_data; + + drw_attributes_add_request( + &attributes, name, type, CustomData_get_named_layer(&custom_data, type, name), domain); + + drw_attributes_merge(&final_cache.attr_used, &attributes, &cache.render_mutex); +} + +GPUTexture **DRW_curves_texture_for_evaluated_attribute(Curves *curves, + const char *name, + bool *r_is_point_domain) +{ + CurvesBatchCache &cache = curves_batch_cache_get(*curves); + const DRWContextState *draw_ctx = DRW_context_state_get(); + const Scene *scene = draw_ctx->scene; + const int subdiv = scene->r.hair_subdiv; + CurvesEvalFinalCache &final_cache = cache.curves_cache.final[subdiv]; + + request_attribute(*curves, name); + + int request_i = -1; + for (const int i : IndexRange(final_cache.attr_used.num_requests)) { + if (STREQ(final_cache.attr_used.requests[i].attribute_name, name)) { + request_i = i; + break; + } + } + if (request_i == -1) { + *r_is_point_domain = false; + return nullptr; + } + switch (final_cache.attr_used.requests[request_i].domain) { + case ATTR_DOMAIN_POINT: + *r_is_point_domain = true; + return &final_cache.attributes_tex[request_i]; + case ATTR_DOMAIN_CURVE: + *r_is_point_domain = false; + return &cache.curves_cache.proc_attributes_tex[request_i]; + default: + BLI_assert_unreachable(); + return nullptr; + } +} + void DRW_curves_batch_cache_create_requested(Object *ob) { Curves *curves = static_cast(ob->data); diff --git a/source/blender/draw/tests/shaders_test.cc b/source/blender/draw/tests/shaders_test.cc index 2bc0c9af895..f8c5715e2f5 100644 --- a/source/blender/draw/tests/shaders_test.cc +++ b/source/blender/draw/tests/shaders_test.cc @@ -270,6 +270,7 @@ static void test_overlay_glsl_shaders() EXPECT_NE(OVERLAY_shader_particle_dot(), nullptr); EXPECT_NE(OVERLAY_shader_particle_shape(), nullptr); EXPECT_NE(OVERLAY_shader_sculpt_mask(), nullptr); + EXPECT_NE(OVERLAY_shader_sculpt_curves_selection(), nullptr); EXPECT_NE(OVERLAY_shader_volume_velocity(false, false), nullptr); EXPECT_NE(OVERLAY_shader_volume_velocity(false, true), nullptr); EXPECT_NE(OVERLAY_shader_volume_velocity(true, false), nullptr); diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc index d9f207103ae..a6ae1ba5e24 100644 --- a/source/blender/editors/curves/intern/curves_ops.cc +++ b/source/blender/editors/curves/intern/curves_ops.cc @@ -900,21 +900,11 @@ static int select_all_exec(bContext *C, wmOperator *op) for (Curves *curves_id : unique_curves) { if (action == SEL_SELECT) { - /* The optimization to avoid storing the selection when everything is selected causes too - * many problems at the moment, since there is no proper visualization yet. Keep the code but - * disable it for now. */ -#if 0 + /* As an optimization, just remove the selection attributes when everything is selected. */ CurveComponent component; component.replace(curves_id, GeometryOwnershipType::Editable); component.attribute_try_delete(".selection_point_float"); component.attribute_try_delete(".selection_curve_float"); -#else - CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry); - MutableSpan selection = curves_id->selection_domain == ATTR_DOMAIN_POINT ? - curves.selection_point_float_for_write() : - curves.selection_curve_float_for_write(); - selection.fill(1.0f); -#endif } else { CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry); diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index 9b5ce6e147e..0f539d4b30a 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -465,6 +465,7 @@ set(SRC_SHADER_CREATE_INFOS ../draw/engines/overlay/shaders/infos/overlay_outline_info.hh ../draw/engines/overlay/shaders/infos/overlay_paint_info.hh ../draw/engines/overlay/shaders/infos/overlay_sculpt_info.hh + ../draw/engines/overlay/shaders/infos/overlay_sculpt_curves_info.hh ../draw/engines/overlay/shaders/infos/overlay_volume_info.hh ../draw/engines/overlay/shaders/infos/overlay_wireframe_info.hh ../draw/engines/select/shaders/infos/select_id_info.hh -- cgit v1.2.3 From 5c3dc52536d373253a0de584f80f7ada9528f003 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 7 Jul 2022 08:13:11 -0500 Subject: Cleanup: Improve variable name The new name makes more sense in non-node-related contexts. --- source/blender/editors/include/UI_interface.hh | 2 +- .../editors/interface/interface_template_attribute_search.cc | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/source/blender/editors/include/UI_interface.hh b/source/blender/editors/include/UI_interface.hh index 63cf8fa0c56..82bfdd7e212 100644 --- a/source/blender/editors/include/UI_interface.hh +++ b/source/blender/editors/include/UI_interface.hh @@ -46,7 +46,7 @@ void template_breadcrumbs(uiLayout &layout, Span context_path); void attribute_search_add_items( StringRefNull str, - bool is_output, + bool can_create_attribute, Span infos, uiSearchItems *items, bool is_first); diff --git a/source/blender/editors/interface/interface_template_attribute_search.cc b/source/blender/editors/interface/interface_template_attribute_search.cc index 4e587bd5338..0a684903f0f 100644 --- a/source/blender/editors/interface/interface_template_attribute_search.cc +++ b/source/blender/editors/interface/interface_template_attribute_search.cc @@ -50,7 +50,7 @@ static bool attribute_search_item_add(uiSearchItems *items, const GeometryAttrib } void attribute_search_add_items(StringRefNull str, - const bool is_output, + const bool can_create_attribute, Span infos, uiSearchItems *seach_items, const bool is_first) @@ -68,8 +68,12 @@ void attribute_search_add_items(StringRefNull str, } if (!contained) { dummy_info.name = str; - UI_search_item_add( - seach_items, str.c_str(), &dummy_info, is_output ? ICON_ADD : ICON_NONE, 0, 0); + UI_search_item_add(seach_items, + str.c_str(), + &dummy_info, + can_create_attribute ? ICON_ADD : ICON_NONE, + 0, + 0); } } -- cgit v1.2.3 From ed7dc4282c034fded840ca69c428050af889742a Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 7 Jul 2022 08:14:00 -0500 Subject: Cleanup: Correct comment with spreadsheet enum type --- source/blender/makesdna/DNA_space_types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index 2a5ca4c9541..6bc68baa640 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -1939,7 +1939,7 @@ typedef struct SpaceSpreadsheet { uint8_t geometry_component_type; /* #eAttrDomain. */ uint8_t attribute_domain; - /* eSpaceSpreadsheet_ObjectContext. */ + /* eSpaceSpreadsheet_ObjectEvalState. */ uint8_t object_eval_state; /* eSpaceSpreadsheet_Flag. */ -- cgit v1.2.3 From 9a4927031dade70e2fac42a5003ab4a21bfb9e3d Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 7 Jul 2022 08:15:29 -0500 Subject: Cleanup: Remove redundant filtering of legacy normal attribute This is already done inside of `attribute_search_add_items`. --- .../blender/editors/space_node/node_geometry_attribute_search.cc | 8 -------- 1 file changed, 8 deletions(-) diff --git a/source/blender/editors/space_node/node_geometry_attribute_search.cc b/source/blender/editors/space_node/node_geometry_attribute_search.cc index f08e23c8371..e328a86b0fd 100644 --- a/source/blender/editors/space_node/node_geometry_attribute_search.cc +++ b/source/blender/editors/space_node/node_geometry_attribute_search.cc @@ -113,14 +113,6 @@ static void attribute_search_update_fn( Vector infos = get_attribute_info_from_context(*C, *data); - /* Remove the deprecated normal attribute from the search. */ - for (const int i : infos.index_range()) { - if (infos[i]->domain == ATTR_DOMAIN_FACE && infos[i]->name == "normal") { - infos.remove(i); - break; - } - } - ui::attribute_search_add_items(str, true, infos, items, is_first); } -- cgit v1.2.3 From eb7218de8dba2a977fdcf8f2e75b16fcd8fc044a Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Mon, 4 Jul 2022 13:02:24 +0200 Subject: Fix T99386: Driven modifiers are always re-evaluated during animation Even if the driver is not dependent on time the modifiers were always re-evaluated during playback. This is due to the legacy nature of the check whether modifier depends on time or not: it was simply checking for sub-string match for modifier in the F-Curve and drivers RNA paths. Nowadays such dependencies are created by the dependency graph builder, which allows to have more granular control over what depends on what. The code is now simplified to only check for "static" dependency of the modifier form time: for example, Wave modifier which always depends on time (even without explicit animation involved). This change also fixes missing relation from the animation component to the shader_fx modifiers, fixing race condition. Additional files used to verify relations: - Geometry: F13257368 - Grease Pencil: F13257369 - Shader FX: F13257370 In these files different types of modifiers have an animated property, and the purpose of the test is to verify that the modifiers do react to the animation and that there is a relation between animation and geometry components of the object. The latter one can only be checked using the dependency graph relation visualization. The drivers are not tested by these files. Those are not typically depend on time, and if there were missing relation from driver to the modifier we'd receive a bug report already. As well as if there was a bug in missing time relation to a driver we'd also receive a report. Differential Revision: https://developer.blender.org/D15358 --- source/blender/blenkernel/BKE_object.h | 5 - source/blender/blenkernel/intern/object.cc | 120 --------------------- .../intern/builder/deg_builder_relations.cc | 6 +- .../depsgraph/intern/builder/deg_builder_rna.cc | 3 +- 4 files changed, 5 insertions(+), 129 deletions(-) diff --git a/source/blender/blenkernel/BKE_object.h b/source/blender/blenkernel/BKE_object.h index 6ed05bc267a..96abea0e5ee 100644 --- a/source/blender/blenkernel/BKE_object.h +++ b/source/blender/blenkernel/BKE_object.h @@ -69,9 +69,6 @@ void BKE_object_free_caches(struct Object *object); void BKE_object_modifier_hook_reset(struct Object *ob, struct HookModifierData *hmd); void BKE_object_modifier_gpencil_hook_reset(struct Object *ob, struct HookGpencilModifierData *hmd); -bool BKE_object_modifier_gpencil_use_time(struct Object *ob, struct GpencilModifierData *md); - -bool BKE_object_shaderfx_use_time(struct Object *ob, struct ShaderFxData *fx); /** * \return True if the object's type supports regular modifiers (not grease pencil modifiers). @@ -633,8 +630,6 @@ void BKE_object_groups_clear(struct Main *bmain, struct Scene *scene, struct Obj */ struct KDTree_3d *BKE_object_as_kdtree(struct Object *ob, int *r_tot); -bool BKE_object_modifier_use_time(struct Scene *scene, struct Object *ob, struct ModifierData *md); - /** * \note this function should eventually be replaced by depsgraph functionality. * Avoid calling this in new code unless there is a very good reason for it! diff --git a/source/blender/blenkernel/intern/object.cc b/source/blender/blenkernel/intern/object.cc index 5ed1ac98ddf..f7436b6112c 100644 --- a/source/blender/blenkernel/intern/object.cc +++ b/source/blender/blenkernel/intern/object.cc @@ -5357,126 +5357,6 @@ KDTree_3d *BKE_object_as_kdtree(Object *ob, int *r_tot) /** \name Object Modifier Utilities * \{ */ -bool BKE_object_modifier_use_time(Scene *scene, Object *ob, ModifierData *md) -{ - if (BKE_modifier_depends_ontime(scene, md)) { - return true; - } - - /* Check whether modifier is animated. */ - /* TODO: this should be handled as part of build_animdata() -- Aligorith */ - if (ob->adt) { - AnimData *adt = ob->adt; - FCurve *fcu; - - char md_name_esc[sizeof(md->name) * 2]; - BLI_str_escape(md_name_esc, md->name, sizeof(md_name_esc)); - - char pattern[sizeof(md_name_esc) + 16]; - BLI_snprintf(pattern, sizeof(pattern), "modifiers[\"%s\"]", md_name_esc); - - /* action - check for F-Curves with paths containing 'modifiers[' */ - if (adt->action) { - for (fcu = (FCurve *)adt->action->curves.first; fcu != nullptr; fcu = (FCurve *)fcu->next) { - if (fcu->rna_path && strstr(fcu->rna_path, pattern)) { - return true; - } - } - } - - /* This here allows modifier properties to get driven and still update properly - * - * Workaround to get T26764 (e.g. subsurf levels not updating when animated/driven) - * working, without the updating problems (T28525 T28690 T28774 T28777) caused - * by the RNA updates cache introduced in r.38649 - */ - for (fcu = (FCurve *)adt->drivers.first; fcu != nullptr; fcu = (FCurve *)fcu->next) { - if (fcu->rna_path && strstr(fcu->rna_path, pattern)) { - return true; - } - } - - /* XXX: also, should check NLA strips, though for now assume that nobody uses - * that and we can omit that for performance reasons... */ - } - - return false; -} - -bool BKE_object_modifier_gpencil_use_time(Object *ob, GpencilModifierData *md) -{ - if (BKE_gpencil_modifier_depends_ontime(md)) { - return true; - } - - /* Check whether modifier is animated. */ - /* TODO(Aligorith): this should be handled as part of build_animdata() */ - if (ob->adt) { - AnimData *adt = ob->adt; - - char md_name_esc[sizeof(md->name) * 2]; - BLI_str_escape(md_name_esc, md->name, sizeof(md_name_esc)); - - char pattern[sizeof(md_name_esc) + 32]; - BLI_snprintf(pattern, sizeof(pattern), "grease_pencil_modifiers[\"%s\"]", md_name_esc); - - /* action - check for F-Curves with paths containing 'grease_pencil_modifiers[' */ - if (adt->action) { - LISTBASE_FOREACH (FCurve *, fcu, &adt->action->curves) { - if (fcu->rna_path && strstr(fcu->rna_path, pattern)) { - return true; - } - } - } - - /* This here allows modifier properties to get driven and still update properly */ - LISTBASE_FOREACH (FCurve *, fcu, &adt->drivers) { - if (fcu->rna_path && strstr(fcu->rna_path, pattern)) { - return true; - } - } - } - - return false; -} - -bool BKE_object_shaderfx_use_time(Object *ob, ShaderFxData *fx) -{ - if (BKE_shaderfx_depends_ontime(fx)) { - return true; - } - - /* Check whether effect is animated. */ - /* TODO(Aligorith): this should be handled as part of build_animdata() */ - if (ob->adt) { - AnimData *adt = ob->adt; - - char fx_name_esc[sizeof(fx->name) * 2]; - BLI_str_escape(fx_name_esc, fx->name, sizeof(fx_name_esc)); - - char pattern[sizeof(fx_name_esc) + 32]; - BLI_snprintf(pattern, sizeof(pattern), "shader_effects[\"%s\"]", fx_name_esc); - - /* action - check for F-Curves with paths containing string[' */ - if (adt->action) { - LISTBASE_FOREACH (FCurve *, fcu, &adt->action->curves) { - if (fcu->rna_path && strstr(fcu->rna_path, pattern)) { - return true; - } - } - } - - /* This here allows properties to get driven and still update properly */ - LISTBASE_FOREACH (FCurve *, fcu, &adt->drivers) { - if (fcu->rna_path && strstr(fcu->rna_path, pattern)) { - return true; - } - } - } - - return false; -} - /** * Set "ignore cache" flag for all caches on this object. */ diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc index afcf3eaacb7..31d5308e825 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc @@ -2189,7 +2189,7 @@ void DepsgraphRelationBuilder::build_object_data_geometry(Object *object) ctx.node = reinterpret_cast<::DepsNodeHandle *>(&handle); mti->updateDepsgraph(md, &ctx); } - if (BKE_object_modifier_use_time(scene_, object, md)) { + if (BKE_modifier_depends_ontime(scene_, md)) { TimeSourceKey time_src_key; add_relation(time_src_key, obdata_ubereval_key, "Time Source -> Modifier"); } @@ -2208,7 +2208,7 @@ void DepsgraphRelationBuilder::build_object_data_geometry(Object *object) ctx.node = reinterpret_cast<::DepsNodeHandle *>(&handle); mti->updateDepsgraph(md, &ctx, graph_->mode); } - if (BKE_object_modifier_gpencil_use_time(object, md)) { + if (BKE_gpencil_modifier_depends_ontime(md)) { TimeSourceKey time_src_key; add_relation(time_src_key, obdata_ubereval_key, "Time Source"); } @@ -2226,7 +2226,7 @@ void DepsgraphRelationBuilder::build_object_data_geometry(Object *object) ctx.node = reinterpret_cast<::DepsNodeHandle *>(&handle); fxi->updateDepsgraph(fx, &ctx); } - if (BKE_object_shaderfx_use_time(object, fx)) { + if (BKE_shaderfx_depends_ontime(fx)) { TimeSourceKey time_src_key; add_relation(time_src_key, obdata_ubereval_key, "Time Source"); } diff --git a/source/blender/depsgraph/intern/builder/deg_builder_rna.cc b/source/blender/depsgraph/intern/builder/deg_builder_rna.cc index ac7a5bc2f30..5202ada5408 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_rna.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_rna.cc @@ -271,7 +271,8 @@ RNANodeIdentifier RNANodeQuery::construct_node_identifier(const PointerRNA *ptr, RNA_struct_is_a(ptr->type, &RNA_LatticePoint) || RNA_struct_is_a(ptr->type, &RNA_MeshUVLoop) || RNA_struct_is_a(ptr->type, &RNA_MeshLoopColor) || - RNA_struct_is_a(ptr->type, &RNA_VertexGroupElement)) { + RNA_struct_is_a(ptr->type, &RNA_VertexGroupElement) || + RNA_struct_is_a(ptr->type, &RNA_ShaderFx)) { /* When modifier is used as FROM operation this is likely referencing to * the property (for example, modifier's influence). * But when it's used as TO operation, this is geometry component. */ -- cgit v1.2.3 From 3063d90cfc057b4e1f269fd0723abbc0f2ae12f0 Mon Sep 17 00:00:00 2001 From: Pratik Borhade Date: Thu, 7 Jul 2022 15:23:41 +0200 Subject: Fix T99453: Regression: Crash on calling menu search Fix T99453. Crash due to null pointer access. So wrap the function call in simple `if` check Reviewed By: sybren Maniphest Tasks: T99453 Differential Revision: https://developer.blender.org/D15370 --- source/blender/editors/object/object_vgroup.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/blender/editors/object/object_vgroup.c b/source/blender/editors/object/object_vgroup.c index 158bc28662a..17b7fe7fe5e 100644 --- a/source/blender/editors/object/object_vgroup.c +++ b/source/blender/editors/object/object_vgroup.c @@ -732,8 +732,10 @@ const EnumPropertyItem *ED_object_vgroup_selection_itemf_helper(const bContext * } /* Set `Deform Bone` as default selection if armature is present. */ - RNA_def_property_enum_default( - prop, BKE_modifiers_is_deformed_by_armature(ob) ? WT_VGROUP_BONE_DEFORM : WT_VGROUP_ALL); + if (ob) { + RNA_def_property_enum_default( + prop, BKE_modifiers_is_deformed_by_armature(ob) ? WT_VGROUP_BONE_DEFORM : WT_VGROUP_ALL); + } RNA_enum_item_end(&item, &totitem); *r_free = true; -- cgit v1.2.3 From a26038ff385122f8d6f827dd02638d9f5dc35943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Am=C3=A9lie=20Fondevilla?= Date: Thu, 7 Jul 2022 15:28:15 +0200 Subject: Fix T99505: NLA tweak mode crashes with GPencil data Adding Grease Pencil keyframes in the dopesheet (rB92d7f9ac56e0) lead to crashes from the NLA editor (T99505). This is now resolved, by removing grease pencil keyframes from NLA editor (as it was in 3.2), and filtering them out of all NLA-related operations. Reviewed By: sybren Differential Revision: https://developer.blender.org/D15391 --- source/blender/editors/space_nla/nla_buttons.c | 2 +- source/blender/editors/space_nla/nla_channels.c | 12 ++-- source/blender/editors/space_nla/nla_draw.c | 6 +- source/blender/editors/space_nla/nla_edit.c | 71 ++++++++++++++-------- source/blender/editors/space_nla/nla_select.c | 10 +-- .../editors/transform/transform_convert_nla.c | 5 +- 6 files changed, 67 insertions(+), 39 deletions(-) diff --git a/source/blender/editors/space_nla/nla_buttons.c b/source/blender/editors/space_nla/nla_buttons.c index 3b469746f17..9652819404e 100644 --- a/source/blender/editors/space_nla/nla_buttons.c +++ b/source/blender/editors/space_nla/nla_buttons.c @@ -79,7 +79,7 @@ bool nla_panel_context(const bContext *C, */ /* XXX: double-check active! */ filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_ACTIVE | - ANIMFILTER_LIST_CHANNELS); + ANIMFILTER_LIST_CHANNELS | ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); for (ale = anim_data.first; ale; ale = ale->next) { diff --git a/source/blender/editors/space_nla/nla_channels.c b/source/blender/editors/space_nla/nla_channels.c index 40082b08806..a0c6a29c422 100644 --- a/source/blender/editors/space_nla/nla_channels.c +++ b/source/blender/editors/space_nla/nla_channels.c @@ -68,7 +68,8 @@ static int mouse_nla_channels(bContext *C, bAnimContext *ac, int channel_index, /* get the channel that was clicked on */ /* filter channels */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* get channel from index */ @@ -394,7 +395,8 @@ static int nlachannels_pushdown_exec(bContext *C, wmOperator *op) int filter; /* filter channels */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* get channel from index */ @@ -561,7 +563,7 @@ bool nlaedit_add_tracks_existing(bAnimContext *ac, bool above_sel) /* get a list of the (selected) NLA Tracks being shown in the NLA */ filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_SEL | - ANIMFILTER_NODUPLIS); + ANIMFILTER_NODUPLIS | ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* add tracks... */ @@ -608,7 +610,7 @@ bool nlaedit_add_tracks_empty(bAnimContext *ac) /* get a list of the selected AnimData blocks in the NLA */ filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_ANIMDATA | - ANIMFILTER_SEL | ANIMFILTER_NODUPLIS); + ANIMFILTER_SEL | ANIMFILTER_NODUPLIS | ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* check if selected AnimData blocks are empty, and add tracks if so... */ @@ -710,7 +712,7 @@ static int nlaedit_delete_tracks_exec(bContext *C, wmOperator *UNUSED(op)) /* get a list of the AnimData blocks being shown in the NLA */ filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_SEL | - ANIMFILTER_NODUPLIS); + ANIMFILTER_NODUPLIS | ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* delete tracks */ diff --git a/source/blender/editors/space_nla/nla_draw.c b/source/blender/editors/space_nla/nla_draw.c index 798a65efdcc..3b108a3ba8a 100644 --- a/source/blender/editors/space_nla/nla_draw.c +++ b/source/blender/editors/space_nla/nla_draw.c @@ -792,7 +792,8 @@ void draw_nla_main_data(bAnimContext *ac, SpaceNla *snla, ARegion *region) /* build list of channels to draw */ ListBase anim_data = {NULL, NULL}; - int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS); + int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS | + ANIMFILTER_FCURVESONLY); size_t items = ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* Update max-extent of channels here (taking into account scrollers): @@ -902,7 +903,8 @@ void draw_nla_channel_list(const bContext *C, bAnimContext *ac, ARegion *region) size_t items; /* build list of channels to draw */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS | + ANIMFILTER_FCURVESONLY); items = ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* Update max-extent of channels here (taking into account scrollers): diff --git a/source/blender/editors/space_nla/nla_edit.c b/source/blender/editors/space_nla/nla_edit.c index 50f00aed867..cc45c86ddcd 100644 --- a/source/blender/editors/space_nla/nla_edit.c +++ b/source/blender/editors/space_nla/nla_edit.c @@ -60,7 +60,8 @@ void ED_nla_postop_refresh(bAnimContext *ac) { ListBase anim_data = {NULL, NULL}; bAnimListElem *ale; - short filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_ANIMDATA | ANIMFILTER_FOREDIT); + short filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_ANIMDATA | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); /* get blocks to work on */ ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); @@ -107,7 +108,7 @@ static int nlaedit_enable_tweakmode_exec(bContext *C, wmOperator *op) } /* get a list of the AnimData blocks being shown in the NLA */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_ANIMDATA); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_ANIMDATA | ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* if no blocks, popup error? */ @@ -211,7 +212,7 @@ bool nlaedit_disable_tweakmode(bAnimContext *ac, bool do_solo) int filter; /* get a list of the AnimData blocks being shown in the NLA */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_ANIMDATA); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_ANIMDATA | ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* if no blocks, popup error? */ @@ -318,7 +319,8 @@ static void get_nlastrip_extents(bAnimContext *ac, float *min, float *max, const bool found_bounds = false; /* get data to filter */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* set large values to try to override */ @@ -436,7 +438,8 @@ static bool nla_channels_get_selected_extents(bAnimContext *ac, float *r_min, fl short found = 0; /* get all items - we need to do it this way */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* loop through all channels, finding the first one that's selected */ @@ -654,7 +657,8 @@ static int nlaedit_add_actionclip_exec(bContext *C, wmOperator *op) /* get a list of the editable tracks being shown in the NLA * - this is limited to active ones for now, but could be expanded to */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_ACTIVE | ANIMFILTER_FOREDIT); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_ACTIVE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); items = ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); if (items == 0) { @@ -771,7 +775,8 @@ static int nlaedit_add_transition_exec(bContext *C, wmOperator *op) } /* get a list of the editable tracks being shown in the NLA */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* for each track, find pairs of strips to add transitions to */ @@ -905,7 +910,7 @@ static int nlaedit_add_sound_exec(bContext *C, wmOperator *UNUSED(op)) /* get a list of the editable tracks being shown in the NLA */ filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_SEL | - ANIMFILTER_FOREDIT); + ANIMFILTER_FOREDIT | ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* for each track, add sound clips if it belongs to a speaker */ @@ -994,7 +999,8 @@ static int nlaedit_add_meta_exec(bContext *C, wmOperator *UNUSED(op)) } /* get a list of the editable tracks being shown in the NLA */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* for each track, find pairs of strips to add transitions to */ @@ -1070,7 +1076,8 @@ static int nlaedit_remove_meta_exec(bContext *C, wmOperator *UNUSED(op)) } /* get a list of the editable tracks being shown in the NLA */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* for each track, find pairs of strips to add transitions to */ @@ -1140,7 +1147,8 @@ static int nlaedit_duplicate_exec(bContext *C, wmOperator *op) } /* get a list of editable tracks being shown in the NLA */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* duplicate strips in tracks starting from the last one so that we're @@ -1267,7 +1275,8 @@ static int nlaedit_delete_exec(bContext *C, wmOperator *UNUSED(op)) } /* get a list of the editable tracks being shown in the NLA */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* for each NLA-Track, delete all selected strips */ @@ -1430,7 +1439,8 @@ static int nlaedit_split_exec(bContext *C, wmOperator *UNUSED(op)) } /* get a list of editable tracks being shown in the NLA */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* for each NLA-Track, split all selected strips into two strips */ @@ -1518,7 +1528,8 @@ static int nlaedit_toggle_mute_exec(bContext *C, wmOperator *UNUSED(op)) } /* get a list of the editable tracks being shown in the NLA */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* go over all selected strips */ @@ -1587,7 +1598,8 @@ static int nlaedit_swap_exec(bContext *C, wmOperator *op) } /* get a list of the editable tracks being shown in the NLA */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* consider each track in turn */ @@ -1769,7 +1781,8 @@ static int nlaedit_move_up_exec(bContext *C, wmOperator *UNUSED(op)) } /* get a list of the editable tracks being shown in the NLA */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* since we're potentially moving strips from lower tracks to higher tracks, we should @@ -1860,7 +1873,8 @@ static int nlaedit_move_down_exec(bContext *C, wmOperator *UNUSED(op)) } /* get a list of the editable tracks being shown in the NLA */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* loop through the tracks in normal order, since we're pushing strips down, @@ -1952,7 +1966,8 @@ static int nlaedit_sync_actlen_exec(bContext *C, wmOperator *op) } /* get a list of the editable tracks being shown in the NLA */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); if (active_only) { filter |= ANIMFILTER_ACTIVE; } @@ -2047,7 +2062,8 @@ static int nlaedit_make_single_user_exec(bContext *C, wmOperator *UNUSED(op)) } /* get a list of the editable tracks being shown in the NLA */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* Ensure that each action used only has a single user @@ -2155,7 +2171,8 @@ static int nlaedit_apply_scale_exec(bContext *C, wmOperator *UNUSED(op)) } /* get a list of the editable tracks being shown in the NLA */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* for each NLA-Track, apply scale of all selected strips */ @@ -2265,7 +2282,8 @@ static int nlaedit_clear_scale_exec(bContext *C, wmOperator *UNUSED(op)) } /* get a list of the editable tracks being shown in the NLA */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* for each NLA-Track, reset scale of all selected strips */ @@ -2350,7 +2368,8 @@ static int nlaedit_snap_exec(bContext *C, wmOperator *op) } /* get a list of the editable tracks being shown in the NLA */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* get some necessary vars */ @@ -2544,7 +2563,8 @@ static int nla_fmodifier_add_exec(bContext *C, wmOperator *op) } /* get a list of the editable tracks being shown in the NLA */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* for each NLA-Track, add the specified modifier to all selected strips */ @@ -2655,7 +2675,8 @@ static int nla_fmodifier_copy_exec(bContext *C, wmOperator *op) ANIM_fmodifiers_copybuf_free(); /* get a list of the editable tracks being shown in the NLA */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* for each NLA-Track, add the specified modifier to all selected strips */ @@ -2734,7 +2755,7 @@ static int nla_fmodifier_paste_exec(bContext *C, wmOperator *op) /* get a list of the editable tracks being shown in the NLA */ filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | - ANIMFILTER_NODUPLIS); + ANIMFILTER_NODUPLIS | ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* for each NLA-Track, add the specified modifier to all selected strips */ diff --git a/source/blender/editors/space_nla/nla_select.c b/source/blender/editors/space_nla/nla_select.c index d51bfce6355..a816f8fa4f6 100644 --- a/source/blender/editors/space_nla/nla_select.c +++ b/source/blender/editors/space_nla/nla_select.c @@ -85,7 +85,7 @@ static void deselect_nla_strips(bAnimContext *ac, short test, short sel) /* determine type-based settings */ /* FIXME: double check whether ANIMFILTER_LIST_VISIBLE is needed! */ - filter = (ANIMFILTER_DATA_VISIBLE); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FCURVESONLY); /* filter data */ ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); @@ -223,7 +223,8 @@ static void box_select_nla_strips(bAnimContext *ac, rcti rect, short mode, short UI_view2d_region_to_view(v2d, rect.xmax, rect.ymax - 2, &rectf.xmax, &rectf.ymax); /* filter data */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* convert selection modes to selection modes */ @@ -278,7 +279,8 @@ static void nlaedit_strip_at_region_position( 0, NLACHANNEL_STEP(snla), 0, NLACHANNEL_FIRST_TOP(ac), view_x, view_y, NULL, &channel_index); ListBase anim_data = {NULL, NULL}; - int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS); + int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* x-range to check is +/- 7 (in screen/region-space) on either side of mouse click @@ -465,7 +467,7 @@ static void nlaedit_select_leftright(bContext *C, select_mode = selmodes_to_flagmodes(select_mode); /* filter data */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* select strips on the side where most data occurs */ diff --git a/source/blender/editors/transform/transform_convert_nla.c b/source/blender/editors/transform/transform_convert_nla.c index b2c0cc553a7..32f70fc010b 100644 --- a/source/blender/editors/transform/transform_convert_nla.c +++ b/source/blender/editors/transform/transform_convert_nla.c @@ -82,7 +82,8 @@ void createTransNlaData(bContext *C, TransInfo *t) snla = (SpaceNla *)ac.sl; /* filter data */ - filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT); + filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_FOREDIT | + ANIMFILTER_FCURVESONLY); ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* which side of the current frame should be allowed */ @@ -475,7 +476,7 @@ void special_aftertrans_update__nla(bContext *C, TransInfo *UNUSED(t)) if (ac.datatype) { ListBase anim_data = {NULL, NULL}; bAnimListElem *ale; - short filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT); + short filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_FCURVESONLY); /* get channels to work on */ ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); -- cgit v1.2.3 From f0ac55f51965c2abb811b2759241225762a06c94 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 7 Jul 2022 08:35:22 -0500 Subject: Fix: Spreadsheet does not display original curves data The spreadsheet ignored the component choice in the data set region for curves and volume objects, and the original curves data-block wasn't retrieved from the original object. --- .../spreadsheet_data_source_geometry.cc | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc index f5315b616d0..6955f23e1c2 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc @@ -398,6 +398,11 @@ GeometrySet spreadsheet_get_display_geometry_set(const SpaceSpreadsheet *sspread geometry_set.get_component_for_write(); pointcloud_component.replace(pointcloud, GeometryOwnershipType::ReadOnly); } + else if (object_orig->type == OB_CURVES) { + const Curves &curves_id = *(const Curves *)object_orig->data; + CurveComponent &curve_component = geometry_set.get_component_for_write(); + curve_component.replace(&const_cast(curves_id), GeometryOwnershipType::ReadOnly); + } } else { if (object_eval->mode == OB_MODE_EDIT && object_eval->type == OB_MESH) { @@ -472,18 +477,6 @@ static void find_fields_to_evaluate(const SpaceSpreadsheet *sspreadsheet, } } -static GeometryComponentType get_display_component_type(const bContext *C, Object *object_eval) -{ - SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); - if (sspreadsheet->object_eval_state != SPREADSHEET_OBJECT_EVAL_STATE_ORIGINAL) { - return (GeometryComponentType)sspreadsheet->geometry_component_type; - } - if (object_eval->type == OB_POINTCLOUD) { - return GEO_COMPONENT_TYPE_POINT_CLOUD; - } - return GEO_COMPONENT_TYPE_MESH; -} - class GeometryComponentCacheKey : public SpreadsheetCache::Key { public: /* Use the pointer to the geometry component as a key to detect when the geometry changed. */ @@ -551,7 +544,8 @@ std::unique_ptr data_source_from_geometry(const bContext *C, Object { SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); const eAttrDomain domain = (eAttrDomain)sspreadsheet->attribute_domain; - const GeometryComponentType component_type = get_display_component_type(C, object_eval); + const GeometryComponentType component_type = GeometryComponentType( + sspreadsheet->geometry_component_type); GeometrySet geometry_set = spreadsheet_get_display_geometry_set(sspreadsheet, object_eval); if (!geometry_set.has(component_type)) { -- cgit v1.2.3 From a91f9c2c01401285208f2962f3be339a5012ec2a Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 7 Jul 2022 08:49:23 -0500 Subject: Cleanup: Use generic index mask utility This may lead to improved performance from multithreading as well. --- .../spreadsheet_data_source_geometry.cc | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc index 6955f23e1c2..1b01dd64c74 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "BLI_index_mask_ops.hh" #include "BLI_virtual_array.hh" #include "BKE_context.h" @@ -235,20 +236,10 @@ bool GeometryDataSource::has_selection_filter() const return true; } -static IndexMask index_mask_from_bool_array(const VArray &selection, - Vector &indices) -{ - for (const int i : selection.index_range()) { - if (selection[i]) { - indices.append(i); - } - } - return IndexMask(indices); -} - IndexMask GeometryDataSource::apply_selection_filter(Vector &indices) const { std::lock_guard lock{mutex_}; + const IndexMask full_range(this->tot_rows()); BLI_assert(object_eval_->mode == OB_MODE_EDIT); BLI_assert(component_->type() == GEO_COMPONENT_TYPE_MESH); @@ -277,7 +268,7 @@ IndexMask GeometryDataSource::apply_selection_filter(Vector &indices) c }), ATTR_DOMAIN_POINT, domain_); - return index_mask_from_bool_array(selection, indices); + return index_mask_ops::find_indices_from_virtual_array(full_range, selection, 1024, indices); } if (mesh_eval->totvert == bm->totvert) { @@ -290,10 +281,10 @@ IndexMask GeometryDataSource::apply_selection_filter(Vector &indices) c }), ATTR_DOMAIN_POINT, domain_); - return index_mask_from_bool_array(selection, indices); + return index_mask_ops::find_indices_from_virtual_array(full_range, selection, 2048, indices); } - return IndexMask(mesh_eval->totvert); + return full_range; } void VolumeDataSource::foreach_default_column_ids( -- cgit v1.2.3 From 85ef8e194555240d518a7a7e7f06fcc4c47fa8c7 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 7 Jul 2022 08:55:36 -0500 Subject: Cleanup: Use C++ style of avoiding unused variable warnings As documented in the best practices section of the style guide: https://wiki.blender.org/wiki/Style_Guide/Best_Practice_C_Cpp --- source/blender/editors/space_spreadsheet/spreadsheet_data_source.hh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source.hh b/source/blender/editors/space_spreadsheet/spreadsheet_data_source.hh index 76c7131e268..6a09d3f84c6 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source.hh @@ -27,9 +27,8 @@ class DataSource { * column. (This can be made a bit more generic in the future when necessary.) */ virtual void foreach_default_column_ids( - FunctionRef fn) const + FunctionRef /*fn*/) const { - UNUSED_VARS(fn); } /** @@ -37,9 +36,8 @@ class DataSource { * returned. */ virtual std::unique_ptr get_column_values( - const SpreadsheetColumnID &column_id) const + const SpreadsheetColumnID & /*column_id*/) const { - UNUSED_VARS(column_id); return {}; } -- cgit v1.2.3 From 59e1009f1094b09d7ce961e574ba4acc33f9df97 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 7 Jul 2022 09:01:46 -0500 Subject: Cleanup: Use std::move for geometry set The only real improvement is avoiding some reference counting, but the main for the change is consistency. Also don't move a StringRef, since that doesn't own any data anyway. --- .../space_spreadsheet/spreadsheet_data_source_geometry.cc | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc index 1b01dd64c74..6be3a65cc1b 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc @@ -513,7 +513,7 @@ static void add_fields_as_extra_columns(SpaceSpreadsheet *sspreadsheet, const eAttrDomain domain = (eAttrDomain)sspreadsheet->attribute_domain; const int domain_num = component.attribute_domain_num(domain); for (const auto item : fields_to_show.items()) { - StringRef name = item.key; + const StringRef name = item.key; const GField &field = item.value; /* Use the cached evaluated array if it exists, otherwise evaluate the field now. */ @@ -527,7 +527,7 @@ static void add_fields_as_extra_columns(SpaceSpreadsheet *sspreadsheet, return evaluated_array; }); - r_extra_columns.add(std::move(name), evaluated_array.as_span()); + r_extra_columns.add(name, evaluated_array.as_span()); } } @@ -538,7 +538,6 @@ std::unique_ptr data_source_from_geometry(const bContext *C, Object const GeometryComponentType component_type = GeometryComponentType( sspreadsheet->geometry_component_type); GeometrySet geometry_set = spreadsheet_get_display_geometry_set(sspreadsheet, object_eval); - if (!geometry_set.has(component_type)) { return {}; } @@ -548,10 +547,10 @@ std::unique_ptr data_source_from_geometry(const bContext *C, Object add_fields_as_extra_columns(sspreadsheet, component, extra_columns); if (component_type == GEO_COMPONENT_TYPE_VOLUME) { - return std::make_unique(geometry_set); + return std::make_unique(std::move(geometry_set)); } return std::make_unique( - object_eval, geometry_set, component_type, domain, std::move(extra_columns)); + object_eval, std::move(geometry_set), component_type, domain, std::move(extra_columns)); } } // namespace blender::ed::spreadsheet -- cgit v1.2.3 From c76e1ecac6d6611269f15b5fd229ca726bde0337 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Thu, 7 Jul 2022 16:13:26 +0200 Subject: Compositor: Pre-fill motion tracking fields Extends current functionality which was only filling in the active movie clip. Now we also pre-fill tracking object name, as well as (plane)track name. --- .../composite/nodes/node_composite_keyingscreen.cc | 13 ++++++++++-- .../nodes/node_composite_planetrackdeform.cc | 24 ++++++++++++++++++++-- .../composite/nodes/node_composite_trackpos.cc | 20 ++++++++++++++++-- 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/source/blender/nodes/composite/nodes/node_composite_keyingscreen.cc b/source/blender/nodes/composite/nodes/node_composite_keyingscreen.cc index e4e37f630a2..e835ee9e721 100644 --- a/source/blender/nodes/composite/nodes/node_composite_keyingscreen.cc +++ b/source/blender/nodes/composite/nodes/node_composite_keyingscreen.cc @@ -6,12 +6,14 @@ */ #include "DNA_movieclip_types.h" +#include "DNA_tracking_types.h" #include "BLI_math_base.h" #include "BLI_math_color.h" #include "BKE_context.h" #include "BKE_lib_id.h" +#include "BKE_tracking.h" #include "RNA_access.h" #include "RNA_prototypes.h" @@ -38,8 +40,15 @@ static void node_composit_init_keyingscreen(const bContext *C, PointerRNA *ptr) node->storage = data; const Scene *scene = CTX_data_scene(C); - node->id = (ID *)scene->clip; - id_us_plus(node->id); + if (scene->clip) { + MovieClip *clip = scene->clip; + + node->id = &clip->id; + id_us_plus(&clip->id); + + const MovieTrackingObject *tracking_object = BKE_tracking_object_get_active(&clip->tracking); + BLI_strncpy(data->tracking_object, tracking_object->name, sizeof(data->tracking_object)); + } } static void node_composit_buts_keyingscreen(uiLayout *layout, bContext *C, PointerRNA *ptr) diff --git a/source/blender/nodes/composite/nodes/node_composite_planetrackdeform.cc b/source/blender/nodes/composite/nodes/node_composite_planetrackdeform.cc index 8055e350d51..472bf2344ca 100644 --- a/source/blender/nodes/composite/nodes/node_composite_planetrackdeform.cc +++ b/source/blender/nodes/composite/nodes/node_composite_planetrackdeform.cc @@ -5,6 +5,11 @@ * \ingroup cmpnodes */ +#include "DNA_movieclip_types.h" +#include "DNA_tracking_types.h" + +#include "BKE_tracking.h" + #include "RNA_access.h" #include "RNA_prototypes.h" @@ -32,8 +37,23 @@ static void init(const bContext *C, PointerRNA *ptr) node->storage = data; const Scene *scene = CTX_data_scene(C); - node->id = (ID *)scene->clip; - id_us_plus(node->id); + if (scene->clip) { + MovieClip *clip = scene->clip; + MovieTracking *tracking = &clip->tracking; + + node->id = &clip->id; + id_us_plus(&clip->id); + + const MovieTrackingObject *tracking_object = BKE_tracking_object_get_active(tracking); + BLI_strncpy(data->tracking_object, tracking_object->name, sizeof(data->tracking_object)); + + const MovieTrackingPlaneTrack *active_plane_track = BKE_tracking_plane_track_get_active( + tracking); + if (active_plane_track) { + BLI_strncpy( + data->plane_track_name, active_plane_track->name, sizeof(data->plane_track_name)); + } + } } static void node_composit_buts_planetrackdeform(uiLayout *layout, bContext *C, PointerRNA *ptr) diff --git a/source/blender/nodes/composite/nodes/node_composite_trackpos.cc b/source/blender/nodes/composite/nodes/node_composite_trackpos.cc index 723b82998ee..0e99ff59327 100644 --- a/source/blender/nodes/composite/nodes/node_composite_trackpos.cc +++ b/source/blender/nodes/composite/nodes/node_composite_trackpos.cc @@ -5,6 +5,9 @@ * \ingroup cmpnodes */ +#include "DNA_movieclip_types.h" +#include "DNA_tracking_types.h" + #include "BKE_context.h" #include "BKE_lib_id.h" #include "BKE_tracking.h" @@ -34,8 +37,21 @@ static void init(const bContext *C, PointerRNA *ptr) node->storage = data; const Scene *scene = CTX_data_scene(C); - node->id = (ID *)scene->clip; - id_us_plus(node->id); + if (scene->clip) { + MovieClip *clip = scene->clip; + MovieTracking *tracking = &clip->tracking; + + node->id = &clip->id; + id_us_plus(&clip->id); + + const MovieTrackingObject *tracking_object = BKE_tracking_object_get_active(tracking); + BLI_strncpy(data->tracking_object, tracking_object->name, sizeof(data->tracking_object)); + + const MovieTrackingTrack *active_track = BKE_tracking_track_get_active(tracking); + if (active_track) { + BLI_strncpy(data->track_name, active_track->name, sizeof(data->track_name)); + } + } } static void node_composit_buts_trackpos(uiLayout *layout, bContext *C, PointerRNA *ptr) -- cgit v1.2.3 From fcf1a9ff71e20e4af56815c4db1816d786298c3f Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Thu, 7 Jul 2022 16:20:27 +0200 Subject: Fix T99256: Regression: Meta balls segfaulting copy-to-selected. Revealed by rB43167a2c251b, but actuall issue is the `rna_MetaBall_update_data` function expecting a never-NULL `scene` pointer, which is not guaranteed. This lead to refactoring the duo `rna_MetaBall_update_data`/`BKE_mball_properties_copy`, since it was doing a very sub-optimal O(n^2) process, new code is O(2n) now (n being the number of Objects). Not sure why the objects were processed through the existing bases of the existing scene in the first place, this could miss a lot of affected objects (e.g. in case said objects are in an excluded collection, etc.). Also noticed that both old and new implementation can fail to fully propagate changes to all affected meta-balls in some specific corner cases, added a comment about it in the code. Reviewed By: sergey Maniphest Tasks: T99256 Differential Revision: https://developer.blender.org/D15338 --- source/blender/blenkernel/BKE_mball.h | 22 ++++-- source/blender/blenkernel/intern/mball.c | 118 +++++++++++++++++++--------- source/blender/makesrna/intern/rna_access.c | 1 + source/blender/makesrna/intern/rna_meta.c | 13 ++- 4 files changed, 104 insertions(+), 50 deletions(-) diff --git a/source/blender/blenkernel/BKE_mball.h b/source/blender/blenkernel/BKE_mball.h index f40d0bb3004..5a4988c7a5f 100644 --- a/source/blender/blenkernel/BKE_mball.h +++ b/source/blender/blenkernel/BKE_mball.h @@ -24,14 +24,25 @@ struct MetaBall *BKE_mball_add(struct Main *bmain, const char *name); bool BKE_mball_is_any_selected(const struct MetaBall *mb); bool BKE_mball_is_any_selected_multi(struct Base **bases, int bases_len); bool BKE_mball_is_any_unselected(const struct MetaBall *mb); -bool BKE_mball_is_basis_for(struct Object *ob1, struct Object *ob2); + +/** + * Return `true` if `ob1` and `ob2` are part of the same metaBall group. + * + * \note Currently checks whether their two base names (without numerical suffix) is the same. + */ +bool BKE_mball_is_same_group(const struct Object *ob1, const struct Object *ob2); +/** + * Return `true` if `ob1` and `ob2` are part of the same metaBall group, and `ob1` is its + * basis. + */ +bool BKE_mball_is_basis_for(const struct Object *ob1, const struct Object *ob2); /** * Test, if \a ob is a basis meta-ball. * * It test last character of Object ID name. * If last character is digit it return 0, else it return 1. */ -bool BKE_mball_is_basis(struct Object *ob); +bool BKE_mball_is_basis(const struct Object *ob); /** * This function finds the basis meta-ball. * @@ -58,13 +69,14 @@ struct BoundBox *BKE_mball_boundbox_get(struct Object *ob); float *BKE_mball_make_orco(struct Object *ob, struct ListBase *dispbase); /** - * Copy some properties from object to other meta-ball object with same base name. + * Copy some properties from a meta-ball obdata to all other meta-ball obdata belonging to the same + * family (i.e. object sharing the same name basis). * * When some properties (wire-size, threshold, update flags) of meta-ball are changed, then this * properties are copied to all meta-balls in same "group" (meta-balls with same base name: * `MBall`, `MBall.001`, `MBall.002`, etc). The most important is to copy properties to the base - * meta-ball, because this meta-ball influence polygonization of meta-balls. */ -void BKE_mball_properties_copy(struct Scene *scene, struct Object *active_object); + * meta-ball, because this meta-ball influences polygonization of meta-balls. */ +void BKE_mball_properties_copy(struct Main *bmain, struct MetaBall *active_metaball); bool BKE_mball_minmax_ex( const struct MetaBall *mb, float min[3], float max[3], const float obmat[4][4], short flag); diff --git a/source/blender/blenkernel/intern/mball.c b/source/blender/blenkernel/intern/mball.c index 1340e53f06e..819bbde6556 100644 --- a/source/blender/blenkernel/intern/mball.c +++ b/source/blender/blenkernel/intern/mball.c @@ -347,7 +347,7 @@ float *BKE_mball_make_orco(Object *ob, ListBase *dispbase) return orcodata; } -bool BKE_mball_is_basis(Object *ob) +bool BKE_mball_is_basis(const Object *ob) { /* Meta-Ball Basis Notes from Blender-2.5x * ======================================= @@ -370,7 +370,7 @@ bool BKE_mball_is_basis(Object *ob) return (!isdigit(ob->id.name[len - 1])); } -bool BKE_mball_is_basis_for(Object *ob1, Object *ob2) +bool BKE_mball_is_same_group(const Object *ob1, const Object *ob2) { int basis1nr, basis2nr; char basis1name[MAX_ID_NAME], basis2name[MAX_ID_NAME]; @@ -383,11 +383,12 @@ bool BKE_mball_is_basis_for(Object *ob1, Object *ob2) BLI_split_name_num(basis1name, &basis1nr, ob1->id.name + 2, '.'); BLI_split_name_num(basis2name, &basis2nr, ob2->id.name + 2, '.'); - if (STREQ(basis1name, basis2name)) { - return BKE_mball_is_basis(ob1); - } + return STREQ(basis1name, basis2name); +} - return false; +bool BKE_mball_is_basis_for(const Object *ob1, const Object *ob2) +{ + return BKE_mball_is_same_group(ob1, ob2) && BKE_mball_is_basis(ob1); } bool BKE_mball_is_any_selected(const MetaBall *mb) @@ -422,41 +423,82 @@ bool BKE_mball_is_any_unselected(const MetaBall *mb) return false; } -void BKE_mball_properties_copy(Scene *scene, Object *active_object) +static void mball_data_properties_copy(MetaBall *mb_dst, MetaBall *mb_src) { - Scene *sce_iter = scene; - Base *base; - Object *ob; - MetaBall *active_mball = (MetaBall *)active_object->data; - int basisnr, obnr; - char basisname[MAX_ID_NAME], obname[MAX_ID_NAME]; - SceneBaseIter iter; - - BLI_split_name_num(basisname, &basisnr, active_object->id.name + 2, '.'); - - /* Pass depsgraph as NULL, which means we will not expand into - * duplis unlike when we generate the meta-ball. Expanding duplis - * would not be compatible when editing multiple view layers. */ - BKE_scene_base_iter_next(NULL, &iter, &sce_iter, 0, NULL, NULL); - while (BKE_scene_base_iter_next(NULL, &iter, &sce_iter, 1, &base, &ob)) { - if (ob->type == OB_MBALL) { - if (ob != active_object) { - BLI_split_name_num(obname, &obnr, ob->id.name + 2, '.'); - - /* Object ob has to be in same "group" ... it means, that it has to have - * same base of its name */ - if (STREQ(obname, basisname)) { - MetaBall *mb = ob->data; - - /* Copy properties from selected/edited metaball */ - mb->wiresize = active_mball->wiresize; - mb->rendersize = active_mball->rendersize; - mb->thresh = active_mball->thresh; - mb->flag = active_mball->flag; - DEG_id_tag_update(&mb->id, 0); - } + mb_dst->wiresize = mb_src->wiresize; + mb_dst->rendersize = mb_src->rendersize; + mb_dst->thresh = mb_src->thresh; + mb_dst->flag = mb_src->flag; + DEG_id_tag_update(&mb_dst->id, 0); +} + +void BKE_mball_properties_copy(Main *bmain, MetaBall *metaball_src) +{ + /* WARNING: This code does not cover all potential corner-cases. E.g. if: + * | Object | ObData | + * | ---------- | ---------- | + * | Meta_A | Meta_A | + * | Meta_A.001 | Meta_A.001 | + * | Meta_B | Meta_A | + * | Meta_B.001 | Meta_B.001 | + * + * Calling this function with `metaball_src` being `Meta_A.001` will update `Meta_A`, but NOT + * `Meta_B.001`. So in the 'Meta_B' family, the two metaballs will have unmatching settings now. + * + * Solving this case would drastically increase the complexity of this code though, so don't + * think it would be worth it. + */ + for (Object *ob_src = bmain->objects.first; ob_src != NULL && !ID_IS_LINKED(ob_src);) { + if (ob_src->data != metaball_src) { + ob_src = ob_src->id.next; + continue; + } + + /* In this code we take advantage of two facts: + * - MetaBalls of the same family have the same basis name, + * - IDs are sorted by name in their Main listbase. + * So, all MetaBall objects of the same family are contiguous in bmain list (potentially mixed + * with non-meta-ball objects with same basis names). + * + * Using this, it is possible to process the whole set of meta-balls with a single loop on the + * whole list of Objects, though additionally going backward on part of the list in some cases. + */ + Object *ob_iter = NULL; + int obactive_nr, ob_nr; + char obactive_name[MAX_ID_NAME], ob_name[MAX_ID_NAME]; + BLI_split_name_num(obactive_name, &obactive_nr, ob_src->id.name + 2, '.'); + + for (ob_iter = ob_src->id.prev; ob_iter != NULL; ob_iter = ob_iter->id.prev) { + if (ob_iter->id.name[2] != obactive_name[0]) { + break; + } + if (ob_iter->type != OB_MBALL || ob_iter->data == metaball_src) { + continue; + } + BLI_split_name_num(ob_name, &ob_nr, ob_iter->id.name + 2, '.'); + if (!STREQ(obactive_name, ob_name)) { + break; + } + + mball_data_properties_copy(ob_iter->data, metaball_src); + } + + for (ob_iter = ob_src->id.next; ob_iter != NULL; ob_iter = ob_iter->id.next) { + if (ob_iter->id.name[2] != obactive_name[0] || ID_IS_LINKED(ob_iter)) { + break; + } + if (ob_iter->type != OB_MBALL || ob_iter->data == metaball_src) { + continue; } + BLI_split_name_num(ob_name, &ob_nr, ob_iter->id.name + 2, '.'); + if (!STREQ(obactive_name, ob_name)) { + break; + } + + mball_data_properties_copy(ob_iter->data, metaball_src); } + + ob_src = ob_iter; } } diff --git a/source/blender/makesrna/intern/rna_access.c b/source/blender/makesrna/intern/rna_access.c index a0b25cf60b2..cf4182243b9 100644 --- a/source/blender/makesrna/intern/rna_access.c +++ b/source/blender/makesrna/intern/rna_access.c @@ -2141,6 +2141,7 @@ void RNA_property_update(bContext *C, PointerRNA *ptr, PropertyRNA *prop) void RNA_property_update_main(Main *bmain, Scene *scene, PointerRNA *ptr, PropertyRNA *prop) { + BLI_assert(bmain != NULL); rna_property_update(NULL, bmain, scene, ptr, prop); } diff --git a/source/blender/makesrna/intern/rna_meta.c b/source/blender/makesrna/intern/rna_meta.c index e6cf743e167..898208e0ba1 100644 --- a/source/blender/makesrna/intern/rna_meta.c +++ b/source/blender/makesrna/intern/rna_meta.c @@ -81,18 +81,17 @@ static void rna_MetaBall_redraw_data(Main *UNUSED(bmain), Scene *UNUSED(scene), WM_main_add_notifier(NC_GEOM | ND_DATA, id); } -static void rna_MetaBall_update_data(Main *bmain, Scene *scene, PointerRNA *ptr) +static void rna_MetaBall_update_data(Main *bmain, Scene *UNUSED(scene), PointerRNA *ptr) { MetaBall *mb = (MetaBall *)ptr->owner_id; Object *ob; - /* cheating way for importers to avoid slow updates */ + /* NOTE: The check on the number of users allows to avoid many repetitive (slow) updates in some + * cases, like e.g. importers. Calling `BKE_mball_properties_copy` on an obdata with no users + * would be meaningless anyway, as by definition it would not be used by any object, so not part + * of any meta-ball group. */ if (mb->id.us > 0) { - for (ob = bmain->objects.first; ob; ob = ob->id.next) { - if (ob->data == mb) { - BKE_mball_properties_copy(scene, ob); - } - } + BKE_mball_properties_copy(bmain, mb); DEG_id_tag_update(&mb->id, 0); WM_main_add_notifier(NC_GEOM | ND_DATA, mb); -- cgit v1.2.3 From f9a805164a944c3b5762f07e535536b9f425651f Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Thu, 7 Jul 2022 17:17:33 +0200 Subject: Outliner, Library Overrides: List child objects under parents Because children point to, or "use" their parent, the Library Overrides Hierarchies mode in the Outliner would show parents contained in children, not children contained in a parent. See D15339 for pictures. In production files this would make the rig listed under all its children, so it would appear many times in the tree. Now it appears once and the children are collected under it. Refactors the tree building, so instead of using `BKE_library_foreach_ID_link()`, it now uses the ID relations mapping in `MainIDRelations`. Reviewed By: mont29 Differential Revision: https://developer.blender.org/D15339 --- .../editors/space_outliner/tree/tree_display.hh | 1 - .../tree_display_override_library_hierarchies.cc | 264 ++++++++++++++------- 2 files changed, 183 insertions(+), 82 deletions(-) diff --git a/source/blender/editors/space_outliner/tree/tree_display.hh b/source/blender/editors/space_outliner/tree/tree_display.hh index 190e35c81d6..f8e35655c26 100644 --- a/source/blender/editors/space_outliner/tree/tree_display.hh +++ b/source/blender/editors/space_outliner/tree/tree_display.hh @@ -161,7 +161,6 @@ class TreeDisplayOverrideLibraryHierarchies final : public AbstractTreeDisplay { ListBase build_hierarchy_for_lib_or_main(Main *bmain, TreeElement &parent_te, Library *lib = nullptr); - void build_hierarchy_for_ID(Main *bmain, ID &override_root_id, TreeElementID &te_id) const; }; /* -------------------------------------------------------------------- */ diff --git a/source/blender/editors/space_outliner/tree/tree_display_override_library_hierarchies.cc b/source/blender/editors/space_outliner/tree/tree_display_override_library_hierarchies.cc index 67798e978ab..f8705c3f0ad 100644 --- a/source/blender/editors/space_outliner/tree/tree_display_override_library_hierarchies.cc +++ b/source/blender/editors/space_outliner/tree/tree_display_override_library_hierarchies.cc @@ -4,25 +4,23 @@ * \ingroup spoutliner */ -#include "DNA_ID.h" -#include "DNA_collection_types.h" #include "DNA_key_types.h" #include "DNA_space_types.h" -#include "BLI_listbase.h" +#include "BLI_function_ref.hh" +#include "BLI_ghash.h" #include "BLI_map.hh" + #include "BLI_set.hh" #include "BLT_translation.h" -#include "BKE_collection.h" #include "BKE_lib_query.h" #include "BKE_main.h" #include "../outliner_intern.hh" #include "common.hh" #include "tree_display.hh" -#include "tree_element_id.hh" namespace blender::ed::outliner { @@ -42,8 +40,8 @@ ListBase TreeDisplayOverrideLibraryHierarchies::buildTree(const TreeSourceData & TreeElement *current_file_te = outliner_add_element( &space_outliner_, &tree, source_data.bmain, nullptr, TSE_ID_BASE, -1); current_file_te->name = IFACE_("Current File"); + AbstractTreeElement::uncollapse_by_default(current_file_te); { - AbstractTreeElement::uncollapse_by_default(current_file_te); build_hierarchy_for_lib_or_main(source_data.bmain, *current_file_te); /* Add dummy child if there's nothing to display. */ @@ -76,11 +74,49 @@ ListBase TreeDisplayOverrideLibraryHierarchies::buildTree(const TreeSourceData & return tree; } +/* -------------------------------------------------------------------- */ +/** \name Library override hierarchy building + * \{ */ + +class OverrideIDHierarchyBuilder { + SpaceOutliner &space_outliner_; + MainIDRelations &id_relations_; + + struct HierarchyBuildData { + const ID &override_root_id_; + /* The ancestor IDs leading to the current ID, to avoid IDs recursing into themselves. Changes + * with every level of recursion. */ + Set parent_ids{}; + /* The IDs that were already added to #parent_te, to avoid duplicates. Entirely new set with + * every level of recursion. */ + Set sibling_ids{}; + }; + + public: + OverrideIDHierarchyBuilder(SpaceOutliner &space_outliner, MainIDRelations &id_relations) + : space_outliner_(space_outliner), id_relations_(id_relations) + { + } + + void build_hierarchy_for_ID(ID &root_id, TreeElement &te_to_expand); + + private: + void build_hierarchy_for_ID_recursive(const ID &parent_id, + HierarchyBuildData &build_data, + TreeElement &te_to_expand); +}; + ListBase TreeDisplayOverrideLibraryHierarchies::build_hierarchy_for_lib_or_main( Main *bmain, TreeElement &parent_te, Library *lib) { ListBase tree = {nullptr}; + /* Ensure #Main.relations contains the latest mapping of relations. Must be freed before + * returning. */ + BKE_main_relations_create(bmain, 0); + + OverrideIDHierarchyBuilder builder(space_outliner_, *bmain->relations); + /* Keep track over which ID base elements were already added, and expand them once added. */ Map id_base_te_map; /* Index for the ID base elements ("Objects", "Materials", etc). */ @@ -109,108 +145,174 @@ ListBase TreeDisplayOverrideLibraryHierarchies::build_hierarchy_for_lib_or_main( TreeElement *new_id_te = outliner_add_element( &space_outliner_, &new_base_te->subtree, iter_id, new_base_te, TSE_SOME_ID, 0, false); - build_hierarchy_for_ID(bmain, *iter_id, *tree_element_cast(new_id_te)); + builder.build_hierarchy_for_ID(*iter_id, *new_id_te); } FOREACH_MAIN_ID_END; + BKE_main_relations_free(bmain); + return tree; } -struct BuildHierarchyForeachIDCbData { - /* Don't allow copies, the sets below would need deep copying. */ - BuildHierarchyForeachIDCbData(const BuildHierarchyForeachIDCbData &) = delete; - - Main &bmain; - SpaceOutliner &space_outliner; - ID &override_root_id; - - /* The tree element to expand. Changes with every level of recursion. */ - TreeElementID *parent_te; - /* The ancestor IDs leading to the current ID, to avoid IDs recursing into themselves. Changes - * with every level of recursion. */ - Set parent_ids{}; - /* The IDs that were already added to #parent_te, to avoid duplicates. Entirely new set with - * every level of recursion. */ - Set sibling_ids{}; -}; +void OverrideIDHierarchyBuilder::build_hierarchy_for_ID(ID &override_root_id, + TreeElement &te_to_expand) +{ + HierarchyBuildData build_data{override_root_id}; + build_hierarchy_for_ID_recursive(override_root_id, build_data, te_to_expand); +} + +/* Helpers (defined below). */ +static void foreach_natural_hierarchy_child(const MainIDRelations &id_relations, + const ID &parent_id, + FunctionRef fn); +static bool id_is_in_override_hierarchy(const ID &id, + const ID &relationship_parent_id, + const ID &override_root_id); + +void OverrideIDHierarchyBuilder::build_hierarchy_for_ID_recursive(const ID &parent_id, + HierarchyBuildData &build_data, + TreeElement &te_to_expand) +{ + /* In case this isn't added to the parents yet (does nothing if already there). */ + build_data.parent_ids.add(&parent_id); + + foreach_natural_hierarchy_child(id_relations_, parent_id, [&](ID &id) { + if (!id_is_in_override_hierarchy(id, parent_id, build_data.override_root_id_)) { + return; + } + + /* Avoid endless recursion: If there is an ancestor for this ID already, it recurses into + * itself. */ + if (build_data.parent_ids.lookup_key_default(&id, nullptr)) { + return; + } + + /* Avoid duplicates: If there is a sibling for this ID already, the same ID is just used + * multiple times by the same parent. */ + if (build_data.sibling_ids.lookup_key_default(&id, nullptr)) { + return; + } + + TreeElement *new_te = outliner_add_element( + &space_outliner_, &te_to_expand.subtree, &id, &te_to_expand, TSE_SOME_ID, 0, false); + + build_data.sibling_ids.add(&id); + + /* Recurse into this ID. */ + HierarchyBuildData child_build_data{build_data.override_root_id_}; + child_build_data.parent_ids = build_data.parent_ids; + child_build_data.parent_ids.add(&id); + child_build_data.sibling_ids.reserve(10); + build_hierarchy_for_ID_recursive(id, child_build_data, *new_te); + }); +} -static int build_hierarchy_foreach_ID_cb(LibraryIDLinkCallbackData *cb_data) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Helpers for library override hierarchy building + * \{ */ + +/** + * Iterate over the IDs \a parent_id uses. E.g. the child collections and contained objects of a + * parent collection. Also does special handling for object parenting, so that: + * - When iterating over a child object, \a fn is executed for the parent instead. + * - When iterating over a parent object, \a fn is _additionally_ executed for all children. Given + * that the parent object isn't skipped, the caller has to ensure it's not added in the hierarchy + * twice. + * This allows us to build the hierarchy in the expected ("natural") order, where parent objects + * are actual parent elements in the hierarchy, even though in data, the relation goes the other + * way around (children point to or "use" the parent). + * + * Only handles regular object parenting, not cases like the "Child of" constraint. Other Outliner + * display modes don't show this as parent in the hierarchy either. + */ +static void foreach_natural_hierarchy_child(const MainIDRelations &id_relations, + const ID &parent_id, + FunctionRef fn) { - if (!*cb_data->id_pointer) { - return IDWALK_RET_NOP; + const MainIDRelationsEntry *relations_of_id = static_cast( + BLI_ghash_lookup(id_relations.relations_from_pointers, &parent_id)); + + /* Iterate over all IDs used by the parent ID (e.g. the child-collections of a collection). */ + for (MainIDRelationsEntryItem *to_id_entry = relations_of_id->to_ids; to_id_entry; + to_id_entry = to_id_entry->next) { + /* An ID pointed to (used) by the ID to recurse into. */ + ID &target_id = **to_id_entry->id_pointer.to; + + /* Don't walk up the hierarchy, e.g. ignore pointers to parent collections. */ + if (to_id_entry->usage_flag & IDWALK_CB_LOOPBACK) { + continue; + } + + /* Special case for objects: Process the parent object instead of the child object. Below the + * parent will add the child objects then. */ + if (GS(target_id.name) == ID_OB) { + const Object &potential_child_ob = reinterpret_cast(target_id); + if (potential_child_ob.parent) { + fn(potential_child_ob.parent->id); + continue; + } + } + + fn(target_id); + } + + /* If the ID is an object, find and iterate over any child objects. */ + if (GS(parent_id.name) == ID_OB) { + for (MainIDRelationsEntryItem *from_id_entry = relations_of_id->from_ids; from_id_entry; + from_id_entry = from_id_entry->next) { + ID &potential_child_id = *from_id_entry->id_pointer.from; + + if (GS(potential_child_id.name) != ID_OB) { + continue; + } + + Object &potential_child_ob = reinterpret_cast(potential_child_id); + if (potential_child_ob.parent && &potential_child_ob.parent->id == &parent_id) { + fn(potential_child_id); + } + } } +} - BuildHierarchyForeachIDCbData &build_data = *reinterpret_cast( - cb_data->user_data); - /* Note that this may be an embedded ID (see #real_override_id). */ - ID &id = **cb_data->id_pointer; +static bool id_is_in_override_hierarchy(const ID &id, + const ID &relationship_parent_id, + const ID &override_root_id) +{ /* If #id is an embedded ID, this will be set to the owner, which is a real ID and contains the * override data. So queries of override data should be done via this, but the actual tree * element we add is the embedded ID. */ const ID *real_override_id = &id; if (ID_IS_OVERRIDE_LIBRARY_VIRTUAL(&id)) { + /* This assumes that the parent ID is always the owner of the 'embedded' one, I.e. that no + * other ID directly uses the embedded one. Should be true, but the debug code adds some checks + * to validate this assumption. */ + real_override_id = &relationship_parent_id; + +#ifndef NDEBUG if (GS(id.name) == ID_KE) { - Key *key = (Key *)&id; - real_override_id = key->from; + const Key *key = (Key *)&id; + BLI_assert(real_override_id == key->from); } - else if (id.flag & LIB_EMBEDDED_DATA) { - /* TODO Needs double-checking if this handles all embedded IDs correctly. */ - real_override_id = cb_data->id_owner; + else { + BLI_assert((id.flag & LIB_EMBEDDED_DATA) != 0); } +#endif } if (!ID_IS_OVERRIDE_LIBRARY(real_override_id)) { - return IDWALK_RET_NOP; + return false; } /* Is this ID part of the same override hierarchy? */ - if (real_override_id->override_library->hierarchy_root != &build_data.override_root_id) { - return IDWALK_RET_NOP; - } - - /* Avoid endless recursion: If there is an ancestor for this ID already, it recurses into itself. - */ - if (build_data.parent_ids.lookup_key_default(&id, nullptr)) { - return IDWALK_RET_NOP; - } - - /* Avoid duplicates: If there is a sibling for this ID already, the same ID is just used multiple - * times by the same parent. */ - if (build_data.sibling_ids.lookup_key_default(&id, nullptr)) { - return IDWALK_RET_NOP; + if (real_override_id->override_library->hierarchy_root != &override_root_id) { + return false; } - TreeElement *new_te = outliner_add_element(&build_data.space_outliner, - &build_data.parent_te->getLegacyElement().subtree, - &id, - &build_data.parent_te->getLegacyElement(), - TSE_SOME_ID, - 0, - false); - build_data.sibling_ids.add(&id); - - BuildHierarchyForeachIDCbData child_build_data{build_data.bmain, - build_data.space_outliner, - build_data.override_root_id, - tree_element_cast(new_te)}; - child_build_data.parent_ids = build_data.parent_ids; - child_build_data.parent_ids.add(&id); - child_build_data.sibling_ids.reserve(10); - BKE_library_foreach_ID_link( - &build_data.bmain, &id, build_hierarchy_foreach_ID_cb, &child_build_data, IDWALK_READONLY); - - return IDWALK_RET_NOP; + return true; } -void TreeDisplayOverrideLibraryHierarchies::build_hierarchy_for_ID(Main *bmain, - ID &override_root_id, - TreeElementID &te_id) const -{ - BuildHierarchyForeachIDCbData build_data{*bmain, space_outliner_, override_root_id, &te_id}; - build_data.parent_ids.add(&override_root_id); - - BKE_library_foreach_ID_link( - bmain, &te_id.get_ID(), build_hierarchy_foreach_ID_cb, &build_data, IDWALK_READONLY); -} +/** \} */ } // namespace blender::ed::outliner -- cgit v1.2.3 From e0cc86978c0f72f57240214ccb6bc7fe71428827 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Thu, 7 Jul 2022 17:51:18 +0200 Subject: Workspaces: Option to pin scene to a workspace Adds a "Pin Scene" option to the workspace. When activated, the workspace will remember the scene that was last activated in it, so that when switching back to this workspace, the same scene will be reactivated. This is important for a VSE workflow, so that users can switch between different workspaces displaying a scene and thus a timeline for a specific task. The option can be found in the Properties, Workspace tab. D11890 additionally adds an icon for this to the scene switcher in the topbar. The workspace data contains a pointer to the scene which is a UI to scene data relation. When appending a workspace, the pointer is cleared. Differential Revision: https://developer.blender.org/D9140 Reviewed by: Brecht Van Lommel, Bastien Montagne (no final accept, but was fine with the general design earlier) --- .../scripts/startup/bl_ui/properties_workspace.py | 2 + source/blender/blenkernel/intern/workspace.c | 11 ++++ source/blender/blenloader/intern/readfile.c | 3 + source/blender/editors/screen/workspace_edit.c | 77 +++++++++++++++++++++- source/blender/makesdna/DNA_windowmanager_types.h | 3 + source/blender/makesdna/DNA_workspace_types.h | 5 ++ source/blender/makesrna/intern/rna_workspace.c | 7 ++ source/blender/windowmanager/intern/wm.c | 8 +++ 8 files changed, 114 insertions(+), 2 deletions(-) diff --git a/release/scripts/startup/bl_ui/properties_workspace.py b/release/scripts/startup/bl_ui/properties_workspace.py index 542d770c062..1a245b33cd2 100644 --- a/release/scripts/startup/bl_ui/properties_workspace.py +++ b/release/scripts/startup/bl_ui/properties_workspace.py @@ -28,6 +28,8 @@ class WORKSPACE_PT_main(WorkSpaceButtonsPanel, Panel): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False + + layout.prop(workspace, "use_pin_scene") layout.prop(workspace, "object_mode", text="Mode") diff --git a/source/blender/blenkernel/intern/workspace.c b/source/blender/blenkernel/intern/workspace.c index ef0a3069815..0265dd037b1 100644 --- a/source/blender/blenkernel/intern/workspace.c +++ b/source/blender/blenkernel/intern/workspace.c @@ -67,6 +67,8 @@ static void workspace_foreach_id(ID *id, LibraryForeachIDData *data) { WorkSpace *workspace = (WorkSpace *)id; + BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, workspace->pin_scene, IDWALK_CB_NOP); + LISTBASE_FOREACH (WorkSpaceLayout *, layout, &workspace->layouts) { BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, layout->screen, IDWALK_CB_USER); } @@ -120,6 +122,15 @@ static void workspace_blend_read_lib(BlendLibReader *reader, ID *id) WorkSpace *workspace = (WorkSpace *)id; Main *bmain = BLO_read_lib_get_main(reader); + /* Do not keep the scene reference when appending a workspace. Setting a scene for a workspace is + * a convenience feature, but the workspace should never truly depend on scene data. */ + if (ID_IS_LINKED(id)) { + workspace->pin_scene = NULL; + } + else { + BLO_read_id_address(reader, NULL, &workspace->pin_scene); + } + /* Restore proper 'parent' pointers to relevant data, and clean up unused/invalid entries. */ LISTBASE_FOREACH_MUTABLE (WorkSpaceDataRelation *, relation, &workspace->hook_layout_relations) { relation->parent = NULL; diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index 973965ada50..f0d390677bb 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -2732,6 +2732,8 @@ void blo_lib_link_restore(Main *oldmain, LISTBASE_FOREACH (WorkSpaceLayout *, layout, &workspace->layouts) { lib_link_workspace_layout_restore(id_map, newmain, layout); } + workspace->pin_scene = restore_pointer_by_name( + id_map, (ID *)workspace->pin_scene, USER_IGNORE); } LISTBASE_FOREACH (wmWindow *, win, &curwm->windows) { @@ -2745,6 +2747,7 @@ void blo_lib_link_restore(Main *oldmain, if (win->scene == NULL) { win->scene = curscene; } + win->unpinned_scene = restore_pointer_by_name(id_map, (ID *)win->unpinned_scene, USER_IGNORE); if (BKE_view_layer_find(win->scene, win->view_layer_name) == NULL) { STRNCPY(win->view_layer_name, cur_view_layer->name); } diff --git a/source/blender/editors/screen/workspace_edit.c b/source/blender/editors/screen/workspace_edit.c index 01417650b3d..0535a270176 100644 --- a/source/blender/editors/screen/workspace_edit.c +++ b/source/blender/editors/screen/workspace_edit.c @@ -54,17 +54,87 @@ WorkSpace *ED_workspace_add(Main *bmain, const char *name) return BKE_workspace_add(bmain, name); } +static void workspace_exit(WorkSpace *workspace, wmWindow *win) +{ + /* Scene pinning: Store whatever scene was active when leaving the workspace. It's reactivated + * when the workspace gets reactivated as well. */ + if (workspace->flags & WORKSPACE_USE_PIN_SCENE) { + workspace->pin_scene = WM_window_get_active_scene(win); + } + else { + /* The active scene may have been changed. So also always update the unpinned scene to the + * latest when leaving a workspace that has no scene pinning. */ + win->unpinned_scene = WM_window_get_active_scene(win); + } +} + +/** + * State changes (old workspace to new workspace): + * 1) unpinned -> pinned + * * Store current scene as the unpinned one (done in #workspace_exit()). + * * Change the current scene to the pinned one. + * 2) pinned -> pinned + * * Change the current scene to the new pinned one. + * 3) pinned -> unpinned + * * Change current scene back to the unpinned one + * 4) unpinned -> unpinned + * * Make sure the unpinned scene is active. + * + * Note that the pin scene must also be updated when leaving a workspace with a pinned scene. + * That's done separately via workspace_exit() above. + */ +static void workspace_scene_pinning_update(WorkSpace *workspace_new, + const WorkSpace *workspace_old, + bContext *C) +{ + wmWindow *win = CTX_wm_window(C); + Main *bmain = CTX_data_main(C); + Scene *active_scene = WM_window_get_active_scene(win); + + const bool is_new_pinned = (workspace_new->flags & WORKSPACE_USE_PIN_SCENE); + const bool is_old_pinned = (workspace_old->flags & WORKSPACE_USE_PIN_SCENE); + + /* State changes 1 and 2. */ + if (is_new_pinned) { + if (workspace_new->pin_scene && (workspace_new->pin_scene != active_scene)) { + WM_window_set_active_scene(bmain, C, win, workspace_new->pin_scene); + workspace_new->pin_scene = NULL; + } + } + /* State change 3 - Changing from workspace with pinned scene to unpinned scene. */ + else if (is_old_pinned) { + if (win->unpinned_scene) { + WM_window_set_active_scene(bmain, C, win, win->unpinned_scene); + } + else { + /* When leaving a workspace where the pinning was just enabled, the unpinned scene wasn't set + * yet. */ + win->unpinned_scene = active_scene; + } + } + else { + /* When leaving a workspace where the pinning was just disabled, we still want to restore the + * unpinned scene. */ + if (win->unpinned_scene) { + WM_window_set_active_scene(bmain, C, win, win->unpinned_scene); + } + } + + BLI_assert(WM_window_get_active_scene(win)); +} + /** * Changes the object mode (if needed) to the one set in \a workspace_new. * Object mode is still stored on object level. In future it should all be workspace level instead. */ static void workspace_change_update(WorkSpace *workspace_new, - const WorkSpace *workspace_old, + WorkSpace *workspace_old, bContext *C, wmWindowManager *wm) { + workspace_scene_pinning_update(workspace_new, workspace_old, C); /* needs to be done before changing mode! (to ensure right context) */ - UNUSED_VARS(workspace_old, workspace_new, C, wm); + UNUSED_VARS(wm); #if 0 Object *ob_act = CTX_data_active_object(C) eObjectMode mode_old = workspace_old->object_mode; eObjectMode mode_new = workspace_new->object_mode; @@ -113,6 +183,8 @@ bool ED_workspace_change(WorkSpace *workspace_new, bContext *C, wmWindowManager return false; } + workspace_exit(workspace_old, win); + screen_change_prepare(screen_old, screen_new, bmain, C, win); if (screen_new == NULL) { @@ -143,6 +215,7 @@ WorkSpace *ED_workspace_duplicate(WorkSpace *workspace_old, Main *bmain, wmWindo WorkSpace *workspace_new = ED_workspace_add(bmain, workspace_old->id.name + 2); workspace_new->flags = workspace_old->flags; + workspace_new->pin_scene = workspace_old->pin_scene; workspace_new->object_mode = workspace_old->object_mode; workspace_new->order = workspace_old->order; BLI_duplicatelist(&workspace_new->owner_ids, &workspace_old->owner_ids); diff --git a/source/blender/makesdna/DNA_windowmanager_types.h b/source/blender/makesdna/DNA_windowmanager_types.h index f7aaa1186db..5e741508603 100644 --- a/source/blender/makesdna/DNA_windowmanager_types.h +++ b/source/blender/makesdna/DNA_windowmanager_types.h @@ -239,6 +239,9 @@ typedef struct wmWindow { struct Scene *new_scene; /** Active view layer displayed in this window. */ char view_layer_name[64]; + /** The workspace may temporarily override the window's scene with scene pinning. This is the + * "overriden" or "default" scene to restore when entering a workspace with no scene pinned. */ + struct Scene *unpinned_scene; struct WorkSpaceInstanceHook *workspace_hook; diff --git a/source/blender/makesdna/DNA_workspace_types.h b/source/blender/makesdna/DNA_workspace_types.h index b8ed39bfd5d..a72689badb1 100644 --- a/source/blender/makesdna/DNA_workspace_types.h +++ b/source/blender/makesdna/DNA_workspace_types.h @@ -123,6 +123,10 @@ typedef struct WorkSpace { /** List of #bToolRef */ ListBase tools; + /** Optional, scene to switch to when enabling this workspace (NULL to disable). Cleared on + * link/append. */ + struct Scene *pin_scene; + char _pad[4]; int object_mode; @@ -195,6 +199,7 @@ typedef struct WorkSpaceInstanceHook { typedef enum eWorkSpaceFlags { WORKSPACE_USE_FILTER_BY_ORIGIN = (1 << 1), + WORKSPACE_USE_PIN_SCENE = (1 << 2), } eWorkSpaceFlags; #ifdef __cplusplus diff --git a/source/blender/makesrna/intern/rna_workspace.c b/source/blender/makesrna/intern/rna_workspace.c index 0b6c3934985..4873831abce 100644 --- a/source/blender/makesrna/intern/rna_workspace.c +++ b/source/blender/makesrna/intern/rna_workspace.c @@ -413,6 +413,13 @@ static void rna_def_workspace(BlenderRNA *brna) RNA_def_property_ui_text( prop, "Object Mode", "Switch to this object mode when activating the workspace"); + prop = RNA_def_property(srna, "use_pin_scene", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flags", WORKSPACE_USE_PIN_SCENE); + RNA_def_property_ui_text(prop, + "Pin Scene", + "Remember the last used scene for the workspace and switch to it " + "whenever this workspace is activated again"); + /* Flags */ prop = RNA_def_property(srna, "use_filter_by_owner", PROP_BOOLEAN, PROP_NONE); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); diff --git a/source/blender/windowmanager/intern/wm.c b/source/blender/windowmanager/intern/wm.c index 40d9b0b9a35..32cec8f779c 100644 --- a/source/blender/windowmanager/intern/wm.c +++ b/source/blender/windowmanager/intern/wm.c @@ -81,6 +81,8 @@ static void window_manager_foreach_id(ID *id, LibraryForeachIDData *data) if (BKE_lib_query_foreachid_iter_stop(data)) { return; } + + BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, win->unpinned_scene, IDWALK_CB_NOP); } if (BKE_lib_query_foreachid_process_flags_get(data) & IDWALK_INCLUDE_UI) { @@ -224,6 +226,7 @@ static void lib_link_workspace_instance_hook(BlendLibReader *reader, { WorkSpace *workspace = BKE_workspace_active_get(hook); BLO_read_id_address(reader, id->lib, &workspace); + BKE_workspace_active_set(hook, workspace); } @@ -239,6 +242,11 @@ static void window_manager_blend_read_lib(BlendLibReader *reader, ID *id) /* deprecated, but needed for versioning (will be NULL'ed then) */ BLO_read_id_address(reader, NULL, &win->screen); + /* The unpinned scene is a UI->Scene-data pointer, and should be NULL'ed on linking (like + * WorkSpace.pin_scene). But the WindowManager ID (owning the window) is never linked. */ + BLI_assert(!ID_IS_LINKED(id)); + BLO_read_id_address(reader, id->lib, &win->unpinned_scene); + LISTBASE_FOREACH (ScrArea *, area, &win->global_areas.areabase) { BKE_screen_area_blend_read_lib(reader, &wm->id, area); } -- cgit v1.2.3 From b8605ee458e31420a4e6653c223f841c5875a21d Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Thu, 7 Jul 2022 18:14:05 +0200 Subject: UI: Superimposed pin icon for workspace scene pinning in the scene switcher Followup to the previous commit, to display a pin icon in the scene switcher. This is a good indicator to have and such workspace-wide functionality should be available in the topbar, close to what it belongs to (scene switching). Downside is that it makes this already crowded region even more crowded. But thanks to the use of superimposed icons, it's not too noisy visually. Differential Revision: https://developer.blender.org/D11890 Reviewed by: Campbell Barton --- .../editors/interface/interface_templates.c | 22 ++++++++++ source/blender/editors/screen/workspace_edit.c | 30 +++++++++++++ .../blender/editors/space_buttons/space_buttons.c | 3 ++ source/blender/editors/space_topbar/space_topbar.c | 3 ++ source/blender/editors/space_view3d/space_view3d.c | 4 ++ source/blender/makesrna/intern/rna_workspace.c | 1 + source/blender/windowmanager/WM_types.h | 49 +++++++++++----------- 7 files changed, 88 insertions(+), 24 deletions(-) diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c index 05ae5299e58..96d31700eb9 100644 --- a/source/blender/editors/interface/interface_templates.c +++ b/source/blender/editors/interface/interface_templates.c @@ -1023,6 +1023,26 @@ static const char *template_id_browse_tip(const StructRNA *type) return N_("Browse ID data to be linked"); } +/** + * Add a superimposed extra icon to \a but, for workspace pinning. + * Rather ugly special handling, but this is really a special case at this point, nothing worth + * generalizing. + */ +static void template_id_workspace_pin_extra_icon(const TemplateID *template_ui, uiBut *but) +{ + if ((template_ui->idcode != ID_SCE) || (template_ui->ptr.type != &RNA_Window)) { + return; + } + + const wmWindow *win = template_ui->ptr.data; + const WorkSpace *workspace = WM_window_get_active_workspace(win); + UI_but_extra_operator_icon_add(but, + "WORKSPACE_OT_scene_pin_toggle", + WM_OP_INVOKE_DEFAULT, + (workspace->flags & WORKSPACE_USE_PIN_SCENE) ? ICON_PINNED : + ICON_UNPINNED); +} + /** * \return a type-based i18n context, needed e.g. by "New" button. * In most languages, this adjective takes different form based on gender of type name... @@ -1220,6 +1240,8 @@ static void template_ID(const bContext *C, UI_but_flag_enable(but, UI_BUT_REDALERT); } + template_id_workspace_pin_extra_icon(template_ui, but); + if (ID_IS_LINKED(id)) { const bool disabled = !BKE_idtype_idcode_is_localizable(GS(id->name)); if (id->tag & LIB_TAG_INDIRECT) { diff --git a/source/blender/editors/screen/workspace_edit.c b/source/blender/editors/screen/workspace_edit.c index 0535a270176..cb29f15420c 100644 --- a/source/blender/editors/screen/workspace_edit.c +++ b/source/blender/editors/screen/workspace_edit.c @@ -585,6 +585,35 @@ static void WORKSPACE_OT_reorder_to_front(wmOperatorType *ot) ot->exec = workspace_reorder_to_front_exec; } +static int workspace_scene_pin_toggle(bContext *C, wmOperator *UNUSED(op)) +{ + WorkSpace *workspace = workspace_context_get(C); + + /* Trivial. The operator is only needed to display a superimposed extra icon, which + * requires an operator. */ + workspace->flags ^= WORKSPACE_USE_PIN_SCENE; + + WM_event_add_notifier(C, NC_WORKSPACE, NULL); + + return OPERATOR_FINISHED; +} + +static void WORKSPACE_OT_scene_pin_toggle(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Pin Scene to Workspace"; + ot->description = + "Remember the last used scene for the current workspace and switch to it whenever this " + "workspace is activated again"; + ot->idname = "WORKSPACE_OT_scene_pin_toggle"; + + /* api callbacks */ + ot->poll = workspace_context_poll; + ot->exec = workspace_scene_pin_toggle; + + ot->flag = OPTYPE_INTERNAL; +} + void ED_operatortypes_workspace(void) { WM_operatortype_append(WORKSPACE_OT_duplicate); @@ -593,6 +622,7 @@ void ED_operatortypes_workspace(void) WM_operatortype_append(WORKSPACE_OT_append_activate); WM_operatortype_append(WORKSPACE_OT_reorder_to_back); WM_operatortype_append(WORKSPACE_OT_reorder_to_front); + WM_operatortype_append(WORKSPACE_OT_scene_pin_toggle); } /** \} Workspace Operators */ diff --git a/source/blender/editors/space_buttons/space_buttons.c b/source/blender/editors/space_buttons/space_buttons.c index 052af39319c..e60946b8f66 100644 --- a/source/blender/editors/space_buttons/space_buttons.c +++ b/source/blender/editors/space_buttons/space_buttons.c @@ -778,6 +778,9 @@ static void buttons_area_listener(const wmSpaceTypeListenerParams *params) sbuts->preview = 1; } break; + case NC_WORKSPACE: + buttons_area_redraw(area, BCONTEXT_TOOL); + break; case NC_SPACE: if (wmn->data == ND_SPACE_PROPERTIES) { ED_area_tag_redraw(area); diff --git a/source/blender/editors/space_topbar/space_topbar.c b/source/blender/editors/space_topbar/space_topbar.c index e9a9e690e60..bc68de1dfce 100644 --- a/source/blender/editors/space_topbar/space_topbar.c +++ b/source/blender/editors/space_topbar/space_topbar.c @@ -155,6 +155,9 @@ static void topbar_header_listener(const wmRegionListenerParams *params) ED_region_tag_redraw(region); } break; + case NC_WORKSPACE: + ED_region_tag_redraw(region); + break; case NC_SPACE: if (wmn->data == ND_SPACE_INFO) { ED_region_tag_redraw(region); diff --git a/source/blender/editors/space_view3d/space_view3d.c b/source/blender/editors/space_view3d/space_view3d.c index 9ed2fec96db..a423a842019 100644 --- a/source/blender/editors/space_view3d/space_view3d.c +++ b/source/blender/editors/space_view3d/space_view3d.c @@ -1300,6 +1300,10 @@ static void view3d_main_region_listener(const wmRegionListenerParams *params) ED_region_tag_redraw(region); } break; + case NC_WORKSPACE: + /* In case the region displays workspace settings. */ + ED_region_tag_redraw(region); + break; } } diff --git a/source/blender/makesrna/intern/rna_workspace.c b/source/blender/makesrna/intern/rna_workspace.c index 4873831abce..a0d89b8b15a 100644 --- a/source/blender/makesrna/intern/rna_workspace.c +++ b/source/blender/makesrna/intern/rna_workspace.c @@ -419,6 +419,7 @@ static void rna_def_workspace(BlenderRNA *brna) "Pin Scene", "Remember the last used scene for the workspace and switch to it " "whenever this workspace is activated again"); + RNA_def_property_update(prop, NC_WORKSPACE, NULL); /* Flags */ prop = RNA_def_property(srna, "use_filter_by_owner", PROP_BOOLEAN, PROP_NONE); diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index aebd62ee91b..32bc2f96365 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -323,34 +323,35 @@ typedef struct wmNotifier { #define NOTE_CATEGORY 0xFF000000 #define NC_WM (1 << 24) #define NC_WINDOW (2 << 24) -#define NC_SCREEN (3 << 24) -#define NC_SCENE (4 << 24) -#define NC_OBJECT (5 << 24) -#define NC_MATERIAL (6 << 24) -#define NC_TEXTURE (7 << 24) -#define NC_LAMP (8 << 24) -#define NC_GROUP (9 << 24) -#define NC_IMAGE (10 << 24) -#define NC_BRUSH (11 << 24) -#define NC_TEXT (12 << 24) -#define NC_WORLD (13 << 24) -#define NC_ANIMATION (14 << 24) +#define NC_WORKSPACE (3 << 24) +#define NC_SCREEN (4 << 24) +#define NC_SCENE (5 << 24) +#define NC_OBJECT (6 << 24) +#define NC_MATERIAL (7 << 24) +#define NC_TEXTURE (8 << 24) +#define NC_LAMP (9 << 24) +#define NC_GROUP (10 << 24) +#define NC_IMAGE (11 << 24) +#define NC_BRUSH (12 << 24) +#define NC_TEXT (13 << 24) +#define NC_WORLD (14 << 24) +#define NC_ANIMATION (15 << 24) /* When passing a space as reference data with this (e.g. `WM_event_add_notifier(..., space)`), * the notifier will only be sent to this space. That avoids unnecessary updates for unrelated * spaces. */ -#define NC_SPACE (15 << 24) -#define NC_GEOM (16 << 24) -#define NC_NODE (17 << 24) -#define NC_ID (18 << 24) -#define NC_PAINTCURVE (19 << 24) -#define NC_MOVIECLIP (20 << 24) -#define NC_MASK (21 << 24) -#define NC_GPENCIL (22 << 24) -#define NC_LINESTYLE (23 << 24) -#define NC_CAMERA (24 << 24) -#define NC_LIGHTPROBE (25 << 24) +#define NC_SPACE (16 << 24) +#define NC_GEOM (17 << 24) +#define NC_NODE (18 << 24) +#define NC_ID (19 << 24) +#define NC_PAINTCURVE (20 << 24) +#define NC_MOVIECLIP (21 << 24) +#define NC_MASK (22 << 24) +#define NC_GPENCIL (23 << 24) +#define NC_LINESTYLE (24 << 24) +#define NC_CAMERA (25 << 24) +#define NC_LIGHTPROBE (26 << 24) /* Changes to asset data in the current .blend. */ -#define NC_ASSET (26 << 24) +#define NC_ASSET (27 << 24) /* data type, 256 entries is enough, it can overlap */ #define NOTE_DATA 0x00FF0000 -- cgit v1.2.3 From 7cfea48752ebc544104d57d19736c201a98bc3cd Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Thu, 7 Jul 2022 18:17:30 +0200 Subject: LibOverride: Make fully editable when creating an experimental user setting. This is temporary to investigate which behavior should be kept when creating an override hierarchy if there are no cherry-picked data defined: make all overrides user-editable, or not. This removes the 'make override - fully editable' menu entries. --- release/scripts/startup/bl_ui/space_userpref.py | 1 + release/scripts/startup/bl_ui/space_view3d.py | 2 -- .../editors/interface/interface_templates.c | 37 +++++++++++++++++----- source/blender/editors/object/object_relations.c | 9 +----- .../editors/space_outliner/outliner_tools.cc | 26 +-------------- source/blender/makesdna/DNA_userdef_types.h | 2 ++ source/blender/makesrna/intern/rna_userdef.c | 9 ++++++ 7 files changed, 43 insertions(+), 43 deletions(-) diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py index 5070bd04142..e08b921b1a9 100644 --- a/release/scripts/startup/bl_ui/space_userpref.py +++ b/release/scripts/startup/bl_ui/space_userpref.py @@ -2279,6 +2279,7 @@ class USERPREF_PT_experimental_prototypes(ExperimentalPanel, Panel): ({"property": "use_full_frame_compositor"}, "T88150"), ({"property": "enable_eevee_next"}, "T93220"), ({"property": "use_draw_manager_acquire_lock"}, "T98016"), + ({"property": "use_override_new_fully_editable"}, None), ), ) diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 91422a817f5..72119030919 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -2354,8 +2354,6 @@ class VIEW3D_MT_object_relations(Menu): layout = self.layout layout.operator("object.make_override_library", text="Make Library Override...") - layout.operator("object.make_override_library", - text="Make Library Override - Fully Editable...").do_fully_editable = True layout.operator("object.make_dupli_face") diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c index 96d31700eb9..14da5a7cd62 100644 --- a/source/blender/editors/interface/interface_templates.c +++ b/source/blender/editors/interface/interface_templates.c @@ -741,8 +741,15 @@ static void template_id_liboverride_hierarchy_create(bContext *C, if (object_active != NULL) { object_active->id.tag |= LIB_TAG_DOIT; } - BKE_lib_override_library_create( - bmain, scene, view_layer, NULL, id, &collection_active->id, NULL, &id_override, false); + BKE_lib_override_library_create(bmain, + scene, + view_layer, + NULL, + id, + &collection_active->id, + NULL, + &id_override, + U.experimental.use_override_new_fully_editable); } else if (object_active != NULL && !ID_IS_LINKED(object_active) && &object_active->instance_collection->id == id) { @@ -755,7 +762,7 @@ static void template_id_liboverride_hierarchy_create(bContext *C, &object_active->id, &object_active->id, &id_override, - false); + U.experimental.use_override_new_fully_editable); } break; case ID_OB: @@ -765,8 +772,15 @@ static void template_id_liboverride_hierarchy_create(bContext *C, if (object_active != NULL) { object_active->id.tag |= LIB_TAG_DOIT; } - BKE_lib_override_library_create( - bmain, scene, view_layer, NULL, id, &collection_active->id, NULL, &id_override, false); + BKE_lib_override_library_create(bmain, + scene, + view_layer, + NULL, + id, + &collection_active->id, + NULL, + &id_override, + U.experimental.use_override_new_fully_editable); } break; case ID_ME: @@ -796,12 +810,19 @@ static void template_id_liboverride_hierarchy_create(bContext *C, &collection_active->id, NULL, &id_override, - false); + U.experimental.use_override_new_fully_editable); } else { object_active->id.tag |= LIB_TAG_DOIT; - BKE_lib_override_library_create( - bmain, scene, view_layer, NULL, id, &object_active->id, NULL, &id_override, false); + BKE_lib_override_library_create(bmain, + scene, + view_layer, + NULL, + id, + &object_active->id, + NULL, + &id_override, + U.experimental.use_override_new_fully_editable); } } break; diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c index f55ffdf0fcd..b58b0dac79b 100644 --- a/source/blender/editors/object/object_relations.c +++ b/source/blender/editors/object/object_relations.c @@ -2267,7 +2267,7 @@ static int make_override_library_exec(bContext *C, wmOperator *op) ID *id_root = NULL; bool is_override_instancing_object = false; - const bool do_fully_editable = RNA_boolean_get(op->ptr, "do_fully_editable"); + const bool do_fully_editable = U.experimental.use_override_new_fully_editable; GSet *user_overrides_objects_uids = do_fully_editable ? NULL : BLI_gset_new(BLI_ghashutil_inthash_p, @@ -2495,13 +2495,6 @@ void OBJECT_OT_make_override_library(wmOperatorType *ot) INT_MAX); RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); ot->prop = prop; - - prop = RNA_def_boolean(ot->srna, - "do_fully_editable", - false, - "Create Fully Editable", - "Make all created override data-blocks fully editable"); - RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); } /** \} */ diff --git a/source/blender/editors/space_outliner/outliner_tools.cc b/source/blender/editors/space_outliner/outliner_tools.cc index 04e74f5438b..7b7182eb106 100644 --- a/source/blender/editors/space_outliner/outliner_tools.cc +++ b/source/blender/editors/space_outliner/outliner_tools.cc @@ -1995,7 +1995,6 @@ enum eOutlinerIdOpTypes { OUTLINER_IDOP_LOCAL, OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE, OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY, - OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY_FULLY_EDITABLE, OUTLINER_IDOP_OVERRIDE_LIBRARY_MAKE_EDITABLE, OUTLINER_IDOP_OVERRIDE_LIBRARY_RESET, OUTLINER_IDOP_OVERRIDE_LIBRARY_RESET_HIERARCHY, @@ -2041,12 +2040,6 @@ static const EnumPropertyItem prop_id_op_types[] = { "Make Library Override Hierarchy", "Make a local override of this linked data-block, and its hierarchy of dependencies - only " "applies to active Outliner item"}, - {OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY_FULLY_EDITABLE, - "OVERRIDE_LIBRARY_CREATE_HIERARCHY_FULLY_EDITABLE", - 0, - "Make Library Override Hierarchy Fully Editable", - "Make a local override of this linked data-block, and its hierarchy of dependencies, making " - "them all fully user-editable - only applies to active Outliner item"}, {OUTLINER_IDOP_OVERRIDE_LIBRARY_MAKE_EDITABLE, "OVERRIDE_LIBRARY_MAKE_EDITABLE", 0, @@ -2126,7 +2119,6 @@ static bool outliner_id_operation_item_poll(bContext *C, } return false; case OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY: - case OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY_FULLY_EDITABLE: if (ID_IS_OVERRIDABLE_LIBRARY(tselem->id) || (ID_IS_LINKED(tselem->id))) { return true; } @@ -2270,6 +2262,7 @@ static int outliner_id_operation_exec(bContext *C, wmOperator *op) case OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY: { OutlinerLibOverrideData override_data{}; override_data.do_hierarchy = true; + override_data.do_fully_editable = U.experimental.use_override_new_fully_editable; outliner_do_libdata_operation(C, op->reports, scene, @@ -2283,23 +2276,6 @@ static int outliner_id_operation_exec(bContext *C, wmOperator *op) ED_undo_push(C, "Overridden Data Hierarchy"); break; } - case OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY_FULLY_EDITABLE: { - OutlinerLibOverrideData override_data{}; - override_data.do_hierarchy = true; - override_data.do_fully_editable = true; - outliner_do_libdata_operation(C, - op->reports, - scene, - space_outliner, - id_override_library_create_hierarchy_pre_process_fn, - &override_data); - outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, id_override_library_create_fn, &override_data); - id_override_library_create_hierarchy_post_process(C, &override_data); - - ED_undo_push(C, "Overridden Data Hierarchy Fully Editable"); - break; - } case OUTLINER_IDOP_OVERRIDE_LIBRARY_MAKE_EDITABLE: { outliner_do_libdata_operation(C, op->reports, diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index d00826208be..753c41f7f1b 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -637,6 +637,7 @@ typedef struct UserDef_Experimental { /* Debug options, always available. */ char use_undo_legacy; char no_override_auto_resync; + char use_override_new_fully_editable; char use_cycles_debug; char show_asset_debug_info; char no_asset_indexing; @@ -654,6 +655,7 @@ typedef struct UserDef_Experimental { char enable_eevee_next; char use_sculpt_texture_paint; char use_draw_manager_acquire_lock; + char _pad[7]; /** `makesdna` does not allow empty structs. */ } UserDef_Experimental; diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index 40dc1254a7d..fd3479f6fe4 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -6392,6 +6392,15 @@ static void rna_def_userdef_experimental(BlenderRNA *brna) "Enable library overrides automatic resync detection and process on file load. Disable when " "dealing with older .blend files that need manual Resync (Enforce) handling"); + prop = RNA_def_property(srna, "use_override_new_fully_editable", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "use_override_new_fully_editable", 1); + RNA_def_property_ui_text( + prop, + "Override New Fully Editable", + "Make all override of a hierarchy fully user-editable by default when creating a new " + "override (if that option is disabled, most overrides created as part of a hierarchy will " + "not be editable by the user by default)"); + prop = RNA_def_property(srna, "use_new_point_cloud_type", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "use_new_point_cloud_type", 1); RNA_def_property_ui_text( -- cgit v1.2.3 From ba62e20af67e1de25f781456df7175fa1b31db67 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Thu, 7 Jul 2022 19:19:01 +0200 Subject: BLI: make some spans default constructible `GSpan` and spans based on virtual arrays were not default constructible before, which made them hard to use sometimes. It's generally fine for spans to be empty. The main thing the keep in mind is that the type pointer in `GSpan` may be null now. Generally, code receiving spans as input can assume that the type is not-null, but sometimes that may be valid. The old #type() method that returned a reference to the type still exists. It asserts when the type is null. --- source/blender/blenlib/BLI_generic_span.hh | 64 ++++++++++++++++------ .../blender/blenlib/BLI_generic_virtual_array.hh | 4 ++ source/blender/blenlib/BLI_virtual_array.hh | 23 +++++++- .../blenlib/intern/generic_virtual_array.cc | 32 +++++++++-- .../blenlib/tests/BLI_virtual_array_test.cc | 33 +++++++++++ 5 files changed, 135 insertions(+), 21 deletions(-) diff --git a/source/blender/blenlib/BLI_generic_span.hh b/source/blender/blenlib/BLI_generic_span.hh index 4c0bfc83ba8..0a40201634a 100644 --- a/source/blender/blenlib/BLI_generic_span.hh +++ b/source/blender/blenlib/BLI_generic_span.hh @@ -16,20 +16,30 @@ namespace blender { */ class GSpan { protected: - const CPPType *type_; - const void *data_; - int64_t size_; + const CPPType *type_ = nullptr; + const void *data_ = nullptr; + int64_t size_ = 0; public: - GSpan(const CPPType &type, const void *buffer, int64_t size) - : type_(&type), data_(buffer), size_(size) + GSpan() = default; + + GSpan(const CPPType *type, const void *buffer, int64_t size) + : type_(type), data_(buffer), size_(size) { BLI_assert(size >= 0); BLI_assert(buffer != nullptr || size == 0); - BLI_assert(type.pointer_has_valid_alignment(buffer)); + BLI_assert(type->pointer_has_valid_alignment(buffer)); + } + + GSpan(const CPPType &type, const void *buffer, int64_t size) : GSpan(&type, buffer, size) + { + } + + GSpan(const CPPType &type) : type_(&type) + { } - GSpan(const CPPType &type) : GSpan(type, nullptr, 0) + GSpan(const CPPType *type) : type_(type) { } @@ -41,9 +51,15 @@ class GSpan { const CPPType &type() const { + BLI_assert(type_ != nullptr); return *type_; } + const CPPType *type_ptr() const + { + return type_; + } + bool is_empty() const { return size_ == 0; @@ -76,7 +92,7 @@ class GSpan { BLI_assert(start >= 0); BLI_assert(size >= 0); const int64_t new_size = std::max(0, std::min(size, size_ - start)); - return GSpan(*type_, POINTER_OFFSET(data_, type_->size() * start), new_size); + return GSpan(type_, POINTER_OFFSET(data_, type_->size() * start), new_size); } GSpan slice(const IndexRange range) const @@ -91,20 +107,30 @@ class GSpan { */ class GMutableSpan { protected: - const CPPType *type_; - void *data_; - int64_t size_; + const CPPType *type_ = nullptr; + void *data_ = nullptr; + int64_t size_ = 0; public: - GMutableSpan(const CPPType &type, void *buffer, int64_t size) - : type_(&type), data_(buffer), size_(size) + GMutableSpan() = default; + + GMutableSpan(const CPPType *type, void *buffer, int64_t size) + : type_(type), data_(buffer), size_(size) { BLI_assert(size >= 0); BLI_assert(buffer != nullptr || size == 0); - BLI_assert(type.pointer_has_valid_alignment(buffer)); + BLI_assert(type->pointer_has_valid_alignment(buffer)); + } + + GMutableSpan(const CPPType &type, void *buffer, int64_t size) : GMutableSpan(&type, buffer, size) + { + } + + GMutableSpan(const CPPType &type) : type_(&type) + { } - GMutableSpan(const CPPType &type) : GMutableSpan(type, nullptr, 0) + GMutableSpan(const CPPType *type) : type_(type) { } @@ -116,14 +142,20 @@ class GMutableSpan { operator GSpan() const { - return GSpan(*type_, data_, size_); + return GSpan(type_, data_, size_); } const CPPType &type() const { + BLI_assert(type_ != nullptr); return *type_; } + const CPPType *type_ptr() const + { + return type_; + } + bool is_empty() const { return size_ == 0; diff --git a/source/blender/blenlib/BLI_generic_virtual_array.hh b/source/blender/blenlib/BLI_generic_virtual_array.hh index 3526925c2e2..43ca16a894f 100644 --- a/source/blender/blenlib/BLI_generic_virtual_array.hh +++ b/source/blender/blenlib/BLI_generic_virtual_array.hh @@ -267,6 +267,7 @@ class GVArraySpan : public GSpan { void *owned_data_ = nullptr; public: + GVArraySpan(); GVArraySpan(GVArray varray); GVArraySpan(GVArraySpan &&other); ~GVArraySpan(); @@ -282,11 +283,14 @@ class GMutableVArraySpan : public GMutableSpan, NonCopyable, NonMovable { bool show_not_saved_warning_ = true; public: + GMutableVArraySpan(); GMutableVArraySpan(GVMutableArray varray, bool copy_values_to_span = true); GMutableVArraySpan(GMutableVArraySpan &&other); ~GMutableVArraySpan(); GMutableVArraySpan &operator=(GMutableVArraySpan &&other); + const GVMutableArray &varray() const; + void save(); void disable_not_applied_warning(); }; diff --git a/source/blender/blenlib/BLI_virtual_array.hh b/source/blender/blenlib/BLI_virtual_array.hh index 84f6223580a..56b2f9f6c6f 100644 --- a/source/blender/blenlib/BLI_virtual_array.hh +++ b/source/blender/blenlib/BLI_virtual_array.hh @@ -1130,6 +1130,9 @@ template class VArraySpan final : public Span { VArraySpan(VArray varray) : Span(), varray_(std::move(varray)) { + if (!varray_) { + return; + } this->size_ = varray_.size(); const CommonVArrayInfo info = varray_.common_info(); if (info.type == CommonVArrayInfo::Type::Span) { @@ -1146,6 +1149,9 @@ template class VArraySpan final : public Span { VArraySpan(VArraySpan &&other) : varray_(std::move(other.varray_)), owned_data_(std::move(other.owned_data_)) { + if (!varray_) { + return; + } this->size_ = varray_.size(); const CommonVArrayInfo info = varray_.common_info(); if (info.type == CommonVArrayInfo::Type::Span) { @@ -1184,11 +1190,17 @@ template class MutableVArraySpan final : public MutableSpan { bool show_not_saved_warning_ = true; public: + MutableVArraySpan() = default; + /* Create a span for any virtual array. This is cheap when the virtual array is a span itself. If * not, a new array has to be allocated as a wrapper for the underlying virtual array. */ MutableVArraySpan(VMutableArray varray, const bool copy_values_to_span = true) : MutableSpan(), varray_(std::move(varray)) { + if (!varray_) { + return; + } + this->size_ = varray_.size(); const CommonVArrayInfo info = varray_.common_info(); if (info.type == CommonVArrayInfo::Type::Span) { @@ -1212,10 +1224,14 @@ template class MutableVArraySpan final : public MutableSpan { owned_data_(std::move(owned_data_)), show_not_saved_warning_(other.show_not_saved_warning_) { + if (!varray_) { + return; + } + this->size_ = varray_.size(); const CommonVArrayInfo info = varray_.common_info(); if (info.type == CommonVArrayInfo::Type::Span) { - this->data_ = reinterpret_cast(info.data); + this->data_ = static_cast(const_cast(info.data)); } else { this->data_ = owned_data_.data(); @@ -1245,6 +1261,11 @@ template class MutableVArraySpan final : public MutableSpan { return *this; } + const VMutableArray &varray() const + { + return varray_; + } + /* Write back all values from a temporary allocated array to the underlying virtual array. */ void save() { diff --git a/source/blender/blenlib/intern/generic_virtual_array.cc b/source/blender/blenlib/intern/generic_virtual_array.cc index 532c7b9b626..28bc66e8524 100644 --- a/source/blender/blenlib/intern/generic_virtual_array.cc +++ b/source/blender/blenlib/intern/generic_virtual_array.cc @@ -287,8 +287,15 @@ template class GVArrayImpl_For_SmallTrivialSingleValue : public /** \name #GVArraySpan * \{ */ -GVArraySpan::GVArraySpan(GVArray varray) : GSpan(varray.type()), varray_(std::move(varray)) +GVArraySpan::GVArraySpan() = default; + +GVArraySpan::GVArraySpan(GVArray varray) + : GSpan(varray ? &varray.type() : nullptr), varray_(std::move(varray)) { + if (!varray_) { + return; + } + size_ = varray_.size(); const CommonVArrayInfo info = varray_.common_info(); if (info.type == CommonVArrayInfo::Type::Span) { @@ -302,8 +309,12 @@ GVArraySpan::GVArraySpan(GVArray varray) : GSpan(varray.type()), varray_(std::mo } GVArraySpan::GVArraySpan(GVArraySpan &&other) - : GSpan(other.type()), varray_(std::move(other.varray_)), owned_data_(other.owned_data_) + : GSpan(other.type_ptr()), varray_(std::move(other.varray_)), owned_data_(other.owned_data_) { + if (!varray_) { + return; + } + size_ = varray_.size(); const CommonVArrayInfo info = varray_.common_info(); if (info.type == CommonVArrayInfo::Type::Span) { @@ -340,9 +351,14 @@ GVArraySpan &GVArraySpan::operator=(GVArraySpan &&other) /** \name #GMutableVArraySpan * \{ */ +GMutableVArraySpan::GMutableVArraySpan() = default; + GMutableVArraySpan::GMutableVArraySpan(GVMutableArray varray, const bool copy_values_to_span) - : GMutableSpan(varray.type()), varray_(std::move(varray)) + : GMutableSpan(varray ? &varray.type() : nullptr), varray_(std::move(varray)) { + if (!varray_) { + return; + } size_ = varray_.size(); const CommonVArrayInfo info = varray_.common_info(); if (info.type == CommonVArrayInfo::Type::Span) { @@ -361,11 +377,14 @@ GMutableVArraySpan::GMutableVArraySpan(GVMutableArray varray, const bool copy_va } GMutableVArraySpan::GMutableVArraySpan(GMutableVArraySpan &&other) - : GMutableSpan(other.type()), + : GMutableSpan(other.type_ptr()), varray_(std::move(other.varray_)), owned_data_(other.owned_data_), show_not_saved_warning_(other.show_not_saved_warning_) { + if (!varray_) { + return; + } size_ = varray_.size(); const CommonVArrayInfo info = varray_.common_info(); if (info.type == CommonVArrayInfo::Type::Span) { @@ -417,6 +436,11 @@ void GMutableVArraySpan::disable_not_applied_warning() show_not_saved_warning_ = false; } +const GVMutableArray &GMutableVArraySpan::varray() const +{ + return varray_; +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenlib/tests/BLI_virtual_array_test.cc b/source/blender/blenlib/tests/BLI_virtual_array_test.cc index c3c6d84ea42..14f5480f751 100644 --- a/source/blender/blenlib/tests/BLI_virtual_array_test.cc +++ b/source/blender/blenlib/tests/BLI_virtual_array_test.cc @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: Apache-2.0 */ #include "BLI_array.hh" +#include "BLI_generic_virtual_array.hh" #include "BLI_strict_flags.h" #include "BLI_vector.hh" #include "BLI_vector_set.hh" @@ -222,4 +223,36 @@ TEST(virtual_array, MaterializeCompressed) } } +TEST(virtual_array, EmptySpanWrapper) +{ + { + VArray varray; + VArraySpan span1 = varray; + EXPECT_TRUE(span1.is_empty()); + VArraySpan span2 = std::move(span1); + EXPECT_TRUE(span2.is_empty()); + } + { + VMutableArray varray; + MutableVArraySpan span1 = varray; + EXPECT_TRUE(span1.is_empty()); + MutableVArraySpan span2 = std::move(span1); + EXPECT_TRUE(span2.is_empty()); + } + { + GVArray varray; + GVArraySpan span1 = varray; + EXPECT_TRUE(span1.is_empty()); + GVArraySpan span2 = std::move(span1); + EXPECT_TRUE(span2.is_empty()); + } + { + GVMutableArray varray; + GMutableVArraySpan span1 = varray; + EXPECT_TRUE(span1.is_empty()); + GMutableVArraySpan span2 = std::move(span1); + EXPECT_TRUE(span2.is_empty()); + } +} + } // namespace blender::tests -- cgit v1.2.3 From 4e9e44ad28fc7a48e0d74a9f752847eb7be4e662 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Thu, 7 Jul 2022 19:27:30 +0200 Subject: Cleanup: improve asserts in generic span --- source/blender/blenlib/BLI_generic_span.hh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/blender/blenlib/BLI_generic_span.hh b/source/blender/blenlib/BLI_generic_span.hh index 0a40201634a..143ab235d2e 100644 --- a/source/blender/blenlib/BLI_generic_span.hh +++ b/source/blender/blenlib/BLI_generic_span.hh @@ -28,7 +28,8 @@ class GSpan { { BLI_assert(size >= 0); BLI_assert(buffer != nullptr || size == 0); - BLI_assert(type->pointer_has_valid_alignment(buffer)); + BLI_assert(size == 0 || type != nullptr); + BLI_assert(type == nullptr || type->pointer_has_valid_alignment(buffer)); } GSpan(const CPPType &type, const void *buffer, int64_t size) : GSpan(&type, buffer, size) @@ -119,7 +120,8 @@ class GMutableSpan { { BLI_assert(size >= 0); BLI_assert(buffer != nullptr || size == 0); - BLI_assert(type->pointer_has_valid_alignment(buffer)); + BLI_assert(size == 0 || type != nullptr); + BLI_assert(type == nullptr || type->pointer_has_valid_alignment(buffer)); } GMutableSpan(const CPPType &type, void *buffer, int64_t size) : GMutableSpan(&type, buffer, size) -- cgit v1.2.3 From 52fa0c4251e7c92b2df665e9e3c150a54066389f Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 7 Jul 2022 13:43:13 -0500 Subject: Cleanup: Remove unused variable --- source/blender/makesrna/intern/rna_meta.c | 1 - 1 file changed, 1 deletion(-) diff --git a/source/blender/makesrna/intern/rna_meta.c b/source/blender/makesrna/intern/rna_meta.c index 898208e0ba1..75188f29fac 100644 --- a/source/blender/makesrna/intern/rna_meta.c +++ b/source/blender/makesrna/intern/rna_meta.c @@ -84,7 +84,6 @@ static void rna_MetaBall_redraw_data(Main *UNUSED(bmain), Scene *UNUSED(scene), static void rna_MetaBall_update_data(Main *bmain, Scene *UNUSED(scene), PointerRNA *ptr) { MetaBall *mb = (MetaBall *)ptr->owner_id; - Object *ob; /* NOTE: The check on the number of users allows to avoid many repetitive (slow) updates in some * cases, like e.g. importers. Calling `BKE_mball_properties_copy` on an obdata with no users -- cgit v1.2.3 From fc06b4c03356b5b9a272df8a2b96b6c4f9254ee7 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Thu, 7 Jul 2022 20:51:32 +0200 Subject: Fix T99332: resize video in image editor does not update correctly Use the image user from the image editor to correctly get the frame in the operators. Based on patch by Nicolas (john-g-h-doe) with changes by me. Differential Revision: https://developer.blender.org/D15380 --- source/blender/editors/space_image/image_ops.c | 37 +++++++++++++++----------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/source/blender/editors/space_image/image_ops.c b/source/blender/editors/space_image/image_ops.c index 49420e9a2a1..9b622d34176 100644 --- a/source/blender/editors/space_image/image_ops.c +++ b/source/blender/editors/space_image/image_ops.c @@ -197,10 +197,17 @@ static ImageUser *image_user_from_context(const bContext *C) return (sima) ? &sima->iuser : NULL; } -static ImageUser image_user_from_active_tile(Image *ima) +static ImageUser image_user_from_context_and_active_tile(const bContext *C, Image *ima) { + /* Try to get image user from contexrt if available, otherwise use default. */ + ImageUser *iuser_context = image_user_from_context(C); ImageUser iuser; - BKE_imageuser_default(&iuser); + if (iuser_context) { + iuser = *iuser_context; + } + else { + BKE_imageuser_default(&iuser); + } /* Use the file associated with the active tile. Otherwise use the first tile. */ if (ima && ima->source == IMA_SRC_TILED) { @@ -233,7 +240,7 @@ static bool image_from_context_has_data_poll(bContext *C) static bool image_from_context_has_data_poll_active_tile(bContext *C) { Image *ima = image_from_context(C); - ImageUser iuser = image_user_from_active_tile(ima); + ImageUser iuser = image_user_from_context_and_active_tile(C, ima); return BKE_image_has_ibuf(ima, &iuser); } @@ -1602,7 +1609,7 @@ static int image_file_browse_invoke(bContext *C, wmOperator *op, const wmEvent * } } else if (ima->source == IMA_SRC_TILED) { - ImageUser iuser = image_user_from_active_tile(ima); + ImageUser iuser = image_user_from_context_and_active_tile(C, ima); BKE_image_user_file_path(&iuser, ima, filepath); } @@ -2698,7 +2705,7 @@ void IMAGE_OT_new(wmOperatorType *ot) static int image_flip_exec(bContext *C, wmOperator *op) { Image *ima = image_from_context(C); - ImageUser iuser = image_user_from_active_tile(ima); + ImageUser iuser = image_user_from_context_and_active_tile(C, ima); ImBuf *ibuf = BKE_image_acquire_ibuf(ima, &iuser, NULL); SpaceImage *sima = CTX_wm_space_image(C); const bool is_paint = ((sima != NULL) && (sima->mode == SI_MODE_PAINT)); @@ -2716,7 +2723,7 @@ static int image_flip_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } - ED_image_undo_push_begin_with_image(op->type->name, ima, ibuf, &sima->iuser); + ED_image_undo_push_begin_with_image(op->type->name, ima, ibuf, &iuser); if (is_paint) { ED_imapaint_clear_partial_redraw(); @@ -2819,7 +2826,7 @@ void IMAGE_OT_flip(wmOperatorType *ot) static int image_invert_exec(bContext *C, wmOperator *op) { Image *ima = image_from_context(C); - ImageUser iuser = image_user_from_active_tile(ima); + ImageUser iuser = image_user_from_context_and_active_tile(C, ima); ImBuf *ibuf = BKE_image_acquire_ibuf(ima, &iuser, NULL); SpaceImage *sima = CTX_wm_space_image(C); const bool is_paint = ((sima != NULL) && (sima->mode == SI_MODE_PAINT)); @@ -2837,7 +2844,7 @@ static int image_invert_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - ED_image_undo_push_begin_with_image(op->type->name, ima, ibuf, &sima->iuser); + ED_image_undo_push_begin_with_image(op->type->name, ima, ibuf, &iuser); if (is_paint) { ED_imapaint_clear_partial_redraw(); @@ -2943,7 +2950,7 @@ void IMAGE_OT_invert(wmOperatorType *ot) static int image_scale_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { Image *ima = image_from_context(C); - ImageUser iuser = image_user_from_active_tile(ima); + ImageUser iuser = image_user_from_context_and_active_tile(C, ima); PropertyRNA *prop = RNA_struct_find_property(op->ptr, "size"); if (!RNA_property_is_set(op->ptr, prop)) { ImBuf *ibuf = BKE_image_acquire_ibuf(ima, &iuser, NULL); @@ -2957,7 +2964,7 @@ static int image_scale_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED static int image_scale_exec(bContext *C, wmOperator *op) { Image *ima = image_from_context(C); - ImageUser iuser = image_user_from_active_tile(ima); + ImageUser iuser = image_user_from_context_and_active_tile(C, ima); ImBuf *ibuf = BKE_image_acquire_ibuf(ima, &iuser, NULL); SpaceImage *sima = CTX_wm_space_image(C); const bool is_paint = ((sima != NULL) && (sima->mode == SI_MODE_PAINT)); @@ -2982,7 +2989,7 @@ static int image_scale_exec(bContext *C, wmOperator *op) RNA_property_int_set_array(op->ptr, prop, size); } - ED_image_undo_push_begin_with_image(op->type->name, ima, ibuf, &sima->iuser); + ED_image_undo_push_begin_with_image(op->type->name, ima, ibuf, &iuser); ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID; IMB_scaleImBuf(ibuf, size[0], size[1]); @@ -3464,10 +3471,10 @@ void IMAGE_OT_cycle_render_slot(wmOperatorType *ot) static int image_clear_render_slot_exec(bContext *C, wmOperator *UNUSED(op)) { - SpaceImage *sima = CTX_wm_space_image(C); Image *ima = image_from_context(C); + ImageUser *iuser = image_user_from_context(C); - if (!BKE_image_clear_renderslot(ima, &sima->iuser, ima->render_slot)) { + if (!BKE_image_clear_renderslot(ima, iuser, ima->render_slot)) { return OPERATOR_CANCELLED; } @@ -3532,10 +3539,10 @@ void IMAGE_OT_add_render_slot(wmOperatorType *ot) static int image_remove_render_slot_exec(bContext *C, wmOperator *UNUSED(op)) { - SpaceImage *sima = CTX_wm_space_image(C); Image *ima = image_from_context(C); + ImageUser *iuser = image_user_from_context(C); - if (!BKE_image_remove_renderslot(ima, &sima->iuser, ima->render_slot)) { + if (!BKE_image_remove_renderslot(ima, iuser, ima->render_slot)) { return OPERATOR_CANCELLED; } -- cgit v1.2.3 From b9c0eed206b0d7d1b6af6809c6d2cb6c2187bcc8 Mon Sep 17 00:00:00 2001 From: Harley Acheson Date: Thu, 7 Jul 2022 12:59:16 -0700 Subject: BLF: Add Support for Variable Fonts Add support for Variable/Multiple Master font features. These are fonts that contain a range of design variations along multiple axes. This contains no client-facing options. See D12977 for details and examples Differential Revision: https://developer.blender.org/D12977 Reviewed by Brecht Van Lommel --- source/blender/blenfont/intern/blf_font.c | 15 +- source/blender/blenfont/intern/blf_glyph.c | 181 ++++++++++++++++++++- .../blender/blenfont/intern/blf_internal_types.h | 26 +++ 3 files changed, 214 insertions(+), 8 deletions(-) diff --git a/source/blender/blenfont/intern/blf_font.c b/source/blender/blenfont/intern/blf_font.c index 3e2927d581e..038e73cc928 100644 --- a/source/blender/blenfont/intern/blf_font.c +++ b/source/blender/blenfont/intern/blf_font.c @@ -18,7 +18,8 @@ #include FT_FREETYPE_H #include FT_GLYPH_H -#include FT_TRUETYPE_TABLES_H /* For TT_OS2 */ +#include FT_TRUETYPE_TABLES_H /* For TT_OS2 */ +#include FT_MULTIPLE_MASTERS_H /* Variable font support. */ #include "MEM_guardedalloc.h" @@ -1285,6 +1286,10 @@ FontBLF *blf_font_new(const char *name, const char *filepath) MEM_freeN(mfile); } + if (FT_HAS_MULTIPLE_MASTERS(font->face)) { + FT_Get_MM_Var(font->face, &(font->variations)); + } + font->name = BLI_strdup(name); font->filepath = BLI_strdup(filepath); blf_font_fill(font); @@ -1351,6 +1356,10 @@ FontBLF *blf_font_new_from_mem(const char *name, const unsigned char *mem, int m return NULL; } + if (FT_HAS_MULTIPLE_MASTERS(font->face)) { + FT_Get_MM_Var(font->face, &(font->variations)); + } + font->name = BLI_strdup(name); font->filepath = NULL; blf_font_fill(font); @@ -1365,6 +1374,10 @@ void blf_font_free(FontBLF *font) MEM_freeN(font->kerning_cache); } + if (font->variations) { + FT_Done_MM_Var(ft_lib, font->variations); + } + FT_Done_Face(font->face); if (font->filepath) { MEM_freeN(font->filepath); diff --git a/source/blender/blenfont/intern/blf_glyph.c b/source/blender/blenfont/intern/blf_glyph.c index 2eb43e3df43..4db083366c3 100644 --- a/source/blender/blenfont/intern/blf_glyph.c +++ b/source/blender/blenfont/intern/blf_glyph.c @@ -18,7 +18,8 @@ #include FT_GLYPH_H #include FT_OUTLINE_H #include FT_BITMAP_H -#include FT_ADVANCES_H /* For FT_Get_Advance. */ +#include FT_ADVANCES_H /* For FT_Get_Advance. */ +#include FT_MULTIPLE_MASTERS_H /* Variable font support. */ #include "MEM_guardedalloc.h" @@ -64,7 +65,9 @@ static GlyphCacheBLF *blf_glyph_cache_find(FontBLF *font, float size, unsigned i GlyphCacheBLF *gc = (GlyphCacheBLF *)font->cache.first; while (gc) { if (gc->size == size && gc->dpi == dpi && (gc->bold == ((font->flags & BLF_BOLD) != 0)) && - (gc->italic == ((font->flags & BLF_ITALIC) != 0))) { + (gc->italic == ((font->flags & BLF_ITALIC) != 0)) && + (gc->char_weight == font->char_weight) && (gc->char_slant == font->char_slant) && + (gc->char_width == font->char_width) && (gc->char_spacing == font->char_spacing)) { return gc; } gc = gc->next; @@ -82,6 +85,10 @@ static GlyphCacheBLF *blf_glyph_cache_new(FontBLF *font) gc->dpi = font->dpi; gc->bold = ((font->flags & BLF_BOLD) != 0); gc->italic = ((font->flags & BLF_ITALIC) != 0); + gc->char_weight = font->char_weight; + gc->char_slant = font->char_slant; + gc->char_width = font->char_width; + gc->char_spacing = font->char_spacing; memset(gc->glyph_ascii_table, 0, sizeof(gc->glyph_ascii_table)); memset(gc->bucket, 0, sizeof(gc->bucket)); @@ -673,6 +680,96 @@ static bool blf_glyph_render_bitmap(FontBLF *font, FT_GlyphSlot glyph) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Variations (Multiple Masters) support + * \{ */ + +/** + * Return a design axis that matches an identifying tag. + * + * \param variations: Variation descriptors from `FT_Get_MM_Var`. + * \param tag: Axis tag (4-character string as uint), like 'wght' + * \param axis_index: returns index of axis in variations array. + */ +static FT_Var_Axis *blf_var_axis_by_tag(FT_MM_Var *variations, uint tag, int *axis_index) +{ + *axis_index = -1; + if (!variations) { + return NULL; + } + for (int i = 0; i < (int)variations->num_axis; i++) { + if (variations->axis[i].tag == tag) { + *axis_index = i; + return &(variations->axis)[i]; + break; + } + } + return NULL; +} + +/** + * Convert a float factor to a fixed-point design coordinate. + * + * \param axis: Pointer to a design space axis structure. + * \param factor: -1 to 1 with 0 meaning "default" + */ +static FT_Fixed blf_factor_to_coordinate(FT_Var_Axis *axis, float factor) +{ + FT_Fixed value = axis->def; + if (factor > 0) { + /* Map 0-1 to axis->def - axis->maximum */ + value += (FT_Fixed)((double)(axis->maximum - axis->def) * factor); + } + else if (factor < 0) { + /* Map -1-0 to axis->minimum - axis->def */ + value += (FT_Fixed)((double)(axis->def - axis->minimum) * factor); + } + return value; +} + +/** + * Alter a face variation axis by a factor + * + * \param coords: array of design coordinates, per axis. + * \param tag: Axis tag (4-character string as uint), like 'wght' + * \param factor: -1 to 1 with 0 meaning "default" + */ +static bool blf_glyph_set_variation_normalized(FontBLF *font, + FT_Fixed coords[], + uint tag, + float factor) +{ + int axis_index; + FT_Var_Axis *axis = blf_var_axis_by_tag(font->variations, tag, &axis_index); + if (axis && (axis_index < BLF_VARIATIONS_MAX)) { + coords[axis_index] = blf_factor_to_coordinate(axis, factor); + return true; + } + return false; +} + +/** + * Set a face variation axis to an exact float value + * + * \param coords: array of design coordinates, per axis. + * \param tag: Axis tag (4-character string as uint), like 'opsz' + * \param value: New float value. Converted to 16.16 and clamped within allowed range. + */ +static bool blf_glyph_set_variation_float(FontBLF *font, FT_Fixed coords[], uint tag, float value) +{ + int axis_index; + FT_Var_Axis *axis = blf_var_axis_by_tag(font->variations, tag, &axis_index); + if (axis && (axis_index < BLF_VARIATIONS_MAX)) { + FT_Fixed int_value = to_16dot16(value); + CLAMP(int_value, axis->minimum, axis->maximum); + coords[axis_index] = int_value; + return true; + } + return false; +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Glyph Transformations * \{ */ @@ -682,9 +779,7 @@ static bool blf_glyph_render_bitmap(FontBLF *font, FT_GlyphSlot glyph) * * \param factor: -1 (min stroke width) <= 0 (normal) => 1 (max boldness). */ -static bool UNUSED_FUNCTION(blf_glyph_transform_weight)(FT_GlyphSlot glyph, - float factor, - bool monospaced) +static bool blf_glyph_transform_weight(FT_GlyphSlot glyph, float factor, bool monospaced) { if (glyph->format == FT_GLYPH_FORMAT_OUTLINE) { /* Fake bold if the font does not have this variable axis. */ @@ -713,7 +808,7 @@ static bool UNUSED_FUNCTION(blf_glyph_transform_weight)(FT_GlyphSlot glyph, * * \note that left-leaning italics are possible in some RTL writing systems. */ -static bool UNUSED_FUNCTION(blf_glyph_transform_slant)(FT_GlyphSlot glyph, float factor) +static bool blf_glyph_transform_slant(FT_GlyphSlot glyph, float factor) { if (glyph->format == FT_GLYPH_FORMAT_OUTLINE) { FT_Matrix transform = {to_16dot16(1), to_16dot16(factor / 2.0f), 0, to_16dot16(1)}; @@ -728,7 +823,7 @@ static bool UNUSED_FUNCTION(blf_glyph_transform_slant)(FT_GlyphSlot glyph, float * * \param factor: -1 (min width) <= 0 (normal) => 1 (max width). */ -static bool UNUSED_FUNCTION(blf_glyph_transform_width)(FT_GlyphSlot glyph, float factor) +static bool blf_glyph_transform_width(FT_GlyphSlot glyph, float factor) { if (glyph->format == FT_GLYPH_FORMAT_OUTLINE) { float scale = (factor * 0.4f) + 1.0f; /* 0.6f - 1.4f */ @@ -740,6 +835,21 @@ static bool UNUSED_FUNCTION(blf_glyph_transform_width)(FT_GlyphSlot glyph, float return false; } +/** + * Change glyph advance to alter letter-spacing (tracking). + * + * \param factor: -1 (min tightness) <= 0 (normal) => 1 (max looseness). + */ +static bool blf_glyph_transform_spacing(FT_GlyphSlot glyph, float factor) +{ + if (glyph->advance.x > 0) { + const int size = glyph->face->size->metrics.height; + glyph->advance.x += (FT_Pos)(factor * (float)size / 6.0f); + return true; + } + return false; +} + /** * Transform glyph to fit nicely within a fixed column width. */ @@ -791,6 +901,48 @@ static FT_GlyphSlot blf_glyph_render(FontBLF *settings_font, glyph_font->dpi = settings_font->dpi; } + /* We need to keep track if changes are still needed. */ + bool weight_done = false; + bool slant_done = false; + bool width_done = false; + bool spacing_done = false; + + /* 70% of maximum weight results in the same amount of boldness and horizontal + * expansion as the bold version (DejaVuSans-Bold.ttf) of our default font. + * Worth reevaluating if we change default font. */ + float weight = (settings_font->flags & BLF_BOLD) ? 0.7f : settings_font->char_weight; + + /* 37.5% of maximum rightward slant results in 6 degree slope, matching italic + * version (DejaVuSans-Oblique.ttf) of our current font. But a nice median when + * checking others. Worth reevaluating if we change default font. We could also + * narrow the glyph slightly as most italics do, but this one does not. */ + float slant = (settings_font->flags & BLF_ITALIC) ? 0.375f : settings_font->char_slant; + + float width = settings_font->char_width; + float spacing = settings_font->char_spacing; + + /* Font variations need to be set before glyph loading. Even if new value is zero. */ + + if (glyph_font->variations) { + FT_Fixed coords[BLF_VARIATIONS_MAX]; + /* Load current design coordinates. */ + FT_Get_Var_Design_Coordinates(glyph_font->face, BLF_VARIATIONS_MAX, &coords[0]); + /* Update design coordinates with new values. */ + weight_done = blf_glyph_set_variation_normalized( + glyph_font, coords, blf_variation_axis_weight, weight); + slant_done = blf_glyph_set_variation_normalized( + glyph_font, coords, blf_variation_axis_slant, slant); + width_done = blf_glyph_set_variation_normalized( + glyph_font, coords, blf_variation_axis_width, width); + spacing_done = blf_glyph_set_variation_normalized( + glyph_font, coords, blf_variation_axis_spacing, spacing); + /* Optical size, if available, is set to current font size. */ + blf_glyph_set_variation_float( + glyph_font, coords, blf_variation_axis_optsize, settings_font->size); + /* Save updated design coordinates. */ + FT_Set_Var_Design_Coordinates(glyph_font->face, BLF_VARIATIONS_MAX, &coords[0]); + } + FT_GlyphSlot glyph = blf_glyph_load(glyph_font, glyph_index); if (!glyph) { return NULL; @@ -800,6 +952,21 @@ static FT_GlyphSlot blf_glyph_render(FontBLF *settings_font, blf_glyph_transform_monospace(glyph, BLI_wcwidth((char32_t)charcode) * fixed_width); } + /* Fallback glyph transforms, but only if required and not yet done. */ + + if (weight != 0.0f && !weight_done) { + blf_glyph_transform_weight(glyph, weight, glyph->face->face_flags & FT_FACE_FLAG_FIXED_WIDTH); + } + if (slant != 0.0f && !slant_done) { + blf_glyph_transform_slant(glyph, slant); + } + if (width != 0.0f && !width_done) { + blf_glyph_transform_width(glyph, width); + } + if (spacing != 0.0f && !spacing_done) { + blf_glyph_transform_spacing(glyph, spacing); + } + if (blf_glyph_render_bitmap(glyph_font, glyph)) { return glyph; } diff --git a/source/blender/blenfont/intern/blf_internal_types.h b/source/blender/blenfont/intern/blf_internal_types.h index 998093dae70..5b55f4af0b8 100644 --- a/source/blender/blenfont/intern/blf_internal_types.h +++ b/source/blender/blenfont/intern/blf_internal_types.h @@ -10,6 +10,19 @@ #include "GPU_texture.h" #include "GPU_vertex_buffer.h" +#include FT_MULTIPLE_MASTERS_H /* Variable font support. */ + +#define BLF_VARIATIONS_MAX 16 /* Maximum variation axes per font. */ + +#define MAKE_DVAR_TAG(a, b, c, d) \ + (((uint32_t)a << 24u) | ((uint32_t)b << 16u) | ((uint32_t)c << 8u) | ((uint32_t)d)) + +#define blf_variation_axis_weight MAKE_DVAR_TAG('w', 'g', 'h', 't') /* 'wght' weight axis. */ +#define blf_variation_axis_slant MAKE_DVAR_TAG('s', 'l', 'n', 't') /* 'slnt' slant axis. */ +#define blf_variation_axis_width MAKE_DVAR_TAG('w', 'd', 't', 'h') /* 'wdth' width axis. */ +#define blf_variation_axis_spacing MAKE_DVAR_TAG('s', 'p', 'a', 'c') /* 'spac' spacing axis. */ +#define blf_variation_axis_optsize MAKE_DVAR_TAG('o', 'p', 's', 'z') /* 'opsz' optical size. */ + /* -------------------------------------------------------------------- */ /** \name Sub-Pixel Offset & Utilities * @@ -125,6 +138,10 @@ typedef struct GlyphCacheBLF { /* and DPI. */ unsigned int dpi; + float char_weight; + float char_slant; + float char_width; + float char_spacing; bool bold; bool italic; @@ -275,6 +292,15 @@ typedef struct FontBLF { /* font size. */ float size; + /* Axes data for Adobe MM, TrueType GX, or OpenType variation fonts. */ + FT_MM_Var *variations; + + /* Character variation; 0=default, -1=min, +1=max. */ + float char_weight; + float char_slant; + float char_width; + float char_spacing; + /* max texture size. */ int tex_size_max; -- cgit v1.2.3 From 56bf92f0f6df8684c5ffb63ffa7218322eedf574 Mon Sep 17 00:00:00 2001 From: Harley Acheson Date: Thu, 7 Jul 2022 14:29:37 -0700 Subject: Cleanup: Calm GCC Conversion Warning Commit b9c0eed206b0 introduced a GCC conversion warning because of an assignment of a long int value to an int. Own Code --- source/blender/blenfont/intern/blf_glyph.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/blenfont/intern/blf_glyph.c b/source/blender/blenfont/intern/blf_glyph.c index 4db083366c3..103919e86f2 100644 --- a/source/blender/blenfont/intern/blf_glyph.c +++ b/source/blender/blenfont/intern/blf_glyph.c @@ -843,7 +843,7 @@ static bool blf_glyph_transform_width(FT_GlyphSlot glyph, float factor) static bool blf_glyph_transform_spacing(FT_GlyphSlot glyph, float factor) { if (glyph->advance.x > 0) { - const int size = glyph->face->size->metrics.height; + const long int size = glyph->face->size->metrics.height; glyph->advance.x += (FT_Pos)(factor * (float)size / 6.0f); return true; } -- cgit v1.2.3 From 9ef37369592ee74a4e675ece6bdd37b9aca3f9ad Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 8 Jul 2022 09:09:59 +1000 Subject: Cleanup: format --- release/scripts/startup/bl_ui/space_outliner.py | 2 +- .../engines/overlay/shaders/overlay_sculpt_curves_selection_vert.glsl | 1 - source/blender/editors/space_graph/graph_utils.c | 3 ++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/release/scripts/startup/bl_ui/space_outliner.py b/release/scripts/startup/bl_ui/space_outliner.py index 18057de6767..011a430a1ec 100644 --- a/release/scripts/startup/bl_ui/space_outliner.py +++ b/release/scripts/startup/bl_ui/space_outliner.py @@ -55,7 +55,7 @@ class OUTLINER_HT_header(Header): layout.operator("outliner.collection_new", text="", icon='COLLECTION_NEW').nested = True elif display_mode == 'ORPHAN_DATA': - layout.operator("outliner.orphans_purge", text="Purge").do_recursive=True + layout.operator("outliner.orphans_purge", text="Purge").do_recursive = True elif space.display_mode == 'DATA_API': layout.separator() diff --git a/source/blender/draw/engines/overlay/shaders/overlay_sculpt_curves_selection_vert.glsl b/source/blender/draw/engines/overlay/shaders/overlay_sculpt_curves_selection_vert.glsl index 9ed959b5ea1..7be3c8e6dfb 100644 --- a/source/blender/draw/engines/overlay/shaders/overlay_sculpt_curves_selection_vert.glsl +++ b/source/blender/draw/engines/overlay/shaders/overlay_sculpt_curves_selection_vert.glsl @@ -2,7 +2,6 @@ #pragma BLENDER_REQUIRE(common_view_clipping_lib.glsl) #pragma BLENDER_REQUIRE(common_view_lib.glsl) - float retrieve_selection() { if (is_point_domain) { diff --git a/source/blender/editors/space_graph/graph_utils.c b/source/blender/editors/space_graph/graph_utils.c index 51af795893f..82067661d57 100644 --- a/source/blender/editors/space_graph/graph_utils.c +++ b/source/blender/editors/space_graph/graph_utils.c @@ -81,7 +81,8 @@ void ED_drivers_editor_init(bContext *C, ScrArea *area) bAnimListElem *get_active_fcurve_channel(bAnimContext *ac) { ListBase anim_data = {NULL, NULL}; - int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_ACTIVE | ANIMFILTER_FCURVESONLY); + int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_ACTIVE | + ANIMFILTER_FCURVESONLY); size_t items = ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* We take the first F-Curve only, since some other ones may have had 'active' flag set -- cgit v1.2.3 From 03173d63c01aede48f361ce83f53e5e9d8236c05 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 8 Jul 2022 09:10:30 +1000 Subject: Cleanup: spelling in comments Also move mis-placed doc-string. --- source/blender/blenfont/intern/blf_glyph.c | 4 ++-- source/blender/blenkernel/BKE_armature.h | 2 +- source/blender/blenkernel/BKE_mball.h | 3 ++- source/blender/blenkernel/BKE_object.h | 9 ++++++--- source/blender/blenkernel/intern/image_save.cc | 2 +- source/blender/blenkernel/intern/mball.c | 17 ++++++++++------- source/blender/editors/space_image/image_ops.c | 2 +- source/blender/makesdna/DNA_windowmanager_types.h | 2 +- source/blender/python/intern/bpy_driver.c | 2 +- 9 files changed, 25 insertions(+), 18 deletions(-) diff --git a/source/blender/blenfont/intern/blf_glyph.c b/source/blender/blenfont/intern/blf_glyph.c index 103919e86f2..215f79e6795 100644 --- a/source/blender/blenfont/intern/blf_glyph.c +++ b/source/blender/blenfont/intern/blf_glyph.c @@ -908,12 +908,12 @@ static FT_GlyphSlot blf_glyph_render(FontBLF *settings_font, bool spacing_done = false; /* 70% of maximum weight results in the same amount of boldness and horizontal - * expansion as the bold version (DejaVuSans-Bold.ttf) of our default font. + * expansion as the bold version `DejaVuSans-Bold.ttf` of our default font. * Worth reevaluating if we change default font. */ float weight = (settings_font->flags & BLF_BOLD) ? 0.7f : settings_font->char_weight; /* 37.5% of maximum rightward slant results in 6 degree slope, matching italic - * version (DejaVuSans-Oblique.ttf) of our current font. But a nice median when + * version `DejaVuSans-Oblique.ttf` of our current font. But a nice median when * checking others. Worth reevaluating if we change default font. We could also * narrow the glyph slightly as most italics do, but this one does not. */ float slant = (settings_font->flags & BLF_ITALIC) ? 0.375f : settings_font->char_slant; diff --git a/source/blender/blenkernel/BKE_armature.h b/source/blender/blenkernel/BKE_armature.h index 98f9f8e3588..ee0f41937e2 100644 --- a/source/blender/blenkernel/BKE_armature.h +++ b/source/blender/blenkernel/BKE_armature.h @@ -159,7 +159,7 @@ struct BoundBox *BKE_armature_boundbox_get(struct Object *ob); * Visual elements such as the envelopes radius & bendy-bone spline segments are *not* included, * making this not so useful for viewport culling. * - * \param use_empty_drawtype: When enabled, the draw type of empty custom-objects is tagen into + * \param use_empty_drawtype: When enabled, the draw type of empty custom-objects is taken into * account when calculating the bounds. */ void BKE_pchan_minmax(const struct Object *ob, diff --git a/source/blender/blenkernel/BKE_mball.h b/source/blender/blenkernel/BKE_mball.h index 5a4988c7a5f..a23d010b51f 100644 --- a/source/blender/blenkernel/BKE_mball.h +++ b/source/blender/blenkernel/BKE_mball.h @@ -75,7 +75,8 @@ float *BKE_mball_make_orco(struct Object *ob, struct ListBase *dispbase); * When some properties (wire-size, threshold, update flags) of meta-ball are changed, then this * properties are copied to all meta-balls in same "group" (meta-balls with same base name: * `MBall`, `MBall.001`, `MBall.002`, etc). The most important is to copy properties to the base - * meta-ball, because this meta-ball influences polygonization of meta-balls. */ + * meta-ball, because this meta-ball influences polygonization of meta-balls. + */ void BKE_mball_properties_copy(struct Main *bmain, struct MetaBall *active_metaball); bool BKE_mball_minmax_ex( diff --git a/source/blender/blenkernel/BKE_object.h b/source/blender/blenkernel/BKE_object.h index 96abea0e5ee..f0eb16a819d 100644 --- a/source/blender/blenkernel/BKE_object.h +++ b/source/blender/blenkernel/BKE_object.h @@ -366,9 +366,6 @@ void BKE_object_empty_draw_type_set(struct Object *ob, int value); void BKE_object_boundbox_calc_from_mesh(struct Object *ob, const struct Mesh *me_eval); bool BKE_object_boundbox_calc_from_evaluated_geometry(struct Object *ob); -/** - * Calculate visual bounds from an empty objects draw-type. - */ void BKE_object_minmax(struct Object *ob, float r_min[3], float r_max[3], bool use_hidden); bool BKE_object_minmax_dupli(struct Depsgraph *depsgraph, struct Scene *scene, @@ -376,6 +373,12 @@ bool BKE_object_minmax_dupli(struct Depsgraph *depsgraph, float r_min[3], float r_max[3], bool use_hidden); +/** + * Calculate visual bounds from an empty objects draw-type. + * + * \note This is not part of the calculation used by #BKE_object_boundbox_get + * as these bounds represent the extents of visual guides (use for viewport culling for e.g.) + */ bool BKE_object_minmax_empty_drawtype(const struct Object *ob, float r_min[3], float r_max[3]); /** diff --git a/source/blender/blenkernel/intern/image_save.cc b/source/blender/blenkernel/intern/image_save.cc index 07ffc35907a..3f6f81845e2 100644 --- a/source/blender/blenkernel/intern/image_save.cc +++ b/source/blender/blenkernel/intern/image_save.cc @@ -290,7 +290,7 @@ static void image_save_post(ReportList *reports, BLI_strncpy(ibuf->name, filepath, sizeof(ibuf->name)); } - /* The tiled image codepath must call this on its own. */ + /* The tiled image code-path must call this on its own. */ if (ima->source != IMA_SRC_TILED) { image_save_update_filepath(ima, filepath, opts); } diff --git a/source/blender/blenkernel/intern/mball.c b/source/blender/blenkernel/intern/mball.c index 819bbde6556..2a1c940493c 100644 --- a/source/blender/blenkernel/intern/mball.c +++ b/source/blender/blenkernel/intern/mball.c @@ -434,13 +434,16 @@ static void mball_data_properties_copy(MetaBall *mb_dst, MetaBall *mb_src) void BKE_mball_properties_copy(Main *bmain, MetaBall *metaball_src) { - /* WARNING: This code does not cover all potential corner-cases. E.g. if: - * | Object | ObData | - * | ---------- | ---------- | - * | Meta_A | Meta_A | - * | Meta_A.001 | Meta_A.001 | - * | Meta_B | Meta_A | - * | Meta_B.001 | Meta_B.001 | + /** + * WARNING: This code does not cover all potential corner-cases. E.g. if: + *
+   * |   Object   |   ObData   |
+   * | ---------- | ---------- |
+   * | Meta_A     | Meta_A     |
+   * | Meta_A.001 | Meta_A.001 |
+   * | Meta_B     | Meta_A     |
+   * | Meta_B.001 | Meta_B.001 |
+   * 
* * Calling this function with `metaball_src` being `Meta_A.001` will update `Meta_A`, but NOT * `Meta_B.001`. So in the 'Meta_B' family, the two metaballs will have unmatching settings now. diff --git a/source/blender/editors/space_image/image_ops.c b/source/blender/editors/space_image/image_ops.c index 9b622d34176..49489b696ef 100644 --- a/source/blender/editors/space_image/image_ops.c +++ b/source/blender/editors/space_image/image_ops.c @@ -199,7 +199,7 @@ static ImageUser *image_user_from_context(const bContext *C) static ImageUser image_user_from_context_and_active_tile(const bContext *C, Image *ima) { - /* Try to get image user from contexrt if available, otherwise use default. */ + /* Try to get image user from context if available, otherwise use default. */ ImageUser *iuser_context = image_user_from_context(C); ImageUser iuser; if (iuser_context) { diff --git a/source/blender/makesdna/DNA_windowmanager_types.h b/source/blender/makesdna/DNA_windowmanager_types.h index 5e741508603..a3d9b5fc7b6 100644 --- a/source/blender/makesdna/DNA_windowmanager_types.h +++ b/source/blender/makesdna/DNA_windowmanager_types.h @@ -240,7 +240,7 @@ typedef struct wmWindow { /** Active view layer displayed in this window. */ char view_layer_name[64]; /** The workspace may temporarily override the window's scene with scene pinning. This is the - * "overriden" or "default" scene to restore when entering a workspace with no scene pinned. */ + * "overridden" or "default" scene to restore when entering a workspace with no scene pinned. */ struct Scene *unpinned_scene; struct WorkSpaceInstanceHook *workspace_hook; diff --git a/source/blender/python/intern/bpy_driver.c b/source/blender/python/intern/bpy_driver.c index 9df3ea9b318..cc64af2a489 100644 --- a/source/blender/python/intern/bpy_driver.c +++ b/source/blender/python/intern/bpy_driver.c @@ -275,7 +275,7 @@ void BPY_driver_reset(void) } /** - * Error return function for #BPY_eval_pydriver. + * Error return function for #BPY_driver_exec. * * \param anim_rna: Used to show the target when printing the error to give additional context. */ -- cgit v1.2.3 From b98a937db64c365de889adcc084248716607521d Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 8 Jul 2022 11:09:47 +1000 Subject: Fix T99364: Unable to select bones when custom shape display is disabled Regression in [0] which revealed an error in [1]. Logic for pose channel custom transform ignored ARM_NO_CUSTOM. [0]: 3267c91b4d5caab7da8aef071a446dd2e86f86a9 [1]: c3fef001ee926fc183255b623f56da9fc5fcbb73 --- source/blender/blenkernel/intern/armature.c | 11 ++++++----- source/blender/makesdna/DNA_action_types.h | 14 ++++++++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/source/blender/blenkernel/intern/armature.c b/source/blender/blenkernel/intern/armature.c index 622ecde6a91..f29074c827c 100644 --- a/source/blender/blenkernel/intern/armature.c +++ b/source/blender/blenkernel/intern/armature.c @@ -2668,20 +2668,21 @@ void BKE_pchan_minmax(const Object *ob, float r_max[3]) { const bArmature *arm = ob->data; - const bPoseChannel *pchan_tx = (pchan->custom && pchan->custom_tx) ? pchan->custom_tx : pchan; + Object *ob_custom = (arm->flag & ARM_NO_CUSTOM) ? NULL : pchan->custom; + const bPoseChannel *pchan_tx = (ob_custom && pchan->custom_tx) ? pchan->custom_tx : pchan; const BoundBox *bb_custom = NULL; BoundBox bb_custom_buf; - if ((pchan->custom) && !(arm->flag & ARM_NO_CUSTOM)) { + if (ob_custom) { float min[3], max[3]; - if (use_empty_drawtype && (pchan->custom->type == OB_EMPTY) && - BKE_object_minmax_empty_drawtype(pchan->custom, min, max)) { + if (use_empty_drawtype && (ob_custom->type == OB_EMPTY) && + BKE_object_minmax_empty_drawtype(ob_custom, min, max)) { memset(&bb_custom_buf, 0x0, sizeof(bb_custom_buf)); BKE_boundbox_init_from_minmax(&bb_custom_buf, min, max); bb_custom = &bb_custom_buf; } else { - bb_custom = BKE_object_boundbox_get(pchan->custom); + bb_custom = BKE_object_boundbox_get(ob_custom); } } diff --git a/source/blender/makesdna/DNA_action_types.h b/source/blender/makesdna/DNA_action_types.h index 516d3ce94f9..53e87e905b5 100644 --- a/source/blender/makesdna/DNA_action_types.h +++ b/source/blender/makesdna/DNA_action_types.h @@ -251,12 +251,18 @@ typedef struct bPoseChannel { /** Motion path cache for this bone. */ bMotionPath *mpath; - /** Draws custom object instead of default bone shape. */ + /** + * Draws custom object instead of default bone shape. + * + * \note For the purpose of user interaction (selection, display etc), + * it's important this value is treated as NULL when #ARM_NO_CUSTOM is set. + */ struct Object *custom; /** - * Odd feature, display with another bones transform. - * needed in rare cases for advanced rigs, - * since the alternative is highly complicated - campbell + * This is a specific feature to display with another bones transform. + * Needed in rare cases for advanced rigs, since alternative solutions are highly complicated. + * + * \note This depends #bPoseChannel.custom being set and the #ARM_NO_CUSTOM flag being unset. */ struct bPoseChannel *custom_tx; float custom_scale; /* Deprecated */ -- cgit v1.2.3 From c4b32f1b291a3c3447879175225a3664aeb0b7ef Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 7 Jul 2022 22:33:57 -0500 Subject: Cleanup: Move mesh legacy conversion to a separate file It's helpful to make the separation of legacy data formats explicit, because it declutters actively changed code and makes it clear which areas do not follow Blender's current design. In this case I separated the `MFace`/"tessface" conversion code into a separate blenkernel .cc file and header. This also makes refactoring to remove these functions simpler because they're easier to find. In the future, conversions to the `MLoopUV` type and `MVert` can be implemented here for the same reasons (see T95965). Differential Revision: https://developer.blender.org/D15396 --- source/blender/blenkernel/BKE_customdata.h | 24 +- source/blender/blenkernel/BKE_mesh.h | 61 -- .../blender/blenkernel/BKE_mesh_legacy_convert.h | 73 ++ source/blender/blenkernel/CMakeLists.txt | 2 + source/blender/blenkernel/intern/customdata.cc | 122 --- source/blender/blenkernel/intern/mesh.cc | 78 +- source/blender/blenkernel/intern/mesh_evaluate.cc | 272 ------- .../blenkernel/intern/mesh_legacy_convert.cc | 876 +++++++++++++++++++++ source/blender/blenkernel/intern/mesh_tessellate.c | 364 --------- source/blender/blenkernel/intern/particle.c | 1 + .../blenkernel/intern/particle_distribute.c | 1 + source/blender/blenkernel/intern/particle_system.c | 1 + source/blender/blenloader/intern/versioning_280.c | 1 + .../draw/intern/draw_cache_impl_particles.c | 1 + source/blender/editors/curves/intern/curves_ops.cc | 1 + source/blender/editors/physics/particle_edit.c | 1 + source/blender/editors/physics/particle_object.c | 1 + .../blender/io/alembic/exporter/abc_writer_hair.cc | 1 + source/blender/io/collada/collada_utils.cpp | 1 + source/blender/makesrna/intern/rna_particle.c | 1 + source/blender/modifiers/intern/MOD_explode.c | 1 + .../blender/modifiers/intern/MOD_particlesystem.cc | 1 + 22 files changed, 967 insertions(+), 918 deletions(-) create mode 100644 source/blender/blenkernel/BKE_mesh_legacy_convert.h create mode 100644 source/blender/blenkernel/intern/mesh_legacy_convert.cc diff --git a/source/blender/blenkernel/BKE_customdata.h b/source/blender/blenkernel/BKE_customdata.h index a1101c1df44..010fbb27172 100644 --- a/source/blender/blenkernel/BKE_customdata.h +++ b/source/blender/blenkernel/BKE_customdata.h @@ -550,33 +550,11 @@ void CustomData_validate_layer_name(const struct CustomData *data, */ bool CustomData_verify_versions(struct CustomData *data, int index); -/* BMesh specific custom-data stuff. - * - * Needed to convert to/from different face representation (for versioning). */ +/* BMesh specific custom-data stuff. */ -void CustomData_to_bmeshpoly(struct CustomData *fdata, struct CustomData *ldata, int totloop); -void CustomData_from_bmeshpoly(struct CustomData *fdata, struct CustomData *ldata, int total); void CustomData_bmesh_update_active_layers(struct CustomData *fdata, struct CustomData *ldata); -/** - * Update active indices for active/render/clone/stencil custom data layers - * based on indices from fdata layers - * used by do_versions in `readfile.c` when creating pdata and ldata for pre-bmesh - * meshes and needed to preserve active/render/clone/stencil flags set in pre-bmesh files. - */ -void CustomData_bmesh_do_versions_update_active_layers(struct CustomData *fdata, - struct CustomData *ldata); void CustomData_bmesh_init_pool(struct CustomData *data, int totelem, char htype); -#ifndef NDEBUG -/** - * Debug check, used to assert when we expect layers to be in/out of sync. - * - * \param fallback: Use when there are no layers to handle, - * since callers may expect success or failure. - */ -bool CustomData_from_bmeshpoly_test(CustomData *fdata, CustomData *ldata, bool fallback); -#endif - /** * Validate and fix data of \a layer, * if possible (needs relevant callback in layer's type to be defined). diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h index 66e0ff8e81a..ff4f4a8dc9e 100644 --- a/source/blender/blenkernel/BKE_mesh.h +++ b/source/blender/blenkernel/BKE_mesh.h @@ -183,14 +183,6 @@ void BKE_mesh_orco_verts_transform(struct Mesh *me, float (*orco)[3], int totver */ void BKE_mesh_orco_ensure(struct Object *ob, struct Mesh *mesh); -/** - * Rotates the vertices of a face in case v[2] or v[3] (vertex index) is = 0. - * this is necessary to make the if #MFace.v4 check for quads work. - */ -int BKE_mesh_mface_index_validate(struct MFace *mface, - struct CustomData *mfdata, - int mfindex, - int nr); struct Mesh *BKE_mesh_from_object(struct Object *ob); void BKE_mesh_assign_object(struct Main *bmain, struct Object *ob, struct Mesh *me); void BKE_mesh_from_metaball(struct ListBase *lb, struct Mesh *me); @@ -304,7 +296,6 @@ bool BKE_mesh_minmax(const struct Mesh *me, float r_min[3], float r_max[3]); void BKE_mesh_transform(struct Mesh *me, const float mat[4][4], bool do_keys); void BKE_mesh_translate(struct Mesh *me, const float offset[3], bool do_keys); -void BKE_mesh_tessface_ensure(struct Mesh *mesh); void BKE_mesh_tessface_clear(struct Mesh *mesh); void BKE_mesh_do_versions_cd_flag_init(struct Mesh *mesh); @@ -333,15 +324,6 @@ void BKE_mesh_vert_coords_apply(struct Mesh *mesh, const float (*vert_coords)[3] /* *** mesh_tessellate.c *** */ -/** - * Recreate #MFace Tessellation. - * - * \note This doesn't use multi-threading like #BKE_mesh_recalc_looptri since - * it's not used in many places and #MFace should be phased out. - */ - -void BKE_mesh_tessface_calc(struct Mesh *mesh); - /** * Calculate tessellation into #MLoopTri which exist only for this purpose. */ @@ -789,37 +771,6 @@ void BKE_mesh_calc_volume(const struct MVert *mverts, float *r_volume, float r_center[3]); -/* tessface */ -void BKE_mesh_convert_mfaces_to_mpolys(struct Mesh *mesh); -/** - * The same as #BKE_mesh_convert_mfaces_to_mpolys - * but oriented to be used in #do_versions from `readfile.c` - * the difference is how active/render/clone/stencil indices are handled here. - * - * normally they're being set from `pdata` which totally makes sense for meshes which are already - * converted to #BMesh structures, but when loading older files indices shall be updated in other - * way around, so newly added `pdata` and `ldata` would have this indices set - * based on `fdata` layer. - * - * this is normally only needed when reading older files, - * in all other cases #BKE_mesh_convert_mfaces_to_mpolys shall be always used. - */ -void BKE_mesh_do_versions_convert_mfaces_to_mpolys(struct Mesh *mesh); -void BKE_mesh_convert_mfaces_to_mpolys_ex(struct ID *id, - struct CustomData *fdata, - struct CustomData *ldata, - struct CustomData *pdata, - int totedge_i, - int totface_i, - int totloop_i, - int totpoly_i, - struct MEdge *medge, - struct MFace *mface, - int *r_totloop, - int *r_totpoly, - struct MLoop **r_mloop, - struct MPoly **r_mpoly); - /** * Flip a single MLoop's #MDisps structure, * low level function to be called from face-flipping code which re-arranged the mdisps themselves. @@ -1072,18 +1023,6 @@ char *BKE_mesh_debug_info(const struct Mesh *me) void BKE_mesh_debug_print(const struct Mesh *me) ATTR_NONNULL(1); #endif -/* Inlines */ - -/* NOTE(@sybren): Instead of -1 that function uses ORIGINDEX_NONE as defined in BKE_customdata.h, - * but I don't want to force every user of BKE_mesh.h to also include that file. */ -BLI_INLINE int BKE_mesh_origindex_mface_mpoly(const int *index_mf_to_mpoly, - const int *index_mp_to_orig, - const int i) -{ - const int j = index_mf_to_mpoly[i]; - return (j != -1) ? (index_mp_to_orig ? index_mp_to_orig[j] : j) : -1; -} - #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/BKE_mesh_legacy_convert.h b/source/blender/blenkernel/BKE_mesh_legacy_convert.h new file mode 100644 index 00000000000..909fd0e0dea --- /dev/null +++ b/source/blender/blenkernel/BKE_mesh_legacy_convert.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ + +#pragma once + +/** \file + * \ingroup bke + */ + +#include "BLI_utildefines.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct CustomData; +struct Mesh; +struct MFace; + +/** + * Recreate #MFace Tessellation. + * + * \note This doesn't use multi-threading like #BKE_mesh_recalc_looptri since + * it's not used in many places and #MFace should be phased out. + */ + +void BKE_mesh_tessface_calc(struct Mesh *mesh); + +void BKE_mesh_tessface_ensure(struct Mesh *mesh); + +void BKE_mesh_add_mface_layers(struct CustomData *fdata, struct CustomData *ldata, int total); + +/** + * Rotates the vertices of a face in case v[2] or v[3] (vertex index) is = 0. + * this is necessary to make the if #MFace.v4 check for quads work. + */ +int BKE_mesh_mface_index_validate(struct MFace *mface, + struct CustomData *mfdata, + int mfindex, + int nr); + +void BKE_mesh_convert_mfaces_to_mpolys(struct Mesh *mesh); + +/** + * The same as #BKE_mesh_convert_mfaces_to_mpolys + * but oriented to be used in #do_versions from `readfile.c` + * the difference is how active/render/clone/stencil indices are handled here. + * + * normally they're being set from `pdata` which totally makes sense for meshes which are already + * converted to #BMesh structures, but when loading older files indices shall be updated in other + * way around, so newly added `pdata` and `ldata` would have this indices set + * based on `fdata` layer. + * + * this is normally only needed when reading older files, + * in all other cases #BKE_mesh_convert_mfaces_to_mpolys shall be always used. + */ +void BKE_mesh_do_versions_convert_mfaces_to_mpolys(struct Mesh *mesh); + +/* Inlines */ + +/* NOTE(@sybren): Instead of -1 that function uses ORIGINDEX_NONE as defined in BKE_customdata.h, + * but I don't want to force every user of BKE_mesh.h to also include that file. */ +BLI_INLINE int BKE_mesh_origindex_mface_mpoly(const int *index_mf_to_mpoly, + const int *index_mp_to_orig, + const int i) +{ + const int j = index_mf_to_mpoly[i]; + return (j != -1) ? (index_mp_to_orig ? index_mp_to_orig[j] : j) : -1; +} + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 50be5b475d4..b2307c0e129 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -199,6 +199,7 @@ set(SRC intern/mesh_evaluate.cc intern/mesh_fair.cc intern/mesh_iterators.c + intern/mesh_legacy_convert.cc intern/mesh_mapping.c intern/mesh_merge.c intern/mesh_merge_customdata.cc @@ -419,6 +420,7 @@ set(SRC BKE_mesh_boolean_convert.hh BKE_mesh_fair.h BKE_mesh_iterators.h + BKE_mesh_legacy_convert.h BKE_mesh_mapping.h BKE_mesh_mirror.h BKE_mesh_remap.h diff --git a/source/blender/blenkernel/intern/customdata.cc b/source/blender/blenkernel/intern/customdata.cc index 74903e72f91..26fbd7d7b54 100644 --- a/source/blender/blenkernel/intern/customdata.cc +++ b/source/blender/blenkernel/intern/customdata.cc @@ -3517,97 +3517,6 @@ void CustomData_set(const CustomData *data, int index, int type, const void *sou /* BMesh functions */ -void CustomData_to_bmeshpoly(CustomData *fdata, CustomData *ldata, int totloop) -{ - for (int i = 0; i < fdata->totlayer; i++) { - if (fdata->layers[i].type == CD_MTFACE) { - CustomData_add_layer_named( - ldata, CD_MLOOPUV, CD_CALLOC, nullptr, totloop, fdata->layers[i].name); - } - else if (fdata->layers[i].type == CD_MCOL) { - CustomData_add_layer_named( - ldata, CD_PROP_BYTE_COLOR, CD_CALLOC, nullptr, totloop, fdata->layers[i].name); - } - else if (fdata->layers[i].type == CD_MDISPS) { - CustomData_add_layer_named( - ldata, CD_MDISPS, CD_CALLOC, nullptr, totloop, fdata->layers[i].name); - } - else if (fdata->layers[i].type == CD_TESSLOOPNORMAL) { - CustomData_add_layer_named( - ldata, CD_NORMAL, CD_CALLOC, nullptr, totloop, fdata->layers[i].name); - } - } -} - -void CustomData_from_bmeshpoly(CustomData *fdata, CustomData *ldata, int total) -{ - /* avoid accumulating extra layers */ - BLI_assert(!CustomData_from_bmeshpoly_test(fdata, ldata, false)); - - for (int i = 0; i < ldata->totlayer; i++) { - if (ldata->layers[i].type == CD_MLOOPUV) { - CustomData_add_layer_named( - fdata, CD_MTFACE, CD_CALLOC, nullptr, total, ldata->layers[i].name); - } - if (ldata->layers[i].type == CD_PROP_BYTE_COLOR) { - CustomData_add_layer_named(fdata, CD_MCOL, CD_CALLOC, nullptr, total, ldata->layers[i].name); - } - else if (ldata->layers[i].type == CD_PREVIEW_MLOOPCOL) { - CustomData_add_layer_named( - fdata, CD_PREVIEW_MCOL, CD_CALLOC, nullptr, total, ldata->layers[i].name); - } - else if (ldata->layers[i].type == CD_ORIGSPACE_MLOOP) { - CustomData_add_layer_named( - fdata, CD_ORIGSPACE, CD_CALLOC, nullptr, total, ldata->layers[i].name); - } - else if (ldata->layers[i].type == CD_NORMAL) { - CustomData_add_layer_named( - fdata, CD_TESSLOOPNORMAL, CD_CALLOC, nullptr, total, ldata->layers[i].name); - } - else if (ldata->layers[i].type == CD_TANGENT) { - CustomData_add_layer_named( - fdata, CD_TANGENT, CD_CALLOC, nullptr, total, ldata->layers[i].name); - } - } - - CustomData_bmesh_update_active_layers(fdata, ldata); -} - -#ifndef NDEBUG -bool CustomData_from_bmeshpoly_test(CustomData *fdata, CustomData *ldata, bool fallback) -{ - int a_num = 0, b_num = 0; -# define LAYER_CMP(l_a, t_a, l_b, t_b) \ - ((a_num += CustomData_number_of_layers(l_a, t_a)) == \ - (b_num += CustomData_number_of_layers(l_b, t_b))) - - if (!LAYER_CMP(ldata, CD_MLOOPUV, fdata, CD_MTFACE)) { - return false; - } - if (!LAYER_CMP(ldata, CD_PROP_BYTE_COLOR, fdata, CD_MCOL)) { - return false; - } - if (!LAYER_CMP(ldata, CD_PREVIEW_MLOOPCOL, fdata, CD_PREVIEW_MCOL)) { - return false; - } - if (!LAYER_CMP(ldata, CD_ORIGSPACE_MLOOP, fdata, CD_ORIGSPACE)) { - return false; - } - if (!LAYER_CMP(ldata, CD_NORMAL, fdata, CD_TESSLOOPNORMAL)) { - return false; - } - if (!LAYER_CMP(ldata, CD_TANGENT, fdata, CD_TANGENT)) { - return false; - } - -# undef LAYER_CMP - - /* if no layers are on either CustomData's, - * then there was nothing to do... */ - return a_num ? true : fallback; -} -#endif - void CustomData_bmesh_update_active_layers(CustomData *fdata, CustomData *ldata) { int act; @@ -3641,38 +3550,7 @@ void CustomData_bmesh_update_active_layers(CustomData *fdata, CustomData *ldata) } } -void CustomData_bmesh_do_versions_update_active_layers(CustomData *fdata, CustomData *ldata) -{ - int act; - - if (CustomData_has_layer(fdata, CD_MTFACE)) { - act = CustomData_get_active_layer(fdata, CD_MTFACE); - CustomData_set_layer_active(ldata, CD_MLOOPUV, act); - act = CustomData_get_render_layer(fdata, CD_MTFACE); - CustomData_set_layer_render(ldata, CD_MLOOPUV, act); - - act = CustomData_get_clone_layer(fdata, CD_MTFACE); - CustomData_set_layer_clone(ldata, CD_MLOOPUV, act); - - act = CustomData_get_stencil_layer(fdata, CD_MTFACE); - CustomData_set_layer_stencil(ldata, CD_MLOOPUV, act); - } - - if (CustomData_has_layer(fdata, CD_MCOL)) { - act = CustomData_get_active_layer(fdata, CD_MCOL); - CustomData_set_layer_active(ldata, CD_PROP_BYTE_COLOR, act); - - act = CustomData_get_render_layer(fdata, CD_MCOL); - CustomData_set_layer_render(ldata, CD_PROP_BYTE_COLOR, act); - - act = CustomData_get_clone_layer(fdata, CD_MCOL); - CustomData_set_layer_clone(ldata, CD_PROP_BYTE_COLOR, act); - - act = CustomData_get_stencil_layer(fdata, CD_MCOL); - CustomData_set_layer_stencil(ldata, CD_PROP_BYTE_COLOR, act); - } -} void CustomData_bmesh_init_pool(CustomData *data, int totelem, const char htype) { diff --git a/source/blender/blenkernel/intern/mesh.cc b/source/blender/blenkernel/intern/mesh.cc index ffbd824712a..98fa551590c 100644 --- a/source/blender/blenkernel/intern/mesh.cc +++ b/source/blender/blenkernel/intern/mesh.cc @@ -47,6 +47,7 @@ #include "BKE_main.h" #include "BKE_material.h" #include "BKE_mesh.h" +#include "BKE_mesh_legacy_convert.h" #include "BKE_mesh_runtime.h" #include "BKE_mesh_wrapper.h" #include "BKE_modifier.h" @@ -755,7 +756,7 @@ static void mesh_ensure_tessellation_customdata(Mesh *me) if (tottex_tessface != tottex_original || totcol_tessface != totcol_original) { BKE_mesh_tessface_clear(me); - CustomData_from_bmeshpoly(&me->fdata, &me->ldata, me->totface); + BKE_mesh_add_mface_layers(&me->fdata, &me->ldata, me->totface); /* TODO: add some `--debug-mesh` option. */ if (G.debug & G_DEBUG) { @@ -1352,74 +1353,6 @@ void BKE_mesh_orco_ensure(Object *ob, Mesh *mesh) CustomData_add_layer(&mesh->vdata, CD_ORCO, CD_ASSIGN, orcodata, mesh->totvert); } -int BKE_mesh_mface_index_validate(MFace *mface, CustomData *fdata, int mfindex, int nr) -{ - /* first test if the face is legal */ - if ((mface->v3 || nr == 4) && mface->v3 == mface->v4) { - mface->v4 = 0; - nr--; - } - if ((mface->v2 || mface->v4) && mface->v2 == mface->v3) { - mface->v3 = mface->v4; - mface->v4 = 0; - nr--; - } - if (mface->v1 == mface->v2) { - mface->v2 = mface->v3; - mface->v3 = mface->v4; - mface->v4 = 0; - nr--; - } - - /* Check corrupt cases, bow-tie geometry, - * can't handle these because edge data won't exist so just return 0. */ - if (nr == 3) { - if ( - /* real edges */ - mface->v1 == mface->v2 || mface->v2 == mface->v3 || mface->v3 == mface->v1) { - return 0; - } - } - else if (nr == 4) { - if ( - /* real edges */ - mface->v1 == mface->v2 || mface->v2 == mface->v3 || mface->v3 == mface->v4 || - mface->v4 == mface->v1 || - /* across the face */ - mface->v1 == mface->v3 || mface->v2 == mface->v4) { - return 0; - } - } - - /* prevent a zero at wrong index location */ - if (nr == 3) { - if (mface->v3 == 0) { - static int corner_indices[4] = {1, 2, 0, 3}; - - SWAP(uint, mface->v1, mface->v2); - SWAP(uint, mface->v2, mface->v3); - - if (fdata) { - CustomData_swap_corners(fdata, mfindex, corner_indices); - } - } - } - else if (nr == 4) { - if (mface->v3 == 0 || mface->v4 == 0) { - static int corner_indices[4] = {2, 3, 0, 1}; - - SWAP(uint, mface->v1, mface->v3); - SWAP(uint, mface->v2, mface->v4); - - if (fdata) { - CustomData_swap_corners(fdata, mfindex, corner_indices); - } - } - } - - return nr; -} - Mesh *BKE_mesh_from_object(Object *ob) { if (ob == nullptr) { @@ -1709,13 +1642,6 @@ void BKE_mesh_translate(Mesh *me, const float offset[3], const bool do_keys) BKE_mesh_tag_coords_changed_uniformly(me); } -void BKE_mesh_tessface_ensure(Mesh *mesh) -{ - if (mesh->totpoly && mesh->totface == 0) { - BKE_mesh_tessface_calc(mesh); - } -} - void BKE_mesh_tessface_clear(Mesh *mesh) { mesh_tessface_clear_intern(mesh, true); diff --git a/source/blender/blenkernel/intern/mesh_evaluate.cc b/source/blender/blenkernel/intern/mesh_evaluate.cc index de0489d668f..7d26262a504 100644 --- a/source/blender/blenkernel/intern/mesh_evaluate.cc +++ b/source/blender/blenkernel/intern/mesh_evaluate.cc @@ -632,278 +632,6 @@ void BKE_mesh_calc_volume(const MVert *mverts, /** \} */ -/* -------------------------------------------------------------------- */ -/** \name NGon Tessellation (NGon to MFace Conversion) - * \{ */ - -static void bm_corners_to_loops_ex(ID *id, - CustomData *fdata, - CustomData *ldata, - MFace *mface, - int totloop, - int findex, - int loopstart, - int numTex, - int numCol) -{ - MFace *mf = mface + findex; - - for (int i = 0; i < numTex; i++) { - const MTFace *texface = (const MTFace *)CustomData_get_n(fdata, CD_MTFACE, findex, i); - - MLoopUV *mloopuv = (MLoopUV *)CustomData_get_n(ldata, CD_MLOOPUV, loopstart, i); - copy_v2_v2(mloopuv->uv, texface->uv[0]); - mloopuv++; - copy_v2_v2(mloopuv->uv, texface->uv[1]); - mloopuv++; - copy_v2_v2(mloopuv->uv, texface->uv[2]); - mloopuv++; - - if (mf->v4) { - copy_v2_v2(mloopuv->uv, texface->uv[3]); - mloopuv++; - } - } - - for (int i = 0; i < numCol; i++) { - MLoopCol *mloopcol = (MLoopCol *)CustomData_get_n(ldata, CD_PROP_BYTE_COLOR, loopstart, i); - const MCol *mcol = (const MCol *)CustomData_get_n(fdata, CD_MCOL, findex, i); - - MESH_MLOOPCOL_FROM_MCOL(mloopcol, &mcol[0]); - mloopcol++; - MESH_MLOOPCOL_FROM_MCOL(mloopcol, &mcol[1]); - mloopcol++; - MESH_MLOOPCOL_FROM_MCOL(mloopcol, &mcol[2]); - mloopcol++; - if (mf->v4) { - MESH_MLOOPCOL_FROM_MCOL(mloopcol, &mcol[3]); - mloopcol++; - } - } - - if (CustomData_has_layer(fdata, CD_TESSLOOPNORMAL)) { - float(*lnors)[3] = (float(*)[3])CustomData_get(ldata, loopstart, CD_NORMAL); - const short(*tlnors)[3] = (short(*)[3])CustomData_get(fdata, findex, CD_TESSLOOPNORMAL); - const int max = mf->v4 ? 4 : 3; - - for (int i = 0; i < max; i++, lnors++, tlnors++) { - normal_short_to_float_v3(*lnors, *tlnors); - } - } - - if (CustomData_has_layer(fdata, CD_MDISPS)) { - MDisps *ld = (MDisps *)CustomData_get(ldata, loopstart, CD_MDISPS); - const MDisps *fd = (const MDisps *)CustomData_get(fdata, findex, CD_MDISPS); - const float(*disps)[3] = fd->disps; - int tot = mf->v4 ? 4 : 3; - int corners; - - if (CustomData_external_test(fdata, CD_MDISPS)) { - if (id && fdata->external) { - CustomData_external_add(ldata, id, CD_MDISPS, totloop, fdata->external->filepath); - } - } - - corners = multires_mdisp_corners(fd); - - if (corners == 0) { - /* Empty #MDisp layers appear in at least one of the `sintel.blend` files. - * Not sure why this happens, but it seems fine to just ignore them here. - * If `corners == 0` for a non-empty layer though, something went wrong. */ - BLI_assert(fd->totdisp == 0); - } - else { - const int side = (int)sqrtf((float)(fd->totdisp / corners)); - const int side_sq = side * side; - - for (int i = 0; i < tot; i++, disps += side_sq, ld++) { - ld->totdisp = side_sq; - ld->level = (int)(logf((float)side - 1.0f) / (float)M_LN2) + 1; - - if (ld->disps) { - MEM_freeN(ld->disps); - } - - ld->disps = (float(*)[3])MEM_malloc_arrayN( - (size_t)side_sq, sizeof(float[3]), "converted loop mdisps"); - if (fd->disps) { - memcpy(ld->disps, disps, (size_t)side_sq * sizeof(float[3])); - } - else { - memset(ld->disps, 0, (size_t)side_sq * sizeof(float[3])); - } - } - } - } -} - -void BKE_mesh_convert_mfaces_to_mpolys(Mesh *mesh) -{ - BKE_mesh_convert_mfaces_to_mpolys_ex(&mesh->id, - &mesh->fdata, - &mesh->ldata, - &mesh->pdata, - mesh->totedge, - mesh->totface, - mesh->totloop, - mesh->totpoly, - mesh->medge, - mesh->mface, - &mesh->totloop, - &mesh->totpoly, - &mesh->mloop, - &mesh->mpoly); - - BKE_mesh_update_customdata_pointers(mesh, true); -} - -void BKE_mesh_do_versions_convert_mfaces_to_mpolys(Mesh *mesh) -{ - BKE_mesh_convert_mfaces_to_mpolys_ex(&mesh->id, - &mesh->fdata, - &mesh->ldata, - &mesh->pdata, - mesh->totedge, - mesh->totface, - mesh->totloop, - mesh->totpoly, - mesh->medge, - mesh->mface, - &mesh->totloop, - &mesh->totpoly, - &mesh->mloop, - &mesh->mpoly); - - CustomData_bmesh_do_versions_update_active_layers(&mesh->fdata, &mesh->ldata); - - BKE_mesh_update_customdata_pointers(mesh, true); -} - -void BKE_mesh_convert_mfaces_to_mpolys_ex(ID *id, - CustomData *fdata, - CustomData *ldata, - CustomData *pdata, - int totedge_i, - int totface_i, - int totloop_i, - int totpoly_i, - MEdge *medge, - MFace *mface, - int *r_totloop, - int *r_totpoly, - MLoop **r_mloop, - MPoly **r_mpoly) -{ - MFace *mf; - MLoop *ml, *mloop; - MPoly *mp, *mpoly; - MEdge *me; - EdgeHash *eh; - int numTex, numCol; - int i, j, totloop, totpoly, *polyindex; - - /* old flag, clear to allow for reuse */ -#define ME_FGON (1 << 3) - - /* just in case some of these layers are filled in (can happen with python created meshes) */ - CustomData_free(ldata, totloop_i); - CustomData_free(pdata, totpoly_i); - - totpoly = totface_i; - mpoly = (MPoly *)MEM_calloc_arrayN((size_t)totpoly, sizeof(MPoly), "mpoly converted"); - CustomData_add_layer(pdata, CD_MPOLY, CD_ASSIGN, mpoly, totpoly); - - numTex = CustomData_number_of_layers(fdata, CD_MTFACE); - numCol = CustomData_number_of_layers(fdata, CD_MCOL); - - totloop = 0; - mf = mface; - for (i = 0; i < totface_i; i++, mf++) { - totloop += mf->v4 ? 4 : 3; - } - - mloop = (MLoop *)MEM_calloc_arrayN((size_t)totloop, sizeof(MLoop), "mloop converted"); - - CustomData_add_layer(ldata, CD_MLOOP, CD_ASSIGN, mloop, totloop); - - CustomData_to_bmeshpoly(fdata, ldata, totloop); - - if (id) { - /* ensure external data is transferred */ - /* TODO(sergey): Use multiresModifier_ensure_external_read(). */ - CustomData_external_read(fdata, id, CD_MASK_MDISPS, totface_i); - } - - eh = BLI_edgehash_new_ex(__func__, (uint)totedge_i); - - /* build edge hash */ - me = medge; - for (i = 0; i < totedge_i; i++, me++) { - BLI_edgehash_insert(eh, me->v1, me->v2, POINTER_FROM_UINT(i)); - - /* unrelated but avoid having the FGON flag enabled, - * so we can reuse it later for something else */ - me->flag &= ~ME_FGON; - } - - polyindex = (int *)CustomData_get_layer(fdata, CD_ORIGINDEX); - - j = 0; /* current loop index */ - ml = mloop; - mf = mface; - mp = mpoly; - for (i = 0; i < totface_i; i++, mf++, mp++) { - mp->loopstart = j; - - mp->totloop = mf->v4 ? 4 : 3; - - mp->mat_nr = mf->mat_nr; - mp->flag = mf->flag; - -#define ML(v1, v2) \ - { \ - ml->v = mf->v1; \ - ml->e = POINTER_AS_UINT(BLI_edgehash_lookup(eh, mf->v1, mf->v2)); \ - ml++; \ - j++; \ - } \ - (void)0 - - ML(v1, v2); - ML(v2, v3); - if (mf->v4) { - ML(v3, v4); - ML(v4, v1); - } - else { - ML(v3, v1); - } - -#undef ML - - bm_corners_to_loops_ex(id, fdata, ldata, mface, totloop, i, mp->loopstart, numTex, numCol); - - if (polyindex) { - *polyindex = i; - polyindex++; - } - } - - /* NOTE: we don't convert NGons at all, these are not even real ngons, - * they have their own UV's, colors etc - its more an editing feature. */ - - BLI_edgehash_free(eh, nullptr); - - *r_totpoly = totpoly; - *r_totloop = totloop; - *r_mpoly = mpoly; - *r_mloop = mloop; - -#undef ME_FGON -} - -/** \} */ - void BKE_mesh_mdisp_flip(MDisps *md, const bool use_loop_mdisp_flip) { if (UNLIKELY(!md->totdisp || !md->disps)) { diff --git a/source/blender/blenkernel/intern/mesh_legacy_convert.cc b/source/blender/blenkernel/intern/mesh_legacy_convert.cc new file mode 100644 index 00000000000..479dd6a012a --- /dev/null +++ b/source/blender/blenkernel/intern/mesh_legacy_convert.cc @@ -0,0 +1,876 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ + +/** \file + * \ingroup bke + * + * Functions to convert mesh data to and from legacy formats like #MFace. + */ + +// #include + +#include "MEM_guardedalloc.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BLI_edgehash.h" +#include "BLI_math.h" +#include "BLI_memarena.h" +#include "BLI_polyfill_2d.h" +#include "BLI_utildefines.h" + +#include "BKE_customdata.h" +#include "BKE_mesh.h" +#include "BKE_mesh_legacy_convert.h" +#include "BKE_multires.h" + +/* -------------------------------------------------------------------- */ +/** \name NGon Tessellation (NGon to MFace Conversion) + * \{ */ + +static void bm_corners_to_loops_ex(ID *id, + CustomData *fdata, + CustomData *ldata, + MFace *mface, + int totloop, + int findex, + int loopstart, + int numTex, + int numCol) +{ + MFace *mf = mface + findex; + + for (int i = 0; i < numTex; i++) { + const MTFace *texface = (const MTFace *)CustomData_get_n(fdata, CD_MTFACE, findex, i); + + MLoopUV *mloopuv = (MLoopUV *)CustomData_get_n(ldata, CD_MLOOPUV, loopstart, i); + copy_v2_v2(mloopuv->uv, texface->uv[0]); + mloopuv++; + copy_v2_v2(mloopuv->uv, texface->uv[1]); + mloopuv++; + copy_v2_v2(mloopuv->uv, texface->uv[2]); + mloopuv++; + + if (mf->v4) { + copy_v2_v2(mloopuv->uv, texface->uv[3]); + mloopuv++; + } + } + + for (int i = 0; i < numCol; i++) { + MLoopCol *mloopcol = (MLoopCol *)CustomData_get_n(ldata, CD_PROP_BYTE_COLOR, loopstart, i); + const MCol *mcol = (const MCol *)CustomData_get_n(fdata, CD_MCOL, findex, i); + + MESH_MLOOPCOL_FROM_MCOL(mloopcol, &mcol[0]); + mloopcol++; + MESH_MLOOPCOL_FROM_MCOL(mloopcol, &mcol[1]); + mloopcol++; + MESH_MLOOPCOL_FROM_MCOL(mloopcol, &mcol[2]); + mloopcol++; + if (mf->v4) { + MESH_MLOOPCOL_FROM_MCOL(mloopcol, &mcol[3]); + mloopcol++; + } + } + + if (CustomData_has_layer(fdata, CD_TESSLOOPNORMAL)) { + float(*lnors)[3] = (float(*)[3])CustomData_get(ldata, loopstart, CD_NORMAL); + const short(*tlnors)[3] = (short(*)[3])CustomData_get(fdata, findex, CD_TESSLOOPNORMAL); + const int max = mf->v4 ? 4 : 3; + + for (int i = 0; i < max; i++, lnors++, tlnors++) { + normal_short_to_float_v3(*lnors, *tlnors); + } + } + + if (CustomData_has_layer(fdata, CD_MDISPS)) { + MDisps *ld = (MDisps *)CustomData_get(ldata, loopstart, CD_MDISPS); + const MDisps *fd = (const MDisps *)CustomData_get(fdata, findex, CD_MDISPS); + const float(*disps)[3] = fd->disps; + int tot = mf->v4 ? 4 : 3; + int corners; + + if (CustomData_external_test(fdata, CD_MDISPS)) { + if (id && fdata->external) { + CustomData_external_add(ldata, id, CD_MDISPS, totloop, fdata->external->filepath); + } + } + + corners = multires_mdisp_corners(fd); + + if (corners == 0) { + /* Empty #MDisp layers appear in at least one of the `sintel.blend` files. + * Not sure why this happens, but it seems fine to just ignore them here. + * If `corners == 0` for a non-empty layer though, something went wrong. */ + BLI_assert(fd->totdisp == 0); + } + else { + const int side = (int)sqrtf((float)(fd->totdisp / corners)); + const int side_sq = side * side; + + for (int i = 0; i < tot; i++, disps += side_sq, ld++) { + ld->totdisp = side_sq; + ld->level = (int)(logf((float)side - 1.0f) / (float)M_LN2) + 1; + + if (ld->disps) { + MEM_freeN(ld->disps); + } + + ld->disps = (float(*)[3])MEM_malloc_arrayN( + (size_t)side_sq, sizeof(float[3]), "converted loop mdisps"); + if (fd->disps) { + memcpy(ld->disps, disps, (size_t)side_sq * sizeof(float[3])); + } + else { + memset(ld->disps, 0, (size_t)side_sq * sizeof(float[3])); + } + } + } + } +} + +static void CustomData_to_bmeshpoly(CustomData *fdata, CustomData *ldata, int totloop) +{ + for (int i = 0; i < fdata->totlayer; i++) { + if (fdata->layers[i].type == CD_MTFACE) { + CustomData_add_layer_named( + ldata, CD_MLOOPUV, CD_CALLOC, nullptr, totloop, fdata->layers[i].name); + } + else if (fdata->layers[i].type == CD_MCOL) { + CustomData_add_layer_named( + ldata, CD_PROP_BYTE_COLOR, CD_CALLOC, nullptr, totloop, fdata->layers[i].name); + } + else if (fdata->layers[i].type == CD_MDISPS) { + CustomData_add_layer_named( + ldata, CD_MDISPS, CD_CALLOC, nullptr, totloop, fdata->layers[i].name); + } + else if (fdata->layers[i].type == CD_TESSLOOPNORMAL) { + CustomData_add_layer_named( + ldata, CD_NORMAL, CD_CALLOC, nullptr, totloop, fdata->layers[i].name); + } + } +} + +static void convert_mfaces_to_mpolys(ID *id, + CustomData *fdata, + CustomData *ldata, + CustomData *pdata, + int totedge_i, + int totface_i, + int totloop_i, + int totpoly_i, + MEdge *medge, + MFace *mface, + int *r_totloop, + int *r_totpoly, + MLoop **r_mloop, + MPoly **r_mpoly) +{ + MFace *mf; + MLoop *ml, *mloop; + MPoly *mp, *mpoly; + MEdge *me; + EdgeHash *eh; + int numTex, numCol; + int i, j, totloop, totpoly, *polyindex; + + /* old flag, clear to allow for reuse */ +#define ME_FGON (1 << 3) + + /* just in case some of these layers are filled in (can happen with python created meshes) */ + CustomData_free(ldata, totloop_i); + CustomData_free(pdata, totpoly_i); + + totpoly = totface_i; + mpoly = (MPoly *)MEM_calloc_arrayN((size_t)totpoly, sizeof(MPoly), "mpoly converted"); + CustomData_add_layer(pdata, CD_MPOLY, CD_ASSIGN, mpoly, totpoly); + + numTex = CustomData_number_of_layers(fdata, CD_MTFACE); + numCol = CustomData_number_of_layers(fdata, CD_MCOL); + + totloop = 0; + mf = mface; + for (i = 0; i < totface_i; i++, mf++) { + totloop += mf->v4 ? 4 : 3; + } + + mloop = (MLoop *)MEM_calloc_arrayN((size_t)totloop, sizeof(MLoop), "mloop converted"); + + CustomData_add_layer(ldata, CD_MLOOP, CD_ASSIGN, mloop, totloop); + + CustomData_to_bmeshpoly(fdata, ldata, totloop); + + if (id) { + /* ensure external data is transferred */ + /* TODO(sergey): Use multiresModifier_ensure_external_read(). */ + CustomData_external_read(fdata, id, CD_MASK_MDISPS, totface_i); + } + + eh = BLI_edgehash_new_ex(__func__, (uint)totedge_i); + + /* build edge hash */ + me = medge; + for (i = 0; i < totedge_i; i++, me++) { + BLI_edgehash_insert(eh, me->v1, me->v2, POINTER_FROM_UINT(i)); + + /* unrelated but avoid having the FGON flag enabled, + * so we can reuse it later for something else */ + me->flag &= ~ME_FGON; + } + + polyindex = (int *)CustomData_get_layer(fdata, CD_ORIGINDEX); + + j = 0; /* current loop index */ + ml = mloop; + mf = mface; + mp = mpoly; + for (i = 0; i < totface_i; i++, mf++, mp++) { + mp->loopstart = j; + + mp->totloop = mf->v4 ? 4 : 3; + + mp->mat_nr = mf->mat_nr; + mp->flag = mf->flag; + +#define ML(v1, v2) \ + { \ + ml->v = mf->v1; \ + ml->e = POINTER_AS_UINT(BLI_edgehash_lookup(eh, mf->v1, mf->v2)); \ + ml++; \ + j++; \ + } \ + (void)0 + + ML(v1, v2); + ML(v2, v3); + if (mf->v4) { + ML(v3, v4); + ML(v4, v1); + } + else { + ML(v3, v1); + } + +#undef ML + + bm_corners_to_loops_ex(id, fdata, ldata, mface, totloop, i, mp->loopstart, numTex, numCol); + + if (polyindex) { + *polyindex = i; + polyindex++; + } + } + + /* NOTE: we don't convert NGons at all, these are not even real ngons, + * they have their own UV's, colors etc - its more an editing feature. */ + + BLI_edgehash_free(eh, nullptr); + + *r_totpoly = totpoly; + *r_totloop = totloop; + *r_mpoly = mpoly; + *r_mloop = mloop; + +#undef ME_FGON +} + +void BKE_mesh_convert_mfaces_to_mpolys(Mesh *mesh) +{ + convert_mfaces_to_mpolys(&mesh->id, + &mesh->fdata, + &mesh->ldata, + &mesh->pdata, + mesh->totedge, + mesh->totface, + mesh->totloop, + mesh->totpoly, + mesh->medge, + mesh->mface, + &mesh->totloop, + &mesh->totpoly, + &mesh->mloop, + &mesh->mpoly); + + BKE_mesh_update_customdata_pointers(mesh, true); +} + +/** + * Update active indices for active/render/clone/stencil custom data layers + * based on indices from fdata layers + * used when creating pdata and ldata for pre-bmesh + * meshes and needed to preserve active/render/clone/stencil flags set in pre-bmesh files. + */ +static void CustomData_bmesh_do_versions_update_active_layers(CustomData *fdata, CustomData *ldata) +{ + int act; + + if (CustomData_has_layer(fdata, CD_MTFACE)) { + act = CustomData_get_active_layer(fdata, CD_MTFACE); + CustomData_set_layer_active(ldata, CD_MLOOPUV, act); + + act = CustomData_get_render_layer(fdata, CD_MTFACE); + CustomData_set_layer_render(ldata, CD_MLOOPUV, act); + + act = CustomData_get_clone_layer(fdata, CD_MTFACE); + CustomData_set_layer_clone(ldata, CD_MLOOPUV, act); + + act = CustomData_get_stencil_layer(fdata, CD_MTFACE); + CustomData_set_layer_stencil(ldata, CD_MLOOPUV, act); + } + + if (CustomData_has_layer(fdata, CD_MCOL)) { + act = CustomData_get_active_layer(fdata, CD_MCOL); + CustomData_set_layer_active(ldata, CD_PROP_BYTE_COLOR, act); + + act = CustomData_get_render_layer(fdata, CD_MCOL); + CustomData_set_layer_render(ldata, CD_PROP_BYTE_COLOR, act); + + act = CustomData_get_clone_layer(fdata, CD_MCOL); + CustomData_set_layer_clone(ldata, CD_PROP_BYTE_COLOR, act); + + act = CustomData_get_stencil_layer(fdata, CD_MCOL); + CustomData_set_layer_stencil(ldata, CD_PROP_BYTE_COLOR, act); + } +} + +void BKE_mesh_do_versions_convert_mfaces_to_mpolys(Mesh *mesh) +{ + convert_mfaces_to_mpolys(&mesh->id, + &mesh->fdata, + &mesh->ldata, + &mesh->pdata, + mesh->totedge, + mesh->totface, + mesh->totloop, + mesh->totpoly, + mesh->medge, + mesh->mface, + &mesh->totloop, + &mesh->totpoly, + &mesh->mloop, + &mesh->mpoly); + + CustomData_bmesh_do_versions_update_active_layers(&mesh->fdata, &mesh->ldata); + + BKE_mesh_update_customdata_pointers(mesh, true); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name MFace Tessellation + * + * #MFace is a legacy data-structure that should be avoided, use #MLoopTri instead. + * \{ */ + +/** + * Convert all CD layers from loop/poly to tessface data. + * + * \param loopindices: is an array of an int[4] per tessface, + * mapping tessface's verts to loops indices. + * + * \note when mface is not null, mface[face_index].v4 + * is used to test quads, else, loopindices[face_index][3] is used. + */ +static void mesh_loops_to_tessdata(CustomData *fdata, + CustomData *ldata, + MFace *mface, + const int *polyindices, + uint (*loopindices)[4], + const int num_faces) +{ + /* NOTE(mont29): performances are sub-optimal when we get a null #MFace, + * we could be ~25% quicker with dedicated code. + * The issue is, unless having two different functions with nearly the same code, + * there's not much ways to solve this. Better IMHO to live with it for now (sigh). */ + const int numUV = CustomData_number_of_layers(ldata, CD_MLOOPUV); + const int numCol = CustomData_number_of_layers(ldata, CD_PROP_BYTE_COLOR); + const bool hasPCol = CustomData_has_layer(ldata, CD_PREVIEW_MLOOPCOL); + const bool hasOrigSpace = CustomData_has_layer(ldata, CD_ORIGSPACE_MLOOP); + const bool hasLoopNormal = CustomData_has_layer(ldata, CD_NORMAL); + const bool hasLoopTangent = CustomData_has_layer(ldata, CD_TANGENT); + int findex, i, j; + const int *pidx; + uint(*lidx)[4]; + + for (i = 0; i < numUV; i++) { + MTFace *texface = (MTFace *)CustomData_get_layer_n(fdata, CD_MTFACE, i); + const MLoopUV *mloopuv = (const MLoopUV *)CustomData_get_layer_n(ldata, CD_MLOOPUV, i); + + for (findex = 0, pidx = polyindices, lidx = loopindices; findex < num_faces; + pidx++, lidx++, findex++, texface++) { + for (j = (mface ? mface[findex].v4 : (*lidx)[3]) ? 4 : 3; j--;) { + copy_v2_v2(texface->uv[j], mloopuv[(*lidx)[j]].uv); + } + } + } + + for (i = 0; i < numCol; i++) { + MCol(*mcol)[4] = (MCol(*)[4])CustomData_get_layer_n(fdata, CD_MCOL, i); + const MLoopCol *mloopcol = (const MLoopCol *)CustomData_get_layer_n( + ldata, CD_PROP_BYTE_COLOR, i); + + for (findex = 0, lidx = loopindices; findex < num_faces; lidx++, findex++, mcol++) { + for (j = (mface ? mface[findex].v4 : (*lidx)[3]) ? 4 : 3; j--;) { + MESH_MLOOPCOL_TO_MCOL(&mloopcol[(*lidx)[j]], &(*mcol)[j]); + } + } + } + + if (hasPCol) { + MCol(*mcol)[4] = (MCol(*)[4])CustomData_get_layer(fdata, CD_PREVIEW_MCOL); + const MLoopCol *mloopcol = (const MLoopCol *)CustomData_get_layer(ldata, CD_PREVIEW_MLOOPCOL); + + for (findex = 0, lidx = loopindices; findex < num_faces; lidx++, findex++, mcol++) { + for (j = (mface ? mface[findex].v4 : (*lidx)[3]) ? 4 : 3; j--;) { + MESH_MLOOPCOL_TO_MCOL(&mloopcol[(*lidx)[j]], &(*mcol)[j]); + } + } + } + + if (hasOrigSpace) { + OrigSpaceFace *of = (OrigSpaceFace *)CustomData_get_layer(fdata, CD_ORIGSPACE); + const OrigSpaceLoop *lof = (const OrigSpaceLoop *)CustomData_get_layer(ldata, + CD_ORIGSPACE_MLOOP); + + for (findex = 0, lidx = loopindices; findex < num_faces; lidx++, findex++, of++) { + for (j = (mface ? mface[findex].v4 : (*lidx)[3]) ? 4 : 3; j--;) { + copy_v2_v2(of->uv[j], lof[(*lidx)[j]].uv); + } + } + } + + if (hasLoopNormal) { + short(*fnors)[4][3] = (short(*)[4][3])CustomData_get_layer(fdata, CD_TESSLOOPNORMAL); + const float(*lnors)[3] = (const float(*)[3])CustomData_get_layer(ldata, CD_NORMAL); + + for (findex = 0, lidx = loopindices; findex < num_faces; lidx++, findex++, fnors++) { + for (j = (mface ? mface[findex].v4 : (*lidx)[3]) ? 4 : 3; j--;) { + normal_float_to_short_v3((*fnors)[j], lnors[(*lidx)[j]]); + } + } + } + + if (hasLoopTangent) { + /* Need to do for all UV maps at some point. */ + float(*ftangents)[4] = (float(*)[4])CustomData_get_layer(fdata, CD_TANGENT); + const float(*ltangents)[4] = (const float(*)[4])CustomData_get_layer(ldata, CD_TANGENT); + + for (findex = 0, pidx = polyindices, lidx = loopindices; findex < num_faces; + pidx++, lidx++, findex++) { + int nverts = (mface ? mface[findex].v4 : (*lidx)[3]) ? 4 : 3; + for (j = nverts; j--;) { + copy_v4_v4(ftangents[findex * 4 + j], ltangents[(*lidx)[j]]); + } + } + } +} + +int BKE_mesh_mface_index_validate(MFace *mface, CustomData *fdata, int mfindex, int nr) +{ + /* first test if the face is legal */ + if ((mface->v3 || nr == 4) && mface->v3 == mface->v4) { + mface->v4 = 0; + nr--; + } + if ((mface->v2 || mface->v4) && mface->v2 == mface->v3) { + mface->v3 = mface->v4; + mface->v4 = 0; + nr--; + } + if (mface->v1 == mface->v2) { + mface->v2 = mface->v3; + mface->v3 = mface->v4; + mface->v4 = 0; + nr--; + } + + /* Check corrupt cases, bow-tie geometry, + * can't handle these because edge data won't exist so just return 0. */ + if (nr == 3) { + if ( + /* real edges */ + mface->v1 == mface->v2 || mface->v2 == mface->v3 || mface->v3 == mface->v1) { + return 0; + } + } + else if (nr == 4) { + if ( + /* real edges */ + mface->v1 == mface->v2 || mface->v2 == mface->v3 || mface->v3 == mface->v4 || + mface->v4 == mface->v1 || + /* across the face */ + mface->v1 == mface->v3 || mface->v2 == mface->v4) { + return 0; + } + } + + /* prevent a zero at wrong index location */ + if (nr == 3) { + if (mface->v3 == 0) { + static int corner_indices[4] = {1, 2, 0, 3}; + + SWAP(uint, mface->v1, mface->v2); + SWAP(uint, mface->v2, mface->v3); + + if (fdata) { + CustomData_swap_corners(fdata, mfindex, corner_indices); + } + } + } + else if (nr == 4) { + if (mface->v3 == 0 || mface->v4 == 0) { + static int corner_indices[4] = {2, 3, 0, 1}; + + SWAP(uint, mface->v1, mface->v3); + SWAP(uint, mface->v2, mface->v4); + + if (fdata) { + CustomData_swap_corners(fdata, mfindex, corner_indices); + } + } + } + + return nr; +} + +static int mesh_tessface_calc(CustomData *fdata, + CustomData *ldata, + CustomData *pdata, + MVert *mvert, + int totface, + int totloop, + int totpoly) +{ +#define USE_TESSFACE_SPEEDUP +#define USE_TESSFACE_QUADS + +/* We abuse #MFace.edcode to tag quad faces. See below for details. */ +#define TESSFACE_IS_QUAD 1 + + const int looptri_num = poly_to_tri_count(totpoly, totloop); + + const MPoly *mp, *mpoly; + const MLoop *ml, *mloop; + MFace *mface, *mf; + MemArena *arena = nullptr; + int *mface_to_poly_map; + uint(*lindices)[4]; + int poly_index, mface_index; + uint j; + + mpoly = (const MPoly *)CustomData_get_layer(pdata, CD_MPOLY); + mloop = (const MLoop *)CustomData_get_layer(ldata, CD_MLOOP); + + /* Allocate the length of `totfaces`, avoid many small reallocation's, + * if all faces are triangles it will be correct, `quads == 2x` allocations. */ + /* Take care since memory is _not_ zeroed so be sure to initialize each field. */ + mface_to_poly_map = (int *)MEM_malloc_arrayN( + (size_t)looptri_num, sizeof(*mface_to_poly_map), __func__); + mface = (MFace *)MEM_malloc_arrayN((size_t)looptri_num, sizeof(*mface), __func__); + lindices = (uint(*)[4])MEM_malloc_arrayN((size_t)looptri_num, sizeof(*lindices), __func__); + + mface_index = 0; + mp = mpoly; + for (poly_index = 0; poly_index < totpoly; poly_index++, mp++) { + const uint mp_loopstart = (uint)mp->loopstart; + const uint mp_totloop = (uint)mp->totloop; + uint l1, l2, l3, l4; + uint *lidx; + if (mp_totloop < 3) { + /* Do nothing. */ + } + +#ifdef USE_TESSFACE_SPEEDUP + +# define ML_TO_MF(i1, i2, i3) \ + mface_to_poly_map[mface_index] = poly_index; \ + mf = &mface[mface_index]; \ + lidx = lindices[mface_index]; \ + /* Set loop indices, transformed to vert indices later. */ \ + l1 = mp_loopstart + i1; \ + l2 = mp_loopstart + i2; \ + l3 = mp_loopstart + i3; \ + mf->v1 = mloop[l1].v; \ + mf->v2 = mloop[l2].v; \ + mf->v3 = mloop[l3].v; \ + mf->v4 = 0; \ + lidx[0] = l1; \ + lidx[1] = l2; \ + lidx[2] = l3; \ + lidx[3] = 0; \ + mf->mat_nr = mp->mat_nr; \ + mf->flag = mp->flag; \ + mf->edcode = 0; \ + (void)0 + +/* ALMOST IDENTICAL TO DEFINE ABOVE (see EXCEPTION) */ +# define ML_TO_MF_QUAD() \ + mface_to_poly_map[mface_index] = poly_index; \ + mf = &mface[mface_index]; \ + lidx = lindices[mface_index]; \ + /* Set loop indices, transformed to vert indices later. */ \ + l1 = mp_loopstart + 0; /* EXCEPTION */ \ + l2 = mp_loopstart + 1; /* EXCEPTION */ \ + l3 = mp_loopstart + 2; /* EXCEPTION */ \ + l4 = mp_loopstart + 3; /* EXCEPTION */ \ + mf->v1 = mloop[l1].v; \ + mf->v2 = mloop[l2].v; \ + mf->v3 = mloop[l3].v; \ + mf->v4 = mloop[l4].v; \ + lidx[0] = l1; \ + lidx[1] = l2; \ + lidx[2] = l3; \ + lidx[3] = l4; \ + mf->mat_nr = mp->mat_nr; \ + mf->flag = mp->flag; \ + mf->edcode = TESSFACE_IS_QUAD; \ + (void)0 + + else if (mp_totloop == 3) { + ML_TO_MF(0, 1, 2); + mface_index++; + } + else if (mp_totloop == 4) { +# ifdef USE_TESSFACE_QUADS + ML_TO_MF_QUAD(); + mface_index++; +# else + ML_TO_MF(0, 1, 2); + mface_index++; + ML_TO_MF(0, 2, 3); + mface_index++; +# endif + } +#endif /* USE_TESSFACE_SPEEDUP */ + else { + const float *co_curr, *co_prev; + + float normal[3]; + + float axis_mat[3][3]; + float(*projverts)[2]; + uint(*tris)[3]; + + const uint totfilltri = mp_totloop - 2; + + if (UNLIKELY(arena == nullptr)) { + arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__); + } + + tris = (uint(*)[3])BLI_memarena_alloc(arena, sizeof(*tris) * (size_t)totfilltri); + projverts = (float(*)[2])BLI_memarena_alloc(arena, sizeof(*projverts) * (size_t)mp_totloop); + + zero_v3(normal); + + /* Calculate the normal, flipped: to get a positive 2D cross product. */ + ml = mloop + mp_loopstart; + co_prev = mvert[ml[mp_totloop - 1].v].co; + for (j = 0; j < mp_totloop; j++, ml++) { + co_curr = mvert[ml->v].co; + add_newell_cross_v3_v3v3(normal, co_prev, co_curr); + co_prev = co_curr; + } + if (UNLIKELY(normalize_v3(normal) == 0.0f)) { + normal[2] = 1.0f; + } + + /* Project verts to 2D. */ + axis_dominant_v3_to_m3_negate(axis_mat, normal); + + ml = mloop + mp_loopstart; + for (j = 0; j < mp_totloop; j++, ml++) { + mul_v2_m3v3(projverts[j], axis_mat, mvert[ml->v].co); + } + + BLI_polyfill_calc_arena(projverts, mp_totloop, 1, tris, arena); + + /* Apply fill. */ + for (j = 0; j < totfilltri; j++) { + uint *tri = tris[j]; + lidx = lindices[mface_index]; + + mface_to_poly_map[mface_index] = poly_index; + mf = &mface[mface_index]; + + /* Set loop indices, transformed to vert indices later. */ + l1 = mp_loopstart + tri[0]; + l2 = mp_loopstart + tri[1]; + l3 = mp_loopstart + tri[2]; + + mf->v1 = mloop[l1].v; + mf->v2 = mloop[l2].v; + mf->v3 = mloop[l3].v; + mf->v4 = 0; + + lidx[0] = l1; + lidx[1] = l2; + lidx[2] = l3; + lidx[3] = 0; + + mf->mat_nr = mp->mat_nr; + mf->flag = mp->flag; + mf->edcode = 0; + + mface_index++; + } + + BLI_memarena_clear(arena); + } + } + + if (arena) { + BLI_memarena_free(arena); + arena = nullptr; + } + + CustomData_free(fdata, totface); + totface = mface_index; + + BLI_assert(totface <= looptri_num); + + /* Not essential but without this we store over-allocated memory in the #CustomData layers. */ + if (LIKELY(looptri_num != totface)) { + mface = (MFace *)MEM_reallocN(mface, sizeof(*mface) * (size_t)totface); + mface_to_poly_map = (int *)MEM_reallocN(mface_to_poly_map, + sizeof(*mface_to_poly_map) * (size_t)totface); + } + + CustomData_add_layer(fdata, CD_MFACE, CD_ASSIGN, mface, totface); + + /* #CD_ORIGINDEX will contain an array of indices from tessellation-faces to the polygons + * they are directly tessellated from. */ + CustomData_add_layer(fdata, CD_ORIGINDEX, CD_ASSIGN, mface_to_poly_map, totface); + BKE_mesh_add_mface_layers(fdata, ldata, totface); + + /* NOTE: quad detection issue - fourth vertidx vs fourth loopidx: + * Polygons take care of their loops ordering, hence not of their vertices ordering. + * Currently, our tfaces' fourth vertex index might be 0 even for a quad. + * However, we know our fourth loop index is never 0 for quads + * (because they are sorted for polygons, and our quads are still mere copies of their polygons). + * So we pass nullptr as MFace pointer, and #mesh_loops_to_tessdata + * will use the fourth loop index as quad test. */ + mesh_loops_to_tessdata(fdata, ldata, nullptr, mface_to_poly_map, lindices, totface); + + /* NOTE: quad detection issue - fourth vertidx vs fourth loopidx: + * ...However, most TFace code uses 'MFace->v4 == 0' test to check whether it is a tri or quad. + * BKE_mesh_mface_index_validate() will check this and rotate the tessellated face if needed. + */ +#ifdef USE_TESSFACE_QUADS + mf = mface; + for (mface_index = 0; mface_index < totface; mface_index++, mf++) { + if (mf->edcode == TESSFACE_IS_QUAD) { + BKE_mesh_mface_index_validate(mf, fdata, mface_index, 4); + mf->edcode = 0; + } + } +#endif + + MEM_freeN(lindices); + + return totface; + +#undef USE_TESSFACE_SPEEDUP +#undef USE_TESSFACE_QUADS + +#undef ML_TO_MF +#undef ML_TO_MF_QUAD +} + +void BKE_mesh_tessface_calc(Mesh *mesh) +{ + mesh->totface = mesh_tessface_calc(&mesh->fdata, + &mesh->ldata, + &mesh->pdata, + mesh->mvert, + mesh->totface, + mesh->totloop, + mesh->totpoly); + + BKE_mesh_update_customdata_pointers(mesh, true); +} + +void BKE_mesh_tessface_ensure(struct Mesh *mesh) +{ + if (mesh->totpoly && mesh->totface == 0) { + BKE_mesh_tessface_calc(mesh); + } +} + +#ifndef NDEBUG +/** + * Debug check, used to assert when we expect layers to be in/out of sync. + * + * \param fallback: Use when there are no layers to handle, + * since callers may expect success or failure. + */ +static bool check_matching_legacy_layer_counts(CustomData *fdata, CustomData *ldata, bool fallback) +{ + int a_num = 0, b_num = 0; +# define LAYER_CMP(l_a, t_a, l_b, t_b) \ + ((a_num += CustomData_number_of_layers(l_a, t_a)) == \ + (b_num += CustomData_number_of_layers(l_b, t_b))) + + if (!LAYER_CMP(ldata, CD_MLOOPUV, fdata, CD_MTFACE)) { + return false; + } + if (!LAYER_CMP(ldata, CD_PROP_BYTE_COLOR, fdata, CD_MCOL)) { + return false; + } + if (!LAYER_CMP(ldata, CD_PREVIEW_MLOOPCOL, fdata, CD_PREVIEW_MCOL)) { + return false; + } + if (!LAYER_CMP(ldata, CD_ORIGSPACE_MLOOP, fdata, CD_ORIGSPACE)) { + return false; + } + if (!LAYER_CMP(ldata, CD_NORMAL, fdata, CD_TESSLOOPNORMAL)) { + return false; + } + if (!LAYER_CMP(ldata, CD_TANGENT, fdata, CD_TANGENT)) { + return false; + } + +# undef LAYER_CMP + + /* if no layers are on either CustomData's, + * then there was nothing to do... */ + return a_num ? true : fallback; +} +#endif + +void BKE_mesh_add_mface_layers(CustomData *fdata, CustomData *ldata, int total) +{ + /* avoid accumulating extra layers */ + BLI_assert(!check_matching_legacy_layer_counts(fdata, ldata, false)); + + for (int i = 0; i < ldata->totlayer; i++) { + if (ldata->layers[i].type == CD_MLOOPUV) { + CustomData_add_layer_named( + fdata, CD_MTFACE, CD_CALLOC, nullptr, total, ldata->layers[i].name); + } + if (ldata->layers[i].type == CD_PROP_BYTE_COLOR) { + CustomData_add_layer_named(fdata, CD_MCOL, CD_CALLOC, nullptr, total, ldata->layers[i].name); + } + else if (ldata->layers[i].type == CD_PREVIEW_MLOOPCOL) { + CustomData_add_layer_named( + fdata, CD_PREVIEW_MCOL, CD_CALLOC, nullptr, total, ldata->layers[i].name); + } + else if (ldata->layers[i].type == CD_ORIGSPACE_MLOOP) { + CustomData_add_layer_named( + fdata, CD_ORIGSPACE, CD_CALLOC, nullptr, total, ldata->layers[i].name); + } + else if (ldata->layers[i].type == CD_NORMAL) { + CustomData_add_layer_named( + fdata, CD_TESSLOOPNORMAL, CD_CALLOC, nullptr, total, ldata->layers[i].name); + } + else if (ldata->layers[i].type == CD_TANGENT) { + CustomData_add_layer_named( + fdata, CD_TANGENT, CD_CALLOC, nullptr, total, ldata->layers[i].name); + } + } + + CustomData_bmesh_update_active_layers(fdata, ldata); +} + +/** \} */ diff --git a/source/blender/blenkernel/intern/mesh_tessellate.c b/source/blender/blenkernel/intern/mesh_tessellate.c index 71d5722cb0e..173fb98912b 100644 --- a/source/blender/blenkernel/intern/mesh_tessellate.c +++ b/source/blender/blenkernel/intern/mesh_tessellate.c @@ -31,370 +31,6 @@ /** Compared against total loops. */ #define MESH_FACE_TESSELLATE_THREADED_LIMIT 4096 -/* -------------------------------------------------------------------- */ -/** \name MFace Tessellation - * - * #MFace is a legacy data-structure that should be avoided, use #MLoopTri instead. - * \{ */ - -/** - * Convert all CD layers from loop/poly to tessface data. - * - * \param loopindices: is an array of an int[4] per tessface, - * mapping tessface's verts to loops indices. - * - * \note when mface is not NULL, mface[face_index].v4 - * is used to test quads, else, loopindices[face_index][3] is used. - */ -static void mesh_loops_to_tessdata(CustomData *fdata, - CustomData *ldata, - MFace *mface, - const int *polyindices, - uint (*loopindices)[4], - const int num_faces) -{ - /* NOTE(mont29): performances are sub-optimal when we get a NULL #MFace, - * we could be ~25% quicker with dedicated code. - * The issue is, unless having two different functions with nearly the same code, - * there's not much ways to solve this. Better IMHO to live with it for now (sigh). */ - const int numUV = CustomData_number_of_layers(ldata, CD_MLOOPUV); - const int numCol = CustomData_number_of_layers(ldata, CD_PROP_BYTE_COLOR); - const bool hasPCol = CustomData_has_layer(ldata, CD_PREVIEW_MLOOPCOL); - const bool hasOrigSpace = CustomData_has_layer(ldata, CD_ORIGSPACE_MLOOP); - const bool hasLoopNormal = CustomData_has_layer(ldata, CD_NORMAL); - const bool hasLoopTangent = CustomData_has_layer(ldata, CD_TANGENT); - int findex, i, j; - const int *pidx; - uint(*lidx)[4]; - - for (i = 0; i < numUV; i++) { - MTFace *texface = CustomData_get_layer_n(fdata, CD_MTFACE, i); - const MLoopUV *mloopuv = CustomData_get_layer_n(ldata, CD_MLOOPUV, i); - - for (findex = 0, pidx = polyindices, lidx = loopindices; findex < num_faces; - pidx++, lidx++, findex++, texface++) { - for (j = (mface ? mface[findex].v4 : (*lidx)[3]) ? 4 : 3; j--;) { - copy_v2_v2(texface->uv[j], mloopuv[(*lidx)[j]].uv); - } - } - } - - for (i = 0; i < numCol; i++) { - MCol(*mcol)[4] = CustomData_get_layer_n(fdata, CD_MCOL, i); - const MLoopCol *mloopcol = CustomData_get_layer_n(ldata, CD_PROP_BYTE_COLOR, i); - - for (findex = 0, lidx = loopindices; findex < num_faces; lidx++, findex++, mcol++) { - for (j = (mface ? mface[findex].v4 : (*lidx)[3]) ? 4 : 3; j--;) { - MESH_MLOOPCOL_TO_MCOL(&mloopcol[(*lidx)[j]], &(*mcol)[j]); - } - } - } - - if (hasPCol) { - MCol(*mcol)[4] = CustomData_get_layer(fdata, CD_PREVIEW_MCOL); - const MLoopCol *mloopcol = CustomData_get_layer(ldata, CD_PREVIEW_MLOOPCOL); - - for (findex = 0, lidx = loopindices; findex < num_faces; lidx++, findex++, mcol++) { - for (j = (mface ? mface[findex].v4 : (*lidx)[3]) ? 4 : 3; j--;) { - MESH_MLOOPCOL_TO_MCOL(&mloopcol[(*lidx)[j]], &(*mcol)[j]); - } - } - } - - if (hasOrigSpace) { - OrigSpaceFace *of = CustomData_get_layer(fdata, CD_ORIGSPACE); - const OrigSpaceLoop *lof = CustomData_get_layer(ldata, CD_ORIGSPACE_MLOOP); - - for (findex = 0, lidx = loopindices; findex < num_faces; lidx++, findex++, of++) { - for (j = (mface ? mface[findex].v4 : (*lidx)[3]) ? 4 : 3; j--;) { - copy_v2_v2(of->uv[j], lof[(*lidx)[j]].uv); - } - } - } - - if (hasLoopNormal) { - short(*fnors)[4][3] = CustomData_get_layer(fdata, CD_TESSLOOPNORMAL); - const float(*lnors)[3] = CustomData_get_layer(ldata, CD_NORMAL); - - for (findex = 0, lidx = loopindices; findex < num_faces; lidx++, findex++, fnors++) { - for (j = (mface ? mface[findex].v4 : (*lidx)[3]) ? 4 : 3; j--;) { - normal_float_to_short_v3((*fnors)[j], lnors[(*lidx)[j]]); - } - } - } - - if (hasLoopTangent) { - /* Need to do for all UV maps at some point. */ - float(*ftangents)[4] = CustomData_get_layer(fdata, CD_TANGENT); - const float(*ltangents)[4] = CustomData_get_layer(ldata, CD_TANGENT); - - for (findex = 0, pidx = polyindices, lidx = loopindices; findex < num_faces; - pidx++, lidx++, findex++) { - int nverts = (mface ? mface[findex].v4 : (*lidx)[3]) ? 4 : 3; - for (j = nverts; j--;) { - copy_v4_v4(ftangents[findex * 4 + j], ltangents[(*lidx)[j]]); - } - } - } -} - -static int mesh_tessface_calc(CustomData *fdata, - CustomData *ldata, - CustomData *pdata, - MVert *mvert, - int totface, - int totloop, - int totpoly) -{ -#define USE_TESSFACE_SPEEDUP -#define USE_TESSFACE_QUADS - -/* We abuse #MFace.edcode to tag quad faces. See below for details. */ -#define TESSFACE_IS_QUAD 1 - - const int looptri_num = poly_to_tri_count(totpoly, totloop); - - const MPoly *mp, *mpoly; - const MLoop *ml, *mloop; - MFace *mface, *mf; - MemArena *arena = NULL; - int *mface_to_poly_map; - uint(*lindices)[4]; - int poly_index, mface_index; - uint j; - - mpoly = CustomData_get_layer(pdata, CD_MPOLY); - mloop = CustomData_get_layer(ldata, CD_MLOOP); - - /* Allocate the length of `totfaces`, avoid many small reallocation's, - * if all faces are triangles it will be correct, `quads == 2x` allocations. */ - /* Take care since memory is _not_ zeroed so be sure to initialize each field. */ - mface_to_poly_map = MEM_malloc_arrayN((size_t)looptri_num, sizeof(*mface_to_poly_map), __func__); - mface = MEM_malloc_arrayN((size_t)looptri_num, sizeof(*mface), __func__); - lindices = MEM_malloc_arrayN((size_t)looptri_num, sizeof(*lindices), __func__); - - mface_index = 0; - mp = mpoly; - for (poly_index = 0; poly_index < totpoly; poly_index++, mp++) { - const uint mp_loopstart = (uint)mp->loopstart; - const uint mp_totloop = (uint)mp->totloop; - uint l1, l2, l3, l4; - uint *lidx; - if (mp_totloop < 3) { - /* Do nothing. */ - } - -#ifdef USE_TESSFACE_SPEEDUP - -# define ML_TO_MF(i1, i2, i3) \ - mface_to_poly_map[mface_index] = poly_index; \ - mf = &mface[mface_index]; \ - lidx = lindices[mface_index]; \ - /* Set loop indices, transformed to vert indices later. */ \ - l1 = mp_loopstart + i1; \ - l2 = mp_loopstart + i2; \ - l3 = mp_loopstart + i3; \ - mf->v1 = mloop[l1].v; \ - mf->v2 = mloop[l2].v; \ - mf->v3 = mloop[l3].v; \ - mf->v4 = 0; \ - lidx[0] = l1; \ - lidx[1] = l2; \ - lidx[2] = l3; \ - lidx[3] = 0; \ - mf->mat_nr = mp->mat_nr; \ - mf->flag = mp->flag; \ - mf->edcode = 0; \ - (void)0 - -/* ALMOST IDENTICAL TO DEFINE ABOVE (see EXCEPTION) */ -# define ML_TO_MF_QUAD() \ - mface_to_poly_map[mface_index] = poly_index; \ - mf = &mface[mface_index]; \ - lidx = lindices[mface_index]; \ - /* Set loop indices, transformed to vert indices later. */ \ - l1 = mp_loopstart + 0; /* EXCEPTION */ \ - l2 = mp_loopstart + 1; /* EXCEPTION */ \ - l3 = mp_loopstart + 2; /* EXCEPTION */ \ - l4 = mp_loopstart + 3; /* EXCEPTION */ \ - mf->v1 = mloop[l1].v; \ - mf->v2 = mloop[l2].v; \ - mf->v3 = mloop[l3].v; \ - mf->v4 = mloop[l4].v; \ - lidx[0] = l1; \ - lidx[1] = l2; \ - lidx[2] = l3; \ - lidx[3] = l4; \ - mf->mat_nr = mp->mat_nr; \ - mf->flag = mp->flag; \ - mf->edcode = TESSFACE_IS_QUAD; \ - (void)0 - - else if (mp_totloop == 3) { - ML_TO_MF(0, 1, 2); - mface_index++; - } - else if (mp_totloop == 4) { -# ifdef USE_TESSFACE_QUADS - ML_TO_MF_QUAD(); - mface_index++; -# else - ML_TO_MF(0, 1, 2); - mface_index++; - ML_TO_MF(0, 2, 3); - mface_index++; -# endif - } -#endif /* USE_TESSFACE_SPEEDUP */ - else { - const float *co_curr, *co_prev; - - float normal[3]; - - float axis_mat[3][3]; - float(*projverts)[2]; - uint(*tris)[3]; - - const uint totfilltri = mp_totloop - 2; - - if (UNLIKELY(arena == NULL)) { - arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__); - } - - tris = BLI_memarena_alloc(arena, sizeof(*tris) * (size_t)totfilltri); - projverts = BLI_memarena_alloc(arena, sizeof(*projverts) * (size_t)mp_totloop); - - zero_v3(normal); - - /* Calculate the normal, flipped: to get a positive 2D cross product. */ - ml = mloop + mp_loopstart; - co_prev = mvert[ml[mp_totloop - 1].v].co; - for (j = 0; j < mp_totloop; j++, ml++) { - co_curr = mvert[ml->v].co; - add_newell_cross_v3_v3v3(normal, co_prev, co_curr); - co_prev = co_curr; - } - if (UNLIKELY(normalize_v3(normal) == 0.0f)) { - normal[2] = 1.0f; - } - - /* Project verts to 2D. */ - axis_dominant_v3_to_m3_negate(axis_mat, normal); - - ml = mloop + mp_loopstart; - for (j = 0; j < mp_totloop; j++, ml++) { - mul_v2_m3v3(projverts[j], axis_mat, mvert[ml->v].co); - } - - BLI_polyfill_calc_arena(projverts, mp_totloop, 1, tris, arena); - - /* Apply fill. */ - for (j = 0; j < totfilltri; j++) { - uint *tri = tris[j]; - lidx = lindices[mface_index]; - - mface_to_poly_map[mface_index] = poly_index; - mf = &mface[mface_index]; - - /* Set loop indices, transformed to vert indices later. */ - l1 = mp_loopstart + tri[0]; - l2 = mp_loopstart + tri[1]; - l3 = mp_loopstart + tri[2]; - - mf->v1 = mloop[l1].v; - mf->v2 = mloop[l2].v; - mf->v3 = mloop[l3].v; - mf->v4 = 0; - - lidx[0] = l1; - lidx[1] = l2; - lidx[2] = l3; - lidx[3] = 0; - - mf->mat_nr = mp->mat_nr; - mf->flag = mp->flag; - mf->edcode = 0; - - mface_index++; - } - - BLI_memarena_clear(arena); - } - } - - if (arena) { - BLI_memarena_free(arena); - arena = NULL; - } - - CustomData_free(fdata, totface); - totface = mface_index; - - BLI_assert(totface <= looptri_num); - - /* Not essential but without this we store over-allocated memory in the #CustomData layers. */ - if (LIKELY(looptri_num != totface)) { - mface = MEM_reallocN(mface, sizeof(*mface) * (size_t)totface); - mface_to_poly_map = MEM_reallocN(mface_to_poly_map, - sizeof(*mface_to_poly_map) * (size_t)totface); - } - - CustomData_add_layer(fdata, CD_MFACE, CD_ASSIGN, mface, totface); - - /* #CD_ORIGINDEX will contain an array of indices from tessellation-faces to the polygons - * they are directly tessellated from. */ - CustomData_add_layer(fdata, CD_ORIGINDEX, CD_ASSIGN, mface_to_poly_map, totface); - CustomData_from_bmeshpoly(fdata, ldata, totface); - - /* NOTE: quad detection issue - fourth vertidx vs fourth loopidx: - * Polygons take care of their loops ordering, hence not of their vertices ordering. - * Currently, our tfaces' fourth vertex index might be 0 even for a quad. - * However, we know our fourth loop index is never 0 for quads - * (because they are sorted for polygons, and our quads are still mere copies of their polygons). - * So we pass NULL as MFace pointer, and #mesh_loops_to_tessdata - * will use the fourth loop index as quad test. */ - mesh_loops_to_tessdata(fdata, ldata, NULL, mface_to_poly_map, lindices, totface); - - /* NOTE: quad detection issue - fourth vertidx vs fourth loopidx: - * ...However, most TFace code uses 'MFace->v4 == 0' test to check whether it is a tri or quad. - * BKE_mesh_mface_index_validate() will check this and rotate the tessellated face if needed. - */ -#ifdef USE_TESSFACE_QUADS - mf = mface; - for (mface_index = 0; mface_index < totface; mface_index++, mf++) { - if (mf->edcode == TESSFACE_IS_QUAD) { - BKE_mesh_mface_index_validate(mf, fdata, mface_index, 4); - mf->edcode = 0; - } - } -#endif - - MEM_freeN(lindices); - - return totface; - -#undef USE_TESSFACE_SPEEDUP -#undef USE_TESSFACE_QUADS - -#undef ML_TO_MF -#undef ML_TO_MF_QUAD -} - -void BKE_mesh_tessface_calc(Mesh *mesh) -{ - mesh->totface = mesh_tessface_calc(&mesh->fdata, - &mesh->ldata, - &mesh->pdata, - mesh->mvert, - mesh->totface, - mesh->totloop, - mesh->totpoly); - - BKE_mesh_update_customdata_pointers(mesh, true); -} - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Loop Tessellation * diff --git a/source/blender/blenkernel/intern/particle.c b/source/blender/blenkernel/intern/particle.c index b44b70bcd55..b5ff12f3caa 100644 --- a/source/blender/blenkernel/intern/particle.c +++ b/source/blender/blenkernel/intern/particle.c @@ -64,6 +64,7 @@ #include "BKE_pointcache.h" #include "BKE_scene.h" #include "BKE_texture.h" +#include "BKE_mesh_legacy_convert.h" #include "DEG_depsgraph.h" #include "DEG_depsgraph_build.h" diff --git a/source/blender/blenkernel/intern/particle_distribute.c b/source/blender/blenkernel/intern/particle_distribute.c index c461b7f108d..da769515f08 100644 --- a/source/blender/blenkernel/intern/particle_distribute.c +++ b/source/blender/blenkernel/intern/particle_distribute.c @@ -28,6 +28,7 @@ #include "BKE_global.h" #include "BKE_lib_id.h" #include "BKE_mesh.h" +#include "BKE_mesh_legacy_convert.h" #include "BKE_object.h" #include "BKE_particle.h" diff --git a/source/blender/blenkernel/intern/particle_system.c b/source/blender/blenkernel/intern/particle_system.c index 3ad770c5429..4a8f029beee 100644 --- a/source/blender/blenkernel/intern/particle_system.c +++ b/source/blender/blenkernel/intern/particle_system.c @@ -47,6 +47,7 @@ #include "BKE_effect.h" #include "BKE_lib_id.h" #include "BKE_lib_query.h" +#include "BKE_mesh_legacy_convert.h" #include "BKE_particle.h" #include "BKE_bvhutils.h" diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c index 426008e887e..82e24801b66 100644 --- a/source/blender/blenloader/intern/versioning_280.c +++ b/source/blender/blenloader/intern/versioning_280.c @@ -73,6 +73,7 @@ #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_mesh.h" +#include "BKE_mesh_legacy_convert.h" #include "BKE_node.h" #include "BKE_node_tree_update.h" #include "BKE_paint.h" diff --git a/source/blender/draw/intern/draw_cache_impl_particles.c b/source/blender/draw/intern/draw_cache_impl_particles.c index c1d609bf648..16201e809c7 100644 --- a/source/blender/draw/intern/draw_cache_impl_particles.c +++ b/source/blender/draw/intern/draw_cache_impl_particles.c @@ -26,6 +26,7 @@ #include "BKE_mesh.h" #include "BKE_particle.h" #include "BKE_pointcache.h" +#include "BKE_mesh_legacy_convert.h" #include "ED_particle.h" diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc index a6ae1ba5e24..cffa590470f 100644 --- a/source/blender/editors/curves/intern/curves_ops.cc +++ b/source/blender/editors/curves/intern/curves_ops.cc @@ -25,6 +25,7 @@ #include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_mesh.h" +#include "BKE_mesh_legacy_convert.h" #include "BKE_mesh_runtime.h" #include "BKE_object.h" #include "BKE_paint.h" diff --git a/source/blender/editors/physics/particle_edit.c b/source/blender/editors/physics/particle_edit.c index 64d5ee91215..03f9b4eb867 100644 --- a/source/blender/editors/physics/particle_edit.c +++ b/source/blender/editors/physics/particle_edit.c @@ -32,6 +32,7 @@ #include "BKE_global.h" #include "BKE_main.h" #include "BKE_mesh.h" +#include "BKE_mesh_legacy_convert.h" #include "BKE_mesh_runtime.h" #include "BKE_modifier.h" #include "BKE_object.h" diff --git a/source/blender/editors/physics/particle_object.c b/source/blender/editors/physics/particle_object.c index 6bea6e2c19e..96aea0ededf 100644 --- a/source/blender/editors/physics/particle_object.c +++ b/source/blender/editors/physics/particle_object.c @@ -26,6 +26,7 @@ #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_mesh.h" +#include "BKE_mesh_legacy_convert.h" #include "BKE_mesh_runtime.h" #include "BKE_modifier.h" #include "BKE_object.h" diff --git a/source/blender/io/alembic/exporter/abc_writer_hair.cc b/source/blender/io/alembic/exporter/abc_writer_hair.cc index d12eaf07e29..99c609b0235 100644 --- a/source/blender/io/alembic/exporter/abc_writer_hair.cc +++ b/source/blender/io/alembic/exporter/abc_writer_hair.cc @@ -18,6 +18,7 @@ #include "BKE_customdata.h" #include "BKE_mesh.h" +#include "BKE_mesh_legacy_convert.h" #include "BKE_mesh_runtime.h" #include "BKE_particle.h" diff --git a/source/blender/io/collada/collada_utils.cpp b/source/blender/io/collada/collada_utils.cpp index 0c902700b6b..75842734b08 100644 --- a/source/blender/io/collada/collada_utils.cpp +++ b/source/blender/io/collada/collada_utils.cpp @@ -40,6 +40,7 @@ #include "BKE_lib_id.h" #include "BKE_material.h" #include "BKE_mesh.h" +#include "BKE_mesh_legacy_convert.h" #include "BKE_mesh_runtime.h" #include "BKE_node.h" #include "BKE_object.h" diff --git a/source/blender/makesrna/intern/rna_particle.c b/source/blender/makesrna/intern/rna_particle.c index a67b0f7c8e6..3fc98d769b6 100644 --- a/source/blender/makesrna/intern/rna_particle.c +++ b/source/blender/makesrna/intern/rna_particle.c @@ -25,6 +25,7 @@ #include "RNA_enum_types.h" #include "BKE_mesh.h" +#include "BKE_mesh_legacy_convert.h" #include "BLI_listbase.h" diff --git a/source/blender/modifiers/intern/MOD_explode.c b/source/blender/modifiers/intern/MOD_explode.c index 8e85cb1bfb3..f88e930e127 100644 --- a/source/blender/modifiers/intern/MOD_explode.c +++ b/source/blender/modifiers/intern/MOD_explode.c @@ -26,6 +26,7 @@ #include "BKE_lattice.h" #include "BKE_lib_id.h" #include "BKE_mesh.h" +#include "BKE_mesh_legacy_convert.h" #include "BKE_modifier.h" #include "BKE_particle.h" #include "BKE_scene.h" diff --git a/source/blender/modifiers/intern/MOD_particlesystem.cc b/source/blender/modifiers/intern/MOD_particlesystem.cc index f410915cad8..ccbc8f1d835 100644 --- a/source/blender/modifiers/intern/MOD_particlesystem.cc +++ b/source/blender/modifiers/intern/MOD_particlesystem.cc @@ -21,6 +21,7 @@ #include "BKE_editmesh.h" #include "BKE_lib_id.h" #include "BKE_mesh.h" +#include "BKE_mesh_legacy_convert.h" #include "BKE_modifier.h" #include "BKE_particle.h" #include "BKE_screen.h" -- cgit v1.2.3 From 418d82af288a4ffdc805f950dc4b7b49437d311f Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 8 Jul 2022 19:36:31 +1000 Subject: GHOST: add GHOST_utildefines Add macros from BLI_utildefines, mainly to avoid that avoid repetition (ELEM, UNPACK*, CLAMP* & ARRAY_SIZE). Also add macros LIKELY/UNLIKELY as there are quiet a lot of checks for unlikely situations for GHOST/Wayland (not having a keyboard, or mouse for e.g.). --- intern/ghost/CMakeLists.txt | 2 + intern/ghost/intern/GHOST_utildefines.h | 210 +++++++++++++++++++++++ intern/ghost/intern/GHOST_utildefines_variadic.h | 36 ++++ 3 files changed, 248 insertions(+) create mode 100644 intern/ghost/intern/GHOST_utildefines.h create mode 100644 intern/ghost/intern/GHOST_utildefines_variadic.h diff --git a/intern/ghost/CMakeLists.txt b/intern/ghost/CMakeLists.txt index 0203c5ecf5d..edf13b6eff0 100644 --- a/intern/ghost/CMakeLists.txt +++ b/intern/ghost/CMakeLists.txt @@ -65,6 +65,8 @@ set(SRC intern/GHOST_Util.h intern/GHOST_Window.h intern/GHOST_WindowManager.h + intern/GHOST_utildefines.h + intern/GHOST_utildefines_variadic.h ) set(LIB diff --git a/intern/ghost/intern/GHOST_utildefines.h b/intern/ghost/intern/GHOST_utildefines.h new file mode 100644 index 00000000000..f0ae6e12d3e --- /dev/null +++ b/intern/ghost/intern/GHOST_utildefines.h @@ -0,0 +1,210 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup GHOST + * + * Utility defines (avoid depending on `BLI_utildefines.h`). + */ + +#pragma once + +#include "GHOST_utildefines_variadic.h" + +/* -------------------------------------------------------------------- */ +/** \name Branch Prediction Macros + * \{ */ + +/* hints for branch prediction, only use in code that runs a _lot_ where */ +#ifdef __GNUC__ +# define LIKELY(x) __builtin_expect(!!(x), 1) +# define UNLIKELY(x) __builtin_expect(!!(x), 0) +#else +# define LIKELY(x) (x) +# define UNLIKELY(x) (x) +#endif + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Array Unpacking Macros + * \{ */ + +/* unpack vector for args */ +#define UNPACK2(a) ((a)[0]), ((a)[1]) +#define UNPACK3(a) UNPACK2(a), ((a)[2]) +#define UNPACK4(a) UNPACK3(a), ((a)[3]) +/* pre may be '&', '*' or func, post may be '->member' */ +#define UNPACK2_EX(pre, a, post) (pre((a)[0]) post), (pre((a)[1]) post) +#define UNPACK3_EX(pre, a, post) UNPACK2_EX(pre, a, post), (pre((a)[2]) post) +#define UNPACK4_EX(pre, a, post) UNPACK3_EX(pre, a, post), (pre((a)[3]) post) + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Array Macros + * \{ */ + +/* Assuming a static array. */ +#if defined(__GNUC__) && !defined(__cplusplus) && !defined(__clang__) && !defined(__INTEL_COMPILER) +# define ARRAY_SIZE(arr) \ + ((sizeof(struct { int isnt_array : ((const void *)&(arr) == &(arr)[0]); }) * 0) + \ + (sizeof(arr) / sizeof(*(arr)))) +#else +# define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(*(arr))) +#endif + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Equal to Any Element (ELEM) Macro + * \{ */ + +/* Manual line breaks for readability. */ +/* clang-format off */ + +/* ELEM#(v, ...): is the first arg equal any others? */ +/* internal helpers. */ +#define _VA_ELEM2(v, a) ((v) == (a)) +#define _VA_ELEM3(v, a, b) \ + (_VA_ELEM2(v, a) || _VA_ELEM2(v, b)) +#define _VA_ELEM4(v, a, b, c) \ + (_VA_ELEM3(v, a, b) || _VA_ELEM2(v, c)) +#define _VA_ELEM5(v, a, b, c, d) \ + (_VA_ELEM4(v, a, b, c) || _VA_ELEM2(v, d)) +#define _VA_ELEM6(v, a, b, c, d, e) \ + (_VA_ELEM5(v, a, b, c, d) || _VA_ELEM2(v, e)) +#define _VA_ELEM7(v, a, b, c, d, e, f) \ + (_VA_ELEM6(v, a, b, c, d, e) || _VA_ELEM2(v, f)) +#define _VA_ELEM8(v, a, b, c, d, e, f, g) \ + (_VA_ELEM7(v, a, b, c, d, e, f) || _VA_ELEM2(v, g)) +#define _VA_ELEM9(v, a, b, c, d, e, f, g, h) \ + (_VA_ELEM8(v, a, b, c, d, e, f, g) || _VA_ELEM2(v, h)) +#define _VA_ELEM10(v, a, b, c, d, e, f, g, h, i) \ + (_VA_ELEM9(v, a, b, c, d, e, f, g, h) || _VA_ELEM2(v, i)) +#define _VA_ELEM11(v, a, b, c, d, e, f, g, h, i, j) \ + (_VA_ELEM10(v, a, b, c, d, e, f, g, h, i) || _VA_ELEM2(v, j)) +#define _VA_ELEM12(v, a, b, c, d, e, f, g, h, i, j, k) \ + (_VA_ELEM11(v, a, b, c, d, e, f, g, h, i, j) || _VA_ELEM2(v, k)) +#define _VA_ELEM13(v, a, b, c, d, e, f, g, h, i, j, k, l) \ + (_VA_ELEM12(v, a, b, c, d, e, f, g, h, i, j, k) || _VA_ELEM2(v, l)) +#define _VA_ELEM14(v, a, b, c, d, e, f, g, h, i, j, k, l, m) \ + (_VA_ELEM13(v, a, b, c, d, e, f, g, h, i, j, k, l) || _VA_ELEM2(v, m)) +#define _VA_ELEM15(v, a, b, c, d, e, f, g, h, i, j, k, l, m, n) \ + (_VA_ELEM14(v, a, b, c, d, e, f, g, h, i, j, k, l, m) || _VA_ELEM2(v, n)) +#define _VA_ELEM16(v, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) \ + (_VA_ELEM15(v, a, b, c, d, e, f, g, h, i, j, k, l, m, n) || _VA_ELEM2(v, o)) +#define _VA_ELEM17(v, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) \ + (_VA_ELEM16(v, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) || _VA_ELEM2(v, p)) +/* clang-format on */ + +/* reusable ELEM macro */ +#define ELEM(...) VA_NARGS_CALL_OVERLOAD(_VA_ELEM, __VA_ARGS__) + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Clamp Macros + * \{ */ + +#define CLAMPIS(a, b, c) ((a) < (b) ? (b) : (a) > (c) ? (c) : (a)) + +#define CLAMP(a, b, c) \ + { \ + if ((a) < (b)) { \ + (a) = (b); \ + } \ + else if ((a) > (c)) { \ + (a) = (c); \ + } \ + } \ + (void)0 + +#define CLAMP_MAX(a, c) \ + { \ + if ((a) > (c)) { \ + (a) = (c); \ + } \ + } \ + (void)0 + +#define CLAMP_MIN(a, b) \ + { \ + if ((a) < (b)) { \ + (a) = (b); \ + } \ + } \ + (void)0 + +#define CLAMP2(vec, b, c) \ + { \ + CLAMP((vec)[0], b, c); \ + CLAMP((vec)[1], b, c); \ + } \ + (void)0 + +#define CLAMP2_MIN(vec, b) \ + { \ + CLAMP_MIN((vec)[0], b); \ + CLAMP_MIN((vec)[1], b); \ + } \ + (void)0 + +#define CLAMP2_MAX(vec, b) \ + { \ + CLAMP_MAX((vec)[0], b); \ + CLAMP_MAX((vec)[1], b); \ + } \ + (void)0 + +#define CLAMP3(vec, b, c) \ + { \ + CLAMP((vec)[0], b, c); \ + CLAMP((vec)[1], b, c); \ + CLAMP((vec)[2], b, c); \ + } \ + (void)0 + +#define CLAMP3_MIN(vec, b) \ + { \ + CLAMP_MIN((vec)[0], b); \ + CLAMP_MIN((vec)[1], b); \ + CLAMP_MIN((vec)[2], b); \ + } \ + (void)0 + +#define CLAMP3_MAX(vec, b) \ + { \ + CLAMP_MAX((vec)[0], b); \ + CLAMP_MAX((vec)[1], b); \ + CLAMP_MAX((vec)[2], b); \ + } \ + (void)0 + +#define CLAMP4(vec, b, c) \ + { \ + CLAMP((vec)[0], b, c); \ + CLAMP((vec)[1], b, c); \ + CLAMP((vec)[2], b, c); \ + CLAMP((vec)[3], b, c); \ + } \ + (void)0 + +#define CLAMP4_MIN(vec, b) \ + { \ + CLAMP_MIN((vec)[0], b); \ + CLAMP_MIN((vec)[1], b); \ + CLAMP_MIN((vec)[2], b); \ + CLAMP_MIN((vec)[3], b); \ + } \ + (void)0 + +#define CLAMP4_MAX(vec, b) \ + { \ + CLAMP_MAX((vec)[0], b); \ + CLAMP_MAX((vec)[1], b); \ + CLAMP_MAX((vec)[2], b); \ + CLAMP_MAX((vec)[3], b); \ + } \ + (void)0 + +/** \} */ diff --git a/intern/ghost/intern/GHOST_utildefines_variadic.h b/intern/ghost/intern/GHOST_utildefines_variadic.h new file mode 100644 index 00000000000..4ee306a27b2 --- /dev/null +++ b/intern/ghost/intern/GHOST_utildefines_variadic.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup bli + */ + +/* NOTE: copied from `BLI_utildefines_variadic.h` which would be a bad-level include. */ + +/* Over wrapped args. */ +/* clang-format off */ + +/* --- internal helpers --- */ +#define _VA_NARGS_GLUE(x, y) x y +#define _VA_NARGS_RETURN_COUNT(\ + _1_, _2_, _3_, _4_, _5_, _6_, _7_, _8_, _9_, _10_, _11_, _12_, _13_, _14_, _15_, _16_, \ + _17_, _18_, _19_, _20_, _21_, _22_, _23_, _24_, _25_, _26_, _27_, _28_, _29_, _30_, _31_, _32_, \ + _33_, _34_, _35_, _36_, _37_, _38_, _39_, _40_, _41_, _42_, _43_, _44_, _45_, _46_, _47_, _48_, \ + _49_, _50_, _51_, _52_, _53_, _54_, _55_, _56_, _57_, _58_, _59_, _60_, _61_, _62_, _63_, _64_, \ + count, ...) count +#define _VA_NARGS_EXPAND(args) _VA_NARGS_RETURN_COUNT args +#define _VA_NARGS_OVERLOAD_MACRO2(name, count) name##count +#define _VA_NARGS_OVERLOAD_MACRO1(name, count) _VA_NARGS_OVERLOAD_MACRO2(name, count) +#define _VA_NARGS_OVERLOAD_MACRO(name, count) _VA_NARGS_OVERLOAD_MACRO1(name, count) +/* --- expose for re-use --- */ +/* 64 args max */ +#define VA_NARGS_COUNT(...) _VA_NARGS_EXPAND((__VA_ARGS__, \ + 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, \ + 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, \ + 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, \ + 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)) +#define VA_NARGS_CALL_OVERLOAD(name, ...) \ + _VA_NARGS_GLUE(_VA_NARGS_OVERLOAD_MACRO(name, VA_NARGS_COUNT(__VA_ARGS__)), (__VA_ARGS__)) + +/* clang-format on */ -- cgit v1.2.3 From 47616992f89502b93e7b982ee95923f2e6f45f01 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 8 Jul 2022 19:36:33 +1000 Subject: GHOST: use ELEM/ARRAY_SIZE/UNPACK macros to avoid repetition Also use UNLIKELY macro for checks for very unlikely scenarios. --- intern/ghost/intern/GHOST_DropTargetX11.cpp | 3 +- intern/ghost/intern/GHOST_NDOFManager.cpp | 3 +- intern/ghost/intern/GHOST_SystemWayland.cpp | 69 ++++++++++++++--------------- intern/ghost/intern/GHOST_SystemX11.cpp | 32 ++++++------- intern/ghost/intern/GHOST_WindowWayland.cpp | 17 +++---- intern/ghost/intern/GHOST_WindowX11.cpp | 11 ++--- 6 files changed, 69 insertions(+), 66 deletions(-) diff --git a/intern/ghost/intern/GHOST_DropTargetX11.cpp b/intern/ghost/intern/GHOST_DropTargetX11.cpp index 900e46c3732..252a8bfd095 100644 --- a/intern/ghost/intern/GHOST_DropTargetX11.cpp +++ b/intern/ghost/intern/GHOST_DropTargetX11.cpp @@ -7,6 +7,7 @@ #include "GHOST_DropTargetX11.h" #include "GHOST_Debug.h" +#include "GHOST_utildefines.h" #include #include @@ -34,7 +35,7 @@ int GHOST_DropTargetX11::m_refCounter = 0; void GHOST_DropTargetX11::Initialize() { Display *display = m_system->getXDisplay(); - int dndTypesCount = sizeof(m_dndMimeTypes) / sizeof(char *); + int dndTypesCount = ARRAY_SIZE(m_dndMimeTypes); int counter; xdnd_init(&m_dndClass, display); diff --git a/intern/ghost/intern/GHOST_NDOFManager.cpp b/intern/ghost/intern/GHOST_NDOFManager.cpp index 2298ba86521..d58fb90f63e 100644 --- a/intern/ghost/intern/GHOST_NDOFManager.cpp +++ b/intern/ghost/intern/GHOST_NDOFManager.cpp @@ -5,6 +5,7 @@ #include "GHOST_EventKey.h" #include "GHOST_EventNDOF.h" #include "GHOST_WindowManager.h" +#include "GHOST_utildefines.h" #include #include @@ -128,7 +129,7 @@ static const NDOF_ButtonT Generic_HID_map[] = { NDOF_BUTTON_C, }; -static const int genericButtonCount = sizeof(Generic_HID_map) / sizeof(NDOF_ButtonT); +static const int genericButtonCount = ARRAY_SIZE(Generic_HID_map); GHOST_NDOFManager::GHOST_NDOFManager(GHOST_System &sys) : m_system(sys), diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 164531f3847..f6406d098ca 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -13,6 +13,7 @@ #include "GHOST_EventWheel.h" #include "GHOST_TimerManager.h" #include "GHOST_WindowManager.h" +#include "GHOST_utildefines.h" #include "GHOST_ContextEGL.h" @@ -628,7 +629,7 @@ static GHOST_TKey xkb_map_gkey_or_scan_code(const xkb_keysym_t sym, const uint32 { GHOST_TKey gkey = xkb_map_gkey(sym); - if (gkey == GHOST_kKeyUnknown) { + if (UNLIKELY(gkey == GHOST_kKeyUnknown)) { /* Fall back to physical location for keys that would otherwise do nothing. */ switch (key) { case KEY_GRAVE: { @@ -774,7 +775,7 @@ static void relative_pointer_handle_relative_motion_impl(input_t *input, bounds.m_t = wl_fixed_from_int(bounds.m_t) / scale; bounds.m_r = wl_fixed_from_int(bounds.m_r) / scale; bounds.m_b = wl_fixed_from_int(bounds.m_b) / scale; - bounds.clampPoint(input->pointer.xy[0], input->pointer.xy[1]); + bounds.clampPoint(UNPACK2(input->pointer.xy)); } #endif input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(), @@ -831,7 +832,7 @@ static void dnd_events(const input_t *const input, const GHOST_TEventType event) const uint64_t time = input->system->getMilliSeconds(); for (const std::string &type : mime_preference_order) { input->system->pushEvent(new GHOST_EventDragnDrop( - time, event, mime_dnd.at(type), win, event_xy[0], event_xy[1], nullptr)); + time, event, mime_dnd.at(type), win, UNPACK2(event_xy), nullptr)); } } } @@ -841,7 +842,7 @@ static std::string read_pipe(data_offer_t *data_offer, std::mutex *mutex) { int pipefd[2]; - if (pipe(pipefd) != 0) { + if (UNLIKELY(pipe(pipefd) != 0)) { return {}; } wl_data_offer_receive(data_offer->id, mime_receive.c_str(), pipefd[1]); @@ -1076,7 +1077,7 @@ static void data_device_handle_drop(void *data, struct wl_data_device * /*wl_dat data_offer_t *data_offer, wl_surface *surface, const std::string mime_receive) { - const wl_fixed_t xy[2] = {data_offer->dnd.xy[0], data_offer->dnd.xy[1]}; + const wl_fixed_t xy[2] = {UNPACK2(data_offer->dnd.xy)}; const std::string data = read_pipe(data_offer, mime_receive, nullptr); @@ -1126,7 +1127,7 @@ static void data_device_handle_drop(void *data, struct wl_data_device * /*wl_dat wl_fixed_to_int(scale * xy[1]), flist)); } - else if (mime_receive == mime_text_plain || mime_receive == mime_text_utf8) { + else if (ELEM(mime_receive, mime_text_plain, mime_text_utf8)) { /* TODO: enable use of internal functions 'txt_insert_buf' and * 'text_update_edited' to behave like dropped text was pasted. */ } @@ -1602,8 +1603,8 @@ static void tablet_tool_handle_tilt(void *data, /* Map degrees to `-1.0..1.0`. */ td.Xtilt = wl_fixed_to_double(tilt_x) / 90.0f; td.Ytilt = wl_fixed_to_double(tilt_y) / 90.0f; - td.Xtilt = td.Xtilt < -1.0f ? -1.0f : (td.Xtilt > 1.0f ? 1.0f : td.Xtilt); - td.Ytilt = td.Ytilt < -1.0f ? -1.0f : (td.Ytilt > 1.0f ? 1.0f : td.Ytilt); + CLAMP(td.Xtilt, -1.0f, 1.0f); + CLAMP(td.Ytilt, -1.0f, 1.0f); } static void tablet_tool_handle_rotation(void * /*data*/, @@ -2559,7 +2560,7 @@ int GHOST_SystemWayland::setConsoleWindowState(GHOST_TConsoleWindowState /*actio GHOST_TSuccess GHOST_SystemWayland::getModifierKeys(GHOST_ModifierKeys &keys) const { - if (d->inputs.empty()) { + if (UNLIKELY(d->inputs.empty())) { return GHOST_kFailure; } @@ -2594,7 +2595,7 @@ GHOST_TSuccess GHOST_SystemWayland::getModifierKeys(GHOST_ModifierKeys &keys) co GHOST_TSuccess GHOST_SystemWayland::getButtons(GHOST_Buttons &buttons) const { - if (d->inputs.empty()) { + if (UNLIKELY(d->inputs.empty())) { return GHOST_kFailure; } input_t *input = d->inputs[0]; @@ -2616,7 +2617,7 @@ char *GHOST_SystemWayland::getClipboard(bool /*selection*/) const void GHOST_SystemWayland::putClipboard(const char *buffer, bool /*selection*/) const { - if (!d->data_device_manager || d->inputs.empty()) { + if (UNLIKELY(!d->data_device_manager || d->inputs.empty())) { return; } @@ -2690,7 +2691,7 @@ GHOST_TSuccess GHOST_SystemWayland::getCursorPositionClientRelative(const GHOST_ int32_t &x, int32_t &y) const { - if (d->inputs.empty()) { + if (UNLIKELY(d->inputs.empty())) { return GHOST_kFailure; } input_t *input = d->inputs[0]; @@ -2706,7 +2707,7 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorPositionClientRelative(GHOST_IWindo const int32_t x, const int32_t y) { - if (d->inputs.empty()) { + if (UNLIKELY(d->inputs.empty())) { return GHOST_kFailure; } input_t *input = d->inputs[0]; @@ -2716,7 +2717,7 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorPositionClientRelative(GHOST_IWindo GHOST_TSuccess GHOST_SystemWayland::getCursorPosition(int32_t &x, int32_t &y) const { - if (d->inputs.empty()) { + if (UNLIKELY(d->inputs.empty())) { return GHOST_kFailure; } input_t *input = d->inputs[0]; @@ -2734,7 +2735,7 @@ GHOST_TSuccess GHOST_SystemWayland::getCursorPosition(int32_t &x, int32_t &y) co GHOST_TSuccess GHOST_SystemWayland::setCursorPosition(const int32_t x, const int32_t y) { - if (d->inputs.empty()) { + if (UNLIKELY(d->inputs.empty())) { return GHOST_kFailure; } input_t *input = d->inputs[0]; @@ -3054,7 +3055,7 @@ static bool cursor_is_software(const GHOST_TGrabCursorMode mode, const bool use_ GHOST_TSuccess GHOST_SystemWayland::setCursorShape(const GHOST_TStandardCursor shape) { - if (d->inputs.empty()) { + if (UNLIKELY(d->inputs.empty())) { return GHOST_kFailure; } auto cursor_find = cursors.find(shape); @@ -3115,7 +3116,7 @@ GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap, const int hotY, const bool /*canInvertColor*/) { - if (d->inputs.empty()) { + if (UNLIKELY(d->inputs.empty())) { return GHOST_kFailure; } @@ -3145,18 +3146,18 @@ GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap, free(tmpname); #endif - if (fd < 0) { + if (UNLIKELY(fd < 0)) { return GHOST_kFailure; } - if (posix_fallocate(fd, 0, int32_t(cursor->file_buffer->size)) != 0) { + if (UNLIKELY(posix_fallocate(fd, 0, int32_t(cursor->file_buffer->size)) != 0)) { return GHOST_kFailure; } cursor->file_buffer->data = mmap( nullptr, cursor->file_buffer->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (cursor->file_buffer->data == MAP_FAILED) { + if (UNLIKELY(cursor->file_buffer->data == MAP_FAILED)) { cursor->file_buffer->data = nullptr; close(fd); return GHOST_kFailure; @@ -3239,7 +3240,7 @@ GHOST_TSuccess GHOST_SystemWayland::getCursorBitmap(GHOST_CursorBitmapRef *bitma GHOST_TSuccess GHOST_SystemWayland::setCursorVisibility(const bool visible) { - if (d->inputs.empty()) { + if (UNLIKELY(d->inputs.empty())) { return GHOST_kFailure; } @@ -3263,7 +3264,7 @@ bool GHOST_SystemWayland::supportsWindowPosition() bool GHOST_SystemWayland::getCursorGrabUseSoftwareDisplay(const GHOST_TGrabCursorMode mode) { - if (d->inputs.empty()) { + if (UNLIKELY(d->inputs.empty())) { return false; } @@ -3309,7 +3310,7 @@ static input_grab_state_t input_grab_state_from_mode(const GHOST_TGrabCursorMode /* Initialize all members. */ const struct input_grab_state_t grab_state = { /* Warping happens to require software cursor which also hides. */ - .use_lock = (mode == GHOST_kGrabWrap || mode == GHOST_kGrabHide) || use_software_confine, + .use_lock = ELEM(mode, GHOST_kGrabWrap, GHOST_kGrabHide) || use_software_confine, .use_confine = (mode == GHOST_kGrabNormal) && (use_software_confine == false), }; return grab_state; @@ -3455,11 +3456,11 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod const int scale) { /* Ignore, if the required protocols are not supported. */ - if (!d->relative_pointer_manager || !d->pointer_constraints) { + if (UNLIKELY(!d->relative_pointer_manager || !d->pointer_constraints)) { return GHOST_kFailure; } - if (d->inputs.empty()) { + if (UNLIKELY(d->inputs.empty())) { return GHOST_kFailure; } /* No change, success. */ @@ -3483,9 +3484,7 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod mode, use_software_confine); /* Check for wrap as #supportsCursorWarp isn't supported. */ - const bool use_visible = !(((mode == GHOST_kGrabHide) || (mode == GHOST_kGrabWrap)) || - use_software_confine); - + const bool use_visible = !(ELEM(mode, GHOST_kGrabHide, GHOST_kGrabWrap) || use_software_confine); const bool is_hardware_cursor = !cursor_is_software(mode, use_software_confine); /* Only hide so the cursor is not made visible before it's location is restored. @@ -3505,7 +3504,7 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod if (mode_current == GHOST_kGrabWrap) { /* Since this call is initiated by Blender, we can be sure the window wasn't closed * by logic outside this function - as the window was needed to make this call. */ - int32_t xy_new[2] = {input->pointer.xy[0], input->pointer.xy[1]}; + int32_t xy_new[2] = {UNPACK2(input->pointer.xy)}; GHOST_Rect bounds_scale; @@ -3514,7 +3513,7 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod bounds_scale.m_r = wl_fixed_from_int(wrap_bounds->m_r) / scale; bounds_scale.m_b = wl_fixed_from_int(wrap_bounds->m_b) / scale; - bounds_scale.wrapPoint(xy_new[0], xy_new[1], 0, wrap_axis); + bounds_scale.wrapPoint(UNPACK2(xy_new), 0, wrap_axis); /* Push an event so the new location is registered. */ if ((xy_new[0] != input->pointer.xy[0]) || (xy_new[1] != input->pointer.xy[1])) { @@ -3528,8 +3527,7 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod input->pointer.xy[0] = xy_new[0]; input->pointer.xy[1] = xy_new[1]; - zwp_locked_pointer_v1_set_cursor_position_hint( - input->locked_pointer, xy_new[0], xy_new[1]); + zwp_locked_pointer_v1_set_cursor_position_hint(input->locked_pointer, UNPACK2(xy_new)); wl_surface_commit(surface); } else if (mode_current == GHOST_kGrabHide) { @@ -3539,16 +3537,15 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod wl_fixed_from_int(init_grab_xy[0]) / scale, wl_fixed_from_int(init_grab_xy[1]) / scale, }; - zwp_locked_pointer_v1_set_cursor_position_hint( - input->locked_pointer, xy_next[0], xy_next[1]); + zwp_locked_pointer_v1_set_cursor_position_hint(input->locked_pointer, UNPACK2(xy_next)); wl_surface_commit(surface); } } #ifdef USE_GNOME_CONFINE_HACK else if (mode_current == GHOST_kGrabNormal) { if (was_software_confine) { - zwp_locked_pointer_v1_set_cursor_position_hint( - input->locked_pointer, input->pointer.xy[0], input->pointer.xy[1]); + zwp_locked_pointer_v1_set_cursor_position_hint(input->locked_pointer, + UNPACK2(input->pointer.xy)); wl_surface_commit(surface); } } diff --git a/intern/ghost/intern/GHOST_SystemX11.cpp b/intern/ghost/intern/GHOST_SystemX11.cpp index 0023ff639f4..5e549b54afd 100644 --- a/intern/ghost/intern/GHOST_SystemX11.cpp +++ b/intern/ghost/intern/GHOST_SystemX11.cpp @@ -25,6 +25,7 @@ #ifdef WITH_INPUT_NDOF # include "GHOST_NDOFManagerUnix.h" #endif +#include "GHOST_utildefines.h" #ifdef WITH_XDND # include "GHOST_DropTargetX11.h" @@ -663,7 +664,7 @@ bool GHOST_SystemX11::processEvents(bool waitForEvent) /* open connection to XIM server and create input context (XIC) * when receiving the first FocusIn or KeyPress event after startup, * or recover XIM and XIC when the XIM server has been restarted */ - if (xevent.type == FocusIn || xevent.type == KeyPress) { + if (ELEM(xevent.type, FocusIn, KeyPress)) { if (!m_xim && openX11_IM()) { GHOST_PRINT("Connected to XIM server\n"); } @@ -724,9 +725,9 @@ bool GHOST_SystemX11::processEvents(bool waitForEvent) * in order to confirm the window is active. */ XPeekEvent(m_display, &xev_next); - if (xev_next.type == KeyPress || xev_next.type == KeyRelease) { - /* XK_Hyper_L/R currently unused */ - const static KeySym modifiers[8] = { + if (ELEM(xev_next.type, KeyPress, KeyRelease)) { + /* XK_Hyper_L/R currently unused. */ + const static KeySym modifiers[] = { XK_Shift_L, XK_Shift_R, XK_Control_L, @@ -737,7 +738,7 @@ bool GHOST_SystemX11::processEvents(bool waitForEvent) XK_Super_R, }; - for (int i = 0; i < (int)(sizeof(modifiers) / sizeof(*modifiers)); i++) { + for (int i = 0; i < (int)ARRAY_SIZE(modifiers); i++) { KeyCode kc = XKeysymToKeycode(m_display, modifiers[i]); if (kc != 0 && ((xevent.xkeymap.key_vector[kc >> 3] >> (kc & 7)) & 1) != 0) { pushEvent(new GHOST_EventKey(getMilliSeconds(), @@ -822,7 +823,7 @@ void GHOST_SystemX11::processEvent(XEvent *xe) /* Detect auto-repeat. */ bool is_repeat = false; - if (xe->type == KeyPress || xe->type == KeyRelease) { + if (ELEM(xe->type, KeyPress, KeyRelease)) { XKeyEvent *xke = &(xe->xkey); /* Set to true if this key will repeat. */ @@ -889,9 +890,11 @@ void GHOST_SystemX11::processEvent(XEvent *xe) if (xe->type == xi_presence) { XDevicePresenceNotifyEvent *notify_event = (XDevicePresenceNotifyEvent *)xe; - if ((notify_event->devchange == DeviceEnabled) || - (notify_event->devchange == DeviceDisabled) || - (notify_event->devchange == DeviceAdded) || (notify_event->devchange == DeviceRemoved)) { + if (ELEM(notify_event->devchange, + DeviceEnabled, + DeviceDisabled, + DeviceAdded, + DeviceRemoved)) { refreshXInputDevices(); /* update all window events */ @@ -1164,7 +1167,7 @@ void GHOST_SystemX11::processEvent(XEvent *xe) len = Xutf8LookupString(xic, xke, utf8_buf, len, &key_sym, &status); } - if ((status == XLookupChars || status == XLookupBoth)) { + if (ELEM(status, XLookupChars, XLookupBoth)) { if ((unsigned char)utf8_buf[0] >= 32) { /* not an ascii control character */ /* do nothing for now, this is valid utf8 */ } @@ -1449,8 +1452,7 @@ void GHOST_SystemX11::processEvent(XEvent *xe) nxe.xselection.time = xse->time; /* Check to see if the requester is asking for String */ - if (xse->target == utf8_string || xse->target == string || xse->target == compound_text || - xse->target == c_string) { + if (ELEM(xse->target, utf8_string, string, compound_text, c_string)) { if (xse->selection == XInternAtom(m_display, "PRIMARY", False)) { XChangeProperty(m_display, xse->requestor, @@ -1503,7 +1505,7 @@ void GHOST_SystemX11::processEvent(XEvent *xe) default: { #ifdef WITH_X11_XINPUT for (GHOST_TabletX11 &xtablet : m_xtablets) { - if (xe->type == xtablet.MotionEvent || xe->type == xtablet.PressEvent) { + if (ELEM(xe->type, xtablet.MotionEvent, xtablet.PressEvent)) { XDeviceMotionEvent *data = (XDeviceMotionEvent *)xe; if (data->deviceid != xtablet.ID) { continue; @@ -2574,7 +2576,7 @@ int GHOST_X11_ApplicationIOErrorHandler(Display * /*display*/) static bool is_filler_char(char c) { - return isspace(c) || c == '_' || c == '-' || c == ';' || c == ':'; + return isspace(c) || ELEM(c, '_', '-', ';', ':'); } /* These C functions are copied from Wine 3.12's `wintab.c` */ @@ -2674,7 +2676,7 @@ void GHOST_SystemX11::refreshXInputDevices() XFree((void *)device_type); } - if (!(tablet_mode == GHOST_kTabletModeStylus || tablet_mode == GHOST_kTabletModeEraser)) { + if (!ELEM(tablet_mode, GHOST_kTabletModeStylus, GHOST_kTabletModeEraser)) { continue; } diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp index a04ff23a1a1..a5177a25ef7 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cpp +++ b/intern/ghost/intern/GHOST_WindowWayland.cpp @@ -8,6 +8,7 @@ #include "GHOST_SystemWayland.h" #include "GHOST_WaylandUtils.h" #include "GHOST_WindowManager.h" +#include "GHOST_utildefines.h" #include "GHOST_Event.h" @@ -215,7 +216,7 @@ static void frame_handle_configure(struct libdecor_frame *frame, win->size[0] = win->scale * size_next[0]; win->size[1] = win->scale * size_next[1]; - wl_egl_window_resize(win->egl_window, win->size[0], win->size[1], 0, 0); + wl_egl_window_resize(win->egl_window, UNPACK2(win->size), 0, 0); win->w->notify_size(); if (!libdecor_configuration_get_window_state(configuration, &window_state)) { @@ -228,7 +229,7 @@ static void frame_handle_configure(struct libdecor_frame *frame, win->is_active ? win->w->activate() : win->w->deactivate(); - state = libdecor_state_new(size_next[0], size_next[1]); + state = libdecor_state_new(UNPACK2(size_next)); libdecor_frame_commit(frame, state, configuration); libdecor_state_free(state); @@ -298,7 +299,7 @@ static void xdg_surface_handle_configure(void *data, if (win->size_pending[0] != 0 && win->size_pending[1] != 0) { win->size[0] = win->size_pending[0]; win->size[1] = win->size_pending[1]; - wl_egl_window_resize(win->egl_window, win->size[0], win->size[1], 0, 0); + wl_egl_window_resize(win->egl_window, UNPACK2(win->size), 0, 0); win->size_pending[0] = 0; win->size_pending[1] = 0; win->w->notify_size(); @@ -433,7 +434,7 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, m_system->decor_context(), w->wl_surface, &libdecor_frame_iface, w); libdecor_frame_map(w->decor_frame); - libdecor_frame_set_min_content_size(w->decor_frame, size_min[0], size_min[1]); + libdecor_frame_set_min_content_size(w->decor_frame, UNPACK2(size_min)); if (parentWindow) { libdecor_frame_set_parent( @@ -443,7 +444,7 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, w->xdg_surface = xdg_wm_base_get_xdg_surface(m_system->xdg_shell(), w->wl_surface); w->xdg_toplevel = xdg_surface_get_toplevel(w->xdg_surface); - xdg_toplevel_set_min_size(w->xdg_toplevel, size_min[0], size_min[1]); + xdg_toplevel_set_min_size(w->xdg_toplevel, UNPACK2(size_min)); if (m_system->xdg_decoration_manager()) { w->xdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( @@ -569,7 +570,7 @@ void GHOST_WindowWayland::getWindowBounds(GHOST_Rect &bounds) const void GHOST_WindowWayland::getClientBounds(GHOST_Rect &bounds) const { - bounds.set(0, 0, w->size[0], w->size[1]); + bounds.set(0, 0, UNPACK2(w->size)); } GHOST_TSuccess GHOST_WindowWayland::setClientWidth(const uint32_t width) @@ -760,7 +761,7 @@ void GHOST_WindowWayland::setOpaque() const /* Make the window opaque. */ region = wl_compositor_create_region(m_system->compositor()); - wl_region_add(region, 0, 0, w->size[0], w->size[1]); + wl_region_add(region, 0, 0, UNPACK2(w->size)); wl_surface_set_opaque_region(w->surface, region); wl_region_destroy(region); } @@ -890,7 +891,7 @@ bool GHOST_WindowWayland::outputs_changed_update_scale() { uint32_t dpi_next; const int scale_next = outputs_max_scale_or_default(this->outputs(), 0, &dpi_next); - if (scale_next == 0) { + if (UNLIKELY(scale_next == 0)) { return false; } diff --git a/intern/ghost/intern/GHOST_WindowX11.cpp b/intern/ghost/intern/GHOST_WindowX11.cpp index ac7a476c76f..01045c516c1 100644 --- a/intern/ghost/intern/GHOST_WindowX11.cpp +++ b/intern/ghost/intern/GHOST_WindowX11.cpp @@ -17,6 +17,7 @@ #include "GHOST_IconX11.h" #include "GHOST_SystemX11.h" #include "GHOST_WindowX11.h" +#include "GHOST_utildefines.h" #ifdef WITH_XDND # include "GHOST_DropTargetX11.h" @@ -296,7 +297,7 @@ GHOST_WindowX11::GHOST_WindowX11(GHOST_SystemX11 *system, GHOST_PRINT("Set drop target\n"); #endif - if (state == GHOST_kWindowStateMaximized || state == GHOST_kWindowStateFullScreen) { + if (ELEM(state, GHOST_kWindowStateMaximized, GHOST_kWindowStateFullScreen)) { Atom atoms[2]; int count = 0; if (state == GHOST_kWindowStateMaximized) { @@ -412,7 +413,7 @@ GHOST_WindowX11::GHOST_WindowX11(GHOST_SystemX11 *system, 32, PropModeReplace, (unsigned char *)BLENDER_ICONS_WM_X11, - sizeof(BLENDER_ICONS_WM_X11) / sizeof(unsigned long)); + ARRAY_SIZE(BLENDER_ICONS_WM_X11)); } /* set the process ID (_NET_WM_PID) */ @@ -984,7 +985,7 @@ GHOST_TWindowState GHOST_WindowX11::getState() const * In the Iconic and Withdrawn state, the window * is unmapped, so only need return a Minimized state. */ - if ((state == IconicState) || (state == WithdrawnState)) { + if (ELEM(state, IconicState, WithdrawnState)) { state_ret = GHOST_kWindowStateMinimized; } else if (netwmIsFullScreen() == True) { @@ -1514,7 +1515,7 @@ GHOST_TSuccess GHOST_WindowX11::setWindowCursorGrab(GHOST_TGrabCursorMode mode) { if (mode != GHOST_kGrabDisable) { if (mode != GHOST_kGrabNormal) { - m_system->getCursorPosition(m_cursorGrabInitPos[0], m_cursorGrabInitPos[1]); + m_system->getCursorPosition(UNPACK2(m_cursorGrabInitPos)); setCursorGrabAccum(0, 0); if (mode == GHOST_kGrabHide) { @@ -1535,7 +1536,7 @@ GHOST_TSuccess GHOST_WindowX11::setWindowCursorGrab(GHOST_TGrabCursorMode mode) } else { if (m_cursorGrab == GHOST_kGrabHide) { - m_system->setCursorPosition(m_cursorGrabInitPos[0], m_cursorGrabInitPos[1]); + m_system->setCursorPosition(UNPACK2(m_cursorGrabInitPos)); } if (m_cursorGrab != GHOST_kGrabNormal) { -- cgit v1.2.3 From 754dae6c7667fd861b6e663c6d33affcd687a251 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 8 Jul 2022 19:36:34 +1000 Subject: GHOST/Wayland: add logging for listener handlers Add logging to all Wayland listener callbacks as it can be difficult to detect the cause of problems. Using break-points often isn't practical for debugging interactive windowing / compositor issues Logging needs to be enabled on the command line, e.g: blender --log "ghost.wl.*" --log-level 2 --log-show-basename --- intern/ghost/CMakeLists.txt | 1 + intern/ghost/intern/GHOST_SystemWayland.cpp | 305 ++++++++++++++++++++++++---- intern/ghost/intern/GHOST_WindowWayland.cpp | 52 ++++- 3 files changed, 316 insertions(+), 42 deletions(-) diff --git a/intern/ghost/CMakeLists.txt b/intern/ghost/CMakeLists.txt index edf13b6eff0..c681dc368bb 100644 --- a/intern/ghost/CMakeLists.txt +++ b/intern/ghost/CMakeLists.txt @@ -3,6 +3,7 @@ set(INC . + ../clog ../glew-mx ../../source/blender/imbuf ../../source/blender/makesdna diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index f6406d098ca..08aa640c5cd 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -57,6 +57,9 @@ #include #include +/* Logging, use `ghost.wl.*` prefix. */ +#include "CLG_log.h" + static void keyboard_handle_key_repeat_cancel(struct input_t *input); static void output_handle_done(void *data, struct wl_output *wl_output); @@ -356,6 +359,8 @@ struct display_t { struct zwp_pointer_constraints_v1 *pointer_constraints = nullptr; }; +#undef LOG + /** \} */ /* -------------------------------------------------------------------- */ @@ -742,6 +747,8 @@ static const std::vector mime_send = { "text/plain", }; +#undef LOG + /** \} */ /* -------------------------------------------------------------------- */ @@ -751,6 +758,9 @@ static const std::vector mime_send = { * an event is received from the compositor. * \{ */ +static CLG_LogRef LOG_WL_RELATIVE_POINTER = {"ghost.wl.handle.relative_pointer"}; +#define LOG (&LOG_WL_RELATIVE_POINTER) + /** * The caller is responsible for setting the value of `input->xy`. */ @@ -798,6 +808,7 @@ static void relative_pointer_handle_relative_motion( { input_t *input = static_cast(data); if (wl_surface *focus_surface = input->pointer.wl_surface) { + CLOG_INFO(LOG, 2, "relative_motion"); GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface); const wl_fixed_t scale = win->scale(); const wl_fixed_t xy_next[2] = { @@ -806,18 +817,26 @@ static void relative_pointer_handle_relative_motion( }; relative_pointer_handle_relative_motion_impl(input, win, xy_next); } + else { + CLOG_INFO(LOG, 2, "relative_motion (skipped)"); + } } static const zwp_relative_pointer_v1_listener relative_pointer_listener = { relative_pointer_handle_relative_motion, }; +#undef LOG + /** \} */ /* -------------------------------------------------------------------- */ /** \name Listener (Data Source), #wl_data_source_listener * \{ */ +static CLG_LogRef LOG_WL_DATA_SOURCE = {"ghost.wl.handle.data_source"}; +#define LOG (&LOG_WL_DATA_SOURCE) + static void dnd_events(const input_t *const input, const GHOST_TEventType event) { /* NOTE: `input->data_offer_dnd_mutex` must already be locked. */ @@ -876,7 +895,7 @@ static void data_source_handle_target(void * /*data*/, struct wl_data_source * /*wl_data_source*/, const char * /*mime_type*/) { - /* pass */ + CLOG_INFO(LOG, 2, "target"); } static void data_source_handle_send(void *data, @@ -887,6 +906,8 @@ static void data_source_handle_send(void *data, input_t *input = static_cast(data); std::lock_guard lock{input->data_source_mutex}; + CLOG_INFO(LOG, 2, "send"); + const char *const buffer = input->data_source->buffer_out; if (write(fd, buffer, strlen(buffer)) < 0) { GHOST_PRINT("error writing to clipboard: " << std::strerror(errno) << std::endl); @@ -896,6 +917,7 @@ static void data_source_handle_send(void *data, static void data_source_handle_cancelled(void * /*data*/, struct wl_data_source *wl_data_source) { + CLOG_INFO(LOG, 2, "cancelled"); wl_data_source_destroy(wl_data_source); } @@ -909,7 +931,7 @@ static void data_source_handle_cancelled(void * /*data*/, struct wl_data_source static void data_source_handle_dnd_drop_performed(void * /*data*/, struct wl_data_source * /*wl_data_source*/) { - /* pass */ + CLOG_INFO(LOG, 2, "dnd_drop_performed"); } /** @@ -922,7 +944,7 @@ static void data_source_handle_dnd_drop_performed(void * /*data*/, static void data_source_handle_dnd_finished(void * /*data*/, struct wl_data_source * /*wl_data_source*/) { - /* pass */ + CLOG_INFO(LOG, 2, "dnd_finished"); } /** @@ -934,9 +956,9 @@ static void data_source_handle_dnd_finished(void * /*data*/, */ static void data_source_handle_action(void * /*data*/, struct wl_data_source * /*wl_data_source*/, - const uint32_t /*dnd_action*/) + const uint32_t dnd_action) { - /* pass */ + CLOG_INFO(LOG, 2, "handle_action (dnd_action=%u)", dnd_action); } static const struct wl_data_source_listener data_source_listener = { @@ -948,16 +970,22 @@ static const struct wl_data_source_listener data_source_listener = { data_source_handle_action, }; +#undef LOG + /** \} */ /* -------------------------------------------------------------------- */ /** \name Listener (Data Offer), #wl_data_offer_listener * \{ */ +static CLG_LogRef LOG_WL_DATA_OFFER = {"ghost.wl.handle.data_offer"}; +#define LOG (&LOG_WL_DATA_OFFER) + static void data_offer_handle_offer(void *data, struct wl_data_offer * /*wl_data_offer*/, const char *mime_type) { + CLOG_INFO(LOG, 2, "offer (mime_type=%s)", mime_type); static_cast(data)->types.insert(mime_type); } @@ -965,6 +993,7 @@ static void data_offer_handle_source_actions(void *data, struct wl_data_offer * /*wl_data_offer*/, const uint32_t source_actions) { + CLOG_INFO(LOG, 2, "source_actions (%u)", source_actions); static_cast(data)->source_actions = source_actions; } @@ -972,6 +1001,7 @@ static void data_offer_handle_action(void *data, struct wl_data_offer * /*wl_data_offer*/, const uint32_t dnd_action) { + CLOG_INFO(LOG, 2, "actions (%u)", dnd_action); static_cast(data)->dnd_action = dnd_action; } @@ -981,16 +1011,23 @@ static const struct wl_data_offer_listener data_offer_listener = { data_offer_handle_action, }; +#undef LOG + /** \} */ /* -------------------------------------------------------------------- */ /** \name Listener (Data Device), #wl_data_device_listener * \{ */ +static CLG_LogRef LOG_WL_DATA_DEVICE = {"ghost.wl.handle.data_device"}; +#define LOG (&LOG_WL_DATA_DEVICE) + static void data_device_handle_data_offer(void * /*data*/, struct wl_data_device * /*wl_data_device*/, struct wl_data_offer *id) { + CLOG_INFO(LOG, 2, "data_offer"); + data_offer_t *data_offer = new data_offer_t; data_offer->id = id; wl_data_offer_add_listener(id, &data_offer_listener, data_offer); @@ -1005,8 +1042,10 @@ static void data_device_handle_enter(void *data, struct wl_data_offer *id) { if (!ghost_wl_surface_own(surface)) { + CLOG_INFO(LOG, 2, "enter (skipped)"); return; } + CLOG_INFO(LOG, 2, "enter"); input_t *input = static_cast(data); std::lock_guard lock{input->data_offer_dnd_mutex}; @@ -1036,6 +1075,8 @@ static void data_device_handle_leave(void *data, struct wl_data_device * /*wl_da input_t *input = static_cast(data); std::lock_guard lock{input->data_offer_dnd_mutex}; + CLOG_INFO(LOG, 2, "leave"); + dnd_events(input, GHOST_kEventDraggingExited); input->focus_dnd = nullptr; @@ -1055,6 +1096,8 @@ static void data_device_handle_motion(void *data, input_t *input = static_cast(data); std::lock_guard lock{input->data_offer_dnd_mutex}; + CLOG_INFO(LOG, 2, "motion"); + input->data_offer_dnd->dnd.xy[0] = x; input->data_offer_dnd->dnd.xy[1] = y; @@ -1066,6 +1109,8 @@ static void data_device_handle_drop(void *data, struct wl_data_device * /*wl_dat input_t *input = static_cast(data); std::lock_guard lock{input->data_offer_dnd_mutex}; + CLOG_INFO(LOG, 2, "drop"); + data_offer_t *data_offer = input->data_offer_dnd; const std::string mime_receive = *std::find_first_of(mime_preference_order.begin(), @@ -1158,8 +1203,10 @@ static void data_device_handle_selection(void *data, } if (id == nullptr) { + CLOG_INFO(LOG, 2, "selection: (skipped)"); return; } + CLOG_INFO(LOG, 2, "selection"); /* Get new data offer. */ data_offer = static_cast(wl_data_offer_get_user_data(id)); @@ -1199,16 +1246,22 @@ static const struct wl_data_device_listener data_device_listener = { data_device_handle_selection, }; +#undef LOG + /** \} */ /* -------------------------------------------------------------------- */ /** \name Listener (Buffer), #wl_buffer_listener * \{ */ +static CLG_LogRef LOG_WL_CURSOR_BUFFER = {"ghost.wl.handle.cursor_buffer"}; +#define LOG (&LOG_WL_CURSOR_BUFFER) + static void cursor_buffer_handle_release(void *data, struct wl_buffer *wl_buffer) { - cursor_t *cursor = static_cast(data); + CLOG_INFO(LOG, 2, "release"); + cursor_t *cursor = static_cast(data); wl_buffer_destroy(wl_buffer); if (wl_buffer == cursor->wl_buffer) { @@ -1221,12 +1274,17 @@ static const struct wl_buffer_listener cursor_buffer_listener = { cursor_buffer_handle_release, }; +#undef LOG + /** \} */ /* -------------------------------------------------------------------- */ /** \name Listener (Surface), #wl_surface_listener * \{ */ +static CLG_LogRef LOG_WL_CURSOR_SURFACE = {"ghost.wl.handle.cursor_surface"}; +#define LOG (&LOG_WL_CURSOR_SURFACE) + static bool update_cursor_scale(cursor_t &cursor, wl_shm *shm) { int scale = 0; @@ -1253,8 +1311,10 @@ static void cursor_surface_handle_enter(void *data, struct wl_output *output) { if (!ghost_wl_output_own(output)) { + CLOG_INFO(LOG, 2, "handle_enter (skipped)"); return; } + CLOG_INFO(LOG, 2, "handle_enter"); input_t *input = static_cast(data); const output_t *reg_output = ghost_wl_output_user_data(output); @@ -1267,8 +1327,10 @@ static void cursor_surface_handle_leave(void *data, struct wl_output *output) { if (!(output && ghost_wl_output_own(output))) { + CLOG_INFO(LOG, 2, "handle_leave (skipped)"); return; } + CLOG_INFO(LOG, 2, "handle_leave"); input_t *input = static_cast(data); const output_t *reg_output = ghost_wl_output_user_data(output); @@ -1281,12 +1343,17 @@ static const struct wl_surface_listener cursor_surface_listener = { cursor_surface_handle_leave, }; +#undef LOG + /** \} */ /* -------------------------------------------------------------------- */ /** \name Listener (Pointer), #wl_pointer_listener * \{ */ +static CLG_LogRef LOG_WL_POINTER = {"ghost.wl.handle.pointer"}; +#define LOG (&LOG_WL_POINTER) + static void pointer_handle_enter(void *data, struct wl_pointer * /*wl_pointer*/, const uint32_t serial, @@ -1295,8 +1362,10 @@ static void pointer_handle_enter(void *data, const wl_fixed_t surface_y) { if (!ghost_wl_surface_own(surface)) { + CLOG_INFO(LOG, 2, "enter (skipped)"); return; } + CLOG_INFO(LOG, 2, "enter"); GHOST_WindowWayland *win = ghost_wl_surface_user_data(surface); @@ -1328,9 +1397,13 @@ static void pointer_handle_leave(void *data, /* First clear the `pointer.wl_surface`, since the window won't exist when closing the window. */ static_cast(data)->pointer.wl_surface = nullptr; if (surface && ghost_wl_surface_own(surface)) { + CLOG_INFO(LOG, 2, "leave"); GHOST_WindowWayland *win = ghost_wl_surface_user_data(surface); win->deactivate(); } + else { + CLOG_INFO(LOG, 2, "leave (skipped)"); + } } static void pointer_handle_motion(void *data, @@ -1344,6 +1417,7 @@ static void pointer_handle_motion(void *data, input->pointer.xy[1] = surface_y; if (wl_surface *focus_surface = input->pointer.wl_surface) { + CLOG_INFO(LOG, 2, "motion"); GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface); const wl_fixed_t scale = win->scale(); input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(), @@ -1353,6 +1427,9 @@ static void pointer_handle_motion(void *data, wl_fixed_to_int(scale * input->pointer.xy[1]), GHOST_TABLET_DATA_NONE)); } + else { + CLOG_INFO(LOG, 2, "motion (skipped)"); + } } static void pointer_handle_button(void *data, @@ -1362,8 +1439,9 @@ static void pointer_handle_button(void *data, const uint32_t button, const uint32_t state) { - input_t *input = static_cast(data); + CLOG_INFO(LOG, 2, "button (button=%u, state=%u)", button, state); + input_t *input = static_cast(data); GHOST_TEventType etype = GHOST_kEventUnknown; switch (state) { case WL_POINTER_BUTTON_STATE_RELEASED: @@ -1415,8 +1493,9 @@ static void pointer_handle_axis(void *data, const uint32_t axis, const wl_fixed_t value) { - input_t *input = static_cast(data); + CLOG_INFO(LOG, 2, "axis (axis=%u, value=%d)", axis, value); + input_t *input = static_cast(data); if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) { return; } @@ -1436,16 +1515,23 @@ static const struct wl_pointer_listener pointer_listener = { pointer_handle_axis, }; +#undef LOG + /** \} */ /* -------------------------------------------------------------------- */ /** \name Listener (Tablet Tool), #zwp_tablet_tool_v2_listener * \{ */ +static CLG_LogRef LOG_WL_TABLET_TOOL = {"ghost.wl.handle.tablet_tool"}; +#define LOG (&LOG_WL_TABLET_TOOL) + static void tablet_tool_handle_type(void *data, struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, const uint32_t tool_type) { + CLOG_INFO(LOG, 2, "type (type=%u)", tool_type); + tablet_tool_input_t *tool_input = static_cast(data); tool_input->data.Active = tablet_tool_map_type((enum zwp_tablet_tool_v2_type)tool_type); @@ -1456,6 +1542,7 @@ static void tablet_tool_handle_hardware_serial(void * /*data*/, const uint32_t /*hardware_serial_hi*/, const uint32_t /*hardware_serial_lo*/) { + CLOG_INFO(LOG, 2, "hardware_serial"); } static void tablet_tool_handle_hardware_id_wacom( @@ -1464,20 +1551,32 @@ static void tablet_tool_handle_hardware_id_wacom( const uint32_t /*hardware_id_hi*/, const uint32_t /*hardware_id_lo*/) { + CLOG_INFO(LOG, 2, "hardware_id_wacom"); } static void tablet_tool_handle_capability(void * /*data*/, struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, - const uint32_t /*capability*/) + const uint32_t capability) { + CLOG_INFO(LOG, + 2, + "capability (tilt=%d, distance=%d, rotation=%d, slider=%d, wheel=%d)", + (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_TILT) != 0, + (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE) != 0, + (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION) != 0, + (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER) != 0, + (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_WHEEL) != 0); } static void tablet_tool_handle_done(void * /*data*/, struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/) { + CLOG_INFO(LOG, 2, "done"); } static void tablet_tool_handle_removed(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) { + CLOG_INFO(LOG, 2, "removed"); + tablet_tool_input_t *tool_input = static_cast(data); input_t *input = tool_input->input; @@ -1495,8 +1594,10 @@ static void tablet_tool_handle_proximity_in(void *data, struct wl_surface *surface) { if (!ghost_wl_surface_own(surface)) { + CLOG_INFO(LOG, 2, "proximity_in (skipped)"); return; } + CLOG_INFO(LOG, 2, "proximity_in"); tablet_tool_input_t *tool_input = static_cast(data); tool_input->proximity = true; @@ -1525,6 +1626,7 @@ static void tablet_tool_handle_proximity_in(void *data, static void tablet_tool_handle_proximity_out(void *data, struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/) { + CLOG_INFO(LOG, 2, "proximity_out"); tablet_tool_input_t *tool_input = static_cast(data); /* Defer clearing the surface until the frame is handled. * Without this, the frame can not access the surface. */ @@ -1535,6 +1637,8 @@ static void tablet_tool_handle_down(void *data, struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, const uint32_t serial) { + CLOG_INFO(LOG, 2, "down"); + tablet_tool_input_t *tool_input = static_cast(data); input_t *input = tool_input->input; const GHOST_TButton ebutton = GHOST_kButtonMaskLeft; @@ -1552,6 +1656,8 @@ static void tablet_tool_handle_down(void *data, static void tablet_tool_handle_up(void *data, struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/) { + CLOG_INFO(LOG, 2, "up"); + tablet_tool_input_t *tool_input = static_cast(data); input_t *input = tool_input->input; const GHOST_TButton ebutton = GHOST_kButtonMaskLeft; @@ -1571,6 +1677,8 @@ static void tablet_tool_handle_motion(void *data, const wl_fixed_t x, const wl_fixed_t y) { + CLOG_INFO(LOG, 2, "motion"); + tablet_tool_input_t *tool_input = static_cast(data); input_t *input = tool_input->input; @@ -1584,41 +1692,51 @@ static void tablet_tool_handle_pressure(void *data, struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, const uint32_t pressure) { + const float pressure_unit = (float)pressure / 65535; + CLOG_INFO(LOG, 2, "pressure (%.4f)", pressure_unit); + tablet_tool_input_t *tool_input = static_cast(data); GHOST_TabletData &td = tool_input->data; - td.Pressure = (float)pressure / 65535; + td.Pressure = pressure_unit; } static void tablet_tool_handle_distance(void * /*data*/, struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, - const uint32_t /*distance*/) + const uint32_t distance) { + CLOG_INFO(LOG, 2, "distance (distance=%u)", distance); } + static void tablet_tool_handle_tilt(void *data, struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, const wl_fixed_t tilt_x, const wl_fixed_t tilt_y) { + /* Map degrees to `-1.0..1.0`. */ + const float tilt_unit[2] = { + (float)(wl_fixed_to_double(tilt_x) / 90.0), + (float)(wl_fixed_to_double(tilt_y) / 90.0), + }; + CLOG_INFO(LOG, 2, "tilt (x=%.4f, y=%.4f)", UNPACK2(tilt_unit)); tablet_tool_input_t *tool_input = static_cast(data); GHOST_TabletData &td = tool_input->data; - /* Map degrees to `-1.0..1.0`. */ - td.Xtilt = wl_fixed_to_double(tilt_x) / 90.0f; - td.Ytilt = wl_fixed_to_double(tilt_y) / 90.0f; + td.Xtilt = tilt_unit[0]; + td.Ytilt = tilt_unit[1]; CLAMP(td.Xtilt, -1.0f, 1.0f); CLAMP(td.Ytilt, -1.0f, 1.0f); } static void tablet_tool_handle_rotation(void * /*data*/, struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, - const wl_fixed_t /*degrees*/) + const wl_fixed_t degrees) { - /* Pass. */ + CLOG_INFO(LOG, 2, "rotation (degrees=%.4f)", wl_fixed_to_double(degrees)); } static void tablet_tool_handle_slider(void * /*data*/, struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, - const int32_t /*position*/) + const int32_t position) { - /* Pass. */ + CLOG_INFO(LOG, 2, "slider (position=%d)", position); } static void tablet_tool_handle_wheel(void *data, struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, @@ -1628,6 +1746,7 @@ static void tablet_tool_handle_wheel(void *data, if (clicks == 0) { return; } + CLOG_INFO(LOG, 2, "wheel (clicks=%d)", clicks); tablet_tool_input_t *tool_input = static_cast(data); input_t *input = tool_input->input; @@ -1642,6 +1761,8 @@ static void tablet_tool_handle_button(void *data, const uint32_t button, const uint32_t state) { + CLOG_INFO(LOG, 2, "button (button=%u, state=%u)", button, state); + tablet_tool_input_t *tool_input = static_cast(data); input_t *input = tool_input->input; @@ -1681,9 +1802,12 @@ static void tablet_tool_handle_frame(void *data, struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, const uint32_t /*time*/) { + CLOG_INFO(LOG, 2, "frame"); + tablet_tool_input_t *tool_input = static_cast(data); input_t *input = tool_input->input; + /* No need to check the surfaces origin, it's already known to be owned by GHOST. */ if (wl_surface *focus_surface = input->tablet.wl_surface) { GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface); const wl_fixed_t scale = win->scale(); @@ -1725,23 +1849,30 @@ static const struct zwp_tablet_tool_v2_listener tablet_tool_listner = { tablet_tool_handle_frame, }; +#undef LOG + /** \} */ /* -------------------------------------------------------------------- */ /** \name Listener (Table Seat), #zwp_tablet_seat_v2_listener * \{ */ +static CLG_LogRef LOG_WL_TABLET_SEAT = {"ghost.wl.handle.tablet_seat"}; +#define LOG (&LOG_WL_TABLET_SEAT) + static void tablet_seat_handle_tablet_added(void * /*data*/, struct zwp_tablet_seat_v2 * /*zwp_tablet_seat_v2*/, - struct zwp_tablet_v2 * /*id*/) + struct zwp_tablet_v2 *id) { - /* Pass. */ + CLOG_INFO(LOG, 2, "tablet_added (id=%p)", id); } static void tablet_seat_handle_tool_added(void *data, struct zwp_tablet_seat_v2 * /*zwp_tablet_seat_v2*/, struct zwp_tablet_tool_v2 *id) { + CLOG_INFO(LOG, 2, "tool_added (id=%p)", id); + input_t *input = static_cast(data); tablet_tool_input_t *tool_input = new tablet_tool_input_t(); tool_input->input = input; @@ -1757,9 +1888,9 @@ static void tablet_seat_handle_tool_added(void *data, static void tablet_seat_handle_pad_added(void * /*data*/, struct zwp_tablet_seat_v2 * /*zwp_tablet_seat_v2*/, - struct zwp_tablet_pad_v2 * /*id*/) + struct zwp_tablet_pad_v2 *id) { - /* Pass. */ + CLOG_INFO(LOG, 2, "pad_added (id=%p)", id); } static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = { @@ -1768,12 +1899,17 @@ static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = { tablet_seat_handle_pad_added, }; +#undef LOG + /** \} */ /* -------------------------------------------------------------------- */ /** \name Listener (Keyboard), #wl_keyboard_listener * \{ */ +static CLG_LogRef LOG_WL_KEYBOARD = {"ghost.wl.handle.keyboard"}; +#define LOG (&LOG_WL_KEYBOARD) + static void keyboard_handle_keymap(void *data, struct wl_keyboard * /*wl_keyboard*/, const uint32_t format, @@ -1783,6 +1919,7 @@ static void keyboard_handle_keymap(void *data, input_t *input = static_cast(data); if ((!data) || (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1)) { + CLOG_INFO(LOG, 2, "keymap (no data or wrong version)"); close(fd); return; } @@ -1799,9 +1936,12 @@ static void keyboard_handle_keymap(void *data, close(fd); if (!keymap) { + CLOG_INFO(LOG, 2, "keymap (not found)"); return; } + CLOG_INFO(LOG, 2, "keymap"); + /* In practice we can assume `xkb_state_new` always succeeds. */ xkb_state_unref(input->xkb_state); input->xkb_state = xkb_state_new(keymap); @@ -1838,8 +1978,10 @@ static void keyboard_handle_enter(void *data, struct wl_array * /*keys*/) { if (!ghost_wl_surface_own(surface)) { + CLOG_INFO(LOG, 2, "enter (skipped)"); return; } + CLOG_INFO(LOG, 2, "enter"); input_t *input = static_cast(data); input->keyboard.serial = serial; @@ -1858,8 +2000,10 @@ static void keyboard_handle_leave(void *data, struct wl_surface *surface) { if (!(surface && ghost_wl_surface_own(surface))) { + CLOG_INFO(LOG, 2, "leave (skipped)"); return; } + CLOG_INFO(LOG, 2, "leave"); input_t *input = static_cast(data); input->keyboard.wl_surface = nullptr; @@ -1939,8 +2083,10 @@ static void keyboard_handle_key(void *data, const xkb_keysym_t sym = xkb_state_key_get_one_sym_without_modifiers( input->xkb_state_empty, input->xkb_state_empty_with_numlock, key_code); if (sym == XKB_KEY_NoSymbol) { + CLOG_INFO(LOG, 2, "key (no symbol, skipped)"); return; } + CLOG_INFO(LOG, 2, "key"); GHOST_TEventType etype = GHOST_kEventUnknown; switch (state) { @@ -2067,6 +2213,8 @@ static void keyboard_handle_modifiers(void *data, const uint32_t mods_locked, const uint32_t group) { + CLOG_INFO(LOG, 2, "modifiers"); + input_t *input = static_cast(data); xkb_state_update_mask(input->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); @@ -2082,8 +2230,9 @@ static void keyboard_repeat_handle_info(void *data, const int32_t rate, const int32_t delay) { - input_t *input = static_cast(data); + CLOG_INFO(LOG, 2, "info (rate=%d, delay=%d)", rate, delay); + input_t *input = static_cast(data); input->key_repeat.rate = rate; input->key_repeat.delay = delay; @@ -2102,16 +2251,28 @@ static const struct wl_keyboard_listener keyboard_listener = { keyboard_repeat_handle_info, }; +#undef LOG + /** \} */ /* -------------------------------------------------------------------- */ /** \name Listener (Seat), #wl_seat_listener * \{ */ +static CLG_LogRef LOG_WL_SEAT = {"ghost.wl.handle.seat"}; +#define LOG (&LOG_WL_SEAT) + static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, const uint32_t capabilities) { + CLOG_INFO(LOG, + 2, + "capabilities (pointer=%d, keyboard=%d, touch=%d)", + (capabilities & WL_SEAT_CAPABILITY_POINTER) != 0, + (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) != 0, + (capabilities & WL_SEAT_CAPABILITY_TOUCH) != 0); + input_t *input = static_cast(data); input->wl_pointer = nullptr; input->wl_keyboard = nullptr; @@ -2138,6 +2299,7 @@ static void seat_handle_capabilities(void *data, static void seat_handle_name(void *data, struct wl_seat * /*wl_seat*/, const char *name) { + CLOG_INFO(LOG, 2, "name (name=\"%s\")", name); static_cast(data)->name = std::string(name); } @@ -2146,17 +2308,24 @@ static const struct wl_seat_listener seat_listener = { seat_handle_name, }; +#undef LOG + /** \} */ /* -------------------------------------------------------------------- */ /** \name Listener (XDG Output), #zxdg_output_v1_listener * \{ */ +static CLG_LogRef LOG_WL_XDG_OUTPUT = {"ghost.wl.handle.xdg_output"}; +#define LOG (&LOG_WL_XDG_OUTPUT) + static void xdg_output_handle_logical_position(void *data, struct zxdg_output_v1 * /*xdg_output*/, const int32_t x, const int32_t y) { + CLOG_INFO(LOG, 2, "logical_position [%d, %d]", x, y); + output_t *output = static_cast(data); output->position_logical[0] = x; output->position_logical[1] = y; @@ -2168,8 +2337,9 @@ static void xdg_output_handle_logical_size(void *data, const int32_t width, const int32_t height) { - output_t *output = static_cast(data); + CLOG_INFO(LOG, 2, "logical_size [%d, %d]", width, height); + output_t *output = static_cast(data); if (output->size_logical[0] != 0 && output->size_logical[1] != 0) { /* Original comment from SDL. */ /* FIXME(@flibit): GNOME has a bug where the logical size does not account for @@ -2198,6 +2368,7 @@ static void xdg_output_handle_logical_size(void *data, static void xdg_output_handle_done(void *data, struct zxdg_output_v1 * /*xdg_output*/) { + CLOG_INFO(LOG, 2, "done"); /* NOTE: `xdg-output.done` events are deprecated and only apply below version 3 of the protocol. * `wl-output.done` event will be emitted in version 3 or higher. */ output_t *output = static_cast(data); @@ -2208,16 +2379,16 @@ static void xdg_output_handle_done(void *data, struct zxdg_output_v1 * /*xdg_out static void xdg_output_handle_name(void * /*data*/, struct zxdg_output_v1 * /*xdg_output*/, - const char * /*name*/) + const char *name) { - /* Pass. */ + CLOG_INFO(LOG, 2, "name (name=\"%s\")", name); } static void xdg_output_handle_description(void * /*data*/, struct zxdg_output_v1 * /*xdg_output*/, - const char * /*description*/) + const char *description) { - /* Pass. */ + CLOG_INFO(LOG, 2, "description (description=\"%s\")", description); } static const struct zxdg_output_v1_listener xdg_output_listener = { @@ -2228,12 +2399,17 @@ static const struct zxdg_output_v1_listener xdg_output_listener = { xdg_output_handle_description, }; +#undef LOG + /** \} */ /* -------------------------------------------------------------------- */ /** \name Listener (Output), #wl_output_listener * \{ */ +static CLG_LogRef LOG_WL_OUTPUT = {"ghost.wl.handle.output"}; +#define LOG (&LOG_WL_OUTPUT) + static void output_handle_geometry(void *data, struct wl_output * /*wl_output*/, const int32_t /*x*/, @@ -2245,6 +2421,15 @@ static void output_handle_geometry(void *data, const char *model, const int32_t transform) { + CLOG_INFO(LOG, + 2, + "geometry (make=\"%s\", model=\"%s\", transform=%d, size=[%d, %d])", + make, + model, + transform, + physical_width, + physical_height); + output_t *output = static_cast(data); output->transform = transform; output->make = std::string(make); @@ -2260,18 +2445,21 @@ static void output_handle_mode(void *data, const int32_t height, const int32_t /*refresh*/) { - output_t *output = static_cast(data); + if ((flags & WL_OUTPUT_MODE_CURRENT) == 0) { + CLOG_INFO(LOG, 2, "mode (skipped)"); + return; + } + CLOG_INFO(LOG, 2, "mode (size=[%d, %d], flags=%u)", width, height, flags); - if (flags & WL_OUTPUT_MODE_CURRENT) { - output->size_native[0] = width; - output->size_native[1] = height; + output_t *output = static_cast(data); + output->size_native[0] = width; + output->size_native[1] = height; - /* Don't rotate this yet, `wl-output` coordinates are transformed in - * handle_done and `xdg-output` coordinates are pre-transformed. */ - if (!output->has_size_logical) { - output->size_logical[0] = width; - output->size_logical[1] = height; - } + /* Don't rotate this yet, `wl-output` coordinates are transformed in + * handle_done and `xdg-output` coordinates are pre-transformed. */ + if (!output->has_size_logical) { + output->size_logical[0] = width; + output->size_logical[1] = height; } } @@ -2285,6 +2473,8 @@ static void output_handle_mode(void *data, */ static void output_handle_done(void *data, struct wl_output * /*wl_output*/) { + CLOG_INFO(LOG, 2, "done"); + output_t *output = static_cast(data); int32_t size_native[2]; if (output->transform & WL_OUTPUT_TRANSFORM_90) { @@ -2322,6 +2512,8 @@ static const struct wl_output_listener output_listener = { output_handle_scale, }; +#undef LOG + /** \} */ /* -------------------------------------------------------------------- */ @@ -2330,10 +2522,14 @@ static const struct wl_output_listener output_listener = { #ifndef WITH_GHOST_WAYLAND_LIBDECOR +static CLG_LogRef LOG_WL_XDG_WM_BASE = {"ghost.wl.handle.output"}; +# define LOG (&LOG_WL_XDG_WM_BASE) + static void shell_handle_ping(void * /*data*/, struct xdg_wm_base *xdg_wm_base, const uint32_t serial) { + CLOG_INFO(LOG, 2, "ping"); xdg_wm_base_pong(xdg_wm_base, serial); } @@ -2341,6 +2537,8 @@ static const struct xdg_wm_base_listener shell_listener = { shell_handle_ping, }; +# undef LOG + #endif /* !WITH_GHOST_WAYLAND_LIBDECOR. */ /** \} */ @@ -2351,10 +2549,15 @@ static const struct xdg_wm_base_listener shell_listener = { #ifdef WITH_GHOST_WAYLAND_LIBDECOR +static CLG_LogRef LOG_WL_LIBDECOR = {"ghost.wl.handle.libdecor"}; +# define LOG (&LOG_WL_LIBDECOR) + static void decor_handle_error(struct libdecor * /*context*/, enum libdecor_error error, const char *message) { + CLOG_INFO(LOG, 2, "error (id=%d, message=%s)", error, message); + (void)(error); (void)(message); GHOST_PRINT("decoration error (" << error << "): " << message << std::endl); @@ -2365,6 +2568,8 @@ static struct libdecor_interface libdecor_interface = { decor_handle_error, }; +# undef LOG + #endif /* WITH_GHOST_WAYLAND_LIBDECOR. */ /** \} */ @@ -2373,12 +2578,18 @@ static struct libdecor_interface libdecor_interface = { /** \name Listener (Registry), #wl_registry_listener * \{ */ +static CLG_LogRef LOG_WL_REGISTRY = {"ghost.wl.handle.registry"}; +#define LOG (&LOG_WL_REGISTRY) + static void global_handle_add(void *data, struct wl_registry *wl_registry, const uint32_t name, const char *interface, - const uint32_t /*version*/) + const uint32_t version) { + /* Log last since it can be noted if the interface was handled or not. */ + bool found = true; + struct display_t *display = static_cast(data); if (!strcmp(interface, wl_compositor_interface.name)) { display->compositor = static_cast( @@ -2452,6 +2663,17 @@ static void global_handle_add(void *data, display->pointer_constraints = static_cast( wl_registry_bind(wl_registry, name, &zwp_pointer_constraints_v1_interface, 1)); } + else { + found = false; + } + + CLOG_INFO(LOG, + 2, + "add %s(interface=%s, version=%u, name=%u)", + found ? "" : "(skipped), ", + interface, + version, + name); } /** @@ -2465,8 +2687,9 @@ static void global_handle_add(void *data, */ static void global_handle_remove(void * /*data*/, struct wl_registry * /*wl_registry*/, - const uint32_t /*name*/) + const uint32_t name) { + CLOG_INFO(LOG, 2, "remove (name=%u)", name); } static const struct wl_registry_listener registry_listener = { @@ -2474,6 +2697,8 @@ static const struct wl_registry_listener registry_listener = { global_handle_remove, }; +#undef LOG + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp index a5177a25ef7..e303bd5b6aa 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cpp +++ b/intern/ghost/intern/GHOST_WindowWayland.cpp @@ -31,6 +31,9 @@ # include #endif +/* Logging, use `ghost.wl.*` prefix. */ +#include "CLG_log.h" + static constexpr size_t base_dpi = 96; static GHOST_WindowManager *window_manager = nullptr; @@ -145,12 +148,18 @@ static int outputs_max_scale_or_default(const std::vector &outputs, #ifndef WITH_GHOST_WAYLAND_LIBDECOR +static CLG_LogRef LOG_WL_XDG_TOPLEVEL = {"ghost.wl.handle.xdg_toplevel"}; +# define LOG (&LOG_WL_XDG_TOPLEVEL) + static void xdg_toplevel_handle_configure(void *data, xdg_toplevel * /*xdg_toplevel*/, const int32_t width, const int32_t height, wl_array *states) { + /* TODO: log `states`, not urgent. */ + CLOG_INFO(LOG, 2, "configure (size=[%d, %d])", width, height); + window_t *win = static_cast(data); win->size_pending[0] = win->scale * width; win->size_pending[1] = win->scale * height; @@ -179,6 +188,7 @@ static void xdg_toplevel_handle_configure(void *data, static void xdg_toplevel_handle_close(void *data, xdg_toplevel * /*xdg_toplevel*/) { + CLOG_INFO(LOG, 2, "close"); static_cast(data)->w->close(); } @@ -187,6 +197,8 @@ static const xdg_toplevel_listener toplevel_listener = { xdg_toplevel_handle_close, }; +# undef LOG + #endif /* !WITH_GHOST_WAYLAND_LIBDECOR. */ /** \} */ @@ -197,10 +209,15 @@ static const xdg_toplevel_listener toplevel_listener = { #ifdef WITH_GHOST_WAYLAND_LIBDECOR +static CLG_LogRef LOG_WL_LIBDECOR_FRAME = {"ghost.wl.handle.libdecor_frame"}; +# define LOG (&LOG_WL_LIBDECOR_FRAME) + static void frame_handle_configure(struct libdecor_frame *frame, struct libdecor_configuration *configuration, void *data) { + CLOG_INFO(LOG, 2, "configure"); + window_t *win = static_cast(data); int size_next[2]; @@ -238,11 +255,15 @@ static void frame_handle_configure(struct libdecor_frame *frame, static void frame_handle_close(struct libdecor_frame * /*frame*/, void *data) { + CLOG_INFO(LOG, 2, "close"); + static_cast(data)->w->close(); } static void frame_handle_commit(struct libdecor_frame * /*frame*/, void *data) { + CLOG_INFO(LOG, 2, "commit"); + /* We have to swap twice to keep any pop-up menus alive. */ static_cast(data)->w->swapBuffers(); static_cast(data)->w->swapBuffers(); @@ -254,6 +275,8 @@ static struct libdecor_frame_interface libdecor_frame_iface = { frame_handle_commit, }; +# undef LOG + #endif /* WITH_GHOST_WAYLAND_LIBDECOR. */ /** \} */ @@ -264,18 +287,24 @@ static struct libdecor_frame_interface libdecor_frame_iface = { #ifndef WITH_GHOST_WAYLAND_LIBDECOR +static CLG_LogRef LOG_WL_XDG_TOPLEVEL_DECORATION = {"ghost.wl.handle.xdg_toplevel_decoration"}; +# define LOG (&LOG_WL_XDG_TOPLEVEL_DECORATION) + static void xdg_toplevel_decoration_handle_configure( void *data, struct zxdg_toplevel_decoration_v1 * /*zxdg_toplevel_decoration_v1*/, const uint32_t mode) { - static_cast(data)->decoration_mode = zxdg_toplevel_decoration_v1_mode(mode); + CLOG_INFO(LOG, 2, "configure (mode=%u)", mode); + static_cast(data)->decoration_mode = (zxdg_toplevel_decoration_v1_mode)mode; } static const zxdg_toplevel_decoration_v1_listener toplevel_decoration_v1_listener = { xdg_toplevel_decoration_handle_configure, }; +# undef LOG + #endif /* !WITH_GHOST_WAYLAND_LIBDECOR. */ /** \} */ @@ -286,6 +315,9 @@ static const zxdg_toplevel_decoration_v1_listener toplevel_decoration_v1_listene #ifndef WITH_GHOST_WAYLAND_LIBDECOR +static CLG_LogRef LOG_WL_XDG_SURFACE = {"ghost.wl.handle.xdg_surface"}; +# define LOG (&LOG_WL_XDG_SURFACE) + static void xdg_surface_handle_configure(void *data, xdg_surface *xdg_surface, const uint32_t serial) @@ -293,10 +325,13 @@ static void xdg_surface_handle_configure(void *data, window_t *win = static_cast(data); if (win->xdg_surface != xdg_surface) { + CLOG_INFO(LOG, 2, "configure (skipped)"); return; } + const bool do_resize = win->size_pending[0] != 0 && win->size_pending[1] != 0; + CLOG_INFO(LOG, 2, "configure (do_resize=%d)", do_resize); - if (win->size_pending[0] != 0 && win->size_pending[1] != 0) { + if (do_resize) { win->size[0] = win->size_pending[0]; win->size[1] = win->size_pending[1]; wl_egl_window_resize(win->egl_window, UNPACK2(win->size), 0, 0); @@ -319,6 +354,8 @@ static const xdg_surface_listener xdg_surface_listener = { xdg_surface_handle_configure, }; +# undef LOG + #endif /* !WITH_GHOST_WAYLAND_LIBDECOR. */ /** \} */ @@ -327,13 +364,19 @@ static const xdg_surface_listener xdg_surface_listener = { /** \name Listener (Surface), #wl_surface_listener * \{ */ +static CLG_LogRef LOG_WL_SURFACE = {"ghost.wl.handle.surface"}; +#define LOG (&LOG_WL_SURFACE) + static void surface_handle_enter(void *data, struct wl_surface * /*wl_surface*/, struct wl_output *output) { if (!ghost_wl_output_own(output)) { + CLOG_INFO(LOG, 2, "enter (skipped)"); return; } + CLOG_INFO(LOG, 2, "enter"); + output_t *reg_output = ghost_wl_output_user_data(output); GHOST_WindowWayland *win = static_cast(data); if (win->outputs_enter(reg_output)) { @@ -346,8 +389,11 @@ static void surface_handle_leave(void *data, struct wl_output *output) { if (!ghost_wl_output_own(output)) { + CLOG_INFO(LOG, 2, "leave (skipped)"); return; } + CLOG_INFO(LOG, 2, "leave"); + output_t *reg_output = ghost_wl_output_user_data(output); GHOST_WindowWayland *win = static_cast(data); if (win->outputs_leave(reg_output)) { @@ -360,6 +406,8 @@ static struct wl_surface_listener wl_surface_listener = { surface_handle_leave, }; +#undef LOG + /** \} */ /* -------------------------------------------------------------------- */ -- cgit v1.2.3 From 2c4dfe3453f09a0877a9c825347df1aef128bea8 Mon Sep 17 00:00:00 2001 From: Damien Picard Date: Fri, 8 Jul 2022 11:54:45 +0200 Subject: Add a few missing UI strings to translation. Reviewed By: mont29 Differential Revision: https://developer.blender.org/D15392 --- release/scripts/startup/bl_operators/wm.py | 33 +++++++++++++------------ release/scripts/startup/bl_ui/space_userpref.py | 2 +- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py index 50fc6bad720..3ab124bf4cf 100644 --- a/release/scripts/startup/bl_operators/wm.py +++ b/release/scripts/startup/bl_operators/wm.py @@ -18,6 +18,7 @@ from bpy.props import ( FloatVectorProperty, ) from bpy.app.translations import pgettext_iface as iface_ +from bpy.app.translations import pgettext_tip as tip_ def _rna_path_prop_search_for_context_impl(context, edit_text, unique_attrs): @@ -1060,31 +1061,31 @@ class WM_OT_url_open_preset(Operator): # Allow dynamically extending. preset_items = [ # Dynamic URL's. - (('BUG', "Bug", - "Report a bug with pre-filled version information"), + (('BUG', iface_("Bug"), + tip_("Report a bug with pre-filled version information")), _url_from_bug), - (('BUG_ADDON', "Add-on Bug", - "Report a bug in an add-on"), + (('BUG_ADDON', iface_("Add-on Bug"), + tip_("Report a bug in an add-on")), _url_from_bug_addon), - (('RELEASE_NOTES', "Release Notes", - "Read about what's new in this version of Blender"), + (('RELEASE_NOTES', iface_("Release Notes"), + tip_("Read about what's new in this version of Blender")), _url_from_release_notes), - (('MANUAL', "User Manual", - "The reference manual for this version of Blender"), + (('MANUAL', iface_("User Manual"), + tip_("The reference manual for this version of Blender")), _url_from_manual), - (('API', "Python API Reference", - "The API reference manual for this version of Blender"), + (('API', iface_("Python API Reference"), + tip_("The API reference manual for this version of Blender")), _url_from_api), # Static URL's. - (('FUND', "Development Fund", - "The donation program to support maintenance and improvements"), + (('FUND', iface_("Development Fund"), + tip_("The donation program to support maintenance and improvements")), "https://fund.blender.org"), - (('BLENDER', "blender.org", - "Blender's official web-site"), + (('BLENDER', iface_("blender.org"), + tip_("Blender's official web-site")), "https://www.blender.org"), - (('CREDITS', "Credits", - "Lists committers to Blender's source code"), + (('CREDITS', iface_("Credits"), + tip_("Lists committers to Blender's source code")), "https://www.blender.org/about/credits/"), ] diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py index e08b921b1a9..603110b76e9 100644 --- a/release/scripts/startup/bl_ui/space_userpref.py +++ b/release/scripts/startup/bl_ui/space_userpref.py @@ -2094,7 +2094,7 @@ class StudioLightPanelMixin: for studio_light in lights: self.draw_studio_light(flow, studio_light) else: - layout.label(text="No custom %s configured" % self.bl_label) + layout.label(text=iface_("No custom %s configured") % self.bl_label) def draw_studio_light(self, layout, studio_light): box = layout.box() -- cgit v1.2.3 From a8f7d41d3898a8d3ae8afb4f95ea9f4f44db2a69 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Fri, 8 Jul 2022 12:07:08 +0200 Subject: Draw: Curve outline drawing in object mode. This patch adds (selected/active) outline around a curve object in object mode. {F13270680} In the past the draw bounds option was enabled for any curve objects. With this patch it isn't needed and will be disabled. In the future the curve outline could also be enabled to improve GPU selection. Reviewed By: dfelinto, HooglyBoogly, fclem Maniphest Tasks: T95933 Differential Revision: https://developer.blender.org/D15308 --- source/blender/blenloader/intern/versioning_300.c | 8 +++ source/blender/draw/CMakeLists.txt | 2 + source/blender/draw/engines/basic/basic_engine.c | 10 ++- source/blender/draw/engines/basic/basic_private.h | 1 + source/blender/draw/engines/basic/basic_shader.c | 11 +++ .../basic/shaders/basic_depth_curves_vert.glsl | 27 ++++++++ .../basic/shaders/infos/basic_depth_info.hh | 6 +- .../blender/draw/engines/overlay/overlay_engine.c | 6 +- .../blender/draw/engines/overlay/overlay_outline.c | 15 ++++ .../blender/draw/engines/overlay/overlay_private.h | 2 + .../blender/draw/engines/overlay/overlay_shader.c | 13 ++++ .../overlay/shaders/infos/overlay_outline_info.hh | 10 +++ .../overlay_outline_prepass_curves_vert.glsl | 81 ++++++++++++++++++++++ .../draw/intern/shaders/common_hair_lib.glsl | 35 ++++++---- source/blender/draw/tests/shaders_test.cc | 2 + source/blender/editors/curves/intern/curves_ops.cc | 1 - source/blender/editors/object/object_add.cc | 2 - 17 files changed, 213 insertions(+), 19 deletions(-) create mode 100644 source/blender/draw/engines/basic/shaders/basic_depth_curves_vert.glsl create mode 100644 source/blender/draw/engines/overlay/shaders/overlay_outline_prepass_curves_vert.glsl diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index 34b32ebc175..14204479849 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -3276,5 +3276,13 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) } brush->curves_sculpt_settings->density_add_attempts = 100; } + + /* Disable 'show_bounds' option of curve objects. Option was set as there was no object mode + * outline implementation. See T95933. */ + LISTBASE_FOREACH (Object *, ob, &bmain->objects) { + if (ob->type == OB_CURVES) { + ob->dtx &= ~OB_DRAWBOUNDOX; + } + } } } diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt index 81e4b00290a..55d789f64b0 100644 --- a/source/blender/draw/CMakeLists.txt +++ b/source/blender/draw/CMakeLists.txt @@ -461,6 +461,7 @@ set(GLSL_SRC engines/basic/shaders/basic_conservative_depth_geom.glsl engines/basic/shaders/basic_depth_vert.glsl + engines/basic/shaders/basic_depth_curves_vert.glsl engines/basic/shaders/basic_depth_pointcloud_vert.glsl engines/basic/shaders/basic_depth_frag.glsl @@ -538,6 +539,7 @@ set(GLSL_SRC engines/overlay/shaders/overlay_motion_path_line_vert.glsl engines/overlay/shaders/overlay_motion_path_point_vert.glsl engines/overlay/shaders/overlay_outline_detect_frag.glsl + engines/overlay/shaders/overlay_outline_prepass_curves_vert.glsl engines/overlay/shaders/overlay_outline_prepass_frag.glsl engines/overlay/shaders/overlay_outline_prepass_geom.glsl engines/overlay/shaders/overlay_outline_prepass_gpencil_frag.glsl diff --git a/source/blender/draw/engines/basic/basic_engine.c b/source/blender/draw/engines/basic/basic_engine.c index 04a3c27959d..975d9e299bf 100644 --- a/source/blender/draw/engines/basic/basic_engine.c +++ b/source/blender/draw/engines/basic/basic_engine.c @@ -53,6 +53,7 @@ typedef struct BASIC_PrivateData { DRWShadingGroup *depth_shgrp[2]; DRWShadingGroup *depth_shgrp_cull[2]; DRWShadingGroup *depth_hair_shgrp[2]; + DRWShadingGroup *depth_curves_shgrp[2]; DRWShadingGroup *depth_pointcloud_shgrp[2]; bool use_material_slot_selection; } BASIC_PrivateData; /* Transient data */ @@ -99,6 +100,9 @@ static void basic_cache_init(void *vedata) stl->g_data->depth_hair_shgrp[i] = grp = DRW_shgroup_create( BASIC_shaders_depth_sh_get(draw_ctx->sh_cfg), psl->depth_pass[i]); + stl->g_data->depth_curves_shgrp[i] = grp = DRW_shgroup_create( + BASIC_shaders_curves_depth_sh_get(draw_ctx->sh_cfg), psl->depth_pass[i]); + sh = DRW_state_is_select() ? BASIC_shaders_depth_conservative_sh_get(draw_ctx->sh_cfg) : BASIC_shaders_depth_sh_get(draw_ctx->sh_cfg); state |= DRW_STATE_CULL_BACK; @@ -156,8 +160,12 @@ static void basic_cache_populate(void *vedata, Object *ob) basic_cache_populate_particles(vedata, ob); } - /* Make flat object selectable in ortho view if wireframe is enabled. */ const bool do_in_front = (ob->dtx & OB_DRAW_IN_FRONT) != 0; + if (ob->type == OB_CURVES) { + DRW_shgroup_curves_create_sub(ob, stl->g_data->depth_curves_shgrp[do_in_front], NULL); + } + + /* Make flat object selectable in ortho view if wireframe is enabled. */ if ((draw_ctx->v3d->overlay.flag & V3D_OVERLAY_WIREFRAMES) || (draw_ctx->v3d->shading.type == OB_WIRE) || (ob->dtx & OB_DRAWWIRE) || (ob->dt == OB_WIRE)) { int flat_axis = 0; diff --git a/source/blender/draw/engines/basic/basic_private.h b/source/blender/draw/engines/basic/basic_private.h index 22b458baca2..197831b9ee8 100644 --- a/source/blender/draw/engines/basic/basic_private.h +++ b/source/blender/draw/engines/basic/basic_private.h @@ -11,6 +11,7 @@ extern "C" { GPUShader *BASIC_shaders_depth_sh_get(eGPUShaderConfig config); GPUShader *BASIC_shaders_pointcloud_depth_sh_get(eGPUShaderConfig config); +GPUShader *BASIC_shaders_curves_depth_sh_get(eGPUShaderConfig config); GPUShader *BASIC_shaders_depth_conservative_sh_get(eGPUShaderConfig config); GPUShader *BASIC_shaders_pointcloud_depth_conservative_sh_get(eGPUShaderConfig config); void BASIC_shaders_free(void); diff --git a/source/blender/draw/engines/basic/basic_shader.c b/source/blender/draw/engines/basic/basic_shader.c index 3d40c627fff..5b7636ca9fd 100644 --- a/source/blender/draw/engines/basic/basic_shader.c +++ b/source/blender/draw/engines/basic/basic_shader.c @@ -24,6 +24,7 @@ typedef struct BASIC_Shaders { /* Depth Pre Pass */ struct GPUShader *depth; struct GPUShader *pointcloud_depth; + struct GPUShader *curves_depth; struct GPUShader *depth_conservative; struct GPUShader *pointcloud_depth_conservative; } BASIC_Shaders; @@ -53,6 +54,16 @@ GPUShader *BASIC_shaders_pointcloud_depth_sh_get(eGPUShaderConfig config) return sh_data->pointcloud_depth; } +GPUShader *BASIC_shaders_curves_depth_sh_get(eGPUShaderConfig config) +{ + BASIC_Shaders *sh_data = &e_data.sh_data[config]; + if (sh_data->curves_depth == NULL) { + sh_data->curves_depth = GPU_shader_create_from_info_name( + config == GPU_SHADER_CFG_CLIPPED ? "basic_depth_curves_clipped" : "basic_depth_curves"); + } + return sh_data->curves_depth; +} + GPUShader *BASIC_shaders_depth_conservative_sh_get(eGPUShaderConfig config) { BASIC_Shaders *sh_data = &e_data.sh_data[config]; diff --git a/source/blender/draw/engines/basic/shaders/basic_depth_curves_vert.glsl b/source/blender/draw/engines/basic/shaders/basic_depth_curves_vert.glsl new file mode 100644 index 00000000000..b0da9754fc6 --- /dev/null +++ b/source/blender/draw/engines/basic/shaders/basic_depth_curves_vert.glsl @@ -0,0 +1,27 @@ + +#pragma BLENDER_REQUIRE(common_hair_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_clipping_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) + +void main() +{ + GPU_INTEL_VERTEX_SHADER_WORKAROUND + + bool is_persp = (ProjectionMatrix[3][3] == 0.0); + float time, thick_time, thickness; + vec3 world_pos, tan, binor; + hair_get_pos_tan_binor_time(is_persp, + ModelMatrixInverse, + ViewMatrixInverse[3].xyz, + ViewMatrixInverse[2].xyz, + world_pos, + tan, + binor, + time, + thickness, + thick_time); + + gl_Position = point_world_to_ndc(world_pos); + + view_clipping_distances(world_pos); +} diff --git a/source/blender/draw/engines/basic/shaders/infos/basic_depth_info.hh b/source/blender/draw/engines/basic/shaders/infos/basic_depth_info.hh index bae50eb48fa..561cef0e442 100644 --- a/source/blender/draw/engines/basic/shaders/infos/basic_depth_info.hh +++ b/source/blender/draw/engines/basic/shaders/infos/basic_depth_info.hh @@ -27,6 +27,9 @@ GPU_SHADER_CREATE_INFO(basic_pointcloud) .vertex_source("basic_depth_pointcloud_vert.glsl") .additional_info("draw_pointcloud"); +GPU_SHADER_CREATE_INFO(basic_curves) + .vertex_source("basic_depth_curves_vert.glsl") + .additional_info("draw_hair"); /** \} */ /* -------------------------------------------------------------------- */ @@ -46,7 +49,8 @@ GPU_SHADER_CREATE_INFO(basic_pointcloud) #define BASIC_OBTYPE_VARIATIONS(prefix, ...) \ BASIC_CONSERVATIVE_VARIATIONS(prefix##_mesh, "basic_mesh", __VA_ARGS__) \ - BASIC_CONSERVATIVE_VARIATIONS(prefix##_pointcloud, "basic_pointcloud", __VA_ARGS__) + BASIC_CONSERVATIVE_VARIATIONS(prefix##_pointcloud, "basic_pointcloud", __VA_ARGS__) \ + BASIC_CLIPPING_VARIATIONS(prefix##_curves, "basic_curves", __VA_ARGS__) /** \} */ diff --git a/source/blender/draw/engines/overlay/overlay_engine.c b/source/blender/draw/engines/overlay/overlay_engine.c index 9ec0398e5cb..5edd68bffff 100644 --- a/source/blender/draw/engines/overlay/overlay_engine.c +++ b/source/blender/draw/engines/overlay/overlay_engine.c @@ -310,6 +310,8 @@ static void OVERLAY_cache_populate(void *vedata, Object *ob) (pd->ctx_mode == CTX_MODE_PARTICLE); const bool in_paint_mode = (ob == draw_ctx->obact) && (draw_ctx->object_mode & OB_MODE_ALL_PAINT); + const bool in_sculpt_curve_mode = (ob == draw_ctx->obact) && + (draw_ctx->object_mode & OB_MODE_SCULPT_CURVES); const bool in_sculpt_mode = (ob == draw_ctx->obact) && (ob->sculpt != NULL) && (ob->sculpt->mode_type == OB_MODE_SCULPT); const bool in_curves_sculpt_mode = (ob == draw_ctx->obact) && @@ -333,8 +335,8 @@ static void OVERLAY_cache_populate(void *vedata, Object *ob) const bool draw_bones = (pd->overlay.flag & V3D_OVERLAY_HIDE_BONES) == 0; const bool draw_wires = draw_surface && has_surface && (pd->wireframe_mode || !pd->hide_overlays); - const bool draw_outlines = !in_edit_mode && !in_paint_mode && renderable && has_surface && - !instance_parent_in_edit_mode && + const bool draw_outlines = !in_edit_mode && !in_paint_mode && !in_sculpt_curve_mode && + renderable && has_surface && !instance_parent_in_edit_mode && (pd->v3d_flag & V3D_SELECT_OUTLINE) && (ob->base_flag & BASE_SELECTED); const bool draw_bone_selection = (ob->type == OB_MESH) && pd->armature.do_pose_fade_geom && diff --git a/source/blender/draw/engines/overlay/overlay_outline.c b/source/blender/draw/engines/overlay/overlay_outline.c index eea9a1a1bef..f2e2acc98a9 100644 --- a/source/blender/draw/engines/overlay/overlay_outline.c +++ b/source/blender/draw/engines/overlay/overlay_outline.c @@ -133,6 +133,10 @@ void OVERLAY_outline_cache_init(OVERLAY_Data *vedata) pd->outlines_gpencil_grp = grp = DRW_shgroup_create(sh_gpencil, psl->outlines_prepass_ps); DRW_shgroup_uniform_bool_copy(grp, "isTransform", (G.moving & G_TRANSFORM_OBJ) != 0); DRW_shgroup_uniform_float_copy(grp, "gpStrokeIndexOffset", 0.0); + + GPUShader *sh_curves = OVERLAY_shader_outline_prepass_curves(); + pd->outlines_curves_grp = grp = DRW_shgroup_create(sh_curves, psl->outlines_prepass_ps); + DRW_shgroup_uniform_bool_copy(grp, "isTransform", (G.moving & G_TRANSFORM_OBJ) != 0); } /* outlines_prepass_ps is still needed for selection of probes. */ @@ -267,6 +271,12 @@ static void OVERLAY_outline_volume(OVERLAY_PrivateData *pd, Object *ob) DRW_shgroup_call(shgroup, geom, ob); } +static void OVERLAY_outline_curves(OVERLAY_PrivateData *pd, Object *ob) +{ + DRWShadingGroup *shgroup = pd->outlines_curves_grp; + DRW_shgroup_curves_create_sub(ob, shgroup, NULL); +} + void OVERLAY_outline_cache_populate(OVERLAY_Data *vedata, Object *ob, OVERLAY_DupliData *dupli, @@ -293,6 +303,11 @@ void OVERLAY_outline_cache_populate(OVERLAY_Data *vedata, return; } + if (ob->type == OB_CURVES) { + OVERLAY_outline_curves(pd, ob); + return; + } + if (ob->type == OB_POINTCLOUD && pd->wireframe_mode) { /* Looks bad in this case. Could be relaxed if we draw a * wireframe of some sort in the future. */ diff --git a/source/blender/draw/engines/overlay/overlay_private.h b/source/blender/draw/engines/overlay/overlay_private.h index 667e443932c..7d216ca54cf 100644 --- a/source/blender/draw/engines/overlay/overlay_private.h +++ b/source/blender/draw/engines/overlay/overlay_private.h @@ -268,6 +268,7 @@ typedef struct OVERLAY_PrivateData { DRWShadingGroup *motion_path_lines_grp; DRWShadingGroup *motion_path_points_grp; DRWShadingGroup *outlines_grp; + DRWShadingGroup *outlines_curves_grp; DRWShadingGroup *outlines_ptcloud_grp; DRWShadingGroup *outlines_gpencil_grp; DRWShadingGroup *paint_depth_grp; @@ -743,6 +744,7 @@ GPUShader *OVERLAY_shader_motion_path_line(void); GPUShader *OVERLAY_shader_motion_path_vert(void); GPUShader *OVERLAY_shader_uniform_color(void); GPUShader *OVERLAY_shader_outline_prepass(bool use_wire); +GPUShader *OVERLAY_shader_outline_prepass_curves(void); GPUShader *OVERLAY_shader_outline_prepass_gpencil(void); GPUShader *OVERLAY_shader_outline_prepass_pointcloud(void); GPUShader *OVERLAY_shader_extra_grid(void); diff --git a/source/blender/draw/engines/overlay/overlay_shader.c b/source/blender/draw/engines/overlay/overlay_shader.c index 7b7e84c307b..faa76ff74b6 100644 --- a/source/blender/draw/engines/overlay/overlay_shader.c +++ b/source/blender/draw/engines/overlay/overlay_shader.c @@ -76,6 +76,7 @@ typedef struct OVERLAY_Shaders { GPUShader *motion_path_line; GPUShader *motion_path_vert; GPUShader *outline_prepass; + GPUShader *outline_prepass_curves; GPUShader *outline_prepass_gpencil; GPUShader *outline_prepass_pointcloud; GPUShader *outline_prepass_wire; @@ -651,6 +652,18 @@ GPUShader *OVERLAY_shader_outline_prepass(bool use_wire) return use_wire ? sh_data->outline_prepass_wire : sh_data->outline_prepass; } +GPUShader *OVERLAY_shader_outline_prepass_curves() +{ + const DRWContextState *draw_ctx = DRW_context_state_get(); + OVERLAY_Shaders *sh_data = &e_data.sh_data[draw_ctx->sh_cfg]; + if (!sh_data->outline_prepass_curves) { + sh_data->outline_prepass_curves = GPU_shader_create_from_info_name( + draw_ctx->sh_cfg ? "overlay_outline_prepass_curves_clipped" : + "overlay_outline_prepass_curves"); + } + return sh_data->outline_prepass_curves; +} + GPUShader *OVERLAY_shader_outline_prepass_gpencil(void) { const DRWContextState *draw_ctx = DRW_context_state_get(); diff --git a/source/blender/draw/engines/overlay/shaders/infos/overlay_outline_info.hh b/source/blender/draw/engines/overlay/shaders/infos/overlay_outline_info.hh index 6f6a9c1622d..288fb3b3cbd 100644 --- a/source/blender/draw/engines/overlay/shaders/infos/overlay_outline_info.hh +++ b/source/blender/draw/engines/overlay/shaders/infos/overlay_outline_info.hh @@ -29,6 +29,16 @@ GPU_SHADER_CREATE_INFO(overlay_outline_prepass_mesh_clipped) GPU_SHADER_INTERFACE_INFO(overlay_outline_prepass_wire_iface, "vert").flat(Type::VEC3, "pos"); +GPU_SHADER_CREATE_INFO(overlay_outline_prepass_curves) + .do_static_compilation(true) + .vertex_source("overlay_outline_prepass_curves_vert.glsl") + .additional_info("draw_hair", "overlay_outline_prepass") + .additional_info("draw_object_infos"); + +GPU_SHADER_CREATE_INFO(overlay_outline_prepass_curves_clipped) + .do_static_compilation(true) + .additional_info("overlay_outline_prepass_curves", "drw_clipped"); + GPU_SHADER_CREATE_INFO(overlay_outline_prepass_wire) .do_static_compilation(true) .define("USE_GEOM") diff --git a/source/blender/draw/engines/overlay/shaders/overlay_outline_prepass_curves_vert.glsl b/source/blender/draw/engines/overlay/shaders/overlay_outline_prepass_curves_vert.glsl new file mode 100644 index 00000000000..f9ec475d21f --- /dev/null +++ b/source/blender/draw/engines/overlay/shaders/overlay_outline_prepass_curves_vert.glsl @@ -0,0 +1,81 @@ + +#pragma BLENDER_REQUIRE(common_view_clipping_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_hair_lib.glsl) + +uint outline_colorid_get(void) +{ + int flag = int(abs(ObjectInfo.w)); + bool is_active = (flag & DRW_BASE_ACTIVE) != 0; + + if (isTransform) { + return 0u; /* colorTransform */ + } + else if (is_active) { + return 3u; /* colorActive */ + } + else { + return 1u; /* colorSelect */ + } + + return 0u; +} + +/* Replace top 2 bits (of the 16bit output) by outlineId. + * This leaves 16K different IDs to create outlines between objects. + vec3 world_pos = point_object_to_world(pos); + * SHIFT = (32 - (16 - 2)) */ +#define SHIFT 18u + +void main() +{ + bool is_persp = (drw_view.winmat[3][3] == 0.0); + float time, thickness; + vec3 center_wpos, tan, binor; + + hair_get_center_pos_tan_binor_time(is_persp, + ModelMatrixInverse, + drw_view.viewinv[3].xyz, + drw_view.viewinv[2].xyz, + center_wpos, + tan, + binor, + time, + thickness); + vec3 world_pos; + if (hairThicknessRes > 1) { + /* Calculate the thickness, thicktime, worldpos taken into account the outline. */ + float outline_width = point_world_to_ndc(center_wpos).w * 1.25 * + drw_view.viewport_size_inverse.y * drw_view.wininv[1][1]; + thickness += outline_width; + float thick_time = float(gl_VertexID % hairThicknessRes) / float(hairThicknessRes - 1); + thick_time = thickness * (thick_time * 2.0 - 1.0); + /* Take object scale into account. + * NOTE: This only works fine with uniform scaling. */ + float scale = 1.0 / length(mat3(ModelMatrixInverse) * binor); + world_pos = center_wpos + binor * thick_time * scale; + } + else { + world_pos = center_wpos; + } + + gl_Position = point_world_to_ndc(world_pos); + +#ifdef USE_GEOM + vert.pos = point_world_to_view(world_pos); +#endif + + /* Small bias to always be on top of the geom. */ + gl_Position.z -= 1e-3; + + /* ID 0 is nothing (background) */ + interp.ob_id = uint(resource_handle + 1); + + /* Should be 2 bits only [0..3]. */ + uint outline_id = outline_colorid_get(); + + /* Combine for 16bit uint target. */ + interp.ob_id = (outline_id << 14u) | ((interp.ob_id << SHIFT) >> SHIFT); + + view_clipping_distances(world_pos); +} diff --git a/source/blender/draw/intern/shaders/common_hair_lib.glsl b/source/blender/draw/intern/shaders/common_hair_lib.glsl index e235da91e8d..b82df4a51dc 100644 --- a/source/blender/draw/intern/shaders/common_hair_lib.glsl +++ b/source/blender/draw/intern/shaders/common_hair_lib.glsl @@ -164,16 +164,15 @@ float hair_shaperadius(float shape, float root, float tip, float time) in float dummy; # endif -void hair_get_pos_tan_binor_time(bool is_persp, - mat4 invmodel_mat, - vec3 camera_pos, - vec3 camera_z, - out vec3 wpos, - out vec3 wtan, - out vec3 wbinor, - out float time, - out float thickness, - out float thick_time) +void hair_get_center_pos_tan_binor_time(bool is_persp, + mat4 invmodel_mat, + vec3 camera_pos, + vec3 camera_z, + out vec3 wpos, + out vec3 wtan, + out vec3 wbinor, + out float time, + out float thickness) { int id = hair_get_base_id(); vec4 data = texelFetch(hairPointBuffer, id); @@ -202,15 +201,27 @@ void hair_get_pos_tan_binor_time(bool is_persp, wbinor = normalize(cross(camera_vec, wtan)); thickness = hair_shaperadius(hairRadShape, hairRadRoot, hairRadTip, time); +} +void hair_get_pos_tan_binor_time(bool is_persp, + mat4 invmodel_mat, + vec3 camera_pos, + vec3 camera_z, + out vec3 wpos, + out vec3 wtan, + out vec3 wbinor, + out float time, + out float thickness, + out float thick_time) +{ + hair_get_center_pos_tan_binor_time( + is_persp, invmodel_mat, camera_pos, camera_z, wpos, wtan, wbinor, time, thickness); if (hairThicknessRes > 1) { thick_time = float(gl_VertexID % hairThicknessRes) / float(hairThicknessRes - 1); thick_time = thickness * (thick_time * 2.0 - 1.0); - /* Take object scale into account. * NOTE: This only works fine with uniform scaling. */ float scale = 1.0 / length(mat3(invmodel_mat) * wbinor); - wpos += wbinor * thick_time * scale; } else { diff --git a/source/blender/draw/tests/shaders_test.cc b/source/blender/draw/tests/shaders_test.cc index f8c5715e2f5..e7baac63aae 100644 --- a/source/blender/draw/tests/shaders_test.cc +++ b/source/blender/draw/tests/shaders_test.cc @@ -256,6 +256,7 @@ static void test_overlay_glsl_shaders() EXPECT_NE(OVERLAY_shader_uniform_color(), nullptr); EXPECT_NE(OVERLAY_shader_outline_prepass(false), nullptr); EXPECT_NE(OVERLAY_shader_outline_prepass(true), nullptr); + EXPECT_NE(OVERLAY_shader_outline_prepass_curves(), nullptr); EXPECT_NE(OVERLAY_shader_outline_prepass_gpencil(), nullptr); EXPECT_NE(OVERLAY_shader_outline_prepass_pointcloud(), nullptr); EXPECT_NE(OVERLAY_shader_extra_grid(), nullptr); @@ -398,6 +399,7 @@ static void test_basic_glsl_shaders() eGPUShaderConfig sh_cfg = static_cast(i); BASIC_shaders_depth_sh_get(sh_cfg); BASIC_shaders_pointcloud_depth_sh_get(sh_cfg); + BASIC_shaders_curves_depth_sh_get(sh_cfg); BASIC_shaders_depth_conservative_sh_get(sh_cfg); BASIC_shaders_pointcloud_depth_conservative_sh_get(sh_cfg); } diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc index cffa590470f..5dfadca5b63 100644 --- a/source/blender/editors/curves/intern/curves_ops.cc +++ b/source/blender/editors/curves/intern/curves_ops.cc @@ -465,7 +465,6 @@ static int curves_convert_from_particle_system_exec(bContext *C, wmOperator *UNU } Object *ob_new = BKE_object_add(&bmain, &view_layer, OB_CURVES, psys_eval->name); - ob_new->dtx |= OB_DRAWBOUNDOX; /* TODO: Remove once there is actual drawing. */ Curves *curves_id = static_cast(ob_new->data); BKE_object_apply_mat4(ob_new, ob_from_orig->obmat, true, false); bke::CurvesGeometry::wrap(curves_id->geometry) = particles_to_curves(*ob_from_eval, *psys_eval); diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc index 64026f7b06b..e712b6e9d32 100644 --- a/source/blender/editors/object/object_add.cc +++ b/source/blender/editors/object/object_add.cc @@ -2044,7 +2044,6 @@ static int object_curves_random_add_exec(bContext *C, wmOperator *op) } Object *object = ED_object_add_type(C, OB_CURVES, nullptr, loc, rot, false, local_view_bits); - object->dtx |= OB_DRAWBOUNDOX; /* TODO: remove once there is actual drawing. */ Curves *curves_id = static_cast(object->data); bke::CurvesGeometry::wrap(curves_id->geometry) = ed::curves::primitive_random_sphere(500, 8); @@ -2081,7 +2080,6 @@ static int object_curves_empty_hair_add_exec(bContext *C, wmOperator *op) Object *surface_ob = CTX_data_active_object(C); Object *object = ED_object_add_type(C, OB_CURVES, nullptr, loc, rot, false, local_view_bits); - object->dtx |= OB_DRAWBOUNDOX; /* TODO: remove once there is actual drawing. */ if (surface_ob != nullptr && surface_ob->type == OB_MESH) { Curves *curves_id = static_cast(object->data); -- cgit v1.2.3 From 155bb95353b2c046413b3e4a949f0f817bf71cba Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Fri, 8 Jul 2022 14:05:11 +0200 Subject: Fix Crash: Reading canvas tool settings. Blender would crash when a file was saved where the tool settings is set to paint on a single image (3d texture painting). Reason is that the selected image memory address wasn't updated when the new address. --- source/blender/blenkernel/intern/scene.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/source/blender/blenkernel/intern/scene.cc b/source/blender/blenkernel/intern/scene.cc index e203d32a658..39157bc9890 100644 --- a/source/blender/blenkernel/intern/scene.cc +++ b/source/blender/blenkernel/intern/scene.cc @@ -1173,6 +1173,7 @@ static void scene_blend_read_data(BlendDataReader *reader, ID *id) BKE_curveprofile_blend_read(reader, sce->toolsettings->custom_bevel_profile_preset); } + BLO_read_data_address(reader, &sce->toolsettings->paint_mode.canvas_image); BLO_read_data_address(reader, &sce->toolsettings->sequencer_tool_settings); } -- cgit v1.2.3 From aa78278ef6571b3784fe3d883ee00dc44bc7ed15 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 8 Jul 2022 13:58:00 +0200 Subject: Fix build error without unity build, after recent changes --- source/blender/nodes/composite/node_composite_tree.cc | 1 + source/blender/nodes/composite/nodes/node_composite_planetrackdeform.cc | 2 ++ 2 files changed, 3 insertions(+) diff --git a/source/blender/nodes/composite/node_composite_tree.cc b/source/blender/nodes/composite/node_composite_tree.cc index 0b75dd9cef3..32b5d98a556 100644 --- a/source/blender/nodes/composite/node_composite_tree.cc +++ b/source/blender/nodes/composite/node_composite_tree.cc @@ -15,6 +15,7 @@ #include "BKE_context.h" #include "BKE_global.h" +#include "BKE_image.h" #include "BKE_main.h" #include "BKE_node.h" #include "BKE_node_tree_update.h" diff --git a/source/blender/nodes/composite/nodes/node_composite_planetrackdeform.cc b/source/blender/nodes/composite/nodes/node_composite_planetrackdeform.cc index 472bf2344ca..6557478fc4b 100644 --- a/source/blender/nodes/composite/nodes/node_composite_planetrackdeform.cc +++ b/source/blender/nodes/composite/nodes/node_composite_planetrackdeform.cc @@ -8,6 +8,8 @@ #include "DNA_movieclip_types.h" #include "DNA_tracking_types.h" +#include "BKE_context.h" +#include "BKE_lib_id.h" #include "BKE_tracking.h" #include "RNA_access.h" -- cgit v1.2.3 From 05b38ecc7835b32a9f3aedf36ead4b3e41ec6ca4 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Fri, 8 Jul 2022 14:45:48 +0200 Subject: Curves: support deforming curves on surface Curves that are attached to a surface can now follow the surface when it is modified using shape keys or modifiers (but not when the original surface is deformed in edit or sculpt mode). The surface is allowed to be changed in any way that keeps uv maps intact. So deformation is allowed, but also some topology changes like subdivision. The following features are added: * A new `Deform Curves on Surface` node, which deforms curves with attachment information based on the surface object and uv map set in the properties panel. * A new `Add Rest Position` checkbox in the shape keys panel. When checked, a new `rest_position` vector attribute is added to the mesh before shape keys and modifiers are applied. This is necessary to support proper deformation of the curves, but can also be used for other purposes. * The `Add > Curve > Empty Hair` operator now sets up a simple geometry nodes setup that deforms the hair. It also makes sure that the rest position attribute is added to the surface. * A new `Object (Attach Curves to Surface)` operator in the `Set Parent To` (ctrl+P) menu, which attaches existing curves to the surface and sets the surface object as parent. Limitations: * Sculpting the procedurally deformed curves will be implemented separately. * The `Deform Curves on Surface` node is not generic and can only be used for one specific purpose currently. We plan to generalize this more in the future by adding support by exposing more inputs and/or by turning it into a node group. Differential Revision: https://developer.blender.org/D14864 --- .../scripts/startup/bl_ui/properties_data_mesh.py | 2 + release/scripts/startup/nodeitems_builtins.py | 1 + source/blender/blenkernel/BKE_mesh.h | 3 +- source/blender/blenkernel/BKE_node.h | 1 + source/blender/blenkernel/intern/DerivedMesh.cc | 22 ++ source/blender/blenkernel/intern/mesh.cc | 34 +- source/blender/blenkernel/intern/node.cc | 1 + source/blender/editors/curves/intern/curves_add.cc | 84 +++++ source/blender/editors/curves/intern/curves_ops.cc | 86 ++++- source/blender/editors/include/ED_curves.h | 1 + source/blender/editors/object/object_add.cc | 41 ++- source/blender/editors/object/object_relations.c | 10 +- source/blender/makesdna/DNA_object_types.h | 9 +- source/blender/makesrna/intern/rna_object.c | 8 + source/blender/modifiers/intern/MOD_nodes.cc | 13 + source/blender/nodes/NOD_geometry.h | 1 + source/blender/nodes/NOD_static_types.h | 1 + source/blender/nodes/geometry/CMakeLists.txt | 1 + .../nodes/node_geo_deform_curves_on_surface.cc | 364 +++++++++++++++++++++ 19 files changed, 654 insertions(+), 29 deletions(-) create mode 100644 source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc diff --git a/release/scripts/startup/bl_ui/properties_data_mesh.py b/release/scripts/startup/bl_ui/properties_data_mesh.py index 0b043905713..2fc949f4aae 100644 --- a/release/scripts/startup/bl_ui/properties_data_mesh.py +++ b/release/scripts/startup/bl_ui/properties_data_mesh.py @@ -411,6 +411,8 @@ class DATA_PT_shape_keys(MeshButtonsPanel, Panel): row.active = enable_edit_value row.prop(key, "eval_time") + layout.prop(ob, "add_rest_position_attribute") + class DATA_PT_uv_texture(MeshButtonsPanel, Panel): bl_label = "UV Maps" diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py index 3f49fb9fb58..e59c98163d7 100644 --- a/release/scripts/startup/nodeitems_builtins.py +++ b/release/scripts/startup/nodeitems_builtins.py @@ -73,6 +73,7 @@ def curve_node_items(context): yield NodeItem("GeometryNodeCurveLength") yield NodeItem("GeometryNodeCurveToMesh") yield NodeItem("GeometryNodeCurveToPoints") + yield NodeItem("GeometryNodeDeformCurvesWithSurface") yield NodeItem("GeometryNodeFillCurve") yield NodeItem("GeometryNodeFilletCurve") yield NodeItem("GeometryNodeResampleCurve") diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h index ff4f4a8dc9e..366083fee7f 100644 --- a/source/blender/blenkernel/BKE_mesh.h +++ b/source/blender/blenkernel/BKE_mesh.h @@ -692,7 +692,8 @@ void BKE_mesh_calc_normals_split(struct Mesh *mesh); * to split geometry along sharp edges. */ void BKE_mesh_calc_normals_split_ex(struct Mesh *mesh, - struct MLoopNorSpaceArray *r_lnors_spacearr); + struct MLoopNorSpaceArray *r_lnors_spacearr, + float (*r_corner_normals)[3]); /** * Higher level functions hiding most of the code needed around call to diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index e13ac3180ec..b7962ade312 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1501,6 +1501,7 @@ struct TexResult; #define GEO_NODE_MESH_TO_VOLUME 1164 #define GEO_NODE_UV_UNWRAP 1165 #define GEO_NODE_UV_PACK_ISLANDS 1166 +#define GEO_NODE_DEFORM_CURVES_ON_SURFACE 1167 /** \} */ diff --git a/source/blender/blenkernel/intern/DerivedMesh.cc b/source/blender/blenkernel/intern/DerivedMesh.cc index ffac89c15e6..c2ea01bcadf 100644 --- a/source/blender/blenkernel/intern/DerivedMesh.cc +++ b/source/blender/blenkernel/intern/DerivedMesh.cc @@ -66,6 +66,9 @@ # include "DNA_userdef_types.h" #endif +using blender::float3; +using blender::IndexRange; + /* very slow! enable for testing only! */ //#define USE_MODIFIER_VALIDATE @@ -814,6 +817,25 @@ static void mesh_calc_modifiers(struct Depsgraph *depsgraph, /* Clear errors before evaluation. */ BKE_modifiers_clear_errors(ob); + if (ob->modifier_flag & OB_MODIFIER_FLAG_ADD_REST_POSITION) { + if (mesh_final == nullptr) { + mesh_final = BKE_mesh_copy_for_eval(mesh_input, true); + ASSERT_IS_VALID_MESH(mesh_final); + } + float3 *rest_positions = static_cast(CustomData_add_layer_named(&mesh_final->vdata, + CD_PROP_FLOAT3, + CD_DEFAULT, + nullptr, + mesh_final->totvert, + "rest_position")); + blender::threading::parallel_for( + IndexRange(mesh_final->totvert), 1024, [&](const IndexRange range) { + for (const int i : range) { + rest_positions[i] = mesh_final->mvert[i].co; + } + }); + } + /* Apply all leading deform modifiers. */ if (use_deform) { for (; md; md = md->next, md_datamask = md_datamask->next) { diff --git a/source/blender/blenkernel/intern/mesh.cc b/source/blender/blenkernel/intern/mesh.cc index 98fa551590c..2a14370bf93 100644 --- a/source/blender/blenkernel/intern/mesh.cc +++ b/source/blender/blenkernel/intern/mesh.cc @@ -1848,9 +1848,25 @@ void BKE_mesh_vert_coords_apply_with_mat4(Mesh *mesh, BKE_mesh_tag_coords_changed(mesh); } -void BKE_mesh_calc_normals_split_ex(Mesh *mesh, MLoopNorSpaceArray *r_lnors_spacearr) +static float (*ensure_corner_normal_layer(Mesh &mesh))[3] { float(*r_loopnors)[3]; + if (CustomData_has_layer(&mesh.ldata, CD_NORMAL)) { + r_loopnors = (float(*)[3])CustomData_get_layer(&mesh.ldata, CD_NORMAL); + memset(r_loopnors, 0, sizeof(float[3]) * mesh.totloop); + } + else { + r_loopnors = (float(*)[3])CustomData_add_layer( + &mesh.ldata, CD_NORMAL, CD_CALLOC, nullptr, mesh.totloop); + CustomData_set_layer_flag(&mesh.ldata, CD_NORMAL, CD_FLAG_TEMPORARY); + } + return r_loopnors; +} + +void BKE_mesh_calc_normals_split_ex(Mesh *mesh, + MLoopNorSpaceArray *r_lnors_spacearr, + float (*r_corner_normals)[3]) +{ short(*clnors)[2] = nullptr; /* Note that we enforce computing clnors when the clnor space array is requested by caller here. @@ -1860,16 +1876,6 @@ void BKE_mesh_calc_normals_split_ex(Mesh *mesh, MLoopNorSpaceArray *r_lnors_spac ((mesh->flag & ME_AUTOSMOOTH) != 0); const float split_angle = (mesh->flag & ME_AUTOSMOOTH) != 0 ? mesh->smoothresh : (float)M_PI; - if (CustomData_has_layer(&mesh->ldata, CD_NORMAL)) { - r_loopnors = (float(*)[3])CustomData_get_layer(&mesh->ldata, CD_NORMAL); - memset(r_loopnors, 0, sizeof(float[3]) * mesh->totloop); - } - else { - r_loopnors = (float(*)[3])CustomData_add_layer( - &mesh->ldata, CD_NORMAL, CD_CALLOC, nullptr, mesh->totloop); - CustomData_set_layer_flag(&mesh->ldata, CD_NORMAL, CD_FLAG_TEMPORARY); - } - /* may be nullptr */ clnors = (short(*)[2])CustomData_get_layer(&mesh->ldata, CD_CUSTOMLOOPNORMAL); @@ -1879,7 +1885,7 @@ void BKE_mesh_calc_normals_split_ex(Mesh *mesh, MLoopNorSpaceArray *r_lnors_spac mesh->medge, mesh->totedge, mesh->mloop, - r_loopnors, + r_corner_normals, mesh->totloop, mesh->mpoly, BKE_mesh_poly_normals_ensure(mesh), @@ -1895,7 +1901,7 @@ void BKE_mesh_calc_normals_split_ex(Mesh *mesh, MLoopNorSpaceArray *r_lnors_spac void BKE_mesh_calc_normals_split(Mesh *mesh) { - BKE_mesh_calc_normals_split_ex(mesh, nullptr); + BKE_mesh_calc_normals_split_ex(mesh, nullptr, ensure_corner_normal_layer(*mesh)); } /* Split faces helper functions. */ @@ -2114,7 +2120,7 @@ void BKE_mesh_split_faces(Mesh *mesh, bool free_loop_normals) MLoopNorSpaceArray lnors_spacearr = {nullptr}; /* Compute loop normals and loop normal spaces (a.k.a. smooth fans of faces around vertices). */ - BKE_mesh_calc_normals_split_ex(mesh, &lnors_spacearr); + BKE_mesh_calc_normals_split_ex(mesh, &lnors_spacearr, ensure_corner_normal_layer(*mesh)); /* Stealing memarena from loop normals space array. */ MemArena *memarena = lnors_spacearr.mem; diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 5be912ffb2b..c6f140b9260 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -4749,6 +4749,7 @@ static void registerGeometryNodes() register_node_type_geo_curve_to_mesh(); register_node_type_geo_curve_to_points(); register_node_type_geo_curve_trim(); + register_node_type_geo_deform_curves_on_surface(); register_node_type_geo_delete_geometry(); register_node_type_geo_duplicate_elements(); register_node_type_geo_distribute_points_on_faces(); diff --git a/source/blender/editors/curves/intern/curves_add.cc b/source/blender/editors/curves/intern/curves_add.cc index 552ef1d96c8..79916253207 100644 --- a/source/blender/editors/curves/intern/curves_add.cc +++ b/source/blender/editors/curves/intern/curves_add.cc @@ -6,12 +6,96 @@ #include "BLI_rand.hh" +#include "BKE_context.h" #include "BKE_curves.hh" +#include "BKE_node.h" +#include "BKE_node_runtime.hh" #include "ED_curves.h" +#include "ED_node.h" +#include "ED_object.h" + +#include "DNA_modifier_types.h" +#include "DNA_node_types.h" +#include "DNA_object_types.h" namespace blender::ed::curves { +static bool has_surface_deformation_node(const bNodeTree &ntree) +{ + LISTBASE_FOREACH (const bNode *, node, &ntree.nodes) { + if (node->type == GEO_NODE_DEFORM_CURVES_ON_SURFACE) { + return true; + } + if (node->type == NODE_GROUP) { + if (node->id != nullptr) { + if (has_surface_deformation_node(*reinterpret_cast(node->id))) { + return true; + } + } + } + } + return false; +} + +static bool has_surface_deformation_node(const Object &curves_ob) +{ + LISTBASE_FOREACH (const ModifierData *, md, &curves_ob.modifiers) { + if (md->type != eModifierType_Nodes) { + continue; + } + const NodesModifierData *nmd = reinterpret_cast(md); + if (nmd->node_group == nullptr) { + continue; + } + if (has_surface_deformation_node(*nmd->node_group)) { + return true; + } + } + return false; +} + +void ensure_surface_deformation_node_exists(bContext &C, Object &curves_ob) +{ + if (has_surface_deformation_node(curves_ob)) { + return; + } + + Main *bmain = CTX_data_main(&C); + Scene *scene = CTX_data_scene(&C); + + ModifierData *md = ED_object_modifier_add( + nullptr, bmain, scene, &curves_ob, "Surface Deform", eModifierType_Nodes); + NodesModifierData &nmd = *reinterpret_cast(md); + nmd.node_group = ntreeAddTree(bmain, "Surface Deform", "GeometryNodeTree"); + + bNodeTree *ntree = nmd.node_group; + ntreeAddSocketInterface(ntree, SOCK_IN, "NodeSocketGeometry", "Geometry"); + ntreeAddSocketInterface(ntree, SOCK_OUT, "NodeSocketGeometry", "Geometry"); + bNode *group_input = nodeAddStaticNode(&C, ntree, NODE_GROUP_INPUT); + bNode *group_output = nodeAddStaticNode(&C, ntree, NODE_GROUP_OUTPUT); + bNode *deform_node = nodeAddStaticNode(&C, ntree, GEO_NODE_DEFORM_CURVES_ON_SURFACE); + + ED_node_tree_propagate_change(&C, bmain, nmd.node_group); + + nodeAddLink(ntree, + group_input, + static_cast(group_input->outputs.first), + deform_node, + nodeFindSocket(deform_node, SOCK_IN, "Curves")); + nodeAddLink(ntree, + deform_node, + nodeFindSocket(deform_node, SOCK_OUT, "Curves"), + group_output, + static_cast(group_output->inputs.first)); + + group_input->locx = -200; + group_output->locx = 200; + deform_node->locx = 0; + + ED_node_tree_propagate_change(&C, bmain, nmd.node_group); +} + bke::CurvesGeometry primitive_random_sphere(const int curves_size, const int points_per_curve) { bke::CurvesGeometry curves(points_per_curve * curves_size, curves_size); diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc index 5dfadca5b63..49e21d00195 100644 --- a/source/blender/editors/curves/intern/curves_ops.cc +++ b/source/blender/editors/curves/intern/curves_ops.cc @@ -681,7 +681,8 @@ static int snap_curves_to_surface_exec(bContext *C, wmOperator *op) BKE_report(op->reports, RPT_INFO, "Could not snap some curves to the surface"); } - WM_main_add_notifier(NC_OBJECT | ND_DRAW, nullptr); + /* Refresh the entire window to also clear eventual modifier and nodes editor warnings.*/ + WM_event_add_notifier(C, NC_WINDOW, nullptr); return OPERATOR_FINISHED; } @@ -944,6 +945,88 @@ static void SCULPT_CURVES_OT_select_all(wmOperatorType *ot) WM_operator_properties_select_all(ot); } +namespace surface_set { + +static bool surface_set_poll(bContext *C) +{ + const Object *object = CTX_data_active_object(C); + if (object == nullptr) { + return false; + } + if (object->type != OB_MESH) { + return false; + } + return true; +} + +static int surface_set_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + + Object &new_surface_ob = *CTX_data_active_object(C); + + Mesh &new_surface_mesh = *static_cast(new_surface_ob.data); + const char *new_uv_map_name = CustomData_get_active_layer_name(&new_surface_mesh.ldata, + CD_MLOOPUV); + + CTX_DATA_BEGIN (C, Object *, selected_ob, selected_objects) { + if (selected_ob->type != OB_CURVES) { + continue; + } + Object &curves_ob = *selected_ob; + Curves &curves_id = *static_cast(curves_ob.data); + + MEM_SAFE_FREE(curves_id.surface_uv_map); + if (new_uv_map_name != nullptr) { + curves_id.surface_uv_map = BLI_strdup(new_uv_map_name); + } + + bool missing_uvs; + bool invalid_uvs; + snap_curves_to_surface::snap_curves_to_surface_exec_object( + curves_ob, + new_surface_ob, + snap_curves_to_surface::AttachMode::Nearest, + &invalid_uvs, + &missing_uvs); + + /* Add deformation modifier if necessary. */ + blender::ed::curves::ensure_surface_deformation_node_exists(*C, curves_ob); + + curves_id.surface = &new_surface_ob; + ED_object_parent_set( + op->reports, C, scene, &curves_ob, &new_surface_ob, PAR_OBJECT, false, true, nullptr); + + DEG_id_tag_update(&curves_ob.id, ID_RECALC_TRANSFORM); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, &curves_id); + + /* Required for deformation. */ + new_surface_ob.modifier_flag |= OB_MODIFIER_FLAG_ADD_REST_POSITION; + DEG_id_tag_update(&new_surface_ob.id, ID_RECALC_GEOMETRY); + } + CTX_DATA_END; + + DEG_relations_tag_update(bmain); + + return OPERATOR_FINISHED; +} + +} // namespace surface_set + +static void CURVES_OT_surface_set(wmOperatorType *ot) +{ + ot->name = "Set Curves Surface Object"; + ot->idname = __func__; + ot->description = + "Use the active object as surface for selected curves objects and set it as the parent"; + + ot->exec = surface_set::surface_set_exec; + ot->poll = surface_set::surface_set_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + } // namespace blender::ed::curves void ED_operatortypes_curves() @@ -955,4 +1038,5 @@ void ED_operatortypes_curves() WM_operatortype_append(CURVES_OT_set_selection_domain); WM_operatortype_append(SCULPT_CURVES_OT_select_all); WM_operatortype_append(CURVES_OT_disable_selection); + WM_operatortype_append(CURVES_OT_surface_set); } diff --git a/source/blender/editors/include/ED_curves.h b/source/blender/editors/include/ED_curves.h index 68e09fd1b12..0817241a5c2 100644 --- a/source/blender/editors/include/ED_curves.h +++ b/source/blender/editors/include/ED_curves.h @@ -29,6 +29,7 @@ bke::CurvesGeometry primitive_random_sphere(int curves_size, int points_per_curv bool selection_operator_poll(bContext *C); bool has_anything_selected(const Curves &curves_id); VectorSet get_unique_editable_curves(const bContext &C); +void ensure_surface_deformation_node_exists(bContext &C, Object &curves_ob); } // namespace blender::ed::curves #endif diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc index e712b6e9d32..c350b955e14 100644 --- a/source/blender/editors/object/object_add.cc +++ b/source/blender/editors/object/object_add.cc @@ -24,6 +24,7 @@ #include "DNA_material_types.h" #include "DNA_mesh_types.h" #include "DNA_meta_types.h" +#include "DNA_modifier_types.h" #include "DNA_object_fluidsim_types.h" #include "DNA_object_force_types.h" #include "DNA_object_types.h" @@ -72,6 +73,7 @@ #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" #include "BKE_nla.h" +#include "BKE_node.h" #include "BKE_object.h" #include "BKE_particle.h" #include "BKE_pointcloud.h" @@ -114,6 +116,10 @@ #include "object_intern.h" +using blender::float3; +using blender::float4x4; +using blender::Vector; + /* -------------------------------------------------------------------- */ /** \name Local Enum Declarations * \{ */ @@ -2070,29 +2076,42 @@ void OBJECT_OT_curves_random_add(wmOperatorType *ot) static int object_curves_empty_hair_add_exec(bContext *C, wmOperator *op) { + Scene *scene = CTX_data_scene(C); + ushort local_view_bits; - float loc[3], rot[3]; + blender::float3 loc, rot; if (!ED_object_add_generic_get_opts( C, op, 'Z', loc, rot, nullptr, nullptr, &local_view_bits, nullptr)) { return OPERATOR_CANCELLED; } Object *surface_ob = CTX_data_active_object(C); + BLI_assert(surface_ob != nullptr); - Object *object = ED_object_add_type(C, OB_CURVES, nullptr, loc, rot, false, local_view_bits); + Object *curves_ob = ED_object_add_type(C, OB_CURVES, nullptr, loc, rot, false, local_view_bits); - if (surface_ob != nullptr && surface_ob->type == OB_MESH) { - Curves *curves_id = static_cast(object->data); - curves_id->surface = surface_ob; - id_us_plus(&surface_ob->id); + /* Set surface object. */ + Curves *curves_id = static_cast(curves_ob->data); + curves_id->surface = surface_ob; - Mesh *surface_mesh = static_cast(surface_ob->data); - const char *uv_name = CustomData_get_active_layer_name(&surface_mesh->ldata, CD_MLOOPUV); - if (uv_name != nullptr) { - curves_id->surface_uv_map = BLI_strdup(uv_name); - } + /* Parent to surface object. */ + ED_object_parent_set( + op->reports, C, scene, curves_ob, surface_ob, PAR_OBJECT, false, true, nullptr); + + /* Decide which UV map to use for attachment. */ + Mesh *surface_mesh = static_cast(surface_ob->data); + const char *uv_name = CustomData_get_active_layer_name(&surface_mesh->ldata, CD_MLOOPUV); + if (uv_name != nullptr) { + curves_id->surface_uv_map = BLI_strdup(uv_name); } + /* Add deformation modifier. */ + blender::ed::curves::ensure_surface_deformation_node_exists(*C, *curves_ob); + + /* Make sure the surface object has a rest position attribute which is necessary for + * deformations. */ + surface_ob->modifier_flag |= OB_MODIFIER_FLAG_ADD_REST_POSITION; + return OPERATOR_FINISHED; } diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c index b58b0dac79b..01042824aac 100644 --- a/source/blender/editors/object/object_relations.c +++ b/source/blender/editors/object/object_relations.c @@ -951,7 +951,7 @@ static int parent_set_invoke_menu(bContext *C, wmOperatorType *ot) 1); struct { - bool mesh, gpencil; + bool mesh, gpencil, curves; } has_children_of_type = {0}; CTX_DATA_BEGIN (C, Object *, child, selected_editable_objects) { @@ -964,6 +964,9 @@ static int parent_set_invoke_menu(bContext *C, wmOperatorType *ot) if (child->type == OB_GPENCIL) { has_children_of_type.gpencil = true; } + if (child->type == OB_CURVES) { + has_children_of_type.curves = true; + } } CTX_DATA_END; @@ -987,6 +990,11 @@ static int parent_set_invoke_menu(bContext *C, wmOperatorType *ot) else if (parent->type == OB_LATTICE) { uiItemEnumO_ptr(layout, ot, NULL, 0, "type", PAR_LATTICE); } + else if (parent->type == OB_MESH) { + if (has_children_of_type.curves) { + uiItemO(layout, "Object (Attach Curves to Surface)", ICON_NONE, "CURVES_OT_surface_set"); + } + } /* vertex parenting */ if (OB_TYPE_SUPPORT_PARVERT(parent->type)) { diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h index db56c79de4e..ac9e61e03e8 100644 --- a/source/blender/makesdna/DNA_object_types.h +++ b/source/blender/makesdna/DNA_object_types.h @@ -434,7 +434,10 @@ typedef struct Object { char empty_image_visibility_flag; char empty_image_depth; char empty_image_flag; - char _pad8[5]; + + /** ObjectModifierFlag */ + uint8_t modifier_flag; + char _pad8[4]; struct PreviewImage *preview; @@ -788,6 +791,10 @@ enum { OB_EMPTY_IMAGE_USE_ALPHA_BLEND = 1 << 0, }; +typedef enum ObjectModifierFlag { + OB_MODIFIER_FLAG_ADD_REST_POSITION = 1 << 0, +} ObjectModifierFlag; + #define MAX_DUPLI_RECUR 8 #ifdef __cplusplus diff --git a/source/blender/makesrna/intern/rna_object.c b/source/blender/makesrna/intern/rna_object.c index 99315a580cf..a68ef361f04 100644 --- a/source/blender/makesrna/intern/rna_object.c +++ b/source/blender/makesrna/intern/rna_object.c @@ -3579,6 +3579,14 @@ static void rna_def_object(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Empty Image Side", "Show front/back side"); RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, NULL); + prop = RNA_def_property(srna, "add_rest_position_attribute", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "modifier_flag", OB_MODIFIER_FLAG_ADD_REST_POSITION); + RNA_def_property_ui_text(prop, + "Add Rest Position", + "Add a \"rest_position\" attribute that is a copy of the position " + "attribute before shape keys and modifiers are evaluated"); + RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_Object_internal_update_data"); + /* render */ prop = RNA_def_property(srna, "pass_index", PROP_INT, PROP_UNSIGNED); RNA_def_property_int_sdna(prop, NULL, "index"); diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 73db56186de..a63a89e076b 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -21,6 +21,7 @@ #include "BLI_utildefines.h" #include "DNA_collection_types.h" +#include "DNA_curves_types.h" #include "DNA_defaults.h" #include "DNA_material_types.h" #include "DNA_mesh_types.h" @@ -190,6 +191,10 @@ static bool node_needs_own_transform_relation(const bNode &node) return storage.transform_space == GEO_NODE_TRANSFORM_SPACE_RELATIVE; } + if (node.type == GEO_NODE_DEFORM_CURVES_ON_SURFACE) { + return true; + } + return false; } @@ -269,6 +274,14 @@ static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphConte Set used_ids; find_used_ids_from_settings(nmd->settings, used_ids); process_nodes_for_depsgraph(*nmd->node_group, used_ids, needs_own_transform_relation); + + if (ctx->object->type == OB_CURVES) { + Curves *curves_id = static_cast(ctx->object->data); + if (curves_id->surface != nullptr) { + used_ids.add(&curves_id->surface->id); + } + } + for (ID *id : used_ids) { switch ((ID_Type)GS(id->name)) { case ID_OB: { diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index 8f15add33fd..86c276fbd6f 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -47,6 +47,7 @@ void register_node_type_geo_curve_subdivide(void); void register_node_type_geo_curve_to_mesh(void); void register_node_type_geo_curve_to_points(void); void register_node_type_geo_curve_trim(void); +void register_node_type_geo_deform_curves_on_surface(void); void register_node_type_geo_delete_geometry(void); void register_node_type_geo_duplicate_elements(void); void register_node_type_geo_distribute_points_on_faces(void); diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 302656b8a7c..7c7f114bb78 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -301,6 +301,7 @@ DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_PARAMETER, 0, "SPLINE_PARAMETER", Sp DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_TYPE, def_geo_curve_spline_type, "CURVE_SPLINE_TYPE", CurveSplineType, "Set Spline Type", "") DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "") DefNode(GeometryNode, GEO_NODE_CURVE_TO_POINTS, def_geo_curve_to_points, "CURVE_TO_POINTS", CurveToPoints, "Curve to Points", "") +DefNode(GeometryNode, GEO_NODE_DEFORM_CURVES_ON_SURFACE, 0, "DEFORM_CURVES_ON_SURFACE", DeformCurvesOnSurface, "Deform Curves on Surface", "") DefNode(GeometryNode, GEO_NODE_DELETE_GEOMETRY, def_geo_delete_geometry, "DELETE_GEOMETRY", DeleteGeometry, "Delete Geometry", "") DefNode(GeometryNode, GEO_NODE_DUPLICATE_ELEMENTS, def_geo_duplicate_elements, "DUPLICATE_ELEMENTS", DuplicateElements, "Duplicate Elements", "") DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_ON_FACES, def_geo_distribute_points_on_faces, "DISTRIBUTE_POINTS_ON_FACES", DistributePointsOnFaces, "Distribute Points on Faces", "") diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index 950124f75d0..d87f0312958 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -57,6 +57,7 @@ set(SRC nodes/node_geo_curve_to_mesh.cc nodes/node_geo_curve_to_points.cc nodes/node_geo_curve_trim.cc + nodes/node_geo_deform_curves_on_surface.cc nodes/node_geo_delete_geometry.cc nodes/node_geo_distribute_points_on_faces.cc nodes/node_geo_dual_mesh.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc new file mode 100644 index 00000000000..60b5f0383ca --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc @@ -0,0 +1,364 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_attribute_math.hh" +#include "BKE_curves.hh" +#include "BKE_editmesh.h" +#include "BKE_lib_id.h" +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_mesh_wrapper.h" +#include "BKE_modifier.h" +#include "BKE_type_conversions.hh" + +#include "BLI_float3x3.hh" +#include "BLI_task.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "NOD_socket_search_link.hh" + +#include "GEO_reverse_uv_sampler.hh" + +#include "DEG_depsgraph_query.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_deform_curves_on_surface_cc { + +using attribute_math::mix3; +using bke::CurvesGeometry; +using geometry::ReverseUVSampler; + +NODE_STORAGE_FUNCS(NodeGeometryCurveTrim) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input(N_("Curves")).supported_type(GEO_COMPONENT_TYPE_CURVE); + b.add_output(N_("Curves")); +} + +static void deform_curves(CurvesGeometry &curves, + const Mesh &surface_mesh_old, + const Mesh &surface_mesh_new, + const Span curve_attachment_uvs, + const ReverseUVSampler &reverse_uv_sampler_old, + const ReverseUVSampler &reverse_uv_sampler_new, + const Span corner_normals_old, + const Span corner_normals_new, + const Span rest_positions, + const float4x4 &surface_to_curves, + std::atomic &r_invalid_uv_count) +{ + /* Find attachment points on old and new mesh. */ + const int curves_num = curves.curves_num(); + Array surface_samples_old(curves_num); + Array surface_samples_new(curves_num); + threading::parallel_invoke( + [&]() { reverse_uv_sampler_old.sample_many(curve_attachment_uvs, surface_samples_old); }, + [&]() { reverse_uv_sampler_new.sample_many(curve_attachment_uvs, surface_samples_new); }); + + MutableSpan positions = curves.positions_for_write(); + + const float4x4 curves_to_surface = surface_to_curves.inverted(); + + threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange range) { + for (const int curve_i : range) { + const ReverseUVSampler::Result &surface_sample_old = surface_samples_old[curve_i]; + if (surface_sample_old.type != ReverseUVSampler::ResultType::Ok) { + r_invalid_uv_count++; + continue; + } + const ReverseUVSampler::Result &surface_sample_new = surface_samples_new[curve_i]; + if (surface_sample_new.type != ReverseUVSampler::ResultType::Ok) { + r_invalid_uv_count++; + continue; + } + + const MLoopTri &looptri_old = *surface_sample_old.looptri; + const MLoopTri &looptri_new = *surface_sample_new.looptri; + const float3 &bary_weights_old = surface_sample_old.bary_weights; + const float3 &bary_weights_new = surface_sample_new.bary_weights; + + const int corner_0_old = looptri_old.tri[0]; + const int corner_1_old = looptri_old.tri[1]; + const int corner_2_old = looptri_old.tri[2]; + + const int corner_0_new = looptri_new.tri[0]; + const int corner_1_new = looptri_new.tri[1]; + const int corner_2_new = looptri_new.tri[2]; + + const int vert_0_old = surface_mesh_old.mloop[corner_0_old].v; + const int vert_1_old = surface_mesh_old.mloop[corner_1_old].v; + const int vert_2_old = surface_mesh_old.mloop[corner_2_old].v; + + const int vert_0_new = surface_mesh_new.mloop[corner_0_new].v; + const int vert_1_new = surface_mesh_new.mloop[corner_1_new].v; + const int vert_2_new = surface_mesh_new.mloop[corner_2_new].v; + + const float3 &normal_0_old = corner_normals_old[corner_0_old]; + const float3 &normal_1_old = corner_normals_old[corner_1_old]; + const float3 &normal_2_old = corner_normals_old[corner_2_old]; + const float3 normal_old = math::normalize( + mix3(bary_weights_old, normal_0_old, normal_1_old, normal_2_old)); + + const float3 &normal_0_new = corner_normals_new[corner_0_new]; + const float3 &normal_1_new = corner_normals_new[corner_1_new]; + const float3 &normal_2_new = corner_normals_new[corner_2_new]; + const float3 normal_new = math::normalize( + mix3(bary_weights_new, normal_0_new, normal_1_new, normal_2_new)); + + const float3 &pos_0_old = surface_mesh_old.mvert[vert_0_old].co; + const float3 &pos_1_old = surface_mesh_old.mvert[vert_1_old].co; + const float3 &pos_2_old = surface_mesh_old.mvert[vert_2_old].co; + const float3 pos_old = mix3(bary_weights_old, pos_0_old, pos_1_old, pos_2_old); + + const float3 &pos_0_new = surface_mesh_new.mvert[vert_0_new].co; + const float3 &pos_1_new = surface_mesh_new.mvert[vert_1_new].co; + const float3 &pos_2_new = surface_mesh_new.mvert[vert_2_new].co; + const float3 pos_new = mix3(bary_weights_new, pos_0_new, pos_1_new, pos_2_new); + + /* The translation is just the difference between the old and new position on the surface. */ + const float3 translation = pos_new - pos_old; + + const float3 &rest_pos_0 = rest_positions[vert_0_new]; + const float3 &rest_pos_1 = rest_positions[vert_1_new]; + + /* The tangent reference direction is used to determine the rotation of the surface point + * around its normal axis. It's important that the old and new tangent reference are computed + * in a consistent way. If the surface has not been rotated, the old and new tangent + * reference have to have the same direction. For that reason, the old tangent reference is + * computed based on the rest position attribute instead of positions on the old mesh. This + * way the old and new tangent reference use the same topology. + * + * TODO: Figure out if this can be smoothly interpolated across the surface as well. + * Currently, this is a source of discontinuity in the deformation, because the vector + * changes intantly from one triangle to the next. */ + const float3 tangent_reference_dir_old = rest_pos_1 - rest_pos_0; + const float3 tangent_reference_dir_new = pos_1_new - pos_0_new; + + /* Compute first local tangent based on the (potentially smoothed) normal and the tangent + * reference. */ + const float3 tangent_x_old = math::normalize( + math::cross(normal_old, tangent_reference_dir_old)); + const float3 tangent_x_new = math::normalize( + math::cross(normal_new, tangent_reference_dir_new)); + + /* The second tangent defined by the normal and first tangent. */ + const float3 tangent_y_old = math::normalize(math::cross(normal_old, tangent_x_old)); + const float3 tangent_y_new = math::normalize(math::cross(normal_new, tangent_x_new)); + + /* Construct rotation matrix that encodes the orientation of the old surface position. */ + float3x3 rotation_old; + copy_v3_v3(rotation_old.values[0], tangent_x_old); + copy_v3_v3(rotation_old.values[1], tangent_y_old); + copy_v3_v3(rotation_old.values[2], normal_old); + + /* Construct rotation matrix that encodes the orientation of the new surface position. */ + float3x3 rotation_new; + copy_v3_v3(rotation_new.values[0], tangent_x_new); + copy_v3_v3(rotation_new.values[1], tangent_y_new); + copy_v3_v3(rotation_new.values[2], normal_new); + + /* Can use transpose instead of inverse because the matrix is orthonormal. In the case of + * zero-area triangles, the matrix would not be orthonormal, but in this case, none of this + * works anyway. */ + const float3x3 rotation_old_inv = rotation_old.transposed(); + + /* Compute a rotation matrix that rotates points from the old to the new surface + * orientation. */ + const float3x3 rotation = rotation_new * rotation_old_inv; + float4x4 rotation_4x4; + copy_m4_m3(rotation_4x4.values, rotation.values); + + /* Construction transformation matrix for this surface position that includes rotation and + * translation. */ + float4x4 surface_transform = float4x4::identity(); + /* Subtract and add #pos_old, so that the rotation origin is the position on the surface. */ + sub_v3_v3(surface_transform.values[3], pos_old); + mul_m4_m4_pre(surface_transform.values, rotation_4x4.values); + add_v3_v3(surface_transform.values[3], pos_old); + add_v3_v3(surface_transform.values[3], translation); + + /* Change the basis of the transformation so to that it can be applied in the local space of + * the curves. */ + const float4x4 curve_transform = surface_to_curves * surface_transform * curves_to_surface; + + /* Actually transform all points. */ + const IndexRange points = curves.points_for_curve(curve_i); + for (const int point_i : points) { + const float3 old_point_pos = positions[point_i]; + const float3 new_point_pos = curve_transform * old_point_pos; + positions[point_i] = new_point_pos; + } + } + }); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet curves_geometry = params.extract_input("Curves"); + + Mesh *surface_mesh_orig = nullptr; + bool free_suface_mesh_orig = false; + BLI_SCOPED_DEFER([&]() { + if (free_suface_mesh_orig) { + BKE_id_free(nullptr, surface_mesh_orig); + } + }); + + auto pass_through_input = [&]() { params.set_output("Curves", std::move(curves_geometry)); }; + + const Object *self_ob_eval = params.self_object(); + if (self_ob_eval == nullptr || self_ob_eval->type != OB_CURVES) { + pass_through_input(); + return; + } + const Curves *self_curves_eval = static_cast(self_ob_eval->data); + /* Take surface information from self-object. */ + Object *surface_ob_eval = self_curves_eval->surface; + const StringRefNull uv_map_name = self_curves_eval->surface_uv_map; + const StringRefNull rest_position_name = "rest_position"; + + if (!curves_geometry.has_curves()) { + pass_through_input(); + return; + } + if (surface_ob_eval == nullptr || surface_ob_eval->type != OB_MESH) { + pass_through_input(); + params.error_message_add(NodeWarningType::Error, "Curves not attached to a surface."); + return; + } + Object *surface_ob_orig = DEG_get_original_object(surface_ob_eval); + Mesh &surface_object_data = *static_cast(surface_ob_orig->data); + + if (BMEditMesh *em = surface_object_data.edit_mesh) { + surface_mesh_orig = BKE_mesh_from_bmesh_for_eval_nomain(em->bm, NULL, &surface_object_data); + free_suface_mesh_orig = true; + } + else { + surface_mesh_orig = &surface_object_data; + } + Mesh *surface_mesh_eval = BKE_modifier_get_evaluated_mesh_from_evaluated_object(surface_ob_eval, + false); + if (surface_mesh_eval == nullptr) { + pass_through_input(); + params.error_message_add(NodeWarningType::Error, "Surface has no mesh."); + return; + } + + BKE_mesh_wrapper_ensure_mdata(surface_mesh_eval); + + MeshComponent mesh_eval; + mesh_eval.replace(surface_mesh_eval, GeometryOwnershipType::ReadOnly); + MeshComponent mesh_orig; + mesh_orig.replace(surface_mesh_orig, GeometryOwnershipType::ReadOnly); + + Curves &curves_id = *curves_geometry.get_curves_for_write(); + CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); + + if (uv_map_name.is_empty()) { + pass_through_input(); + const char *message = TIP_("Surface UV map not defined."); + params.error_message_add(NodeWarningType::Error, message); + return; + } + if (!mesh_eval.attribute_exists(uv_map_name)) { + pass_through_input(); + char *message = BLI_sprintfN(TIP_("Evaluated surface missing UV map: %s."), + uv_map_name.c_str()); + params.error_message_add(NodeWarningType::Error, message); + MEM_freeN(message); + return; + } + if (!mesh_orig.attribute_exists(uv_map_name)) { + pass_through_input(); + char *message = BLI_sprintfN(TIP_("Original surface missing UV map: %s."), + uv_map_name.c_str()); + params.error_message_add(NodeWarningType::Error, message); + MEM_freeN(message); + return; + } + if (!mesh_eval.attribute_exists(rest_position_name)) { + pass_through_input(); + params.error_message_add(NodeWarningType::Error, + TIP_("Evaluated surface missing attribute: rest_position.")); + return; + } + if (curves.surface_uv_coords().is_empty()) { + pass_through_input(); + params.error_message_add(NodeWarningType::Error, + TIP_("Curves are not attached to any UV map.")); + return; + } + const VArraySpan uv_map_orig = mesh_orig.attribute_get_for_read( + uv_map_name, ATTR_DOMAIN_CORNER, {0.0f, 0.0f}); + const VArraySpan uv_map_eval = mesh_eval.attribute_get_for_read( + uv_map_name, ATTR_DOMAIN_CORNER, {0.0f, 0.0f}); + const VArraySpan rest_positions = mesh_eval.attribute_get_for_read( + rest_position_name, ATTR_DOMAIN_POINT, {0.0f, 0.0f, 0.0f}); + const Span surface_uv_coords = curves.surface_uv_coords(); + + const Span looptris_orig{BKE_mesh_runtime_looptri_ensure(surface_mesh_orig), + BKE_mesh_runtime_looptri_len(surface_mesh_orig)}; + const Span looptris_eval{BKE_mesh_runtime_looptri_ensure(surface_mesh_eval), + BKE_mesh_runtime_looptri_len(surface_mesh_eval)}; + const ReverseUVSampler reverse_uv_sampler_orig{uv_map_orig, looptris_orig}; + const ReverseUVSampler reverse_uv_sampler_eval{uv_map_eval, looptris_eval}; + + /* Retrieve face corner normals from each mesh. It's necessary to use face corner normals + * because face normals or vertex normals may lose information (custom normals, auto smooth) in + * some cases. It isn't yet possible to retrieve lazily calculated face corner normals from a + * const mesh, so they are calculated here every time. */ + Array corner_normals_orig(surface_mesh_orig->totloop); + Array corner_normals_eval(surface_mesh_eval->totloop); + BKE_mesh_calc_normals_split_ex( + surface_mesh_orig, nullptr, reinterpret_cast(corner_normals_orig.data())); + BKE_mesh_calc_normals_split_ex( + surface_mesh_eval, nullptr, reinterpret_cast(corner_normals_eval.data())); + + std::atomic invalid_uv_count = 0; + + const bke::CurvesSurfaceTransforms transforms{*self_ob_eval, surface_ob_eval}; + + deform_curves(curves, + *surface_mesh_orig, + *surface_mesh_eval, + surface_uv_coords, + reverse_uv_sampler_orig, + reverse_uv_sampler_eval, + corner_normals_orig, + corner_normals_eval, + rest_positions, + transforms.surface_to_curves, + invalid_uv_count); + + curves.tag_positions_changed(); + + if (invalid_uv_count) { + char *message = BLI_sprintfN(TIP_("Invalid surface UVs on %d curves."), + invalid_uv_count.load()); + params.error_message_add(NodeWarningType::Warning, message); + MEM_freeN(message); + } + + params.set_output("Curves", curves_geometry); +} + +} // namespace blender::nodes::node_geo_deform_curves_on_surface_cc + +void register_node_type_geo_deform_curves_on_surface() +{ + namespace file_ns = blender::nodes::node_geo_deform_curves_on_surface_cc; + + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_DEFORM_CURVES_ON_SURFACE, "Deform Curves on Surface", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + node_type_size(&ntype, 170, 120, 700); + nodeRegisterType(&ntype); +} -- cgit v1.2.3 From 2c55d8c1cf6acd633f8acf9adc4e545cd965822c Mon Sep 17 00:00:00 2001 From: Dalai Felinto Date: Fri, 8 Jul 2022 15:07:24 +0200 Subject: Cleanup: make format --- source/blender/blenkernel/intern/customdata.cc | 2 -- source/blender/blenkernel/intern/particle.c | 2 +- source/blender/draw/engines/overlay/overlay_shader.c | 2 +- source/blender/draw/intern/draw_cache_impl_particles.c | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/source/blender/blenkernel/intern/customdata.cc b/source/blender/blenkernel/intern/customdata.cc index 26fbd7d7b54..277218033e9 100644 --- a/source/blender/blenkernel/intern/customdata.cc +++ b/source/blender/blenkernel/intern/customdata.cc @@ -3550,8 +3550,6 @@ void CustomData_bmesh_update_active_layers(CustomData *fdata, CustomData *ldata) } } - - void CustomData_bmesh_init_pool(CustomData *data, int totelem, const char htype) { int chunksize; diff --git a/source/blender/blenkernel/intern/particle.c b/source/blender/blenkernel/intern/particle.c index b5ff12f3caa..2471d3baa59 100644 --- a/source/blender/blenkernel/intern/particle.c +++ b/source/blender/blenkernel/intern/particle.c @@ -58,13 +58,13 @@ #include "BKE_main.h" #include "BKE_material.h" #include "BKE_mesh.h" +#include "BKE_mesh_legacy_convert.h" #include "BKE_modifier.h" #include "BKE_object.h" #include "BKE_particle.h" #include "BKE_pointcache.h" #include "BKE_scene.h" #include "BKE_texture.h" -#include "BKE_mesh_legacy_convert.h" #include "DEG_depsgraph.h" #include "DEG_depsgraph_build.h" diff --git a/source/blender/draw/engines/overlay/overlay_shader.c b/source/blender/draw/engines/overlay/overlay_shader.c index faa76ff74b6..2373363ab9d 100644 --- a/source/blender/draw/engines/overlay/overlay_shader.c +++ b/source/blender/draw/engines/overlay/overlay_shader.c @@ -659,7 +659,7 @@ GPUShader *OVERLAY_shader_outline_prepass_curves() if (!sh_data->outline_prepass_curves) { sh_data->outline_prepass_curves = GPU_shader_create_from_info_name( draw_ctx->sh_cfg ? "overlay_outline_prepass_curves_clipped" : - "overlay_outline_prepass_curves"); + "overlay_outline_prepass_curves"); } return sh_data->outline_prepass_curves; } diff --git a/source/blender/draw/intern/draw_cache_impl_particles.c b/source/blender/draw/intern/draw_cache_impl_particles.c index 16201e809c7..dee7a8cec37 100644 --- a/source/blender/draw/intern/draw_cache_impl_particles.c +++ b/source/blender/draw/intern/draw_cache_impl_particles.c @@ -24,9 +24,9 @@ #include "BKE_customdata.h" #include "BKE_mesh.h" +#include "BKE_mesh_legacy_convert.h" #include "BKE_particle.h" #include "BKE_pointcache.h" -#include "BKE_mesh_legacy_convert.h" #include "ED_particle.h" -- cgit v1.2.3 From becb1530b1c81a408e202ebd3c43037928dc1679 Mon Sep 17 00:00:00 2001 From: Dalai Felinto Date: Fri, 8 Jul 2022 14:41:21 +0200 Subject: Hair Curves: The new curves object is now available This commit doesn't implement any new feature but makes the new curves object type no longer experimental. Documentation: * https://docs.blender.org/manual/en/3.3/modeling/curves/primitives.html#empty-hair * https://docs.blender.org/manual/en/3.3/sculpt_paint/curves_sculpting/introduction.html Note: This also makes the Selection Paint tool available. This tool should have been moved out of the "New Curves Tool" flag when we got the selection drawing to work. Differential Revision: https://developer.blender.org/D15402 --- .../scripts/startup/bl_ui/properties_particle.py | 2 +- .../startup/bl_ui/space_toolsystem_toolbar.py | 10 ++---- release/scripts/startup/bl_ui/space_userpref.py | 1 - release/scripts/startup/bl_ui/space_view3d.py | 35 ++++++++++----------- source/blender/editors/object/object_add.cc | 36 +--------------------- source/blender/makesdna/DNA_userdef_types.h | 3 -- source/blender/makesrna/intern/rna_userdef.c | 15 --------- 7 files changed, 20 insertions(+), 82 deletions(-) diff --git a/release/scripts/startup/bl_ui/properties_particle.py b/release/scripts/startup/bl_ui/properties_particle.py index db4e609be65..ae94accf5c7 100644 --- a/release/scripts/startup/bl_ui/properties_particle.py +++ b/release/scripts/startup/bl_ui/properties_particle.py @@ -72,7 +72,7 @@ class PARTICLE_MT_context_menu(Menu): props.use_active = False props.remove_target_particles = True - if experimental.use_new_curves_type and psys.settings.type == 'HAIR': + if psys.settings.type == 'HAIR': layout.operator( "curves.convert_from_particle_system", text="Convert to Curves") diff --git a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py index 9f7ca89b8c9..02abbd43986 100644 --- a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -3172,14 +3172,8 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel): ), ], 'SCULPT_CURVES': [ - lambda context: ( - ( - _defs_curves_sculpt.selection_paint, - None, - ) - if context is None or context.preferences.experimental.use_new_curves_tools - else () - ), + _defs_curves_sculpt.selection_paint, + None, _defs_curves_sculpt.comb, _defs_curves_sculpt.add, _defs_curves_sculpt.delete, diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py index 603110b76e9..cccbb63d27c 100644 --- a/release/scripts/startup/bl_ui/space_userpref.py +++ b/release/scripts/startup/bl_ui/space_userpref.py @@ -2272,7 +2272,6 @@ class USERPREF_PT_experimental_prototypes(ExperimentalPanel, Panel): def draw(self, context): self._draw_items( context, ( - ({"property": "use_new_curves_type"}, "T68981"), ({"property": "use_new_curves_tools"}, "T68981"), ({"property": "use_new_point_cloud_type"}, "T75717"), ({"property": "use_sculpt_texture_paint"}, "T96225"), diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 72119030919..92dc4138530 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -718,19 +718,17 @@ class VIEW3D_HT_header(Header): row = layout.row(align=True) - experimental = context.preferences.experimental - if experimental.use_new_curves_tools: - # Combine the "use selection" toggle with the "set domain" operators - # to allow turning selection off directly. - domain = curves.selection_domain - if domain == 'POINT': - row.prop(curves, "use_sculpt_selection", text="", icon='CURVE_BEZCIRCLE') - else: - row.operator("curves.set_selection_domain", text="", icon='CURVE_BEZCIRCLE').domain = 'POINT' - if domain == 'CURVE': - row.prop(curves, "use_sculpt_selection", text="", icon='CURVE_PATH') - else: - row.operator("curves.set_selection_domain", text="", icon='CURVE_PATH').domain = 'CURVE' + # Combine the "use selection" toggle with the "set domain" operators + # to allow turning selection off directly. + domain = curves.selection_domain + if domain == 'POINT': + row.prop(curves, "use_sculpt_selection", text="", icon='CURVE_BEZCIRCLE') + else: + row.operator("curves.set_selection_domain", text="", icon='CURVE_BEZCIRCLE').domain = 'POINT' + if domain == 'CURVE': + row.prop(curves, "use_sculpt_selection", text="", icon='CURVE_PATH') + else: + row.operator("curves.set_selection_domain", text="", icon='CURVE_PATH').domain = 'CURVE' # Grease Pencil if obj and obj.type == 'GPENCIL' and context.gpencil_data: @@ -2111,14 +2109,13 @@ class VIEW3D_MT_curve_add(Menu): layout.operator("curve.primitive_nurbs_circle_add", text="Nurbs Circle", icon='CURVE_NCIRCLE') layout.operator("curve.primitive_nurbs_path_add", text="Path", icon='CURVE_PATH') - experimental = context.preferences.experimental - if experimental.use_new_curves_type: - layout.separator() + layout.separator() - layout.operator("object.curves_empty_hair_add", text="Empty Hair", icon='CURVES_DATA') + layout.operator("object.curves_empty_hair_add", text="Empty Hair", icon='CURVES_DATA') - if experimental.use_new_curves_tools: - layout.operator("object.curves_random_add", text="Random", icon='CURVES_DATA') + experimental = context.preferences.experimental + if experimental.use_new_curves_tools: + layout.operator("object.curves_random_add", text="Random", icon='CURVES_DATA') class VIEW3D_MT_surface_add(Menu): diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc index c350b955e14..a2c08109ec0 100644 --- a/source/blender/editors/object/object_add.cc +++ b/source/blender/editors/object/object_add.cc @@ -2030,14 +2030,6 @@ void OBJECT_OT_speaker_add(wmOperatorType *ot) /** \name Add Curves Operator * \{ */ -static bool object_curves_add_poll(bContext *C) -{ - if (!U.experimental.use_new_curves_type) { - return false; - } - return ED_operator_objectmode(C); -} - static int object_curves_random_add_exec(bContext *C, wmOperator *op) { using namespace blender; @@ -2066,7 +2058,7 @@ void OBJECT_OT_curves_random_add(wmOperatorType *ot) /* api callbacks */ ot->exec = object_curves_random_add_exec; - ot->poll = object_curves_add_poll; + ot->poll = ED_operator_objectmode; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -2117,9 +2109,6 @@ static int object_curves_empty_hair_add_exec(bContext *C, wmOperator *op) static bool object_curves_empty_hair_add_poll(bContext *C) { - if (!U.experimental.use_new_curves_type) { - return false; - } if (!ED_operator_objectmode(C)) { return false; } @@ -2781,28 +2770,6 @@ static const EnumPropertyItem convert_target_items[] = { {0, nullptr, 0, nullptr, nullptr}, }; -static const EnumPropertyItem *convert_target_items_fn(bContext *UNUSED(C), - PointerRNA *UNUSED(ptr), - PropertyRNA *UNUSED(prop), - bool *r_free) -{ - EnumPropertyItem *items = nullptr; - int items_num = 0; - for (const EnumPropertyItem *item = convert_target_items; item->identifier != nullptr; item++) { - if (item->value == OB_CURVES) { - if (U.experimental.use_new_curves_type) { - RNA_enum_item_add(&items, &items_num, item); - } - } - else { - RNA_enum_item_add(&items, &items_num, item); - } - } - RNA_enum_item_end(&items, &items_num); - *r_free = true; - return items; -} - static void object_data_convert_ensure_curve_cache(Depsgraph *depsgraph, Scene *scene, Object *ob) { if (ob->runtime.curve_cache == nullptr) { @@ -3562,7 +3529,6 @@ void OBJECT_OT_convert(wmOperatorType *ot) /* properties */ ot->prop = RNA_def_enum( ot->srna, "target", convert_target_items, OB_MESH, "Target", "Type of object to convert to"); - RNA_def_enum_funcs(ot->prop, convert_target_items_fn); RNA_def_boolean(ot->srna, "keep_original", false, diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 753c41f7f1b..2ceef4f623e 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -644,8 +644,6 @@ typedef struct UserDef_Experimental { char SANITIZE_AFTER_HERE; /* The following options are automatically sanitized (set to 0) * when the release cycle is not alpha. */ - char use_new_curves_type; - /** Only available when #use_new_curves_type is enabled. */ char use_new_curves_tools; char use_new_point_cloud_type; char use_full_frame_compositor; @@ -655,7 +653,6 @@ typedef struct UserDef_Experimental { char enable_eevee_next; char use_sculpt_texture_paint; char use_draw_manager_acquire_lock; - char _pad[7]; /** `makesdna` does not allow empty structs. */ } UserDef_Experimental; diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index fd3479f6fe4..25eacdaab30 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -1101,16 +1101,6 @@ int rna_show_statusbar_vram_editable(struct PointerRNA *UNUSED(ptr), const char return GPU_mem_stats_supported() ? PROP_EDITABLE : 0; } -static int rna_userdef_experimental_use_new_curve_tools_editable(struct PointerRNA *UNUSED(ptr), - const char **r_info) -{ - if (U.experimental.use_new_curves_type) { - return PROP_EDITABLE; - } - *r_info = "Only available when new curves type is enabled"; - return 0; -} - #else # define USERDEF_TAG_DIRTY_PROPERTY_UPDATE_ENABLE \ @@ -6414,13 +6404,8 @@ static void rna_def_userdef_experimental(BlenderRNA *brna) "reduces execution time and memory usage)"); RNA_def_property_update(prop, 0, "rna_userdef_update"); - prop = RNA_def_property(srna, "use_new_curves_type", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "use_new_curves_type", 1); - RNA_def_property_ui_text(prop, "New Curves Type", "Enable the new curves data type in the UI"); - prop = RNA_def_property(srna, "use_new_curves_tools", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "use_new_curves_tools", 1); - RNA_def_property_editable_func(prop, "rna_userdef_experimental_use_new_curve_tools_editable"); RNA_def_property_ui_text( prop, "New Curves Tools", "Enable additional features for the new curves data block"); -- cgit v1.2.3 From 5723bf926dcb387224dd0de2a4c562bc316e8edc Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 8 Jul 2022 08:32:32 -0500 Subject: Fix T99191: Boolean modifier creates invalid material indices Similar to 1a6d0ec71cf3b0c2c which changed the mesh boolean node (and also caused this bug), this commit changes the material mapping for the exact mode of the boolean modifier. Now the result should contain any material on the faces of the input objects (including materials linked to objects and meshes). The improvement is possible because materials can be changed during evaluation (as of 1a81d268a19f2f1402). Differential Revision: https://developer.blender.org/D15365 --- source/blender/modifiers/intern/MOD_boolean.cc | 66 ++++++++++++++++++-------- 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/source/blender/modifiers/intern/MOD_boolean.cc b/source/blender/modifiers/intern/MOD_boolean.cc index deafe795061..09676d0e9ee 100644 --- a/source/blender/modifiers/intern/MOD_boolean.cc +++ b/source/blender/modifiers/intern/MOD_boolean.cc @@ -14,6 +14,7 @@ #include "BLI_math_geom.h" #include "BLI_math_matrix.h" #include "BLI_vector.hh" +#include "BLI_vector_set.hh" #include "BLT_translation.h" @@ -62,7 +63,11 @@ using blender::Array; using blender::float4x4; +using blender::IndexRange; +using blender::MutableSpan; +using blender::Span; using blender::Vector; +using blender::VectorSet; static void initData(ModifierData *md) { @@ -375,19 +380,23 @@ static void BMD_mesh_intersection(BMesh *bm, #ifdef WITH_GMP -/* Get a mapping from material slot numbers in the src_ob to slot numbers in the dst_ob. - * If a material doesn't exist in the dst_ob, the mapping just goes to the same slot - * or to zero if there aren't enough slots in the destination. - * Caller owns the returned array. */ -static Array get_material_remap(Object *dest_ob, Object *src_ob) +/* Get a mapping from material slot numbers in the source geometry to slot numbers in the result + * geometry. The material is added to the result geometry if it doesn't already use it. */ +static Array get_material_remap(Object &object, + const Mesh &mesh, + VectorSet &materials) { - int n = src_ob->totcol; - if (n <= 0) { - n = 1; + const int material_num = mesh.totcol; + if (material_num == 0) { + /* Necessary for faces using the default material when there are no material slots. */ + return Array({materials.index_of_or_add(nullptr)}); } - Array remap(n); - BKE_object_material_remap_calc(dest_ob, src_ob, remap.data()); - return remap; + Array map(material_num); + for (const int i : IndexRange(material_num)) { + Material *material = BKE_object_material_get_eval(&object, i + 1); + map[i] = materials.index_of_or_add(material); + } + return map; } static Mesh *exact_boolean_mesh(BooleanModifierData *bmd, @@ -396,6 +405,8 @@ static Mesh *exact_boolean_mesh(BooleanModifierData *bmd, { Vector meshes; Vector obmats; + + VectorSet materials; Vector> material_remaps; # ifdef DEBUG_TIME @@ -409,6 +420,14 @@ static Mesh *exact_boolean_mesh(BooleanModifierData *bmd, meshes.append(mesh); obmats.append((float4x4 *)&ctx->object->obmat); material_remaps.append({}); + if (mesh->totcol == 0) { + /* Necessary for faces using the default material when there are no material slots. */ + materials.add(nullptr); + } + else { + materials.add_multiple({mesh->mat, mesh->totcol}); + } + if (bmd->flag & eBooleanModifierFlag_Object) { Mesh *mesh_operand = BKE_modifier_get_evaluated_mesh_from_evaluated_object(bmd->object, false); if (!mesh_operand) { @@ -417,7 +436,7 @@ static Mesh *exact_boolean_mesh(BooleanModifierData *bmd, BKE_mesh_wrapper_ensure_mdata(mesh_operand); meshes.append(mesh_operand); obmats.append((float4x4 *)&bmd->object->obmat); - material_remaps.append(get_material_remap(ctx->object, bmd->object)); + material_remaps.append(get_material_remap(*bmd->object, *mesh_operand, materials)); } else if (bmd->flag & eBooleanModifierFlag_Collection) { Collection *collection = bmd->collection; @@ -432,7 +451,7 @@ static Mesh *exact_boolean_mesh(BooleanModifierData *bmd, BKE_mesh_wrapper_ensure_mdata(collection_mesh); meshes.append(collection_mesh); obmats.append((float4x4 *)&ob->obmat); - material_remaps.append(get_material_remap(ctx->object, ob)); + material_remaps.append(get_material_remap(*ob, *collection_mesh, materials)); } } FOREACH_COLLECTION_OBJECT_RECURSIVE_END; @@ -441,14 +460,19 @@ static Mesh *exact_boolean_mesh(BooleanModifierData *bmd, const bool use_self = (bmd->flag & eBooleanModifierFlag_Self) != 0; const bool hole_tolerant = (bmd->flag & eBooleanModifierFlag_HoleTolerant) != 0; - return blender::meshintersect::direct_mesh_boolean(meshes, - obmats, - *(float4x4 *)&ctx->object->obmat, - material_remaps, - use_self, - hole_tolerant, - bmd->operation, - nullptr); + Mesh *result = blender::meshintersect::direct_mesh_boolean(meshes, + obmats, + *(float4x4 *)&ctx->object->obmat, + material_remaps, + use_self, + hole_tolerant, + bmd->operation, + nullptr); + MEM_SAFE_FREE(result->mat); + result->mat = (Material **)MEM_malloc_arrayN(materials.size(), sizeof(Material *), __func__); + result->totcol = materials.size(); + MutableSpan(result->mat, result->totcol).copy_from(materials); + return result; } #endif -- cgit v1.2.3 From 0f50ae131f54d51f778424d4c9655128cafbbefc Mon Sep 17 00:00:00 2001 From: Xavier Hallade Date: Fri, 8 Jul 2022 15:33:44 +0200 Subject: Cycles: enable oneAPI in Linux release builds with a very high min-driver version requirement, placeholder until JIT CentOS runtime compilation issue gets fixed in a defined version. min-driver version check can be worked around by setting CYCLES_ONEAPI_ALL_DEVICES environment variable. --- build_files/cmake/config/blender_release.cmake | 2 +- intern/cycles/blender/addon/properties.py | 2 +- intern/cycles/kernel/device/oneapi/kernel.cpp | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/build_files/cmake/config/blender_release.cmake b/build_files/cmake/config/blender_release.cmake index 42759fec7cc..b4609426069 100644 --- a/build_files/cmake/config/blender_release.cmake +++ b/build_files/cmake/config/blender_release.cmake @@ -71,7 +71,6 @@ if(NOT WIN32) endif() if(WIN32) set(WITH_WASAPI ON CACHE BOOL "" FORCE) - set(WITH_CYCLES_DEVICE_ONEAPI ON CACHE BOOL "" FORCE) endif() if(UNIX AND NOT APPLE) set(WITH_DOC_MANPAGE ON CACHE BOOL "" FORCE) @@ -92,6 +91,7 @@ if(NOT APPLE) set(WITH_CYCLES_CUDA_BINARIES ON CACHE BOOL "" FORCE) set(WITH_CYCLES_CUBIN_COMPILER OFF CACHE BOOL "" FORCE) set(WITH_CYCLES_HIP_BINARIES ON CACHE BOOL "" FORCE) + set(WITH_CYCLES_DEVICE_ONEAPI ON CACHE BOOL "" FORCE) # Disable AoT kernels compilations until buildbot can deliver them in a reasonabel time. set(WITH_CYCLES_ONEAPI_BINARIES OFF CACHE BOOL "" FORCE) diff --git a/intern/cycles/blender/addon/properties.py b/intern/cycles/blender/addon/properties.py index c41e1f02b75..e88b65b5119 100644 --- a/intern/cycles/blender/addon/properties.py +++ b/intern/cycles/blender/addon/properties.py @@ -1560,7 +1560,7 @@ class CyclesPreferences(bpy.types.AddonPreferences): if sys.platform.startswith("win"): col.label(text="and Windows driver version 101.1660 or newer", icon='BLANK1') elif sys.platform.startswith("linux"): - col.label(text="and Linux driver version xx.xx.20066 or newer", icon='BLANK1') + col.label(text="and Linux driver version xx.xx.28000 or newer", icon='BLANK1') elif device_type == 'METAL': col.label(text="Requires Apple Silicon with macOS 12.2 or newer", icon='BLANK1') col.label(text="or AMD with macOS 12.3 or newer", icon='BLANK1') diff --git a/intern/cycles/kernel/device/oneapi/kernel.cpp b/intern/cycles/kernel/device/oneapi/kernel.cpp index 9b9c046de4c..82910d72105 100644 --- a/intern/cycles/kernel/device/oneapi/kernel.cpp +++ b/intern/cycles/kernel/device/oneapi/kernel.cpp @@ -670,7 +670,10 @@ bool oneapi_enqueue_kernel(KernelContext *kernel_context, } static const int lowest_supported_driver_version_win = 1011660; -static const int lowest_supported_driver_version_neo = 20066; +/* TODO: once Linux JIT compilation crash from CentOS generated spv is fixed, adjust version below. + * Until then, set CYCLES_ONEAPI_ALL_DEVICES environment variable to avoid getting it filtered out. + */ +static const int lowest_supported_driver_version_neo = 28000; static int parse_driver_build_version(const sycl::device &device) { -- cgit v1.2.3 From f391e8f316bd29b700cef874a59cf3b64203d70c Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Fri, 8 Jul 2022 15:15:02 +0200 Subject: Linux: Move Mesa software OpenGL libraries to sub-directory Allows to put libraries which are always needed by Blender into the lib/ folder and not worry about OpenGL libraries picked up from there. Currently no functional changes as we do not yet have dynamic libraries which we load at startup. It allows to use direct linking of oneAPI Cycles device (see D15397), also it is something which would need to happen to support USD/Hydra/TBB compiler as dynamic libraries in the future. Differential Revision: https://developer.blender.org/D15403 --- release/bin/blender-softwaregl | 8 ++++---- source/creator/CMakeLists.txt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/release/bin/blender-softwaregl b/release/bin/blender-softwaregl index 8628dca2202..acd4dc3eec5 100755 --- a/release/bin/blender-softwaregl +++ b/release/bin/blender-softwaregl @@ -2,16 +2,16 @@ BF_DIST_BIN=$(dirname "$0") BF_PROGRAM="blender" # BF_PROGRAM=$(basename "$0")-bin -LD_LIBRARY_PATH=${BF_DIST_BIN}/lib:${LD_LIBRARY_PATH} +LD_LIBRARY_PATH=${BF_DIST_BIN}/lib/mesa:${LD_LIBRARY_PATH} if [ -n "$LD_LIBRARYN32_PATH" ]; then - LD_LIBRARYN32_PATH=${BF_DIST_BIN}/lib:${LD_LIBRARYN32_PATH} + LD_LIBRARYN32_PATH=${BF_DIST_BIN}/lib/mesa:${LD_LIBRARYN32_PATH} fi if [ -n "$LD_LIBRARYN64_PATH" ]; then - LD_LIBRARYN64_PATH=${BF_DIST_BIN}/lib:${LD_LIBRARYN64_PATH} + LD_LIBRARYN64_PATH=${BF_DIST_BIN}/lib/mesa:${LD_LIBRARYN64_PATH} fi if [ -n "$LD_LIBRARY_PATH_64" ]; then - LD_LIBRARY_PATH_64=${BF_DIST_BIN}/lib:${LD_LIBRARY_PATH_64} + LD_LIBRARY_PATH_64=${BF_DIST_BIN}/lib/mesa:${LD_LIBRARY_PATH_64} fi # Workaround for half-transparent windows when compiz is enabled diff --git a/source/creator/CMakeLists.txt b/source/creator/CMakeLists.txt index 7457358698d..278cd4362c3 100644 --- a/source/creator/CMakeLists.txt +++ b/source/creator/CMakeLists.txt @@ -511,7 +511,7 @@ if(UNIX AND NOT APPLE) endif() if(EXISTS ${LIBDIR}/mesa) - install(DIRECTORY ${LIBDIR}/mesa/lib DESTINATION ".") + install(DIRECTORY ${LIBDIR}/mesa/lib/ DESTINATION "lib/mesa/") install( PROGRAMS -- cgit v1.2.3 From b876ce2a4a4638142439a7cf265a0780491ae4cc Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Fri, 8 Jul 2022 16:16:56 +0200 Subject: Geometry Nodes: new geometry attribute API Currently, there are two attribute API. The first, defined in `BKE_attribute.h` is accessible from RNA and C code. The second is implemented with `GeometryComponent` and is only accessible in C++ code. The second is widely used, but only being accessible through the `GeometrySet` API makes it awkward to use, and even impossible for types that don't correspond directly to a geometry component like `CurvesGeometry`. This patch adds a new attribute API, designed to replace the `GeometryComponent` attribute API now, and to eventually replace or be the basis of the other one. The basic idea is that there is an `AttributeAccessor` class that allows code to interact with a set of attributes owned by some geometry. The accessor itself has no ownership. `AttributeAccessor` is a simple type that can be passed around by value. That makes it easy to return it from functions and to store it in containers. For const-correctness, there is also a `MutableAttributeAccessor` that allows changing individual and can add or remove attributes. Currently, `AttributeAccessor` is composed of two pointers. The first is a pointer to the owner of the attribute data. The second is a pointer to a struct with function pointers, that is similar to a virtual function table. The functions know how to access attributes on the owner. The actual attribute access for geometries is still implemented with the `AttributeProvider` pattern, which makes it easy to support different sources of attributes on a geometry and simplifies dealing with built-in attributes. There are different ways to get an attribute accessor for a geometry: * `GeometryComponent.attributes()` * `CurvesGeometry.attributes()` * `bke::mesh_attributes(const Mesh &)` * `bke::pointcloud_attributes(const PointCloud &)` All of these also have a `_for_write` variant that returns a `MutabelAttributeAccessor`. Differential Revision: https://developer.blender.org/D15280 --- source/blender/blenkernel/BKE_attribute.hh | 814 ++++++++++++++++++++ source/blender/blenkernel/BKE_attribute_access.hh | 541 ------------- source/blender/blenkernel/BKE_curves.hh | 5 +- source/blender/blenkernel/BKE_geometry_set.hh | 260 +------ source/blender/blenkernel/BKE_mesh_sample.hh | 10 +- source/blender/blenkernel/BKE_spline.hh | 4 +- source/blender/blenkernel/CMakeLists.txt | 2 +- source/blender/blenkernel/intern/attribute.cc | 2 +- .../blender/blenkernel/intern/attribute_access.cc | 856 +++++---------------- .../blenkernel/intern/attribute_access_intern.hh | 266 +++++-- source/blender/blenkernel/intern/curve_eval.cc | 125 +-- .../blenkernel/intern/curve_legacy_convert.cc | 11 +- .../blenkernel/intern/curve_to_mesh_convert.cc | 72 +- .../blenkernel/intern/geometry_component_curve.cc | 230 +++--- .../blenkernel/intern/geometry_component_curves.cc | 191 ++--- .../intern/geometry_component_instances.cc | 101 ++- .../blenkernel/intern/geometry_component_mesh.cc | 212 ++--- .../intern/geometry_component_pointcloud.cc | 102 ++- source/blender/blenkernel/intern/geometry_set.cc | 41 +- source/blender/blenkernel/intern/mesh_convert.cc | 4 +- source/blender/blenkernel/intern/mesh_sample.cc | 7 +- source/blender/blenkernel/intern/spline_base.cc | 2 +- .../blender/draw/intern/draw_cache_impl_curves.cc | 15 +- source/blender/draw/intern/draw_curves.cc | 4 +- source/blender/editors/curves/intern/curves_ops.cc | 22 +- .../editors/geometry/geometry_attributes.cc | 28 +- source/blender/editors/object/object_add.cc | 4 +- source/blender/editors/object/object_modifier.cc | 9 +- .../editors/sculpt_paint/curves_sculpt_add.cc | 9 +- .../editors/sculpt_paint/curves_sculpt_density.cc | 10 +- .../editors/sculpt_paint/curves_sculpt_slide.cc | 9 +- .../editors/sculpt_paint/paint_vertex_color_ops.cc | 48 +- .../spreadsheet_data_source_geometry.cc | 33 +- .../space_spreadsheet/spreadsheet_dataset_draw.cc | 2 +- .../geometry/intern/mesh_primitive_cuboid.cc | 12 +- .../geometry/intern/mesh_to_curve_convert.cc | 22 +- .../geometry/intern/point_merge_by_distance.cc | 27 +- .../blender/geometry/intern/realize_instances.cc | 268 +++---- source/blender/geometry/intern/resample_curves.cc | 25 +- source/blender/geometry/intern/set_curve_type.cc | 36 +- source/blender/geometry/intern/subdivide_curves.cc | 58 +- .../exporter/obj_export_file_writer.cc | 5 +- source/blender/modifiers/intern/MOD_nodes.cc | 37 +- source/blender/nodes/NOD_geometry_exec.hh | 15 +- .../geometry/nodes/node_geo_accumulate_field.cc | 26 +- .../geometry/nodes/node_geo_attribute_capture.cc | 17 +- .../nodes/node_geo_attribute_domain_size.cc | 20 +- .../geometry/nodes/node_geo_attribute_statistic.cc | 16 +- .../nodes/geometry/nodes/node_geo_boolean.cc | 14 +- .../nodes/geometry/nodes/node_geo_convex_hull.cc | 18 +- .../nodes/geometry/nodes/node_geo_curve_fillet.cc | 4 +- .../nodes/node_geo_curve_primitive_star.cc | 12 +- .../nodes/geometry/nodes/node_geo_curve_reverse.cc | 4 +- .../nodes/node_geo_curve_set_handle_type.cc | 4 +- .../geometry/nodes/node_geo_curve_to_points.cc | 13 +- .../nodes/geometry/nodes/node_geo_curve_trim.cc | 4 +- .../nodes/node_geo_deform_curves_on_surface.cc | 24 +- .../geometry/nodes/node_geo_delete_geometry.cc | 42 +- .../nodes/node_geo_distribute_points_on_faces.cc | 57 +- .../nodes/geometry/nodes/node_geo_dual_mesh.cc | 11 +- .../geometry/nodes/node_geo_duplicate_elements.cc | 111 +-- .../nodes/geometry/nodes/node_geo_edge_split.cc | 4 +- .../nodes/geometry/nodes/node_geo_extrude_mesh.cc | 64 +- .../geometry/nodes/node_geo_field_at_index.cc | 2 +- .../geometry/nodes/node_geo_field_on_domain.cc | 4 +- .../nodes/geometry/nodes/node_geo_flip_faces.cc | 17 +- .../geometry/nodes/node_geo_input_curve_handles.cc | 12 +- .../nodes/node_geo_input_mesh_edge_angle.cc | 4 +- .../nodes/node_geo_input_mesh_edge_neighbors.cc | 2 +- .../nodes/node_geo_input_mesh_edge_vertices.cc | 4 +- .../nodes/node_geo_input_mesh_face_area.cc | 2 +- .../nodes/node_geo_input_mesh_face_is_planar.cc | 2 +- .../nodes/node_geo_input_mesh_face_neighbors.cc | 4 +- .../geometry/nodes/node_geo_input_mesh_island.cc | 5 +- .../geometry/nodes/node_geo_input_spline_length.cc | 2 +- .../nodes/geometry/nodes/node_geo_input_tangent.cc | 4 +- .../geometry/nodes/node_geo_instance_on_points.cc | 8 +- .../geometry/nodes/node_geo_instances_to_points.cc | 12 +- .../nodes/geometry/nodes/node_geo_join_geometry.cc | 16 +- .../geometry/nodes/node_geo_material_selection.cc | 2 +- .../geometry/nodes/node_geo_merge_by_distance.cc | 6 +- .../geometry/nodes/node_geo_mesh_primitive_cone.cc | 42 +- .../geometry/nodes/node_geo_mesh_primitive_grid.cc | 15 +- .../nodes/node_geo_mesh_primitive_uv_sphere.cc | 12 +- .../nodes/geometry/nodes/node_geo_mesh_to_curve.cc | 2 +- .../geometry/nodes/node_geo_mesh_to_points.cc | 26 +- .../nodes/geometry/nodes/node_geo_points.cc | 13 +- .../geometry/nodes/node_geo_points_to_vertices.cc | 13 +- .../geometry/nodes/node_geo_points_to_volume.cc | 7 +- .../nodes/geometry/nodes/node_geo_raycast.cc | 4 +- .../geometry/nodes/node_geo_remove_attribute.cc | 4 +- .../geometry/nodes/node_geo_set_curve_handles.cc | 10 +- .../geometry/nodes/node_geo_set_curve_radius.cc | 17 +- .../geometry/nodes/node_geo_set_curve_tilt.cc | 18 +- .../nodes/geometry/nodes/node_geo_set_id.cc | 26 +- .../geometry/nodes/node_geo_set_material_index.cc | 17 +- .../geometry/nodes/node_geo_set_point_radius.cc | 17 +- .../nodes/geometry/nodes/node_geo_set_position.cc | 52 +- .../geometry/nodes/node_geo_set_shade_smooth.cc | 18 +- .../geometry/nodes/node_geo_set_spline_cyclic.cc | 18 +- .../nodes/node_geo_set_spline_resolution.cc | 18 +- .../nodes/node_geo_store_named_attribute.cc | 25 +- .../geometry/nodes/node_geo_string_to_curves.cc | 18 +- .../geometry/nodes/node_geo_subdivision_surface.cc | 14 +- .../geometry/nodes/node_geo_transfer_attribute.cc | 10 +- .../nodes/geometry/nodes/node_geo_triangulate.cc | 4 +- .../geometry/nodes/node_geo_uv_pack_islands.cc | 6 +- .../nodes/geometry/nodes/node_geo_uv_unwrap.cc | 6 +- .../nodes/intern/geometry_nodes_eval_log.cc | 12 +- 109 files changed, 2764 insertions(+), 2809 deletions(-) create mode 100644 source/blender/blenkernel/BKE_attribute.hh delete mode 100644 source/blender/blenkernel/BKE_attribute_access.hh diff --git a/source/blender/blenkernel/BKE_attribute.hh b/source/blender/blenkernel/BKE_attribute.hh new file mode 100644 index 00000000000..b92d0d4326b --- /dev/null +++ b/source/blender/blenkernel/BKE_attribute.hh @@ -0,0 +1,814 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_color.hh" +#include "BLI_function_ref.hh" +#include "BLI_generic_span.hh" +#include "BLI_generic_virtual_array.hh" +#include "BLI_math_vec_types.hh" +#include "BLI_set.hh" + +#include "BKE_anonymous_attribute.hh" +#include "BKE_attribute.h" + +struct Mesh; +struct PointCloud; + +namespace blender::bke { + +/** + * Identifies an attribute that is either named or anonymous. + * It does not own the identifier, so it is just a reference. + */ +class AttributeIDRef { + private: + StringRef name_; + const AnonymousAttributeID *anonymous_id_ = nullptr; + + public: + AttributeIDRef(); + AttributeIDRef(StringRef name); + AttributeIDRef(StringRefNull name); + AttributeIDRef(const char *name); + AttributeIDRef(const std::string &name); + AttributeIDRef(const AnonymousAttributeID *anonymous_id); + + operator bool() const; + uint64_t hash() const; + bool is_named() const; + bool is_anonymous() const; + StringRef name() const; + const AnonymousAttributeID &anonymous_id() const; + bool should_be_kept() const; + + friend bool operator==(const AttributeIDRef &a, const AttributeIDRef &b); + friend std::ostream &operator<<(std::ostream &stream, const AttributeIDRef &attribute_id); +}; + +/** + * Contains information about an attribute in a geometry component. + * More information can be added in the future. E.g. whether the attribute is builtin and how it is + * stored (uv map, vertex group, ...). + */ +struct AttributeMetaData { + eAttrDomain domain; + eCustomDataType data_type; + + constexpr friend bool operator==(AttributeMetaData a, AttributeMetaData b) + { + return (a.domain == b.domain) && (a.data_type == b.data_type); + } +}; + +struct AttributeKind { + eAttrDomain domain; + eCustomDataType data_type; +}; + +/** + * Base class for the attribute initializer types described below. + */ +struct AttributeInit { + enum class Type { + Default, + VArray, + MoveArray, + }; + Type type; + AttributeInit(const Type type) : type(type) + { + } +}; + +/** + * Create an attribute using the default value for the data type. + * The default values may depend on the attribute provider implementation. + */ +struct AttributeInitDefault : public AttributeInit { + AttributeInitDefault() : AttributeInit(Type::Default) + { + } +}; + +/** + * Create an attribute by copying data from an existing virtual array. The virtual array + * must have the same type as the newly created attribute. + * + * Note that this can be used to fill the new attribute with the default + */ +struct AttributeInitVArray : public AttributeInit { + blender::GVArray varray; + + AttributeInitVArray(blender::GVArray varray) + : AttributeInit(Type::VArray), varray(std::move(varray)) + { + } +}; + +/** + * Create an attribute with a by passing ownership of a pre-allocated contiguous array of data. + * Sometimes data is created before a geometry component is available. In that case, it's + * preferable to move data directly to the created attribute to avoid a new allocation and a copy. + * + * Note that this will only have a benefit for attributes that are stored directly as contiguous + * arrays, so not for some built-in attributes. + * + * The array must be allocated with MEM_*, since `attribute_try_create` will free the array if it + * can't be used directly, and that is generally how Blender expects custom data to be allocated. + */ +struct AttributeInitMove : public AttributeInit { + void *data = nullptr; + + AttributeInitMove(void *data) : AttributeInit(Type::MoveArray), data(data) + { + } +}; + +/* Returns false when the iteration should be stopped. */ +using AttributeForeachCallback = + FunctionRef; + +/** + * Result when looking up an attribute from some geometry with the intention of only reading from + * it. + */ +template struct AttributeReader { + /** + * Virtual array that provides access to the attribute data. This may be empty. + */ + VArray varray; + /** + * Domain where the attribute is stored. This also determines the size of the virtual array. + */ + eAttrDomain domain; + + operator bool() const + { + return this->varray; + } +}; + +/** + * Result when looking up an attribute from some geometry with read an write access. After writing + * to the attribute, the #finish method has to be called. This may invalidate caches based on this + * attribute. + */ +template struct AttributeWriter { + /** + * Virtual array giving read and write access to the attribute. This may be empty. + * Consider using #SpanAttributeWriter when you want to access the virtual array as a span. + */ + VMutableArray varray; + /** + * Domain where the attribute is stored on the geometry. Also determines the size of the virtual + * array. + */ + eAttrDomain domain; + /** + * A function that has to be called after the attribute has been edited. This may be empty. + */ + std::function tag_modified_fn; + + operator bool() const + { + return this->varray; + } + + /** + * Has to be called after the attribute has been modified. + */ + void finish() + { + if (this->tag_modified_fn) { + this->tag_modified_fn(); + } + } +}; + +/** + * A version of #AttributeWriter for the common case when the user of the attribute wants to write + * to a span instead of a virtual array. Since most attributes are spans internally, this can + * result in better performance and also simplifies code. + */ +template struct SpanAttributeWriter { + /** + * A span based on the virtual array that contains the attribute data. This may be empty. + */ + MutableVArraySpan span; + /** + * Domain of the attribute. Also determines the size of the span. + */ + eAttrDomain domain; + /** + * Has to be called after writing to the span. + */ + std::function tag_modified_fn; + + SpanAttributeWriter() = default; + + SpanAttributeWriter(AttributeWriter &&other, const bool copy_values_to_span) + : span(std::move(other.varray), copy_values_to_span), + domain(other.domain), + tag_modified_fn(std::move(other.tag_modified_fn)) + { + } + + operator bool() const + { + return span.varray(); + } + + /** + * Has to be called when done writing to the attribute. This makes sure that the data is copied + * to the underlying attribute if it was not stored as an array. Furthermore, this may invalidate + * other data depending on the modified attribute. + */ + void finish() + { + this->span.save(); + if (this->tag_modified_fn) { + this->tag_modified_fn(); + } + } +}; + +/** + * A generic version of #AttributeReader. + */ +struct GAttributeReader { + GVArray varray; + eAttrDomain domain; + + operator bool() const + { + return this->varray; + } + + template AttributeReader typed() const + { + return {varray.typed(), domain}; + } +}; + +/** + * A generic version of #AttributeWriter. + */ +struct GAttributeWriter { + GVMutableArray varray; + eAttrDomain domain; + std::function tag_modified_fn; + + operator bool() const + { + return this->varray; + } + + void finish() + { + if (this->tag_modified_fn) { + this->tag_modified_fn(); + } + } + + template AttributeWriter typed() const + { + return {varray.typed(), domain, tag_modified_fn}; + } +}; + +/** + * A generic version of #SpanAttributeWriter. + */ +struct GSpanAttributeWriter { + GMutableVArraySpan span; + eAttrDomain domain; + std::function tag_modified_fn; + + GSpanAttributeWriter() = default; + + GSpanAttributeWriter(GAttributeWriter &&other, const bool copy_values_to_span) + : span(std::move(other.varray), copy_values_to_span), + domain(other.domain), + tag_modified_fn(std::move(other.tag_modified_fn)) + { + } + + operator bool() const + { + return span.varray(); + } + + void finish() + { + this->span.save(); + if (this->tag_modified_fn) { + this->tag_modified_fn(); + } + } +}; + +/** + * Core functions which make up the attribute API. They should not be called directly, but through + * #AttributesAccessor or #MutableAttributesAccessor. + * + * This is similar to a virtual function table. A struct of function pointers is used instead, + * because this way the attribute accessors can be trivial and can be passed around by value. This + * makes it easy to return the attribute accessor for a geometry from a function. + */ +struct AttributeAccessorFunctions { + bool (*contains)(const void *owner, const AttributeIDRef &attribute_id); + std::optional (*lookup_meta_data)(const void *owner, + const AttributeIDRef &attribute_id); + bool (*domain_supported)(const void *owner, eAttrDomain domain); + int (*domain_size)(const void *owner, eAttrDomain domain); + bool (*is_builtin)(const void *owner, const AttributeIDRef &attribute_id); + GAttributeReader (*lookup)(const void *owner, const AttributeIDRef &attribute_id); + GVArray (*adapt_domain)(const void *owner, + const GVArray &varray, + eAttrDomain from_domain, + eAttrDomain to_domain); + bool (*for_all)(const void *owner, + FunctionRef fn); + + GAttributeWriter (*lookup_for_write)(void *owner, const AttributeIDRef &attribute_id); + bool (*remove)(void *owner, const AttributeIDRef &attribute_id); + bool (*add)(void *owner, + const AttributeIDRef &attribute_id, + eAttrDomain domain, + eCustomDataType data_type, + const AttributeInit &initializer); +}; + +/** + * Provides read-only access to the set of attributes on some geometry. + * + * Note, this does not own the attributes. When the owner is freed, it is invalid to access its + * attributes. + */ +class AttributeAccessor { + protected: + /** + * The data that actually owns the attributes, for example, a pointer to a #Mesh or #PointCloud + * Most commonly this is a pointer to a #Mesh or #PointCloud. + * Under some circumstances this can be null. In that case most methods can't be used. Just e.g. + * the #domain_size method works and returns 0 for every domain. + * + * \note This class cannot modify the owner's attributes, but the pointer is still non-const, so + * this class can be a base class for the mutable version. + */ + void *owner_; + /** + * Functions that know how to access the attributes stored in the owner above. + */ + const AttributeAccessorFunctions *fn_; + + public: + AttributeAccessor(const void *owner, const AttributeAccessorFunctions &fn) + : owner_(const_cast(owner)), fn_(&fn) + { + } + + /** + * \return True, when the attribute is available. + */ + bool contains(const AttributeIDRef &attribute_id) const + { + return fn_->contains(owner_, attribute_id); + } + + /** + * \return Information about the attribute if it exists. + */ + std::optional lookup_meta_data(const AttributeIDRef &attribute_id) const + { + return fn_->lookup_meta_data(owner_, attribute_id); + } + + /** + * \return True, when attributes can exist on that domain. + */ + bool domain_supported(const eAttrDomain domain) const + { + return fn_->domain_supported(owner_, domain); + } + + /** + * \return Number of elements in the given domain. + */ + int domain_size(const eAttrDomain domain) const + { + return fn_->domain_size(owner_, domain); + } + + /** + * \return True, when the attribute has a special meaning for Blender and can't be used for + * arbitrary things. + */ + bool is_builtin(const AttributeIDRef &attribute_id) const + { + return fn_->is_builtin(owner_, attribute_id); + } + + /** + * Get read-only access to the attribute. If the attribute does not exist, the return value is + * empty. + */ + GAttributeReader lookup(const AttributeIDRef &attribute_id) const + { + return fn_->lookup(owner_, attribute_id); + } + + /** + * Get read-only access to the attribute. If necessary, the attribute is interpolated to the + * given domain, and converted to the given type, in that order. The result may be empty. + */ + GVArray lookup(const AttributeIDRef &attribute_id, + const std::optional domain, + const std::optional data_type) const; + + /** + * Get read-only access to the attribute whereby the attribute is interpolated to the given + * domain. The result may be empty. + */ + GVArray lookup(const AttributeIDRef &attribute_id, const eAttrDomain domain) const + { + return this->lookup(attribute_id, domain, std::nullopt); + } + + /** + * Get read-only access to the attribute whereby the attribute is converted to the given type. + * The result may be empty. + */ + GVArray lookup(const AttributeIDRef &attribute_id, const eCustomDataType data_type) const + { + return this->lookup(attribute_id, std::nullopt, data_type); + } + + /** + * Get read-only access to the attribute. If necessary, the attribute is interpolated to the + * given domain and then converted to the given type, in that order. The result may be empty. + */ + template + VArray lookup(const AttributeIDRef &attribute_id, + const std::optional domain = std::nullopt) const + { + const CPPType &cpp_type = CPPType::get(); + const eCustomDataType data_type = cpp_type_to_custom_data_type(cpp_type); + return this->lookup(attribute_id, domain, data_type).typed(); + } + + /** + * Get read-only access to the attribute. If necessary, the attribute is interpolated to the + * given domain and then converted to the given data type, in that order. + * If the attribute does not exist, a virtual array with the given default value is returned. + * If the passed in default value is null, the default value of the type is used (generally 0). + */ + GVArray lookup_or_default(const AttributeIDRef &attribute_id, + const eAttrDomain domain, + const eCustomDataType data_type, + const void *default_value = nullptr) const; + + /** + * Same as the generic version above, but should be used when the type is known at compile time. + */ + template + VArray lookup_or_default(const AttributeIDRef &attribute_id, + const eAttrDomain domain, + const T &default_value) const + { + if (VArray varray = this->lookup(attribute_id, domain)) { + return varray; + } + return VArray::ForSingle(default_value, this->domain_size(domain)); + } + + /** + * Interpolate data from one domain to another. + */ + GVArray adapt_domain(const GVArray &varray, + const eAttrDomain from_domain, + const eAttrDomain to_domain) const + { + return fn_->adapt_domain(owner_, varray, from_domain, to_domain); + } + + /** + * Interpolate data from one domain to another. + */ + template + VArray adapt_domain(const VArray &varray, + const eAttrDomain from_domain, + const eAttrDomain to_domain) const + { + return this->adapt_domain(GVArray(varray), from_domain, to_domain).typed(); + } + + /** + * Run the provided function for every attribute. + */ + bool for_all(const AttributeForeachCallback fn) const + { + return fn_->for_all(owner_, fn); + } + + /** + * Get a set of all attributes. + */ + Set all_ids() const; +}; + +/** + * Extends #AttributeAccessor with methods that allow modifying individual attributes as well as + * the set of attributes. + */ +class MutableAttributeAccessor : public AttributeAccessor { + public: + MutableAttributeAccessor(void *owner, const AttributeAccessorFunctions &fn) + : AttributeAccessor(owner, fn) + { + } + + /** + * Get a writable attribute or none if it does not exist. + * Make sure to call #finish after changes are done. + */ + GAttributeWriter lookup_for_write(const AttributeIDRef &attribute_id) + { + return fn_->lookup_for_write(owner_, attribute_id); + } + + /** + * Get a writable attribute or non if it does not exist. + * Make sure to call #finish after changes are done. + */ + template AttributeWriter lookup_for_write(const AttributeIDRef &attribute_id) + { + GAttributeWriter attribute = this->lookup_for_write(attribute_id); + if (!attribute) { + return {}; + } + if (!attribute.varray.type().is()) { + return {}; + } + return attribute.typed(); + } + + /** + * Create a new attribute. + * \return True, when a new attribute has been created. False, when it's not possible to create + * this attribute or there is already an attribute with that id. + */ + bool add(const AttributeIDRef &attribute_id, + const eAttrDomain domain, + const eCustomDataType data_type, + const AttributeInit &initializer) + { + return fn_->add(owner_, attribute_id, domain, data_type, initializer); + } + + /** + * Find an attribute with the given id, domain and data type. If it does not exist, create a new + * attribute. If the attribute does not exist and can't be created (e.g. because it already + * exists on a different domain or with a different type), none is returned. + */ + GAttributeWriter lookup_or_add_for_write( + const AttributeIDRef &attribute_id, + const eAttrDomain domain, + const eCustomDataType data_type, + const AttributeInit &initializer = AttributeInitDefault()); + + /** + * Same as above, but returns a type that makes it easier to work with the attribute as a span. + * If the caller newly initializes the attribute, it's better to use + * #lookup_or_add_for_write_only_span. + */ + GSpanAttributeWriter lookup_or_add_for_write_span( + const AttributeIDRef &attribute_id, + const eAttrDomain domain, + const eCustomDataType data_type, + const AttributeInit &initializer = AttributeInitDefault()); + + /** + * Same as above, but should be used when the type is known at compile time. + */ + template + AttributeWriter lookup_or_add_for_write( + const AttributeIDRef &attribute_id, + const eAttrDomain domain, + const AttributeInit &initializer = AttributeInitDefault()) + { + const CPPType &cpp_type = CPPType::get(); + const eCustomDataType data_type = cpp_type_to_custom_data_type(cpp_type); + return this->lookup_or_add_for_write(attribute_id, domain, data_type, initializer).typed(); + } + + /** + * Same as above, but should be used when the type is known at compile time. + */ + template + SpanAttributeWriter lookup_or_add_for_write_span( + const AttributeIDRef &attribute_id, + const eAttrDomain domain, + const AttributeInit &initializer = AttributeInitDefault()) + { + AttributeWriter attribute = this->lookup_or_add_for_write( + attribute_id, domain, initializer); + if (attribute) { + return SpanAttributeWriter{std::move(attribute), true}; + } + return {}; + } + + /** + * Find an attribute with the given id, domain and data type. If it does not exist, create a new + * attribute. If the attribute does not exist and can't be created, none is returned. + * + * The "only" in the name indicates that the caller should not read existing values from the + * span. If the attribute is not stored as span internally, the existing values won't be copied + * over to the span. + */ + GSpanAttributeWriter lookup_or_add_for_write_only_span(const AttributeIDRef &attribute_id, + const eAttrDomain domain, + const eCustomDataType data_type); + + /** + * Same as above, but should be used when the type is known at compile time. + */ + template + SpanAttributeWriter lookup_or_add_for_write_only_span(const AttributeIDRef &attribute_id, + const eAttrDomain domain) + { + AttributeWriter attribute = this->lookup_or_add_for_write(attribute_id, domain); + if (attribute) { + return SpanAttributeWriter{std::move(attribute), false}; + } + return {}; + } + + /** + * Remove an attribute. + * \return True, when the attribute has been deleted. False, when it's not possible to delete + * this attribute or if there is no attribute with that id. + */ + bool remove(const AttributeIDRef &attribute_id) + { + return fn_->remove(owner_, attribute_id); + } + + /** + * Remove all anonymous attributes. + */ + void remove_anonymous(); +}; + +bool allow_procedural_attribute_access(StringRef attribute_name); +extern const char *no_procedural_access_message; + +eCustomDataType attribute_data_type_highest_complexity(Span data_types); +/** + * Domains with a higher "information density" have a higher priority, + * in order to choose a domain that will not lose data through domain conversion. + */ +eAttrDomain attribute_domain_highest_priority(Span domains); + +/** + * A basic container around DNA CustomData so that its users + * don't have to implement special copy and move constructors. + */ +class CustomDataAttributes { + /** + * #CustomData needs a size to be freed, and unfortunately it isn't stored in the struct + * itself, so keep track of the size here so this class can implement its own destructor. + * If the implementation of the attribute storage changes, this could be removed. + */ + int size_; + + public: + CustomData data; + + CustomDataAttributes(); + ~CustomDataAttributes(); + CustomDataAttributes(const CustomDataAttributes &other); + CustomDataAttributes(CustomDataAttributes &&other); + CustomDataAttributes &operator=(const CustomDataAttributes &other); + + void reallocate(int size); + + void clear(); + + std::optional get_for_read(const AttributeIDRef &attribute_id) const; + + /** + * Return a virtual array for a stored attribute, or a single value virtual array with the + * default value if the attribute doesn't exist. If no default value is provided, the default + * value for the type will be used. + */ + blender::GVArray get_for_read(const AttributeIDRef &attribute_id, + eCustomDataType data_type, + const void *default_value) const; + + template + blender::VArray get_for_read(const AttributeIDRef &attribute_id, const T &default_value) const + { + const blender::CPPType &cpp_type = blender::CPPType::get(); + const eCustomDataType type = blender::bke::cpp_type_to_custom_data_type(cpp_type); + GVArray varray = this->get_for_read(attribute_id, type, &default_value); + return varray.typed(); + } + + std::optional get_for_write(const AttributeIDRef &attribute_id); + bool create(const AttributeIDRef &attribute_id, eCustomDataType data_type); + bool create_by_move(const AttributeIDRef &attribute_id, eCustomDataType data_type, void *buffer); + bool remove(const AttributeIDRef &attribute_id); + + /** + * Change the order of the attributes to match the order of IDs in the argument. + */ + void reorder(Span new_order); + + bool foreach_attribute(const AttributeForeachCallback callback, eAttrDomain domain) const; +}; + +AttributeAccessor mesh_attributes(const Mesh &mesh); +MutableAttributeAccessor mesh_attributes_for_write(Mesh &mesh); + +AttributeAccessor pointcloud_attributes(const PointCloud &pointcloud); +MutableAttributeAccessor pointcloud_attributes_for_write(PointCloud &pointcloud); + +/* -------------------------------------------------------------------- */ +/** \name #AttributeIDRef Inline Methods + * \{ */ + +inline AttributeIDRef::AttributeIDRef() = default; + +inline AttributeIDRef::AttributeIDRef(StringRef name) : name_(name) +{ +} + +inline AttributeIDRef::AttributeIDRef(StringRefNull name) : name_(name) +{ +} + +inline AttributeIDRef::AttributeIDRef(const char *name) : name_(name) +{ +} + +inline AttributeIDRef::AttributeIDRef(const std::string &name) : name_(name) +{ +} + +/* The anonymous id is only borrowed, the caller has to keep a reference to it. */ +inline AttributeIDRef::AttributeIDRef(const AnonymousAttributeID *anonymous_id) + : anonymous_id_(anonymous_id) +{ +} + +inline bool operator==(const AttributeIDRef &a, const AttributeIDRef &b) +{ + return a.anonymous_id_ == b.anonymous_id_ && a.name_ == b.name_; +} + +inline AttributeIDRef::operator bool() const +{ + return this->is_named() || this->is_anonymous(); +} + +inline uint64_t AttributeIDRef::hash() const +{ + return get_default_hash_2(name_, anonymous_id_); +} + +inline bool AttributeIDRef::is_named() const +{ + return !name_.is_empty(); +} + +inline bool AttributeIDRef::is_anonymous() const +{ + return anonymous_id_ != nullptr; +} + +inline StringRef AttributeIDRef::name() const +{ + BLI_assert(this->is_named()); + return name_; +} + +inline const AnonymousAttributeID &AttributeIDRef::anonymous_id() const +{ + BLI_assert(this->is_anonymous()); + return *anonymous_id_; +} + +/** + * \return True if the attribute should not be removed automatically as an optimization during + * processing or copying. Anonymous attributes can be removed when they no longer have any + * references. + */ +inline bool AttributeIDRef::should_be_kept() const +{ + return this->is_named() || BKE_anonymous_attribute_id_has_strong_references(anonymous_id_); +} + +} // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_attribute_access.hh b/source/blender/blenkernel/BKE_attribute_access.hh deleted file mode 100644 index 9648b5b7cde..00000000000 --- a/source/blender/blenkernel/BKE_attribute_access.hh +++ /dev/null @@ -1,541 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -#pragma once - -#include - -#include "BKE_anonymous_attribute.hh" -#include "BKE_attribute.h" - -#include "BLI_color.hh" -#include "BLI_function_ref.hh" -#include "BLI_generic_span.hh" -#include "BLI_generic_virtual_array.hh" -#include "BLI_math_vec_types.hh" - -/** - * This file defines classes that help to provide access to attribute data on a #GeometryComponent. - * The API for retrieving attributes is defined in `BKE_geometry_set.hh`, but this comment has some - * general comments about the system. - * - * Attributes are stored in geometry data, though they can also be stored in instances. Their - * storage is often tied to `CustomData`, which is a system to store "layers" of data with specific - * types and names. However, since `CustomData` was added to Blender before attributes were - * conceptualized, it combines the "legacy" style of task-specific attribute types with generic - * types like "Float". The attribute API here only provides access to generic types. - * - * Attributes are retrieved from geometry components by providing an "id" (#AttributeIDRef). This - * is most commonly just an attribute name. The attribute API on geometry components has some more - * advanced capabilities: - * 1. Read-only access: With a `const` geometry component, an attribute on the geometry cannot be - * modified, so the `for_write` and `for_output` versions of the API are not available. This is - * extremely important for writing coherent bug-free code. When an attribute is retrieved with - * write access, via #WriteAttributeLookup or #OutputAttribute, the geometry component must be - * tagged to clear caches that depend on the changed data. - * 2. Domain interpolation: When retrieving an attribute, a domain (#eAttrDomain) can be - * provided. If the attribute is stored on a different domain and conversion is possible, a - * version of the data interpolated to the requested domain will be provided. These conversions - * are implemented in each #GeometryComponent by `attribute_try_adapt_domain_impl`. - * 3. Implicit type conversion: In addition to interpolating domains, attribute types can be - * converted, using the conversions in `BKE_type_conversions.hh`. The #VArray / #GVArray system - * makes it possible to only convert necessary indices on-demand. - * 4. Anonymous attributes: The "id" used to look up an attribute can also be an anonymous - * attribute reference. Currently anonymous attributes are only used in geometry nodes. - * 5. Abstracted storage: Since the data returned from the API is usually a virtual array, - * it doesn't have to be stored contiguously (even though that is generally preferred). This - * allows accessing "legacy" attributes like `material_index`, which is stored in `MPoly`. - */ - -namespace blender::bke { - -/** - * Identifies an attribute that is either named or anonymous. - * It does not own the identifier, so it is just a reference. - */ -class AttributeIDRef { - private: - StringRef name_; - const AnonymousAttributeID *anonymous_id_ = nullptr; - - public: - AttributeIDRef(); - AttributeIDRef(StringRef name); - AttributeIDRef(StringRefNull name); - AttributeIDRef(const char *name); - AttributeIDRef(const std::string &name); - AttributeIDRef(const AnonymousAttributeID *anonymous_id); - - operator bool() const; - uint64_t hash() const; - bool is_named() const; - bool is_anonymous() const; - StringRef name() const; - const AnonymousAttributeID &anonymous_id() const; - bool should_be_kept() const; - - friend bool operator==(const AttributeIDRef &a, const AttributeIDRef &b); - friend std::ostream &operator<<(std::ostream &stream, const AttributeIDRef &attribute_id); -}; - -bool allow_procedural_attribute_access(StringRef attribute_name); -extern const char *no_procedural_access_message; - -} // namespace blender::bke - -/** - * Contains information about an attribute in a geometry component. - * More information can be added in the future. E.g. whether the attribute is builtin and how it is - * stored (uv map, vertex group, ...). - */ -struct AttributeMetaData { - eAttrDomain domain; - eCustomDataType data_type; - - constexpr friend bool operator==(AttributeMetaData a, AttributeMetaData b) - { - return (a.domain == b.domain) && (a.data_type == b.data_type); - } -}; - -struct AttributeKind { - eAttrDomain domain; - eCustomDataType data_type; -}; - -/** - * Base class for the attribute initializer types described below. - */ -struct AttributeInit { - enum class Type { - Default, - VArray, - MoveArray, - }; - Type type; - AttributeInit(const Type type) : type(type) - { - } -}; - -/** - * Create an attribute using the default value for the data type. - * The default values may depend on the attribute provider implementation. - */ -struct AttributeInitDefault : public AttributeInit { - AttributeInitDefault() : AttributeInit(Type::Default) - { - } -}; - -/** - * Create an attribute by copying data from an existing virtual array. The virtual array - * must have the same type as the newly created attribute. - * - * Note that this can be used to fill the new attribute with the default - */ -struct AttributeInitVArray : public AttributeInit { - blender::GVArray varray; - - AttributeInitVArray(blender::GVArray varray) - : AttributeInit(Type::VArray), varray(std::move(varray)) - { - } -}; - -/** - * Create an attribute with a by passing ownership of a pre-allocated contiguous array of data. - * Sometimes data is created before a geometry component is available. In that case, it's - * preferable to move data directly to the created attribute to avoid a new allocation and a copy. - * - * Note that this will only have a benefit for attributes that are stored directly as contiguous - * arrays, so not for some built-in attributes. - * - * The array must be allocated with MEM_*, since `attribute_try_create` will free the array if it - * can't be used directly, and that is generally how Blender expects custom data to be allocated. - */ -struct AttributeInitMove : public AttributeInit { - void *data = nullptr; - - AttributeInitMove(void *data) : AttributeInit(Type::MoveArray), data(data) - { - } -}; - -/* Returns false when the iteration should be stopped. */ -using AttributeForeachCallback = blender::FunctionRef; - -namespace blender::bke { - -eCustomDataType attribute_data_type_highest_complexity(Span data_types); -/** - * Domains with a higher "information density" have a higher priority, - * in order to choose a domain that will not lose data through domain conversion. - */ -eAttrDomain attribute_domain_highest_priority(Span domains); - -/** - * Used when looking up a "plain attribute" based on a name for reading from it. - */ -struct ReadAttributeLookup { - /* The virtual array that is used to read from this attribute. */ - GVArray varray; - /* Domain the attribute lives on in the geometry. */ - eAttrDomain domain; - - /* Convenience function to check if the attribute has been found. */ - operator bool() const - { - return this->varray; - } -}; - -/** - * Used when looking up a "plain attribute" based on a name for reading from it and writing to it. - */ -struct WriteAttributeLookup { - /** The virtual array that is used to read from and write to the attribute. */ - GVMutableArray varray; - /** Domain the attributes lives on in the geometry component. */ - eAttrDomain domain; - /** - * Call this after changing the attribute to invalidate caches that depend on this attribute. - * \note Do not call this after the component the attribute is from has been destructed. - */ - std::function tag_modified_fn; - - /* Convenience function to check if the attribute has been found. */ - operator bool() const - { - return this->varray; - } -}; - -/** - * An output attribute allows writing to an attribute (and optionally reading as well). It adds - * some convenience features on top of `GVMutableArray` that are very commonly used. - * - * Supported convenience features: - * - Implicit type conversion when writing to builtin attributes. - * - Supports simple access to a span containing the attribute values (that avoids the use of - * MutableVArraySpan in many cases). - * - An output attribute can live side by side with an existing attribute with a different domain - * or data type. The old attribute will only be overwritten when the #save function is called. - * - * \note The lifetime of an output attribute should not be longer than the lifetime of the - * geometry component it comes from, since it can keep a reference to the component for use in - * the #save method. - */ -class OutputAttribute { - public: - using SaveFn = std::function; - - private: - GVMutableArray varray_; - eAttrDomain domain_ = ATTR_DOMAIN_AUTO; - SaveFn save_; - std::unique_ptr optional_span_varray_; - bool ignore_old_values_ = false; - bool save_has_been_called_ = false; - - public: - OutputAttribute(); - OutputAttribute(OutputAttribute &&other); - OutputAttribute(GVMutableArray varray, eAttrDomain domain, SaveFn save, bool ignore_old_values); - - ~OutputAttribute(); - - operator bool() const; - - GVMutableArray &operator*(); - GVMutableArray *operator->(); - GVMutableArray &varray(); - eAttrDomain domain() const; - const CPPType &cpp_type() const; - eCustomDataType custom_data_type() const; - - GMutableSpan as_span(); - template MutableSpan as_span(); - - void save(); -}; - -/** - * Same as OutputAttribute, but should be used when the data type is known at compile time. - */ -template class OutputAttribute_Typed { - private: - OutputAttribute attribute_; - VMutableArray varray_; - - public: - OutputAttribute_Typed(); - OutputAttribute_Typed(OutputAttribute attribute) : attribute_(std::move(attribute)) - { - if (attribute_) { - varray_ = attribute_.varray().template typed(); - } - } - - OutputAttribute_Typed(OutputAttribute_Typed &&other); - ~OutputAttribute_Typed(); - - OutputAttribute_Typed &operator=(OutputAttribute_Typed &&other) - { - if (this == &other) { - return *this; - } - this->~OutputAttribute_Typed(); - new (this) OutputAttribute_Typed(std::move(other)); - return *this; - } - - operator bool() const - { - return varray_; - } - - VMutableArray &operator*() - { - return varray_; - } - - VMutableArray *operator->() - { - return &varray_; - } - - VMutableArray &varray() - { - return varray_; - } - - eAttrDomain domain() const - { - return attribute_.domain(); - } - - const CPPType &cpp_type() const - { - return CPPType::get(); - } - - eCustomDataType custom_data_type() const - { - return cpp_type_to_custom_data_type(this->cpp_type()); - } - - MutableSpan as_span() - { - return attribute_.as_span(); - } - - void save() - { - attribute_.save(); - } -}; - -/* These are not defined in the class directly, because when defining them there, the external - * template instantiation does not work, resulting in longer compile times. */ -template inline OutputAttribute_Typed::OutputAttribute_Typed() = default; -template -inline OutputAttribute_Typed::OutputAttribute_Typed(OutputAttribute_Typed &&other) = default; -template inline OutputAttribute_Typed::~OutputAttribute_Typed() = default; - -/** - * A basic container around DNA CustomData so that its users - * don't have to implement special copy and move constructors. - */ -class CustomDataAttributes { - /** - * #CustomData needs a size to be freed, and unfortunately it isn't stored in the struct - * itself, so keep track of the size here so this class can implement its own destructor. - * If the implementation of the attribute storage changes, this could be removed. - */ - int size_; - - public: - CustomData data; - - CustomDataAttributes(); - ~CustomDataAttributes(); - CustomDataAttributes(const CustomDataAttributes &other); - CustomDataAttributes(CustomDataAttributes &&other); - CustomDataAttributes &operator=(const CustomDataAttributes &other); - - void reallocate(int size); - - void clear(); - - std::optional get_for_read(const AttributeIDRef &attribute_id) const; - - /** - * Return a virtual array for a stored attribute, or a single value virtual array with the - * default value if the attribute doesn't exist. If no default value is provided, the default - * value for the type will be used. - */ - blender::GVArray get_for_read(const AttributeIDRef &attribute_id, - eCustomDataType data_type, - const void *default_value) const; - - template - blender::VArray get_for_read(const AttributeIDRef &attribute_id, const T &default_value) const - { - const blender::CPPType &cpp_type = blender::CPPType::get(); - const eCustomDataType type = blender::bke::cpp_type_to_custom_data_type(cpp_type); - GVArray varray = this->get_for_read(attribute_id, type, &default_value); - return varray.typed(); - } - - std::optional get_for_write(const AttributeIDRef &attribute_id); - bool create(const AttributeIDRef &attribute_id, eCustomDataType data_type); - bool create_by_move(const AttributeIDRef &attribute_id, eCustomDataType data_type, void *buffer); - bool remove(const AttributeIDRef &attribute_id); - - /** - * Change the order of the attributes to match the order of IDs in the argument. - */ - void reorder(Span new_order); - - bool foreach_attribute(const AttributeForeachCallback callback, eAttrDomain domain) const; -}; - -/* -------------------------------------------------------------------- */ -/** \name #AttributeIDRef Inline Methods - * \{ */ - -inline AttributeIDRef::AttributeIDRef() = default; - -inline AttributeIDRef::AttributeIDRef(StringRef name) : name_(name) -{ -} - -inline AttributeIDRef::AttributeIDRef(StringRefNull name) : name_(name) -{ -} - -inline AttributeIDRef::AttributeIDRef(const char *name) : name_(name) -{ -} - -inline AttributeIDRef::AttributeIDRef(const std::string &name) : name_(name) -{ -} - -/* The anonymous id is only borrowed, the caller has to keep a reference to it. */ -inline AttributeIDRef::AttributeIDRef(const AnonymousAttributeID *anonymous_id) - : anonymous_id_(anonymous_id) -{ -} - -inline bool operator==(const AttributeIDRef &a, const AttributeIDRef &b) -{ - return a.anonymous_id_ == b.anonymous_id_ && a.name_ == b.name_; -} - -inline AttributeIDRef::operator bool() const -{ - return this->is_named() || this->is_anonymous(); -} - -inline uint64_t AttributeIDRef::hash() const -{ - return get_default_hash_2(name_, anonymous_id_); -} - -inline bool AttributeIDRef::is_named() const -{ - return !name_.is_empty(); -} - -inline bool AttributeIDRef::is_anonymous() const -{ - return anonymous_id_ != nullptr; -} - -inline StringRef AttributeIDRef::name() const -{ - BLI_assert(this->is_named()); - return name_; -} - -inline const AnonymousAttributeID &AttributeIDRef::anonymous_id() const -{ - BLI_assert(this->is_anonymous()); - return *anonymous_id_; -} - -/** - * \return True if the attribute should not be removed automatically as an optimization during - * processing or copying. Anonymous attributes can be removed when they no longer have any - * references. - */ -inline bool AttributeIDRef::should_be_kept() const -{ - return this->is_named() || BKE_anonymous_attribute_id_has_strong_references(anonymous_id_); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name #OutputAttribute Inline Methods - * \{ */ - -inline OutputAttribute::OutputAttribute() = default; -inline OutputAttribute::OutputAttribute(OutputAttribute &&other) = default; - -inline OutputAttribute::OutputAttribute(GVMutableArray varray, - eAttrDomain domain, - SaveFn save, - const bool ignore_old_values) - : varray_(std::move(varray)), - domain_(domain), - save_(std::move(save)), - ignore_old_values_(ignore_old_values) -{ -} - -inline OutputAttribute::operator bool() const -{ - return varray_; -} - -inline GVMutableArray &OutputAttribute::operator*() -{ - return varray_; -} - -inline GVMutableArray *OutputAttribute::operator->() -{ - return &varray_; -} - -inline GVMutableArray &OutputAttribute::varray() -{ - return varray_; -} - -inline eAttrDomain OutputAttribute::domain() const -{ - return domain_; -} - -inline const CPPType &OutputAttribute::cpp_type() const -{ - return varray_.type(); -} - -inline eCustomDataType OutputAttribute::custom_data_type() const -{ - return cpp_type_to_custom_data_type(this->cpp_type()); -} - -template inline MutableSpan OutputAttribute::as_span() -{ - return this->as_span().typed(); -} - -/** \} */ - -} // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh index 3e00dc78b74..fb97e52f6da 100644 --- a/source/blender/blenkernel/BKE_curves.hh +++ b/source/blender/blenkernel/BKE_curves.hh @@ -20,7 +20,7 @@ #include "BLI_vector.hh" #include "BLI_virtual_array.hh" -#include "BKE_attribute_access.hh" +#include "BKE_attribute.hh" namespace blender::bke { @@ -401,6 +401,9 @@ class CurvesGeometry : public ::CurvesGeometry { */ void remove_attributes_based_on_types(); + AttributeAccessor attributes() const; + MutableAttributeAccessor attributes_for_write(); + /* -------------------------------------------------------------------- * Attributes. */ diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index 1d4291473bb..4108e2f7e2e 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -8,6 +8,7 @@ #include #include +#include #include "BLI_float4x4.hh" #include "BLI_function_ref.hh" @@ -19,7 +20,7 @@ #include "BLI_vector_set.hh" #include "BKE_anonymous_attribute.hh" -#include "BKE_attribute_access.hh" +#include "BKE_attribute.hh" #include "BKE_geometry_set.h" struct Curves; @@ -63,6 +64,15 @@ class GeometryComponent { virtual ~GeometryComponent() = default; static GeometryComponent *create(GeometryComponentType component_type); + int attribute_domain_size(eAttrDomain domain) const; + + /** + * Get access to the attributes in this geometry component. May return none if the geometry does + * not support the attribute system. + */ + virtual std::optional attributes() const; + virtual std::optional attributes_for_write(); + /* The returned component should be of the same type as the type this is called on. */ virtual GeometryComponent *copy() const = 0; @@ -78,203 +88,7 @@ class GeometryComponent { GeometryComponentType type() const; - /** - * Return true when any attribute with this name exists, including built in attributes. - */ - bool attribute_exists(const blender::bke::AttributeIDRef &attribute_id) const; - - /** - * Return the data type and domain of an attribute with the given name if it exists. - */ - std::optional attribute_get_meta_data( - const blender::bke::AttributeIDRef &attribute_id) const; - - /** - * Return true when the geometry component supports this attribute domain. - * \note Conceptually this function is static, the result is always the same for different - * instances of the same geometry component type. - */ - bool attribute_domain_supported(eAttrDomain domain) const; - /** - * Return the length of a specific domain, or 0 if the domain is not supported. - */ - virtual int attribute_domain_num(eAttrDomain domain) const; - - /** - * Return true if the attribute name corresponds to a built-in attribute with a hardcoded domain - * and data type. - */ - bool attribute_is_builtin(const blender::StringRef attribute_name) const; - bool attribute_is_builtin(const blender::bke::AttributeIDRef &attribute_id) const; - - /** - * Get read-only access to an attribute with the given name or id, on the highest priority domain - * if there is a name collision. - * \return null if the attribute does not exist. - */ - blender::bke::ReadAttributeLookup attribute_try_get_for_read( - const blender::bke::AttributeIDRef &attribute_id) const; - - /** - * Get read and write access to an attribute with the given name or id, on the highest priority - * domain if there is a name collision. - * \note #WriteAttributeLookup.tag_modified_fn must be called after modifying data. - * \return null if the attribute does not exist - */ - blender::bke::WriteAttributeLookup attribute_try_get_for_write( - const blender::bke::AttributeIDRef &attribute_id); - - /** - * Get a read-only attribute for the domain based on the given attribute. This can be used to - * interpolate from one domain to another. - * \return null if the interpolation is not implemented. - */ - blender::GVArray attribute_try_adapt_domain(const blender::GVArray &varray, - const eAttrDomain from_domain, - const eAttrDomain to_domain) const - { - return this->attribute_try_adapt_domain_impl(varray, from_domain, to_domain); - } - /* Use instead of the method above when the type is known at compile time for type safety. */ - template - blender::VArray attribute_try_adapt_domain(const blender::VArray &varray, - const eAttrDomain from_domain, - const eAttrDomain to_domain) const - { - return this->attribute_try_adapt_domain_impl(varray, from_domain, to_domain) - .template typed(); - } - - /** Returns true when the attribute has been deleted. */ - bool attribute_try_delete(const blender::bke::AttributeIDRef &attribute_id); - - /** - * Remove any anonymous attributes on the geometry (they generally shouldn't exist on original - * geometry). - */ - void attributes_remove_anonymous(); - - /** Returns true when the attribute has been created. */ - bool attribute_try_create(const blender::bke::AttributeIDRef &attribute_id, - eAttrDomain domain, - eCustomDataType data_type, - const AttributeInit &initializer); - - /** - * Try to create the builtin attribute with the given name. No data type or domain has to be - * provided, because those are fixed for builtin attributes. - */ - bool attribute_try_create_builtin(const blender::StringRef attribute_name, - const AttributeInit &initializer); - - blender::Set attribute_ids() const; - /** - * \return False if the callback explicitly returned false at any point, otherwise true, - * meaning the callback made it all the way through. - */ - bool attribute_foreach(const AttributeForeachCallback callback) const; - virtual bool is_empty() const; - - /** - * Get a virtual array that refers to the data of an attribute, interpolated to the given domain - * and converted to the data type. Returns null when the attribute does not exist or cannot be - * interpolated or converted. - */ - blender::GVArray attribute_try_get_for_read(const blender::bke::AttributeIDRef &attribute_id, - eAttrDomain domain, - eCustomDataType data_type) const; - - /** - * Get a virtual array that refers to the data of an attribute, interpolated to the given domain. - * The data type is left unchanged. Returns null when the attribute does not exist or cannot be - * interpolated. - */ - blender::GVArray attribute_try_get_for_read(const blender::bke::AttributeIDRef &attribute_id, - eAttrDomain domain) const; - - /** - * Get a virtual array that refers to the data of an attribute converted to the given data type. - * The attribute's domain is left unchanged. Returns null when the attribute does not exist or - * cannot be converted. - */ - blender::bke::ReadAttributeLookup attribute_try_get_for_read( - const blender::bke::AttributeIDRef &attribute_id, eCustomDataType data_type) const; - - /** - * Get a virtual array that refers to the data of an attribute, interpolated to the given domain - * and converted to the data type. If that is not possible, the returned virtual array will - * contain a default value. This never returns null. - */ - blender::GVArray attribute_get_for_read(const blender::bke::AttributeIDRef &attribute_id, - eAttrDomain domain, - eCustomDataType data_type, - const void *default_value = nullptr) const; - /* Use instead of the method above when the type is known at compile time for type safety. */ - template - blender::VArray attribute_get_for_read(const blender::bke::AttributeIDRef &attribute_id, - const eAttrDomain domain, - const T &default_value) const - { - const blender::CPPType &cpp_type = blender::CPPType::get(); - const eCustomDataType type = blender::bke::cpp_type_to_custom_data_type(cpp_type); - return this->attribute_get_for_read(attribute_id, domain, type, &default_value) - .template typed(); - } - - /** - * Returns an "output attribute", which is essentially a mutable virtual array with some commonly - * used convince features. The returned output attribute might be empty if requested attribute - * cannot exist on the geometry. - * - * The included convenience features are: - * - Implicit type conversion when writing to builtin attributes. - * - If the attribute name exists already, but has a different type/domain, a temporary attribute - * is created that will overwrite the existing attribute in the end. - */ - blender::bke::OutputAttribute attribute_try_get_for_output( - const blender::bke::AttributeIDRef &attribute_id, - eAttrDomain domain, - eCustomDataType data_type, - const void *default_value = nullptr); - /* Use instead of the method above when the type is known at compile time for type safety. */ - template - blender::bke::OutputAttribute_Typed attribute_try_get_for_output( - const blender::bke::AttributeIDRef &attribute_id, - const eAttrDomain domain, - const T default_value) - { - const blender::CPPType &cpp_type = blender::CPPType::get(); - const eCustomDataType data_type = blender::bke::cpp_type_to_custom_data_type(cpp_type); - return this->attribute_try_get_for_output(attribute_id, domain, data_type, &default_value); - } - - /** - * Same as #attribute_try_get_for_output, but should be used when the original values in the - * attributes are not read, i.e. the attribute is used only for output. The can be faster because - * it can avoid interpolation and conversion of existing values. Since values are not read from - * this attribute, no default value is necessary. - */ - blender::bke::OutputAttribute attribute_try_get_for_output_only( - const blender::bke::AttributeIDRef &attribute_id, - eAttrDomain domain, - eCustomDataType data_type); - /* Use instead of the method above when the type is known at compile time for type safety. */ - template - blender::bke::OutputAttribute_Typed attribute_try_get_for_output_only( - const blender::bke::AttributeIDRef &attribute_id, const eAttrDomain domain) - { - const blender::CPPType &cpp_type = blender::CPPType::get(); - const eCustomDataType data_type = blender::bke::cpp_type_to_custom_data_type(cpp_type); - return this->attribute_try_get_for_output_only(attribute_id, domain, data_type); - } - - private: - virtual const blender::bke::ComponentAttributeProviders *get_attribute_providers() const; - - virtual blender::GVArray attribute_try_adapt_domain_impl(const blender::GVArray &varray, - eAttrDomain from_domain, - eAttrDomain to_domain) const; }; template @@ -381,7 +195,7 @@ struct GeometrySet { using AttributeForeachCallback = blender::FunctionRef; void attribute_foreach(blender::Span component_types, @@ -392,7 +206,7 @@ struct GeometrySet { blender::Span component_types, GeometryComponentType dst_component_type, bool include_instances, - blender::Map &r_attributes) const; + blender::Map &r_attributes) const; blender::Vector gather_component_types(bool include_instances, bool ignore_empty) const; @@ -565,8 +379,6 @@ class MeshComponent : public GeometryComponent { */ Mesh *get_for_write(); - int attribute_domain_num(eAttrDomain domain) const final; - bool is_empty() const final; bool owns_direct_data() const override; @@ -574,12 +386,8 @@ class MeshComponent : public GeometryComponent { static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_MESH; - private: - const blender::bke::ComponentAttributeProviders *get_attribute_providers() const final; - - blender::GVArray attribute_try_adapt_domain_impl(const blender::GVArray &varray, - eAttrDomain from_domain, - eAttrDomain to_domain) const final; + std::optional attributes() const final; + std::optional attributes_for_write() final; }; /** @@ -628,17 +436,17 @@ class PointCloudComponent : public GeometryComponent { */ PointCloud *get_for_write(); - int attribute_domain_num(eAttrDomain domain) const final; - bool is_empty() const final; bool owns_direct_data() const override; void ensure_owns_direct_data() override; + std::optional attributes() const final; + std::optional attributes_for_write() final; + static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_POINT_CLOUD; private: - const blender::bke::ComponentAttributeProviders *get_attribute_providers() const final; }; /** @@ -669,21 +477,15 @@ class CurveComponentLegacy : public GeometryComponent { const CurveEval *get_for_read() const; CurveEval *get_for_write(); - int attribute_domain_num(eAttrDomain domain) const final; - bool is_empty() const final; bool owns_direct_data() const override; void ensure_owns_direct_data() override; - static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_CURVE; + std::optional attributes() const final; + std::optional attributes_for_write() final; - private: - const blender::bke::ComponentAttributeProviders *get_attribute_providers() const final; - - blender::GVArray attribute_try_adapt_domain_impl(const blender::GVArray &varray, - eAttrDomain from_domain, - eAttrDomain to_domain) const final; + static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_CURVE; }; /** @@ -721,8 +523,6 @@ class CurveComponent : public GeometryComponent { const Curves *get_for_read() const; Curves *get_for_write(); - int attribute_domain_num(eAttrDomain domain) const final; - bool is_empty() const final; bool owns_direct_data() const override; @@ -734,14 +534,10 @@ class CurveComponent : public GeometryComponent { */ const Curve *get_curve_for_render() const; - static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_CURVE; + std::optional attributes() const final; + std::optional attributes_for_write() final; - private: - const blender::bke::ComponentAttributeProviders *get_attribute_providers() const final; - - blender::GVArray attribute_try_adapt_domain_impl(const blender::GVArray &varray, - eAttrDomain from_domain, - eAttrDomain to_domain) const final; + static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_CURVE; }; /** @@ -966,10 +762,11 @@ class InstancesComponent : public GeometryComponent { blender::Span almost_unique_ids() const; - blender::bke::CustomDataAttributes &attributes(); - const blender::bke::CustomDataAttributes &attributes() const; + blender::bke::CustomDataAttributes &instance_attributes(); + const blender::bke::CustomDataAttributes &instance_attributes() const; - int attribute_domain_num(eAttrDomain domain) const final; + std::optional attributes() const final; + std::optional attributes_for_write() final; void foreach_referenced_geometry( blender::FunctionRef callback) const; @@ -982,7 +779,6 @@ class InstancesComponent : public GeometryComponent { static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_INSTANCES; private: - const blender::bke::ComponentAttributeProviders *get_attribute_providers() const final; }; /** diff --git a/source/blender/blenkernel/BKE_mesh_sample.hh b/source/blender/blenkernel/BKE_mesh_sample.hh index cbbd0249f4f..356709d8942 100644 --- a/source/blender/blenkernel/BKE_mesh_sample.hh +++ b/source/blender/blenkernel/BKE_mesh_sample.hh @@ -13,6 +13,7 @@ #include "DNA_meshdata_types.h" #include "BKE_attribute.h" +#include "BKE_attribute.hh" struct Mesh; struct BVHTreeFromMesh; @@ -21,11 +22,6 @@ namespace blender { class RandomNumberGenerator; } -namespace blender::bke { -struct ReadAttributeLookup; -class OutputAttribute; -} // namespace blender::bke - namespace blender::bke::mesh_surface_sample { void sample_point_attribute(const Mesh &mesh, @@ -81,8 +77,8 @@ class MeshAttributeInterpolator { eAttributeMapMode mode, const GMutableSpan dst); - void sample_attribute(const ReadAttributeLookup &src_attribute, - OutputAttribute &dst_attribute, + void sample_attribute(const GAttributeReader &src_attribute, + GSpanAttributeWriter &dst_attribute, eAttributeMapMode mode); protected: diff --git a/source/blender/blenkernel/BKE_spline.hh b/source/blender/blenkernel/BKE_spline.hh index 28f326a4ad4..767018ae0bb 100644 --- a/source/blender/blenkernel/BKE_spline.hh +++ b/source/blender/blenkernel/BKE_spline.hh @@ -15,7 +15,7 @@ #include "BLI_math_vec_types.hh" #include "BLI_vector.hh" -#include "BKE_attribute_access.hh" +#include "BKE_attribute.hh" #include "BKE_attribute_math.hh" struct Curve; @@ -646,6 +646,8 @@ struct CurveEval { void transform(const blender::float4x4 &matrix); bool bounds_min_max(blender::float3 &min, blender::float3 &max, bool use_evaluated) const; + blender::bke::MutableAttributeAccessor attributes_for_write(); + /** * Return the start indices for each of the curve spline's control points, if they were part * of a flattened array. This can be used to facilitate parallelism by avoiding the need to diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index b2307c0e129..d0f9a67f167 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -325,7 +325,7 @@ set(SRC BKE_asset_library.h BKE_asset_library.hh BKE_attribute.h - BKE_attribute_access.hh + BKE_attribute.hh BKE_attribute_math.hh BKE_autoexec.h BKE_blender.h diff --git a/source/blender/blenkernel/intern/attribute.cc b/source/blender/blenkernel/intern/attribute.cc index 7c09b4a4ce3..030e4941874 100644 --- a/source/blender/blenkernel/intern/attribute.cc +++ b/source/blender/blenkernel/intern/attribute.cc @@ -23,7 +23,7 @@ #include "BLI_string_utils.h" #include "BKE_attribute.h" -#include "BKE_attribute_access.hh" +#include "BKE_attribute.hh" #include "BKE_curves.h" #include "BKE_customdata.h" #include "BKE_editmesh.h" diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index ee9358eb97e..ac1ee19927c 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -2,7 +2,6 @@ #include -#include "BKE_attribute_access.hh" #include "BKE_attribute_math.hh" #include "BKE_customdata.h" #include "BKE_deform.h" @@ -26,8 +25,6 @@ #include "attribute_access_intern.hh" -static CLG_LogRef LOG = {"bke.attribute_access"}; - using blender::float3; using blender::GMutableSpan; using blender::GSpan; @@ -36,7 +33,6 @@ using blender::Set; using blender::StringRef; using blender::StringRefNull; using blender::bke::AttributeIDRef; -using blender::bke::OutputAttribute; namespace blender::bke { @@ -151,36 +147,6 @@ eAttrDomain attribute_domain_highest_priority(Span domains) return highest_priority_domain; } -GMutableSpan OutputAttribute::as_span() -{ - if (!optional_span_varray_) { - const bool materialize_old_values = !ignore_old_values_; - optional_span_varray_ = std::make_unique(varray_, materialize_old_values); - } - GMutableVArraySpan &span_varray = *optional_span_varray_; - return span_varray; -} - -void OutputAttribute::save() -{ - save_has_been_called_ = true; - if (optional_span_varray_) { - optional_span_varray_->save(); - } - if (save_) { - save_(*this); - } -} - -OutputAttribute::~OutputAttribute() -{ - if (!save_has_been_called_) { - if (varray_) { - std::cout << "Warning: Call `save()` to make sure that changes persist in all cases.\n"; - } - } -} - static AttributeIDRef attribute_id_from_custom_data_layer(const CustomDataLayer &layer) { if (layer.anonymous_id != nullptr) { @@ -292,9 +258,9 @@ static bool custom_data_layer_matches_attribute_id(const CustomDataLayer &layer, return layer.name == attribute_id.name(); } -GVArray BuiltinCustomDataLayerProvider::try_get_for_read(const GeometryComponent &component) const +GVArray BuiltinCustomDataLayerProvider::try_get_for_read(const void *owner) const { - const CustomData *custom_data = custom_data_access_.get_const_custom_data(component); + const CustomData *custom_data = custom_data_access_.get_const_custom_data(owner); if (custom_data == nullptr) { return {}; } @@ -310,21 +276,20 @@ GVArray BuiltinCustomDataLayerProvider::try_get_for_read(const GeometryComponent return {}; } - const int domain_num = component.attribute_domain_num(domain_); - return as_read_attribute_(data, domain_num); + const int element_num = custom_data_access_.get_element_num(owner); + return as_read_attribute_(data, element_num); } -WriteAttributeLookup BuiltinCustomDataLayerProvider::try_get_for_write( - GeometryComponent &component) const +GAttributeWriter BuiltinCustomDataLayerProvider::try_get_for_write(void *owner) const { if (writable_ != Writable) { return {}; } - CustomData *custom_data = custom_data_access_.get_custom_data(component); + CustomData *custom_data = custom_data_access_.get_custom_data(owner); if (custom_data == nullptr) { return {}; } - const int domain_num = component.attribute_domain_num(domain_); + const int element_num = custom_data_access_.get_element_num(owner); void *data; if (stored_as_named_attribute_) { @@ -340,44 +305,42 @@ WriteAttributeLookup BuiltinCustomDataLayerProvider::try_get_for_write( void *new_data; if (stored_as_named_attribute_) { new_data = CustomData_duplicate_referenced_layer_named( - custom_data, stored_type_, name_.c_str(), domain_num); + custom_data, stored_type_, name_.c_str(), element_num); } else { - new_data = CustomData_duplicate_referenced_layer(custom_data, stored_type_, domain_num); + new_data = CustomData_duplicate_referenced_layer(custom_data, stored_type_, element_num); } if (data != new_data) { if (custom_data_access_.update_custom_data_pointers) { - custom_data_access_.update_custom_data_pointers(component); + custom_data_access_.update_custom_data_pointers(owner); } data = new_data; } std::function tag_modified_fn; if (update_on_write_ != nullptr) { - tag_modified_fn = [component = &component, update = update_on_write_]() { - update(*component); - }; + tag_modified_fn = [owner, update = update_on_write_]() { update(owner); }; } - return {as_write_attribute_(data, domain_num), domain_, std::move(tag_modified_fn)}; + return {as_write_attribute_(data, element_num), domain_, std::move(tag_modified_fn)}; } -bool BuiltinCustomDataLayerProvider::try_delete(GeometryComponent &component) const +bool BuiltinCustomDataLayerProvider::try_delete(void *owner) const { if (deletable_ != Deletable) { return false; } - CustomData *custom_data = custom_data_access_.get_custom_data(component); + CustomData *custom_data = custom_data_access_.get_custom_data(owner); if (custom_data == nullptr) { return {}; } - const int domain_num = component.attribute_domain_num(domain_); + const int element_num = custom_data_access_.get_element_num(owner); if (stored_as_named_attribute_) { - if (CustomData_free_layer_named(custom_data, name_.c_str(), domain_num)) { + if (CustomData_free_layer_named(custom_data, name_.c_str(), element_num)) { if (custom_data_access_.update_custom_data_pointers) { - custom_data_access_.update_custom_data_pointers(component); + custom_data_access_.update_custom_data_pointers(owner); } return true; } @@ -385,27 +348,27 @@ bool BuiltinCustomDataLayerProvider::try_delete(GeometryComponent &component) co } const int layer_index = CustomData_get_layer_index(custom_data, stored_type_); - if (CustomData_free_layer(custom_data, stored_type_, domain_num, layer_index)) { + if (CustomData_free_layer(custom_data, stored_type_, element_num, layer_index)) { if (custom_data_access_.update_custom_data_pointers) { - custom_data_access_.update_custom_data_pointers(component); + custom_data_access_.update_custom_data_pointers(owner); } return true; } return false; } -bool BuiltinCustomDataLayerProvider::try_create(GeometryComponent &component, +bool BuiltinCustomDataLayerProvider::try_create(void *owner, const AttributeInit &initializer) const { if (createable_ != Creatable) { return false; } - CustomData *custom_data = custom_data_access_.get_custom_data(component); + CustomData *custom_data = custom_data_access_.get_custom_data(owner); if (custom_data == nullptr) { return false; } - const int domain_num = component.attribute_domain_num(domain_); + const int element_num = custom_data_access_.get_element_num(owner); bool success; if (stored_as_named_attribute_) { if (CustomData_get_layer_named(custom_data, data_type_, name_.c_str())) { @@ -413,7 +376,7 @@ bool BuiltinCustomDataLayerProvider::try_create(GeometryComponent &component, return false; } success = add_custom_data_layer_from_attribute_init( - name_, *custom_data, stored_type_, domain_num, initializer); + name_, *custom_data, stored_type_, element_num, initializer); } else { if (CustomData_get_layer(custom_data, stored_type_) != nullptr) { @@ -421,19 +384,19 @@ bool BuiltinCustomDataLayerProvider::try_create(GeometryComponent &component, return false; } success = add_builtin_type_custom_data_layer_from_init( - *custom_data, stored_type_, domain_num, initializer); + *custom_data, stored_type_, element_num, initializer); } if (success) { if (custom_data_access_.update_custom_data_pointers) { - custom_data_access_.update_custom_data_pointers(component); + custom_data_access_.update_custom_data_pointers(owner); } } return success; } -bool BuiltinCustomDataLayerProvider::exists(const GeometryComponent &component) const +bool BuiltinCustomDataLayerProvider::exists(const void *owner) const { - const CustomData *custom_data = custom_data_access_.get_const_custom_data(component); + const CustomData *custom_data = custom_data_access_.get_const_custom_data(owner); if (custom_data == nullptr) { return false; } @@ -443,14 +406,14 @@ bool BuiltinCustomDataLayerProvider::exists(const GeometryComponent &component) return CustomData_get_layer(custom_data, stored_type_) != nullptr; } -ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read( - const GeometryComponent &component, const AttributeIDRef &attribute_id) const +GAttributeReader CustomDataAttributeProvider::try_get_for_read( + const void *owner, const AttributeIDRef &attribute_id) const { - const CustomData *custom_data = custom_data_access_.get_const_custom_data(component); + const CustomData *custom_data = custom_data_access_.get_const_custom_data(owner); if (custom_data == nullptr) { return {}; } - const int domain_num = component.attribute_domain_num(domain_); + const int element_num = custom_data_access_.get_element_num(owner); for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) { if (!custom_data_layer_matches_attribute_id(layer, attribute_id)) { continue; @@ -459,61 +422,62 @@ ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read( if (type == nullptr) { continue; } - GSpan data{*type, layer.data, domain_num}; + GSpan data{*type, layer.data, element_num}; return {GVArray::ForSpan(data), domain_}; } return {}; } -WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write( - GeometryComponent &component, const AttributeIDRef &attribute_id) const +GAttributeWriter CustomDataAttributeProvider::try_get_for_write( + void *owner, const AttributeIDRef &attribute_id) const { - CustomData *custom_data = custom_data_access_.get_custom_data(component); + CustomData *custom_data = custom_data_access_.get_custom_data(owner); if (custom_data == nullptr) { return {}; } - const int domain_num = component.attribute_domain_num(domain_); + const int element_num = custom_data_access_.get_element_num(owner); for (CustomDataLayer &layer : MutableSpan(custom_data->layers, custom_data->totlayer)) { if (!custom_data_layer_matches_attribute_id(layer, attribute_id)) { continue; } if (attribute_id.is_named()) { - CustomData_duplicate_referenced_layer_named(custom_data, layer.type, layer.name, domain_num); + CustomData_duplicate_referenced_layer_named( + custom_data, layer.type, layer.name, element_num); } else { CustomData_duplicate_referenced_layer_anonymous( - custom_data, layer.type, &attribute_id.anonymous_id(), domain_num); + custom_data, layer.type, &attribute_id.anonymous_id(), element_num); } const CPPType *type = custom_data_type_to_cpp_type((eCustomDataType)layer.type); if (type == nullptr) { continue; } - GMutableSpan data{*type, layer.data, domain_num}; + GMutableSpan data{*type, layer.data, element_num}; return {GVMutableArray::ForSpan(data), domain_}; } return {}; } -bool CustomDataAttributeProvider::try_delete(GeometryComponent &component, - const AttributeIDRef &attribute_id) const +bool CustomDataAttributeProvider::try_delete(void *owner, const AttributeIDRef &attribute_id) const { - CustomData *custom_data = custom_data_access_.get_custom_data(component); + CustomData *custom_data = custom_data_access_.get_custom_data(owner); if (custom_data == nullptr) { return false; } - const int domain_num = component.attribute_domain_num(domain_); + const int element_num = custom_data_access_.get_element_num(owner); + ; for (const int i : IndexRange(custom_data->totlayer)) { const CustomDataLayer &layer = custom_data->layers[i]; if (this->type_is_supported((eCustomDataType)layer.type) && custom_data_layer_matches_attribute_id(layer, attribute_id)) { - CustomData_free_layer(custom_data, layer.type, domain_num, i); + CustomData_free_layer(custom_data, layer.type, element_num, i); return true; } } return false; } -bool CustomDataAttributeProvider::try_create(GeometryComponent &component, +bool CustomDataAttributeProvider::try_create(void *owner, const AttributeIDRef &attribute_id, const eAttrDomain domain, const eCustomDataType data_type, @@ -525,7 +489,7 @@ bool CustomDataAttributeProvider::try_create(GeometryComponent &component, if (!this->type_is_supported(data_type)) { return false; } - CustomData *custom_data = custom_data_access_.get_custom_data(component); + CustomData *custom_data = custom_data_access_.get_custom_data(owner); if (custom_data == nullptr) { return false; } @@ -534,16 +498,16 @@ bool CustomDataAttributeProvider::try_create(GeometryComponent &component, return false; } } - const int domain_num = component.attribute_domain_num(domain_); + const int element_num = custom_data_access_.get_element_num(owner); add_custom_data_layer_from_attribute_init( - attribute_id, *custom_data, data_type, domain_num, initializer); + attribute_id, *custom_data, data_type, element_num, initializer); return true; } -bool CustomDataAttributeProvider::foreach_attribute(const GeometryComponent &component, +bool CustomDataAttributeProvider::foreach_attribute(const void *owner, const AttributeForeachCallback callback) const { - const CustomData *custom_data = custom_data_access_.get_const_custom_data(component); + const CustomData *custom_data = custom_data_access_.get_const_custom_data(owner); if (custom_data == nullptr) { return true; } @@ -560,17 +524,17 @@ bool CustomDataAttributeProvider::foreach_attribute(const GeometryComponent &com return true; } -ReadAttributeLookup NamedLegacyCustomDataProvider::try_get_for_read( - const GeometryComponent &component, const AttributeIDRef &attribute_id) const +GAttributeReader NamedLegacyCustomDataProvider::try_get_for_read( + const void *owner, const AttributeIDRef &attribute_id) const { - const CustomData *custom_data = custom_data_access_.get_const_custom_data(component); + const CustomData *custom_data = custom_data_access_.get_const_custom_data(owner); if (custom_data == nullptr) { return {}; } for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) { if (layer.type == stored_type_) { if (custom_data_layer_matches_attribute_id(layer, attribute_id)) { - const int domain_num = component.attribute_domain_num(domain_); + const int domain_num = custom_data_access_.get_element_num(owner); return {as_read_attribute_(layer.data, domain_num), domain_}; } } @@ -578,36 +542,36 @@ ReadAttributeLookup NamedLegacyCustomDataProvider::try_get_for_read( return {}; } -WriteAttributeLookup NamedLegacyCustomDataProvider::try_get_for_write( - GeometryComponent &component, const AttributeIDRef &attribute_id) const +GAttributeWriter NamedLegacyCustomDataProvider::try_get_for_write( + void *owner, const AttributeIDRef &attribute_id) const { - CustomData *custom_data = custom_data_access_.get_custom_data(component); + CustomData *custom_data = custom_data_access_.get_custom_data(owner); if (custom_data == nullptr) { return {}; } for (CustomDataLayer &layer : MutableSpan(custom_data->layers, custom_data->totlayer)) { if (layer.type == stored_type_) { if (custom_data_layer_matches_attribute_id(layer, attribute_id)) { - const int domain_num = component.attribute_domain_num(domain_); + const int element_num = custom_data_access_.get_element_num(owner); void *data_old = layer.data; void *data_new = CustomData_duplicate_referenced_layer_named( - custom_data, stored_type_, layer.name, domain_num); + custom_data, stored_type_, layer.name, element_num); if (data_old != data_new) { if (custom_data_access_.update_custom_data_pointers) { - custom_data_access_.update_custom_data_pointers(component); + custom_data_access_.update_custom_data_pointers(owner); } } - return {as_write_attribute_(layer.data, domain_num), domain_}; + return {as_write_attribute_(layer.data, element_num), domain_}; } } } return {}; } -bool NamedLegacyCustomDataProvider::try_delete(GeometryComponent &component, +bool NamedLegacyCustomDataProvider::try_delete(void *owner, const AttributeIDRef &attribute_id) const { - CustomData *custom_data = custom_data_access_.get_custom_data(component); + CustomData *custom_data = custom_data_access_.get_custom_data(owner); if (custom_data == nullptr) { return false; } @@ -615,10 +579,10 @@ bool NamedLegacyCustomDataProvider::try_delete(GeometryComponent &component, const CustomDataLayer &layer = custom_data->layers[i]; if (layer.type == stored_type_) { if (custom_data_layer_matches_attribute_id(layer, attribute_id)) { - const int domain_num = component.attribute_domain_num(domain_); - CustomData_free_layer(custom_data, stored_type_, domain_num, i); + const int element_num = custom_data_access_.get_element_num(owner); + CustomData_free_layer(custom_data, stored_type_, element_num, i); if (custom_data_access_.update_custom_data_pointers) { - custom_data_access_.update_custom_data_pointers(component); + custom_data_access_.update_custom_data_pointers(owner); } return true; } @@ -628,9 +592,9 @@ bool NamedLegacyCustomDataProvider::try_delete(GeometryComponent &component, } bool NamedLegacyCustomDataProvider::foreach_attribute( - const GeometryComponent &component, const AttributeForeachCallback callback) const + const void *owner, const AttributeForeachCallback callback) const { - const CustomData *custom_data = custom_data_access_.get_const_custom_data(component); + const CustomData *custom_data = custom_data_access_.get_const_custom_data(owner); if (custom_data == nullptr) { return true; } @@ -804,275 +768,10 @@ void CustomDataAttributes::reorder(Span new_order) CustomData_update_typemap(&data); } -} // namespace blender::bke - /* -------------------------------------------------------------------- */ /** \name Geometry Component * \{ */ -const blender::bke::ComponentAttributeProviders *GeometryComponent::get_attribute_providers() const -{ - return nullptr; -} - -bool GeometryComponent::attribute_domain_supported(const eAttrDomain domain) const -{ - using namespace blender::bke; - const ComponentAttributeProviders *providers = this->get_attribute_providers(); - if (providers == nullptr) { - return false; - } - return providers->supported_domains().contains(domain); -} - -int GeometryComponent::attribute_domain_num(const eAttrDomain UNUSED(domain)) const -{ - return 0; -} - -bool GeometryComponent::attribute_is_builtin(const blender::StringRef attribute_name) const -{ - using namespace blender::bke; - const ComponentAttributeProviders *providers = this->get_attribute_providers(); - if (providers == nullptr) { - return false; - } - return providers->builtin_attribute_providers().contains_as(attribute_name); -} - -bool GeometryComponent::attribute_is_builtin(const AttributeIDRef &attribute_id) const -{ - /* Anonymous attributes cannot be built-in. */ - return attribute_id.is_named() && this->attribute_is_builtin(attribute_id.name()); -} - -blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read( - const AttributeIDRef &attribute_id) const -{ - using namespace blender::bke; - const ComponentAttributeProviders *providers = this->get_attribute_providers(); - if (providers == nullptr) { - return {}; - } - if (attribute_id.is_named()) { - const BuiltinAttributeProvider *builtin_provider = - providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr); - if (builtin_provider != nullptr) { - return {builtin_provider->try_get_for_read(*this), builtin_provider->domain()}; - } - } - for (const DynamicAttributesProvider *dynamic_provider : - providers->dynamic_attribute_providers()) { - ReadAttributeLookup attribute = dynamic_provider->try_get_for_read(*this, attribute_id); - if (attribute) { - return attribute; - } - } - return {}; -} - -blender::GVArray GeometryComponent::attribute_try_adapt_domain_impl( - const blender::GVArray &varray, - const eAttrDomain from_domain, - const eAttrDomain to_domain) const -{ - if (from_domain == to_domain) { - return varray; - } - return {}; -} - -blender::bke::WriteAttributeLookup GeometryComponent::attribute_try_get_for_write( - const AttributeIDRef &attribute_id) -{ - using namespace blender::bke; - const ComponentAttributeProviders *providers = this->get_attribute_providers(); - if (providers == nullptr) { - return {}; - } - if (attribute_id.is_named()) { - const BuiltinAttributeProvider *builtin_provider = - providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr); - if (builtin_provider != nullptr) { - return builtin_provider->try_get_for_write(*this); - } - } - for (const DynamicAttributesProvider *dynamic_provider : - providers->dynamic_attribute_providers()) { - WriteAttributeLookup attribute = dynamic_provider->try_get_for_write(*this, attribute_id); - if (attribute) { - return attribute; - } - } - return {}; -} - -bool GeometryComponent::attribute_try_delete(const AttributeIDRef &attribute_id) -{ - using namespace blender::bke; - const ComponentAttributeProviders *providers = this->get_attribute_providers(); - if (providers == nullptr) { - return {}; - } - if (attribute_id.is_named()) { - const BuiltinAttributeProvider *builtin_provider = - providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr); - if (builtin_provider != nullptr) { - return builtin_provider->try_delete(*this); - } - } - bool success = false; - for (const DynamicAttributesProvider *dynamic_provider : - providers->dynamic_attribute_providers()) { - success = dynamic_provider->try_delete(*this, attribute_id) || success; - } - return success; -} - -void GeometryComponent::attributes_remove_anonymous() -{ - using namespace blender; - Vector anonymous_ids; - for (const AttributeIDRef &id : this->attribute_ids()) { - if (id.is_anonymous()) { - anonymous_ids.append(&id.anonymous_id()); - } - } - - while (!anonymous_ids.is_empty()) { - this->attribute_try_delete(anonymous_ids.pop_last()); - } -} - -bool GeometryComponent::attribute_try_create(const AttributeIDRef &attribute_id, - const eAttrDomain domain, - const eCustomDataType data_type, - const AttributeInit &initializer) -{ - using namespace blender::bke; - if (!attribute_id) { - return false; - } - const ComponentAttributeProviders *providers = this->get_attribute_providers(); - if (providers == nullptr) { - return false; - } - if (this->attribute_exists(attribute_id)) { - return false; - } - if (attribute_id.is_named()) { - const BuiltinAttributeProvider *builtin_provider = - providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr); - if (builtin_provider != nullptr) { - if (builtin_provider->domain() != domain) { - return false; - } - if (builtin_provider->data_type() != data_type) { - return false; - } - return builtin_provider->try_create(*this, initializer); - } - } - for (const DynamicAttributesProvider *dynamic_provider : - providers->dynamic_attribute_providers()) { - if (dynamic_provider->try_create(*this, attribute_id, domain, data_type, initializer)) { - return true; - } - } - return false; -} - -bool GeometryComponent::attribute_try_create_builtin(const blender::StringRef attribute_name, - const AttributeInit &initializer) -{ - using namespace blender::bke; - if (attribute_name.is_empty()) { - return false; - } - const ComponentAttributeProviders *providers = this->get_attribute_providers(); - if (providers == nullptr) { - return false; - } - const BuiltinAttributeProvider *builtin_provider = - providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr); - if (builtin_provider == nullptr) { - return false; - } - return builtin_provider->try_create(*this, initializer); -} - -Set GeometryComponent::attribute_ids() const -{ - Set attributes; - this->attribute_foreach( - [&](const AttributeIDRef &attribute_id, const AttributeMetaData &UNUSED(meta_data)) { - attributes.add(attribute_id); - return true; - }); - return attributes; -} - -bool GeometryComponent::attribute_foreach(const AttributeForeachCallback callback) const -{ - using namespace blender::bke; - const ComponentAttributeProviders *providers = this->get_attribute_providers(); - if (providers == nullptr) { - return true; - } - - /* Keep track handled attribute names to make sure that we do not return the same name twice. */ - Set handled_attribute_names; - - for (const BuiltinAttributeProvider *provider : - providers->builtin_attribute_providers().values()) { - if (provider->exists(*this)) { - AttributeMetaData meta_data{provider->domain(), provider->data_type()}; - if (!callback(provider->name(), meta_data)) { - return false; - } - handled_attribute_names.add_new(provider->name()); - } - } - for (const DynamicAttributesProvider *provider : providers->dynamic_attribute_providers()) { - const bool continue_loop = provider->foreach_attribute( - *this, [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { - if (attribute_id.is_anonymous() || handled_attribute_names.add(attribute_id.name())) { - return callback(attribute_id, meta_data); - } - return true; - }); - if (!continue_loop) { - return false; - } - } - - return true; -} - -bool GeometryComponent::attribute_exists(const AttributeIDRef &attribute_id) const -{ - blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id); - if (attribute) { - return true; - } - return false; -} - -std::optional GeometryComponent::attribute_get_meta_data( - const AttributeIDRef &attribute_id) const -{ - std::optional result{std::nullopt}; - this->attribute_foreach( - [&](const AttributeIDRef ¤t_attribute_id, const AttributeMetaData &meta_data) { - if (attribute_id == current_attribute_id) { - result = meta_data; - return false; - } - return true; - }); - return result; -} - static blender::GVArray try_adapt_data_type(blender::GVArray varray, const blender::CPPType &to_type) { @@ -1081,294 +780,6 @@ static blender::GVArray try_adapt_data_type(blender::GVArray varray, return conversions.try_convert(std::move(varray), to_type); } -blender::GVArray GeometryComponent::attribute_try_get_for_read( - const AttributeIDRef &attribute_id, - const eAttrDomain domain, - const eCustomDataType data_type) const -{ - blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id); - if (!attribute) { - return {}; - } - - blender::GVArray varray = std::move(attribute.varray); - if (!ELEM(domain, ATTR_DOMAIN_AUTO, attribute.domain)) { - varray = this->attribute_try_adapt_domain(std::move(varray), attribute.domain, domain); - if (!varray) { - return {}; - } - } - - const blender::CPPType *cpp_type = blender::bke::custom_data_type_to_cpp_type(data_type); - BLI_assert(cpp_type != nullptr); - if (varray.type() != *cpp_type) { - varray = try_adapt_data_type(std::move(varray), *cpp_type); - if (!varray) { - return {}; - } - } - - return varray; -} - -blender::GVArray GeometryComponent::attribute_try_get_for_read(const AttributeIDRef &attribute_id, - const eAttrDomain domain) const -{ - if (!this->attribute_domain_supported(domain)) { - return {}; - } - - blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id); - if (!attribute) { - return {}; - } - - if (attribute.domain != domain) { - return this->attribute_try_adapt_domain(std::move(attribute.varray), attribute.domain, domain); - } - - return std::move(attribute.varray); -} - -blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read( - const AttributeIDRef &attribute_id, const eCustomDataType data_type) const -{ - blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id); - if (!attribute) { - return {}; - } - const blender::CPPType *type = blender::bke::custom_data_type_to_cpp_type(data_type); - BLI_assert(type != nullptr); - if (attribute.varray.type() == *type) { - return attribute; - } - const blender::bke::DataTypeConversions &conversions = - blender::bke::get_implicit_type_conversions(); - return {conversions.try_convert(std::move(attribute.varray), *type), attribute.domain}; -} - -blender::GVArray GeometryComponent::attribute_get_for_read(const AttributeIDRef &attribute_id, - const eAttrDomain domain, - const eCustomDataType data_type, - const void *default_value) const -{ - blender::GVArray varray = this->attribute_try_get_for_read(attribute_id, domain, data_type); - if (varray) { - return varray; - } - const blender::CPPType *type = blender::bke::custom_data_type_to_cpp_type(data_type); - if (default_value == nullptr) { - default_value = type->default_value(); - } - const int domain_num = this->attribute_domain_num(domain); - return blender::GVArray::ForSingle(*type, domain_num, default_value); -} - -class GVMutableAttribute_For_OutputAttribute : public blender::GVArrayImpl_For_GSpan { - public: - GeometryComponent *component; - std::string attribute_name; - blender::bke::WeakAnonymousAttributeID anonymous_attribute_id; - - GVMutableAttribute_For_OutputAttribute(GMutableSpan data, - GeometryComponent &component, - const AttributeIDRef &attribute_id) - : blender::GVArrayImpl_For_GSpan(data), component(&component) - { - if (attribute_id.is_named()) { - this->attribute_name = attribute_id.name(); - } - else { - const AnonymousAttributeID *anonymous_id = &attribute_id.anonymous_id(); - BKE_anonymous_attribute_id_increment_weak(anonymous_id); - this->anonymous_attribute_id = blender::bke::WeakAnonymousAttributeID{anonymous_id}; - } - } - - ~GVMutableAttribute_For_OutputAttribute() override - { - type_->destruct_n(data_, size_); - MEM_freeN(data_); - } -}; - -static void save_output_attribute(OutputAttribute &output_attribute) -{ - using namespace blender; - using namespace blender::fn; - using namespace blender::bke; - - GVMutableAttribute_For_OutputAttribute &varray = - dynamic_cast( - *output_attribute.varray().get_implementation()); - - GeometryComponent &component = *varray.component; - AttributeIDRef attribute_id; - if (!varray.attribute_name.empty()) { - attribute_id = varray.attribute_name; - } - else { - attribute_id = varray.anonymous_attribute_id.extract(); - } - const eAttrDomain domain = output_attribute.domain(); - const eCustomDataType data_type = output_attribute.custom_data_type(); - const CPPType &cpp_type = output_attribute.cpp_type(); - - component.attribute_try_delete(attribute_id); - if (!component.attribute_try_create(attribute_id, domain, data_type, AttributeInitDefault())) { - if (!varray.attribute_name.empty()) { - CLOG_WARN(&LOG, - "Could not create the '%s' attribute with type '%s'.", - varray.attribute_name.c_str(), - cpp_type.name().c_str()); - } - return; - } - WriteAttributeLookup write_attribute = component.attribute_try_get_for_write(attribute_id); - BUFFER_FOR_CPP_TYPE_VALUE(varray.type(), buffer); - for (const int i : IndexRange(varray.size())) { - varray.get(i, buffer); - write_attribute.varray.set_by_relocate(i, buffer); - } - if (write_attribute.tag_modified_fn) { - write_attribute.tag_modified_fn(); - } -} - -static std::function get_simple_output_attribute_save_method( - const blender::bke::WriteAttributeLookup &attribute) -{ - if (!attribute.tag_modified_fn) { - return {}; - } - return [tag_modified_fn = attribute.tag_modified_fn](OutputAttribute &UNUSED(attribute)) { - tag_modified_fn(); - }; -} - -static OutputAttribute create_output_attribute(GeometryComponent &component, - const AttributeIDRef &attribute_id, - const eAttrDomain domain, - const eCustomDataType data_type, - const bool ignore_old_values, - const void *default_value) -{ - using namespace blender; - using namespace blender::fn; - using namespace blender::bke; - - if (!attribute_id) { - return {}; - } - - const CPPType *cpp_type = custom_data_type_to_cpp_type(data_type); - BLI_assert(cpp_type != nullptr); - const DataTypeConversions &conversions = get_implicit_type_conversions(); - - if (component.attribute_is_builtin(attribute_id)) { - const StringRef attribute_name = attribute_id.name(); - WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_name); - if (!attribute) { - if (default_value) { - const int64_t domain_num = component.attribute_domain_num(domain); - component.attribute_try_create_builtin( - attribute_name, - AttributeInitVArray(GVArray::ForSingleRef(*cpp_type, domain_num, default_value))); - } - else { - component.attribute_try_create_builtin(attribute_name, AttributeInitDefault()); - } - attribute = component.attribute_try_get_for_write(attribute_name); - if (!attribute) { - /* Builtin attribute does not exist and can't be created. */ - return {}; - } - } - if (attribute.domain != domain) { - /* Builtin attribute is on different domain. */ - return {}; - } - GVMutableArray varray = std::move(attribute.varray); - if (varray.type() == *cpp_type) { - /* Builtin attribute matches exactly. */ - return OutputAttribute(std::move(varray), - domain, - get_simple_output_attribute_save_method(attribute), - ignore_old_values); - } - /* Builtin attribute is on the same domain but has a different data type. */ - varray = conversions.try_convert(std::move(varray), *cpp_type); - return OutputAttribute(std::move(varray), - domain, - get_simple_output_attribute_save_method(attribute), - ignore_old_values); - } - - const int domain_num = component.attribute_domain_num(domain); - - WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_id); - if (!attribute) { - if (default_value) { - component.attribute_try_create( - attribute_id, - domain, - data_type, - AttributeInitVArray(GVArray::ForSingleRef(*cpp_type, domain_num, default_value))); - } - else { - component.attribute_try_create(attribute_id, domain, data_type, AttributeInitDefault()); - } - - attribute = component.attribute_try_get_for_write(attribute_id); - if (!attribute) { - /* Can't create the attribute. */ - return {}; - } - } - if (attribute.domain == domain && attribute.varray.type() == *cpp_type) { - /* Existing generic attribute matches exactly. */ - - return OutputAttribute(std::move(attribute.varray), - domain, - get_simple_output_attribute_save_method(attribute), - ignore_old_values); - } - - /* Allocate a new array that lives next to the existing attribute. It will overwrite the existing - * attribute after processing is done. */ - void *data = MEM_mallocN_aligned(cpp_type->size() * domain_num, cpp_type->alignment(), __func__); - if (ignore_old_values) { - /* This does nothing for trivially constructible types, but is necessary for correctness. */ - cpp_type->default_construct_n(data, domain); - } - else { - /* Fill the temporary array with values from the existing attribute. */ - GVArray old_varray = component.attribute_get_for_read( - attribute_id, domain, data_type, default_value); - old_varray.materialize_to_uninitialized(IndexRange(domain_num), data); - } - GVMutableArray varray = GVMutableArray::For( - GMutableSpan{*cpp_type, data, domain_num}, component, attribute_id); - - return OutputAttribute(std::move(varray), domain, save_output_attribute, true); -} - -OutputAttribute GeometryComponent::attribute_try_get_for_output(const AttributeIDRef &attribute_id, - const eAttrDomain domain, - const eCustomDataType data_type, - const void *default_value) -{ - return create_output_attribute(*this, attribute_id, domain, data_type, false, default_value); -} - -OutputAttribute GeometryComponent::attribute_try_get_for_output_only( - const AttributeIDRef &attribute_id, const eAttrDomain domain, const eCustomDataType data_type) -{ - return create_output_attribute(*this, attribute_id, domain, data_type, true, nullptr); -} - -namespace blender::bke { - GVArray GeometryFieldInput::get_varray_for_context(const fn::FieldContext &context, IndexMask mask, ResourceScope &UNUSED(scope)) const @@ -1387,7 +798,10 @@ GVArray AttributeFieldInput::get_varray_for_context(const GeometryComponent &com IndexMask UNUSED(mask)) const { const eCustomDataType data_type = cpp_type_to_custom_data_type(*type_); - return component.attribute_try_get_for_read(name_, domain, data_type); + if (auto attributes = component.attributes()) { + return attributes->lookup(name_, domain, data_type); + } + return {}; } std::string AttributeFieldInput::socket_inspection_name() const @@ -1427,10 +841,10 @@ GVArray IDAttributeFieldInput::get_varray_for_context(const GeometryComponent &c { const StringRef name = get_random_id_attribute_name(domain); - GVArray attribute = component.attribute_try_get_for_read(name, domain, CD_PROP_INT32); - if (attribute) { - BLI_assert(attribute.size() == component.attribute_domain_num(domain)); - return attribute; + if (auto attributes = component.attributes()) { + if (GVArray attribute = attributes->lookup(name, domain, CD_PROP_INT32)) { + return attribute; + } } /* Use the index as the fallback if no random ID attribute exists. */ @@ -1459,7 +873,7 @@ GVArray AnonymousAttributeFieldInput::get_varray_for_context(const GeometryCompo IndexMask UNUSED(mask)) const { const eCustomDataType data_type = cpp_type_to_custom_data_type(*type_); - return component.attribute_try_get_for_read(anonymous_id_.get(), domain, data_type); + return component.attributes()->lookup(anonymous_id_.get(), domain, data_type); } std::string AnonymousAttributeFieldInput::socket_inspection_name() const @@ -1483,6 +897,120 @@ bool AnonymousAttributeFieldInput::is_equal_to(const fn::FieldNode &other) const return false; } +GVArray AttributeAccessor::lookup(const AttributeIDRef &attribute_id, + const std::optional domain, + const std::optional data_type) const +{ + GAttributeReader attribute = this->lookup(attribute_id); + if (!attribute) { + return {}; + } + GVArray varray = std::move(attribute.varray); + if (domain.has_value()) { + if (attribute.domain != domain) { + varray = this->adapt_domain(varray, attribute.domain, *domain); + if (!varray) { + return {}; + } + } + } + if (data_type.has_value()) { + const CPPType &type = *custom_data_type_to_cpp_type(*data_type); + if (varray.type() != type) { + varray = try_adapt_data_type(std::move(varray), type); + if (!varray) { + return {}; + } + } + } + return varray; +} + +GVArray AttributeAccessor::lookup_or_default(const AttributeIDRef &attribute_id, + const eAttrDomain domain, + const eCustomDataType data_type, + const void *default_value) const +{ + GVArray varray = this->lookup(attribute_id, domain, data_type); + if (varray) { + return varray; + } + const CPPType &type = *custom_data_type_to_cpp_type(data_type); + const int64_t domain_size = this->domain_size(domain); + if (default_value == nullptr) { + return GVArray::ForSingleRef(type, domain_size, type.default_value()); + } + return GVArray::ForSingle(type, domain_size, default_value); +} + +Set AttributeAccessor::all_ids() const +{ + Set ids; + this->for_all( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData & /* meta_data */) { + ids.add(attribute_id); + return true; + }); + return ids; +} + +void MutableAttributeAccessor::remove_anonymous() +{ + Vector anonymous_ids; + for (const AttributeIDRef &id : this->all_ids()) { + if (id.is_anonymous()) { + anonymous_ids.append(&id.anonymous_id()); + } + } + + while (!anonymous_ids.is_empty()) { + this->remove(anonymous_ids.pop_last()); + } +} + +GAttributeWriter MutableAttributeAccessor::lookup_or_add_for_write( + const AttributeIDRef &attribute_id, + const eAttrDomain domain, + const eCustomDataType data_type, + const AttributeInit &initializer) +{ + std::optional meta_data = this->lookup_meta_data(attribute_id); + if (meta_data.has_value()) { + if (meta_data->domain == domain && meta_data->data_type == data_type) { + return this->lookup_for_write(attribute_id); + } + return {}; + } + if (this->add(attribute_id, domain, data_type, initializer)) { + return this->lookup_for_write(attribute_id); + } + return {}; +} + +GSpanAttributeWriter MutableAttributeAccessor::lookup_or_add_for_write_span( + const AttributeIDRef &attribute_id, + const eAttrDomain domain, + const eCustomDataType data_type, + const AttributeInit &initializer) +{ + GAttributeWriter attribute = this->lookup_or_add_for_write( + attribute_id, domain, data_type, initializer); + if (attribute) { + return GSpanAttributeWriter{std::move(attribute), true}; + } + return {}; +} + +GSpanAttributeWriter MutableAttributeAccessor::lookup_or_add_for_write_only_span( + const AttributeIDRef &attribute_id, const eAttrDomain domain, const eCustomDataType data_type) +{ + GAttributeWriter attribute = this->lookup_or_add_for_write(attribute_id, domain, data_type); + if (attribute) { + return GSpanAttributeWriter{std::move(attribute), false}; + } + return {}; +} + } // namespace blender::bke /** \} */ diff --git a/source/blender/blenkernel/intern/attribute_access_intern.hh b/source/blender/blenkernel/intern/attribute_access_intern.hh index ac43754dd1a..17432fa2726 100644 --- a/source/blender/blenkernel/intern/attribute_access_intern.hh +++ b/source/blender/blenkernel/intern/attribute_access_intern.hh @@ -15,12 +15,14 @@ namespace blender::bke { * components in a generic way. */ struct CustomDataAccessInfo { - using CustomDataGetter = CustomData *(*)(GeometryComponent &component); - using ConstCustomDataGetter = const CustomData *(*)(const GeometryComponent &component); - using UpdateCustomDataPointers = void (*)(GeometryComponent &component); + using CustomDataGetter = CustomData *(*)(void *owner); + using ConstCustomDataGetter = const CustomData *(*)(const void *owner); + using GetElementNum = int (*)(const void *owner); + using UpdateCustomDataPointers = void (*)(void *owner); CustomDataGetter get_custom_data; ConstCustomDataGetter get_const_custom_data; + GetElementNum get_element_num; UpdateCustomDataPointers update_custom_data_pointers; }; @@ -69,12 +71,11 @@ class BuiltinAttributeProvider { { } - virtual GVArray try_get_for_read(const GeometryComponent &component) const = 0; - virtual WriteAttributeLookup try_get_for_write(GeometryComponent &component) const = 0; - virtual bool try_delete(GeometryComponent &component) const = 0; - virtual bool try_create(GeometryComponent &UNUSED(component), - const AttributeInit &UNUSED(initializer)) const = 0; - virtual bool exists(const GeometryComponent &component) const = 0; + virtual GVArray try_get_for_read(const void *owner) const = 0; + virtual GAttributeWriter try_get_for_write(void *owner) const = 0; + virtual bool try_delete(void *owner) const = 0; + virtual bool try_create(void *onwer, const AttributeInit &initializer) const = 0; + virtual bool exists(const void *owner) const = 0; StringRefNull name() const { @@ -98,23 +99,23 @@ class BuiltinAttributeProvider { */ class DynamicAttributesProvider { public: - virtual ReadAttributeLookup try_get_for_read(const GeometryComponent &component, - const AttributeIDRef &attribute_id) const = 0; - virtual WriteAttributeLookup try_get_for_write(GeometryComponent &component, - const AttributeIDRef &attribute_id) const = 0; - virtual bool try_delete(GeometryComponent &component, - const AttributeIDRef &attribute_id) const = 0; - virtual bool try_create(GeometryComponent &UNUSED(component), - const AttributeIDRef &UNUSED(attribute_id), - const eAttrDomain UNUSED(domain), - const eCustomDataType UNUSED(data_type), - const AttributeInit &UNUSED(initializer)) const + virtual GAttributeReader try_get_for_read(const void *owner, + const AttributeIDRef &attribute_id) const = 0; + virtual GAttributeWriter try_get_for_write(void *owner, + const AttributeIDRef &attribute_id) const = 0; + virtual bool try_delete(void *owner, const AttributeIDRef &attribute_id) const = 0; + virtual bool try_create(void *owner, + const AttributeIDRef &attribute_id, + const eAttrDomain domain, + const eCustomDataType data_type, + const AttributeInit &initializer) const { + UNUSED_VARS(owner, attribute_id, domain, data_type, initializer); /* Some providers should not create new attributes. */ return false; }; - virtual bool foreach_attribute(const GeometryComponent &component, + virtual bool foreach_attribute(const void *owner, const AttributeForeachCallback callback) const = 0; virtual void foreach_domain(const FunctionRef callback) const = 0; }; @@ -138,22 +139,20 @@ class CustomDataAttributeProvider final : public DynamicAttributesProvider { { } - ReadAttributeLookup try_get_for_read(const GeometryComponent &component, - const AttributeIDRef &attribute_id) const final; + GAttributeReader try_get_for_read(const void *owner, + const AttributeIDRef &attribute_id) const final; - WriteAttributeLookup try_get_for_write(GeometryComponent &component, - const AttributeIDRef &attribute_id) const final; + GAttributeWriter try_get_for_write(void *owner, const AttributeIDRef &attribute_id) const final; - bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final; + bool try_delete(void *owner, const AttributeIDRef &attribute_id) const final; - bool try_create(GeometryComponent &component, + bool try_create(void *owner, const AttributeIDRef &attribute_id, eAttrDomain domain, const eCustomDataType data_type, const AttributeInit &initializer) const final; - bool foreach_attribute(const GeometryComponent &component, - const AttributeForeachCallback callback) const final; + bool foreach_attribute(const void *owner, const AttributeForeachCallback callback) const final; void foreach_domain(const FunctionRef callback) const final { @@ -197,13 +196,11 @@ class NamedLegacyCustomDataProvider final : public DynamicAttributesProvider { { } - ReadAttributeLookup try_get_for_read(const GeometryComponent &component, - const AttributeIDRef &attribute_id) const final; - WriteAttributeLookup try_get_for_write(GeometryComponent &component, - const AttributeIDRef &attribute_id) const final; - bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final; - bool foreach_attribute(const GeometryComponent &component, - const AttributeForeachCallback callback) const final; + GAttributeReader try_get_for_read(const void *owner, + const AttributeIDRef &attribute_id) const final; + GAttributeWriter try_get_for_write(void *owner, const AttributeIDRef &attribute_id) const final; + bool try_delete(void *owner, const AttributeIDRef &attribute_id) const final; + bool foreach_attribute(const void *owner, const AttributeForeachCallback callback) const final; void foreach_domain(const FunctionRef callback) const final; }; @@ -226,10 +223,10 @@ template GVMutableArray make_array_write_attribute(void *data, const * if the stored type is the same as the attribute type. */ class BuiltinCustomDataLayerProvider final : public BuiltinAttributeProvider { - using AsReadAttribute = GVArray (*)(const void *data, int domain_num); - using AsWriteAttribute = GVMutableArray (*)(void *data, int domain_num); - using UpdateOnRead = void (*)(const GeometryComponent &component); - using UpdateOnWrite = void (*)(GeometryComponent &component); + using AsReadAttribute = GVArray (*)(const void *data, int element_num); + using AsWriteAttribute = GVMutableArray (*)(void *data, int element_num); + using UpdateOnRead = void (*)(const void *owner); + using UpdateOnWrite = void (*)(void *owner); const eCustomDataType stored_type_; const CustomDataAccessInfo custom_data_access_; const AsReadAttribute as_read_attribute_; @@ -260,11 +257,11 @@ class BuiltinCustomDataLayerProvider final : public BuiltinAttributeProvider { { } - GVArray try_get_for_read(const GeometryComponent &component) const final; - WriteAttributeLookup try_get_for_write(GeometryComponent &component) const final; - bool try_delete(GeometryComponent &component) const final; - bool try_create(GeometryComponent &component, const AttributeInit &initializer) const final; - bool exists(const GeometryComponent &component) const final; + GVArray try_get_for_read(const void *owner) const final; + GAttributeWriter try_get_for_write(void *owner) const final; + bool try_delete(void *owner) const final; + bool try_create(void *owner, const AttributeInit &initializer) const final; + bool exists(const void *owner) const final; }; /** @@ -321,4 +318,183 @@ class ComponentAttributeProviders { } }; +namespace attribute_accessor_functions { + +template +inline bool is_builtin(const void *UNUSED(owner), const AttributeIDRef &attribute_id) +{ + if (!attribute_id.is_named()) { + return false; + } + const StringRef name = attribute_id.name(); + return providers.builtin_attribute_providers().contains_as(name); +} + +template +inline GAttributeReader lookup(const void *owner, const AttributeIDRef &attribute_id) +{ + if (attribute_id.is_named()) { + const StringRef name = attribute_id.name(); + if (const BuiltinAttributeProvider *provider = + providers.builtin_attribute_providers().lookup_default_as(name, nullptr)) { + return {provider->try_get_for_read(owner), provider->domain()}; + } + } + for (const DynamicAttributesProvider *provider : providers.dynamic_attribute_providers()) { + GAttributeReader attribute = provider->try_get_for_read(owner, attribute_id); + if (attribute) { + return attribute; + } + } + return {}; +} + +template +inline bool for_all(const void *owner, + FunctionRef fn) +{ + Set handled_attribute_ids; + for (const BuiltinAttributeProvider *provider : + providers.builtin_attribute_providers().values()) { + if (provider->exists(owner)) { + AttributeMetaData meta_data{provider->domain(), provider->data_type()}; + if (!fn(provider->name(), meta_data)) { + return false; + } + handled_attribute_ids.add_new(provider->name()); + } + } + for (const DynamicAttributesProvider *provider : providers.dynamic_attribute_providers()) { + const bool continue_loop = provider->foreach_attribute( + owner, [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + if (handled_attribute_ids.add(attribute_id)) { + return fn(attribute_id, meta_data); + } + return true; + }); + if (!continue_loop) { + return false; + } + } + return true; +} + +template +inline bool contains(const void *owner, const blender::bke::AttributeIDRef &attribute_id) +{ + bool found = false; + for_all( + owner, + [&](const AttributeIDRef &other_attribute_id, const AttributeMetaData & /* meta_data */) { + if (attribute_id == other_attribute_id) { + found = true; + return false; + } + return true; + }); + return found; +} + +template +inline std::optional lookup_meta_data(const void *owner, + const AttributeIDRef &attribute_id) +{ + std::optional meta_data; + for_all( + owner, + [&](const AttributeIDRef &other_attribute_id, const AttributeMetaData &other_meta_data) { + if (attribute_id == other_attribute_id) { + meta_data = other_meta_data; + return false; + } + return true; + }); + return meta_data; +} + +template +inline GAttributeWriter lookup_for_write(void *owner, const AttributeIDRef &attribute_id) +{ + if (attribute_id.is_named()) { + const StringRef name = attribute_id.name(); + if (const BuiltinAttributeProvider *provider = + providers.builtin_attribute_providers().lookup_default_as(name, nullptr)) { + return provider->try_get_for_write(owner); + } + } + for (const DynamicAttributesProvider *provider : providers.dynamic_attribute_providers()) { + GAttributeWriter attribute = provider->try_get_for_write(owner, attribute_id); + if (attribute) { + return attribute; + } + } + return {}; +} + +template +inline bool remove(void *owner, const AttributeIDRef &attribute_id) +{ + if (attribute_id.is_named()) { + const StringRef name = attribute_id.name(); + if (const BuiltinAttributeProvider *provider = + providers.builtin_attribute_providers().lookup_default_as(name, nullptr)) { + return provider->try_delete(owner); + } + } + bool success = false; + for (const DynamicAttributesProvider *provider : providers.dynamic_attribute_providers()) { + success = provider->try_delete(owner, attribute_id) || success; + } + return success; +} + +template +inline bool add(void *owner, + const AttributeIDRef &attribute_id, + eAttrDomain domain, + eCustomDataType data_type, + const AttributeInit &initializer) +{ + if (contains(owner, attribute_id)) { + return false; + } + if (attribute_id.is_named()) { + const StringRef name = attribute_id.name(); + if (const BuiltinAttributeProvider *provider = + providers.builtin_attribute_providers().lookup_default_as(name, nullptr)) { + if (provider->domain() != domain) { + return false; + } + if (provider->data_type() != data_type) { + return false; + } + return provider->try_create(owner, initializer); + } + } + for (const DynamicAttributesProvider *provider : providers.dynamic_attribute_providers()) { + if (provider->try_create(owner, attribute_id, domain, data_type, initializer)) { + return true; + } + } + return false; +} + +template +inline AttributeAccessorFunctions accessor_functions_for_providers() +{ + return AttributeAccessorFunctions{contains, + lookup_meta_data, + nullptr, + nullptr, + is_builtin, + lookup, + nullptr, + for_all, + lookup_for_write, + remove, + add}; +} + +} // namespace attribute_accessor_functions + } // namespace blender::bke diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc index c01a184c6f9..424fa311dc7 100644 --- a/source/blender/blenkernel/intern/curve_eval.cc +++ b/source/blender/blenkernel/intern/curve_eval.cc @@ -31,9 +31,7 @@ using blender::VArray; using blender::VArraySpan; using blender::Vector; using blender::bke::AttributeIDRef; -using blender::bke::OutputAttribute; -using blender::bke::OutputAttribute_Typed; -using blender::bke::ReadAttributeLookup; +using blender::bke::AttributeMetaData; blender::Span CurveEval::splines() const { @@ -345,32 +343,31 @@ std::unique_ptr curve_eval_from_dna_curve(const Curve &dna_curve) return curve_eval_from_dna_curve(dna_curve, *BKE_curve_nurbs_get_for_read(&dna_curve)); } -static void copy_attributes_between_components(const GeometryComponent &src_component, - GeometryComponent &dst_component, - Span skip) +static void copy_attributes_between_components( + const blender::bke::AttributeAccessor &src_attributes, + blender::bke::MutableAttributeAccessor &dst_attributes, + Span skip) { - src_component.attribute_foreach( - [&](const AttributeIDRef &id, const AttributeMetaData meta_data) { - if (id.is_named() && skip.contains(id.name())) { - return true; - } + src_attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { + if (id.is_named() && skip.contains(id.name())) { + return true; + } - GVArray src_attribute = src_component.attribute_try_get_for_read( - id, meta_data.domain, meta_data.data_type); - if (!src_attribute) { - return true; - } - GVArraySpan src_attribute_data{src_attribute}; + GVArray src_attribute = src_attributes.lookup(id, meta_data.domain, meta_data.data_type); + if (!src_attribute) { + return true; + } + GVArraySpan src_attribute_data{src_attribute}; - OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( - id, meta_data.domain, meta_data.data_type); - if (!dst_attribute) { - return true; - } - dst_attribute.varray().set_all(src_attribute_data.data()); - dst_attribute.save(); - return true; - }); + blender::bke::GAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write( + id, meta_data.domain, meta_data.data_type); + if (!dst_attribute) { + return true; + } + dst_attribute.varray.set_all(src_attribute_data.data()); + dst_attribute.finish(); + return true; + }); } std::unique_ptr curves_to_curve_eval(const Curves &curves_id) @@ -379,21 +376,22 @@ std::unique_ptr curves_to_curve_eval(const Curves &curves_id) src_component.replace(&const_cast(curves_id), GeometryOwnershipType::ReadOnly); const blender::bke::CurvesGeometry &curves = blender::bke::CurvesGeometry::wrap( curves_id.geometry); + const blender::bke::AttributeAccessor src_attributes = curves.attributes(); VArray resolution = curves.resolution(); VArray normal_mode = curves.normal_mode(); VArraySpan nurbs_weights{ - src_component.attribute_get_for_read("nurbs_weight", ATTR_DOMAIN_POINT, 0.0f)}; + src_attributes.lookup_or_default("nurbs_weight", ATTR_DOMAIN_POINT, 0.0f)}; VArraySpan nurbs_orders{ - src_component.attribute_get_for_read("nurbs_order", ATTR_DOMAIN_CURVE, 4)}; + src_attributes.lookup_or_default("nurbs_order", ATTR_DOMAIN_CURVE, 4)}; VArraySpan nurbs_knots_modes{ - src_component.attribute_get_for_read("knots_mode", ATTR_DOMAIN_CURVE, 0)}; + src_attributes.lookup_or_default("knots_mode", ATTR_DOMAIN_CURVE, 0)}; VArraySpan handle_types_right{ - src_component.attribute_get_for_read("handle_type_right", ATTR_DOMAIN_POINT, 0)}; + src_attributes.lookup_or_default("handle_type_right", ATTR_DOMAIN_POINT, 0)}; VArraySpan handle_types_left{ - src_component.attribute_get_for_read("handle_type_left", ATTR_DOMAIN_POINT, 0)}; + src_attributes.lookup_or_default("handle_type_left", ATTR_DOMAIN_POINT, 0)}; /* Create splines with the correct size and type. */ VArray curve_types = curves.curve_types(); @@ -446,9 +444,10 @@ std::unique_ptr curves_to_curve_eval(const Curves &curves_id) CurveComponentLegacy dst_component; dst_component.replace(curve_eval.get(), GeometryOwnershipType::Editable); + blender::bke::MutableAttributeAccessor dst_attributes = *dst_component.attributes_for_write(); - copy_attributes_between_components(src_component, - dst_component, + copy_attributes_between_components(src_attributes, + dst_attributes, {"curve_type", "resolution", "normal_mode", @@ -467,37 +466,38 @@ Curves *curve_eval_to_curves(const CurveEval &curve_eval) curve_eval.splines().size()); CurveComponent dst_component; dst_component.replace(curves_id, GeometryOwnershipType::Editable); + blender::bke::MutableAttributeAccessor dst_attributes = *dst_component.attributes_for_write(); blender::bke::CurvesGeometry &curves = blender::bke::CurvesGeometry::wrap(curves_id->geometry); curves.offsets_for_write().copy_from(curve_eval.control_point_offsets()); MutableSpan curve_types = curves.curve_types_for_write(); - OutputAttribute_Typed normal_mode = - dst_component.attribute_try_get_for_output_only("normal_mode", ATTR_DOMAIN_CURVE); - OutputAttribute_Typed nurbs_weight; - OutputAttribute_Typed nurbs_order; - OutputAttribute_Typed nurbs_knots_mode; + blender::bke::SpanAttributeWriter normal_mode = + dst_attributes.lookup_or_add_for_write_only_span("normal_mode", ATTR_DOMAIN_CURVE); + blender::bke::SpanAttributeWriter nurbs_weight; + blender::bke::SpanAttributeWriter nurbs_order; + blender::bke::SpanAttributeWriter nurbs_knots_mode; if (curve_eval.has_spline_with_type(CURVE_TYPE_NURBS)) { - nurbs_weight = dst_component.attribute_try_get_for_output_only("nurbs_weight", - ATTR_DOMAIN_POINT); - nurbs_order = dst_component.attribute_try_get_for_output_only("nurbs_order", - ATTR_DOMAIN_CURVE); - nurbs_knots_mode = dst_component.attribute_try_get_for_output_only("knots_mode", - ATTR_DOMAIN_CURVE); + nurbs_weight = dst_attributes.lookup_or_add_for_write_only_span("nurbs_weight", + ATTR_DOMAIN_POINT); + nurbs_order = dst_attributes.lookup_or_add_for_write_only_span("nurbs_order", + ATTR_DOMAIN_CURVE); + nurbs_knots_mode = dst_attributes.lookup_or_add_for_write_only_span("knots_mode", + ATTR_DOMAIN_CURVE); } - OutputAttribute_Typed handle_type_right; - OutputAttribute_Typed handle_type_left; + blender::bke::SpanAttributeWriter handle_type_right; + blender::bke::SpanAttributeWriter handle_type_left; if (curve_eval.has_spline_with_type(CURVE_TYPE_BEZIER)) { - handle_type_right = dst_component.attribute_try_get_for_output_only( + handle_type_right = dst_attributes.lookup_or_add_for_write_only_span( "handle_type_right", ATTR_DOMAIN_POINT); - handle_type_left = dst_component.attribute_try_get_for_output_only("handle_type_left", - ATTR_DOMAIN_POINT); + handle_type_left = dst_attributes.lookup_or_add_for_write_only_span("handle_type_left", + ATTR_DOMAIN_POINT); } for (const int curve_index : curve_eval.splines().index_range()) { const Spline &spline = *curve_eval.splines()[curve_index]; curve_types[curve_index] = curve_eval.splines()[curve_index]->type(); - normal_mode.as_span()[curve_index] = curve_eval.splines()[curve_index]->normal_mode; + normal_mode.span[curve_index] = curve_eval.splines()[curve_index]->normal_mode; const IndexRange points = curves.points_for_curve(curve_index); switch (spline.type()) { @@ -505,15 +505,15 @@ Curves *curve_eval_to_curves(const CurveEval &curve_eval) break; case CURVE_TYPE_BEZIER: { const BezierSpline &src = static_cast(spline); - handle_type_right.as_span().slice(points).copy_from(src.handle_types_right()); - handle_type_left.as_span().slice(points).copy_from(src.handle_types_left()); + handle_type_right.span.slice(points).copy_from(src.handle_types_right()); + handle_type_left.span.slice(points).copy_from(src.handle_types_left()); break; } case CURVE_TYPE_NURBS: { const NURBSpline &src = static_cast(spline); - nurbs_knots_mode.as_span()[curve_index] = static_cast(src.knots_mode); - nurbs_order.as_span()[curve_index] = src.order(); - nurbs_weight.as_span().slice(points).copy_from(src.weights()); + nurbs_knots_mode.span[curve_index] = static_cast(src.knots_mode); + nurbs_order.span[curve_index] = src.order(); + nurbs_weight.span.slice(points).copy_from(src.weights()); break; } case CURVE_TYPE_CATMULL_ROM: { @@ -525,17 +525,18 @@ Curves *curve_eval_to_curves(const CurveEval &curve_eval) curves.update_curve_types(); - normal_mode.save(); - nurbs_weight.save(); - nurbs_order.save(); - nurbs_knots_mode.save(); - handle_type_right.save(); - handle_type_left.save(); + normal_mode.finish(); + nurbs_weight.finish(); + nurbs_order.finish(); + nurbs_knots_mode.finish(); + handle_type_right.finish(); + handle_type_left.finish(); CurveComponentLegacy src_component; src_component.replace(&const_cast(curve_eval), GeometryOwnershipType::ReadOnly); + const blender::bke::AttributeAccessor src_attributes = *src_component.attributes(); - copy_attributes_between_components(src_component, dst_component, {}); + copy_attributes_between_components(src_attributes, dst_attributes, {}); return curves_id; } diff --git a/source/blender/blenkernel/intern/curve_legacy_convert.cc b/source/blender/blenkernel/intern/curve_legacy_convert.cc index 299a0fbb44c..ff5bbc32afe 100644 --- a/source/blender/blenkernel/intern/curve_legacy_convert.cc +++ b/source/blender/blenkernel/intern/curve_legacy_convert.cc @@ -83,8 +83,7 @@ Curves *curve_legacy_to_curves(const Curve &curve_legacy, const ListBase &nurbs_ Curves *curves_id = curves_new_nomain(0, src_curves.size()); CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry); - CurveComponent component; - component.replace(curves_id, GeometryOwnershipType::Editable); + MutableAttributeAccessor curves_attributes = curves.attributes_for_write(); MutableSpan types = curves.curve_types_for_write(); MutableSpan cyclic = curves.cyclic_for_write(); @@ -110,9 +109,9 @@ Curves *curve_legacy_to_curves(const Curve &curve_legacy, const ListBase &nurbs_ } MutableSpan positions = curves.positions_for_write(); - OutputAttribute_Typed radius_attribute = - component.attribute_try_get_for_output_only("radius", ATTR_DOMAIN_POINT); - MutableSpan radii = radius_attribute.as_span(); + SpanAttributeWriter radius_attribute = + curves_attributes.lookup_or_add_for_write_only_span("radius", ATTR_DOMAIN_POINT); + MutableSpan radii = radius_attribute.span; MutableSpan tilts = curves.tilt_for_write(); auto create_poly = [&](IndexMask selection) { @@ -203,7 +202,7 @@ Curves *curve_legacy_to_curves(const Curve &curve_legacy, const ListBase &nurbs_ curves.normal_mode_for_write().fill(normal_mode_from_legacy(curve_legacy.twist_mode)); - radius_attribute.save(); + radius_attribute.finish(); return curves_id; } diff --git a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc index baf56c0c350..7f051b683b4 100644 --- a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc +++ b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc @@ -8,7 +8,6 @@ #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" -#include "BKE_attribute_access.hh" #include "BKE_attribute_math.hh" #include "BKE_curves.hh" #include "BKE_geometry_set.hh" @@ -315,15 +314,15 @@ static ResultOffsets calculate_result_offsets(const CurvesInfo &info, const bool return result; } -static eAttrDomain get_attribute_domain_for_mesh(const MeshComponent &mesh, +static eAttrDomain get_attribute_domain_for_mesh(const AttributeAccessor &mesh_attributes, const AttributeIDRef &attribute_id) { /* Only use a different domain if it is builtin and must only exist on one domain. */ - if (!mesh.attribute_is_builtin(attribute_id)) { + if (!mesh_attributes.is_builtin(attribute_id)) { return ATTR_DOMAIN_POINT; } - std::optional meta_data = mesh.attribute_get_meta_data(attribute_id); + std::optional meta_data = mesh_attributes.lookup_meta_data(attribute_id); if (!meta_data) { return ATTR_DOMAIN_POINT; } @@ -331,16 +330,17 @@ static eAttrDomain get_attribute_domain_for_mesh(const MeshComponent &mesh, return meta_data->domain; } -static bool should_add_attribute_to_mesh(const CurveComponent &curve_component, - const MeshComponent &mesh_component, +static bool should_add_attribute_to_mesh(const AttributeAccessor &curve_attributes, + const AttributeAccessor &mesh_attributes, const AttributeIDRef &id) { + /* The position attribute has special non-generic evaluation. */ if (id.is_named() && id.name() == "position") { return false; } /* Don't propagate built-in curves attributes that are not built-in on meshes. */ - if (curve_component.attribute_is_builtin(id) && !mesh_component.attribute_is_builtin(id)) { + if (curve_attributes.is_builtin(id) && !mesh_attributes.is_builtin(id)) { return false; } if (!id.should_be_kept()) { @@ -667,20 +667,13 @@ Mesh *curve_to_mesh_sweep(const CurvesGeometry &main, Vector eval_buffer; - Curves main_id = {{nullptr}}; - main_id.geometry = reinterpret_cast(main); - CurveComponent main_component; - main_component.replace(&main_id, GeometryOwnershipType::Editable); - - Curves profile_id = {{nullptr}}; - profile_id.geometry = reinterpret_cast(profile); - CurveComponent profile_component; - profile_component.replace(&profile_id, GeometryOwnershipType::Editable); + const AttributeAccessor main_attributes = main.attributes(); + const AttributeAccessor profile_attributes = profile.attributes(); Span radii = {}; - if (main_component.attribute_exists("radius")) { + if (main_attributes.contains("radius")) { radii = evaluated_attribute_if_necessary( - main_component.attribute_get_for_read("radius", ATTR_DOMAIN_POINT, 1.0f), + main_attributes.lookup_or_default("radius", ATTR_DOMAIN_POINT, 1.0f), main, main.curve_type_counts(), eval_buffer) @@ -716,24 +709,23 @@ Mesh *curve_to_mesh_sweep(const CurvesGeometry &main, }); } - Set main_attributes; + Set main_attributes_set; - MeshComponent mesh_component; - mesh_component.replace(mesh, GeometryOwnershipType::Editable); + MutableAttributeAccessor mesh_attributes = bke::mesh_attributes_for_write(*mesh); - main_component.attribute_foreach([&](const AttributeIDRef &id, - const AttributeMetaData meta_data) { - if (!should_add_attribute_to_mesh(main_component, mesh_component, id)) { + main_attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { + if (!should_add_attribute_to_mesh(main_attributes, mesh_attributes, id)) { return true; } - main_attributes.add_new(id); + main_attributes_set.add_new(id); const eAttrDomain src_domain = meta_data.domain; const eCustomDataType type = meta_data.data_type; - GVArray src = main_component.attribute_try_get_for_read(id, src_domain, type); + GVArray src = main_attributes.lookup(id, src_domain, type); - const eAttrDomain dst_domain = get_attribute_domain_for_mesh(mesh_component, id); - OutputAttribute dst = mesh_component.attribute_try_get_for_output_only(id, dst_domain, type); + const eAttrDomain dst_domain = get_attribute_domain_for_mesh(mesh_attributes, id); + GSpanAttributeWriter dst = mesh_attributes.lookup_or_add_for_write_only_span( + id, dst_domain, type); if (!dst) { return true; } @@ -744,31 +736,31 @@ Mesh *curve_to_mesh_sweep(const CurvesGeometry &main, offsets, dst_domain, evaluated_attribute_if_necessary(src, main, main.curve_type_counts(), eval_buffer), - dst.as_span()); + dst.span); } else if (src_domain == ATTR_DOMAIN_CURVE) { copy_curve_domain_attribute_to_mesh( - offsets, offsets.main_indices, dst_domain, src, dst.as_span()); + offsets, offsets.main_indices, dst_domain, src, dst.span); } - dst.save(); + dst.finish(); return true; }); - profile_component.attribute_foreach([&](const AttributeIDRef &id, - const AttributeMetaData meta_data) { + profile_attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { if (main_attributes.contains(id)) { return true; } - if (!should_add_attribute_to_mesh(profile_component, mesh_component, id)) { + if (!should_add_attribute_to_mesh(profile_attributes, mesh_attributes, id)) { return true; } const eAttrDomain src_domain = meta_data.domain; const eCustomDataType type = meta_data.data_type; - GVArray src = profile_component.attribute_try_get_for_read(id, src_domain, type); + GVArray src = profile_attributes.lookup(id, src_domain, type); - const eAttrDomain dst_domain = get_attribute_domain_for_mesh(mesh_component, id); - OutputAttribute dst = mesh_component.attribute_try_get_for_output_only(id, dst_domain, type); + const eAttrDomain dst_domain = get_attribute_domain_for_mesh(mesh_attributes, id); + GSpanAttributeWriter dst = mesh_attributes.lookup_or_add_for_write_only_span( + id, dst_domain, type); if (!dst) { return true; } @@ -779,14 +771,14 @@ Mesh *curve_to_mesh_sweep(const CurvesGeometry &main, offsets, dst_domain, evaluated_attribute_if_necessary(src, profile, profile.curve_type_counts(), eval_buffer), - dst.as_span()); + dst.span); } else if (src_domain == ATTR_DOMAIN_CURVE) { copy_curve_domain_attribute_to_mesh( - offsets, offsets.profile_indices, dst_domain, src, dst.as_span()); + offsets, offsets.profile_indices, dst_domain, src, dst.span); } - dst.save(); + dst.finish(); return true; }); diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc index 5725c6043b2..0d899ec7b06 100644 --- a/source/blender/blenkernel/intern/geometry_component_curve.cc +++ b/source/blender/blenkernel/intern/geometry_component_curve.cc @@ -5,7 +5,6 @@ #include "DNA_ID_enums.h" #include "DNA_curve_types.h" -#include "BKE_attribute_access.hh" #include "BKE_attribute_math.hh" #include "BKE_curve.h" #include "BKE_geometry_set.hh" @@ -114,24 +113,6 @@ void CurveComponentLegacy::ensure_owns_direct_data() /** \name Attribute Access Helper Functions * \{ */ -int CurveComponentLegacy::attribute_domain_num(const eAttrDomain domain) const -{ - if (curve_ == nullptr) { - return 0; - } - if (domain == ATTR_DOMAIN_POINT) { - int total = 0; - for (const SplinePtr &spline : curve_->splines()) { - total += spline->size(); - } - return total; - } - if (domain == ATTR_DOMAIN_CURVE) { - return curve_->splines().size(); - } - return 0; -} - namespace blender::bke { namespace { @@ -308,9 +289,10 @@ static GVArray adapt_curve_domain_spline_to_point(const CurveEval &curve, GVArra } // namespace blender::bke -GVArray CurveComponentLegacy::attribute_try_adapt_domain_impl(const GVArray &varray, - const eAttrDomain from_domain, - const eAttrDomain to_domain) const +static GVArray adapt_curve_attribute_domain(const CurveEval &curve, + const GVArray &varray, + const eAttrDomain from_domain, + const eAttrDomain to_domain) { if (!varray) { return {}; @@ -323,30 +305,15 @@ GVArray CurveComponentLegacy::attribute_try_adapt_domain_impl(const GVArray &var } if (from_domain == ATTR_DOMAIN_POINT && to_domain == ATTR_DOMAIN_CURVE) { - return blender::bke::adapt_curve_domain_point_to_spline(*curve_, std::move(varray)); + return blender::bke::adapt_curve_domain_point_to_spline(curve, std::move(varray)); } if (from_domain == ATTR_DOMAIN_CURVE && to_domain == ATTR_DOMAIN_POINT) { - return blender::bke::adapt_curve_domain_spline_to_point(*curve_, std::move(varray)); + return blender::bke::adapt_curve_domain_spline_to_point(curve, std::move(varray)); } return {}; } -static CurveEval *get_curve_from_component_for_write(GeometryComponent &component) -{ - BLI_assert(component.type() == GEO_COMPONENT_TYPE_CURVE); - CurveComponentLegacy &curve_component = static_cast(component); - return curve_component.get_for_write(); -} - -static const CurveEval *get_curve_from_component_for_read(const GeometryComponent &component) -{ - BLI_assert(component.type() == GEO_COMPONENT_TYPE_CURVE); - const CurveComponentLegacy &curve_component = static_cast( - component); - return curve_component.get_for_read(); -} - /** \} */ namespace blender::bke { @@ -380,41 +347,41 @@ class BuiltinSplineAttributeProvider final : public BuiltinAttributeProvider { { } - GVArray try_get_for_read(const GeometryComponent &component) const final + GVArray try_get_for_read(const void *owner) const final { - const CurveEval *curve = get_curve_from_component_for_read(component); + const CurveEval *curve = static_cast(owner); if (curve == nullptr) { return {}; } return as_read_attribute_(*curve); } - WriteAttributeLookup try_get_for_write(GeometryComponent &component) const final + GAttributeWriter try_get_for_write(void *owner) const final { if (writable_ != Writable) { return {}; } - CurveEval *curve = get_curve_from_component_for_write(component); + CurveEval *curve = static_cast(owner); if (curve == nullptr) { return {}; } return {as_write_attribute_(*curve), domain_}; } - bool try_delete(GeometryComponent &UNUSED(component)) const final + bool try_delete(void *UNUSED(owner)) const final { return false; } - bool try_create(GeometryComponent &UNUSED(component), - const AttributeInit &UNUSED(initializer)) const final + bool try_create(void *UNUSED(owner), const AttributeInit &UNUSED(initializer)) const final { return false; } - bool exists(const GeometryComponent &component) const final + bool exists(const void *owner) const final { - return component.attribute_domain_num(ATTR_DOMAIN_CURVE) != 0; + const CurveEval *curve = static_cast(owner); + return !curve->splines().is_empty(); } }; @@ -600,12 +567,11 @@ static GVArray varray_from_initializer(const AttributeInit &initializer, return {}; } -static bool create_point_attribute(GeometryComponent &component, +static bool create_point_attribute(CurveEval *curve, const AttributeIDRef &attribute_id, const AttributeInit &initializer, const eCustomDataType data_type) { - CurveEval *curve = get_curve_from_component_for_write(component); if (curve == nullptr || curve->splines().size() == 0) { return false; } @@ -638,7 +604,7 @@ static bool create_point_attribute(GeometryComponent &component, return true; } - WriteAttributeLookup write_attribute = component.attribute_try_get_for_write(attribute_id); + GAttributeWriter write_attribute = curve->attributes_for_write().lookup_for_write(attribute_id); /* We just created the attribute, it should exist. */ BLI_assert(write_attribute); @@ -647,6 +613,7 @@ static bool create_point_attribute(GeometryComponent &component, * this theoretically unnecessary materialize step could be removed. */ GVArraySpan source_VArraySpan{source_varray}; write_attribute.varray.set_all(source_VArraySpan.data()); + write_attribute.finish(); if (initializer.type == AttributeInit::Type::MoveArray) { MEM_freeN(static_cast(initializer).data); @@ -655,10 +622,8 @@ static bool create_point_attribute(GeometryComponent &component, return true; } -static bool remove_point_attribute(GeometryComponent &component, - const AttributeIDRef &attribute_id) +static bool remove_point_attribute(CurveEval *curve, const AttributeIDRef &attribute_id) { - CurveEval *curve = get_curve_from_component_for_write(component); if (curve == nullptr) { return false; } @@ -934,14 +899,14 @@ template class BuiltinPointAttributeProvider : public BuiltinAttribu { } - GVArray try_get_for_read(const GeometryComponent &component) const override + GVArray try_get_for_read(const void *owner) const override { - const CurveEval *curve = get_curve_from_component_for_read(component); + const CurveEval *curve = static_cast(owner); if (curve == nullptr) { return {}; } - if (!this->exists(component)) { + if (!this->exists(owner)) { return {}; } @@ -962,14 +927,14 @@ template class BuiltinPointAttributeProvider : public BuiltinAttribu return point_data_varray(spans, offsets); } - WriteAttributeLookup try_get_for_write(GeometryComponent &component) const override + GAttributeWriter try_get_for_write(void *owner) const override { - CurveEval *curve = get_curve_from_component_for_write(component); + CurveEval *curve = static_cast(owner); if (curve == nullptr) { return {}; } - if (!this->exists(component)) { + if (!this->exists(owner)) { return {}; } @@ -998,25 +963,27 @@ template class BuiltinPointAttributeProvider : public BuiltinAttribu return {point_data_varray_mutable(spans, offsets), domain_, tag_modified_fn}; } - bool try_delete(GeometryComponent &component) const final + bool try_delete(void *owner) const final { if (deletable_ == DeletableEnum::NonDeletable) { return false; } - return remove_point_attribute(component, name_); + CurveEval *curve = static_cast(owner); + return remove_point_attribute(curve, name_); } - bool try_create(GeometryComponent &component, const AttributeInit &initializer) const final + bool try_create(void *owner, const AttributeInit &initializer) const final { if (createable_ == CreatableEnum::NonCreatable) { return false; } - return create_point_attribute(component, name_, initializer, CD_PROP_INT32); + CurveEval *curve = static_cast(owner); + return create_point_attribute(curve, name_, initializer, CD_PROP_INT32); } - bool exists(const GeometryComponent &component) const final + bool exists(const void *owner) const final { - const CurveEval *curve = get_curve_from_component_for_read(component); + const CurveEval *curve = static_cast(owner); if (curve == nullptr) { return false; } @@ -1067,9 +1034,9 @@ class PositionAttributeProvider final : public BuiltinPointAttributeProvider(owner); if (curve == nullptr) { return {}; } @@ -1077,7 +1044,7 @@ class PositionAttributeProvider final : public BuiltinPointAttributeProviderhas_spline_with_type(CURVE_TYPE_BEZIER)) { - return BuiltinPointAttributeProvider::try_get_for_write(component); + return BuiltinPointAttributeProvider::try_get_for_write(owner); } auto tag_modified_fn = [curve]() { @@ -1110,9 +1077,9 @@ class BezierHandleAttributeProvider : public BuiltinAttributeProvider { { } - GVArray try_get_for_read(const GeometryComponent &component) const override + GVArray try_get_for_read(const void *owner) const override { - const CurveEval *curve = get_curve_from_component_for_read(component); + const CurveEval *curve = static_cast(owner); if (curve == nullptr) { return {}; } @@ -1128,9 +1095,9 @@ class BezierHandleAttributeProvider : public BuiltinAttributeProvider { const_cast(curve)->splines(), std::move(offsets), is_right_); } - WriteAttributeLookup try_get_for_write(GeometryComponent &component) const override + GAttributeWriter try_get_for_write(void *owner) const override { - CurveEval *curve = get_curve_from_component_for_write(component); + CurveEval *curve = static_cast(owner); if (curve == nullptr) { return {}; } @@ -1148,26 +1115,27 @@ class BezierHandleAttributeProvider : public BuiltinAttributeProvider { tag_modified_fn}; } - bool try_delete(GeometryComponent &UNUSED(component)) const final + bool try_delete(void *UNUSED(owner)) const final { return false; } - bool try_create(GeometryComponent &UNUSED(component), - const AttributeInit &UNUSED(initializer)) const final + bool try_create(void *UNUSED(owner), const AttributeInit &UNUSED(initializer)) const final { return false; } - bool exists(const GeometryComponent &component) const final + bool exists(const void *owner) const final { - const CurveEval *curve = get_curve_from_component_for_read(component); + const CurveEval *curve = static_cast(owner); if (curve == nullptr) { return false; } - return curve->has_spline_with_type(CURVE_TYPE_BEZIER) && - component.attribute_domain_num(ATTR_DOMAIN_POINT) != 0; + CurveComponentLegacy component; + component.replace(const_cast(curve), GeometryOwnershipType::ReadOnly); + + return curve->has_spline_with_type(CURVE_TYPE_BEZIER) && !curve->splines().is_empty(); } }; @@ -1190,10 +1158,10 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { CD_MASK_PROP_INT8; public: - ReadAttributeLookup try_get_for_read(const GeometryComponent &component, - const AttributeIDRef &attribute_id) const final + GAttributeReader try_get_for_read(const void *owner, + const AttributeIDRef &attribute_id) const final { - const CurveEval *curve = get_curve_from_component_for_read(component); + const CurveEval *curve = static_cast(owner); if (curve == nullptr || curve->splines().size() == 0) { return {}; } @@ -1228,7 +1196,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { return {GVArray::ForSpan(spans.first()), ATTR_DOMAIN_POINT}; } - ReadAttributeLookup attribute = {}; + GAttributeReader attribute = {}; Array offsets = curve->control_point_offsets(); attribute_math::convert_to_static_type(spans[0].type(), [&](auto dummy) { using T = decltype(dummy); @@ -1246,10 +1214,9 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { } /* This function is almost the same as #try_get_for_read, but without const. */ - WriteAttributeLookup try_get_for_write(GeometryComponent &component, - const AttributeIDRef &attribute_id) const final + GAttributeWriter try_get_for_write(void *owner, const AttributeIDRef &attribute_id) const final { - CurveEval *curve = get_curve_from_component_for_write(component); + CurveEval *curve = static_cast(owner); if (curve == nullptr || curve->splines().size() == 0) { return {}; } @@ -1284,7 +1251,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { return {GVMutableArray::ForSpan(spans.first()), ATTR_DOMAIN_POINT}; } - WriteAttributeLookup attribute = {}; + GAttributeWriter attribute = {}; Array offsets = curve->control_point_offsets(); attribute_math::convert_to_static_type(spans[0].type(), [&](auto dummy) { using T = decltype(dummy); @@ -1298,12 +1265,13 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { return attribute; } - bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final + bool try_delete(void *owner, const AttributeIDRef &attribute_id) const final { - return remove_point_attribute(component, attribute_id); + CurveEval *curve = static_cast(owner); + return remove_point_attribute(curve, attribute_id); } - bool try_create(GeometryComponent &component, + bool try_create(void *owner, const AttributeIDRef &attribute_id, const eAttrDomain domain, const eCustomDataType data_type, @@ -1313,13 +1281,13 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { if (domain != ATTR_DOMAIN_POINT) { return false; } - return create_point_attribute(component, attribute_id, initializer, data_type); + CurveEval *curve = static_cast(owner); + return create_point_attribute(curve, attribute_id, initializer, data_type); } - bool foreach_attribute(const GeometryComponent &component, - const AttributeForeachCallback callback) const final + bool foreach_attribute(const void *owner, const AttributeForeachCallback callback) const final { - const CurveEval *curve = get_curve_from_component_for_read(component); + const CurveEval *curve = static_cast(owner); if (curve == nullptr || curve->splines().size() == 0) { return false; } @@ -1371,14 +1339,18 @@ static ComponentAttributeProviders create_attribute_providers_for_curve() make_cyclic_write_attribute); static CustomDataAccessInfo spline_custom_data_access = { - [](GeometryComponent &component) -> CustomData * { - CurveEval *curve = get_curve_from_component_for_write(component); + [](void *owner) -> CustomData * { + CurveEval *curve = static_cast(owner); return curve ? &curve->attributes.data : nullptr; }, - [](const GeometryComponent &component) -> const CustomData * { - const CurveEval *curve = get_curve_from_component_for_read(component); + [](const void *owner) -> const CustomData * { + const CurveEval *curve = static_cast(owner); return curve ? &curve->attributes.data : nullptr; }, + [](const void *owner) -> int { + const CurveEval *curve = static_cast(owner); + return curve->splines().size(); + }, nullptr}; static CustomDataAttributeProvider spline_custom_data(ATTR_DOMAIN_CURVE, @@ -1430,12 +1402,62 @@ static ComponentAttributeProviders create_attribute_providers_for_curve() /** \} */ +static AttributeAccessorFunctions get_curve_accessor_functions() +{ + static const ComponentAttributeProviders providers = create_attribute_providers_for_curve(); + AttributeAccessorFunctions fn = + attribute_accessor_functions::accessor_functions_for_providers(); + fn.domain_size = [](const void *owner, const eAttrDomain domain) -> int { + if (owner == nullptr) { + return 0; + } + const CurveEval &curve_eval = *static_cast(owner); + switch (domain) { + case ATTR_DOMAIN_POINT: + return curve_eval.total_control_point_num(); + case ATTR_DOMAIN_CURVE: + return curve_eval.splines().size(); + default: + return 0; + } + }; + fn.domain_supported = [](const void *UNUSED(owner), const eAttrDomain domain) { + return ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE); + }; + fn.adapt_domain = [](const void *owner, + const blender::GVArray &varray, + const eAttrDomain from_domain, + const eAttrDomain to_domain) -> GVArray { + if (owner == nullptr) { + return {}; + } + const CurveEval &curve_eval = *static_cast(owner); + return adapt_curve_attribute_domain(curve_eval, varray, from_domain, to_domain); + }; + return fn; +} + +static const AttributeAccessorFunctions &get_curve_accessor_functions_ref() +{ + static const AttributeAccessorFunctions fn = get_curve_accessor_functions(); + return fn; +} + } // namespace blender::bke -const blender::bke::ComponentAttributeProviders *CurveComponentLegacy::get_attribute_providers() - const +std::optional CurveComponentLegacy::attributes() const +{ + return blender::bke::AttributeAccessor(curve_, blender::bke::get_curve_accessor_functions_ref()); +} + +std::optional CurveComponentLegacy::attributes_for_write() +{ + return blender::bke::MutableAttributeAccessor(curve_, + blender::bke::get_curve_accessor_functions_ref()); +} + +blender::bke::MutableAttributeAccessor CurveEval::attributes_for_write() { - static blender::bke::ComponentAttributeProviders providers = - blender::bke::create_attribute_providers_for_curve(); - return &providers; + return blender::bke::MutableAttributeAccessor(this, + blender::bke::get_curve_accessor_functions_ref()); } diff --git a/source/blender/blenkernel/intern/geometry_component_curves.cc b/source/blender/blenkernel/intern/geometry_component_curves.cc index af058534f68..34c17bedc2c 100644 --- a/source/blender/blenkernel/intern/geometry_component_curves.cc +++ b/source/blender/blenkernel/intern/geometry_component_curves.cc @@ -5,7 +5,6 @@ #include "DNA_ID_enums.h" #include "DNA_curve_types.h" -#include "BKE_attribute_access.hh" #include "BKE_attribute_math.hh" #include "BKE_curve.h" #include "BKE_curves.hh" @@ -218,7 +217,7 @@ VArray curve_normals_varray(const CurveComponent &component, const eAttr const VArray types = curves.curve_types(); if (curves.is_single_type(CURVE_TYPE_POLY)) { - return component.attribute_try_adapt_domain( + return component.attributes()->adapt_domain( VArray::ForSpan(curves.evaluated_normals()), ATTR_DOMAIN_POINT, domain); } @@ -229,7 +228,7 @@ VArray curve_normals_varray(const CurveComponent &component, const eAttr } if (domain == ATTR_DOMAIN_CURVE) { - return component.attribute_try_adapt_domain( + return component.attributes()->adapt_domain( VArray::ForContainer(std::move(normals)), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE); } @@ -264,7 +263,7 @@ static VArray construct_curve_length_gvarray(const CurveComponent &compon } if (domain == ATTR_DOMAIN_POINT) { - return component.attribute_try_adapt_domain( + return component.attributes()->adapt_domain( std::move(lengths), ATTR_DOMAIN_CURVE, ATTR_DOMAIN_POINT); } @@ -307,75 +306,29 @@ bool CurveLengthFieldInput::is_equal_to(const fn::FieldNode &other) const /** \name Attribute Access Helper Functions * \{ */ -int CurveComponent::attribute_domain_num(const eAttrDomain domain) const +static void tag_component_topology_changed(void *owner) { - if (curves_ == nullptr) { - return 0; - } - const blender::bke::CurvesGeometry &curves = blender::bke::CurvesGeometry::wrap( - curves_->geometry); - if (domain == ATTR_DOMAIN_POINT) { - return curves.points_num(); - } - if (domain == ATTR_DOMAIN_CURVE) { - return curves.curves_num(); - } - return 0; -} - -GVArray CurveComponent::attribute_try_adapt_domain_impl(const GVArray &varray, - const eAttrDomain from_domain, - const eAttrDomain to_domain) const -{ - return blender::bke::CurvesGeometry::wrap(curves_->geometry) - .adapt_domain(varray, from_domain, to_domain); -} - -static Curves *get_curves_from_component_for_write(GeometryComponent &component) -{ - BLI_assert(component.type() == GEO_COMPONENT_TYPE_CURVE); - CurveComponent &curve_component = static_cast(component); - return curve_component.get_for_write(); -} - -static const Curves *get_curves_from_component_for_read(const GeometryComponent &component) -{ - BLI_assert(component.type() == GEO_COMPONENT_TYPE_CURVE); - const CurveComponent &curve_component = static_cast(component); - return curve_component.get_for_read(); + blender::bke::CurvesGeometry &curves = *static_cast(owner); + curves.tag_topology_changed(); } -static void tag_component_topology_changed(GeometryComponent &component) +static void tag_component_curve_types_changed(void *owner) { - Curves *curves = get_curves_from_component_for_write(component); - if (curves) { - blender::bke::CurvesGeometry::wrap(curves->geometry).tag_topology_changed(); - } + blender::bke::CurvesGeometry &curves = *static_cast(owner); + curves.update_curve_types(); + curves.tag_topology_changed(); } -static void tag_component_curve_types_changed(GeometryComponent &component) +static void tag_component_positions_changed(void *owner) { - Curves *curves = get_curves_from_component_for_write(component); - if (curves) { - blender::bke::CurvesGeometry::wrap(curves->geometry).update_curve_types(); - blender::bke::CurvesGeometry::wrap(curves->geometry).tag_topology_changed(); - } + blender::bke::CurvesGeometry &curves = *static_cast(owner); + curves.tag_positions_changed(); } -static void tag_component_positions_changed(GeometryComponent &component) +static void tag_component_normals_changed(void *owner) { - Curves *curves = get_curves_from_component_for_write(component); - if (curves) { - blender::bke::CurvesGeometry::wrap(curves->geometry).tag_positions_changed(); - } -} - -static void tag_component_normals_changed(GeometryComponent &component) -{ - Curves *curves = get_curves_from_component_for_write(component); - if (curves) { - blender::bke::CurvesGeometry::wrap(curves->geometry).tag_normals_changed(); - } + blender::bke::CurvesGeometry &curves = *static_cast(owner); + curves.tag_normals_changed(); } /** \} */ @@ -393,34 +346,38 @@ namespace blender::bke { static ComponentAttributeProviders create_attribute_providers_for_curve() { static CustomDataAccessInfo curve_access = { - [](GeometryComponent &component) -> CustomData * { - Curves *curves = get_curves_from_component_for_write(component); - return curves ? &curves->geometry.curve_data : nullptr; + [](void *owner) -> CustomData * { + CurvesGeometry &curves = *static_cast(owner); + return &curves.curve_data; }, - [](const GeometryComponent &component) -> const CustomData * { - const Curves *curves = get_curves_from_component_for_read(component); - return curves ? &curves->geometry.curve_data : nullptr; + [](const void *owner) -> const CustomData * { + const CurvesGeometry &curves = *static_cast(owner); + return &curves.curve_data; }, - [](GeometryComponent &component) { - Curves *curves = get_curves_from_component_for_write(component); - if (curves) { - blender::bke::CurvesGeometry::wrap(curves->geometry).update_customdata_pointers(); - } + [](const void *owner) -> int { + const CurvesGeometry &curves = *static_cast(owner); + return curves.curves_num(); + }, + [](void *owner) { + CurvesGeometry &curves = *static_cast(owner); + curves.update_customdata_pointers(); }}; static CustomDataAccessInfo point_access = { - [](GeometryComponent &component) -> CustomData * { - Curves *curves = get_curves_from_component_for_write(component); - return curves ? &curves->geometry.point_data : nullptr; + [](void *owner) -> CustomData * { + CurvesGeometry &curves = *static_cast(owner); + return &curves.point_data; }, - [](const GeometryComponent &component) -> const CustomData * { - const Curves *curves = get_curves_from_component_for_read(component); - return curves ? &curves->geometry.point_data : nullptr; + [](const void *owner) -> const CustomData * { + const CurvesGeometry &curves = *static_cast(owner); + return &curves.point_data; }, - [](GeometryComponent &component) { - Curves *curves = get_curves_from_component_for_write(component); - if (curves) { - blender::bke::CurvesGeometry::wrap(curves->geometry).update_customdata_pointers(); - } + [](const void *owner) -> int { + const CurvesGeometry &curves = *static_cast(owner); + return curves.points_num(); + }, + [](void *owner) { + CurvesGeometry &curves = *static_cast(owner); + curves.update_customdata_pointers(); }}; static BuiltinCustomDataLayerProvider position("position", @@ -626,11 +583,67 @@ static ComponentAttributeProviders create_attribute_providers_for_curve() /** \} */ +static AttributeAccessorFunctions get_curves_accessor_functions() +{ + static const ComponentAttributeProviders providers = create_attribute_providers_for_curve(); + AttributeAccessorFunctions fn = + attribute_accessor_functions::accessor_functions_for_providers(); + fn.domain_size = [](const void *owner, const eAttrDomain domain) { + if (owner == nullptr) { + return 0; + } + const CurvesGeometry &curves = *static_cast(owner); + switch (domain) { + case ATTR_DOMAIN_POINT: + return curves.points_num(); + case ATTR_DOMAIN_CURVE: + return curves.curves_num(); + default: + return 0; + } + }; + fn.domain_supported = [](const void *UNUSED(owner), const eAttrDomain domain) { + return ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE); + }; + fn.adapt_domain = [](const void *owner, + const blender::GVArray &varray, + const eAttrDomain from_domain, + const eAttrDomain to_domain) -> GVArray { + if (owner == nullptr) { + return {}; + } + const CurvesGeometry &curves = *static_cast(owner); + return curves.adapt_domain(varray, from_domain, to_domain); + }; + return fn; +} + +static const AttributeAccessorFunctions &get_curves_accessor_functions_ref() +{ + static const AttributeAccessorFunctions fn = get_curves_accessor_functions(); + return fn; +} + +AttributeAccessor CurvesGeometry::attributes() const +{ + return AttributeAccessor(this, get_curves_accessor_functions_ref()); +} + +MutableAttributeAccessor CurvesGeometry::attributes_for_write() +{ + return MutableAttributeAccessor(this, get_curves_accessor_functions_ref()); +} + } // namespace blender::bke -const blender::bke::ComponentAttributeProviders *CurveComponent::get_attribute_providers() const +std::optional CurveComponent::attributes() const +{ + return blender::bke::AttributeAccessor(curves_ ? &curves_->geometry : nullptr, + blender::bke::get_curves_accessor_functions_ref()); +} + +std::optional CurveComponent::attributes_for_write() { - static blender::bke::ComponentAttributeProviders providers = - blender::bke::create_attribute_providers_for_curve(); - return &providers; + return blender::bke::MutableAttributeAccessor(curves_ ? &curves_->geometry : nullptr, + blender::bke::get_curves_accessor_functions_ref()); } diff --git a/source/blender/blenkernel/intern/geometry_component_instances.cc b/source/blender/blenkernel/intern/geometry_component_instances.cc index 653be03b991..c16311945ba 100644 --- a/source/blender/blenkernel/intern/geometry_component_instances.cc +++ b/source/blender/blenkernel/intern/geometry_component_instances.cc @@ -13,7 +13,6 @@ #include "DNA_collection_types.h" -#include "BKE_attribute_access.hh" #include "BKE_attribute_math.hh" #include "BKE_geometry_set.hh" #include "BKE_geometry_set_instances.hh" @@ -157,7 +156,7 @@ void InstancesComponent::remove_instances(const IndexMask mask) dst_attributes.reallocate(mask.size()); src_attributes.foreach_attribute( - [&](const bke::AttributeIDRef &id, const AttributeMetaData &meta_data) { + [&](const bke::AttributeIDRef &id, const bke::AttributeMetaData &meta_data) { if (!id.should_be_kept()) { return true; } @@ -366,20 +365,12 @@ blender::Span InstancesComponent::almost_unique_ids() const return almost_unique_ids_; } -int InstancesComponent::attribute_domain_num(const eAttrDomain domain) const -{ - if (domain != ATTR_DOMAIN_INSTANCE) { - return 0; - } - return this->instances_num(); -} - -blender::bke::CustomDataAttributes &InstancesComponent::attributes() +blender::bke::CustomDataAttributes &InstancesComponent::instance_attributes() { return this->attributes_; } -const blender::bke::CustomDataAttributes &InstancesComponent::attributes() const +const blender::bke::CustomDataAttributes &InstancesComponent::instance_attributes() const { return this->attributes_; } @@ -404,17 +395,17 @@ class InstancePositionAttributeProvider final : public BuiltinAttributeProvider { } - GVArray try_get_for_read(const GeometryComponent &component) const final + GVArray try_get_for_read(const void *owner) const final { - const InstancesComponent &instances_component = static_cast( - component); + const InstancesComponent &instances_component = *static_cast( + owner); Span transforms = instances_component.instance_transforms(); return VArray::ForDerivedSpan(transforms); } - WriteAttributeLookup try_get_for_write(GeometryComponent &component) const final + GAttributeWriter try_get_for_write(void *owner) const final { - InstancesComponent &instances_component = static_cast(component); + InstancesComponent &instances_component = *static_cast(owner); MutableSpan transforms = instances_component.instance_transforms(); return {VMutableArray::ForDerivedSpan CustomData * { - InstancesComponent &inst = static_cast(component); - return &inst.attributes().data; + [](void *owner) -> CustomData * { + InstancesComponent &inst = *static_cast(owner); + return &inst.instance_attributes().data; + }, + [](const void *owner) -> const CustomData * { + const InstancesComponent &inst = *static_cast(owner); + return &inst.instance_attributes().data; }, - [](const GeometryComponent &component) -> const CustomData * { - const InstancesComponent &inst = static_cast(component); - return &inst.attributes().data; + [](const void *owner) -> int { + const InstancesComponent &inst = *static_cast(owner); + return inst.instances_num(); }, nullptr}; @@ -476,14 +470,57 @@ static ComponentAttributeProviders create_attribute_providers_for_instances() return ComponentAttributeProviders({&position, &id}, {&instance_custom_data}); } + +static AttributeAccessorFunctions get_instances_accessor_functions() +{ + static const ComponentAttributeProviders providers = create_attribute_providers_for_instances(); + AttributeAccessorFunctions fn = + attribute_accessor_functions::accessor_functions_for_providers(); + fn.domain_size = [](const void *owner, const eAttrDomain domain) { + if (owner == nullptr) { + return 0; + } + const InstancesComponent &instances = *static_cast(owner); + switch (domain) { + case ATTR_DOMAIN_INSTANCE: + return instances.instances_num(); + default: + return 0; + } + }; + fn.domain_supported = [](const void *UNUSED(owner), const eAttrDomain domain) { + return domain == ATTR_DOMAIN_INSTANCE; + }; + fn.adapt_domain = [](const void *UNUSED(owner), + const blender::GVArray &varray, + const eAttrDomain from_domain, + const eAttrDomain to_domain) { + if (from_domain == to_domain && from_domain == ATTR_DOMAIN_INSTANCE) { + return varray; + } + return blender::GVArray{}; + }; + return fn; +} + +static const AttributeAccessorFunctions &get_instances_accessor_functions_ref() +{ + static const AttributeAccessorFunctions fn = get_instances_accessor_functions(); + return fn; +} + } // namespace blender::bke -const blender::bke::ComponentAttributeProviders *InstancesComponent::get_attribute_providers() - const +std::optional InstancesComponent::attributes() const +{ + return blender::bke::AttributeAccessor(this, + blender::bke::get_instances_accessor_functions_ref()); +} + +std::optional InstancesComponent::attributes_for_write() { - static blender::bke::ComponentAttributeProviders providers = - blender::bke::create_attribute_providers_for_instances(); - return &providers; + return blender::bke::MutableAttributeAccessor( + this, blender::bke::get_instances_accessor_functions_ref()); } /** \} */ diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc index 9e64acf218b..cb36b9b19f7 100644 --- a/source/blender/blenkernel/intern/geometry_component_mesh.cc +++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc @@ -7,7 +7,6 @@ #include "DNA_meshdata_types.h" #include "DNA_object_types.h" -#include "BKE_attribute_access.hh" #include "BKE_attribute_math.hh" #include "BKE_deform.h" #include "BKE_geometry_fields.hh" @@ -151,7 +150,7 @@ VArray mesh_normals_varray(const MeshComponent &mesh_component, * array and copy the face normal for each of its corners. In this case using the mesh * component's generic domain interpolation is fine, the data will still be normalized, * since the face normal is just copied to every corner. */ - return mesh_component.attribute_try_adapt_domain( + return mesh_component.attributes()->adapt_domain( VArray::ForSpan({(float3 *)BKE_mesh_poly_normals_ensure(&mesh), mesh.totpoly}), ATTR_DOMAIN_FACE, ATTR_DOMAIN_CORNER); @@ -169,26 +168,6 @@ VArray mesh_normals_varray(const MeshComponent &mesh_component, /** \name Attribute Access * \{ */ -int MeshComponent::attribute_domain_num(const eAttrDomain domain) const -{ - if (mesh_ == nullptr) { - return 0; - } - switch (domain) { - case ATTR_DOMAIN_CORNER: - return mesh_->totloop; - case ATTR_DOMAIN_POINT: - return mesh_->totvert; - case ATTR_DOMAIN_EDGE: - return mesh_->totedge; - case ATTR_DOMAIN_FACE: - return mesh_->totpoly; - default: - break; - } - return 0; -} - namespace blender::bke { template @@ -747,9 +726,10 @@ static GVArray adapt_mesh_domain_edge_to_face(const Mesh &mesh, const GVArray &v } // namespace blender::bke -blender::GVArray MeshComponent::attribute_try_adapt_domain_impl(const blender::GVArray &varray, - const eAttrDomain from_domain, - const eAttrDomain to_domain) const +static blender::GVArray adapt_mesh_attribute_domain(const Mesh &mesh, + const blender::GVArray &varray, + const eAttrDomain from_domain, + const eAttrDomain to_domain) { if (!varray) { return {}; @@ -765,11 +745,11 @@ blender::GVArray MeshComponent::attribute_try_adapt_domain_impl(const blender::G case ATTR_DOMAIN_CORNER: { switch (to_domain) { case ATTR_DOMAIN_POINT: - return blender::bke::adapt_mesh_domain_corner_to_point(*mesh_, varray); + return blender::bke::adapt_mesh_domain_corner_to_point(mesh, varray); case ATTR_DOMAIN_FACE: - return blender::bke::adapt_mesh_domain_corner_to_face(*mesh_, varray); + return blender::bke::adapt_mesh_domain_corner_to_face(mesh, varray); case ATTR_DOMAIN_EDGE: - return blender::bke::adapt_mesh_domain_corner_to_edge(*mesh_, varray); + return blender::bke::adapt_mesh_domain_corner_to_edge(mesh, varray); default: break; } @@ -778,11 +758,11 @@ blender::GVArray MeshComponent::attribute_try_adapt_domain_impl(const blender::G case ATTR_DOMAIN_POINT: { switch (to_domain) { case ATTR_DOMAIN_CORNER: - return blender::bke::adapt_mesh_domain_point_to_corner(*mesh_, varray); + return blender::bke::adapt_mesh_domain_point_to_corner(mesh, varray); case ATTR_DOMAIN_FACE: - return blender::bke::adapt_mesh_domain_point_to_face(*mesh_, varray); + return blender::bke::adapt_mesh_domain_point_to_face(mesh, varray); case ATTR_DOMAIN_EDGE: - return blender::bke::adapt_mesh_domain_point_to_edge(*mesh_, varray); + return blender::bke::adapt_mesh_domain_point_to_edge(mesh, varray); default: break; } @@ -791,11 +771,11 @@ blender::GVArray MeshComponent::attribute_try_adapt_domain_impl(const blender::G case ATTR_DOMAIN_FACE: { switch (to_domain) { case ATTR_DOMAIN_POINT: - return blender::bke::adapt_mesh_domain_face_to_point(*mesh_, varray); + return blender::bke::adapt_mesh_domain_face_to_point(mesh, varray); case ATTR_DOMAIN_CORNER: - return blender::bke::adapt_mesh_domain_face_to_corner(*mesh_, varray); + return blender::bke::adapt_mesh_domain_face_to_corner(mesh, varray); case ATTR_DOMAIN_EDGE: - return blender::bke::adapt_mesh_domain_face_to_edge(*mesh_, varray); + return blender::bke::adapt_mesh_domain_face_to_edge(mesh, varray); default: break; } @@ -804,11 +784,11 @@ blender::GVArray MeshComponent::attribute_try_adapt_domain_impl(const blender::G case ATTR_DOMAIN_EDGE: { switch (to_domain) { case ATTR_DOMAIN_CORNER: - return blender::bke::adapt_mesh_domain_edge_to_corner(*mesh_, varray); + return blender::bke::adapt_mesh_domain_edge_to_corner(mesh, varray); case ATTR_DOMAIN_POINT: - return blender::bke::adapt_mesh_domain_edge_to_point(*mesh_, varray); + return blender::bke::adapt_mesh_domain_edge_to_point(mesh, varray); case ATTR_DOMAIN_FACE: - return blender::bke::adapt_mesh_domain_edge_to_face(*mesh_, varray); + return blender::bke::adapt_mesh_domain_edge_to_face(mesh, varray); default: break; } @@ -821,20 +801,6 @@ blender::GVArray MeshComponent::attribute_try_adapt_domain_impl(const blender::G return {}; } -static Mesh *get_mesh_from_component_for_write(GeometryComponent &component) -{ - BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); - MeshComponent &mesh_component = static_cast(component); - return mesh_component.get_for_write(); -} - -static const Mesh *get_mesh_from_component_for_read(const GeometryComponent &component) -{ - BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); - const MeshComponent &mesh_component = static_cast(component); - return mesh_component.get_for_read(); -} - namespace blender::bke { template @@ -864,9 +830,9 @@ static void set_vertex_position(MVert &vert, float3 position) copy_v3_v3(vert.co, position); } -static void tag_component_positions_changed(GeometryComponent &component) +static void tag_component_positions_changed(void *owner) { - Mesh *mesh = get_mesh_from_component_for_write(component); + Mesh *mesh = static_cast(owner); if (mesh != nullptr) { BKE_mesh_tag_coords_changed(mesh); } @@ -1001,15 +967,13 @@ class VArrayImpl_For_VertexWeights final : public VMutableArrayImpl { */ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { public: - ReadAttributeLookup try_get_for_read(const GeometryComponent &component, - const AttributeIDRef &attribute_id) const final + GAttributeReader try_get_for_read(const void *owner, + const AttributeIDRef &attribute_id) const final { - BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); if (!attribute_id.is_named()) { return {}; } - const MeshComponent &mesh_component = static_cast(component); - const Mesh *mesh = mesh_component.get_for_read(); + const Mesh *mesh = static_cast(owner); if (mesh == nullptr) { return {}; } @@ -1028,15 +992,12 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { ATTR_DOMAIN_POINT}; } - WriteAttributeLookup try_get_for_write(GeometryComponent &component, - const AttributeIDRef &attribute_id) const final + GAttributeWriter try_get_for_write(void *owner, const AttributeIDRef &attribute_id) const final { - BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); if (!attribute_id.is_named()) { return {}; } - MeshComponent &mesh_component = static_cast(component); - Mesh *mesh = mesh_component.get_for_write(); + Mesh *mesh = static_cast(owner); if (mesh == nullptr) { return {}; } @@ -1060,14 +1021,12 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { ATTR_DOMAIN_POINT}; } - bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final + bool try_delete(void *owner, const AttributeIDRef &attribute_id) const final { - BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); if (!attribute_id.is_named()) { return false; } - MeshComponent &mesh_component = static_cast(component); - Mesh *mesh = mesh_component.get_for_write(); + Mesh *mesh = static_cast(owner); if (mesh == nullptr) { return true; } @@ -1101,12 +1060,9 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { return true; } - bool foreach_attribute(const GeometryComponent &component, - const AttributeForeachCallback callback) const final + bool foreach_attribute(const void *owner, const AttributeForeachCallback callback) const final { - BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); - const MeshComponent &mesh_component = static_cast(component); - const Mesh *mesh = mesh_component.get_for_read(); + const Mesh *mesh = static_cast(owner); if (mesh == nullptr) { return true; } @@ -1136,35 +1092,34 @@ class NormalAttributeProvider final : public BuiltinAttributeProvider { { } - GVArray try_get_for_read(const GeometryComponent &component) const final + GVArray try_get_for_read(const void *owner) const final { - const MeshComponent &mesh_component = static_cast(component); - const Mesh *mesh = mesh_component.get_for_read(); + const Mesh *mesh = static_cast(owner); if (mesh == nullptr || mesh->totpoly == 0) { return {}; } return VArray::ForSpan({(float3 *)BKE_mesh_poly_normals_ensure(mesh), mesh->totpoly}); } - WriteAttributeLookup try_get_for_write(GeometryComponent &UNUSED(component)) const final + GAttributeWriter try_get_for_write(void *UNUSED(owner)) const final { return {}; } - bool try_delete(GeometryComponent &UNUSED(component)) const final + bool try_delete(void *UNUSED(owner)) const final { return false; } - bool try_create(GeometryComponent &UNUSED(component), - const AttributeInit &UNUSED(initializer)) const final + bool try_create(void *UNUSED(owner), const AttributeInit &UNUSED(initializer)) const final { return false; } - bool exists(const GeometryComponent &component) const final + bool exists(const void *owner) const final { - return component.attribute_domain_num(ATTR_DOMAIN_FACE) != 0; + const Mesh *mesh = static_cast(owner); + return mesh->totpoly != 0; } }; @@ -1174,34 +1129,42 @@ class NormalAttributeProvider final : public BuiltinAttributeProvider { */ static ComponentAttributeProviders create_attribute_providers_for_mesh() { - static auto update_custom_data_pointers = [](GeometryComponent &component) { - if (Mesh *mesh = get_mesh_from_component_for_write(component)) { - BKE_mesh_update_customdata_pointers(mesh, false); - } + static auto update_custom_data_pointers = [](void *owner) { + Mesh *mesh = static_cast(owner); + BKE_mesh_update_customdata_pointers(mesh, false); }; #define MAKE_MUTABLE_CUSTOM_DATA_GETTER(NAME) \ - [](GeometryComponent &component) -> CustomData * { \ - Mesh *mesh = get_mesh_from_component_for_write(component); \ - return mesh ? &mesh->NAME : nullptr; \ + [](void *owner) -> CustomData * { \ + Mesh *mesh = static_cast(owner); \ + return &mesh->NAME; \ } #define MAKE_CONST_CUSTOM_DATA_GETTER(NAME) \ - [](const GeometryComponent &component) -> const CustomData * { \ - const Mesh *mesh = get_mesh_from_component_for_read(component); \ - return mesh ? &mesh->NAME : nullptr; \ + [](const void *owner) -> const CustomData * { \ + const Mesh *mesh = static_cast(owner); \ + return &mesh->NAME; \ + } +#define MAKE_GET_ELEMENT_NUM_GETTER(NAME) \ + [](const void *owner) -> int { \ + const Mesh *mesh = static_cast(owner); \ + return mesh->NAME; \ } static CustomDataAccessInfo corner_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(ldata), MAKE_CONST_CUSTOM_DATA_GETTER(ldata), + MAKE_GET_ELEMENT_NUM_GETTER(totloop), update_custom_data_pointers}; static CustomDataAccessInfo point_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(vdata), MAKE_CONST_CUSTOM_DATA_GETTER(vdata), + MAKE_GET_ELEMENT_NUM_GETTER(totvert), update_custom_data_pointers}; static CustomDataAccessInfo edge_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(edata), MAKE_CONST_CUSTOM_DATA_GETTER(edata), + MAKE_GET_ELEMENT_NUM_GETTER(totedge), update_custom_data_pointers}; static CustomDataAccessInfo face_access = {MAKE_MUTABLE_CUSTOM_DATA_GETTER(pdata), MAKE_CONST_CUSTOM_DATA_GETTER(pdata), + MAKE_GET_ELEMENT_NUM_GETTER(totpoly), update_custom_data_pointers}; #undef MAKE_CONST_CUSTOM_DATA_GETTER @@ -1297,13 +1260,72 @@ static ComponentAttributeProviders create_attribute_providers_for_mesh() &face_custom_data}); } +static AttributeAccessorFunctions get_mesh_accessor_functions() +{ + static const ComponentAttributeProviders providers = create_attribute_providers_for_mesh(); + AttributeAccessorFunctions fn = + attribute_accessor_functions::accessor_functions_for_providers(); + fn.domain_size = [](const void *owner, const eAttrDomain domain) { + if (owner == nullptr) { + return 0; + } + const Mesh &mesh = *static_cast(owner); + switch (domain) { + case ATTR_DOMAIN_POINT: + return mesh.totvert; + case ATTR_DOMAIN_EDGE: + return mesh.totedge; + case ATTR_DOMAIN_FACE: + return mesh.totpoly; + case ATTR_DOMAIN_CORNER: + return mesh.totloop; + default: + return 0; + } + }; + fn.domain_supported = [](const void *UNUSED(owner), const eAttrDomain domain) { + return ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_EDGE, ATTR_DOMAIN_FACE, ATTR_DOMAIN_CORNER); + }; + fn.adapt_domain = [](const void *owner, + const blender::GVArray &varray, + const eAttrDomain from_domain, + const eAttrDomain to_domain) -> blender::GVArray { + if (owner == nullptr) { + return {}; + } + const Mesh &mesh = *static_cast(owner); + return adapt_mesh_attribute_domain(mesh, varray, from_domain, to_domain); + }; + return fn; +} + +static const AttributeAccessorFunctions &get_mesh_accessor_functions_ref() +{ + static const AttributeAccessorFunctions fn = get_mesh_accessor_functions(); + return fn; +} + +AttributeAccessor mesh_attributes(const Mesh &mesh) +{ + return AttributeAccessor(&mesh, get_mesh_accessor_functions_ref()); +} + +MutableAttributeAccessor mesh_attributes_for_write(Mesh &mesh) +{ + return MutableAttributeAccessor(&mesh, get_mesh_accessor_functions_ref()); +} + } // namespace blender::bke -const blender::bke::ComponentAttributeProviders *MeshComponent::get_attribute_providers() const +std::optional MeshComponent::attributes() const +{ + return blender::bke::AttributeAccessor(mesh_, blender::bke::get_mesh_accessor_functions_ref()); +} + +std::optional MeshComponent::attributes_for_write() { - static blender::bke::ComponentAttributeProviders providers = - blender::bke::create_attribute_providers_for_mesh(); - return &providers; + return blender::bke::MutableAttributeAccessor(mesh_, + blender::bke::get_mesh_accessor_functions_ref()); } /** \} */ diff --git a/source/blender/blenkernel/intern/geometry_component_pointcloud.cc b/source/blender/blenkernel/intern/geometry_component_pointcloud.cc index facdbed265d..b439a9ba7f8 100644 --- a/source/blender/blenkernel/intern/geometry_component_pointcloud.cc +++ b/source/blender/blenkernel/intern/geometry_component_pointcloud.cc @@ -2,7 +2,6 @@ #include "DNA_pointcloud_types.h" -#include "BKE_attribute_access.hh" #include "BKE_geometry_set.hh" #include "BKE_lib_id.h" #include "BKE_pointcloud.h" @@ -104,17 +103,6 @@ void PointCloudComponent::ensure_owns_direct_data() /** \name Attribute Access * \{ */ -int PointCloudComponent::attribute_domain_num(const eAttrDomain domain) const -{ - if (pointcloud_ == nullptr) { - return 0; - } - if (domain != ATTR_DOMAIN_POINT) { - return 0; - } - return pointcloud_->totpoint; -} - namespace blender::bke { /** @@ -123,23 +111,22 @@ namespace blender::bke { */ static ComponentAttributeProviders create_attribute_providers_for_point_cloud() { - static auto update_custom_data_pointers = [](GeometryComponent &component) { - PointCloudComponent &pointcloud_component = static_cast(component); - if (PointCloud *pointcloud = pointcloud_component.get_for_write()) { - BKE_pointcloud_update_customdata_pointers(pointcloud); - } + static auto update_custom_data_pointers = [](void *owner) { + PointCloud *pointcloud = static_cast(owner); + BKE_pointcloud_update_customdata_pointers(pointcloud); }; static CustomDataAccessInfo point_access = { - [](GeometryComponent &component) -> CustomData * { - PointCloudComponent &pointcloud_component = static_cast(component); - PointCloud *pointcloud = pointcloud_component.get_for_write(); - return pointcloud ? &pointcloud->pdata : nullptr; + [](void *owner) -> CustomData * { + PointCloud *pointcloud = static_cast(owner); + return &pointcloud->pdata; + }, + [](const void *owner) -> const CustomData * { + const PointCloud *pointcloud = static_cast(owner); + return &pointcloud->pdata; }, - [](const GeometryComponent &component) -> const CustomData * { - const PointCloudComponent &pointcloud_component = static_cast( - component); - const PointCloud *pointcloud = pointcloud_component.get_for_read(); - return pointcloud ? &pointcloud->pdata : nullptr; + [](const void *owner) -> int { + const PointCloud *pointcloud = static_cast(owner); + return pointcloud->totpoint; }, update_custom_data_pointers}; @@ -180,14 +167,67 @@ static ComponentAttributeProviders create_attribute_providers_for_point_cloud() return ComponentAttributeProviders({&position, &radius, &id}, {&point_custom_data}); } +static AttributeAccessorFunctions get_pointcloud_accessor_functions() +{ + static const ComponentAttributeProviders providers = + create_attribute_providers_for_point_cloud(); + AttributeAccessorFunctions fn = + attribute_accessor_functions::accessor_functions_for_providers(); + fn.domain_size = [](const void *owner, const eAttrDomain domain) { + if (owner == nullptr) { + return 0; + } + const PointCloud &pointcloud = *static_cast(owner); + switch (domain) { + case ATTR_DOMAIN_POINT: + return pointcloud.totpoint; + default: + return 0; + } + }; + fn.domain_supported = [](const void *UNUSED(owner), const eAttrDomain domain) { + return domain == ATTR_DOMAIN_POINT; + }; + fn.adapt_domain = [](const void *UNUSED(owner), + const blender::GVArray &varray, + const eAttrDomain from_domain, + const eAttrDomain to_domain) { + if (from_domain == to_domain && from_domain == ATTR_DOMAIN_POINT) { + return varray; + } + return blender::GVArray{}; + }; + return fn; +} + +static const AttributeAccessorFunctions &get_pointcloud_accessor_functions_ref() +{ + static const AttributeAccessorFunctions fn = get_pointcloud_accessor_functions(); + return fn; +} + +AttributeAccessor pointcloud_attributes(const PointCloud &pointcloud) +{ + return AttributeAccessor(&pointcloud, get_pointcloud_accessor_functions_ref()); +} + +MutableAttributeAccessor pointcloud_attributes_for_write(PointCloud &pointcloud) +{ + return MutableAttributeAccessor(&pointcloud, get_pointcloud_accessor_functions_ref()); +} + } // namespace blender::bke -const blender::bke::ComponentAttributeProviders *PointCloudComponent::get_attribute_providers() - const +std::optional PointCloudComponent::attributes() const +{ + return blender::bke::AttributeAccessor(pointcloud_, + blender::bke::get_pointcloud_accessor_functions_ref()); +} + +std::optional PointCloudComponent::attributes_for_write() { - static blender::bke::ComponentAttributeProviders providers = - blender::bke::create_attribute_providers_for_point_cloud(); - return &providers; + return blender::bke::MutableAttributeAccessor( + pointcloud_, blender::bke::get_pointcloud_accessor_functions_ref()); } /** \} */ diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc index 70a39acf620..c6fe8eebc7f 100644 --- a/source/blender/blenkernel/intern/geometry_set.cc +++ b/source/blender/blenkernel/intern/geometry_set.cc @@ -7,7 +7,6 @@ #include "BLT_translation.h" #include "BKE_attribute.h" -#include "BKE_attribute_access.hh" #include "BKE_curves.hh" #include "BKE_geometry_fields.hh" #include "BKE_geometry_set.hh" @@ -59,6 +58,27 @@ GeometryComponent *GeometryComponent::create(GeometryComponentType component_typ return nullptr; } +int GeometryComponent::attribute_domain_size(const eAttrDomain domain) const +{ + if (this->is_empty()) { + return 0; + } + const std::optional attributes = this->attributes(); + if (attributes.has_value()) { + return attributes->domain_size(domain); + } + return 0; +} + +std::optional GeometryComponent::attributes() const +{ + return std::nullopt; +}; +std::optional GeometryComponent::attributes_for_write() +{ + return std::nullopt; +} + void GeometryComponent::user_add() const { users_.fetch_add(1); @@ -444,11 +464,14 @@ void GeometrySet::attribute_foreach(const Span component_ continue; } const GeometryComponent &component = *this->get_component_for_read(component_type); - component.attribute_foreach( - [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { - callback(attribute_id, meta_data, component); - return true; - }); + const std::optional attributes = component.attributes(); + if (attributes.has_value()) { + attributes->for_all( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + callback(attribute_id, meta_data, component); + return true; + }); + } } if (include_instances && this->has_instances()) { const InstancesComponent &instances = *this->get_component_for_read(); @@ -462,7 +485,7 @@ void GeometrySet::gather_attributes_for_propagation( const Span component_types, const GeometryComponentType dst_component_type, bool include_instances, - blender::Map &r_attributes) const + blender::Map &r_attributes) const { using namespace blender; using namespace blender::bke; @@ -475,8 +498,8 @@ void GeometrySet::gather_attributes_for_propagation( [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data, const GeometryComponent &component) { - if (component.attribute_is_builtin(attribute_id)) { - if (!dummy_component->attribute_is_builtin(attribute_id)) { + if (component.attributes()->is_builtin(attribute_id)) { + if (!dummy_component->attributes()->is_builtin(attribute_id)) { /* Don't propagate built-in attributes that are not built-in on the destination * component. */ return; diff --git a/source/blender/blenkernel/intern/mesh_convert.cc b/source/blender/blenkernel/intern/mesh_convert.cc index 9cb3e684667..7ebb3e25fd4 100644 --- a/source/blender/blenkernel/intern/mesh_convert.cc +++ b/source/blender/blenkernel/intern/mesh_convert.cc @@ -1209,9 +1209,7 @@ Mesh *BKE_mesh_new_from_object_to_bmain(Main *bmain, BKE_mesh_nomain_to_mesh(mesh, mesh_in_bmain, nullptr, &CD_MASK_MESH, true); /* Anonymous attributes shouldn't exist on original data. */ - MeshComponent component; - component.replace(mesh_in_bmain, GeometryOwnershipType::Editable); - component.attributes_remove_anonymous(); + blender::bke::mesh_attributes_for_write(*mesh_in_bmain).remove_anonymous(); /* User-count is required because so far mesh was in a limbo, where library management does * not perform any user management (i.e. copy of a mesh will not increase users of materials). */ diff --git a/source/blender/blenkernel/intern/mesh_sample.cc b/source/blender/blenkernel/intern/mesh_sample.cc index b792c5c98b9..dd09a3d6917 100644 --- a/source/blender/blenkernel/intern/mesh_sample.cc +++ b/source/blender/blenkernel/intern/mesh_sample.cc @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include "BKE_attribute_access.hh" #include "BKE_attribute_math.hh" #include "BKE_bvhutils.h" #include "BKE_mesh_runtime.h" @@ -253,12 +252,12 @@ void MeshAttributeInterpolator::sample_data(const GVArray &src, } } -void MeshAttributeInterpolator::sample_attribute(const ReadAttributeLookup &src_attribute, - OutputAttribute &dst_attribute, +void MeshAttributeInterpolator::sample_attribute(const GAttributeReader &src_attribute, + GSpanAttributeWriter &dst_attribute, eAttributeMapMode mode) { if (src_attribute && dst_attribute) { - this->sample_data(src_attribute.varray, src_attribute.domain, mode, dst_attribute.as_span()); + this->sample_data(src_attribute.varray, src_attribute.domain, mode, dst_attribute.span); } } diff --git a/source/blender/blenkernel/intern/spline_base.cc b/source/blender/blenkernel/intern/spline_base.cc index e8c7aff75d1..a674bf7800a 100644 --- a/source/blender/blenkernel/intern/spline_base.cc +++ b/source/blender/blenkernel/intern/spline_base.cc @@ -6,7 +6,6 @@ #include "BLI_task.hh" #include "BLI_timeit.hh" -#include "BKE_attribute_access.hh" #include "BKE_attribute_math.hh" #include "BKE_spline.hh" @@ -21,6 +20,7 @@ using blender::Span; using blender::VArray; using blender::attribute_math::convert_to_static_type; using blender::bke::AttributeIDRef; +using blender::bke::AttributeMetaData; CurveType Spline::type() const { diff --git a/source/blender/draw/intern/draw_cache_impl_curves.cc b/source/blender/draw/intern/draw_cache_impl_curves.cc index 2460482fbbf..d5f188402d6 100644 --- a/source/blender/draw/intern/draw_cache_impl_curves.cc +++ b/source/blender/draw/intern/draw_cache_impl_curves.cc @@ -357,20 +357,20 @@ static void curves_batch_ensure_attribute(const Curves &curves, request.domain == ATTR_DOMAIN_POINT ? curves.geometry.point_num : curves.geometry.curve_num); - CurveComponent component; - component.replace(const_cast(&curves), GeometryOwnershipType::ReadOnly); + const blender::bke::AttributeAccessor attributes = + blender::bke::CurvesGeometry::wrap(curves.geometry).attributes(); /* TODO(@kevindietrich): float4 is used for scalar attributes as the implicit conversion done * by OpenGL to vec4 for a scalar `s` will produce a `vec4(s, 0, 0, 1)`. However, following * the Blender convention, it should be `vec4(s, s, s, 1)`. This could be resolved using a * similar texture state swizzle to map the attribute correctly as for volume attributes, so we * can control the conversion ourselves. */ - blender::VArray attribute = component.attribute_get_for_read( + blender::VArray attribute = attributes.lookup_or_default( request.attribute_name, request.domain, {0.0f, 0.0f, 0.0f, 1.0f}); MutableSpan vbo_span{ static_cast(GPU_vertbuf_get_data(attr_vbo)), - component.attribute_domain_num(request.domain)}; + attributes.domain_size(request.domain)}; attribute.materialize(vbo_span); @@ -629,9 +629,10 @@ static void request_attribute(Curves &curves, const char *name) DRW_Attributes attributes{}; - CurveComponent component; - component.replace(&curves, GeometryOwnershipType::ReadOnly); - std::optional meta_data = component.attribute_get_meta_data(name); + blender::bke::CurvesGeometry &curves_geometry = blender::bke::CurvesGeometry::wrap( + curves.geometry); + std::optional meta_data = + curves_geometry.attributes().lookup_meta_data(name); if (!meta_data) { return; } diff --git a/source/blender/draw/intern/draw_curves.cc b/source/blender/draw/intern/draw_curves.cc index 246f7c843ba..233af08c363 100644 --- a/source/blender/draw/intern/draw_curves.cc +++ b/source/blender/draw/intern/draw_curves.cc @@ -336,9 +336,7 @@ DRWShadingGroup *DRW_shgroup_curves_create_sub(Object *object, const blender::bke::CurvesGeometry &curves = blender::bke::CurvesGeometry::wrap( curves_id.geometry); if (curves.curves_num() >= 1) { - CurveComponent curves_component; - curves_component.replace(&curves_id, GeometryOwnershipType::ReadOnly); - blender::VArray radii = curves_component.attribute_get_for_read( + blender::VArray radii = curves.attributes().lookup_or_default( "radius", ATTR_DOMAIN_POINT, 0.005f); const blender::IndexRange first_curve_points = curves.points_for_curve(0); const float first_radius = radii[first_curve_points.first()]; diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc index 49e21d00195..a4492a1d516 100644 --- a/source/blender/editors/curves/intern/curves_ops.cc +++ b/source/blender/editors/curves/intern/curves_ops.cc @@ -533,9 +533,9 @@ static void snap_curves_to_surface_exec_object(Object &curves_ob, VArraySpan surface_uv_map; if (curves_id.surface_uv_map != nullptr) { - surface_uv_map = surface_mesh_component - .attribute_try_get_for_read( - curves_id.surface_uv_map, ATTR_DOMAIN_CORNER, CD_PROP_FLOAT2) + const bke::AttributeAccessor surface_attributes = bke::mesh_attributes(surface_mesh); + surface_uv_map = surface_attributes + .lookup(curves_id.surface_uv_map, ATTR_DOMAIN_CORNER, CD_PROP_FLOAT2) .typed(); } @@ -756,21 +756,20 @@ static int curves_set_selection_domain_exec(bContext *C, wmOperator *op) curves_id->selection_domain = domain; curves_id->flag |= CV_SCULPT_SELECTION_ENABLED; - CurveComponent component; - component.replace(curves_id, GeometryOwnershipType::Editable); CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry); + bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); if (old_domain == ATTR_DOMAIN_POINT && domain == ATTR_DOMAIN_CURVE) { VArray curve_selection = curves.adapt_domain( curves.selection_point_float(), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE); curve_selection.materialize(curves.selection_curve_float_for_write()); - component.attribute_try_delete(".selection_point_float"); + attributes.remove(".selection_point_float"); } else if (old_domain == ATTR_DOMAIN_CURVE && domain == ATTR_DOMAIN_POINT) { VArray point_selection = curves.adapt_domain( curves.selection_curve_float(), ATTR_DOMAIN_CURVE, ATTR_DOMAIN_POINT); point_selection.materialize(curves.selection_point_float_for_write()); - component.attribute_try_delete(".selection_curve_float"); + attributes.remove(".selection_curve_float"); } /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic @@ -900,15 +899,14 @@ static int select_all_exec(bContext *C, wmOperator *op) } for (Curves *curves_id : unique_curves) { + CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry); if (action == SEL_SELECT) { /* As an optimization, just remove the selection attributes when everything is selected. */ - CurveComponent component; - component.replace(curves_id, GeometryOwnershipType::Editable); - component.attribute_try_delete(".selection_point_float"); - component.attribute_try_delete(".selection_curve_float"); + bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); + attributes.remove(".selection_point_float"); + attributes.remove(".selection_curve_float"); } else { - CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry); MutableSpan selection = curves_id->selection_domain == ATTR_DOMAIN_POINT ? curves.selection_point_float_for_write() : curves.selection_curve_float_for_write(); diff --git a/source/blender/editors/geometry/geometry_attributes.cc b/source/blender/editors/geometry/geometry_attributes.cc index c7e782b7b89..4cf14334ac7 100644 --- a/source/blender/editors/geometry/geometry_attributes.cc +++ b/source/blender/editors/geometry/geometry_attributes.cc @@ -282,8 +282,7 @@ static int geometry_attribute_convert_exec(bContext *C, wmOperator *op) RNA_enum_get(op->ptr, "mode")); Mesh *mesh = reinterpret_cast(ob_data); - MeshComponent mesh_component; - mesh_component.replace(mesh, GeometryOwnershipType::Editable); + bke::MutableAttributeAccessor attributes = bke::mesh_attributes_for_write(*mesh); /* General conversion steps are always the same: * 1. Convert old data to right domain and data type. @@ -301,33 +300,33 @@ static int geometry_attribute_convert_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - GVArray src_varray = mesh_component.attribute_get_for_read(name, dst_domain, dst_type); + GVArray src_varray = attributes.lookup_or_default(name, dst_domain, dst_type); const CPPType &cpp_type = src_varray.type(); void *new_data = MEM_malloc_arrayN(src_varray.size(), cpp_type.size(), __func__); src_varray.materialize_to_uninitialized(new_data); - mesh_component.attribute_try_delete(name); - mesh_component.attribute_try_create(name, dst_domain, dst_type, AttributeInitMove(new_data)); + attributes.remove(name); + attributes.add(name, dst_domain, dst_type, blender::bke::AttributeInitMove(new_data)); break; } case ConvertAttributeMode::UVMap: { MLoopUV *dst_uvs = static_cast( MEM_calloc_arrayN(mesh->totloop, sizeof(MLoopUV), __func__)); - VArray src_varray = mesh_component.attribute_get_for_read( + VArray src_varray = attributes.lookup_or_default( name, ATTR_DOMAIN_CORNER, {0.0f, 0.0f}); for (const int i : IndexRange(mesh->totloop)) { copy_v2_v2(dst_uvs[i].uv, src_varray[i]); } - mesh_component.attribute_try_delete(name); + attributes.remove(name); CustomData_add_layer_named( &mesh->ldata, CD_MLOOPUV, CD_ASSIGN, dst_uvs, mesh->totloop, name.c_str()); break; } case ConvertAttributeMode::VertexGroup: { Array src_weights(mesh->totvert); - VArray src_varray = mesh_component.attribute_get_for_read( + VArray src_varray = attributes.lookup_or_default( name, ATTR_DOMAIN_POINT, 0.0f); src_varray.materialize(src_weights); - mesh_component.attribute_try_delete(name); + attributes.remove(name); bDeformGroup *defgroup = BKE_object_defgroup_new(ob, name.c_str()); const int defgroup_index = BLI_findindex(BKE_id_defgroup_list_get(&mesh->id), defgroup); @@ -652,15 +651,16 @@ bool ED_geometry_attribute_convert(Mesh *mesh, return false; } - MeshComponent mesh_component; - mesh_component.replace(mesh, GeometryOwnershipType::Editable); - GVArray src_varray = mesh_component.attribute_get_for_read(name, new_domain, new_type); + blender::bke::MutableAttributeAccessor attributes = blender::bke::mesh_attributes_for_write( + *mesh); + + GVArray src_varray = attributes.lookup_or_default(name, new_domain, new_type); const CPPType &cpp_type = src_varray.type(); void *new_data = MEM_malloc_arrayN(src_varray.size(), cpp_type.size(), __func__); src_varray.materialize_to_uninitialized(new_data); - mesh_component.attribute_try_delete(name); - mesh_component.attribute_try_create(name, new_domain, new_type, AttributeInitMove(new_data)); + attributes.remove(name); + attributes.add(name, new_domain, new_type, blender::bke::AttributeInitMove(new_data)); int *active_index = BKE_id_attributes_active_index_p(&mesh->id); if (*active_index > 0) { diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc index a2c08109ec0..66e76addd6f 100644 --- a/source/blender/editors/object/object_add.cc +++ b/source/blender/editors/object/object_add.cc @@ -3177,9 +3177,7 @@ static int object_convert_exec(bContext *C, wmOperator *op) } /* Anonymous attributes shouldn't be available on the applied geometry. */ - MeshComponent component; - component.replace(new_mesh, GeometryOwnershipType::Editable); - component.attributes_remove_anonymous(); + blender::bke::mesh_attributes_for_write(*new_mesh).remove_anonymous(); BKE_object_free_modifiers(newob, 0); /* after derivedmesh calls! */ } diff --git a/source/blender/editors/object/object_modifier.cc b/source/blender/editors/object/object_modifier.cc index 202c6d96a47..69edd00ae24 100644 --- a/source/blender/editors/object/object_modifier.cc +++ b/source/blender/editors/object/object_modifier.cc @@ -757,9 +757,7 @@ static bool modifier_apply_obdata( BKE_mesh_nomain_to_mesh(mesh_applied, me, ob, &CD_MASK_MESH, true); /* Anonymous attributes shouldn't be available on the applied geometry. */ - MeshComponent component; - component.replace(me, GeometryOwnershipType::Editable); - component.attributes_remove_anonymous(); + blender::bke::mesh_attributes_for_write(*me).remove_anonymous(); if (md_eval->type == eModifierType_Multires) { multires_customdata_delete(me); @@ -828,11 +826,12 @@ static bool modifier_apply_obdata( BKE_report(reports, RPT_ERROR, "Evaluated geometry from modifier does not contain curves"); return false; } - CurveComponent &component = geometry_set.get_component_for_write(); Curves &curves_eval = *geometry_set.get_curves_for_write(); /* Anonymous attributes shouldn't be available on the applied geometry. */ - component.attributes_remove_anonymous(); + blender::bke::CurvesGeometry::wrap(curves_eval.geometry) + .attributes_for_write() + .remove_anonymous(); /* If the modifier's output is a different curves data-block, copy the relevant information to * the original. */ diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc index 401550b3b3a..26145a386f5 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc @@ -176,12 +176,9 @@ struct AddOperationExecutor { /* Find UV map. */ VArraySpan surface_uv_map; if (curves_id_->surface_uv_map != nullptr) { - MeshComponent surface_component; - surface_component.replace(surface_, GeometryOwnershipType::ReadOnly); - surface_uv_map = surface_component - .attribute_try_get_for_read(curves_id_->surface_uv_map, - ATTR_DOMAIN_CORNER) - .typed(); + const bke::AttributeAccessor surface_attributes = bke::mesh_attributes(*surface_); + surface_uv_map = surface_attributes.lookup(curves_id_->surface_uv_map, + ATTR_DOMAIN_CORNER); } /* Find normals. */ diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc index 826b0611e81..e211a568705 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc @@ -64,7 +64,6 @@ struct DensityAddOperationExecutor { Mesh *surface_ = nullptr; Span surface_looptris_; Span corner_normals_su_; - VArraySpan surface_uv_map_; const CurvesSculpt *curves_sculpt_ = nullptr; const Brush *brush_ = nullptr; @@ -228,12 +227,9 @@ struct DensityAddOperationExecutor { /* Find UV map. */ VArraySpan surface_uv_map; if (curves_id_->surface_uv_map != nullptr) { - MeshComponent surface_component; - surface_component.replace(surface_, GeometryOwnershipType::ReadOnly); - surface_uv_map = surface_component - .attribute_try_get_for_read(curves_id_->surface_uv_map, - ATTR_DOMAIN_CORNER) - .typed(); + bke::AttributeAccessor surface_attributes = bke::mesh_attributes(*surface_); + surface_uv_map = surface_attributes.lookup(curves_id_->surface_uv_map, + ATTR_DOMAIN_CORNER); } /* Find normals. */ diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc b/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc index dedc880988d..aabe6fd93e4 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc @@ -148,12 +148,9 @@ struct SlideOperationExecutor { BKE_mesh_runtime_looptri_len(surface_)}; if (curves_id_->surface_uv_map != nullptr) { - MeshComponent surface_component; - surface_component.replace(surface_, GeometryOwnershipType::ReadOnly); - surface_uv_map_ = surface_component - .attribute_try_get_for_read(curves_id_->surface_uv_map, - ATTR_DOMAIN_CORNER) - .typed(); + const bke::AttributeAccessor surface_attributes = bke::mesh_attributes(*surface_); + surface_uv_map_ = surface_attributes.lookup(curves_id_->surface_uv_map, + ATTR_DOMAIN_CORNER); } if (stroke_extension.is_first) { diff --git a/source/blender/editors/sculpt_paint/paint_vertex_color_ops.cc b/source/blender/editors/sculpt_paint/paint_vertex_color_ops.cc index 50f8854d90f..8b726c7b942 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex_color_ops.cc +++ b/source/blender/editors/sculpt_paint/paint_vertex_color_ops.cc @@ -92,11 +92,9 @@ static bool vertex_paint_from_weight(Object *ob) return false; } - MeshComponent component; - component.replace(me, GeometryOwnershipType::Editable); + bke::MutableAttributeAccessor attributes = bke::mesh_attributes_for_write(*me); - bke::WriteAttributeLookup color_attribute = component.attribute_try_get_for_write( - active_color_layer->name); + bke::GAttributeWriter color_attribute = attributes.lookup_for_write(active_color_layer->name); if (!color_attribute) { BLI_assert_unreachable(); return false; @@ -104,7 +102,7 @@ static bool vertex_paint_from_weight(Object *ob) /* Retrieve the vertex group with the domain and type of the existing color * attribute, in order to let the attribute API handle both conversions. */ - const GVArray vertex_group = component.attribute_get_for_read( + const GVArray vertex_group = attributes.lookup( deform_group->name, ATTR_DOMAIN_POINT, bke::cpp_type_to_custom_data_type(color_attribute.varray.type())); @@ -113,14 +111,11 @@ static bool vertex_paint_from_weight(Object *ob) return false; } - GVArraySpan interpolated{component.attribute_try_adapt_domain( - vertex_group, ATTR_DOMAIN_POINT, color_attribute.domain)}; + GVArraySpan interpolated{ + attributes.adapt_domain(vertex_group, ATTR_DOMAIN_POINT, color_attribute.domain)}; color_attribute.varray.set_all(interpolated.data()); - - if (color_attribute.tag_modified_fn) { - color_attribute.tag_modified_fn(); - } + color_attribute.finish(); tag_object_after_update(ob); return true; @@ -167,29 +162,28 @@ static IndexMask get_selected_indices(const Mesh &mesh, Span verts(mesh.mvert, mesh.totvert); Span faces(mesh.mpoly, mesh.totpoly); - MeshComponent component; - component.replace(&const_cast(mesh), GeometryOwnershipType::ReadOnly); + bke::AttributeAccessor attributes = bke::mesh_attributes(mesh); if (mesh.editflag & ME_EDIT_PAINT_FACE_SEL) { - const VArray selection = component.attribute_try_adapt_domain( + const VArray selection = attributes.adapt_domain( VArray::ForFunc(faces.size(), [&](const int i) { return faces[i].flag & ME_FACE_SEL; }), ATTR_DOMAIN_FACE, domain); return index_mask_ops::find_indices_from_virtual_array( - IndexMask(component.attribute_domain_num(domain)), selection, 4096, indices); + IndexMask(attributes.domain_size(domain)), selection, 4096, indices); } if (mesh.editflag & ME_EDIT_PAINT_VERT_SEL) { - const VArray selection = component.attribute_try_adapt_domain( + const VArray selection = attributes.adapt_domain( VArray::ForFunc(verts.size(), [&](const int i) { return verts[i].flag & SELECT; }), ATTR_DOMAIN_POINT, domain); return index_mask_ops::find_indices_from_virtual_array( - IndexMask(component.attribute_domain_num(domain)), selection, 4096, indices); + IndexMask(attributes.domain_size(domain)), selection, 4096, indices); } - return IndexMask(component.attribute_domain_num(domain)); + return IndexMask(attributes.domain_size(domain)); } static void face_corner_color_equalize_vertices(Mesh &mesh, const IndexMask selection) @@ -202,17 +196,15 @@ static void face_corner_color_equalize_vertices(Mesh &mesh, const IndexMask sele return; } - MeshComponent component; - component.replace(&mesh, GeometryOwnershipType::Editable); + bke::AttributeAccessor attributes = bke::mesh_attributes(mesh); - if (component.attribute_get_meta_data(active_color_layer->name)->domain == ATTR_DOMAIN_POINT) { + if (attributes.lookup_meta_data(active_color_layer->name)->domain == ATTR_DOMAIN_POINT) { return; } - GVArray color_attribute_point = component.attribute_try_get_for_read(active_color_layer->name, - ATTR_DOMAIN_POINT); + GVArray color_attribute_point = attributes.lookup(active_color_layer->name, ATTR_DOMAIN_POINT); - GVArray color_attribute_corner = component.attribute_try_adapt_domain( + GVArray color_attribute_corner = attributes.adapt_domain( color_attribute_point, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CORNER); color_attribute_corner.materialize(selection, active_color_layer->data); @@ -278,11 +270,9 @@ static bool transform_active_color(Mesh &mesh, const TransformFn &transform_fn) return false; } - MeshComponent component; - component.replace(&mesh, GeometryOwnershipType::Editable); + bke::MutableAttributeAccessor attributes = bke::mesh_attributes_for_write(mesh); - bke::WriteAttributeLookup color_attribute = component.attribute_try_get_for_write( - active_color_layer->name); + bke::GAttributeWriter color_attribute = attributes.lookup_for_write(active_color_layer->name); if (!color_attribute) { BLI_assert_unreachable(); return false; @@ -310,6 +300,8 @@ static bool transform_active_color(Mesh &mesh, const TransformFn &transform_fn) }); }); + color_attribute.finish(); + DEG_id_tag_update(&mesh.id, 0); return true; diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc index 6be3a65cc1b..2a87c51da5d 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc @@ -3,6 +3,7 @@ #include "BLI_index_mask_ops.hh" #include "BLI_virtual_array.hh" +#include "BKE_attribute.hh" #include "BKE_context.h" #include "BKE_editmesh.h" #include "BKE_geometry_fields.hh" @@ -65,7 +66,12 @@ std::unique_ptr ExtraColumns::get_column_values( void GeometryDataSource::foreach_default_column_ids( FunctionRef fn) const { - if (component_->attribute_domain_num(domain_) == 0) { + if (!component_->attributes().has_value()) { + return; + } + const bke::AttributeAccessor attributes = *component_->attributes(); + + if (attributes.domain_size(domain_) == 0) { return; } @@ -74,8 +80,9 @@ void GeometryDataSource::foreach_default_column_ids( } extra_columns_.foreach_default_column_ids(fn); - component_->attribute_foreach( - [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + + attributes.for_all( + [&](const bke::AttributeIDRef &attribute_id, const bke::AttributeMetaData &meta_data) { if (meta_data.domain != domain_) { return true; } @@ -114,7 +121,11 @@ void GeometryDataSource::foreach_default_column_ids( std::unique_ptr GeometryDataSource::get_column_values( const SpreadsheetColumnID &column_id) const { - const int domain_num = component_->attribute_domain_num(domain_); + if (!component_->attributes().has_value()) { + return {}; + } + const bke::AttributeAccessor attributes = *component_->attributes(); + const int domain_num = attributes.domain_size(domain_); if (domain_num == 0) { return {}; } @@ -200,7 +211,7 @@ std::unique_ptr GeometryDataSource::get_column_values( } } - bke::ReadAttributeLookup attribute = component_->attribute_try_get_for_read(column_id.name); + bke::GAttributeReader attribute = attributes.lookup(column_id.name); if (!attribute) { return {}; } @@ -214,7 +225,11 @@ std::unique_ptr GeometryDataSource::get_column_values( int GeometryDataSource::tot_rows() const { - return component_->attribute_domain_num(domain_); + if (!component_->attributes().has_value()) { + return {}; + } + const bke::AttributeAccessor attributes = *component_->attributes(); + return attributes.domain_size(domain_); } /** @@ -253,7 +268,7 @@ IndexMask GeometryDataSource::apply_selection_filter(Vector &indices) c const int *orig_indices = (int *)CustomData_get_layer(&mesh_eval->vdata, CD_ORIGINDEX); if (orig_indices != nullptr) { /* Use CD_ORIGINDEX layer if it exists. */ - VArray selection = mesh_component->attribute_try_adapt_domain( + VArray selection = mesh_component->attributes()->adapt_domain( VArray::ForFunc(mesh_eval->totvert, [bm, orig_indices](int vertex_index) -> bool { const int i_orig = orig_indices[vertex_index]; @@ -273,7 +288,7 @@ IndexMask GeometryDataSource::apply_selection_filter(Vector &indices) c if (mesh_eval->totvert == bm->totvert) { /* Use a simple heuristic to match original vertices to evaluated ones. */ - VArray selection = mesh_component->attribute_try_adapt_domain( + VArray selection = mesh_component->attributes()->adapt_domain( VArray::ForFunc(mesh_eval->totvert, [bm](int vertex_index) -> bool { BMVert *vert = bm->vtable[vertex_index]; @@ -511,7 +526,7 @@ static void add_fields_as_extra_columns(SpaceSpreadsheet *sspreadsheet, std::make_unique(component)); const eAttrDomain domain = (eAttrDomain)sspreadsheet->attribute_domain; - const int domain_num = component.attribute_domain_num(domain); + const int domain_num = component.attributes()->domain_size(domain); for (const auto item : fields_to_show.items()) { const StringRef name = item.key; const GField &field = item.value; diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc b/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc index ee22f4173ab..aa9b867264a 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc @@ -194,7 +194,7 @@ std::optional GeometryDataSetTreeViewItem::count() const } if (const GeometryComponent *component = geometry.get_component_for_read(component_type_)) { - return component->attribute_domain_num(*domain_); + return component->attribute_domain_size(*domain_); } return 0; diff --git a/source/blender/geometry/intern/mesh_primitive_cuboid.cc b/source/blender/geometry/intern/mesh_primitive_cuboid.cc index 07ac2419ad9..486d8adbf39 100644 --- a/source/blender/geometry/intern/mesh_primitive_cuboid.cc +++ b/source/blender/geometry/intern/mesh_primitive_cuboid.cc @@ -7,7 +7,6 @@ #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" -#include "BKE_attribute_access.hh" #include "BKE_geometry_set.hh" #include "BKE_mesh.h" @@ -322,11 +321,10 @@ static void calculate_polys(const CuboidConfig &config, static void calculate_uvs(const CuboidConfig &config, Mesh *mesh, const bke::AttributeIDRef &uv_id) { - MeshComponent mesh_component; - mesh_component.replace(mesh, GeometryOwnershipType::Editable); - bke::OutputAttribute_Typed uv_attribute = - mesh_component.attribute_try_get_for_output_only(uv_id, ATTR_DOMAIN_CORNER); - MutableSpan uvs = uv_attribute.as_span(); + bke::MutableAttributeAccessor attributes = bke::mesh_attributes_for_write(*mesh); + bke::SpanAttributeWriter uv_attribute = + attributes.lookup_or_add_for_write_only_span(uv_id, ATTR_DOMAIN_CORNER); + MutableSpan uvs = uv_attribute.span; int loop_index = 0; @@ -394,7 +392,7 @@ static void calculate_uvs(const CuboidConfig &config, Mesh *mesh, const bke::Att } } - uv_attribute.save(); + uv_attribute.finish(); } Mesh *create_cuboid_mesh(const float3 &size, diff --git a/source/blender/geometry/intern/mesh_to_curve_convert.cc b/source/blender/geometry/intern/mesh_to_curve_convert.cc index 38f9da2a60c..681dfd15137 100644 --- a/source/blender/geometry/intern/mesh_to_curve_convert.cc +++ b/source/blender/geometry/intern/mesh_to_curve_convert.cc @@ -8,7 +8,7 @@ #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" -#include "BKE_attribute_access.hh" +#include "BKE_attribute.hh" #include "BKE_attribute_math.hh" #include "BKE_curves.hh" #include "BKE_geometry_set.hh" @@ -44,14 +44,13 @@ static Curves *create_curve_from_vert_indices(const MeshComponent &mesh_componen curves.cyclic_for_write().fill(false); curves.cyclic_for_write().slice(cyclic_curves).fill(true); - Set source_attribute_ids = mesh_component.attribute_ids(); + bke::MutableAttributeAccessor curves_attributes = curves.attributes_for_write(); + const bke::AttributeAccessor mesh_attributes = *mesh_component.attributes(); - CurveComponent curves_component; - curves_component.replace(curves_id, GeometryOwnershipType::Editable); + Set source_attribute_ids = mesh_attributes.all_ids(); for (const bke::AttributeIDRef &attribute_id : source_attribute_ids) { - if (mesh_component.attribute_is_builtin(attribute_id) && - !curves_component.attribute_is_builtin(attribute_id)) { + if (mesh_attributes.is_builtin(attribute_id) && !curves_attributes.is_builtin(attribute_id)) { /* Don't copy attributes that are built-in on meshes but not on curves. */ continue; } @@ -60,8 +59,7 @@ static Curves *create_curve_from_vert_indices(const MeshComponent &mesh_componen continue; } - const GVArray mesh_attribute = mesh_component.attribute_try_get_for_read(attribute_id, - ATTR_DOMAIN_POINT); + const GVArray mesh_attribute = mesh_attributes.lookup(attribute_id, ATTR_DOMAIN_POINT); /* Some attributes might not exist if they were builtin attribute on domains that don't * have any elements, i.e. a face attribute on the output of the line primitive node. */ if (!mesh_attribute) { @@ -71,10 +69,10 @@ static Curves *create_curve_from_vert_indices(const MeshComponent &mesh_componen /* Copy attribute based on the map for this curve. */ attribute_math::convert_to_static_type(mesh_attribute.type(), [&](auto dummy) { using T = decltype(dummy); - bke::OutputAttribute_Typed attribute = - curves_component.attribute_try_get_for_output_only(attribute_id, ATTR_DOMAIN_POINT); - copy_with_map(mesh_attribute.typed(), vert_indices, attribute.as_span()); - attribute.save(); + bke::SpanAttributeWriter attribute = + curves_attributes.lookup_or_add_for_write_only_span(attribute_id, ATTR_DOMAIN_POINT); + copy_with_map(mesh_attribute.typed(), vert_indices, attribute.span); + attribute.finish(); }); } diff --git a/source/blender/geometry/intern/point_merge_by_distance.cc b/source/blender/geometry/intern/point_merge_by_distance.cc index 6639ff650d3..40bc02cbd34 100644 --- a/source/blender/geometry/intern/point_merge_by_distance.cc +++ b/source/blender/geometry/intern/point_merge_by_distance.cc @@ -104,24 +104,24 @@ PointCloud *point_merge_by_distance(const PointCloudComponent &src_points, point_merge_counts[dst_index]++; } - Set attributes = src_points.attribute_ids(); + const bke::AttributeAccessor src_attributes = *src_points.attributes(); + bke::MutableAttributeAccessor dst_attributes = *dst_points.attributes_for_write(); + Set attributes = src_attributes.all_ids(); /* Transfer the ID attribute if it exists, using the ID of the first merged point. */ if (attributes.contains("id")) { - VArray src = src_points.attribute_get_for_read("id", ATTR_DOMAIN_POINT, 0); - bke::OutputAttribute_Typed dst = dst_points.attribute_try_get_for_output_only( + VArraySpan src = src_attributes.lookup_or_default("id", ATTR_DOMAIN_POINT, 0); + bke::SpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span( "id", ATTR_DOMAIN_POINT); - Span src_ids = src.get_internal_span(); - MutableSpan dst_ids = dst.as_span(); threading::parallel_for(IndexRange(dst_size), 1024, [&](IndexRange range) { for (const int i_dst : range) { const IndexRange points(map_offsets[i_dst], map_offsets[i_dst + 1] - map_offsets[i_dst]); - dst_ids[i_dst] = src_ids[points.first()]; + dst.span[i_dst] = src[points.first()]; } }); - dst.save(); + dst.finish(); attributes.remove_contained("id"); } @@ -131,20 +131,19 @@ PointCloud *point_merge_by_distance(const PointCloudComponent &src_points, continue; } - bke::ReadAttributeLookup src_attribute = src_points.attribute_try_get_for_read(id); + bke::GAttributeReader src_attribute = src_attributes.lookup(id); attribute_math::convert_to_static_type(src_attribute.varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v>) { - bke::OutputAttribute_Typed dst_attribute = - dst_points.attribute_try_get_for_output_only(id, ATTR_DOMAIN_POINT); - Span src = src_attribute.varray.get_internal_span().typed(); - MutableSpan dst = dst_attribute.as_span(); + bke::SpanAttributeWriter dst_attribute = + dst_attributes.lookup_or_add_for_write_only_span(id, ATTR_DOMAIN_POINT); + VArraySpan src = src_attribute.varray.typed(); threading::parallel_for(IndexRange(dst_size), 1024, [&](IndexRange range) { for (const int i_dst : range) { /* Create a separate mixer for every point to avoid allocating temporary buffers * in the mixer the size of the result point cloud and to improve memory locality. */ - attribute_math::DefaultMixer mixer{dst.slice(i_dst, 1)}; + attribute_math::DefaultMixer mixer{dst_attribute.span.slice(i_dst, 1)}; const IndexRange points(map_offsets[i_dst], map_offsets[i_dst + 1] - map_offsets[i_dst]); @@ -157,7 +156,7 @@ PointCloud *point_merge_by_distance(const PointCloudComponent &src_points, } }); - dst_attribute.save(); + dst_attribute.finish(); } }); } diff --git a/source/blender/geometry/intern/realize_instances.cc b/source/blender/geometry/intern/realize_instances.cc index 060a1aa7603..25bf00c5783 100644 --- a/source/blender/geometry/intern/realize_instances.cc +++ b/source/blender/geometry/intern/realize_instances.cc @@ -23,12 +23,13 @@ namespace blender::geometry { using blender::bke::AttributeIDRef; +using blender::bke::AttributeKind; +using blender::bke::AttributeMetaData; using blender::bke::custom_data_type_to_cpp_type; using blender::bke::CustomDataAttributes; +using blender::bke::GSpanAttributeWriter; using blender::bke::object_get_evaluated_geometry_set; -using blender::bke::OutputAttribute; -using blender::bke::OutputAttribute_Typed; -using blender::bke::ReadAttributeLookup; +using blender::bke::SpanAttributeWriter; /** * An ordered set of attribute ids. Attributes are ordered to avoid name lookups in many places. @@ -287,26 +288,27 @@ static void copy_generic_attributes_to_result( const AttributeFallbacksArray &attribute_fallbacks, const OrderedAttributes &ordered_attributes, const FunctionRef &range_fn, - MutableSpan dst_attributes) + MutableSpan dst_attribute_writers) { - threading::parallel_for(dst_attributes.index_range(), 10, [&](const IndexRange attribute_range) { - for (const int attribute_index : attribute_range) { - const eAttrDomain domain = ordered_attributes.kinds[attribute_index].domain; - const IndexRange element_slice = range_fn(domain); - - GMutableSpan dst_span = dst_attributes[attribute_index].slice(element_slice); - if (src_attributes[attribute_index].has_value()) { - threaded_copy(*src_attributes[attribute_index], dst_span); - } - else { - const CPPType &cpp_type = dst_span.type(); - const void *fallback = attribute_fallbacks.array[attribute_index] == nullptr ? - cpp_type.default_value() : - attribute_fallbacks.array[attribute_index]; - threaded_fill({cpp_type, fallback}, dst_span); - } - } - }); + threading::parallel_for( + dst_attribute_writers.index_range(), 10, [&](const IndexRange attribute_range) { + for (const int attribute_index : attribute_range) { + const eAttrDomain domain = ordered_attributes.kinds[attribute_index].domain; + const IndexRange element_slice = range_fn(domain); + + GMutableSpan dst_span = dst_attribute_writers[attribute_index].span.slice(element_slice); + if (src_attributes[attribute_index].has_value()) { + threaded_copy(*src_attributes[attribute_index], dst_span); + } + else { + const CPPType &cpp_type = dst_span.type(); + const void *fallback = attribute_fallbacks.array[attribute_index] == nullptr ? + cpp_type.default_value() : + attribute_fallbacks.array[attribute_index]; + threaded_fill({cpp_type, fallback}, dst_span); + } + } + }); } static void create_result_ids(const RealizeInstancesOptions &options, @@ -361,7 +363,7 @@ static Vector> prepare_attribute_fallbacks( const OrderedAttributes &ordered_attributes) { Vector> attributes_to_override; - const CustomDataAttributes &attributes = instances_component.attributes(); + const CustomDataAttributes &attributes = instances_component.instance_attributes(); attributes.foreach_attribute( [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { const int attribute_index = ordered_attributes.ids.index_of_try(attribute_id); @@ -447,7 +449,7 @@ static void gather_realize_tasks_for_instances(GatherTasksInfo &gather_info, Span stored_instance_ids; if (gather_info.create_id_attribute_on_any_component) { - std::optional ids = instances_component.attributes().get_for_read("id"); + std::optional ids = instances_component.instance_attributes().get_for_read("id"); if (ids.has_value()) { stored_instance_ids = ids->typed(); } @@ -646,34 +648,34 @@ static AllPointCloudsInfo preprocess_pointclouds(const GeometrySet &geometry_set pointcloud_info.pointcloud = pointcloud; /* Access attributes. */ - PointCloudComponent component; - component.replace(const_cast(pointcloud), GeometryOwnershipType::ReadOnly); + bke::AttributeAccessor attributes = bke::pointcloud_attributes(*pointcloud); pointcloud_info.attributes.reinitialize(info.attributes.size()); for (const int attribute_index : info.attributes.index_range()) { const AttributeIDRef &attribute_id = info.attributes.ids[attribute_index]; const eCustomDataType data_type = info.attributes.kinds[attribute_index].data_type; const eAttrDomain domain = info.attributes.kinds[attribute_index].domain; - if (component.attribute_exists(attribute_id)) { - GVArray attribute = component.attribute_get_for_read(attribute_id, domain, data_type); + if (attributes.contains(attribute_id)) { + GVArray attribute = attributes.lookup_or_default(attribute_id, domain, data_type); pointcloud_info.attributes[attribute_index].emplace(std::move(attribute)); } } if (info.create_id_attribute) { - ReadAttributeLookup ids_lookup = component.attribute_try_get_for_read("id"); - if (ids_lookup) { - pointcloud_info.stored_ids = ids_lookup.varray.get_internal_span().typed(); + bke::GAttributeReader ids_attribute = attributes.lookup("id"); + if (ids_attribute) { + pointcloud_info.stored_ids = ids_attribute.varray.get_internal_span().typed(); } } } return info; } -static void execute_realize_pointcloud_task(const RealizeInstancesOptions &options, - const RealizePointCloudTask &task, - const OrderedAttributes &ordered_attributes, - PointCloud &dst_pointcloud, - MutableSpan dst_attribute_spans, - MutableSpan all_dst_ids) +static void execute_realize_pointcloud_task( + const RealizeInstancesOptions &options, + const RealizePointCloudTask &task, + const OrderedAttributes &ordered_attributes, + PointCloud &dst_pointcloud, + MutableSpan dst_attribute_writers, + MutableSpan all_dst_ids) { const PointCloudRealizeInfo &pointcloud_info = *task.pointcloud_info; const PointCloud &pointcloud = *pointcloud_info.pointcloud; @@ -699,7 +701,7 @@ static void execute_realize_pointcloud_task(const RealizeInstancesOptions &optio UNUSED_VARS_NDEBUG(domain); return point_slice; }, - dst_attribute_spans); + dst_attribute_writers); } static void execute_realize_pointcloud_tasks(const RealizeInstancesOptions &options, @@ -721,42 +723,43 @@ static void execute_realize_pointcloud_tasks(const RealizeInstancesOptions &opti PointCloudComponent &dst_component = r_realized_geometry.get_component_for_write(); dst_component.replace(dst_pointcloud); + bke::MutableAttributeAccessor dst_attributes = bke::pointcloud_attributes_for_write( + *dst_pointcloud); /* Prepare id attribute. */ - OutputAttribute_Typed point_ids; - MutableSpan point_ids_span; + SpanAttributeWriter point_ids; if (all_pointclouds_info.create_id_attribute) { - point_ids = dst_component.attribute_try_get_for_output_only("id", ATTR_DOMAIN_POINT); - point_ids_span = point_ids.as_span(); + point_ids = dst_attributes.lookup_or_add_for_write_only_span("id", ATTR_DOMAIN_POINT); } /* Prepare generic output attributes. */ - Vector dst_attributes; - Vector dst_attribute_spans; + Vector dst_attribute_writers; for (const int attribute_index : ordered_attributes.index_range()) { const AttributeIDRef &attribute_id = ordered_attributes.ids[attribute_index]; const eCustomDataType data_type = ordered_attributes.kinds[attribute_index].data_type; - OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( - attribute_id, ATTR_DOMAIN_POINT, data_type); - dst_attribute_spans.append(dst_attribute.as_span()); - dst_attributes.append(std::move(dst_attribute)); + dst_attribute_writers.append(dst_attributes.lookup_or_add_for_write_only_span( + attribute_id, ATTR_DOMAIN_POINT, data_type)); } /* Actually execute all tasks. */ threading::parallel_for(tasks.index_range(), 100, [&](const IndexRange task_range) { for (const int task_index : task_range) { const RealizePointCloudTask &task = tasks[task_index]; - execute_realize_pointcloud_task( - options, task, ordered_attributes, *dst_pointcloud, dst_attribute_spans, point_ids_span); + execute_realize_pointcloud_task(options, + task, + ordered_attributes, + *dst_pointcloud, + dst_attribute_writers, + point_ids.span); } }); - /* Save modified attributes. */ - for (OutputAttribute &dst_attribute : dst_attributes) { - dst_attribute.save(); + /* Tag modified attributes. */ + for (GSpanAttributeWriter &dst_attribute : dst_attribute_writers) { + dst_attribute.finish(); } if (point_ids) { - point_ids.save(); + point_ids.finish(); } } @@ -837,22 +840,21 @@ static AllMeshesInfo preprocess_meshes(const GeometrySet &geometry_set, } /* Access attributes. */ - MeshComponent component; - component.replace(const_cast(mesh), GeometryOwnershipType::ReadOnly); + bke::AttributeAccessor attributes = bke::mesh_attributes(*mesh); mesh_info.attributes.reinitialize(info.attributes.size()); for (const int attribute_index : info.attributes.index_range()) { const AttributeIDRef &attribute_id = info.attributes.ids[attribute_index]; const eCustomDataType data_type = info.attributes.kinds[attribute_index].data_type; const eAttrDomain domain = info.attributes.kinds[attribute_index].domain; - if (component.attribute_exists(attribute_id)) { - GVArray attribute = component.attribute_get_for_read(attribute_id, domain, data_type); + if (attributes.contains(attribute_id)) { + GVArray attribute = attributes.lookup_or_default(attribute_id, domain, data_type); mesh_info.attributes[attribute_index].emplace(std::move(attribute)); } } if (info.create_id_attribute) { - ReadAttributeLookup ids_lookup = component.attribute_try_get_for_read("id"); - if (ids_lookup) { - mesh_info.stored_vertex_ids = ids_lookup.varray.get_internal_span().typed(); + bke::GAttributeReader ids_attribute = attributes.lookup("id"); + if (ids_attribute) { + mesh_info.stored_vertex_ids = ids_attribute.varray.get_internal_span().typed(); } } } @@ -863,7 +865,7 @@ static void execute_realize_mesh_task(const RealizeInstancesOptions &options, const RealizeMeshTask &task, const OrderedAttributes &ordered_attributes, Mesh &dst_mesh, - MutableSpan dst_attribute_spans, + MutableSpan dst_attribute_writers, MutableSpan all_dst_vertex_ids) { const MeshRealizeInfo &mesh_info = *task.mesh_info; @@ -949,7 +951,7 @@ static void execute_realize_mesh_task(const RealizeInstancesOptions &options, return IndexRange(); } }, - dst_attribute_spans); + dst_attribute_writers); } static void execute_realize_mesh_tasks(const RealizeInstancesOptions &options, @@ -973,6 +975,7 @@ static void execute_realize_mesh_tasks(const RealizeInstancesOptions &options, Mesh *dst_mesh = BKE_mesh_new_nomain(tot_vertices, tot_edges, 0, tot_loops, tot_poly); MeshComponent &dst_component = r_realized_geometry.get_component_for_write(); dst_component.replace(dst_mesh); + bke::MutableAttributeAccessor dst_attributes = bke::mesh_attributes_for_write(*dst_mesh); /* Copy settings from the first input geometry set with a mesh. */ const RealizeMeshTask &first_task = tasks.first(); @@ -986,24 +989,19 @@ static void execute_realize_mesh_tasks(const RealizeInstancesOptions &options, } /* Prepare id attribute. */ - OutputAttribute_Typed vertex_ids; - MutableSpan vertex_ids_span; + SpanAttributeWriter vertex_ids; if (all_meshes_info.create_id_attribute) { - vertex_ids = dst_component.attribute_try_get_for_output_only("id", ATTR_DOMAIN_POINT); - vertex_ids_span = vertex_ids.as_span(); + vertex_ids = dst_attributes.lookup_or_add_for_write_only_span("id", ATTR_DOMAIN_POINT); } /* Prepare generic output attributes. */ - Vector dst_attributes; - Vector dst_attribute_spans; + Vector dst_attribute_writers; for (const int attribute_index : ordered_attributes.index_range()) { const AttributeIDRef &attribute_id = ordered_attributes.ids[attribute_index]; const eAttrDomain domain = ordered_attributes.kinds[attribute_index].domain; const eCustomDataType data_type = ordered_attributes.kinds[attribute_index].data_type; - OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( - attribute_id, domain, data_type); - dst_attribute_spans.append(dst_attribute.as_span()); - dst_attributes.append(std::move(dst_attribute)); + dst_attribute_writers.append( + dst_attributes.lookup_or_add_for_write_only_span(attribute_id, domain, data_type)); } /* Actually execute all tasks. */ @@ -1011,16 +1009,16 @@ static void execute_realize_mesh_tasks(const RealizeInstancesOptions &options, for (const int task_index : task_range) { const RealizeMeshTask &task = tasks[task_index]; execute_realize_mesh_task( - options, task, ordered_attributes, *dst_mesh, dst_attribute_spans, vertex_ids_span); + options, task, ordered_attributes, *dst_mesh, dst_attribute_writers, vertex_ids.span); } }); - /* Save modified attributes. */ - for (OutputAttribute &dst_attribute : dst_attributes) { - dst_attribute.save(); + /* Tag modified attributes. */ + for (GSpanAttributeWriter &dst_attribute : dst_attribute_writers) { + dst_attribute.finish(); } if (vertex_ids) { - vertex_ids.save(); + vertex_ids.finish(); } } @@ -1088,49 +1086,43 @@ static AllCurvesInfo preprocess_curves(const GeometrySet &geometry_set, curve_info.curves = curves_id; /* Access attributes. */ - CurveComponent component; - component.replace(const_cast(curves_id), GeometryOwnershipType::ReadOnly); + bke::AttributeAccessor attributes = curves.attributes(); curve_info.attributes.reinitialize(info.attributes.size()); for (const int attribute_index : info.attributes.index_range()) { const eAttrDomain domain = info.attributes.kinds[attribute_index].domain; const AttributeIDRef &attribute_id = info.attributes.ids[attribute_index]; const eCustomDataType data_type = info.attributes.kinds[attribute_index].data_type; - if (component.attribute_exists(attribute_id)) { - GVArray attribute = component.attribute_get_for_read(attribute_id, domain, data_type); + if (attributes.contains(attribute_id)) { + GVArray attribute = attributes.lookup_or_default(attribute_id, domain, data_type); curve_info.attributes[attribute_index].emplace(std::move(attribute)); } } if (info.create_id_attribute) { - ReadAttributeLookup ids_lookup = component.attribute_try_get_for_read("id"); - if (ids_lookup) { - curve_info.stored_ids = ids_lookup.varray.get_internal_span().typed(); + bke::GAttributeReader id_attribute = attributes.lookup("id"); + if (id_attribute) { + curve_info.stored_ids = id_attribute.varray.get_internal_span().typed(); } } /* Retrieve the radius attribute, if it exists. */ - if (component.attribute_exists("radius")) { - curve_info.radius = component - .attribute_get_for_read("radius", ATTR_DOMAIN_POINT, 0.0f) - .get_internal_span(); + if (attributes.contains("radius")) { + curve_info.radius = + attributes.lookup("radius", ATTR_DOMAIN_POINT).get_internal_span(); info.create_radius_attribute = true; } /* Retrieve the resolution attribute, if it exists. */ curve_info.resolution = curves.resolution(); - if (component.attribute_exists("resolution")) { + if (attributes.contains("resolution")) { info.create_resolution_attribute = true; } /* Retrieve handle position attributes, if they exist. */ - if (component.attribute_exists("handle_right")) { - curve_info.handle_left = component - .attribute_get_for_read( - "handle_left", ATTR_DOMAIN_POINT, float3(0)) - .get_internal_span(); - curve_info.handle_right = component - .attribute_get_for_read( - "handle_right", ATTR_DOMAIN_POINT, float3(0)) - .get_internal_span(); + if (attributes.contains("handle_right")) { + curve_info.handle_left = + attributes.lookup("handle_left", ATTR_DOMAIN_POINT).get_internal_span(); + curve_info.handle_right = + attributes.lookup("handle_right", ATTR_DOMAIN_POINT).get_internal_span(); info.create_handle_postion_attributes = true; } } @@ -1142,7 +1134,7 @@ static void execute_realize_curve_task(const RealizeInstancesOptions &options, const RealizeCurveTask &task, const OrderedAttributes &ordered_attributes, bke::CurvesGeometry &dst_curves, - MutableSpan dst_attribute_spans, + MutableSpan dst_attribute_writers, MutableSpan all_dst_ids, MutableSpan all_handle_left, MutableSpan all_handle_right, @@ -1220,7 +1212,7 @@ static void execute_realize_curve_task(const RealizeInstancesOptions &options, return IndexRange(); } }, - dst_attribute_spans); + dst_attribute_writers); } static void execute_realize_curve_tasks(const RealizeInstancesOptions &options, @@ -1244,57 +1236,45 @@ static void execute_realize_curve_tasks(const RealizeInstancesOptions &options, dst_curves.offsets_for_write().last() = points_num; CurveComponent &dst_component = r_realized_geometry.get_component_for_write(); dst_component.replace(dst_curves_id); + bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write(); /* Prepare id attribute. */ - OutputAttribute_Typed point_ids; - MutableSpan point_ids_span; + SpanAttributeWriter point_ids; if (all_curves_info.create_id_attribute) { - point_ids = dst_component.attribute_try_get_for_output_only("id", ATTR_DOMAIN_POINT); - point_ids_span = point_ids.as_span(); + point_ids = dst_attributes.lookup_or_add_for_write_only_span("id", ATTR_DOMAIN_POINT); } /* Prepare generic output attributes. */ - Vector dst_attributes; - Vector dst_attribute_spans; + Vector dst_attribute_writers; for (const int attribute_index : ordered_attributes.index_range()) { const AttributeIDRef &attribute_id = ordered_attributes.ids[attribute_index]; const eAttrDomain domain = ordered_attributes.kinds[attribute_index].domain; const eCustomDataType data_type = ordered_attributes.kinds[attribute_index].data_type; - OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( - attribute_id, domain, data_type); - dst_attribute_spans.append(dst_attribute.as_span()); - dst_attributes.append(std::move(dst_attribute)); + dst_attribute_writers.append( + dst_attributes.lookup_or_add_for_write_only_span(attribute_id, domain, data_type)); } /* Prepare handle position attributes if necessary. */ - OutputAttribute_Typed handle_left; - OutputAttribute_Typed handle_right; - MutableSpan handle_left_span; - MutableSpan handle_right_span; + SpanAttributeWriter handle_left; + SpanAttributeWriter handle_right; if (all_curves_info.create_handle_postion_attributes) { - handle_left = dst_component.attribute_try_get_for_output_only("handle_left", - ATTR_DOMAIN_POINT); - handle_right = dst_component.attribute_try_get_for_output_only("handle_right", + handle_left = dst_attributes.lookup_or_add_for_write_only_span("handle_left", ATTR_DOMAIN_POINT); - handle_left_span = handle_left.as_span(); - handle_right_span = handle_right.as_span(); + handle_right = dst_attributes.lookup_or_add_for_write_only_span("handle_right", + ATTR_DOMAIN_POINT); } /* Prepare radius attribute if necessary. */ - OutputAttribute_Typed radius; - MutableSpan radius_span; + SpanAttributeWriter radius; if (all_curves_info.create_radius_attribute) { - radius = dst_component.attribute_try_get_for_output_only("radius", ATTR_DOMAIN_POINT); - radius_span = radius.as_span(); + radius = dst_attributes.lookup_or_add_for_write_only_span("radius", ATTR_DOMAIN_POINT); } /* Prepare resolution attribute if necessary. */ - OutputAttribute_Typed resolution; - MutableSpan resolution_span; + SpanAttributeWriter resolution; if (all_curves_info.create_resolution_attribute) { - resolution = dst_component.attribute_try_get_for_output_only("resolution", - ATTR_DOMAIN_CURVE); - resolution_span = resolution.as_span(); + resolution = dst_attributes.lookup_or_add_for_write_only_span("resolution", + ATTR_DOMAIN_CURVE); } /* Actually execute all tasks. */ @@ -1306,31 +1286,31 @@ static void execute_realize_curve_tasks(const RealizeInstancesOptions &options, task, ordered_attributes, dst_curves, - dst_attribute_spans, - point_ids_span, - handle_left_span, - handle_right_span, - radius_span, - resolution_span); + dst_attribute_writers, + point_ids.span, + handle_left.span, + handle_right.span, + radius.span, + resolution.span); } }); - /* Save modified attributes. */ - for (OutputAttribute &dst_attribute : dst_attributes) { - dst_attribute.save(); + /* Tag modified attributes. */ + for (GSpanAttributeWriter &dst_attribute : dst_attribute_writers) { + dst_attribute.finish(); } if (point_ids) { - point_ids.save(); + point_ids.finish(); } if (radius) { - radius.save(); + radius.finish(); } if (resolution) { - resolution.save(); + resolution.finish(); } if (all_curves_info.create_handle_postion_attributes) { - handle_left.save(); - handle_right.save(); + handle_left.finish(); + handle_right.finish(); } } @@ -1345,7 +1325,7 @@ static void remove_id_attribute_from_instances(GeometrySet &geometry_set) geometry_set.modify_geometry_sets([&](GeometrySet &sub_geometry) { if (sub_geometry.has()) { InstancesComponent &component = sub_geometry.get_component_for_write(); - component.attributes().remove("id"); + component.instance_attributes().remove("id"); } }); } diff --git a/source/blender/geometry/intern/resample_curves.cc b/source/blender/geometry/intern/resample_curves.cc index dd1da62408c..c9b8a032ce6 100644 --- a/source/blender/geometry/intern/resample_curves.cc +++ b/source/blender/geometry/intern/resample_curves.cc @@ -92,17 +92,18 @@ static void retrieve_attribute_spans(const Span ids, CurveComponent &dst_component, Vector &src, Vector &dst, - Vector &dst_attributes) + Vector &dst_attributes) { for (const int i : ids.index_range()) { - GVArray src_attribute = src_component.attribute_try_get_for_read(ids[i], ATTR_DOMAIN_POINT); + GVArray src_attribute = src_component.attributes()->lookup(ids[i], ATTR_DOMAIN_POINT); BLI_assert(src_attribute); src.append(src_attribute.get_internal_span()); const eCustomDataType data_type = bke::cpp_type_to_custom_data_type(src_attribute.type()); - bke::OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( - ids[i], ATTR_DOMAIN_POINT, data_type); - dst.append(dst_attribute.as_span()); + bke::GSpanAttributeWriter dst_attribute = + dst_component.attributes_for_write()->lookup_or_add_for_write_only_span( + ids[i], ATTR_DOMAIN_POINT, data_type); + dst.append(dst_attribute.span); dst_attributes.append(std::move(dst_attribute)); } } @@ -111,7 +112,7 @@ struct AttributesForInterpolation : NonCopyable, NonMovable { Vector src; Vector dst; - Vector dst_attributes; + Vector dst_attributes; Vector src_no_interpolation; Vector dst_no_interpolation; @@ -129,8 +130,8 @@ static void gather_point_attributes_to_interpolate(const CurveComponent &src_com VectorSet ids; VectorSet ids_no_interpolation; - src_component.attribute_foreach( - [&](const bke::AttributeIDRef &id, const AttributeMetaData meta_data) { + src_component.attributes()->for_all( + [&](const bke::AttributeIDRef &id, const bke::AttributeMetaData meta_data) { if (meta_data.domain != ATTR_DOMAIN_POINT) { return true; } @@ -311,8 +312,8 @@ static Curves *resample_to_uniform(const CurveComponent &src_component, bke::curves::copy_point_data( src_curves, dst_curves, unselected_ranges, src_positions, dst_positions); - for (bke::OutputAttribute &attribute : attributes.dst_attributes) { - attribute.save(); + for (bke::GSpanAttributeWriter &attribute : attributes.dst_attributes) { + attribute.finish(); } return dst_curves_id; @@ -433,8 +434,8 @@ Curves *resample_to_evaluated(const CurveComponent &src_component, bke::curves::copy_point_data( src_curves, dst_curves, unselected_ranges, src_positions, dst_positions); - for (bke::OutputAttribute &attribute : attributes.dst_attributes) { - attribute.save(); + for (bke::GSpanAttributeWriter &attribute : attributes.dst_attributes) { + attribute.finish(); } return dst_curves_id; diff --git a/source/blender/geometry/intern/set_curve_type.cc b/source/blender/geometry/intern/set_curve_type.cc index d7a5bc9b27d..08888a74973 100644 --- a/source/blender/geometry/intern/set_curve_type.cc +++ b/source/blender/geometry/intern/set_curve_type.cc @@ -290,32 +290,32 @@ struct GenericAttributes : NonCopyable, NonMovable { Vector src; Vector dst; - Vector attributes; + Vector attributes; }; -static void retrieve_generic_point_attributes(const CurveComponent &src_component, - CurveComponent &dst_component, +static void retrieve_generic_point_attributes(const bke::AttributeAccessor &src_attributes, + bke::MutableAttributeAccessor &dst_attributes, GenericAttributes &attributes) { - src_component.attribute_foreach( - [&](const bke::AttributeIDRef &id, const AttributeMetaData meta_data) { + src_attributes.for_all( + [&](const bke::AttributeIDRef &id, const bke::AttributeMetaData meta_data) { if (meta_data.domain != ATTR_DOMAIN_POINT) { /* Curve domain attributes are all copied directly to the result in one step. */ return true; } - if (src_component.attribute_is_builtin(id)) { + if (src_attributes.is_builtin(id)) { if (!(id.is_named() && ELEM(id, "tilt", "radius"))) { return true; } } - GVArray src_attribute = src_component.attribute_try_get_for_read(id, ATTR_DOMAIN_POINT); + GVArray src_attribute = src_attributes.lookup(id, ATTR_DOMAIN_POINT); BLI_assert(src_attribute); attributes.src.append(src_attribute.get_internal_span()); - bke::OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( + bke::GSpanAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write_span( id, ATTR_DOMAIN_POINT, meta_data.data_type); - attributes.dst.append(dst_attribute.as_span()); + attributes.dst.append(dst_attribute.span); attributes.attributes.append(std::move(dst_attribute)); return true; @@ -367,8 +367,11 @@ static Curves *convert_curves_to_bezier(const CurveComponent &src_component, bke::curves::accumulate_counts_to_offsets(dst_offsets); dst_curves.resize(dst_offsets.last(), dst_curves.curves_num()); + const bke::AttributeAccessor src_attributes = *src_component.attributes(); + bke::MutableAttributeAccessor dst_attributes = *dst_component.attributes_for_write(); + GenericAttributes attributes; - retrieve_generic_point_attributes(src_component, dst_component, attributes); + retrieve_generic_point_attributes(src_attributes, dst_attributes, attributes); MutableSpan dst_positions = dst_curves.positions_for_write(); MutableSpan dst_handles_l = dst_curves.handle_positions_left_for_write(); @@ -494,8 +497,8 @@ static Curves *convert_curves_to_bezier(const CurveComponent &src_component, src_curves, dst_curves, unselected_ranges, attributes.src[i], attributes.dst[i]); } - for (bke::OutputAttribute &attribute : attributes.attributes) { - attribute.save(); + for (bke::GSpanAttributeWriter &attribute : attributes.attributes) { + attribute.finish(); } return dst_curves_id; @@ -524,8 +527,11 @@ static Curves *convert_curves_to_nurbs(const CurveComponent &src_component, bke::curves::accumulate_counts_to_offsets(dst_offsets); dst_curves.resize(dst_offsets.last(), dst_curves.curves_num()); + const bke::AttributeAccessor src_attributes = *src_component.attributes(); + bke::MutableAttributeAccessor dst_attributes = *dst_component.attributes_for_write(); + GenericAttributes attributes; - retrieve_generic_point_attributes(src_component, dst_component, attributes); + retrieve_generic_point_attributes(src_attributes, dst_attributes, attributes); MutableSpan dst_positions = dst_curves.positions_for_write(); @@ -659,8 +665,8 @@ static Curves *convert_curves_to_nurbs(const CurveComponent &src_component, src_curves, dst_curves, unselected_ranges, attributes.src[i], attributes.dst[i]); } - for (bke::OutputAttribute &attribute : attributes.attributes) { - attribute.save(); + for (bke::GSpanAttributeWriter &attribute : attributes.attributes) { + attribute.finish(); } return dst_curves_id; diff --git a/source/blender/geometry/intern/subdivide_curves.cc b/source/blender/geometry/intern/subdivide_curves.cc index 4fb21e53013..914174235cd 100644 --- a/source/blender/geometry/intern/subdivide_curves.cc +++ b/source/blender/geometry/intern/subdivide_curves.cc @@ -79,16 +79,17 @@ static void calculate_result_offsets(const bke::CurvesGeometry &src_curves, struct AttributeTransferData { /* Expect that if an attribute exists, it is stored as a contiguous array internally anyway. */ GVArraySpan src; - bke::OutputAttribute dst; + bke::GSpanAttributeWriter dst; }; -static Vector retrieve_point_attributes(const CurveComponent &src_component, - CurveComponent &dst_component, - const Set &skip = {}) +static Vector retrieve_point_attributes( + const bke::AttributeAccessor &src_attributes, + bke::MutableAttributeAccessor &dst_attributes, + const Set &skip = {}) { Vector attributes; - src_component.attribute_foreach( - [&](const bke::AttributeIDRef &id, const AttributeMetaData meta_data) { + src_attributes.for_all( + [&](const bke::AttributeIDRef &id, const bke::AttributeMetaData meta_data) { if (meta_data.domain != ATTR_DOMAIN_POINT) { /* Curve domain attributes are all copied directly to the result in one step. */ return true; @@ -97,9 +98,9 @@ static Vector retrieve_point_attributes(const CurveCompon return true; } - GVArray src = src_component.attribute_try_get_for_read(id, ATTR_DOMAIN_POINT); + GVArray src = src_attributes.lookup(id, ATTR_DOMAIN_POINT); BLI_assert(src); - bke::OutputAttribute dst = dst_component.attribute_try_get_for_output_only( + bke::GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span( id, ATTR_DOMAIN_POINT, meta_data.data_type); BLI_assert(dst); attributes.append({std::move(src), std::move(dst)}); @@ -384,28 +385,27 @@ Curves *subdivide_curves(const CurveComponent &src_component, dst_curves.resize(dst_curves.offsets().last(), dst_curves.curves_num()); + const bke::AttributeAccessor src_attributes = *src_component.attributes(); + bke::MutableAttributeAccessor dst_attributes = *dst_component.attributes_for_write(); + auto subdivide_catmull_rom = [&](IndexMask selection) { - for (auto &attribute : retrieve_point_attributes(src_component, dst_component)) { + for (auto &attribute : retrieve_point_attributes(src_attributes, dst_attributes)) { subdivide_attribute_catmull_rom(src_curves, dst_curves, selection, point_offsets, cyclic, attribute.src, - attribute.dst.as_span()); - attribute.dst.save(); + attribute.dst.span); + attribute.dst.finish(); } }; auto subdivide_poly = [&](IndexMask selection) { - for (auto &attribute : retrieve_point_attributes(src_component, dst_component)) { - subdivide_attribute_linear(src_curves, - dst_curves, - selection, - point_offsets, - attribute.src, - attribute.dst.as_span()); - attribute.dst.save(); + for (auto &attribute : retrieve_point_attributes(src_attributes, dst_attributes)) { + subdivide_attribute_linear( + src_curves, dst_curves, selection, point_offsets, attribute.src, attribute.dst.span); + attribute.dst.finish(); } }; @@ -443,20 +443,16 @@ Curves *subdivide_curves(const CurveComponent &src_component, } }); - for (auto &attribute : retrieve_point_attributes(src_component, - dst_component, + for (auto &attribute : retrieve_point_attributes(src_attributes, + dst_attributes, {"position", "handle_type_left", "handle_type_right", "handle_right", "handle_left"})) { - subdivide_attribute_linear(src_curves, - dst_curves, - selection, - point_offsets, - attribute.src, - attribute.dst.as_span()); - attribute.dst.save(); + subdivide_attribute_linear( + src_curves, dst_curves, selection, point_offsets, attribute.src, attribute.dst.span); + attribute.dst.finish(); } }; @@ -473,10 +469,10 @@ Curves *subdivide_curves(const CurveComponent &src_component, subdivide_nurbs); if (!unselected_ranges.is_empty()) { - for (auto &attribute : retrieve_point_attributes(src_component, dst_component)) { + for (auto &attribute : retrieve_point_attributes(src_attributes, dst_attributes)) { bke::curves::copy_point_data( - src_curves, dst_curves, unselected_ranges, attribute.src, attribute.dst.as_span()); - attribute.dst.save(); + src_curves, dst_curves, unselected_ranges, attribute.src, attribute.dst.span); + attribute.dst.finish(); } } diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc index cb95c561547..731587bfcea 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc @@ -254,9 +254,8 @@ void OBJWriter::write_vertex_coords(FormatHandler &fh, colors_layer = BKE_id_attributes_active_color_get(&mesh->id); } if (write_colors && (colors_layer != nullptr)) { - MeshComponent component; - component.replace(mesh, GeometryOwnershipType::ReadOnly); - VArray attribute = component.attribute_get_for_read( + const bke::AttributeAccessor attributes = bke::mesh_attributes(*mesh); + const VArray attribute = attributes.lookup_or_default( colors_layer->name, ATTR_DOMAIN_POINT, {0.0f, 0.0f, 0.0f, 0.0f}); BLI_assert(tot_count == attribute.size()); diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index a63a89e076b..885d2f901ec 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -109,7 +109,7 @@ using blender::Span; using blender::StringRef; using blender::StringRefNull; using blender::Vector; -using blender::bke::OutputAttribute; +using blender::bke::AttributeMetaData; using blender::fn::Field; using blender::fn::GField; using blender::fn::ValueOrField; @@ -999,22 +999,27 @@ static Vector compute_attributes_to_store( continue; } const GeometryComponent &component = *geometry.get_component_for_read(component_type); + if (component.is_empty()) { + continue; + } + const blender::bke::AttributeAccessor attributes = *component.attributes(); for (const auto item : outputs_by_domain.items()) { const eAttrDomain domain = item.key; const Span outputs_info = item.value; - if (!component.attribute_domain_supported(domain)) { + if (!attributes.domain_supported(domain)) { continue; } - const int domain_num = component.attribute_domain_num(domain); + const int domain_size = attributes.domain_size(domain); blender::bke::GeometryComponentFieldContext field_context{component, domain}; - blender::fn::FieldEvaluator field_evaluator{field_context, domain_num}; + blender::fn::FieldEvaluator field_evaluator{field_context, domain_size}; for (const OutputAttributeInfo &output_info : outputs_info) { const CPPType &type = output_info.field.cpp_type(); OutputAttributeToStore store{ component_type, domain, output_info.name, - GMutableSpan{type, MEM_malloc_arrayN(domain_num, type.size(), __func__), domain_num}}; + GMutableSpan{ + type, MEM_malloc_arrayN(domain_size, type.size(), __func__), domain_size}}; field_evaluator.add_with_destination(output_info.field, store.data); attributes_to_store.append(store); } @@ -1029,33 +1034,33 @@ static void store_computed_output_attributes( { for (const OutputAttributeToStore &store : attributes_to_store) { GeometryComponent &component = geometry.get_component_for_write(store.component_type); + blender::bke::MutableAttributeAccessor attributes = *component.attributes_for_write(); + const eCustomDataType data_type = blender::bke::cpp_type_to_custom_data_type( store.data.type()); - const std::optional meta_data = component.attribute_get_meta_data( - store.name); + const std::optional meta_data = attributes.lookup_meta_data(store.name); /* Attempt to remove the attribute if it already exists but the domain and type don't match. * Removing the attribute won't succeed if it is built in and non-removable. */ if (meta_data.has_value() && (meta_data->domain != store.domain || meta_data->data_type != data_type)) { - component.attribute_try_delete(store.name); + attributes.remove(store.name); } /* Try to create the attribute reusing the stored buffer. This will only succeed if the * attribute didn't exist before, or if it existed but was removed above. */ - if (component.attribute_try_create( - store.name, - store.domain, - blender::bke::cpp_type_to_custom_data_type(store.data.type()), - AttributeInitMove(store.data.data()))) { + if (attributes.add(store.name, + store.domain, + blender::bke::cpp_type_to_custom_data_type(store.data.type()), + blender::bke::AttributeInitMove(store.data.data()))) { continue; } - OutputAttribute attribute = component.attribute_try_get_for_output_only( + blender::bke::GAttributeWriter attribute = attributes.lookup_or_add_for_write( store.name, store.domain, data_type); if (attribute) { - attribute.varray().set_all(store.data.data()); - attribute.save(); + attribute.varray.set_all(store.data.data()); + attribute.finish(); } /* We were unable to reuse the data, so it must be destructed and freed. */ diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh index c2dd1cd3ab5..0d5ba6cf5db 100644 --- a/source/blender/nodes/NOD_geometry_exec.hh +++ b/source/blender/nodes/NOD_geometry_exec.hh @@ -5,7 +5,6 @@ #include "FN_field.hh" #include "FN_multi_function_builder.hh" -#include "BKE_attribute_access.hh" #include "BKE_geometry_fields.hh" #include "BKE_geometry_set.hh" @@ -20,16 +19,22 @@ struct ModifierData; namespace blender::nodes { using bke::AnonymousAttributeFieldInput; +using bke::AttributeAccessor; using bke::AttributeFieldInput; using bke::AttributeIDRef; +using bke::AttributeKind; +using bke::AttributeMetaData; +using bke::AttributeReader; +using bke::AttributeWriter; +using bke::GAttributeReader; +using bke::GAttributeWriter; using bke::GeometryComponentFieldContext; using bke::GeometryFieldInput; -using bke::OutputAttribute; -using bke::OutputAttribute_Typed; -using bke::ReadAttributeLookup; +using bke::GSpanAttributeWriter; +using bke::MutableAttributeAccessor; +using bke::SpanAttributeWriter; using bke::StrongAnonymousAttributeID; using bke::WeakAnonymousAttributeID; -using bke::WriteAttributeLookup; using fn::Field; using fn::FieldContext; using fn::FieldEvaluator; diff --git a/source/blender/nodes/geometry/nodes/node_geo_accumulate_field.cc b/source/blender/nodes/geometry/nodes/node_geo_accumulate_field.cc index be7b3446125..58fbfb5a000 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_accumulate_field.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_accumulate_field.cc @@ -217,16 +217,20 @@ template class AccumulateFieldInput final : public GeometryFieldInpu IndexMask UNUSED(mask)) const final { const GeometryComponentFieldContext field_context{component, source_domain_}; - const int domain_num = component.attribute_domain_num(field_context.domain()); + const int domain_size = component.attribute_domain_size(field_context.domain()); + if (domain_size == 0) { + return {}; + } + const AttributeAccessor attributes = *component.attributes(); - fn::FieldEvaluator evaluator{field_context, domain_num}; + fn::FieldEvaluator evaluator{field_context, domain_size}; evaluator.add(input_); evaluator.add(group_index_); evaluator.evaluate(); const VArray values = evaluator.get_evaluated(0); const VArray group_indices = evaluator.get_evaluated(1); - Array accumulations_out(domain_num); + Array accumulations_out(domain_size); if (group_indices.is_single()) { T accumulation = T(); @@ -261,7 +265,7 @@ template class AccumulateFieldInput final : public GeometryFieldInpu } } - return component.attribute_try_adapt_domain( + return attributes.adapt_domain( VArray::ForContainer(std::move(accumulations_out)), source_domain_, domain); } @@ -303,9 +307,13 @@ template class TotalFieldInput final : public GeometryFieldInput { IndexMask UNUSED(mask)) const final { const GeometryComponentFieldContext field_context{component, source_domain_}; - const int domain_num = component.attribute_domain_num(field_context.domain()); + const int domain_size = component.attribute_domain_size(field_context.domain()); + if (domain_size == 0) { + return {}; + } + const AttributeAccessor attributes = *component.attributes(); - fn::FieldEvaluator evaluator{field_context, domain_num}; + fn::FieldEvaluator evaluator{field_context, domain_size}; evaluator.add(input_); evaluator.add(group_index_); evaluator.evaluate(); @@ -317,10 +325,10 @@ template class TotalFieldInput final : public GeometryFieldInput { for (const int i : values.index_range()) { accumulation = values[i] + accumulation; } - return VArray::ForSingle(accumulation, domain_num); + return VArray::ForSingle(accumulation, domain_size); } - Array accumulations_out(domain_num); + Array accumulations_out(domain_size); Map accumulations; for (const int i : values.index_range()) { T &value = accumulations.lookup_or_add_default(group_indices[i]); @@ -330,7 +338,7 @@ template class TotalFieldInput final : public GeometryFieldInput { accumulations_out[i] = accumulations.lookup(group_indices[i]); } - return component.attribute_try_adapt_domain( + return attributes.adapt_domain( VArray::ForContainer(std::move(accumulations_out)), source_domain_, domain); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc index 496fb081d6b..9f317075bb5 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc @@ -111,19 +111,26 @@ static void try_capture_field_on_geometry(GeometryComponent &component, const eAttrDomain domain, const GField &field) { + const int domain_size = component.attribute_domain_size(domain); + if (domain_size == 0) { + return; + } GeometryComponentFieldContext field_context{component, domain}; - const int domain_num = component.attribute_domain_num(domain); - const IndexMask mask{IndexMask(domain_num)}; + MutableAttributeAccessor attributes = *component.attributes_for_write(); + const IndexMask mask{IndexMask(domain_size)}; const eCustomDataType data_type = bke::cpp_type_to_custom_data_type(field.cpp_type()); - OutputAttribute output_attribute = component.attribute_try_get_for_output_only( + GAttributeWriter output_attribute = attributes.lookup_or_add_for_write( attribute_id, domain, data_type); + if (!output_attribute) { + return; + } fn::FieldEvaluator evaluator{field_context, &mask}; - evaluator.add_with_destination(field, output_attribute.varray()); + evaluator.add_with_destination(field, output_attribute.varray); evaluator.evaluate(); - output_attribute.save(); + output_attribute.finish(); } static StringRefNull identifier_suffix(eCustomDataType data_type) diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc index 8ab0eb678e7..7e4904a7a6a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc @@ -72,11 +72,11 @@ static void node_geo_exec(GeoNodeExecParams params) case GEO_COMPONENT_TYPE_MESH: { if (geometry_set.has_mesh()) { const MeshComponent *component = geometry_set.get_component_for_read(); - params.set_output("Point Count", component->attribute_domain_num(ATTR_DOMAIN_POINT)); - params.set_output("Edge Count", component->attribute_domain_num(ATTR_DOMAIN_EDGE)); - params.set_output("Face Count", component->attribute_domain_num(ATTR_DOMAIN_FACE)); - params.set_output("Face Corner Count", - component->attribute_domain_num(ATTR_DOMAIN_CORNER)); + const AttributeAccessor attributes = *component->attributes(); + params.set_output("Point Count", attributes.domain_size(ATTR_DOMAIN_POINT)); + params.set_output("Edge Count", attributes.domain_size(ATTR_DOMAIN_EDGE)); + params.set_output("Face Count", attributes.domain_size(ATTR_DOMAIN_FACE)); + params.set_output("Face Corner Count", attributes.domain_size(ATTR_DOMAIN_CORNER)); } else { params.set_default_remaining_outputs(); @@ -86,8 +86,9 @@ static void node_geo_exec(GeoNodeExecParams params) case GEO_COMPONENT_TYPE_CURVE: { if (geometry_set.has_curves()) { const CurveComponent *component = geometry_set.get_component_for_read(); - params.set_output("Point Count", component->attribute_domain_num(ATTR_DOMAIN_POINT)); - params.set_output("Spline Count", component->attribute_domain_num(ATTR_DOMAIN_CURVE)); + const AttributeAccessor attributes = *component->attributes(); + params.set_output("Point Count", attributes.domain_size(ATTR_DOMAIN_POINT)); + params.set_output("Spline Count", attributes.domain_size(ATTR_DOMAIN_CURVE)); } else { params.set_default_remaining_outputs(); @@ -98,7 +99,7 @@ static void node_geo_exec(GeoNodeExecParams params) if (geometry_set.has_pointcloud()) { const PointCloudComponent *component = geometry_set.get_component_for_read(); - params.set_output("Point Count", component->attribute_domain_num(ATTR_DOMAIN_POINT)); + params.set_output("Point Count", component->attributes()->domain_size(ATTR_DOMAIN_POINT)); } else { params.set_default_remaining_outputs(); @@ -109,7 +110,8 @@ static void node_geo_exec(GeoNodeExecParams params) if (geometry_set.has_instances()) { const InstancesComponent *component = geometry_set.get_component_for_read(); - params.set_output("Instance Count", component->attribute_domain_num(ATTR_DOMAIN_INSTANCE)); + params.set_output("Instance Count", + component->attributes()->domain_size(ATTR_DOMAIN_INSTANCE)); } else { params.set_default_remaining_outputs(); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc index 08e72dae8f6..34e28e50c81 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc @@ -195,9 +195,13 @@ static void node_geo_exec(GeoNodeExecParams params) const Field input_field = params.get_input>("Attribute"); Vector data; for (const GeometryComponent *component : components) { - if (component->attribute_domain_supported(domain)) { + const std::optional attributes = component->attributes(); + if (!attributes.has_value()) { + continue; + } + if (attributes->domain_supported(domain)) { GeometryComponentFieldContext field_context{*component, domain}; - const int domain_num = component->attribute_domain_num(domain); + const int domain_num = attributes->domain_size(domain); fn::FieldEvaluator data_evaluator{field_context, domain_num}; data_evaluator.add(input_field); @@ -273,9 +277,13 @@ static void node_geo_exec(GeoNodeExecParams params) const Field input_field = params.get_input>("Attribute_001"); Vector data; for (const GeometryComponent *component : components) { - if (component->attribute_domain_supported(domain)) { + const std::optional attributes = component->attributes(); + if (!attributes.has_value()) { + continue; + } + if (attributes->domain_supported(domain)) { GeometryComponentFieldContext field_context{*component, domain}; - const int domain_num = component->attribute_domain_num(domain); + const int domain_num = attributes->domain_size(domain); fn::FieldEvaluator data_evaluator{field_context, domain_num}; data_evaluator.add(input_field); diff --git a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc index 81cce1fc5da..69938f3e447 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc @@ -154,17 +154,15 @@ static void node_geo_exec(GeoNodeExecParams params) /* Store intersecting edges in attribute. */ if (attribute_outputs.intersecting_edges_id) { - MeshComponent mesh_component; - mesh_component.replace(result, GeometryOwnershipType::Editable); - OutputAttribute_Typed attribute = mesh_component.attribute_try_get_for_output_only( + MutableAttributeAccessor attributes = bke::mesh_attributes_for_write(*result); + SpanAttributeWriter selection = attributes.lookup_or_add_for_write_only_span( attribute_outputs.intersecting_edges_id.get(), ATTR_DOMAIN_EDGE); - MutableSpan selection = attribute.as_span(); - selection.fill(false); + + selection.span.fill(false); for (const int i : intersecting_edges) { - selection[i] = true; + selection.span[i] = true; } - - attribute.save(); + selection.finish(); params.set_output( "Intersecting Edges", diff --git a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc index 045206d04cd..489b618b8be 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc @@ -143,7 +143,8 @@ static Mesh *compute_hull(const GeometrySet &geometry_set) if (geometry_set.has_mesh()) { count++; const MeshComponent *component = geometry_set.get_component_for_read(); - total_num += component->attribute_domain_num(ATTR_DOMAIN_POINT); + const Mesh *mesh = component->get_for_read(); + total_num += mesh->totvert; } if (geometry_set.has_pointcloud()) { @@ -151,10 +152,9 @@ static Mesh *compute_hull(const GeometrySet &geometry_set) span_count++; const PointCloudComponent *component = geometry_set.get_component_for_read(); - VArray varray = component->attribute_get_for_read( - "position", ATTR_DOMAIN_POINT, {0, 0, 0}); - total_num += varray.size(); - positions_span = varray.get_internal_span(); + const PointCloud *pointcloud = component->get_for_read(); + positions_span = {reinterpret_cast(pointcloud->co), pointcloud->totpoint}; + total_num += pointcloud->totpoint; } if (geometry_set.has_curves()) { @@ -181,8 +181,8 @@ static Mesh *compute_hull(const GeometrySet &geometry_set) if (geometry_set.has_mesh()) { const MeshComponent *component = geometry_set.get_component_for_read(); - VArray varray = component->attribute_get_for_read( - "position", ATTR_DOMAIN_POINT, {0, 0, 0}); + const VArray varray = component->attributes()->lookup("position", + ATTR_DOMAIN_POINT); varray.materialize(positions.as_mutable_span().slice(offset, varray.size())); offset += varray.size(); } @@ -190,8 +190,8 @@ static Mesh *compute_hull(const GeometrySet &geometry_set) if (geometry_set.has_pointcloud()) { const PointCloudComponent *component = geometry_set.get_component_for_read(); - VArray varray = component->attribute_get_for_read( - "position", ATTR_DOMAIN_POINT, {0, 0, 0}); + const VArray varray = component->attributes()->lookup("position", + ATTR_DOMAIN_POINT); varray.materialize(positions.as_mutable_span().slice(offset, varray.size())); offset += varray.size(); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc index fb8a488ddae..e200350dc18 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc @@ -581,8 +581,8 @@ static void calculate_curve_fillet(GeometrySet &geometry_set, CurveComponent &component = geometry_set.get_component_for_write(); GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; - const int domain_num = component.attribute_domain_num(ATTR_DOMAIN_POINT); - fn::FieldEvaluator field_evaluator{field_context, domain_num}; + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT); + fn::FieldEvaluator field_evaluator{field_context, domain_size}; field_evaluator.add(radius_field); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc index 91ba5f2845f..286d9993d0e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc @@ -61,13 +61,13 @@ static Curves *create_star_curve(const float inner_radius, static void create_selection_output(CurveComponent &component, StrongAnonymousAttributeID &r_attribute) { - OutputAttribute_Typed attribute = component.attribute_try_get_for_output_only( - r_attribute.get(), ATTR_DOMAIN_POINT); - MutableSpan selection = attribute.as_span(); - for (int i : selection.index_range()) { - selection[i] = i % 2 == 0; + SpanAttributeWriter selection = + component.attributes_for_write()->lookup_or_add_for_write_only_span(r_attribute.get(), + ATTR_DOMAIN_POINT); + for (int i : selection.span.index_range()) { + selection.span[i] = i % 2 == 0; } - attribute.save(); + selection.finish(); } static void node_geo_exec(GeoNodeExecParams params) diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc index 64a174df599..de29735bd2d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc @@ -27,9 +27,9 @@ static void node_geo_exec(GeoNodeExecParams params) Field selection_field = params.get_input>("Selection"); const CurveComponent &component = *geometry_set.get_component_for_read(); GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_CURVE}; - const int domain_num = component.attribute_domain_num(ATTR_DOMAIN_CURVE); + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_CURVE); - fn::FieldEvaluator selection_evaluator{field_context, domain_num}; + fn::FieldEvaluator selection_evaluator{field_context, domain_size}; selection_evaluator.add(selection_field); selection_evaluator.evaluate(); const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_set_handle_type.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_set_handle_type.cc index 469d8d8d13b..f7ba724c377 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_set_handle_type.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_set_handle_type.cc @@ -98,8 +98,8 @@ static void node_geo_exec(GeoNodeExecParams params) } has_curves = true; const CurveComponent &component = *geometry_set.get_component_for_read(); - if (!component.attribute_exists("handle_type_left") || - !component.attribute_exists("handle_type_right")) { + const AttributeAccessor attributes = *component.attributes(); + if (!attributes.contains("handle_type_left") || !attributes.contains("handle_type_right")) { return; } has_bezier = true; diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc index 7d83b4b3ecb..6c4fb2e0855 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc @@ -126,19 +126,18 @@ static GMutableSpan ensure_point_attribute(PointCloudComponent &points, const AttributeIDRef &attribute_id, const eCustomDataType data_type) { - points.attribute_try_create(attribute_id, ATTR_DOMAIN_POINT, data_type, AttributeInitDefault()); - WriteAttributeLookup attribute = points.attribute_try_get_for_write(attribute_id); - BLI_assert(attribute); - return attribute.varray.get_internal_span(); + return points.attributes_for_write() + ->lookup_or_add_for_write(attribute_id, ATTR_DOMAIN_POINT, data_type) + .varray.get_internal_span(); } template static MutableSpan ensure_point_attribute(PointCloudComponent &points, const AttributeIDRef &attribute_id) { - GMutableSpan attribute = ensure_point_attribute( - points, attribute_id, bke::cpp_type_to_custom_data_type(CPPType::get())); - return attribute.typed(); + return points.attributes_for_write() + ->lookup_or_add_for_write(attribute_id, ATTR_DOMAIN_POINT) + .varray.get_internal_span(); } namespace { diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc index 2b90428acb1..00a79168087 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc @@ -506,9 +506,9 @@ static void geometry_set_curve_trim(GeometrySet &geometry_set, CurveComponent &component = geometry_set.get_component_for_write(); GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_CURVE}; - const int domain_num = component.attribute_domain_num(ATTR_DOMAIN_CURVE); + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_CURVE); - fn::FieldEvaluator evaluator{field_context, domain_num}; + fn::FieldEvaluator evaluator{field_context, domain_size}; evaluator.add(start_field); evaluator.add(end_field); evaluator.evaluate(); diff --git a/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc index 60b5f0383ca..ab7ddfa71f1 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc @@ -252,10 +252,8 @@ static void node_geo_exec(GeoNodeExecParams params) BKE_mesh_wrapper_ensure_mdata(surface_mesh_eval); - MeshComponent mesh_eval; - mesh_eval.replace(surface_mesh_eval, GeometryOwnershipType::ReadOnly); - MeshComponent mesh_orig; - mesh_orig.replace(surface_mesh_orig, GeometryOwnershipType::ReadOnly); + const AttributeAccessor mesh_attributes_eval = bke::mesh_attributes(*surface_mesh_eval); + const AttributeAccessor mesh_attributes_orig = bke::mesh_attributes(*surface_mesh_orig); Curves &curves_id = *curves_geometry.get_curves_for_write(); CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); @@ -266,7 +264,7 @@ static void node_geo_exec(GeoNodeExecParams params) params.error_message_add(NodeWarningType::Error, message); return; } - if (!mesh_eval.attribute_exists(uv_map_name)) { + if (!mesh_attributes_eval.contains(uv_map_name)) { pass_through_input(); char *message = BLI_sprintfN(TIP_("Evaluated surface missing UV map: %s."), uv_map_name.c_str()); @@ -274,7 +272,7 @@ static void node_geo_exec(GeoNodeExecParams params) MEM_freeN(message); return; } - if (!mesh_orig.attribute_exists(uv_map_name)) { + if (!mesh_attributes_orig.contains(uv_map_name)) { pass_through_input(); char *message = BLI_sprintfN(TIP_("Original surface missing UV map: %s."), uv_map_name.c_str()); @@ -282,7 +280,7 @@ static void node_geo_exec(GeoNodeExecParams params) MEM_freeN(message); return; } - if (!mesh_eval.attribute_exists(rest_position_name)) { + if (!mesh_attributes_eval.contains(rest_position_name)) { pass_through_input(); params.error_message_add(NodeWarningType::Error, TIP_("Evaluated surface missing attribute: rest_position.")); @@ -294,12 +292,12 @@ static void node_geo_exec(GeoNodeExecParams params) TIP_("Curves are not attached to any UV map.")); return; } - const VArraySpan uv_map_orig = mesh_orig.attribute_get_for_read( - uv_map_name, ATTR_DOMAIN_CORNER, {0.0f, 0.0f}); - const VArraySpan uv_map_eval = mesh_eval.attribute_get_for_read( - uv_map_name, ATTR_DOMAIN_CORNER, {0.0f, 0.0f}); - const VArraySpan rest_positions = mesh_eval.attribute_get_for_read( - rest_position_name, ATTR_DOMAIN_POINT, {0.0f, 0.0f, 0.0f}); + const VArraySpan uv_map_orig = mesh_attributes_orig.lookup(uv_map_name, + ATTR_DOMAIN_CORNER); + const VArraySpan uv_map_eval = mesh_attributes_eval.lookup(uv_map_name, + ATTR_DOMAIN_CORNER); + const VArraySpan rest_positions = mesh_attributes_eval.lookup(rest_position_name, + ATTR_DOMAIN_POINT); const Span surface_uv_coords = curves.surface_uv_coords(); const Span looptris_orig{BKE_mesh_runtime_looptri_ensure(surface_mesh_orig), diff --git a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc index 68489d1b9a6..408596b65aa 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc @@ -49,7 +49,7 @@ static void copy_attributes(const Map &attributes { for (Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; - ReadAttributeLookup attribute = in_component.attribute_try_get_for_read(attribute_id); + GAttributeReader attribute = in_component.attributes()->lookup(attribute_id); if (!attribute) { continue; } @@ -60,8 +60,9 @@ static void copy_attributes(const Map &attributes } const eCustomDataType data_type = bke::cpp_type_to_custom_data_type(attribute.varray.type()); - OutputAttribute result_attribute = result_component.attribute_try_get_for_output_only( - attribute_id, attribute.domain, data_type); + GSpanAttributeWriter result_attribute = + result_component.attributes_for_write()->lookup_or_add_for_write_only_span( + attribute_id, attribute.domain, data_type); if (!result_attribute) { continue; @@ -70,10 +71,10 @@ static void copy_attributes(const Map &attributes attribute_math::convert_to_static_type(data_type, [&](auto dummy) { using T = decltype(dummy); VArraySpan span{attribute.varray.typed()}; - MutableSpan out_span = result_attribute.as_span(); + MutableSpan out_span = result_attribute.span.typed(); out_span.copy_from(span); }); - result_attribute.save(); + result_attribute.finish(); } } @@ -89,7 +90,7 @@ static void copy_attributes_based_on_mask(const Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; - ReadAttributeLookup attribute = in_component.attribute_try_get_for_read(attribute_id); + GAttributeReader attribute = in_component.attributes()->lookup(attribute_id); if (!attribute) { continue; } @@ -100,8 +101,9 @@ static void copy_attributes_based_on_mask(const Maplookup_or_add_for_write_only_span( + attribute_id, attribute.domain, data_type); if (!result_attribute) { continue; @@ -110,10 +112,10 @@ static void copy_attributes_based_on_mask(const Map span{attribute.varray.typed()}; - MutableSpan out_span = result_attribute.as_span(); + MutableSpan out_span = result_attribute.span.typed(); copy_data_based_on_mask(span, out_span, mask); }); - result_attribute.save(); + result_attribute.finish(); } } @@ -125,7 +127,7 @@ static void copy_attributes_based_on_map(const Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; - ReadAttributeLookup attribute = in_component.attribute_try_get_for_read(attribute_id); + GAttributeReader attribute = in_component.attributes()->lookup(attribute_id); if (!attribute) { continue; } @@ -136,8 +138,9 @@ static void copy_attributes_based_on_map(const Maplookup_or_add_for_write_only_span( + attribute_id, attribute.domain, data_type); if (!result_attribute) { continue; @@ -146,10 +149,10 @@ static void copy_attributes_based_on_map(const Map span{attribute.varray.typed()}; - MutableSpan out_span = result_attribute.as_span(); + MutableSpan out_span = result_attribute.span.typed(); copy_data_based_on_map(span, out_span, index_map); }); - result_attribute.save(); + result_attribute.finish(); } } @@ -319,7 +322,7 @@ static void delete_curves_selection(GeometrySet &geometry_set, const CurveComponent &src_component = *geometry_set.get_component_for_read(); GeometryComponentFieldContext field_context{src_component, selection_domain}; - const int domain_num = src_component.attribute_domain_num(selection_domain); + const int domain_num = src_component.attribute_domain_size(selection_domain); fn::FieldEvaluator evaluator{field_context, domain_num}; evaluator.set_selection(selection_field); evaluator.evaluate(); @@ -351,7 +354,7 @@ static void separate_point_cloud_selection(GeometrySet &geometry_set, *geometry_set.get_component_for_read(); GeometryComponentFieldContext field_context{src_points, ATTR_DOMAIN_POINT}; - fn::FieldEvaluator evaluator{field_context, src_points.attribute_domain_num(ATTR_DOMAIN_POINT)}; + fn::FieldEvaluator evaluator{field_context, src_points.attribute_domain_size(ATTR_DOMAIN_POINT)}; evaluator.set_selection(selection_field); evaluator.evaluate(); const IndexMask selection = evaluator.get_evaluated_selection_as_mask(); @@ -379,8 +382,7 @@ static void delete_selected_instances(GeometrySet &geometry_set, InstancesComponent &instances = geometry_set.get_component_for_write(); GeometryComponentFieldContext field_context{instances, ATTR_DOMAIN_INSTANCE}; - fn::FieldEvaluator evaluator{field_context, - instances.attribute_domain_num(ATTR_DOMAIN_INSTANCE)}; + fn::FieldEvaluator evaluator{field_context, instances.instances_num()}; evaluator.set_selection(selection_field); evaluator.evaluate(); const IndexMask selection = evaluator.get_evaluated_selection_as_mask(); @@ -1058,7 +1060,7 @@ static void separate_mesh_selection(GeometrySet &geometry_set, GeometryComponentFieldContext field_context{src_component, selection_domain}; fn::FieldEvaluator evaluator{field_context, - src_component.attribute_domain_num(selection_domain)}; + src_component.attribute_domain_size(selection_domain)}; evaluator.add(selection_field); evaluator.evaluate(); const VArray selection = evaluator.get_evaluated(0); diff --git a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc index f95601813a3..faf5b7f65fa 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc @@ -290,31 +290,32 @@ BLI_NOINLINE static void propagate_existing_attributes( const Span looptri_indices) { const Mesh &mesh = *mesh_component.get_for_read(); + const AttributeAccessor mesh_attributes = *mesh_component.attributes(); + MutableAttributeAccessor point_attributes = *point_component.attributes_for_write(); for (Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; const eCustomDataType output_data_type = entry.value.data_type; - ReadAttributeLookup source_attribute = mesh_component.attribute_try_get_for_read(attribute_id); + GAttributeReader source_attribute = mesh_attributes.lookup(attribute_id); if (!source_attribute) { continue; } /* The output domain is always #ATTR_DOMAIN_POINT, since we are creating a point cloud. */ - OutputAttribute attribute_out = point_component.attribute_try_get_for_output_only( + GSpanAttributeWriter attribute_out = point_attributes.lookup_or_add_for_write_only_span( attribute_id, ATTR_DOMAIN_POINT, output_data_type); if (!attribute_out) { continue; } - GMutableSpan out_span = attribute_out.as_span(); interpolate_attribute(mesh, bary_coords, looptri_indices, source_attribute.domain, source_attribute.varray, - out_span); - attribute_out.save(); + attribute_out.span); + attribute_out.finish(); } } @@ -331,25 +332,21 @@ BLI_NOINLINE static void compute_attribute_outputs(const MeshComponent &mesh_com const Span looptri_indices, const AttributeOutputs &attribute_outputs) { - OutputAttribute_Typed id_attribute = point_component.attribute_try_get_for_output_only( - "id", ATTR_DOMAIN_POINT); - MutableSpan ids = id_attribute.as_span(); + MutableAttributeAccessor pointcloud_attributes = *point_component.attributes_for_write(); - OutputAttribute_Typed normal_attribute; - OutputAttribute_Typed rotation_attribute; + SpanAttributeWriter ids = pointcloud_attributes.lookup_or_add_for_write_only_span( + "id", ATTR_DOMAIN_POINT); - MutableSpan normals; - MutableSpan rotations; + SpanAttributeWriter normals; + SpanAttributeWriter rotations; if (attribute_outputs.normal_id) { - normal_attribute = point_component.attribute_try_get_for_output_only( + normals = pointcloud_attributes.lookup_or_add_for_write_only_span( attribute_outputs.normal_id.get(), ATTR_DOMAIN_POINT); - normals = normal_attribute.as_span(); } if (attribute_outputs.rotation_id) { - rotation_attribute = point_component.attribute_try_get_for_output_only( + rotations = pointcloud_attributes.lookup_or_add_for_write_only_span( attribute_outputs.rotation_id.get(), ATTR_DOMAIN_POINT); - rotations = rotation_attribute.as_span(); } const Mesh &mesh = *mesh_component.get_for_read(); @@ -368,27 +365,27 @@ BLI_NOINLINE static void compute_attribute_outputs(const MeshComponent &mesh_com const float3 v1_pos = float3(mesh.mvert[v1_index].co); const float3 v2_pos = float3(mesh.mvert[v2_index].co); - ids[i] = noise::hash(noise::hash_float(bary_coord), looptri_index); + ids.span[i] = noise::hash(noise::hash_float(bary_coord), looptri_index); float3 normal; - if (!normals.is_empty() || !rotations.is_empty()) { + if (!normals.span.is_empty() || !rotations.span.is_empty()) { normal_tri_v3(normal, v0_pos, v1_pos, v2_pos); } - if (!normals.is_empty()) { - normals[i] = normal; + if (!normals.span.is_empty()) { + normals.span[i] = normal; } - if (!rotations.is_empty()) { - rotations[i] = normal_to_euler_rotation(normal); + if (!rotations.span.is_empty()) { + rotations.span[i] = normal_to_euler_rotation(normal); } } - id_attribute.save(); + ids.finish(); - if (normal_attribute) { - normal_attribute.save(); + if (normals) { + normals.finish(); } - if (rotation_attribute) { - rotation_attribute.save(); + if (rotations) { + rotations.finish(); } } @@ -398,11 +395,11 @@ static Array calc_full_density_factors_with_selection(const MeshComponent { const eAttrDomain attribute_domain = ATTR_DOMAIN_CORNER; GeometryComponentFieldContext field_context{component, attribute_domain}; - const int domain_num = component.attribute_domain_num(attribute_domain); + const int domain_size = component.attribute_domain_size(attribute_domain); - Array densities(domain_num, 0.0f); + Array densities(domain_size, 0.0f); - fn::FieldEvaluator evaluator{field_context, domain_num}; + fn::FieldEvaluator evaluator{field_context, domain_size}; evaluator.set_selection(selection_field); evaluator.add_with_destination(density_field, densities.as_mutable_span()); evaluator.evaluate(); diff --git a/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc index 8a256a9b91b..bf5479d552e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc @@ -146,9 +146,12 @@ static void transfer_attributes( const GeometryComponent &src_component, GeometryComponent &dst_component) { + const AttributeAccessor src_attributes = *src_component.attributes(); + MutableAttributeAccessor dst_attributes = *dst_component.attributes_for_write(); + for (Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; - ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read(attribute_id); + GAttributeReader src_attribute = src_attributes.lookup(attribute_id); if (!src_attribute) { continue; } @@ -166,7 +169,7 @@ static void transfer_attributes( } const eCustomDataType data_type = bke::cpp_type_to_custom_data_type( src_attribute.varray.type()); - OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( + GSpanAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write_only_span( attribute_id, out_domain, data_type); if (!dst_attribute) { @@ -176,7 +179,7 @@ static void transfer_attributes( attribute_math::convert_to_static_type(data_type, [&](auto dummy) { using T = decltype(dummy); VArraySpan span{src_attribute.varray.typed()}; - MutableSpan dst_span = dst_attribute.as_span(); + MutableSpan dst_span = dst_attribute.span.typed(); if (src_attribute.domain == ATTR_DOMAIN_FACE) { dst_span.take_front(span.size()).copy_from(span); if (keep_boundaries) { @@ -193,7 +196,7 @@ static void transfer_attributes( copy_data_based_on_new_to_old_map(span, dst_span, new_to_old_face_corners_map); } }); - dst_attribute.save(); + dst_attribute.finish(); } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc index 5fe300e0a08..41136060ab7 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc @@ -147,17 +147,17 @@ static void create_duplicate_index_attribute(GeometryComponent &component, const IndexAttributes &attribute_outputs, const Span offsets) { - OutputAttribute_Typed copy_attribute = component.attribute_try_get_for_output_only( - attribute_outputs.duplicate_index.get(), output_domain); - MutableSpan duplicate_indices = copy_attribute.as_span(); + SpanAttributeWriter duplicate_indices = + component.attributes_for_write()->lookup_or_add_for_write_only_span( + attribute_outputs.duplicate_index.get(), output_domain); for (const int i : IndexRange(selection.size())) { const IndexRange range = range_for_offsets_index(offsets, i); - MutableSpan indices = duplicate_indices.slice(range); + MutableSpan indices = duplicate_indices.span.slice(range); for (const int i : indices.index_range()) { indices[i] = i; } } - copy_attribute.save(); + duplicate_indices.finish(); } /** @@ -168,20 +168,21 @@ static void copy_stable_id_point(const Span offsets, const GeometryComponent &src_component, GeometryComponent &dst_component) { - ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read("id"); + GAttributeReader src_attribute = src_component.attributes()->lookup("id"); if (!src_attribute) { return; } - OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( - "id", ATTR_DOMAIN_POINT, CD_PROP_INT32); + GSpanAttributeWriter dst_attribute = + dst_component.attributes_for_write()->lookup_or_add_for_write_only_span( + "id", ATTR_DOMAIN_POINT, CD_PROP_INT32); if (!dst_attribute) { return; } VArraySpan src{src_attribute.varray.typed()}; - MutableSpan dst = dst_attribute.as_span(); + MutableSpan dst = dst_attribute.span.typed(); threaded_id_offset_copy(offsets, src, dst); - dst_attribute.save(); + dst_attribute.finish(); } static void copy_attributes_without_id(GeometrySet &geometry_set, @@ -197,25 +198,26 @@ static void copy_attributes_without_id(GeometrySet &geometry_set, for (const Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; - ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read(attribute_id); + GAttributeReader src_attribute = src_component.attributes()->lookup(attribute_id); if (!src_attribute || src_attribute.domain != domain) { continue; } eAttrDomain out_domain = src_attribute.domain; const eCustomDataType data_type = bke::cpp_type_to_custom_data_type( src_attribute.varray.type()); - OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( - attribute_id, out_domain, data_type); + GSpanAttributeWriter dst_attribute = + dst_component.attributes_for_write()->lookup_or_add_for_write_only_span( + attribute_id, out_domain, data_type); if (!dst_attribute) { continue; } attribute_math::convert_to_static_type(data_type, [&](auto dummy) { using T = decltype(dummy); VArraySpan src = src_attribute.varray.typed(); - MutableSpan dst = dst_attribute.as_span(); + MutableSpan dst = dst_attribute.span.typed(); threaded_slice_fill(offsets, selection, src, dst); }); - dst_attribute.save(); + dst_attribute.finish(); } } @@ -242,7 +244,7 @@ static void copy_curve_attributes_without_id(const GeometrySet &geometry_set, for (const Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; - ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read(attribute_id); + GAttributeReader src_attribute = src_component.attributes()->lookup(attribute_id); if (!src_attribute) { continue; } @@ -250,8 +252,9 @@ static void copy_curve_attributes_without_id(const GeometrySet &geometry_set, eAttrDomain out_domain = src_attribute.domain; const eCustomDataType data_type = bke::cpp_type_to_custom_data_type( src_attribute.varray.type()); - OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( - attribute_id, out_domain, data_type); + GSpanAttributeWriter dst_attribute = + dst_component.attributes_for_write()->lookup_or_add_for_write_only_span( + attribute_id, out_domain, data_type); if (!dst_attribute) { continue; } @@ -259,7 +262,7 @@ static void copy_curve_attributes_without_id(const GeometrySet &geometry_set, attribute_math::convert_to_static_type(data_type, [&](auto dummy) { using T = decltype(dummy); VArraySpan src{src_attribute.varray.typed()}; - MutableSpan dst = dst_attribute.as_span(); + MutableSpan dst = dst_attribute.span.typed(); switch (out_domain) { case ATTR_DOMAIN_CURVE: @@ -280,7 +283,7 @@ static void copy_curve_attributes_without_id(const GeometrySet &geometry_set, break; } }); - dst_attribute.save(); + dst_attribute.finish(); } } @@ -297,18 +300,19 @@ static void copy_stable_id_curves(const bke::CurvesGeometry &src_curves, bke::CurvesGeometry &dst_curves, CurveComponent &dst_component) { - ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read("id"); + GAttributeReader src_attribute = src_component.attributes()->lookup("id"); if (!src_attribute) { return; } - OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( - "id", ATTR_DOMAIN_POINT, CD_PROP_INT32); + GSpanAttributeWriter dst_attribute = + dst_component.attributes_for_write()->lookup_or_add_for_write_only_span( + "id", ATTR_DOMAIN_POINT, CD_PROP_INT32); if (!dst_attribute) { return; } VArraySpan src{src_attribute.varray.typed()}; - MutableSpan dst = dst_attribute.as_span(); + MutableSpan dst = dst_attribute.span.typed(); threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) { for (const int i_selection : range) { @@ -322,7 +326,7 @@ static void copy_stable_id_curves(const bke::CurvesGeometry &src_curves, } } }); - dst_attribute.save(); + dst_attribute.finish(); } static void duplicate_curves(GeometrySet &geometry_set, @@ -423,7 +427,7 @@ static void copy_face_attributes_without_id(GeometrySet &geometry_set, for (const Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; - ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read(attribute_id); + GAttributeReader src_attribute = src_component.attributes()->lookup(attribute_id); if (!src_attribute) { continue; } @@ -431,8 +435,9 @@ static void copy_face_attributes_without_id(GeometrySet &geometry_set, eAttrDomain out_domain = src_attribute.domain; const eCustomDataType data_type = bke::cpp_type_to_custom_data_type( src_attribute.varray.type()); - OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( - attribute_id, out_domain, data_type); + GSpanAttributeWriter dst_attribute = + dst_component.attributes_for_write()->lookup_or_add_for_write_only_span( + attribute_id, out_domain, data_type); if (!dst_attribute) { continue; } @@ -440,7 +445,7 @@ static void copy_face_attributes_without_id(GeometrySet &geometry_set, attribute_math::convert_to_static_type(data_type, [&](auto dummy) { using T = decltype(dummy); VArraySpan src{src_attribute.varray.typed()}; - MutableSpan dst = dst_attribute.as_span(); + MutableSpan dst = dst_attribute.span.typed(); switch (out_domain) { case ATTR_DOMAIN_FACE: @@ -459,7 +464,7 @@ static void copy_face_attributes_without_id(GeometrySet &geometry_set, break; } }); - dst_attribute.save(); + dst_attribute.finish(); } } @@ -477,18 +482,19 @@ static void copy_stable_id_faces(const Mesh &mesh, const MeshComponent &src_component, MeshComponent &dst_component) { - ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read("id"); + GAttributeReader src_attribute = src_component.attributes()->lookup("id"); if (!src_attribute) { return; } - OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( - "id", ATTR_DOMAIN_POINT, CD_PROP_INT32); + GSpanAttributeWriter dst_attribute = + dst_component.attributes_for_write()->lookup_or_add_for_write_only_span( + "id", ATTR_DOMAIN_POINT, CD_PROP_INT32); if (!dst_attribute) { return; } VArraySpan src{src_attribute.varray.typed()}; - MutableSpan dst = dst_attribute.as_span(); + MutableSpan dst = dst_attribute.span.typed(); Span polys(mesh.mpoly, mesh.totpoly); int loop_index = 0; @@ -511,7 +517,7 @@ static void copy_stable_id_faces(const Mesh &mesh, } } - dst_attribute.save(); + dst_attribute.finish(); } static void duplicate_faces(GeometrySet &geometry_set, @@ -636,7 +642,7 @@ static void copy_edge_attributes_without_id(GeometrySet &geometry_set, for (const Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; - ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read(attribute_id); + GAttributeReader src_attribute = src_component.attributes()->lookup(attribute_id); if (!src_attribute) { continue; } @@ -644,15 +650,16 @@ static void copy_edge_attributes_without_id(GeometrySet &geometry_set, const eAttrDomain out_domain = src_attribute.domain; const eCustomDataType data_type = bke::cpp_type_to_custom_data_type( src_attribute.varray.type()); - OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( - attribute_id, out_domain, data_type); + GSpanAttributeWriter dst_attribute = + dst_component.attributes_for_write()->lookup_or_add_for_write_only_span( + attribute_id, out_domain, data_type); if (!dst_attribute) { continue; } attribute_math::convert_to_static_type(data_type, [&](auto dummy) { using T = decltype(dummy); VArraySpan src{src_attribute.varray.typed()}; - MutableSpan dst = dst_attribute.as_span(); + MutableSpan dst = dst_attribute.span.typed(); switch (out_domain) { case ATTR_DOMAIN_EDGE: @@ -665,7 +672,7 @@ static void copy_edge_attributes_without_id(GeometrySet &geometry_set, break; } }); - dst_attribute.save(); + dst_attribute.finish(); } } @@ -679,12 +686,13 @@ static void copy_stable_id_edges(const Mesh &mesh, const MeshComponent &src_component, MeshComponent &dst_component) { - ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read("id"); + GAttributeReader src_attribute = src_component.attributes()->lookup("id"); if (!src_attribute) { return; } - OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( - "id", ATTR_DOMAIN_POINT, CD_PROP_INT32); + GSpanAttributeWriter dst_attribute = + dst_component.attributes_for_write()->lookup_or_add_for_write_only_span( + "id", ATTR_DOMAIN_POINT, CD_PROP_INT32); if (!dst_attribute) { return; } @@ -692,7 +700,7 @@ static void copy_stable_id_edges(const Mesh &mesh, Span edges(mesh.medge, mesh.totedge); VArraySpan src{src_attribute.varray.typed()}; - MutableSpan dst = dst_attribute.as_span(); + MutableSpan dst = dst_attribute.span.typed(); threading::parallel_for(IndexRange(selection.size()), 1024, [&](IndexRange range) { for (const int i_selection : range) { const IndexRange edge_range = range_for_offsets_index(edge_offsets, i_selection); @@ -710,7 +718,7 @@ static void copy_stable_id_edges(const Mesh &mesh, } } }); - dst_attribute.save(); + dst_attribute.finish(); } static void duplicate_edges(GeometrySet &geometry_set, @@ -837,7 +845,7 @@ static void duplicate_points_curve(GeometrySet &geometry_set, for (const Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; - ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read(attribute_id); + GAttributeReader src_attribute = src_component.attributes()->lookup(attribute_id); if (!src_attribute) { continue; } @@ -845,8 +853,9 @@ static void duplicate_points_curve(GeometrySet &geometry_set, eAttrDomain domain = src_attribute.domain; const eCustomDataType data_type = bke::cpp_type_to_custom_data_type( src_attribute.varray.type()); - OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( - attribute_id, domain, data_type); + GSpanAttributeWriter dst_attribute = + dst_component.attributes_for_write()->lookup_or_add_for_write_only_span( + attribute_id, domain, data_type); if (!dst_attribute) { continue; } @@ -854,7 +863,7 @@ static void duplicate_points_curve(GeometrySet &geometry_set, attribute_math::convert_to_static_type(data_type, [&](auto dummy) { using T = decltype(dummy); VArraySpan src{src_attribute.varray.typed()}; - MutableSpan dst = dst_attribute.as_span(); + MutableSpan dst = dst_attribute.span.typed(); switch (domain) { case ATTR_DOMAIN_CURVE: @@ -873,7 +882,7 @@ static void duplicate_points_curve(GeometrySet &geometry_set, break; } }); - dst_attribute.save(); + dst_attribute.finish(); } copy_stable_id_point(offsets, src_component, dst_component); @@ -949,7 +958,7 @@ static void duplicate_points_pointcloud(GeometrySet &geometry_set, { const PointCloudComponent &src_points = *geometry_set.get_component_for_read(); - const int point_num = src_points.attribute_domain_num(ATTR_DOMAIN_POINT); + const int point_num = src_points.attribute_domain_size(ATTR_DOMAIN_POINT); GeometryComponentFieldContext field_context{src_points, ATTR_DOMAIN_POINT}; FieldEvaluator evaluator{field_context, point_num}; diff --git a/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc b/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc index 94fbec66bfe..84acab47661 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc @@ -57,8 +57,8 @@ static void node_geo_exec(GeoNodeExecParams params) const MeshComponent &mesh_component = *geometry_set.get_component_for_read(); GeometryComponentFieldContext field_context{mesh_component, ATTR_DOMAIN_EDGE}; - const int domain_num = mesh_component.attribute_domain_num(ATTR_DOMAIN_EDGE); - fn::FieldEvaluator selection_evaluator{field_context, domain_num}; + const int domain_size = mesh_component.attribute_domain_size(ATTR_DOMAIN_EDGE); + fn::FieldEvaluator selection_evaluator{field_context, domain_size}; selection_evaluator.add(selection_field); selection_evaluator.evaluate(); const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); diff --git a/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc index 59d7154db6e..baa9952b950 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc @@ -66,21 +66,21 @@ static void save_selection_as_attribute(MeshComponent &component, const eAttrDomain domain, const IndexMask selection) { - BLI_assert(!component.attribute_exists(id)); + BLI_assert(!component.attributes()->contains(id)); - OutputAttribute_Typed attribute = component.attribute_try_get_for_output_only( - id, domain); + SpanAttributeWriter attribute = + component.attributes_for_write()->lookup_or_add_for_write_span(id, domain); /* Rely on the new attribute being zeroed by default. */ - BLI_assert(!attribute.as_span().as_span().contains(true)); + BLI_assert(!attribute.span.as_span().contains(true)); if (selection.is_range()) { - attribute.as_span().slice(selection.as_range()).fill(true); + attribute.span.slice(selection.as_range()).fill(true); } else { - attribute.as_span().fill_indices(selection, true); + attribute.span.fill_indices(selection, true); } - attribute.save(); + attribute.finish(); } static MutableSpan mesh_verts(Mesh &mesh) @@ -168,7 +168,7 @@ static MutableSpan get_orig_index_layer(Mesh &mesh, const eAttrDomain domai component.replace(&mesh, GeometryOwnershipType::ReadOnly); CustomData &custom_data = get_customdata(mesh, domain); if (int *orig_indices = static_cast(CustomData_get_layer(&custom_data, CD_ORIGINDEX))) { - return {orig_indices, component.attribute_domain_num(domain)}; + return {orig_indices, component.attribute_domain_size(domain)}; } return {}; } @@ -280,16 +280,18 @@ static void extrude_mesh_vertices(MeshComponent &component, new_edges[i_selection] = new_loose_edge(selection[i_selection], new_vert_range[i_selection]); } - component.attribute_foreach([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { + MutableAttributeAccessor attributes = *component.attributes_for_write(); + + attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { if (!ELEM(meta_data.domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_EDGE)) { return true; } - OutputAttribute attribute = component.attribute_try_get_for_output( + GSpanAttributeWriter attribute = attributes.lookup_or_add_for_write_span( id, meta_data.domain, meta_data.data_type); attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) { using T = decltype(dummy); - MutableSpan data = attribute.as_span().typed(); - switch (attribute.domain()) { + MutableSpan data = attribute.span.typed(); + switch (attribute.domain) { case ATTR_DOMAIN_POINT: { /* New vertices copy the attribute values from their source vertex. */ copy_with_mask(data.slice(new_vert_range), data.as_span(), selection); @@ -307,7 +309,7 @@ static void extrude_mesh_vertices(MeshComponent &component, } }); - attribute.save(); + attribute.finish(); return true; }); @@ -524,8 +526,10 @@ static void extrude_mesh_edges(MeshComponent &component, const Array> new_vert_to_duplicate_edge_map = create_vert_to_edge_map( new_vert_range.size(), duplicate_edges, orig_vert_size); - component.attribute_foreach([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { - OutputAttribute attribute = component.attribute_try_get_for_output( + MutableAttributeAccessor attributes = *component.attributes_for_write(); + + attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { + GSpanAttributeWriter attribute = attributes.lookup_or_add_for_write_span( id, meta_data.domain, meta_data.data_type); if (!attribute) { return true; /* Impossible to write the "normal" attribute. */ @@ -533,8 +537,8 @@ static void extrude_mesh_edges(MeshComponent &component, attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) { using T = decltype(dummy); - MutableSpan data = attribute.as_span().typed(); - switch (attribute.domain()) { + MutableSpan data = attribute.span.typed(); + switch (attribute.domain) { case ATTR_DOMAIN_POINT: { /* New vertices copy the attribute values from their source vertex. */ copy_with_indices(data.slice(new_vert_range), data.as_span(), new_vert_indices); @@ -626,7 +630,7 @@ static void extrude_mesh_edges(MeshComponent &component, } }); - attribute.save(); + attribute.finish(); return true; }); @@ -902,8 +906,10 @@ static void extrude_mesh_face_regions(MeshComponent &component, const Array> new_vert_to_duplicate_edge_map = create_vert_to_edge_map( new_vert_range.size(), boundary_edges, orig_vert_size); - component.attribute_foreach([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { - OutputAttribute attribute = component.attribute_try_get_for_output( + MutableAttributeAccessor attributes = *component.attributes_for_write(); + + attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { + GSpanAttributeWriter attribute = attributes.lookup_or_add_for_write_span( id, meta_data.domain, meta_data.data_type); if (!attribute) { return true; /* Impossible to write the "normal" attribute. */ @@ -911,8 +917,8 @@ static void extrude_mesh_face_regions(MeshComponent &component, attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) { using T = decltype(dummy); - MutableSpan data = attribute.as_span().typed(); - switch (attribute.domain()) { + MutableSpan data = attribute.span.typed(); + switch (attribute.domain) { case ATTR_DOMAIN_POINT: { /* New vertices copy the attributes from their original vertices. */ copy_with_indices(data.slice(new_vert_range), data.as_span(), new_vert_indices); @@ -991,7 +997,7 @@ static void extrude_mesh_face_regions(MeshComponent &component, } }); - attribute.save(); + attribute.finish(); return true; }); @@ -1154,8 +1160,10 @@ static void extrude_individual_mesh_faces(MeshComponent &component, } }); - component.attribute_foreach([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { - OutputAttribute attribute = component.attribute_try_get_for_output( + MutableAttributeAccessor attributes = *component.attributes_for_write(); + + attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { + GSpanAttributeWriter attribute = attributes.lookup_or_add_for_write_span( id, meta_data.domain, meta_data.data_type); if (!attribute) { return true; /* Impossible to write the "normal" attribute. */ @@ -1163,8 +1171,8 @@ static void extrude_individual_mesh_faces(MeshComponent &component, attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) { using T = decltype(dummy); - MutableSpan data = attribute.as_span().typed(); - switch (attribute.domain()) { + MutableSpan data = attribute.span.typed(); + switch (attribute.domain) { case ATTR_DOMAIN_POINT: { /* New vertices copy the attributes from their original vertices. */ MutableSpan new_data = data.slice(new_vert_range); @@ -1267,7 +1275,7 @@ static void extrude_individual_mesh_faces(MeshComponent &component, } }); - attribute.save(); + attribute.finish(); return true; }); diff --git a/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc b/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc index 7839e148ee3..64861e529bc 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc @@ -91,7 +91,7 @@ class FieldAtIndex final : public GeometryFieldInput { { const GeometryComponentFieldContext value_field_context{component, value_field_domain_}; FieldEvaluator value_evaluator{value_field_context, - component.attribute_domain_num(value_field_domain_)}; + component.attribute_domain_size(value_field_domain_)}; value_evaluator.add(value_field_); value_evaluator.evaluate(); const GVArray &values = value_evaluator.get_evaluated(0); diff --git a/source/blender/nodes/geometry/nodes/node_geo_field_on_domain.cc b/source/blender/nodes/geometry/nodes/node_geo_field_on_domain.cc index e6906f4fb21..5939ed5334d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_field_on_domain.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_field_on_domain.cc @@ -85,12 +85,12 @@ class FieldOnDomain final : public GeometryFieldInput { IndexMask /* mask */) const final { const GeometryComponentFieldContext context{component, src_domain_}; - FieldEvaluator value_evaluator{context, component.attribute_domain_num(src_domain_)}; + FieldEvaluator value_evaluator{context, component.attribute_domain_size(src_domain_)}; value_evaluator.add(src_field_); value_evaluator.evaluate(); const GVArray &values = value_evaluator.get_evaluated(0); - return component.attribute_try_adapt_domain(values, src_domain_, domain); + return component.attributes()->adapt_domain(values, src_domain_, domain); } }; diff --git a/source/blender/nodes/geometry/nodes/node_geo_flip_faces.cc b/source/blender/nodes/geometry/nodes/node_geo_flip_faces.cc index 0484017cf3b..15b2822805a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_flip_faces.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_flip_faces.cc @@ -22,11 +22,11 @@ static void node_declare(NodeDeclarationBuilder &b) static void mesh_flip_faces(MeshComponent &component, const Field &selection_field) { GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_FACE}; - const int domain_num = component.attribute_domain_num(ATTR_DOMAIN_FACE); - if (domain_num == 0) { + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_FACE); + if (domain_size == 0) { return; } - fn::FieldEvaluator evaluator{field_context, domain_num}; + fn::FieldEvaluator evaluator{field_context, domain_size}; evaluator.add(selection_field); evaluator.evaluate(); const IndexMask selection = evaluator.get_evaluated_as_mask(0); @@ -49,20 +49,21 @@ static void mesh_flip_faces(MeshComponent &component, const Field &selecti } } - component.attribute_foreach( + MutableAttributeAccessor attributes = *component.attributes_for_write(); + attributes.for_all( [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { if (meta_data.domain == ATTR_DOMAIN_CORNER) { - OutputAttribute attribute = component.attribute_try_get_for_output( - attribute_id, ATTR_DOMAIN_CORNER, meta_data.data_type, nullptr); + GSpanAttributeWriter attribute = attributes.lookup_or_add_for_write_span( + attribute_id, ATTR_DOMAIN_CORNER, meta_data.data_type); attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) { using T = decltype(dummy); - MutableSpan dst_span = attribute.as_span(); + MutableSpan dst_span = attribute.span.typed(); for (const int j : selection.index_range()) { const MPoly &poly = polys[selection[j]]; dst_span.slice(poly.loopstart + 1, poly.totloop - 1).reverse(); } }); - attribute.save(); + attribute.finish(); } return true; }); diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_curve_handles.cc b/source/blender/nodes/geometry/nodes/node_geo_input_curve_handles.cc index e0aaf43235c..bc1b9e940a1 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_curve_handles.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_curve_handles.cc @@ -39,11 +39,13 @@ class HandlePositionFieldInput final : public GeometryFieldInput { evaluator.evaluate(); const VArray relative = evaluator.get_evaluated(0); - VArray positions = component.attribute_get_for_read( + const AttributeAccessor attributes = *component.attributes(); + + VArray positions = attributes.lookup_or_default( "position", ATTR_DOMAIN_POINT, {0, 0, 0}); StringRef side = left_ ? "handle_left" : "handle_right"; - VArray handles = component.attribute_get_for_read( + VArray handles = attributes.lookup_or_default( side, ATTR_DOMAIN_POINT, {0, 0, 0}); if (relative.is_single()) { @@ -52,10 +54,10 @@ class HandlePositionFieldInput final : public GeometryFieldInput { for (const int i : positions.index_range()) { output[i] = handles[i] - positions[i]; } - return component.attribute_try_adapt_domain( + return attributes.adapt_domain( VArray::ForContainer(std::move(output)), ATTR_DOMAIN_POINT, domain); } - return component.attribute_try_adapt_domain(handles, ATTR_DOMAIN_POINT, domain); + return attributes.adapt_domain(handles, ATTR_DOMAIN_POINT, domain); } Array output(positions.size()); @@ -67,7 +69,7 @@ class HandlePositionFieldInput final : public GeometryFieldInput { output[i] = handles[i]; } } - return component.attribute_try_adapt_domain( + return component.attributes()->adapt_domain( VArray::ForContainer(std::move(output)), ATTR_DOMAIN_POINT, domain); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_angle.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_angle.cc index f27e0305c7d..b009aaa5291 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_angle.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_angle.cc @@ -91,7 +91,7 @@ class AngleFieldInput final : public GeometryFieldInput { }; VArray angles = VArray::ForFunc(mesh->totedge, angle_fn); - return component.attribute_try_adapt_domain( + return component.attributes()->adapt_domain( std::move(angles), ATTR_DOMAIN_EDGE, domain); } @@ -166,7 +166,7 @@ class SignedAngleFieldInput final : public GeometryFieldInput { }; VArray angles = VArray::ForFunc(mesh->totedge, angle_fn); - return component.attribute_try_adapt_domain( + return component.attributes()->adapt_domain( std::move(angles), ATTR_DOMAIN_EDGE, domain); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_neighbors.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_neighbors.cc index cbc2ebc3e68..50d6998bb27 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_neighbors.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_neighbors.cc @@ -40,7 +40,7 @@ class EdgeNeighborCountFieldInput final : public GeometryFieldInput { face_count[mesh->mloop[i].e]++; } - return mesh_component.attribute_try_adapt_domain( + return mesh_component.attributes()->adapt_domain( VArray::ForContainer(std::move(face_count)), ATTR_DOMAIN_EDGE, domain); } return {}; diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_vertices.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_vertices.cc index 6201ad26bfb..83e511f45c2 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_vertices.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_vertices.cc @@ -93,14 +93,14 @@ static VArray construct_edge_positions_gvarray(const MeshComponent &comp } if (vertex == VERTEX_ONE) { - return component.attribute_try_adapt_domain( + return component.attributes()->adapt_domain( VArray::ForFunc( mesh->totedge, [mesh](const int i) { return float3(mesh->mvert[mesh->medge[i].v1].co); }), ATTR_DOMAIN_EDGE, domain); } - return component.attribute_try_adapt_domain( + return component.attributes()->adapt_domain( VArray::ForFunc( mesh->totedge, [mesh](const int i) { return float3(mesh->mvert[mesh->medge[i].v2].co); }), diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_area.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_area.cc index 7a0e3e37a65..4d21bf9443a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_area.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_area.cc @@ -29,7 +29,7 @@ static VArray construct_face_area_gvarray(const MeshComponent &component, return BKE_mesh_calc_poly_area(mp, &mesh->mloop[mp->loopstart], mesh->mvert); }; - return component.attribute_try_adapt_domain( + return component.attributes()->adapt_domain( VArray::ForFunc(mesh->totpoly, area_fn), ATTR_DOMAIN_FACE, domain); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc index d02f7291704..6b04ff08d9e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc @@ -80,7 +80,7 @@ class PlanarFieldInput final : public GeometryFieldInput { return max - min < thresholds[i_poly] / 2.0f; }; - return component.attribute_try_adapt_domain( + return component.attributes()->adapt_domain( VArray::ForFunc(mesh->totpoly, planar_fn), ATTR_DOMAIN_FACE, domain); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_neighbors.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_neighbors.cc index 67a21cb06f0..a225ce61b14 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_neighbors.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_neighbors.cc @@ -40,7 +40,7 @@ static VArray construct_neighbor_count_gvarray(const MeshComponent &compone } } - return component.attribute_try_adapt_domain( + return component.attributes()->adapt_domain( VArray::ForContainer(std::move(poly_count)), ATTR_DOMAIN_FACE, domain); } @@ -83,7 +83,7 @@ static VArray construct_vertex_count_gvarray(const MeshComponent &component return {}; } - return component.attribute_try_adapt_domain( + return component.attributes()->adapt_domain( VArray::ForFunc(mesh->totpoly, [mesh](const int i) -> float { return mesh->mpoly[i].totloop; }), ATTR_DOMAIN_FACE, diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_island.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_island.cc index bd57924d685..2c7eef5665f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_island.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_island.cc @@ -54,7 +54,7 @@ class IslandFieldInput final : public GeometryFieldInput { output[i] = ordered_roots.index_of_or_add(root); } - return mesh_component.attribute_try_adapt_domain( + return mesh_component.attributes()->adapt_domain( VArray::ForContainer(std::move(output)), ATTR_DOMAIN_POINT, domain); } @@ -101,7 +101,8 @@ class IslandCountFieldInput final : public GeometryFieldInput { island_list.add(root); } - return VArray::ForSingle(island_list.size(), mesh_component.attribute_domain_num(domain)); + return VArray::ForSingle(island_list.size(), + mesh_component.attribute_domain_size(domain)); } uint64_t hash() const override diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_spline_length.cc b/source/blender/nodes/geometry/nodes/node_geo_input_spline_length.cc index def82eefca5..267ba44cc00 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_spline_length.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_spline_length.cc @@ -32,7 +32,7 @@ static VArray construct_curve_point_count_gvarray(const CurveComponent &com } if (domain == ATTR_DOMAIN_POINT) { VArray count = VArray::ForFunc(curves.curves_num(), count_fn); - return component.attribute_try_adapt_domain( + return component.attributes()->adapt_domain( std::move(count), ATTR_DOMAIN_CURVE, ATTR_DOMAIN_POINT); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc b/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc index f5831941094..a2aab5464aa 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc @@ -75,7 +75,7 @@ static VArray construct_curve_tangent_gvarray(const CurveComponent &comp const VArray types = curves.curve_types(); if (curves.is_single_type(CURVE_TYPE_POLY)) { - return component.attribute_try_adapt_domain( + return component.attributes()->adapt_domain( VArray::ForSpan(curves.evaluated_tangents()), ATTR_DOMAIN_POINT, domain); } @@ -86,7 +86,7 @@ static VArray construct_curve_tangent_gvarray(const CurveComponent &comp } if (domain == ATTR_DOMAIN_CURVE) { - return component.attribute_try_adapt_domain( + return component.attributes()->adapt_domain( VArray::ForContainer(std::move(tangents)), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc b/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc index 21ef8765e43..3ce26a086e2 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc @@ -50,7 +50,7 @@ static void add_instances_from_component( const Map &attributes_to_propagate) { const eAttrDomain domain = ATTR_DOMAIN_POINT; - const int domain_num = src_component.attribute_domain_num(domain); + const int domain_num = src_component.attribute_domain_size(domain); VArray pick_instance; VArray indices; @@ -82,7 +82,7 @@ static void add_instances_from_component( MutableSpan dst_transforms = dst_component.instance_transforms().slice(start_len, select_len); - VArray positions = src_component.attribute_get_for_read( + VArray positions = src_component.attributes()->lookup_or_default( "position", domain, {0, 0, 0}); const InstancesComponent *src_instances = instance.get_component_for_read(); @@ -154,12 +154,12 @@ static void add_instances_from_component( } } - bke::CustomDataAttributes &instance_attributes = dst_component.attributes(); + bke::CustomDataAttributes &instance_attributes = dst_component.instance_attributes(); for (const auto item : attributes_to_propagate.items()) { const AttributeIDRef &attribute_id = item.key; const AttributeKind attribute_kind = item.value; - const GVArray src_attribute = src_component.attribute_get_for_read( + const GVArray src_attribute = src_component.attributes()->lookup_or_default( attribute_id, ATTR_DOMAIN_POINT, attribute_kind.data_type); BLI_assert(src_attribute); std::optional dst_attribute_opt = instance_attributes.get_for_write( diff --git a/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc index ffc6137cf83..edbe6e1593f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc @@ -40,9 +40,9 @@ static void convert_instances_to_points(GeometrySet &geometry_set, const InstancesComponent &instances = *geometry_set.get_component_for_read(); GeometryComponentFieldContext field_context{instances, ATTR_DOMAIN_INSTANCE}; - const int domain_num = instances.attribute_domain_num(ATTR_DOMAIN_INSTANCE); + const int domain_size = instances.instances_num(); - fn::FieldEvaluator evaluator{field_context, domain_num}; + fn::FieldEvaluator evaluator{field_context, domain_size}; evaluator.set_selection(std::move(selection_field)); evaluator.add(std::move(position_field)); evaluator.add(std::move(radius_field)); @@ -75,18 +75,18 @@ static void convert_instances_to_points(GeometrySet &geometry_set, const AttributeIDRef &attribute_id = item.key; const AttributeKind attribute_kind = item.value; - const GVArray src = instances.attribute_get_for_read( + const GVArray src = instances.attributes()->lookup_or_default( attribute_id, ATTR_DOMAIN_INSTANCE, attribute_kind.data_type); BLI_assert(src); - OutputAttribute dst = points.attribute_try_get_for_output_only( + GSpanAttributeWriter dst = points.attributes_for_write()->lookup_or_add_for_write_only_span( attribute_id, ATTR_DOMAIN_POINT, attribute_kind.data_type); BLI_assert(dst); attribute_math::convert_to_static_type(attribute_kind.data_type, [&](auto dummy) { using T = decltype(dummy); - copy_attribute_to_points(src.typed(), selection, dst.as_span().typed()); + copy_attribute_to_points(src.typed(), selection, dst.span.typed()); }); - dst.save(); + dst.finish(); } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc index 62cd1d8ac3a..083a505539a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc @@ -24,7 +24,7 @@ static Map get_final_attribute_info( Map info; for (const GeometryComponent *component : components) { - component->attribute_foreach( + component->attributes()->for_all( [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { if (attribute_id.is_named() && ignored_attributes.contains(attribute_id.name())) { return true; @@ -56,11 +56,11 @@ static void fill_new_attribute(Span src_components, int offset = 0; for (const GeometryComponent *component : src_components) { - const int domain_num = component->attribute_domain_num(domain); + const int domain_num = component->attribute_domain_size(domain); if (domain_num == 0) { continue; } - GVArray read_attribute = component->attribute_get_for_read( + GVArray read_attribute = component->attributes()->lookup_or_default( attribute_id, domain, data_type, nullptr); GVArraySpan src_span{read_attribute}; @@ -83,15 +83,15 @@ static void join_attributes(Span src_components, const AttributeIDRef attribute_id = item.key; const AttributeMetaData &meta_data = item.value; - OutputAttribute write_attribute = result.attribute_try_get_for_output_only( - attribute_id, meta_data.domain, meta_data.data_type); + GSpanAttributeWriter write_attribute = + result.attributes_for_write()->lookup_or_add_for_write_only_span( + attribute_id, meta_data.domain, meta_data.data_type); if (!write_attribute) { continue; } - GMutableSpan dst_span = write_attribute.as_span(); fill_new_attribute( - src_components, attribute_id, meta_data.data_type, meta_data.domain, dst_span); - write_attribute.save(); + src_components, attribute_id, meta_data.data_type, meta_data.domain, write_attribute.span); + write_attribute.finish(); } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc b/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc index 5875606da97..ca613ae009b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc @@ -71,7 +71,7 @@ class MaterialSelectionFieldInput final : public GeometryFieldInput { Array selection(mesh->totpoly); select_mesh_by_material(*mesh, material_, IndexMask(mesh->totpoly), selection); - return mesh_component.attribute_try_adapt_domain( + return mesh_component.attributes()->adapt_domain( VArray::ForContainer(std::move(selection)), ATTR_DOMAIN_FACE, domain); return nullptr; diff --git a/source/blender/nodes/geometry/nodes/node_geo_merge_by_distance.cc b/source/blender/nodes/geometry/nodes/node_geo_merge_by_distance.cc index 1def4089115..4bc84a7a08a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_merge_by_distance.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_merge_by_distance.cc @@ -39,7 +39,7 @@ static PointCloud *pointcloud_merge_by_distance(const PointCloudComponent &src_p const float merge_distance, const Field &selection_field) { - const int src_num = src_points.attribute_domain_num(ATTR_DOMAIN_POINT); + const int src_num = src_points.attribute_domain_size(ATTR_DOMAIN_POINT); GeometryComponentFieldContext context{src_points, ATTR_DOMAIN_POINT}; FieldEvaluator evaluator{context, src_num}; evaluator.add(selection_field); @@ -57,7 +57,7 @@ static std::optional mesh_merge_by_distance_connected(const MeshComponen const float merge_distance, const Field &selection_field) { - const int src_num = mesh_component.attribute_domain_num(ATTR_DOMAIN_POINT); + const int src_num = mesh_component.attribute_domain_size(ATTR_DOMAIN_POINT); Array selection(src_num); GeometryComponentFieldContext context{mesh_component, ATTR_DOMAIN_POINT}; FieldEvaluator evaluator{context, src_num}; @@ -72,7 +72,7 @@ static std::optional mesh_merge_by_distance_all(const MeshComponent &mes const float merge_distance, const Field &selection_field) { - const int src_num = mesh_component.attribute_domain_num(ATTR_DOMAIN_POINT); + const int src_num = mesh_component.attribute_domain_size(ATTR_DOMAIN_POINT); GeometryComponentFieldContext context{mesh_component, ATTR_DOMAIN_POINT}; FieldEvaluator evaluator{context, src_num}; evaluator.add(selection_field); 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 b882d4bdf09..cb79ef93de9 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 @@ -480,53 +480,49 @@ static void calculate_selection_outputs(Mesh *mesh, const ConeConfig &config, ConeAttributeOutputs &attribute_outputs) { - MeshComponent mesh_component; - mesh_component.replace(mesh, GeometryOwnershipType::Editable); + MutableAttributeAccessor attributes = bke::mesh_attributes_for_write(*mesh); /* Populate "Top" selection output. */ if (attribute_outputs.top_id) { const bool face = !config.top_is_point && config.fill_type != GEO_NODE_MESH_CIRCLE_FILL_NONE; - OutputAttribute_Typed attribute = mesh_component.attribute_try_get_for_output_only( + SpanAttributeWriter selection = attributes.lookup_or_add_for_write_only_span( attribute_outputs.top_id.get(), face ? ATTR_DOMAIN_FACE : ATTR_DOMAIN_POINT); - MutableSpan selection = attribute.as_span(); if (config.top_is_point) { - selection[config.first_vert] = true; + selection.span[config.first_vert] = true; } else { - selection.slice(0, face ? config.top_faces_len : config.circle_segments).fill(true); + selection.span.slice(0, face ? config.top_faces_len : config.circle_segments).fill(true); } - attribute.save(); + selection.finish(); } /* Populate "Bottom" selection output. */ if (attribute_outputs.bottom_id) { const bool face = !config.bottom_is_point && config.fill_type != GEO_NODE_MESH_CIRCLE_FILL_NONE; - OutputAttribute_Typed attribute = mesh_component.attribute_try_get_for_output_only( + SpanAttributeWriter selection = attributes.lookup_or_add_for_write_only_span( attribute_outputs.bottom_id.get(), face ? ATTR_DOMAIN_FACE : ATTR_DOMAIN_POINT); - MutableSpan selection = attribute.as_span(); if (config.bottom_is_point) { - selection[config.last_vert] = true; + selection.span[config.last_vert] = true; } else if (face) { - selection.slice(config.bottom_faces_start, config.bottom_faces_len).fill(true); + selection.span.slice(config.bottom_faces_start, config.bottom_faces_len).fill(true); } else { - selection.slice(config.last_ring_verts_start + 1, config.circle_segments).fill(true); + selection.span.slice(config.last_ring_verts_start + 1, config.circle_segments).fill(true); } - attribute.save(); + selection.finish(); } /* Populate "Side" selection output. */ if (attribute_outputs.side_id) { - OutputAttribute_Typed attribute = mesh_component.attribute_try_get_for_output_only( + SpanAttributeWriter selection = attributes.lookup_or_add_for_write_only_span( attribute_outputs.side_id.get(), ATTR_DOMAIN_FACE); - MutableSpan selection = attribute.as_span(); - selection.slice(config.side_faces_start, config.side_faces_len).fill(true); - attribute.save(); + selection.span.slice(config.side_faces_start, config.side_faces_len).fill(true); + selection.finish(); } } @@ -540,11 +536,11 @@ static void calculate_selection_outputs(Mesh *mesh, */ static void calculate_cone_uvs(Mesh *mesh, const ConeConfig &config) { - MeshComponent mesh_component; - mesh_component.replace(mesh, GeometryOwnershipType::Editable); - OutputAttribute_Typed uv_attribute = - mesh_component.attribute_try_get_for_output_only("uv_map", ATTR_DOMAIN_CORNER); - MutableSpan uvs = uv_attribute.as_span(); + MutableAttributeAccessor attributes = bke::mesh_attributes_for_write(*mesh); + + SpanAttributeWriter uv_attribute = attributes.lookup_or_add_for_write_only_span( + "uv_map", ATTR_DOMAIN_CORNER); + MutableSpan uvs = uv_attribute.span; Array circle(config.circle_segments); float angle = 0.0f; @@ -654,7 +650,7 @@ static void calculate_cone_uvs(Mesh *mesh, const ConeConfig &config) } } - uv_attribute.save(); + uv_attribute.finish(); } static Mesh *create_vertex_mesh() diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc index 523dbd5dac2..9baf0b3171e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc @@ -18,23 +18,22 @@ namespace blender::nodes { static void calculate_uvs( Mesh *mesh, Span verts, Span loops, const float size_x, const float size_y) { - MeshComponent mesh_component; - mesh_component.replace(mesh, GeometryOwnershipType::Editable); - OutputAttribute_Typed uv_attribute = - mesh_component.attribute_try_get_for_output_only("uv_map", ATTR_DOMAIN_CORNER); - MutableSpan uvs = uv_attribute.as_span(); + MutableAttributeAccessor attributes = bke::mesh_attributes_for_write(*mesh); + + SpanAttributeWriter uv_attribute = attributes.lookup_or_add_for_write_only_span( + "uv_map", ATTR_DOMAIN_CORNER); const float dx = (size_x == 0.0f) ? 0.0f : 1.0f / size_x; const float dy = (size_y == 0.0f) ? 0.0f : 1.0f / size_y; threading::parallel_for(loops.index_range(), 1024, [&](IndexRange range) { for (const int i : range) { const float3 &co = verts[loops[i].v].co; - uvs[i].x = (co.x + size_x * 0.5f) * dx; - uvs[i].y = (co.y + size_y * 0.5f) * dy; + uv_attribute.span[i].x = (co.x + size_x * 0.5f) * dx; + uv_attribute.span[i].y = (co.y + size_y * 0.5f) * dy; } }); - uv_attribute.save(); + uv_attribute.finish(); } Mesh *create_grid_mesh(const int verts_x, 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 4e0e5c7c912..f78752387c6 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 @@ -220,11 +220,11 @@ static void calculate_sphere_faces(MutableSpan loops, static void calculate_sphere_uvs(Mesh *mesh, const float segments, const float rings) { - MeshComponent mesh_component; - mesh_component.replace(mesh, GeometryOwnershipType::Editable); - OutputAttribute_Typed uv_attribute = - mesh_component.attribute_try_get_for_output_only("uv_map", ATTR_DOMAIN_CORNER); - MutableSpan uvs = uv_attribute.as_span(); + MutableAttributeAccessor attributes = bke::mesh_attributes_for_write(*mesh); + + SpanAttributeWriter uv_attribute = attributes.lookup_or_add_for_write_only_span( + "uv_map", ATTR_DOMAIN_CORNER); + MutableSpan uvs = uv_attribute.span; int loop_index = 0; const float dy = 1.0f / rings; @@ -254,7 +254,7 @@ static void calculate_sphere_uvs(Mesh *mesh, const float segments, const float r uvs[loop_index++] = float2(segment / segments, 1.0f - dy); } - uv_attribute.save(); + uv_attribute.finish(); } static Mesh *create_uv_sphere_mesh(const float radius, const int segments, const int rings) diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc index ec6acf55dd8..f6ee3d00dee 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc @@ -25,7 +25,7 @@ static void node_geo_exec(GeoNodeExecParams params) const MeshComponent &component = *geometry_set.get_component_for_read(); GeometryComponentFieldContext context{component, ATTR_DOMAIN_EDGE}; - fn::FieldEvaluator evaluator{context, component.attribute_domain_num(ATTR_DOMAIN_EDGE)}; + fn::FieldEvaluator evaluator{context, component.attribute_domain_size(ATTR_DOMAIN_EDGE)}; evaluator.add(params.get_input>("Selection")); evaluator.evaluate(); const IndexMask selection = evaluator.get_evaluated_as_mask(0); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc index 7463eb01471..8e621d3ed97 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc @@ -66,7 +66,7 @@ static void geometry_set_mesh_to_points(GeometrySet &geometry_set, return; } GeometryComponentFieldContext field_context{*mesh_component, domain}; - const int domain_num = mesh_component->attribute_domain_num(domain); + const int domain_num = mesh_component->attribute_domain_size(domain); if (domain_num == 0) { geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); return; @@ -83,20 +83,20 @@ static void geometry_set_mesh_to_points(GeometrySet &geometry_set, PointCloud *pointcloud = BKE_pointcloud_new_nomain(selection.size()); geometry_set.replace_pointcloud(pointcloud); - PointCloudComponent &point_component = - geometry_set.get_component_for_write(); + MutableAttributeAccessor pointcloud_attributes = bke::pointcloud_attributes_for_write( + *pointcloud); - OutputAttribute position = point_component.attribute_try_get_for_output_only( + GSpanAttributeWriter position = pointcloud_attributes.lookup_or_add_for_write_only_span( "position", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3); materialize_compressed_to_uninitialized_threaded( - evaluator.get_evaluated(0), selection, position.as_span()); - position.save(); + evaluator.get_evaluated(0), selection, position.span); + position.finish(); - OutputAttribute radius = point_component.attribute_try_get_for_output_only( + GSpanAttributeWriter radius = pointcloud_attributes.lookup_or_add_for_write_only_span( "radius", ATTR_DOMAIN_POINT, CD_PROP_FLOAT); materialize_compressed_to_uninitialized_threaded( - evaluator.get_evaluated(1), selection, radius.as_span()); - radius.save(); + evaluator.get_evaluated(1), selection, radius.span); + radius.finish(); Map attributes; geometry_set.gather_attributes_for_propagation( @@ -106,12 +106,12 @@ static void geometry_set_mesh_to_points(GeometrySet &geometry_set, for (Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; const eCustomDataType data_type = entry.value.data_type; - GVArray src = mesh_component->attribute_get_for_read(attribute_id, domain, data_type); - OutputAttribute dst = point_component.attribute_try_get_for_output_only( + GVArray src = mesh_component->attributes()->lookup_or_default(attribute_id, domain, data_type); + GSpanAttributeWriter dst = pointcloud_attributes.lookup_or_add_for_write_only_span( attribute_id, ATTR_DOMAIN_POINT, data_type); if (dst && src) { - materialize_compressed_to_uninitialized_threaded(src, selection, dst.as_span()); - dst.save(); + materialize_compressed_to_uninitialized_threaded(src, selection, dst.span); + dst.finish(); } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_points.cc b/source/blender/nodes/geometry/nodes/node_geo_points.cc index da414960e1d..dd32e6714f4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points.cc @@ -72,19 +72,20 @@ static void node_geo_exec(GeoNodeExecParams params) PointCloud *new_point_cloud = BKE_pointcloud_new_nomain(count); GeometrySet geometry_set = GeometrySet::create_with_pointcloud(new_point_cloud); PointCloudComponent &points = geometry_set.get_component_for_write(); - OutputAttribute_Typed output_position = points.attribute_try_get_for_output_only( + MutableAttributeAccessor attributes = *points.attributes_for_write(); + AttributeWriter output_position = attributes.lookup_or_add_for_write( "position", ATTR_DOMAIN_POINT); - OutputAttribute_Typed output_radii = points.attribute_try_get_for_output_only( + AttributeWriter output_radii = attributes.lookup_or_add_for_write( "radius", ATTR_DOMAIN_POINT); PointsFieldContext context{count}; fn::FieldEvaluator evaluator{context, count}; - evaluator.add_with_destination(position_field, output_position.as_span()); - evaluator.add_with_destination(radius_field, output_radii.as_span()); + evaluator.add_with_destination(position_field, output_position.varray); + evaluator.add_with_destination(radius_field, output_radii.varray); evaluator.evaluate(); - output_position.save(); - output_radii.save(); + output_position.finish(); + output_radii.finish(); params.set_output("Geometry", std::move(geometry_set)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc index 95ff53b7146..9cc64d4bc44 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc @@ -38,7 +38,7 @@ static void geometry_set_points_to_vertices(GeometrySet &geometry_set, } GeometryComponentFieldContext field_context{*point_component, ATTR_DOMAIN_POINT}; - const int domain_num = point_component->attribute_domain_num(ATTR_DOMAIN_POINT); + const int domain_num = point_component->attribute_domain_size(ATTR_DOMAIN_POINT); if (domain_num == 0) { geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); return; @@ -60,18 +60,19 @@ static void geometry_set_points_to_vertices(GeometrySet &geometry_set, for (Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; const eCustomDataType data_type = entry.value.data_type; - GVArray src = point_component->attribute_get_for_read( - attribute_id, ATTR_DOMAIN_POINT, data_type); - OutputAttribute dst = mesh_component.attribute_try_get_for_output_only( + GVArray src = point_component->attributes()->lookup_or_default( attribute_id, ATTR_DOMAIN_POINT, data_type); + GSpanAttributeWriter dst = + mesh_component.attributes_for_write()->lookup_or_add_for_write_only_span( + attribute_id, ATTR_DOMAIN_POINT, data_type); if (dst && src) { attribute_math::convert_to_static_type(data_type, [&](auto dummy) { using T = decltype(dummy); VArray src_typed = src.typed(); VArraySpan src_typed_span{src_typed}; - copy_attribute_to_vertices(src_typed_span, selection, dst.as_span().typed()); + copy_attribute_to_vertices(src_typed_span, selection, dst.span.typed()); }); - dst.save(); + dst.finish(); } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc index 42cee4c0efe..28a01a5cbce 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc @@ -163,12 +163,15 @@ static void gather_point_data_from_component(GeoNodeExecParams ¶ms, Vector &r_positions, Vector &r_radii) { - VArray positions = component.attribute_get_for_read( + if (component.is_empty()) { + return; + } + VArray positions = component.attributes()->lookup_or_default( "position", ATTR_DOMAIN_POINT, {0, 0, 0}); Field radius_field = params.get_input>("Radius"); GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; - const int domain_num = component.attribute_domain_num(ATTR_DOMAIN_POINT); + const int domain_num = component.attribute_domain_size(ATTR_DOMAIN_POINT); r_positions.resize(r_positions.size() + domain_num); positions.materialize(r_positions.as_mutable_span().take_back(domain_num)); diff --git a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc index a92cee2d066..f81748da587 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc @@ -312,8 +312,8 @@ class RaycastFunction : public fn::MultiFunction { } const MeshComponent &mesh_component = *target_.get_component_for_read(); target_context_.emplace(GeometryComponentFieldContext{mesh_component, domain_}); - const int domain_num = mesh_component.attribute_domain_num(domain_); - target_evaluator_ = std::make_unique(*target_context_, domain_num); + const int domain_size = mesh_component.attribute_domain_size(domain_); + target_evaluator_ = std::make_unique(*target_context_, domain_size); target_evaluator_->add(std::move(src_field)); target_evaluator_->evaluate(); target_data_ = &target_evaluator_->get_evaluated(0); diff --git a/source/blender/nodes/geometry/nodes/node_geo_remove_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_remove_attribute.cc index da42b8c5ee0..ee279ba58f9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_remove_attribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_remove_attribute.cc @@ -39,7 +39,7 @@ static void node_geo_exec(GeoNodeExecParams params) /* First check if the attribute exists before getting write access, * to avoid potentially expensive unnecessary copies. */ const GeometryComponent &read_only_component = *geometry_set.get_component_for_read(type); - if (read_only_component.attribute_exists(name)) { + if (read_only_component.attributes()->contains(name)) { attribute_exists = true; } else { @@ -47,7 +47,7 @@ static void node_geo_exec(GeoNodeExecParams params) } GeometryComponent &component = geometry_set.get_component_for_write(type); - if (!component.attribute_try_delete(name)) { + if (!component.attributes_for_write()->remove(name)) { cannot_delete = true; } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc b/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc index 37533a7b99a..fc3cb7006bb 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_curve_handles.cc @@ -75,12 +75,12 @@ static void set_position_in_component(CurveComponent &component, const Field &offset_field) { GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; - const int domain_num = component.attribute_domain_num(ATTR_DOMAIN_POINT); - if (domain_num == 0) { + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT); + if (domain_size == 0) { return; } - fn::FieldEvaluator evaluator{field_context, domain_num}; + fn::FieldEvaluator evaluator{field_context, domain_size}; evaluator.set_selection(selection_field); evaluator.add(position_field); evaluator.add(offset_field); @@ -146,8 +146,8 @@ static void node_geo_exec(GeoNodeExecParams params) } has_curves = true; const CurveComponent &component = *geometry_set.get_component_for_read(); - if (!component.attribute_exists("handle_left") || - !component.attribute_exists("handle_right")) { + const AttributeAccessor attributes = *component.attributes(); + if (!attributes.contains("handle_left") || !attributes.contains("handle_right")) { return; } has_bezier = true; diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_curve_radius.cc b/source/blender/nodes/geometry/nodes/node_geo_set_curve_radius.cc index 4c84093bfcb..90411baac3e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_curve_radius.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_curve_radius.cc @@ -20,21 +20,22 @@ static void set_radius_in_component(GeometryComponent &component, const Field &selection_field, const Field &radius_field) { - GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; - const int domain_num = component.attribute_domain_num(ATTR_DOMAIN_POINT); - if (domain_num == 0) { + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT); + if (domain_size == 0) { return; } + MutableAttributeAccessor attributes = *component.attributes_for_write(); + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; - OutputAttribute_Typed radii = component.attribute_try_get_for_output_only( - "radius", ATTR_DOMAIN_POINT); + AttributeWriter radii = attributes.lookup_or_add_for_write("radius", + ATTR_DOMAIN_POINT); - fn::FieldEvaluator evaluator{field_context, domain_num}; + fn::FieldEvaluator evaluator{field_context, domain_size}; evaluator.set_selection(selection_field); - evaluator.add_with_destination(radius_field, radii.varray()); + evaluator.add_with_destination(radius_field, radii.varray); evaluator.evaluate(); - radii.save(); + radii.finish(); } static void node_geo_exec(GeoNodeExecParams params) diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_curve_tilt.cc b/source/blender/nodes/geometry/nodes/node_geo_set_curve_tilt.cc index 8b1e5935a61..2211ac62727 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_curve_tilt.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_curve_tilt.cc @@ -16,21 +16,23 @@ static void set_tilt_in_component(GeometryComponent &component, const Field &selection_field, const Field &tilt_field) { - GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; - const int domain_num = component.attribute_domain_num(ATTR_DOMAIN_POINT); - if (domain_num == 0) { + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT); + if (domain_size == 0) { return; } + MutableAttributeAccessor attributes = *component.attributes_for_write(); + + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; - OutputAttribute_Typed tilts = component.attribute_try_get_for_output_only( - "tilt", ATTR_DOMAIN_POINT); + AttributeWriter tilts = attributes.lookup_or_add_for_write("tilt", + ATTR_DOMAIN_POINT); - fn::FieldEvaluator evaluator{field_context, domain_num}; + fn::FieldEvaluator evaluator{field_context, domain_size}; evaluator.set_selection(selection_field); - evaluator.add_with_destination(tilt_field, tilts.varray()); + evaluator.add_with_destination(tilt_field, tilts.varray); evaluator.evaluate(); - tilts.save(); + tilts.finish(); } static void node_geo_exec(GeoNodeExecParams params) diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_id.cc b/source/blender/nodes/geometry/nodes/node_geo_set_id.cc index a7f17c02ce8..fbb2ecbb799 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_id.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_id.cc @@ -19,34 +19,34 @@ static void set_id_in_component(GeometryComponent &component, const eAttrDomain domain = (component.type() == GEO_COMPONENT_TYPE_INSTANCES) ? ATTR_DOMAIN_INSTANCE : ATTR_DOMAIN_POINT; - GeometryComponentFieldContext field_context{component, domain}; - const int domain_num = component.attribute_domain_num(domain); - if (domain_num == 0) { + const int domain_size = component.attribute_domain_size(domain); + if (domain_size == 0) { return; } + MutableAttributeAccessor attributes = *component.attributes_for_write(); + GeometryComponentFieldContext field_context{component, domain}; - fn::FieldEvaluator evaluator{field_context, domain_num}; + fn::FieldEvaluator evaluator{field_context, domain_size}; evaluator.set_selection(selection_field); /* Since adding the ID attribute can change the result of the field evaluation (the random value * node uses the index if the ID is unavailable), make sure that it isn't added before evaluating * the field. However, as an optimization, use a faster code path when it already exists. */ - if (component.attribute_exists("id")) { - OutputAttribute_Typed id_attribute = component.attribute_try_get_for_output_only( - "id", domain); - evaluator.add_with_destination(id_field, id_attribute.varray()); + if (attributes.contains("id")) { + AttributeWriter id_attribute = attributes.lookup_or_add_for_write("id", domain); + evaluator.add_with_destination(id_field, id_attribute.varray); evaluator.evaluate(); - id_attribute.save(); + id_attribute.finish(); } else { evaluator.add(id_field); evaluator.evaluate(); const IndexMask selection = evaluator.get_evaluated_selection_as_mask(); const VArray result_ids = evaluator.get_evaluated(0); - OutputAttribute_Typed id_attribute = component.attribute_try_get_for_output_only( - "id", domain); - result_ids.materialize(selection, id_attribute.as_span()); - id_attribute.save(); + SpanAttributeWriter id_attribute = attributes.lookup_or_add_for_write_span("id", + domain); + result_ids.materialize(selection, id_attribute.span); + id_attribute.finish(); } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_material_index.cc b/source/blender/nodes/geometry/nodes/node_geo_set_material_index.cc index 58613dae832..0dc89bb7ef4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_material_index.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_material_index.cc @@ -16,20 +16,21 @@ static void set_material_index_in_component(GeometryComponent &component, const Field &selection_field, const Field &index_field) { - GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_FACE}; - const int domain_num = component.attribute_domain_num(ATTR_DOMAIN_FACE); - if (domain_num == 0) { + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_FACE); + if (domain_size == 0) { return; } + MutableAttributeAccessor attributes = *component.attributes_for_write(); + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_FACE}; - OutputAttribute_Typed indices = component.attribute_try_get_for_output_only( - "material_index", ATTR_DOMAIN_FACE); + AttributeWriter indices = attributes.lookup_or_add_for_write("material_index", + ATTR_DOMAIN_FACE); - fn::FieldEvaluator evaluator{field_context, domain_num}; + fn::FieldEvaluator evaluator{field_context, domain_size}; evaluator.set_selection(selection_field); - evaluator.add_with_destination(index_field, indices.varray()); + evaluator.add_with_destination(index_field, indices.varray); evaluator.evaluate(); - indices.save(); + indices.finish(); } static void node_geo_exec(GeoNodeExecParams params) diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_point_radius.cc b/source/blender/nodes/geometry/nodes/node_geo_set_point_radius.cc index 571bead9743..da7977a4fb4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_point_radius.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_point_radius.cc @@ -20,21 +20,22 @@ static void set_radius_in_component(GeometryComponent &component, const Field &selection_field, const Field &radius_field) { - GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; - const int domain_num = component.attribute_domain_num(ATTR_DOMAIN_POINT); - if (domain_num == 0) { + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT); + if (domain_size == 0) { return; } + MutableAttributeAccessor attributes = *component.attributes_for_write(); + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; - OutputAttribute_Typed radii = component.attribute_try_get_for_output_only( - "radius", ATTR_DOMAIN_POINT); + AttributeWriter radii = attributes.lookup_or_add_for_write("radius", + ATTR_DOMAIN_POINT); - fn::FieldEvaluator evaluator{field_context, domain_num}; + fn::FieldEvaluator evaluator{field_context, domain_size}; evaluator.set_selection(selection_field); - evaluator.add_with_destination(radius_field, radii.varray()); + evaluator.add_with_destination(radius_field, radii.varray); evaluator.evaluate(); - radii.save(); + radii.finish(); } static void node_geo_exec(GeoNodeExecParams params) diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_position.cc b/source/blender/nodes/geometry/nodes/node_geo_set_position.cc index 1935409b3e5..880252de4fa 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_position.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_position.cc @@ -25,12 +25,10 @@ static void node_declare(NodeDeclarationBuilder &b) static void set_computed_position_and_offset(GeometryComponent &component, const VArray &in_positions, const VArray &in_offsets, - const eAttrDomain domain, const IndexMask selection) { - - OutputAttribute_Typed positions = component.attribute_try_get_for_output( - "position", domain, {0, 0, 0}); + MutableAttributeAccessor attributes = *component.attributes_for_write(); + AttributeWriter positions = attributes.lookup_for_write("position"); const int grain_size = 10000; @@ -38,7 +36,7 @@ static void set_computed_position_and_offset(GeometryComponent &component, case GEO_COMPONENT_TYPE_MESH: { Mesh *mesh = static_cast(component).get_for_write(); MutableSpan mverts{mesh->mvert, mesh->totvert}; - if (in_positions.is_same(positions.varray())) { + if (in_positions.is_same(positions.varray)) { devirtualize_varray(in_offsets, [&](const auto in_offsets) { threading::parallel_for( selection.index_range(), grain_size, [&](const IndexRange range) { @@ -67,18 +65,13 @@ static void set_computed_position_and_offset(GeometryComponent &component, CurveComponent &curve_component = static_cast(component); Curves &curves_id = *curve_component.get_for_write(); bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry); - if (component.attribute_exists("handle_right") && - component.attribute_exists("handle_left")) { - OutputAttribute_Typed handle_right_attribute = - component.attribute_try_get_for_output( - "handle_right", ATTR_DOMAIN_POINT, {0, 0, 0}); - OutputAttribute_Typed handle_left_attribute = - component.attribute_try_get_for_output( - "handle_left", ATTR_DOMAIN_POINT, {0, 0, 0}); - MutableSpan handle_right = handle_right_attribute.as_span(); - MutableSpan handle_left = handle_left_attribute.as_span(); - - MutableSpan out_positions_span = positions.as_span(); + if (attributes.contains("handle_right") && attributes.contains("handle_left")) { + SpanAttributeWriter handle_right_attribute = + attributes.lookup_or_add_for_write_span("handle_right", ATTR_DOMAIN_POINT); + SpanAttributeWriter handle_left_attribute = + attributes.lookup_or_add_for_write_span("handle_left", ATTR_DOMAIN_POINT); + + MutableVArraySpan out_positions_span = positions.varray; devirtualize_varray2( in_positions, in_offsets, [&](const auto in_positions, const auto in_offsets) { threading::parallel_for( @@ -86,15 +79,16 @@ static void set_computed_position_and_offset(GeometryComponent &component, for (const int i : selection.slice(range)) { const float3 new_position = in_positions[i] + in_offsets[i]; const float3 delta = new_position - out_positions_span[i]; - handle_right[i] += delta; - handle_left[i] += delta; + handle_right_attribute.span[i] += delta; + handle_left_attribute.span[i] += delta; out_positions_span[i] = new_position; } }); }); - handle_right_attribute.save(); - handle_left_attribute.save(); + out_positions_span.save(); + handle_right_attribute.finish(); + handle_left_attribute.finish(); /* Automatic Bezier handles must be recalculated based on the new positions. */ curves.calculate_bezier_auto_handles(); @@ -105,8 +99,8 @@ static void set_computed_position_and_offset(GeometryComponent &component, } } default: { - MutableSpan out_positions_span = positions.as_span(); - if (in_positions.is_same(positions.varray())) { + MutableVArraySpan out_positions_span = positions.varray; + if (in_positions.is_same(positions.varray)) { devirtualize_varray(in_offsets, [&](const auto in_offsets) { threading::parallel_for( selection.index_range(), grain_size, [&](const IndexRange range) { @@ -127,11 +121,12 @@ static void set_computed_position_and_offset(GeometryComponent &component, }); }); } + out_positions_span.save(); break; } } - positions.save(); + positions.finish(); } static void set_position_in_component(GeometryComponent &component, @@ -142,21 +137,22 @@ static void set_position_in_component(GeometryComponent &component, eAttrDomain domain = component.type() == GEO_COMPONENT_TYPE_INSTANCES ? ATTR_DOMAIN_INSTANCE : ATTR_DOMAIN_POINT; GeometryComponentFieldContext field_context{component, domain}; - const int domain_num = component.attribute_domain_num(domain); - if (domain_num == 0) { + const int domain_size = component.attribute_domain_size(domain); + if (domain_size == 0) { return; } - fn::FieldEvaluator evaluator{field_context, domain_num}; + fn::FieldEvaluator evaluator{field_context, domain_size}; evaluator.set_selection(selection_field); evaluator.add(position_field); evaluator.add(offset_field); evaluator.evaluate(); const IndexMask selection = evaluator.get_evaluated_selection_as_mask(); + const VArray positions_input = evaluator.get_evaluated(0); const VArray offsets_input = evaluator.get_evaluated(1); - set_computed_position_and_offset(component, positions_input, offsets_input, domain, selection); + set_computed_position_and_offset(component, positions_input, offsets_input, selection); } static void node_geo_exec(GeoNodeExecParams params) diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_shade_smooth.cc b/source/blender/nodes/geometry/nodes/node_geo_set_shade_smooth.cc index b98fbd0a0fe..e0cf0f98d58 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_shade_smooth.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_shade_smooth.cc @@ -16,21 +16,23 @@ static void set_smooth_in_component(GeometryComponent &component, const Field &selection_field, const Field &shade_field) { - GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_FACE}; - const int domain_num = component.attribute_domain_num(ATTR_DOMAIN_FACE); - if (domain_num == 0) { + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_FACE); + if (domain_size == 0) { return; } + MutableAttributeAccessor attributes = *component.attributes_for_write(); + + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_FACE}; - OutputAttribute_Typed shades = component.attribute_try_get_for_output_only( - "shade_smooth", ATTR_DOMAIN_FACE); + AttributeWriter shades = attributes.lookup_or_add_for_write("shade_smooth", + ATTR_DOMAIN_FACE); - fn::FieldEvaluator evaluator{field_context, domain_num}; + fn::FieldEvaluator evaluator{field_context, domain_size}; evaluator.set_selection(selection_field); - evaluator.add_with_destination(shade_field, shades.varray()); + evaluator.add_with_destination(shade_field, shades.varray); evaluator.evaluate(); - shades.save(); + shades.finish(); } static void node_geo_exec(GeoNodeExecParams params) diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_spline_cyclic.cc b/source/blender/nodes/geometry/nodes/node_geo_set_spline_cyclic.cc index 976857883f0..a35d8d66558 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_spline_cyclic.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_spline_cyclic.cc @@ -16,21 +16,23 @@ static void set_cyclic_in_component(GeometryComponent &component, const Field &selection_field, const Field &cyclic_field) { - GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_CURVE}; - const int domain_num = component.attribute_domain_num(ATTR_DOMAIN_CURVE); - if (domain_num == 0) { + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_CURVE); + if (domain_size == 0) { return; } + MutableAttributeAccessor attributes = *component.attributes_for_write(); + + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_CURVE}; - OutputAttribute_Typed cyclics = component.attribute_try_get_for_output_only( - "cyclic", ATTR_DOMAIN_CURVE); + AttributeWriter cyclics = attributes.lookup_or_add_for_write("cyclic", + ATTR_DOMAIN_CURVE); - fn::FieldEvaluator evaluator{field_context, domain_num}; + fn::FieldEvaluator evaluator{field_context, domain_size}; evaluator.set_selection(selection_field); - evaluator.add_with_destination(cyclic_field, cyclics.varray()); + evaluator.add_with_destination(cyclic_field, cyclics.varray); evaluator.evaluate(); - cyclics.save(); + cyclics.finish(); } static void node_geo_exec(GeoNodeExecParams params) diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc b/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc index 8b665376c01..fcebc1116d7 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc @@ -16,21 +16,23 @@ static void set_resolution_in_component(GeometryComponent &component, const Field &selection_field, const Field &resolution_field) { - GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_CURVE}; - const int domain_num = component.attribute_domain_num(ATTR_DOMAIN_CURVE); - if (domain_num == 0) { + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_CURVE); + if (domain_size == 0) { return; } + MutableAttributeAccessor attributes = *component.attributes_for_write(); + + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_CURVE}; - OutputAttribute_Typed resolutions = component.attribute_try_get_for_output_only( - "resolution", ATTR_DOMAIN_CURVE); + AttributeWriter resolutions = attributes.lookup_or_add_for_write("resolution", + ATTR_DOMAIN_CURVE); - fn::FieldEvaluator evaluator{field_context, domain_num}; + fn::FieldEvaluator evaluator{field_context, domain_size}; evaluator.set_selection(selection_field); - evaluator.add_with_destination(resolution_field, resolutions.varray()); + evaluator.add_with_destination(resolution_field, resolutions.varray); evaluator.evaluate(); - resolutions.save(); + resolutions.finish(); } static void node_geo_exec(GeoNodeExecParams params) diff --git a/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc index abac6d8d6b3..69a4fad10e2 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc @@ -87,9 +87,14 @@ static void try_capture_field_on_geometry(GeometryComponent &component, const eAttrDomain domain, const GField &field) { + const int domain_size = component.attribute_domain_size(domain); + if (domain_size == 0) { + return; + } + MutableAttributeAccessor attributes = *component.attributes_for_write(); + GeometryComponentFieldContext field_context{component, domain}; - const int domain_num = component.attribute_domain_num(domain); - const IndexMask mask{IndexMask(domain_num)}; + const IndexMask mask{IndexMask(domain_size)}; const CPPType &type = field.cpp_type(); const eCustomDataType data_type = bke::cpp_type_to_custom_data_type(type); @@ -97,28 +102,28 @@ static void try_capture_field_on_geometry(GeometryComponent &component, /* Could avoid allocating a new buffer if: * - We are writing to an attribute that exists already. * - The field does not depend on that attribute (we can't easily check for that yet). */ - void *buffer = MEM_mallocN(type.size() * domain_num, __func__); + void *buffer = MEM_mallocN(type.size() * domain_size, __func__); fn::FieldEvaluator evaluator{field_context, &mask}; - evaluator.add_with_destination(field, GMutableSpan{type, buffer, domain_num}); + evaluator.add_with_destination(field, GMutableSpan{type, buffer, domain_size}); evaluator.evaluate(); - component.attribute_try_delete(name); - if (component.attribute_exists(name)) { - WriteAttributeLookup write_attribute = component.attribute_try_get_for_write(name); + attributes.remove(name); + if (attributes.contains(name)) { + GAttributeWriter write_attribute = attributes.lookup_for_write(name); if (write_attribute && write_attribute.domain == domain && write_attribute.varray.type() == type) { write_attribute.varray.set_all(buffer); - write_attribute.tag_modified_fn(); + write_attribute.finish(); } else { /* Cannot change type of built-in attribute. */ } - type.destruct_n(buffer, domain_num); + type.destruct_n(buffer, domain_size); MEM_freeN(buffer); } else { - if (!component.attribute_try_create(name, domain, data_type, AttributeInitMove{buffer})) { + if (!attributes.add(name, domain, data_type, bke::AttributeInitMove{buffer})) { MEM_freeN(buffer); } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc b/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc index 94d5d7f946f..afd7db6604d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc @@ -335,13 +335,14 @@ static void create_attributes(GeoNodeExecParams ¶ms, const TextLayout &layout, InstancesComponent &instances) { + MutableAttributeAccessor attributes = *instances.attributes_for_write(); + if (params.output_is_required("Line")) { StrongAnonymousAttributeID line_id = StrongAnonymousAttributeID("Line"); - OutputAttribute_Typed line_attribute = instances.attribute_try_get_for_output_only( + SpanAttributeWriter line_attribute = attributes.lookup_or_add_for_write_only_span( line_id.get(), ATTR_DOMAIN_INSTANCE); - MutableSpan lines = line_attribute.as_span(); - lines.copy_from(layout.line_numbers); - line_attribute.save(); + line_attribute.span.copy_from(layout.line_numbers); + line_attribute.finish(); params.set_output("Line", AnonymousAttributeFieldInput::Create(std::move(line_id), params.attribute_producer_name())); @@ -349,15 +350,14 @@ static void create_attributes(GeoNodeExecParams ¶ms, if (params.output_is_required("Pivot Point")) { StrongAnonymousAttributeID pivot_id = StrongAnonymousAttributeID("Pivot"); - OutputAttribute_Typed pivot_attribute = - instances.attribute_try_get_for_output_only(pivot_id.get(), ATTR_DOMAIN_INSTANCE); - MutableSpan pivots = pivot_attribute.as_span(); + SpanAttributeWriter pivot_attribute = + attributes.lookup_or_add_for_write_only_span(pivot_id.get(), ATTR_DOMAIN_INSTANCE); for (const int i : layout.char_codes.index_range()) { - pivots[i] = layout.pivot_points.lookup(layout.char_codes[i]); + pivot_attribute.span[i] = layout.pivot_points.lookup(layout.char_codes[i]); } - pivot_attribute.save(); + pivot_attribute.finish(); params.set_output("Pivot Point", AnonymousAttributeFieldInput::Create( std::move(pivot_id), params.attribute_producer_name())); diff --git a/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc index 9eda5bb34ff..eda6a51d412 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc @@ -6,6 +6,7 @@ #include "DNA_meshdata_types.h" #include "DNA_modifier_types.h" +#include "BKE_attribute.hh" #include "BKE_mesh.h" #include "BKE_subdiv.h" #include "BKE_subdiv_mesh.h" @@ -79,10 +80,11 @@ static void write_vertex_creases(Mesh &mesh, const VArray &crease_varray) static void write_edge_creases(MeshComponent &mesh, const VArray &crease_varray) { - OutputAttribute_Typed attribute = mesh.attribute_try_get_for_output_only( - "crease", ATTR_DOMAIN_EDGE); - materialize_and_clamp_creases(crease_varray, attribute.as_span()); - attribute.save(); + bke::SpanAttributeWriter attribute = + mesh.attributes_for_write()->lookup_or_add_for_write_only_span("crease", + ATTR_DOMAIN_EDGE); + materialize_and_clamp_creases(crease_varray, attribute.span); + attribute.finish(); } static bool varray_is_nonzero(const VArray &varray) @@ -118,8 +120,8 @@ static void node_geo_exec(GeoNodeExecParams params) } const MeshComponent &mesh_component = *geometry_set.get_component_for_read(); - const int verts_num = mesh_component.attribute_domain_num(ATTR_DOMAIN_POINT); - const int edges_num = mesh_component.attribute_domain_num(ATTR_DOMAIN_EDGE); + const int verts_num = mesh_component.attribute_domain_size(ATTR_DOMAIN_POINT); + const int edges_num = mesh_component.attribute_domain_size(ATTR_DOMAIN_EDGE); if (verts_num == 0 || edges_num == 0) { return; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc index 0af6c76feaf..cd75822f665 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc @@ -365,7 +365,7 @@ static bool component_is_available(const GeometrySet &geometry, if (component.is_empty()) { return false; } - return component.attribute_domain_num(domain) != 0; + return component.attribute_domain_size(domain) != 0; } /** @@ -433,7 +433,7 @@ class NearestInterpolatedTransferFunction : public fn::MultiFunction { { const MeshComponent &mesh_component = *source_.get_component_for_read(); source_context_.emplace(GeometryComponentFieldContext{mesh_component, domain_}); - const int domain_num = mesh_component.attribute_domain_num(domain_); + const int domain_num = mesh_component.attribute_domain_size(domain_); source_evaluator_ = std::make_unique(*source_context_, domain_num); source_evaluator_->add(src_field_); source_evaluator_->evaluate(); @@ -578,7 +578,7 @@ class NearestTransferFunction : public fn::MultiFunction { { if (use_mesh_) { const MeshComponent &mesh = *source_.get_component_for_read(); - const int domain_num = mesh.attribute_domain_num(domain_); + const int domain_num = mesh.attribute_domain_size(domain_); mesh_context_.emplace(GeometryComponentFieldContext(mesh, domain_)); mesh_evaluator_ = std::make_unique(*mesh_context_, domain_num); mesh_evaluator_->add(src_field_); @@ -588,7 +588,7 @@ class NearestTransferFunction : public fn::MultiFunction { if (use_points_) { const PointCloudComponent &points = *source_.get_component_for_read(); - const int domain_num = points.attribute_domain_num(domain_); + const int domain_num = points.attribute_domain_size(domain_); point_context_.emplace(GeometryComponentFieldContext(points, domain_)); point_evaluator_ = std::make_unique(*point_context_, domain_num); point_evaluator_->add(src_field_); @@ -658,7 +658,7 @@ class IndexTransferFunction : public fn::MultiFunction { if (component == nullptr) { return; } - const int domain_num = component->attribute_domain_num(domain_); + const int domain_num = component->attribute_domain_size(domain_); geometry_context_.emplace(GeometryComponentFieldContext(*component, domain_)); evaluator_ = std::make_unique(*geometry_context_, domain_num); evaluator_->add(src_field_); diff --git a/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc b/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc index e47dc22da04..992470e8279 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc @@ -83,9 +83,9 @@ static void node_geo_exec(GeoNodeExecParams params) GeometryComponent &component = geometry_set.get_component_for_write(); const Mesh &mesh_in = *geometry_set.get_mesh_for_read(); - const int domain_num = component.attribute_domain_num(ATTR_DOMAIN_FACE); + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_FACE); GeometryComponentFieldContext context{component, ATTR_DOMAIN_FACE}; - FieldEvaluator evaluator{context, domain_num}; + FieldEvaluator evaluator{context, domain_size}; evaluator.add(selection_field); evaluator.evaluate(); const IndexMask selection = evaluator.get_evaluated_as_mask(0); diff --git a/source/blender/nodes/geometry/nodes/node_geo_uv_pack_islands.cc b/source/blender/nodes/geometry/nodes/node_geo_uv_pack_islands.cc index feff6efc3f8..17413e64f7d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_uv_pack_islands.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_uv_pack_islands.cc @@ -40,7 +40,7 @@ static VArray construct_uv_gvarray(const MeshComponent &component, return {}; } - const int face_num = component.attribute_domain_num(ATTR_DOMAIN_FACE); + const int face_num = component.attribute_domain_size(ATTR_DOMAIN_FACE); GeometryComponentFieldContext face_context{component, ATTR_DOMAIN_FACE}; FieldEvaluator face_evaluator{face_context, face_num}; face_evaluator.add(selection_field); @@ -50,7 +50,7 @@ static VArray construct_uv_gvarray(const MeshComponent &component, return {}; } - const int corner_num = component.attribute_domain_num(ATTR_DOMAIN_CORNER); + const int corner_num = component.attribute_domain_size(ATTR_DOMAIN_CORNER); GeometryComponentFieldContext corner_context{component, ATTR_DOMAIN_CORNER}; FieldEvaluator evaluator{corner_context, corner_num}; Array uv(corner_num); @@ -88,7 +88,7 @@ static VArray construct_uv_gvarray(const MeshComponent &component, GEO_uv_parametrizer_flush(handle); GEO_uv_parametrizer_delete(handle); - return component.attribute_try_adapt_domain( + return component.attributes()->adapt_domain( VArray::ForContainer(std::move(uv)), ATTR_DOMAIN_CORNER, domain); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_uv_unwrap.cc b/source/blender/nodes/geometry/nodes/node_geo_uv_unwrap.cc index 364106455b6..2ec14ad2d29 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_uv_unwrap.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_uv_unwrap.cc @@ -65,7 +65,7 @@ static VArray construct_uv_gvarray(const MeshComponent &component, return {}; } - const int face_num = component.attribute_domain_num(ATTR_DOMAIN_FACE); + const int face_num = component.attribute_domain_size(ATTR_DOMAIN_FACE); GeometryComponentFieldContext face_context{component, ATTR_DOMAIN_FACE}; FieldEvaluator face_evaluator{face_context, face_num}; face_evaluator.add(selection_field); @@ -75,7 +75,7 @@ static VArray construct_uv_gvarray(const MeshComponent &component, return {}; } - const int edge_num = component.attribute_domain_num(ATTR_DOMAIN_EDGE); + const int edge_num = component.attribute_domain_size(ATTR_DOMAIN_EDGE); GeometryComponentFieldContext edge_context{component, ATTR_DOMAIN_EDGE}; FieldEvaluator edge_evaluator{edge_context, edge_num}; edge_evaluator.add(seam_field); @@ -126,7 +126,7 @@ static VArray construct_uv_gvarray(const MeshComponent &component, GEO_uv_parametrizer_flush(handle); GEO_uv_parametrizer_delete(handle); - return component.attribute_try_adapt_domain( + return component.attributes()->adapt_domain( VArray::ForContainer(std::move(uv)), ATTR_DOMAIN_CORNER, domain); } diff --git a/source/blender/nodes/intern/geometry_nodes_eval_log.cc b/source/blender/nodes/intern/geometry_nodes_eval_log.cc index 85dfdf03b82..cf7cbbdc4bf 100644 --- a/source/blender/nodes/intern/geometry_nodes_eval_log.cc +++ b/source/blender/nodes/intern/geometry_nodes_eval_log.cc @@ -228,7 +228,7 @@ GeometryValueLog::GeometryValueLog(const GeometrySet &geometry_set, bool log_ful all_component_types, true, [&](const bke::AttributeIDRef &attribute_id, - const AttributeMetaData &meta_data, + const bke::AttributeMetaData &meta_data, const GeometryComponent &UNUSED(component)) { if (attribute_id.is_named() && names.add(attribute_id.name())) { this->attributes_.append({attribute_id.name(), meta_data.domain, meta_data.data_type}); @@ -241,21 +241,21 @@ GeometryValueLog::GeometryValueLog(const GeometrySet &geometry_set, bool log_ful case GEO_COMPONENT_TYPE_MESH: { const MeshComponent &mesh_component = *(const MeshComponent *)component; MeshInfo &info = this->mesh_info.emplace(); - info.verts_num = mesh_component.attribute_domain_num(ATTR_DOMAIN_POINT); - info.edges_num = mesh_component.attribute_domain_num(ATTR_DOMAIN_EDGE); - info.faces_num = mesh_component.attribute_domain_num(ATTR_DOMAIN_FACE); + info.verts_num = mesh_component.attribute_domain_size(ATTR_DOMAIN_POINT); + info.edges_num = mesh_component.attribute_domain_size(ATTR_DOMAIN_EDGE); + info.faces_num = mesh_component.attribute_domain_size(ATTR_DOMAIN_FACE); break; } case GEO_COMPONENT_TYPE_CURVE: { const CurveComponent &curve_component = *(const CurveComponent *)component; CurveInfo &info = this->curve_info.emplace(); - info.splines_num = curve_component.attribute_domain_num(ATTR_DOMAIN_CURVE); + info.splines_num = curve_component.attribute_domain_size(ATTR_DOMAIN_CURVE); break; } case GEO_COMPONENT_TYPE_POINT_CLOUD: { const PointCloudComponent &pointcloud_component = *(const PointCloudComponent *)component; PointCloudInfo &info = this->pointcloud_info.emplace(); - info.points_num = pointcloud_component.attribute_domain_num(ATTR_DOMAIN_POINT); + info.points_num = pointcloud_component.attribute_domain_size(ATTR_DOMAIN_POINT); break; } case GEO_COMPONENT_TYPE_INSTANCES: { -- cgit v1.2.3 From f639b59a298d42410f4760d125b3e2b524199373 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 22 Jun 2022 20:16:36 +0200 Subject: Cleanup: convert brush.c to C++ In preparation of refactoring for texture nodes. --- source/blender/blenkernel/CMakeLists.txt | 2 +- source/blender/blenkernel/intern/brush.c | 2525 ----------------------------- source/blender/blenkernel/intern/brush.cc | 2521 ++++++++++++++++++++++++++++ 3 files changed, 2522 insertions(+), 2526 deletions(-) delete mode 100644 source/blender/blenkernel/intern/brush.c create mode 100644 source/blender/blenkernel/intern/brush.cc diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index d0f9a67f167..044a306a1b9 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -88,7 +88,7 @@ set(SRC intern/blendfile_link_append.c intern/boids.c intern/bpath.c - intern/brush.c + intern/brush.cc intern/bvhutils.cc intern/cachefile.c intern/callbacks.c diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c deleted file mode 100644 index dd38af126fe..00000000000 --- a/source/blender/blenkernel/intern/brush.c +++ /dev/null @@ -1,2525 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup bke - */ - -#include "MEM_guardedalloc.h" - -#include "DNA_brush_types.h" -#include "DNA_defaults.h" -#include "DNA_gpencil_types.h" -#include "DNA_material_types.h" -#include "DNA_object_types.h" -#include "DNA_scene_types.h" - -#include "BLI_listbase.h" -#include "BLI_math.h" -#include "BLI_rand.h" - -#include "BLT_translation.h" - -#include "BKE_bpath.h" -#include "BKE_brush.h" -#include "BKE_colortools.h" -#include "BKE_context.h" -#include "BKE_gpencil.h" -#include "BKE_icons.h" -#include "BKE_idtype.h" -#include "BKE_lib_id.h" -#include "BKE_lib_query.h" -#include "BKE_lib_remap.h" -#include "BKE_main.h" -#include "BKE_material.h" -#include "BKE_paint.h" -#include "BKE_texture.h" - -#include "IMB_colormanagement.h" -#include "IMB_imbuf.h" -#include "IMB_imbuf_types.h" - -#include "RE_texture.h" /* RE_texture_evaluate */ - -#include "BLO_read_write.h" - -static void brush_init_data(ID *id) -{ - Brush *brush = (Brush *)id; - BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(brush, id)); - - MEMCPY_STRUCT_AFTER(brush, DNA_struct_default_get(Brush), id); - - /* enable fake user by default */ - id_fake_user_set(&brush->id); - - /* the default alpha falloff curve */ - BKE_brush_curve_preset(brush, CURVE_PRESET_SMOOTH); -} - -static void brush_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, const int flag) -{ - Brush *brush_dst = (Brush *)id_dst; - const Brush *brush_src = (const Brush *)id_src; - if (brush_src->icon_imbuf) { - brush_dst->icon_imbuf = IMB_dupImBuf(brush_src->icon_imbuf); - } - - if ((flag & LIB_ID_COPY_NO_PREVIEW) == 0) { - BKE_previewimg_id_copy(&brush_dst->id, &brush_src->id); - } - else { - brush_dst->preview = NULL; - } - - brush_dst->curve = BKE_curvemapping_copy(brush_src->curve); - if (brush_src->gpencil_settings != NULL) { - brush_dst->gpencil_settings = MEM_dupallocN(brush_src->gpencil_settings); - brush_dst->gpencil_settings->curve_sensitivity = BKE_curvemapping_copy( - brush_src->gpencil_settings->curve_sensitivity); - brush_dst->gpencil_settings->curve_strength = BKE_curvemapping_copy( - brush_src->gpencil_settings->curve_strength); - brush_dst->gpencil_settings->curve_jitter = BKE_curvemapping_copy( - brush_src->gpencil_settings->curve_jitter); - - brush_dst->gpencil_settings->curve_rand_pressure = BKE_curvemapping_copy( - brush_src->gpencil_settings->curve_rand_pressure); - brush_dst->gpencil_settings->curve_rand_strength = BKE_curvemapping_copy( - brush_src->gpencil_settings->curve_rand_strength); - brush_dst->gpencil_settings->curve_rand_uv = BKE_curvemapping_copy( - brush_src->gpencil_settings->curve_rand_uv); - brush_dst->gpencil_settings->curve_rand_hue = BKE_curvemapping_copy( - brush_src->gpencil_settings->curve_rand_hue); - brush_dst->gpencil_settings->curve_rand_saturation = BKE_curvemapping_copy( - brush_src->gpencil_settings->curve_rand_saturation); - brush_dst->gpencil_settings->curve_rand_value = BKE_curvemapping_copy( - brush_src->gpencil_settings->curve_rand_value); - } - if (brush_src->curves_sculpt_settings != NULL) { - brush_dst->curves_sculpt_settings = MEM_dupallocN(brush_src->curves_sculpt_settings); - } - - /* enable fake user by default */ - id_fake_user_set(&brush_dst->id); -} - -static void brush_free_data(ID *id) -{ - Brush *brush = (Brush *)id; - if (brush->icon_imbuf) { - IMB_freeImBuf(brush->icon_imbuf); - } - BKE_curvemapping_free(brush->curve); - - if (brush->gpencil_settings != NULL) { - BKE_curvemapping_free(brush->gpencil_settings->curve_sensitivity); - BKE_curvemapping_free(brush->gpencil_settings->curve_strength); - BKE_curvemapping_free(brush->gpencil_settings->curve_jitter); - - BKE_curvemapping_free(brush->gpencil_settings->curve_rand_pressure); - BKE_curvemapping_free(brush->gpencil_settings->curve_rand_strength); - BKE_curvemapping_free(brush->gpencil_settings->curve_rand_uv); - BKE_curvemapping_free(brush->gpencil_settings->curve_rand_hue); - BKE_curvemapping_free(brush->gpencil_settings->curve_rand_saturation); - BKE_curvemapping_free(brush->gpencil_settings->curve_rand_value); - - MEM_SAFE_FREE(brush->gpencil_settings); - } - if (brush->curves_sculpt_settings != NULL) { - MEM_freeN(brush->curves_sculpt_settings); - } - - MEM_SAFE_FREE(brush->gradient); - - BKE_previewimg_free(&(brush->preview)); -} - -static void brush_make_local(Main *bmain, ID *id, const int flags) -{ - if (!ID_IS_LINKED(id)) { - return; - } - - Brush *brush = (Brush *)id; - const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0; - - bool force_local, force_copy; - BKE_lib_id_make_local_generic_action_define(bmain, id, flags, &force_local, &force_copy); - - if (brush->clone.image) { - /* Special case: ima always local immediately. Clone image should only have one user anyway. */ - /* FIXME: Recursive calls affecting other non-embedded IDs are really bad and should be avoided - * in IDType callbacks. Higher-level ID management code usually does not expect such things and - * does not deal properly with it. */ - /* NOTE: assert below ensures that the comment above is valid, and that exception is - * acceptable for the time being. */ - BKE_lib_id_make_local(bmain, &brush->clone.image->id, 0); - BLI_assert(!ID_IS_LINKED(brush->clone.image) && brush->clone.image->id.newid == NULL); - } - - if (force_local) { - BKE_lib_id_clear_library_data(bmain, &brush->id, flags); - BKE_lib_id_expand_local(bmain, &brush->id, flags); - - /* enable fake user by default */ - id_fake_user_set(&brush->id); - } - else if (force_copy) { - Brush *brush_new = (Brush *)BKE_id_copy(bmain, &brush->id); /* Ensures FAKE_USER is set */ - - brush_new->id.us = 0; - - /* setting newid is mandatory for complex make_lib_local logic... */ - ID_NEW_SET(brush, brush_new); - - if (!lib_local) { - BKE_libblock_remap(bmain, brush, brush_new, ID_REMAP_SKIP_INDIRECT_USAGE); - } - } -} - -static void brush_foreach_id(ID *id, LibraryForeachIDData *data) -{ - Brush *brush = (Brush *)id; - - BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, brush->toggle_brush, IDWALK_CB_NOP); - BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, brush->clone.image, IDWALK_CB_NOP); - BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, brush->paint_curve, IDWALK_CB_USER); - if (brush->gpencil_settings) { - BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, brush->gpencil_settings->material, IDWALK_CB_USER); - } - BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data, BKE_texture_mtex_foreach_id(data, &brush->mtex)); - BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data, - BKE_texture_mtex_foreach_id(data, &brush->mask_mtex)); -} - -static void brush_foreach_path(ID *id, BPathForeachPathData *bpath_data) -{ - Brush *brush = (Brush *)id; - if (brush->icon_filepath[0] != '\0') { - BKE_bpath_foreach_path_fixed_process(bpath_data, brush->icon_filepath); - } -} - -static void brush_blend_write(BlendWriter *writer, ID *id, const void *id_address) -{ - Brush *brush = (Brush *)id; - - BLO_write_id_struct(writer, Brush, id_address, &brush->id); - BKE_id_blend_write(writer, &brush->id); - - if (brush->curve) { - BKE_curvemapping_blend_write(writer, brush->curve); - } - - if (brush->gpencil_settings) { - BLO_write_struct(writer, BrushGpencilSettings, brush->gpencil_settings); - - if (brush->gpencil_settings->curve_sensitivity) { - BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_sensitivity); - } - if (brush->gpencil_settings->curve_strength) { - BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_strength); - } - if (brush->gpencil_settings->curve_jitter) { - BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_jitter); - } - if (brush->gpencil_settings->curve_rand_pressure) { - BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_pressure); - } - if (brush->gpencil_settings->curve_rand_strength) { - BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_strength); - } - if (brush->gpencil_settings->curve_rand_uv) { - BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_uv); - } - if (brush->gpencil_settings->curve_rand_hue) { - BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_hue); - } - if (brush->gpencil_settings->curve_rand_saturation) { - BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_saturation); - } - if (brush->gpencil_settings->curve_rand_value) { - BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_value); - } - } - if (brush->curves_sculpt_settings) { - BLO_write_struct(writer, BrushCurvesSculptSettings, brush->curves_sculpt_settings); - } - if (brush->gradient) { - BLO_write_struct(writer, ColorBand, brush->gradient); - } -} - -static void brush_blend_read_data(BlendDataReader *reader, ID *id) -{ - Brush *brush = (Brush *)id; - - /* Falloff curve. */ - BLO_read_data_address(reader, &brush->curve); - - BLO_read_data_address(reader, &brush->gradient); - - if (brush->curve) { - BKE_curvemapping_blend_read(reader, brush->curve); - } - else { - BKE_brush_curve_preset(brush, CURVE_PRESET_SHARP); - } - - /* grease pencil */ - BLO_read_data_address(reader, &brush->gpencil_settings); - if (brush->gpencil_settings != NULL) { - BLO_read_data_address(reader, &brush->gpencil_settings->curve_sensitivity); - BLO_read_data_address(reader, &brush->gpencil_settings->curve_strength); - BLO_read_data_address(reader, &brush->gpencil_settings->curve_jitter); - - BLO_read_data_address(reader, &brush->gpencil_settings->curve_rand_pressure); - BLO_read_data_address(reader, &brush->gpencil_settings->curve_rand_strength); - BLO_read_data_address(reader, &brush->gpencil_settings->curve_rand_uv); - BLO_read_data_address(reader, &brush->gpencil_settings->curve_rand_hue); - BLO_read_data_address(reader, &brush->gpencil_settings->curve_rand_saturation); - BLO_read_data_address(reader, &brush->gpencil_settings->curve_rand_value); - - if (brush->gpencil_settings->curve_sensitivity) { - BKE_curvemapping_blend_read(reader, brush->gpencil_settings->curve_sensitivity); - } - - if (brush->gpencil_settings->curve_strength) { - BKE_curvemapping_blend_read(reader, brush->gpencil_settings->curve_strength); - } - - if (brush->gpencil_settings->curve_jitter) { - BKE_curvemapping_blend_read(reader, brush->gpencil_settings->curve_jitter); - } - - if (brush->gpencil_settings->curve_rand_pressure) { - BKE_curvemapping_blend_read(reader, brush->gpencil_settings->curve_rand_pressure); - } - - if (brush->gpencil_settings->curve_rand_strength) { - BKE_curvemapping_blend_read(reader, brush->gpencil_settings->curve_rand_strength); - } - - if (brush->gpencil_settings->curve_rand_uv) { - BKE_curvemapping_blend_read(reader, brush->gpencil_settings->curve_rand_uv); - } - - if (brush->gpencil_settings->curve_rand_hue) { - BKE_curvemapping_blend_read(reader, brush->gpencil_settings->curve_rand_hue); - } - - if (brush->gpencil_settings->curve_rand_saturation) { - BKE_curvemapping_blend_read(reader, brush->gpencil_settings->curve_rand_saturation); - } - - if (brush->gpencil_settings->curve_rand_value) { - BKE_curvemapping_blend_read(reader, brush->gpencil_settings->curve_rand_value); - } - } - - BLO_read_data_address(reader, &brush->curves_sculpt_settings); - - brush->preview = NULL; - brush->icon_imbuf = NULL; -} - -static void brush_blend_read_lib(BlendLibReader *reader, ID *id) -{ - Brush *brush = (Brush *)id; - - /* brush->(mask_)mtex.obj is ignored on purpose? */ - BLO_read_id_address(reader, brush->id.lib, &brush->mtex.tex); - BLO_read_id_address(reader, brush->id.lib, &brush->mask_mtex.tex); - BLO_read_id_address(reader, brush->id.lib, &brush->clone.image); - BLO_read_id_address(reader, brush->id.lib, &brush->toggle_brush); - BLO_read_id_address(reader, brush->id.lib, &brush->paint_curve); - - /* link default grease pencil palette */ - if (brush->gpencil_settings != NULL) { - if (brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED) { - BLO_read_id_address(reader, brush->id.lib, &brush->gpencil_settings->material); - - if (!brush->gpencil_settings->material) { - brush->gpencil_settings->flag &= ~GP_BRUSH_MATERIAL_PINNED; - } - } - else { - brush->gpencil_settings->material = NULL; - } - } -} - -static void brush_blend_read_expand(BlendExpander *expander, ID *id) -{ - Brush *brush = (Brush *)id; - BLO_expand(expander, brush->mtex.tex); - BLO_expand(expander, brush->mask_mtex.tex); - BLO_expand(expander, brush->clone.image); - BLO_expand(expander, brush->paint_curve); - if (brush->gpencil_settings != NULL) { - BLO_expand(expander, brush->gpencil_settings->material); - } -} - -static int brush_undo_preserve_cb(LibraryIDLinkCallbackData *cb_data) -{ - BlendLibReader *reader = cb_data->user_data; - ID *id_old = *cb_data->id_pointer; - /* Old data has not been remapped to new values of the pointers, if we want to keep the old - * pointer here we need its new address. */ - ID *id_old_new = id_old != NULL ? BLO_read_get_new_id_address(reader, id_old->lib, id_old) : - NULL; - BLI_assert(id_old_new == NULL || ELEM(id_old, id_old_new, id_old_new->orig_id)); - if (cb_data->cb_flag & IDWALK_CB_USER) { - id_us_plus_no_lib(id_old_new); - id_us_min(id_old); - } - *cb_data->id_pointer = id_old_new; - return IDWALK_RET_NOP; -} - -static void brush_undo_preserve(BlendLibReader *reader, ID *id_new, ID *id_old) -{ - /* Whole Brush is preserved across undo-steps. */ - BKE_lib_id_swap(NULL, id_new, id_old); - - /* `id_new` now has content from `id_old`, we need to ensure those old ID pointers are valid. - * NOTE: Since we want to re-use all old pointers here, code is much simpler than for Scene. */ - BKE_library_foreach_ID_link(NULL, id_new, brush_undo_preserve_cb, reader, IDWALK_NOP); - - /* NOTE: We do not swap IDProperties, as dealing with potential ID pointers in those would be - * fairly delicate. */ - SWAP(IDProperty *, id_new->properties, id_old->properties); -} - -IDTypeInfo IDType_ID_BR = { - .id_code = ID_BR, - .id_filter = FILTER_ID_BR, - .main_listbase_index = INDEX_ID_BR, - .struct_size = sizeof(Brush), - .name = "Brush", - .name_plural = "brushes", - .translation_context = BLT_I18NCONTEXT_ID_BRUSH, - .flags = IDTYPE_FLAGS_NO_ANIMDATA, - .asset_type_info = NULL, - - .init_data = brush_init_data, - .copy_data = brush_copy_data, - .free_data = brush_free_data, - .make_local = brush_make_local, - .foreach_id = brush_foreach_id, - .foreach_cache = NULL, - .foreach_path = brush_foreach_path, - .owner_get = NULL, - - .blend_write = brush_blend_write, - .blend_read_data = brush_blend_read_data, - .blend_read_lib = brush_blend_read_lib, - .blend_read_expand = brush_blend_read_expand, - - .blend_read_undo_preserve = brush_undo_preserve, - - .lib_override_apply_post = NULL, -}; - -static RNG *brush_rng; - -void BKE_brush_system_init(void) -{ - brush_rng = BLI_rng_new(0); - BLI_rng_srandom(brush_rng, 31415682); -} - -void BKE_brush_system_exit(void) -{ - if (brush_rng == NULL) { - return; - } - BLI_rng_free(brush_rng); - brush_rng = NULL; -} - -static void brush_defaults(Brush *brush) -{ - - const Brush *brush_def = DNA_struct_default_get(Brush); - -#define FROM_DEFAULT(member) memcpy(&brush->member, &brush_def->member, sizeof(brush->member)) -#define FROM_DEFAULT_PTR(member) memcpy(brush->member, brush_def->member, sizeof(brush->member)) - - FROM_DEFAULT(blend); - FROM_DEFAULT(flag); - FROM_DEFAULT(weight); - FROM_DEFAULT(size); - FROM_DEFAULT(alpha); - FROM_DEFAULT(hardness); - FROM_DEFAULT(autosmooth_factor); - FROM_DEFAULT(topology_rake_factor); - FROM_DEFAULT(crease_pinch_factor); - FROM_DEFAULT(normal_radius_factor); - FROM_DEFAULT(wet_paint_radius_factor); - FROM_DEFAULT(area_radius_factor); - FROM_DEFAULT(disconnected_distance_max); - FROM_DEFAULT(sculpt_plane); - FROM_DEFAULT(plane_offset); - FROM_DEFAULT(clone.alpha); - FROM_DEFAULT(normal_weight); - FROM_DEFAULT(fill_threshold); - FROM_DEFAULT(flag); - FROM_DEFAULT(sampling_flag); - FROM_DEFAULT_PTR(rgb); - FROM_DEFAULT_PTR(secondary_rgb); - FROM_DEFAULT(spacing); - FROM_DEFAULT(smooth_stroke_radius); - FROM_DEFAULT(smooth_stroke_factor); - FROM_DEFAULT(rate); - FROM_DEFAULT(jitter); - FROM_DEFAULT(texture_sample_bias); - FROM_DEFAULT(texture_overlay_alpha); - FROM_DEFAULT(mask_overlay_alpha); - FROM_DEFAULT(cursor_overlay_alpha); - FROM_DEFAULT(overlay_flags); - FROM_DEFAULT_PTR(add_col); - FROM_DEFAULT_PTR(sub_col); - FROM_DEFAULT(stencil_pos); - FROM_DEFAULT(stencil_dimension); - FROM_DEFAULT(mtex); - FROM_DEFAULT(mask_mtex); - -#undef FROM_DEFAULT -#undef FROM_DEFAULT_PTR -} - -/* Datablock add/copy/free/make_local */ - -Brush *BKE_brush_add(Main *bmain, const char *name, const eObjectMode ob_mode) -{ - Brush *brush; - - brush = BKE_id_new(bmain, ID_BR, name); - - brush->ob_mode = ob_mode; - - if (ob_mode == OB_MODE_SCULPT_CURVES) { - BKE_brush_init_curves_sculpt_settings(brush); - } - - return brush; -} - -void BKE_brush_init_gpencil_settings(Brush *brush) -{ - if (brush->gpencil_settings == NULL) { - brush->gpencil_settings = MEM_callocN(sizeof(BrushGpencilSettings), "BrushGpencilSettings"); - } - - brush->gpencil_settings->draw_smoothlvl = 1; - brush->gpencil_settings->flag = 0; - brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; - brush->gpencil_settings->draw_strength = 1.0f; - brush->gpencil_settings->draw_jitter = 0.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_JITTER_PRESSURE; - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_PEN; - - /* curves */ - brush->gpencil_settings->curve_sensitivity = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); - brush->gpencil_settings->curve_strength = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); - brush->gpencil_settings->curve_jitter = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); - - brush->gpencil_settings->curve_rand_pressure = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); - brush->gpencil_settings->curve_rand_strength = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); - brush->gpencil_settings->curve_rand_uv = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); - brush->gpencil_settings->curve_rand_hue = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); - brush->gpencil_settings->curve_rand_saturation = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); - brush->gpencil_settings->curve_rand_value = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); -} - -Brush *BKE_brush_add_gpencil(Main *bmain, ToolSettings *ts, const char *name, eObjectMode mode) -{ - Paint *paint = NULL; - Brush *brush; - switch (mode) { - case OB_MODE_PAINT_GPENCIL: { - paint = &ts->gp_paint->paint; - break; - } - case OB_MODE_SCULPT_GPENCIL: { - paint = &ts->gp_sculptpaint->paint; - break; - } - case OB_MODE_WEIGHT_GPENCIL: { - paint = &ts->gp_weightpaint->paint; - break; - } - case OB_MODE_VERTEX_GPENCIL: { - paint = &ts->gp_vertexpaint->paint; - break; - } - default: - paint = &ts->gp_paint->paint; - } - - brush = BKE_brush_add(bmain, name, mode); - - BKE_paint_brush_set(paint, brush); - id_us_min(&brush->id); - - brush->size = 3; - - /* grease pencil basic settings */ - BKE_brush_init_gpencil_settings(brush); - - /* return brush */ - return brush; -} - -bool BKE_brush_delete(Main *bmain, Brush *brush) -{ - if (brush->id.tag & LIB_TAG_INDIRECT) { - return false; - } - if (ID_REAL_USERS(brush) <= 1 && ID_EXTRA_USERS(brush) == 0 && - BKE_library_ID_is_indirectly_used(bmain, brush)) { - return false; - } - - BKE_id_delete(bmain, brush); - - return true; -} - -/* grease pencil cumapping->preset */ -typedef enum eGPCurveMappingPreset { - GPCURVE_PRESET_PENCIL = 0, - GPCURVE_PRESET_INK = 1, - GPCURVE_PRESET_INKNOISE = 2, - GPCURVE_PRESET_MARKER = 3, - GPCURVE_PRESET_CHISEL_SENSIVITY = 4, - GPCURVE_PRESET_CHISEL_STRENGTH = 5, -} eGPCurveMappingPreset; - -static void brush_gpencil_curvemap_reset(CurveMap *cuma, int tot, int preset) -{ - if (cuma->curve) { - MEM_freeN(cuma->curve); - } - - cuma->totpoint = tot; - cuma->curve = MEM_callocN(cuma->totpoint * sizeof(CurveMapPoint), __func__); - - switch (preset) { - case GPCURVE_PRESET_PENCIL: - cuma->curve[0].x = 0.0f; - cuma->curve[0].y = 0.0f; - cuma->curve[1].x = 0.75115f; - cuma->curve[1].y = 0.25f; - cuma->curve[2].x = 1.0f; - cuma->curve[2].y = 1.0f; - break; - case GPCURVE_PRESET_INK: - cuma->curve[0].x = 0.0f; - cuma->curve[0].y = 0.0f; - cuma->curve[1].x = 0.63448f; - cuma->curve[1].y = 0.375f; - cuma->curve[2].x = 1.0f; - cuma->curve[2].y = 1.0f; - break; - case GPCURVE_PRESET_INKNOISE: - cuma->curve[0].x = 0.0f; - cuma->curve[0].y = 0.0f; - cuma->curve[1].x = 0.55f; - cuma->curve[1].y = 0.45f; - cuma->curve[2].x = 0.85f; - cuma->curve[2].y = 1.0f; - break; - case GPCURVE_PRESET_MARKER: - cuma->curve[0].x = 0.0f; - cuma->curve[0].y = 0.0f; - cuma->curve[1].x = 0.38f; - cuma->curve[1].y = 0.22f; - cuma->curve[2].x = 0.65f; - cuma->curve[2].y = 0.68f; - cuma->curve[3].x = 1.0f; - cuma->curve[3].y = 1.0f; - break; - case GPCURVE_PRESET_CHISEL_SENSIVITY: - cuma->curve[0].x = 0.0f; - cuma->curve[0].y = 0.0f; - cuma->curve[1].x = 0.25f; - cuma->curve[1].y = 0.40f; - cuma->curve[2].x = 1.0f; - cuma->curve[2].y = 1.0f; - break; - case GPCURVE_PRESET_CHISEL_STRENGTH: - cuma->curve[0].x = 0.0f; - cuma->curve[0].y = 0.0f; - cuma->curve[1].x = 0.31f; - cuma->curve[1].y = 0.22f; - cuma->curve[2].x = 0.61f; - cuma->curve[2].y = 0.88f; - cuma->curve[3].x = 1.0f; - cuma->curve[3].y = 1.0f; - break; - default: - break; - } - - MEM_SAFE_FREE(cuma->table); -} - -void BKE_gpencil_brush_preset_set(Main *bmain, Brush *brush, const short type) -{ -#define SMOOTH_STROKE_RADIUS 40 -#define SMOOTH_STROKE_FACTOR 0.9f -#define ACTIVE_SMOOTH 0.35f - - CurveMapping *custom_curve = NULL; - - /* Optionally assign a material preset. */ - enum { - PRESET_MATERIAL_NONE = 0, - PRESET_MATERIAL_DOT_STROKE, - } material_preset = PRESET_MATERIAL_NONE; - - /* Set general defaults at brush level. */ - brush->smooth_stroke_radius = SMOOTH_STROKE_RADIUS; - brush->smooth_stroke_factor = SMOOTH_STROKE_FACTOR; - - brush->rgb[0] = 0.498f; - brush->rgb[1] = 1.0f; - brush->rgb[2] = 0.498f; - - brush->secondary_rgb[0] = 1.0f; - brush->secondary_rgb[1] = 1.0f; - brush->secondary_rgb[2] = 1.0f; - - brush->curve_preset = BRUSH_CURVE_SMOOTH; - - if (brush->gpencil_settings == NULL) { - return; - } - - /* Set preset type. */ - brush->gpencil_settings->preset_type = type; - - /* Set vertex mix factor. */ - brush->gpencil_settings->vertex_mode = GPPAINT_MODE_BOTH; - brush->gpencil_settings->vertex_factor = 1.0f; - - switch (type) { - case GP_BRUSH_PRESET_AIRBRUSH: { - brush->size = 300.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; - - brush->gpencil_settings->draw_strength = 0.4f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; - - brush->gpencil_settings->input_samples = 10; - brush->gpencil_settings->active_smooth = ACTIVE_SMOOTH; - brush->gpencil_settings->draw_angle = 0.0f; - brush->gpencil_settings->draw_angle_factor = 0.0f; - brush->gpencil_settings->hardeness = 0.9f; - copy_v2_fl(brush->gpencil_settings->aspect_ratio, 1.0f); - - brush->gpencil_tool = GPAINT_TOOL_DRAW; - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_AIRBRUSH; - - zero_v3(brush->secondary_rgb); - - material_preset = PRESET_MATERIAL_DOT_STROKE; - - break; - } - case GP_BRUSH_PRESET_INK_PEN: { - - brush->size = 60.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; - - brush->gpencil_settings->draw_strength = 1.0f; - - brush->gpencil_settings->input_samples = 10; - brush->gpencil_settings->active_smooth = ACTIVE_SMOOTH; - brush->gpencil_settings->draw_angle = 0.0f; - brush->gpencil_settings->draw_angle_factor = 0.0f; - brush->gpencil_settings->hardeness = 1.0f; - copy_v2_fl(brush->gpencil_settings->aspect_ratio, 1.0f); - - brush->gpencil_settings->flag |= GP_BRUSH_GROUP_SETTINGS; - brush->gpencil_settings->draw_smoothfac = 0.1f; - brush->gpencil_settings->draw_smoothlvl = 1; - brush->gpencil_settings->draw_subdivide = 0; - brush->gpencil_settings->simplify_f = 0.002f; - - brush->gpencil_settings->draw_random_press = 0.0f; - brush->gpencil_settings->draw_jitter = 0.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_JITTER_PRESSURE; - - /* Curve. */ - custom_curve = brush->gpencil_settings->curve_sensitivity; - BKE_curvemapping_set_defaults(custom_curve, 0, 0.0f, 0.0f, 1.0f, 1.0f); - BKE_curvemapping_init(custom_curve); - brush_gpencil_curvemap_reset(custom_curve->cm, 3, GPCURVE_PRESET_INK); - - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_INK; - brush->gpencil_tool = GPAINT_TOOL_DRAW; - - zero_v3(brush->secondary_rgb); - break; - } - case GP_BRUSH_PRESET_INK_PEN_ROUGH: { - brush->size = 60.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; - - brush->gpencil_settings->draw_strength = 1.0f; - - brush->gpencil_settings->input_samples = 10; - brush->gpencil_settings->active_smooth = ACTIVE_SMOOTH; - brush->gpencil_settings->draw_angle = 0.0f; - brush->gpencil_settings->draw_angle_factor = 0.0f; - brush->gpencil_settings->hardeness = 1.0f; - copy_v2_fl(brush->gpencil_settings->aspect_ratio, 1.0f); - - brush->gpencil_settings->flag &= ~GP_BRUSH_GROUP_SETTINGS; - brush->gpencil_settings->draw_smoothfac = 0.0f; - brush->gpencil_settings->draw_smoothlvl = 2; - brush->gpencil_settings->draw_subdivide = 0; - brush->gpencil_settings->simplify_f = 0.000f; - - brush->gpencil_settings->flag |= GP_BRUSH_GROUP_RANDOM; - brush->gpencil_settings->draw_random_press = 0.6f; - brush->gpencil_settings->draw_random_strength = 0.0f; - brush->gpencil_settings->draw_jitter = 0.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_JITTER_PRESSURE; - - /* Curve. */ - custom_curve = brush->gpencil_settings->curve_sensitivity; - BKE_curvemapping_set_defaults(custom_curve, 0, 0.0f, 0.0f, 1.0f, 1.0f); - BKE_curvemapping_init(custom_curve); - brush_gpencil_curvemap_reset(custom_curve->cm, 3, GPCURVE_PRESET_INKNOISE); - - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_INKNOISE; - brush->gpencil_tool = GPAINT_TOOL_DRAW; - - zero_v3(brush->secondary_rgb); - break; - } - case GP_BRUSH_PRESET_MARKER_BOLD: { - brush->size = 150.0f; - brush->gpencil_settings->flag &= ~GP_BRUSH_USE_PRESSURE; - - brush->gpencil_settings->draw_strength = 0.3f; - - brush->gpencil_settings->input_samples = 10; - brush->gpencil_settings->active_smooth = ACTIVE_SMOOTH; - brush->gpencil_settings->draw_angle = 0.0f; - brush->gpencil_settings->draw_angle_factor = 0.0f; - brush->gpencil_settings->hardeness = 1.0f; - copy_v2_fl(brush->gpencil_settings->aspect_ratio, 1.0f); - - brush->gpencil_settings->flag |= GP_BRUSH_GROUP_SETTINGS; - brush->gpencil_settings->draw_smoothfac = 0.1f; - brush->gpencil_settings->draw_smoothlvl = 1; - brush->gpencil_settings->draw_subdivide = 0; - brush->gpencil_settings->simplify_f = 0.002f; - - brush->gpencil_settings->flag &= ~GP_BRUSH_GROUP_RANDOM; - brush->gpencil_settings->draw_random_press = 0.0f; - brush->gpencil_settings->draw_random_strength = 0.0f; - brush->gpencil_settings->draw_jitter = 0.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_JITTER_PRESSURE; - - /* Curve. */ - custom_curve = brush->gpencil_settings->curve_sensitivity; - BKE_curvemapping_set_defaults(custom_curve, 0, 0.0f, 0.0f, 1.0f, 1.0f); - BKE_curvemapping_init(custom_curve); - brush_gpencil_curvemap_reset(custom_curve->cm, 4, GPCURVE_PRESET_MARKER); - - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_MARKER; - brush->gpencil_tool = GPAINT_TOOL_DRAW; - - zero_v3(brush->secondary_rgb); - break; - } - case GP_BRUSH_PRESET_MARKER_CHISEL: { - brush->size = 150.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; - - brush->gpencil_settings->draw_strength = 1.0f; - - brush->gpencil_settings->input_samples = 10; - brush->gpencil_settings->active_smooth = 0.3f; - brush->gpencil_settings->draw_angle = DEG2RAD(35.0f); - brush->gpencil_settings->draw_angle_factor = 0.5f; - brush->gpencil_settings->hardeness = 1.0f; - copy_v2_fl(brush->gpencil_settings->aspect_ratio, 1.0f); - - brush->gpencil_settings->flag |= GP_BRUSH_GROUP_SETTINGS; - brush->gpencil_settings->draw_smoothfac = 0.0f; - brush->gpencil_settings->draw_smoothlvl = 1; - brush->gpencil_settings->draw_subdivide = 0; - brush->gpencil_settings->simplify_f = 0.002f; - - brush->gpencil_settings->flag &= ~GP_BRUSH_GROUP_RANDOM; - brush->gpencil_settings->draw_random_press = 0.0f; - brush->gpencil_settings->draw_jitter = 0.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_JITTER_PRESSURE; - - /* Curve. */ - custom_curve = brush->gpencil_settings->curve_sensitivity; - BKE_curvemapping_set_defaults(custom_curve, 0, 0.0f, 0.0f, 1.0f, 1.0f); - BKE_curvemapping_init(custom_curve); - brush_gpencil_curvemap_reset(custom_curve->cm, 3, GPCURVE_PRESET_CHISEL_SENSIVITY); - - custom_curve = brush->gpencil_settings->curve_strength; - BKE_curvemapping_set_defaults(custom_curve, 0, 0.0f, 0.0f, 1.0f, 1.0f); - BKE_curvemapping_init(custom_curve); - brush_gpencil_curvemap_reset(custom_curve->cm, 4, GPCURVE_PRESET_CHISEL_STRENGTH); - - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_CHISEL; - brush->gpencil_tool = GPAINT_TOOL_DRAW; - - zero_v3(brush->secondary_rgb); - break; - } - case GP_BRUSH_PRESET_PEN: { - brush->size = 25.0f; - brush->gpencil_settings->flag &= ~GP_BRUSH_USE_PRESSURE; - - brush->gpencil_settings->draw_strength = 1.0f; - brush->gpencil_settings->flag &= ~GP_BRUSH_USE_STRENGTH_PRESSURE; - - brush->gpencil_settings->input_samples = 10; - brush->gpencil_settings->active_smooth = ACTIVE_SMOOTH; - brush->gpencil_settings->draw_angle = 0.0f; - brush->gpencil_settings->draw_angle_factor = 0.0f; - brush->gpencil_settings->hardeness = 1.0f; - copy_v2_fl(brush->gpencil_settings->aspect_ratio, 1.0f); - - brush->gpencil_settings->flag |= GP_BRUSH_GROUP_SETTINGS; - brush->gpencil_settings->draw_smoothfac = 0.0f; - brush->gpencil_settings->draw_smoothlvl = 1; - brush->gpencil_settings->draw_subdivide = 1; - brush->gpencil_settings->simplify_f = 0.002f; - - brush->gpencil_settings->draw_random_press = 0.0f; - brush->gpencil_settings->draw_random_strength = 0.0f; - brush->gpencil_settings->draw_jitter = 0.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_JITTER_PRESSURE; - - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_PEN; - brush->gpencil_tool = GPAINT_TOOL_DRAW; - - zero_v3(brush->secondary_rgb); - break; - } - case GP_BRUSH_PRESET_PENCIL_SOFT: { - brush->size = 80.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; - - brush->gpencil_settings->draw_strength = 0.4f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; - - brush->gpencil_settings->input_samples = 10; - brush->gpencil_settings->active_smooth = ACTIVE_SMOOTH; - brush->gpencil_settings->draw_angle = 0.0f; - brush->gpencil_settings->draw_angle_factor = 0.0f; - brush->gpencil_settings->hardeness = 0.8f; - copy_v2_fl(brush->gpencil_settings->aspect_ratio, 1.0f); - - brush->gpencil_settings->flag |= GP_BRUSH_GROUP_SETTINGS; - brush->gpencil_settings->draw_smoothfac = 0.0f; - brush->gpencil_settings->draw_smoothlvl = 1; - brush->gpencil_settings->draw_subdivide = 0; - brush->gpencil_settings->simplify_f = 0.000f; - - brush->gpencil_settings->draw_random_press = 0.0f; - brush->gpencil_settings->draw_random_strength = 0.0f; - brush->gpencil_settings->draw_jitter = 0.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_JITTER_PRESSURE; - - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_PENCIL; - brush->gpencil_tool = GPAINT_TOOL_DRAW; - - zero_v3(brush->secondary_rgb); - - material_preset = PRESET_MATERIAL_DOT_STROKE; - - break; - } - case GP_BRUSH_PRESET_PENCIL: { - brush->size = 20.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; - - brush->gpencil_settings->draw_strength = 0.6f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; - - brush->gpencil_settings->input_samples = 10; - brush->gpencil_settings->active_smooth = ACTIVE_SMOOTH; - brush->gpencil_settings->draw_angle = 0.0f; - brush->gpencil_settings->draw_angle_factor = 0.0f; - brush->gpencil_settings->hardeness = 1.0f; - copy_v2_fl(brush->gpencil_settings->aspect_ratio, 1.0f); - - brush->gpencil_settings->flag |= GP_BRUSH_GROUP_SETTINGS; - brush->gpencil_settings->draw_smoothfac = 0.0f; - brush->gpencil_settings->draw_smoothlvl = 1; - brush->gpencil_settings->draw_subdivide = 0; - brush->gpencil_settings->simplify_f = 0.002f; - - brush->gpencil_settings->draw_random_press = 0.0f; - brush->gpencil_settings->draw_jitter = 0.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_JITTER_PRESSURE; - - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_PENCIL; - brush->gpencil_tool = GPAINT_TOOL_DRAW; - - zero_v3(brush->secondary_rgb); - break; - } - case GP_BRUSH_PRESET_FILL_AREA: { - brush->size = 5.0f; - - brush->gpencil_settings->fill_leak = 3; - brush->gpencil_settings->fill_threshold = 0.1f; - brush->gpencil_settings->fill_simplylvl = 1; - brush->gpencil_settings->fill_factor = 1.0f; - - brush->gpencil_settings->draw_strength = 1.0f; - brush->gpencil_settings->hardeness = 1.0f; - copy_v2_fl(brush->gpencil_settings->aspect_ratio, 1.0f); - brush->gpencil_settings->draw_smoothfac = 0.1f; - brush->gpencil_settings->draw_smoothlvl = 1; - brush->gpencil_settings->draw_subdivide = 1; - brush->gpencil_settings->dilate_pixels = 1; - - brush->gpencil_settings->flag |= GP_BRUSH_FILL_SHOW_EXTENDLINES; - - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_FILL; - brush->gpencil_tool = GPAINT_TOOL_FILL; - brush->gpencil_settings->vertex_mode = GPPAINT_MODE_FILL; - - zero_v3(brush->secondary_rgb); - break; - } - case GP_BRUSH_PRESET_ERASER_SOFT: { - brush->size = 30.0f; - brush->gpencil_settings->draw_strength = 0.5f; - brush->gpencil_settings->flag |= GP_BRUSH_DEFAULT_ERASER; - brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; - brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_ERASE_SOFT; - brush->gpencil_tool = GPAINT_TOOL_ERASE; - brush->gpencil_settings->eraser_mode = GP_BRUSH_ERASER_SOFT; - brush->gpencil_settings->era_strength_f = 100.0f; - brush->gpencil_settings->era_thickness_f = 10.0f; - - break; - } - case GP_BRUSH_PRESET_ERASER_HARD: { - brush->size = 30.0f; - brush->gpencil_settings->draw_strength = 1.0f; - brush->gpencil_settings->eraser_mode = GP_BRUSH_ERASER_SOFT; - brush->gpencil_settings->era_strength_f = 100.0f; - brush->gpencil_settings->era_thickness_f = 50.0f; - - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_ERASE_HARD; - brush->gpencil_tool = GPAINT_TOOL_ERASE; - - break; - } - case GP_BRUSH_PRESET_ERASER_POINT: { - brush->size = 30.0f; - brush->gpencil_settings->eraser_mode = GP_BRUSH_ERASER_HARD; - - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_ERASE_HARD; - brush->gpencil_tool = GPAINT_TOOL_ERASE; - - break; - } - case GP_BRUSH_PRESET_ERASER_STROKE: { - brush->size = 30.0f; - brush->gpencil_settings->eraser_mode = GP_BRUSH_ERASER_STROKE; - - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_ERASE_STROKE; - brush->gpencil_tool = GPAINT_TOOL_ERASE; - - break; - } - case GP_BRUSH_PRESET_TINT: { - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_TINT; - brush->gpencil_tool = GPAINT_TOOL_TINT; - - brush->size = 25.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; - - brush->gpencil_settings->draw_strength = 0.8f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; - - zero_v3(brush->secondary_rgb); - break; - } - case GP_BRUSH_PRESET_VERTEX_DRAW: { - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_VERTEX_DRAW; - brush->gpencil_vertex_tool = GPVERTEX_TOOL_DRAW; - - brush->size = 25.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; - - brush->gpencil_settings->draw_strength = 0.8f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; - - zero_v3(brush->secondary_rgb); - break; - } - case GP_BRUSH_PRESET_VERTEX_BLUR: { - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_VERTEX_BLUR; - brush->gpencil_vertex_tool = GPVERTEX_TOOL_BLUR; - - brush->size = 25.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; - - brush->gpencil_settings->draw_strength = 0.8f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; - - zero_v3(brush->secondary_rgb); - break; - } - case GP_BRUSH_PRESET_VERTEX_AVERAGE: { - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_VERTEX_AVERAGE; - brush->gpencil_vertex_tool = GPVERTEX_TOOL_AVERAGE; - - brush->size = 25.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; - - brush->gpencil_settings->draw_strength = 0.8f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; - - zero_v3(brush->secondary_rgb); - break; - } - case GP_BRUSH_PRESET_VERTEX_SMEAR: { - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_VERTEX_SMEAR; - brush->gpencil_vertex_tool = GPVERTEX_TOOL_SMEAR; - - brush->size = 25.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; - - brush->gpencil_settings->draw_strength = 0.8f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; - - zero_v3(brush->secondary_rgb); - break; - } - case GP_BRUSH_PRESET_VERTEX_REPLACE: { - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_VERTEX_REPLACE; - brush->gpencil_vertex_tool = GPVERTEX_TOOL_REPLACE; - - brush->size = 25.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; - - brush->gpencil_settings->draw_strength = 0.8f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; - - zero_v3(brush->secondary_rgb); - break; - } - case GP_BRUSH_PRESET_SMOOTH_STROKE: { - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_GPBRUSH_SMOOTH; - brush->gpencil_sculpt_tool = GPSCULPT_TOOL_SMOOTH; - - brush->size = 25.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; - - brush->gpencil_settings->draw_strength = 0.3f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; - brush->gpencil_settings->sculpt_flag = GP_SCULPT_FLAGMODE_APPLY_THICKNESS; - brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION; - - break; - } - case GP_BRUSH_PRESET_STRENGTH_STROKE: { - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_GPBRUSH_STRENGTH; - brush->gpencil_sculpt_tool = GPSCULPT_TOOL_STRENGTH; - - brush->size = 25.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; - - brush->gpencil_settings->draw_strength = 0.3f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; - brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION; - - break; - } - case GP_BRUSH_PRESET_THICKNESS_STROKE: { - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_GPBRUSH_THICKNESS; - brush->gpencil_sculpt_tool = GPSCULPT_TOOL_THICKNESS; - - brush->size = 25.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; - - brush->gpencil_settings->draw_strength = 0.5f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; - brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION; - - break; - } - case GP_BRUSH_PRESET_GRAB_STROKE: { - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_GPBRUSH_GRAB; - brush->gpencil_sculpt_tool = GPSCULPT_TOOL_GRAB; - brush->gpencil_settings->flag &= ~GP_BRUSH_USE_PRESSURE; - - brush->size = 25.0f; - - brush->gpencil_settings->draw_strength = 0.3f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; - brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION; - - break; - } - case GP_BRUSH_PRESET_PUSH_STROKE: { - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_GPBRUSH_PUSH; - brush->gpencil_sculpt_tool = GPSCULPT_TOOL_PUSH; - - brush->size = 25.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; - - brush->gpencil_settings->draw_strength = 0.3f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; - brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION; - - break; - } - case GP_BRUSH_PRESET_TWIST_STROKE: { - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_GPBRUSH_TWIST; - brush->gpencil_sculpt_tool = GPSCULPT_TOOL_TWIST; - - brush->size = 50.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; - - brush->gpencil_settings->draw_strength = 0.3f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; - brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION; - - break; - } - case GP_BRUSH_PRESET_PINCH_STROKE: { - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_GPBRUSH_PINCH; - brush->gpencil_sculpt_tool = GPSCULPT_TOOL_PINCH; - - brush->size = 50.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; - - brush->gpencil_settings->draw_strength = 0.5f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; - brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION; - - break; - } - case GP_BRUSH_PRESET_RANDOMIZE_STROKE: { - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_GPBRUSH_RANDOMIZE; - brush->gpencil_sculpt_tool = GPSCULPT_TOOL_RANDOMIZE; - - brush->size = 25.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; - - brush->gpencil_settings->draw_strength = 0.5f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; - brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION; - - break; - } - case GP_BRUSH_PRESET_CLONE_STROKE: { - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_GPBRUSH_CLONE; - brush->gpencil_sculpt_tool = GPSCULPT_TOOL_CLONE; - brush->gpencil_settings->flag &= ~GP_BRUSH_USE_PRESSURE; - - brush->size = 25.0f; - - brush->gpencil_settings->draw_strength = 1.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; - brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION; - - break; - } - case GP_BRUSH_PRESET_DRAW_WEIGHT: { - brush->gpencil_settings->icon_id = GP_BRUSH_ICON_GPBRUSH_WEIGHT; - brush->gpencil_weight_tool = GPWEIGHT_TOOL_DRAW; - - brush->size = 25.0f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; - - brush->gpencil_settings->draw_strength = 0.8f; - brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; - brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION; - - break; - } - default: - break; - } - - switch (material_preset) { - case PRESET_MATERIAL_NONE: - break; - case PRESET_MATERIAL_DOT_STROKE: { - /* Create and link Black Dots material to brush. - * This material is required because the brush uses the material - * to define how the stroke is drawn. */ - const char *ma_id = "Dots Stroke"; - Material *ma = BLI_findstring(&bmain->materials, ma_id, offsetof(ID, name) + 2); - if (ma == NULL) { - ma = BKE_gpencil_material_add(bmain, ma_id); - ma->gp_style->mode = GP_MATERIAL_MODE_DOT; - BLI_assert(ma->id.us == 1); - id_us_min(&ma->id); - } - - BKE_gpencil_brush_material_set(brush, ma); - - /* Pin the material to the brush. */ - brush->gpencil_settings->flag |= GP_BRUSH_MATERIAL_PINNED; - break; - } - } -} - -static Brush *gpencil_brush_ensure( - Main *bmain, ToolSettings *ts, const char *brush_name, eObjectMode mode, bool *r_new) -{ - *r_new = false; - Brush *brush = BLI_findstring(&bmain->brushes, brush_name, offsetof(ID, name) + 2); - - /* If the brush exist, but the type is not GPencil or the mode is wrong, create a new one. */ - if ((brush != NULL) && ((brush->gpencil_settings == NULL) || (brush->ob_mode != mode))) { - brush = NULL; - } - - if (brush == NULL) { - brush = BKE_brush_add_gpencil(bmain, ts, brush_name, mode); - *r_new = true; - } - - if (brush->gpencil_settings == NULL) { - BKE_brush_init_gpencil_settings(brush); - } - - return brush; -} - -void BKE_brush_gpencil_paint_presets(Main *bmain, ToolSettings *ts, const bool reset) -{ - bool r_new = false; - - Paint *paint = &ts->gp_paint->paint; - Brush *brush_prev = paint->brush; - Brush *brush, *deft_draw; - /* Airbrush brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Airbrush", OB_MODE_PAINT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_AIRBRUSH); - } - - /* Ink Pen brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Ink Pen", OB_MODE_PAINT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_INK_PEN); - } - - /* Ink Pen Rough brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Ink Pen Rough", OB_MODE_PAINT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_INK_PEN_ROUGH); - } - - /* Marker Bold brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Marker Bold", OB_MODE_PAINT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_MARKER_BOLD); - } - - /* Marker Chisel brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Marker Chisel", OB_MODE_PAINT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_MARKER_CHISEL); - } - - /* Pen brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Pen", OB_MODE_PAINT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_PEN); - } - - /* Pencil Soft brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Pencil Soft", OB_MODE_PAINT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_PENCIL_SOFT); - } - - /* Pencil brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Pencil", OB_MODE_PAINT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_PENCIL); - } - deft_draw = brush; /* save default brush. */ - - /* Fill brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Fill Area", OB_MODE_PAINT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_FILL_AREA); - } - - /* Soft Eraser brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Eraser Soft", OB_MODE_PAINT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_ERASER_SOFT); - } - - /* Hard Eraser brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Eraser Hard", OB_MODE_PAINT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_ERASER_HARD); - } - - /* Point Eraser brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Eraser Point", OB_MODE_PAINT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_ERASER_POINT); - } - - /* Stroke Eraser brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Eraser Stroke", OB_MODE_PAINT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_ERASER_STROKE); - } - - /* Tint brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Tint", OB_MODE_PAINT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_TINT); - } - - /* Set default Draw brush. */ - if ((reset == false) && (brush_prev != NULL)) { - BKE_paint_brush_set(paint, brush_prev); - } - else { - BKE_paint_brush_set(paint, deft_draw); - } -} - -void BKE_brush_gpencil_vertex_presets(Main *bmain, ToolSettings *ts, const bool reset) -{ - bool r_new = false; - - Paint *vertexpaint = &ts->gp_vertexpaint->paint; - Brush *brush_prev = vertexpaint->brush; - Brush *brush, *deft_vertex; - /* Vertex Draw brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Vertex Draw", OB_MODE_VERTEX_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_VERTEX_DRAW); - } - deft_vertex = brush; /* save default brush. */ - - /* Vertex Blur brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Vertex Blur", OB_MODE_VERTEX_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_VERTEX_BLUR); - } - /* Vertex Average brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Vertex Average", OB_MODE_VERTEX_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_VERTEX_AVERAGE); - } - /* Vertex Smear brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Vertex Smear", OB_MODE_VERTEX_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_VERTEX_SMEAR); - } - /* Vertex Replace brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Vertex Replace", OB_MODE_VERTEX_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_VERTEX_REPLACE); - } - - /* Set default Vertex brush. */ - if (reset || brush_prev == NULL) { - BKE_paint_brush_set(vertexpaint, deft_vertex); - } - else { - if (brush_prev != NULL) { - BKE_paint_brush_set(vertexpaint, brush_prev); - } - } -} - -void BKE_brush_gpencil_sculpt_presets(Main *bmain, ToolSettings *ts, const bool reset) -{ - bool r_new = false; - - Paint *sculptpaint = &ts->gp_sculptpaint->paint; - Brush *brush_prev = sculptpaint->brush; - Brush *brush, *deft_sculpt; - - /* Smooth brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Smooth Stroke", OB_MODE_SCULPT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_SMOOTH_STROKE); - } - deft_sculpt = brush; - - /* Strength brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Strength Stroke", OB_MODE_SCULPT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_STRENGTH_STROKE); - } - - /* Thickness brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Thickness Stroke", OB_MODE_SCULPT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_THICKNESS_STROKE); - } - - /* Grab brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Grab Stroke", OB_MODE_SCULPT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_GRAB_STROKE); - } - - /* Push brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Push Stroke", OB_MODE_SCULPT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_PUSH_STROKE); - } - - /* Twist brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Twist Stroke", OB_MODE_SCULPT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_TWIST_STROKE); - } - - /* Pinch brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Pinch Stroke", OB_MODE_SCULPT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_PINCH_STROKE); - } - - /* Randomize brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Randomize Stroke", OB_MODE_SCULPT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_RANDOMIZE_STROKE); - } - - /* Clone brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Clone Stroke", OB_MODE_SCULPT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_CLONE_STROKE); - } - - /* Set default brush. */ - if (reset || brush_prev == NULL) { - BKE_paint_brush_set(sculptpaint, deft_sculpt); - } - else { - if (brush_prev != NULL) { - BKE_paint_brush_set(sculptpaint, brush_prev); - } - } -} - -void BKE_brush_gpencil_weight_presets(Main *bmain, ToolSettings *ts, const bool reset) -{ - bool r_new = false; - - Paint *weightpaint = &ts->gp_weightpaint->paint; - Brush *brush_prev = weightpaint->brush; - Brush *brush, *deft_weight; - /* Vertex Draw brush. */ - brush = gpencil_brush_ensure(bmain, ts, "Draw Weight", OB_MODE_WEIGHT_GPENCIL, &r_new); - if ((reset) || (r_new)) { - BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_DRAW_WEIGHT); - } - deft_weight = brush; /* save default brush. */ - - /* Set default brush. */ - if (reset || brush_prev == NULL) { - BKE_paint_brush_set(weightpaint, deft_weight); - } - else { - if (brush_prev != NULL) { - BKE_paint_brush_set(weightpaint, brush_prev); - } - } -} - -void BKE_brush_init_curves_sculpt_settings(Brush *brush) -{ - if (brush->curves_sculpt_settings == NULL) { - brush->curves_sculpt_settings = MEM_callocN(sizeof(BrushCurvesSculptSettings), __func__); - } - BrushCurvesSculptSettings *settings = brush->curves_sculpt_settings; - settings->add_amount = 1; - settings->points_per_curve = 8; - settings->minimum_length = 0.01f; - settings->curve_length = 0.3f; - settings->density_add_attempts = 100; -} - -struct Brush *BKE_brush_first_search(struct Main *bmain, const eObjectMode ob_mode) -{ - Brush *brush; - - for (brush = bmain->brushes.first; brush; brush = brush->id.next) { - if (brush->ob_mode & ob_mode) { - return brush; - } - } - return NULL; -} - -void BKE_brush_debug_print_state(Brush *br) -{ - /* create a fake brush and set it to the defaults */ - Brush def = {{NULL}}; - brush_defaults(&def); - -#define BR_TEST(field, t) \ - if (br->field != def.field) { \ - printf("br->" #field " = %" #t ";\n", br->field); \ - } \ - ((void)0) - -#define BR_TEST_FLAG(_f) \ - if ((br->flag & _f) && !(def.flag & _f)) { \ - printf("br->flag |= " #_f ";\n"); \ - } \ - else if (!(br->flag & _f) && (def.flag & _f)) { \ - printf("br->flag &= ~" #_f ";\n"); \ - } \ - ((void)0) - -#define BR_TEST_FLAG_OVERLAY(_f) \ - if ((br->overlay_flags & _f) && !(def.overlay_flags & _f)) { \ - printf("br->overlay_flags |= " #_f ";\n"); \ - } \ - else if (!(br->overlay_flags & _f) && (def.overlay_flags & _f)) { \ - printf("br->overlay_flags &= ~" #_f ";\n"); \ - } \ - ((void)0) - - /* print out any non-default brush state */ - BR_TEST(normal_weight, f); - - BR_TEST(blend, d); - BR_TEST(size, d); - - /* br->flag */ - BR_TEST_FLAG(BRUSH_AIRBRUSH); - BR_TEST_FLAG(BRUSH_ALPHA_PRESSURE); - BR_TEST_FLAG(BRUSH_SIZE_PRESSURE); - BR_TEST_FLAG(BRUSH_JITTER_PRESSURE); - BR_TEST_FLAG(BRUSH_SPACING_PRESSURE); - BR_TEST_FLAG(BRUSH_ANCHORED); - BR_TEST_FLAG(BRUSH_DIR_IN); - BR_TEST_FLAG(BRUSH_SPACE); - BR_TEST_FLAG(BRUSH_SMOOTH_STROKE); - BR_TEST_FLAG(BRUSH_PERSISTENT); - BR_TEST_FLAG(BRUSH_ACCUMULATE); - BR_TEST_FLAG(BRUSH_LOCK_ALPHA); - BR_TEST_FLAG(BRUSH_ORIGINAL_NORMAL); - BR_TEST_FLAG(BRUSH_OFFSET_PRESSURE); - BR_TEST_FLAG(BRUSH_SPACE_ATTEN); - BR_TEST_FLAG(BRUSH_ADAPTIVE_SPACE); - BR_TEST_FLAG(BRUSH_LOCK_SIZE); - BR_TEST_FLAG(BRUSH_EDGE_TO_EDGE); - BR_TEST_FLAG(BRUSH_DRAG_DOT); - BR_TEST_FLAG(BRUSH_INVERSE_SMOOTH_PRESSURE); - BR_TEST_FLAG(BRUSH_PLANE_TRIM); - BR_TEST_FLAG(BRUSH_FRONTFACE); - BR_TEST_FLAG(BRUSH_CUSTOM_ICON); - - BR_TEST_FLAG_OVERLAY(BRUSH_OVERLAY_CURSOR); - BR_TEST_FLAG_OVERLAY(BRUSH_OVERLAY_PRIMARY); - BR_TEST_FLAG_OVERLAY(BRUSH_OVERLAY_SECONDARY); - BR_TEST_FLAG_OVERLAY(BRUSH_OVERLAY_CURSOR_OVERRIDE_ON_STROKE); - BR_TEST_FLAG_OVERLAY(BRUSH_OVERLAY_PRIMARY_OVERRIDE_ON_STROKE); - BR_TEST_FLAG_OVERLAY(BRUSH_OVERLAY_SECONDARY_OVERRIDE_ON_STROKE); - - BR_TEST(jitter, f); - BR_TEST(spacing, d); - BR_TEST(smooth_stroke_radius, d); - BR_TEST(smooth_stroke_factor, f); - BR_TEST(rate, f); - - BR_TEST(alpha, f); - - BR_TEST(sculpt_plane, d); - - BR_TEST(plane_offset, f); - - BR_TEST(autosmooth_factor, f); - - BR_TEST(topology_rake_factor, f); - - BR_TEST(crease_pinch_factor, f); - - BR_TEST(plane_trim, f); - - BR_TEST(texture_sample_bias, f); - BR_TEST(texture_overlay_alpha, d); - - BR_TEST(add_col[0], f); - BR_TEST(add_col[1], f); - BR_TEST(add_col[2], f); - BR_TEST(add_col[3], f); - BR_TEST(sub_col[0], f); - BR_TEST(sub_col[1], f); - BR_TEST(sub_col[2], f); - BR_TEST(sub_col[3], f); - - printf("\n"); - -#undef BR_TEST -#undef BR_TEST_FLAG -} - -void BKE_brush_sculpt_reset(Brush *br) -{ - /* enable this to see any non-default - * settings used by a brush: */ - // BKE_brush_debug_print_state(br); - - brush_defaults(br); - BKE_brush_curve_preset(br, CURVE_PRESET_SMOOTH); - - /* Use the curve presets by default */ - br->curve_preset = BRUSH_CURVE_SMOOTH; - - /* Note that sculpt defaults where set when 0.5 was the default (now it's 1.0) - * assign this so logic below can remain the same. */ - br->alpha = 0.5f; - - /* Brush settings */ - switch (br->sculpt_tool) { - case SCULPT_TOOL_DRAW_SHARP: - br->flag |= BRUSH_DIR_IN; - br->curve_preset = BRUSH_CURVE_POW4; - br->spacing = 5; - break; - case SCULPT_TOOL_DISPLACEMENT_ERASER: - br->curve_preset = BRUSH_CURVE_SMOOTHER; - br->spacing = 10; - br->alpha = 1.0f; - break; - case SCULPT_TOOL_SLIDE_RELAX: - br->spacing = 10; - br->alpha = 1.0f; - br->slide_deform_type = BRUSH_SLIDE_DEFORM_DRAG; - break; - case SCULPT_TOOL_CLAY: - br->flag |= BRUSH_SIZE_PRESSURE; - br->spacing = 3; - br->autosmooth_factor = 0.25f; - br->normal_radius_factor = 0.75f; - br->hardness = 0.65f; - break; - case SCULPT_TOOL_CLAY_THUMB: - br->alpha = 0.5f; - br->normal_radius_factor = 1.0f; - br->spacing = 6; - br->hardness = 0.5f; - br->flag |= BRUSH_SIZE_PRESSURE; - br->flag &= ~BRUSH_SPACE_ATTEN; - break; - case SCULPT_TOOL_CLAY_STRIPS: - br->flag |= BRUSH_ACCUMULATE | BRUSH_SIZE_PRESSURE; - br->flag &= ~BRUSH_SPACE_ATTEN; - br->alpha = 0.6f; - br->spacing = 5; - br->normal_radius_factor = 1.55f; - br->tip_roundness = 0.18f; - br->curve_preset = BRUSH_CURVE_SMOOTHER; - break; - case SCULPT_TOOL_MULTIPLANE_SCRAPE: - br->flag2 |= BRUSH_MULTIPLANE_SCRAPE_DYNAMIC | BRUSH_MULTIPLANE_SCRAPE_PLANES_PREVIEW; - br->alpha = 0.7f; - br->normal_radius_factor = 0.70f; - br->multiplane_scrape_angle = 60; - br->curve_preset = BRUSH_CURVE_SMOOTH; - br->spacing = 5; - break; - case SCULPT_TOOL_CREASE: - br->flag |= BRUSH_DIR_IN; - br->alpha = 0.25; - break; - case SCULPT_TOOL_SCRAPE: - case SCULPT_TOOL_FILL: - br->alpha = 0.7f; - br->area_radius_factor = 0.5f; - br->spacing = 7; - br->flag |= BRUSH_ACCUMULATE; - br->flag |= BRUSH_INVERT_TO_SCRAPE_FILL; - break; - case SCULPT_TOOL_ROTATE: - br->alpha = 1.0; - break; - case SCULPT_TOOL_SMOOTH: - br->flag &= ~BRUSH_SPACE_ATTEN; - br->spacing = 5; - br->alpha = 0.7f; - br->surface_smooth_shape_preservation = 0.5f; - br->surface_smooth_current_vertex = 0.5f; - br->surface_smooth_iterations = 4; - break; - case SCULPT_TOOL_SNAKE_HOOK: - br->alpha = 1.0f; - br->rake_factor = 1.0f; - break; - case SCULPT_TOOL_THUMB: - br->size = 75; - br->flag &= ~BRUSH_ALPHA_PRESSURE; - br->flag &= ~BRUSH_SPACE; - br->flag &= ~BRUSH_SPACE_ATTEN; - break; - case SCULPT_TOOL_ELASTIC_DEFORM: - br->elastic_deform_volume_preservation = 0.4f; - br->elastic_deform_type = BRUSH_ELASTIC_DEFORM_GRAB_TRISCALE; - br->flag &= ~BRUSH_ALPHA_PRESSURE; - br->flag &= ~BRUSH_SPACE; - br->flag &= ~BRUSH_SPACE_ATTEN; - break; - case SCULPT_TOOL_POSE: - br->pose_smooth_iterations = 4; - br->pose_ik_segments = 1; - br->flag2 |= BRUSH_POSE_IK_ANCHORED | BRUSH_USE_CONNECTED_ONLY; - br->flag &= ~BRUSH_ALPHA_PRESSURE; - br->flag &= ~BRUSH_SPACE; - br->flag &= ~BRUSH_SPACE_ATTEN; - break; - case SCULPT_TOOL_BOUNDARY: - br->flag &= ~BRUSH_ALPHA_PRESSURE; - br->flag &= ~BRUSH_SPACE; - br->flag &= ~BRUSH_SPACE_ATTEN; - br->curve_preset = BRUSH_CURVE_CONSTANT; - break; - case SCULPT_TOOL_DRAW_FACE_SETS: - br->alpha = 0.5f; - br->flag &= ~BRUSH_ALPHA_PRESSURE; - br->flag &= ~BRUSH_SPACE; - br->flag &= ~BRUSH_SPACE_ATTEN; - break; - case SCULPT_TOOL_GRAB: - br->alpha = 0.4f; - br->size = 75; - br->flag &= ~BRUSH_ALPHA_PRESSURE; - br->flag &= ~BRUSH_SPACE; - br->flag &= ~BRUSH_SPACE_ATTEN; - break; - case SCULPT_TOOL_CLOTH: - br->cloth_mass = 1.0f; - br->cloth_damping = 0.01f; - br->cloth_sim_limit = 2.5f; - br->cloth_sim_falloff = 0.75f; - br->cloth_deform_type = BRUSH_CLOTH_DEFORM_DRAG; - br->flag &= ~(BRUSH_ALPHA_PRESSURE | BRUSH_SIZE_PRESSURE); - break; - case SCULPT_TOOL_LAYER: - br->flag &= ~BRUSH_SPACE_ATTEN; - br->hardness = 0.35f; - br->alpha = 1.0f; - br->height = 0.05f; - break; - case SCULPT_TOOL_PAINT: - br->hardness = 0.4f; - br->spacing = 10; - br->alpha = 0.6f; - br->flow = 1.0f; - br->tip_scale_x = 1.0f; - br->tip_roundness = 1.0f; - br->density = 1.0f; - br->flag &= ~BRUSH_SPACE_ATTEN; - copy_v3_fl(br->rgb, 1.0f); - zero_v3(br->secondary_rgb); - break; - case SCULPT_TOOL_SMEAR: - br->alpha = 1.0f; - br->spacing = 5; - br->flag &= ~BRUSH_ALPHA_PRESSURE; - br->flag &= ~BRUSH_SPACE_ATTEN; - br->curve_preset = BRUSH_CURVE_SPHERE; - break; - case SCULPT_TOOL_DISPLACEMENT_SMEAR: - br->alpha = 1.0f; - br->spacing = 5; - br->hardness = 0.7f; - br->flag &= ~BRUSH_ALPHA_PRESSURE; - br->flag &= ~BRUSH_SPACE_ATTEN; - br->curve_preset = BRUSH_CURVE_SMOOTHER; - break; - default: - break; - } - - /* Cursor colors */ - - /* Default Alpha */ - br->add_col[3] = 0.90f; - br->sub_col[3] = 0.90f; - - switch (br->sculpt_tool) { - case SCULPT_TOOL_DRAW: - case SCULPT_TOOL_DRAW_SHARP: - case SCULPT_TOOL_CLAY: - case SCULPT_TOOL_CLAY_STRIPS: - case SCULPT_TOOL_CLAY_THUMB: - case SCULPT_TOOL_LAYER: - case SCULPT_TOOL_INFLATE: - case SCULPT_TOOL_BLOB: - case SCULPT_TOOL_CREASE: - br->add_col[0] = 0.0f; - br->add_col[1] = 0.5f; - br->add_col[2] = 1.0f; - br->sub_col[0] = 0.0f; - br->sub_col[1] = 0.5f; - br->sub_col[2] = 1.0f; - break; - - case SCULPT_TOOL_SMOOTH: - case SCULPT_TOOL_FLATTEN: - case SCULPT_TOOL_FILL: - case SCULPT_TOOL_SCRAPE: - case SCULPT_TOOL_MULTIPLANE_SCRAPE: - br->add_col[0] = 0.877f; - br->add_col[1] = 0.142f; - br->add_col[2] = 0.117f; - br->sub_col[0] = 0.877f; - br->sub_col[1] = 0.142f; - br->sub_col[2] = 0.117f; - break; - - case SCULPT_TOOL_PINCH: - case SCULPT_TOOL_GRAB: - case SCULPT_TOOL_SNAKE_HOOK: - case SCULPT_TOOL_THUMB: - case SCULPT_TOOL_NUDGE: - case SCULPT_TOOL_ROTATE: - case SCULPT_TOOL_ELASTIC_DEFORM: - case SCULPT_TOOL_POSE: - case SCULPT_TOOL_BOUNDARY: - case SCULPT_TOOL_SLIDE_RELAX: - br->add_col[0] = 1.0f; - br->add_col[1] = 0.95f; - br->add_col[2] = 0.005f; - br->sub_col[0] = 1.0f; - br->sub_col[1] = 0.95f; - br->sub_col[2] = 0.005f; - break; - - case SCULPT_TOOL_SIMPLIFY: - case SCULPT_TOOL_PAINT: - case SCULPT_TOOL_MASK: - case SCULPT_TOOL_DRAW_FACE_SETS: - case SCULPT_TOOL_DISPLACEMENT_ERASER: - case SCULPT_TOOL_DISPLACEMENT_SMEAR: - br->add_col[0] = 0.75f; - br->add_col[1] = 0.75f; - br->add_col[2] = 0.75f; - br->sub_col[0] = 0.75f; - br->sub_col[1] = 0.75f; - br->sub_col[2] = 0.75f; - break; - - case SCULPT_TOOL_CLOTH: - br->add_col[0] = 1.0f; - br->add_col[1] = 0.5f; - br->add_col[2] = 0.1f; - br->sub_col[0] = 1.0f; - br->sub_col[1] = 0.5f; - br->sub_col[2] = 0.1f; - break; - default: - break; - } -} - -void BKE_brush_curve_preset(Brush *b, eCurveMappingPreset preset) -{ - CurveMapping *cumap = NULL; - CurveMap *cuma = NULL; - - if (!b->curve) { - b->curve = BKE_curvemapping_add(1, 0, 0, 1, 1); - } - cumap = b->curve; - cumap->flag &= ~CUMA_EXTEND_EXTRAPOLATE; - cumap->preset = preset; - - cuma = b->curve->cm; - BKE_curvemap_reset(cuma, &cumap->clipr, cumap->preset, CURVEMAP_SLOPE_NEGATIVE); - BKE_curvemapping_changed(cumap, false); -} - -float BKE_brush_sample_tex_3d(const Scene *scene, - const Brush *br, - const float point[3], - float rgba[4], - const int thread, - struct ImagePool *pool) -{ - UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; - const MTex *mtex = &br->mtex; - float intensity = 1.0; - bool hasrgb = false; - - if (!mtex->tex) { - intensity = 1; - } - else if (mtex->brush_map_mode == MTEX_MAP_MODE_3D) { - /* Get strength by feeding the vertex - * location directly into a texture */ - hasrgb = RE_texture_evaluate(mtex, point, thread, pool, false, false, &intensity, rgba); - } - else if (mtex->brush_map_mode == MTEX_MAP_MODE_STENCIL) { - float rotation = -mtex->rot; - const float point_2d[2] = {point[0], point[1]}; - float x, y; - float co[3]; - - x = point_2d[0] - br->stencil_pos[0]; - y = point_2d[1] - br->stencil_pos[1]; - - if (rotation > 0.001f || rotation < -0.001f) { - const float angle = atan2f(y, x) + rotation; - const float flen = sqrtf(x * x + y * y); - - x = flen * cosf(angle); - y = flen * sinf(angle); - } - - if (fabsf(x) > br->stencil_dimension[0] || fabsf(y) > br->stencil_dimension[1]) { - zero_v4(rgba); - return 0.0f; - } - x /= (br->stencil_dimension[0]); - y /= (br->stencil_dimension[1]); - - co[0] = x; - co[1] = y; - co[2] = 0.0f; - - hasrgb = RE_texture_evaluate(mtex, co, thread, pool, false, false, &intensity, rgba); - } - else { - float rotation = -mtex->rot; - const float point_2d[2] = {point[0], point[1]}; - float x = 0.0f, y = 0.0f; /* Quite warnings */ - float invradius = 1.0f; /* Quite warnings */ - float co[3]; - - if (mtex->brush_map_mode == MTEX_MAP_MODE_VIEW) { - /* keep coordinates relative to mouse */ - - rotation += ups->brush_rotation; - - x = point_2d[0] - ups->tex_mouse[0]; - y = point_2d[1] - ups->tex_mouse[1]; - - /* use pressure adjusted size for fixed mode */ - invradius = 1.0f / ups->pixel_radius; - } - else if (mtex->brush_map_mode == MTEX_MAP_MODE_TILED) { - /* leave the coordinates relative to the screen */ - - /* use unadjusted size for tiled mode */ - invradius = 1.0f / ups->start_pixel_radius; - - x = point_2d[0]; - y = point_2d[1]; - } - else if (mtex->brush_map_mode == MTEX_MAP_MODE_RANDOM) { - rotation += ups->brush_rotation; - /* these contain a random coordinate */ - x = point_2d[0] - ups->tex_mouse[0]; - y = point_2d[1] - ups->tex_mouse[1]; - - invradius = 1.0f / ups->pixel_radius; - } - - x *= invradius; - y *= invradius; - - /* it is probably worth optimizing for those cases where - * the texture is not rotated by skipping the calls to - * atan2, sqrtf, sin, and cos. */ - if (rotation > 0.001f || rotation < -0.001f) { - const float angle = atan2f(y, x) + rotation; - const float flen = sqrtf(x * x + y * y); - - x = flen * cosf(angle); - y = flen * sinf(angle); - } - - co[0] = x; - co[1] = y; - co[2] = 0.0f; - - hasrgb = RE_texture_evaluate(mtex, co, thread, pool, false, false, &intensity, rgba); - } - - intensity += br->texture_sample_bias; - - if (!hasrgb) { - rgba[0] = intensity; - rgba[1] = intensity; - rgba[2] = intensity; - rgba[3] = 1.0f; - } - /* For consistency, sampling always returns color in linear space */ - else if (ups->do_linear_conversion) { - IMB_colormanagement_colorspace_to_scene_linear_v3(rgba, ups->colorspace); - } - - return intensity; -} - -float BKE_brush_sample_masktex( - const Scene *scene, Brush *br, const float point[2], const int thread, struct ImagePool *pool) -{ - UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; - MTex *mtex = &br->mask_mtex; - float rgba[4], intensity; - - if (!mtex->tex) { - return 1.0f; - } - if (mtex->brush_map_mode == MTEX_MAP_MODE_STENCIL) { - float rotation = -mtex->rot; - const float point_2d[2] = {point[0], point[1]}; - float x, y; - float co[3]; - - x = point_2d[0] - br->mask_stencil_pos[0]; - y = point_2d[1] - br->mask_stencil_pos[1]; - - if (rotation > 0.001f || rotation < -0.001f) { - const float angle = atan2f(y, x) + rotation; - const float flen = sqrtf(x * x + y * y); - - x = flen * cosf(angle); - y = flen * sinf(angle); - } - - if (fabsf(x) > br->mask_stencil_dimension[0] || fabsf(y) > br->mask_stencil_dimension[1]) { - zero_v4(rgba); - return 0.0f; - } - x /= (br->mask_stencil_dimension[0]); - y /= (br->mask_stencil_dimension[1]); - - co[0] = x; - co[1] = y; - co[2] = 0.0f; - - RE_texture_evaluate(mtex, co, thread, pool, false, false, &intensity, rgba); - } - else { - float rotation = -mtex->rot; - const float point_2d[2] = {point[0], point[1]}; - float x = 0.0f, y = 0.0f; /* Quite warnings */ - float invradius = 1.0f; /* Quite warnings */ - float co[3]; - - if (mtex->brush_map_mode == MTEX_MAP_MODE_VIEW) { - /* keep coordinates relative to mouse */ - - rotation += ups->brush_rotation_sec; - - x = point_2d[0] - ups->mask_tex_mouse[0]; - y = point_2d[1] - ups->mask_tex_mouse[1]; - - /* use pressure adjusted size for fixed mode */ - invradius = 1.0f / ups->pixel_radius; - } - else if (mtex->brush_map_mode == MTEX_MAP_MODE_TILED) { - /* leave the coordinates relative to the screen */ - - /* use unadjusted size for tiled mode */ - invradius = 1.0f / ups->start_pixel_radius; - - x = point_2d[0]; - y = point_2d[1]; - } - else if (mtex->brush_map_mode == MTEX_MAP_MODE_RANDOM) { - rotation += ups->brush_rotation_sec; - /* these contain a random coordinate */ - x = point_2d[0] - ups->mask_tex_mouse[0]; - y = point_2d[1] - ups->mask_tex_mouse[1]; - - invradius = 1.0f / ups->pixel_radius; - } - - x *= invradius; - y *= invradius; - - /* it is probably worth optimizing for those cases where - * the texture is not rotated by skipping the calls to - * atan2, sqrtf, sin, and cos. */ - if (rotation > 0.001f || rotation < -0.001f) { - const float angle = atan2f(y, x) + rotation; - const float flen = sqrtf(x * x + y * y); - - x = flen * cosf(angle); - y = flen * sinf(angle); - } - - co[0] = x; - co[1] = y; - co[2] = 0.0f; - - RE_texture_evaluate(mtex, co, thread, pool, false, false, &intensity, rgba); - } - - CLAMP(intensity, 0.0f, 1.0f); - - switch (br->mask_pressure) { - case BRUSH_MASK_PRESSURE_CUTOFF: - intensity = ((1.0f - intensity) < ups->size_pressure_value) ? 1.0f : 0.0f; - break; - case BRUSH_MASK_PRESSURE_RAMP: - intensity = ups->size_pressure_value + intensity * (1.0f - ups->size_pressure_value); - break; - default: - break; - } - - return intensity; -} - -/* Unified Size / Strength / Color */ - -/* XXX: be careful about setting size and unprojected radius - * because they depend on one another - * these functions do not set the other corresponding value - * this can lead to odd behavior if size and unprojected - * radius become inconsistent. - * the biggest problem is that it isn't possible to change - * unprojected radius because a view context is not - * available. my usual solution to this is to use the - * ratio of change of the size to change the unprojected - * radius. Not completely convinced that is correct. - * In any case, a better solution is needed to prevent - * inconsistency. */ - -const float *BKE_brush_color_get(const struct Scene *scene, const struct Brush *brush) -{ - UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; - return (ups->flag & UNIFIED_PAINT_COLOR) ? ups->rgb : brush->rgb; -} - -const float *BKE_brush_secondary_color_get(const struct Scene *scene, const struct Brush *brush) -{ - UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; - return (ups->flag & UNIFIED_PAINT_COLOR) ? ups->secondary_rgb : brush->secondary_rgb; -} - -void BKE_brush_color_set(struct Scene *scene, struct Brush *brush, const float color[3]) -{ - UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; - - if (ups->flag & UNIFIED_PAINT_COLOR) { - copy_v3_v3(ups->rgb, color); - } - else { - copy_v3_v3(brush->rgb, color); - } -} - -void BKE_brush_size_set(Scene *scene, Brush *brush, int size) -{ - UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; - - /* make sure range is sane */ - CLAMP(size, 1, MAX_BRUSH_PIXEL_RADIUS); - - if (ups->flag & UNIFIED_PAINT_SIZE) { - ups->size = size; - } - else { - brush->size = size; - } -} - -int BKE_brush_size_get(const Scene *scene, const Brush *brush) -{ - UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; - int size = (ups->flag & UNIFIED_PAINT_SIZE) ? ups->size : brush->size; - - return size; -} - -bool BKE_brush_use_locked_size(const Scene *scene, const Brush *brush) -{ - const short us_flag = scene->toolsettings->unified_paint_settings.flag; - - return (us_flag & UNIFIED_PAINT_SIZE) ? (us_flag & UNIFIED_PAINT_BRUSH_LOCK_SIZE) : - (brush->flag & BRUSH_LOCK_SIZE); -} - -bool BKE_brush_use_size_pressure(const Brush *brush) -{ - return brush->flag & BRUSH_SIZE_PRESSURE; -} - -bool BKE_brush_use_alpha_pressure(const Brush *brush) -{ - return brush->flag & BRUSH_ALPHA_PRESSURE; -} - -bool BKE_brush_sculpt_has_secondary_color(const Brush *brush) -{ - return ELEM(brush->sculpt_tool, - SCULPT_TOOL_BLOB, - SCULPT_TOOL_DRAW, - SCULPT_TOOL_DRAW_SHARP, - SCULPT_TOOL_INFLATE, - SCULPT_TOOL_CLAY, - SCULPT_TOOL_CLAY_STRIPS, - SCULPT_TOOL_CLAY_THUMB, - SCULPT_TOOL_PINCH, - SCULPT_TOOL_CREASE, - SCULPT_TOOL_LAYER, - SCULPT_TOOL_FLATTEN, - SCULPT_TOOL_FILL, - SCULPT_TOOL_SCRAPE, - SCULPT_TOOL_MASK); -} - -void BKE_brush_unprojected_radius_set(Scene *scene, Brush *brush, float unprojected_radius) -{ - UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; - - if (ups->flag & UNIFIED_PAINT_SIZE) { - ups->unprojected_radius = unprojected_radius; - } - else { - brush->unprojected_radius = unprojected_radius; - } -} - -float BKE_brush_unprojected_radius_get(const Scene *scene, const Brush *brush) -{ - UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; - - return (ups->flag & UNIFIED_PAINT_SIZE) ? ups->unprojected_radius : brush->unprojected_radius; -} - -void BKE_brush_alpha_set(Scene *scene, Brush *brush, float alpha) -{ - UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; - - if (ups->flag & UNIFIED_PAINT_ALPHA) { - ups->alpha = alpha; - } - else { - brush->alpha = alpha; - } -} - -float BKE_brush_alpha_get(const Scene *scene, const Brush *brush) -{ - UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; - - return (ups->flag & UNIFIED_PAINT_ALPHA) ? ups->alpha : brush->alpha; -} - -float BKE_brush_weight_get(const Scene *scene, const Brush *brush) -{ - UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; - - return (ups->flag & UNIFIED_PAINT_WEIGHT) ? ups->weight : brush->weight; -} - -void BKE_brush_weight_set(const Scene *scene, Brush *brush, float value) -{ - UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; - - if (ups->flag & UNIFIED_PAINT_WEIGHT) { - ups->weight = value; - } - else { - brush->weight = value; - } -} - -void BKE_brush_scale_unprojected_radius(float *unprojected_radius, - int new_brush_size, - int old_brush_size) -{ - float scale = new_brush_size; - /* avoid division by zero */ - if (old_brush_size != 0) { - scale /= (float)old_brush_size; - } - (*unprojected_radius) *= scale; -} - -void BKE_brush_scale_size(int *r_brush_size, - float new_unprojected_radius, - float old_unprojected_radius) -{ - float scale = new_unprojected_radius; - /* avoid division by zero */ - if (old_unprojected_radius != 0) { - scale /= new_unprojected_radius; - } - (*r_brush_size) = (int)((float)(*r_brush_size) * scale); -} - -void BKE_brush_jitter_pos(const Scene *scene, Brush *brush, const float pos[2], float jitterpos[2]) -{ - float rand_pos[2]; - float spread; - int diameter; - - do { - rand_pos[0] = BLI_rng_get_float(brush_rng) - 0.5f; - rand_pos[1] = BLI_rng_get_float(brush_rng) - 0.5f; - } while (len_squared_v2(rand_pos) > square_f(0.5f)); - - if (brush->flag & BRUSH_ABSOLUTE_JITTER) { - diameter = 2 * brush->jitter_absolute; - spread = 1.0; - } - else { - diameter = 2 * BKE_brush_size_get(scene, brush); - spread = brush->jitter; - } - /* find random position within a circle of diameter 1 */ - jitterpos[0] = pos[0] + 2 * rand_pos[0] * diameter * spread; - jitterpos[1] = pos[1] + 2 * rand_pos[1] * diameter * spread; -} - -void BKE_brush_randomize_texture_coords(UnifiedPaintSettings *ups, bool mask) -{ - /* we multiply with brush radius as an optimization for the brush - * texture sampling functions */ - if (mask) { - ups->mask_tex_mouse[0] = BLI_rng_get_float(brush_rng) * ups->pixel_radius; - ups->mask_tex_mouse[1] = BLI_rng_get_float(brush_rng) * ups->pixel_radius; - } - else { - ups->tex_mouse[0] = BLI_rng_get_float(brush_rng) * ups->pixel_radius; - ups->tex_mouse[1] = BLI_rng_get_float(brush_rng) * ups->pixel_radius; - } -} - -float BKE_brush_curve_strength(const Brush *br, float p, const float len) -{ - float strength = 1.0f; - - if (p >= len) { - return 0; - } - - p = p / len; - p = 1.0f - p; - - switch (br->curve_preset) { - case BRUSH_CURVE_CUSTOM: - strength = BKE_curvemapping_evaluateF(br->curve, 0, 1.0f - p); - break; - case BRUSH_CURVE_SHARP: - strength = p * p; - break; - case BRUSH_CURVE_SMOOTH: - strength = 3.0f * p * p - 2.0f * p * p * p; - break; - case BRUSH_CURVE_SMOOTHER: - strength = pow3f(p) * (p * (p * 6.0f - 15.0f) + 10.0f); - break; - case BRUSH_CURVE_ROOT: - strength = sqrtf(p); - break; - case BRUSH_CURVE_LIN: - strength = p; - break; - case BRUSH_CURVE_CONSTANT: - strength = 1.0f; - break; - case BRUSH_CURVE_SPHERE: - strength = sqrtf(2 * p - p * p); - break; - case BRUSH_CURVE_POW4: - strength = p * p * p * p; - break; - case BRUSH_CURVE_INVSQUARE: - strength = p * (2.0f - p); - break; - } - - return strength; -} - -float BKE_brush_curve_strength_clamped(const Brush *br, float p, const float len) -{ - float strength = BKE_brush_curve_strength(br, p, len); - - CLAMP(strength, 0.0f, 1.0f); - - return strength; -} - -/* TODO: should probably be unified with BrushPainter stuff? */ -static bool brush_gen_texture(const Brush *br, - const int side, - const bool use_secondary, - float *rect) -{ - const MTex *mtex = (use_secondary) ? &br->mask_mtex : &br->mtex; - if (mtex->tex == NULL) { - return false; - } - - const float step = 2.0 / side; - int ix, iy; - float x, y; - - /* Do normalized canonical view coords for texture. */ - for (y = -1.0, iy = 0; iy < side; iy++, y += step) { - for (x = -1.0, ix = 0; ix < side; ix++, x += step) { - const float co[3] = {x, y, 0.0f}; - - float intensity; - float rgba_dummy[4]; - RE_texture_evaluate(mtex, co, 0, NULL, false, false, &intensity, rgba_dummy); - - rect[iy * side + ix] = intensity; - } - } - - return true; -} - -struct ImBuf *BKE_brush_gen_radial_control_imbuf(Brush *br, bool secondary, bool display_gradient) -{ - ImBuf *im = MEM_callocN(sizeof(ImBuf), "radial control texture"); - int side = 512; - int half = side / 2; - - BKE_curvemapping_init(br->curve); - im->rect_float = MEM_callocN(sizeof(float) * side * side, "radial control rect"); - im->x = im->y = side; - - const bool have_texture = brush_gen_texture(br, side, secondary, im->rect_float); - - if (display_gradient || have_texture) { - for (int i = 0; i < side; i++) { - for (int j = 0; j < side; j++) { - float magn = sqrtf(pow2f(i - half) + pow2f(j - half)); - im->rect_float[i * side + j] *= BKE_brush_curve_strength_clamped(br, magn, half); - } - } - } - - return im; -} diff --git a/source/blender/blenkernel/intern/brush.cc b/source/blender/blenkernel/intern/brush.cc new file mode 100644 index 00000000000..ffa63f9792f --- /dev/null +++ b/source/blender/blenkernel/intern/brush.cc @@ -0,0 +1,2521 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup bke + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_brush_types.h" +#include "DNA_defaults.h" +#include "DNA_gpencil_types.h" +#include "DNA_material_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_rand.h" + +#include "BLT_translation.h" + +#include "BKE_bpath.h" +#include "BKE_brush.h" +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_gpencil.h" +#include "BKE_icons.h" +#include "BKE_idtype.h" +#include "BKE_lib_id.h" +#include "BKE_lib_query.h" +#include "BKE_lib_remap.h" +#include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_paint.h" +#include "BKE_texture.h" + +#include "IMB_colormanagement.h" +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" + +#include "RE_texture.h" /* RE_texture_evaluate */ + +#include "BLO_read_write.h" + +static void brush_init_data(ID *id) +{ + Brush *brush = (Brush *)id; + BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(brush, id)); + + MEMCPY_STRUCT_AFTER(brush, DNA_struct_default_get(Brush), id); + + /* enable fake user by default */ + id_fake_user_set(&brush->id); + + /* the default alpha falloff curve */ + BKE_brush_curve_preset(brush, CURVE_PRESET_SMOOTH); +} + +static void brush_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, const int flag) +{ + Brush *brush_dst = (Brush *)id_dst; + const Brush *brush_src = (const Brush *)id_src; + if (brush_src->icon_imbuf) { + brush_dst->icon_imbuf = IMB_dupImBuf(brush_src->icon_imbuf); + } + + if ((flag & LIB_ID_COPY_NO_PREVIEW) == 0) { + BKE_previewimg_id_copy(&brush_dst->id, &brush_src->id); + } + else { + brush_dst->preview = nullptr; + } + + brush_dst->curve = BKE_curvemapping_copy(brush_src->curve); + if (brush_src->gpencil_settings != nullptr) { + brush_dst->gpencil_settings = MEM_cnew(__func__, *(brush_src->gpencil_settings)); + brush_dst->gpencil_settings->curve_sensitivity = BKE_curvemapping_copy( + brush_src->gpencil_settings->curve_sensitivity); + brush_dst->gpencil_settings->curve_strength = BKE_curvemapping_copy( + brush_src->gpencil_settings->curve_strength); + brush_dst->gpencil_settings->curve_jitter = BKE_curvemapping_copy( + brush_src->gpencil_settings->curve_jitter); + + brush_dst->gpencil_settings->curve_rand_pressure = BKE_curvemapping_copy( + brush_src->gpencil_settings->curve_rand_pressure); + brush_dst->gpencil_settings->curve_rand_strength = BKE_curvemapping_copy( + brush_src->gpencil_settings->curve_rand_strength); + brush_dst->gpencil_settings->curve_rand_uv = BKE_curvemapping_copy( + brush_src->gpencil_settings->curve_rand_uv); + brush_dst->gpencil_settings->curve_rand_hue = BKE_curvemapping_copy( + brush_src->gpencil_settings->curve_rand_hue); + brush_dst->gpencil_settings->curve_rand_saturation = BKE_curvemapping_copy( + brush_src->gpencil_settings->curve_rand_saturation); + brush_dst->gpencil_settings->curve_rand_value = BKE_curvemapping_copy( + brush_src->gpencil_settings->curve_rand_value); + } + if (brush_src->curves_sculpt_settings != nullptr) { + brush_dst->curves_sculpt_settings = MEM_cnew(__func__, *(brush_src->curves_sculpt_settings)); + } + + /* enable fake user by default */ + id_fake_user_set(&brush_dst->id); +} + +static void brush_free_data(ID *id) +{ + Brush *brush = (Brush *)id; + if (brush->icon_imbuf) { + IMB_freeImBuf(brush->icon_imbuf); + } + BKE_curvemapping_free(brush->curve); + + if (brush->gpencil_settings != nullptr) { + BKE_curvemapping_free(brush->gpencil_settings->curve_sensitivity); + BKE_curvemapping_free(brush->gpencil_settings->curve_strength); + BKE_curvemapping_free(brush->gpencil_settings->curve_jitter); + + BKE_curvemapping_free(brush->gpencil_settings->curve_rand_pressure); + BKE_curvemapping_free(brush->gpencil_settings->curve_rand_strength); + BKE_curvemapping_free(brush->gpencil_settings->curve_rand_uv); + BKE_curvemapping_free(brush->gpencil_settings->curve_rand_hue); + BKE_curvemapping_free(brush->gpencil_settings->curve_rand_saturation); + BKE_curvemapping_free(brush->gpencil_settings->curve_rand_value); + + MEM_SAFE_FREE(brush->gpencil_settings); + } + if (brush->curves_sculpt_settings != nullptr) { + MEM_freeN(brush->curves_sculpt_settings); + } + + MEM_SAFE_FREE(brush->gradient); + + BKE_previewimg_free(&(brush->preview)); +} + +static void brush_make_local(Main *bmain, ID *id, const int flags) +{ + if (!ID_IS_LINKED(id)) { + return; + } + + Brush *brush = (Brush *)id; + const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0; + + bool force_local, force_copy; + BKE_lib_id_make_local_generic_action_define(bmain, id, flags, &force_local, &force_copy); + + if (brush->clone.image) { + /* Special case: ima always local immediately. Clone image should only have one user anyway. */ + /* FIXME: Recursive calls affecting other non-embedded IDs are really bad and should be avoided + * in IDType callbacks. Higher-level ID management code usually does not expect such things and + * does not deal properly with it. */ + /* NOTE: assert below ensures that the comment above is valid, and that exception is + * acceptable for the time being. */ + BKE_lib_id_make_local(bmain, &brush->clone.image->id, 0); + BLI_assert(!ID_IS_LINKED(brush->clone.image) && brush->clone.image->id.newid == nullptr); + } + + if (force_local) { + BKE_lib_id_clear_library_data(bmain, &brush->id, flags); + BKE_lib_id_expand_local(bmain, &brush->id, flags); + + /* enable fake user by default */ + id_fake_user_set(&brush->id); + } + else if (force_copy) { + Brush *brush_new = (Brush *)BKE_id_copy(bmain, &brush->id); /* Ensures FAKE_USER is set */ + + brush_new->id.us = 0; + + /* setting newid is mandatory for complex make_lib_local logic... */ + ID_NEW_SET(brush, brush_new); + + if (!lib_local) { + BKE_libblock_remap(bmain, brush, brush_new, ID_REMAP_SKIP_INDIRECT_USAGE); + } + } +} + +static void brush_foreach_id(ID *id, LibraryForeachIDData *data) +{ + Brush *brush = (Brush *)id; + + BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, brush->toggle_brush, IDWALK_CB_NOP); + BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, brush->clone.image, IDWALK_CB_NOP); + BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, brush->paint_curve, IDWALK_CB_USER); + if (brush->gpencil_settings) { + BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, brush->gpencil_settings->material, IDWALK_CB_USER); + } + BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data, BKE_texture_mtex_foreach_id(data, &brush->mtex)); + BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data, + BKE_texture_mtex_foreach_id(data, &brush->mask_mtex)); +} + +static void brush_foreach_path(ID *id, BPathForeachPathData *bpath_data) +{ + Brush *brush = (Brush *)id; + if (brush->icon_filepath[0] != '\0') { + BKE_bpath_foreach_path_fixed_process(bpath_data, brush->icon_filepath); + } +} + +static void brush_blend_write(BlendWriter *writer, ID *id, const void *id_address) +{ + Brush *brush = (Brush *)id; + + BLO_write_id_struct(writer, Brush, id_address, &brush->id); + BKE_id_blend_write(writer, &brush->id); + + if (brush->curve) { + BKE_curvemapping_blend_write(writer, brush->curve); + } + + if (brush->gpencil_settings) { + BLO_write_struct(writer, BrushGpencilSettings, brush->gpencil_settings); + + if (brush->gpencil_settings->curve_sensitivity) { + BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_sensitivity); + } + if (brush->gpencil_settings->curve_strength) { + BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_strength); + } + if (brush->gpencil_settings->curve_jitter) { + BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_jitter); + } + if (brush->gpencil_settings->curve_rand_pressure) { + BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_pressure); + } + if (brush->gpencil_settings->curve_rand_strength) { + BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_strength); + } + if (brush->gpencil_settings->curve_rand_uv) { + BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_uv); + } + if (brush->gpencil_settings->curve_rand_hue) { + BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_hue); + } + if (brush->gpencil_settings->curve_rand_saturation) { + BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_saturation); + } + if (brush->gpencil_settings->curve_rand_value) { + BKE_curvemapping_blend_write(writer, brush->gpencil_settings->curve_rand_value); + } + } + if (brush->curves_sculpt_settings) { + BLO_write_struct(writer, BrushCurvesSculptSettings, brush->curves_sculpt_settings); + } + if (brush->gradient) { + BLO_write_struct(writer, ColorBand, brush->gradient); + } +} + +static void brush_blend_read_data(BlendDataReader *reader, ID *id) +{ + Brush *brush = (Brush *)id; + + /* Falloff curve. */ + BLO_read_data_address(reader, &brush->curve); + + BLO_read_data_address(reader, &brush->gradient); + + if (brush->curve) { + BKE_curvemapping_blend_read(reader, brush->curve); + } + else { + BKE_brush_curve_preset(brush, CURVE_PRESET_SHARP); + } + + /* grease pencil */ + BLO_read_data_address(reader, &brush->gpencil_settings); + if (brush->gpencil_settings != nullptr) { + BLO_read_data_address(reader, &brush->gpencil_settings->curve_sensitivity); + BLO_read_data_address(reader, &brush->gpencil_settings->curve_strength); + BLO_read_data_address(reader, &brush->gpencil_settings->curve_jitter); + + BLO_read_data_address(reader, &brush->gpencil_settings->curve_rand_pressure); + BLO_read_data_address(reader, &brush->gpencil_settings->curve_rand_strength); + BLO_read_data_address(reader, &brush->gpencil_settings->curve_rand_uv); + BLO_read_data_address(reader, &brush->gpencil_settings->curve_rand_hue); + BLO_read_data_address(reader, &brush->gpencil_settings->curve_rand_saturation); + BLO_read_data_address(reader, &brush->gpencil_settings->curve_rand_value); + + if (brush->gpencil_settings->curve_sensitivity) { + BKE_curvemapping_blend_read(reader, brush->gpencil_settings->curve_sensitivity); + } + + if (brush->gpencil_settings->curve_strength) { + BKE_curvemapping_blend_read(reader, brush->gpencil_settings->curve_strength); + } + + if (brush->gpencil_settings->curve_jitter) { + BKE_curvemapping_blend_read(reader, brush->gpencil_settings->curve_jitter); + } + + if (brush->gpencil_settings->curve_rand_pressure) { + BKE_curvemapping_blend_read(reader, brush->gpencil_settings->curve_rand_pressure); + } + + if (brush->gpencil_settings->curve_rand_strength) { + BKE_curvemapping_blend_read(reader, brush->gpencil_settings->curve_rand_strength); + } + + if (brush->gpencil_settings->curve_rand_uv) { + BKE_curvemapping_blend_read(reader, brush->gpencil_settings->curve_rand_uv); + } + + if (brush->gpencil_settings->curve_rand_hue) { + BKE_curvemapping_blend_read(reader, brush->gpencil_settings->curve_rand_hue); + } + + if (brush->gpencil_settings->curve_rand_saturation) { + BKE_curvemapping_blend_read(reader, brush->gpencil_settings->curve_rand_saturation); + } + + if (brush->gpencil_settings->curve_rand_value) { + BKE_curvemapping_blend_read(reader, brush->gpencil_settings->curve_rand_value); + } + } + + BLO_read_data_address(reader, &brush->curves_sculpt_settings); + + brush->preview = nullptr; + brush->icon_imbuf = nullptr; +} + +static void brush_blend_read_lib(BlendLibReader *reader, ID *id) +{ + Brush *brush = (Brush *)id; + + /* brush->(mask_)mtex.obj is ignored on purpose? */ + BLO_read_id_address(reader, brush->id.lib, &brush->mtex.tex); + BLO_read_id_address(reader, brush->id.lib, &brush->mask_mtex.tex); + BLO_read_id_address(reader, brush->id.lib, &brush->clone.image); + BLO_read_id_address(reader, brush->id.lib, &brush->toggle_brush); + BLO_read_id_address(reader, brush->id.lib, &brush->paint_curve); + + /* link default grease pencil palette */ + if (brush->gpencil_settings != nullptr) { + if (brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED) { + BLO_read_id_address(reader, brush->id.lib, &brush->gpencil_settings->material); + + if (!brush->gpencil_settings->material) { + brush->gpencil_settings->flag &= ~GP_BRUSH_MATERIAL_PINNED; + } + } + else { + brush->gpencil_settings->material = nullptr; + } + } +} + +static void brush_blend_read_expand(BlendExpander *expander, ID *id) +{ + Brush *brush = (Brush *)id; + BLO_expand(expander, brush->mtex.tex); + BLO_expand(expander, brush->mask_mtex.tex); + BLO_expand(expander, brush->clone.image); + BLO_expand(expander, brush->paint_curve); + if (brush->gpencil_settings != nullptr) { + BLO_expand(expander, brush->gpencil_settings->material); + } +} + +static int brush_undo_preserve_cb(LibraryIDLinkCallbackData *cb_data) +{ + BlendLibReader *reader = (BlendLibReader *)cb_data->user_data; + ID *id_old = *cb_data->id_pointer; + /* Old data has not been remapped to new values of the pointers, if we want to keep the old + * pointer here we need its new address. */ + ID *id_old_new = id_old != nullptr ? BLO_read_get_new_id_address(reader, id_old->lib, id_old) : + nullptr; + BLI_assert(id_old_new == nullptr || ELEM(id_old, id_old_new, id_old_new->orig_id)); + if (cb_data->cb_flag & IDWALK_CB_USER) { + id_us_plus_no_lib(id_old_new); + id_us_min(id_old); + } + *cb_data->id_pointer = id_old_new; + return IDWALK_RET_NOP; +} + +static void brush_undo_preserve(BlendLibReader *reader, ID *id_new, ID *id_old) +{ + /* Whole Brush is preserved across undo-steps. */ + BKE_lib_id_swap(nullptr, id_new, id_old); + + /* `id_new` now has content from `id_old`, we need to ensure those old ID pointers are valid. + * NOTE: Since we want to re-use all old pointers here, code is much simpler than for Scene. */ + BKE_library_foreach_ID_link(nullptr, id_new, brush_undo_preserve_cb, reader, IDWALK_NOP); + + /* NOTE: We do not swap IDProperties, as dealing with potential ID pointers in those would be + * fairly delicate. */ + SWAP(IDProperty *, id_new->properties, id_old->properties); +} + +IDTypeInfo IDType_ID_BR = { + /* id_code */ ID_BR, + /* id_filter */ FILTER_ID_BR, + /* main_listbase_index */ INDEX_ID_BR, + /* struct_size */ sizeof(Brush), + /* name */ "Brush", + /* name_plural */ "brushes", + /* translation_context */ BLT_I18NCONTEXT_ID_BRUSH, + /* flags */ IDTYPE_FLAGS_NO_ANIMDATA, + /* asset_type_info */ nullptr, + + /* init_data */ brush_init_data, + /* copy_data */ brush_copy_data, + /* free_data */ brush_free_data, + /* make_local */ brush_make_local, + /* foreach_id */ brush_foreach_id, + /* foreach_cache */ nullptr, + /* foreach_path */ brush_foreach_path, + /* owner_get */ nullptr, + + /* blend_write */ brush_blend_write, + /* blend_read_data */ brush_blend_read_data, + /* blend_read_lib */ brush_blend_read_lib, + /* blend_read_expand */ brush_blend_read_expand, + + /* blend_read_undo_preserve */ brush_undo_preserve, + + /* lib_override_apply_post */ nullptr, +}; + +static RNG *brush_rng; + +void BKE_brush_system_init(void) +{ + brush_rng = BLI_rng_new(0); + BLI_rng_srandom(brush_rng, 31415682); +} + +void BKE_brush_system_exit(void) +{ + if (brush_rng == nullptr) { + return; + } + BLI_rng_free(brush_rng); + brush_rng = nullptr; +} + +static void brush_defaults(Brush *brush) +{ + + const Brush *brush_def = DNA_struct_default_get(Brush); + +#define FROM_DEFAULT(member) memcpy(&brush->member, &brush_def->member, sizeof(brush->member)) +#define FROM_DEFAULT_PTR(member) memcpy(brush->member, brush_def->member, sizeof(brush->member)) + + FROM_DEFAULT(blend); + FROM_DEFAULT(flag); + FROM_DEFAULT(weight); + FROM_DEFAULT(size); + FROM_DEFAULT(alpha); + FROM_DEFAULT(hardness); + FROM_DEFAULT(autosmooth_factor); + FROM_DEFAULT(topology_rake_factor); + FROM_DEFAULT(crease_pinch_factor); + FROM_DEFAULT(normal_radius_factor); + FROM_DEFAULT(wet_paint_radius_factor); + FROM_DEFAULT(area_radius_factor); + FROM_DEFAULT(disconnected_distance_max); + FROM_DEFAULT(sculpt_plane); + FROM_DEFAULT(plane_offset); + FROM_DEFAULT(clone.alpha); + FROM_DEFAULT(normal_weight); + FROM_DEFAULT(fill_threshold); + FROM_DEFAULT(flag); + FROM_DEFAULT(sampling_flag); + FROM_DEFAULT_PTR(rgb); + FROM_DEFAULT_PTR(secondary_rgb); + FROM_DEFAULT(spacing); + FROM_DEFAULT(smooth_stroke_radius); + FROM_DEFAULT(smooth_stroke_factor); + FROM_DEFAULT(rate); + FROM_DEFAULT(jitter); + FROM_DEFAULT(texture_sample_bias); + FROM_DEFAULT(texture_overlay_alpha); + FROM_DEFAULT(mask_overlay_alpha); + FROM_DEFAULT(cursor_overlay_alpha); + FROM_DEFAULT(overlay_flags); + FROM_DEFAULT_PTR(add_col); + FROM_DEFAULT_PTR(sub_col); + FROM_DEFAULT(stencil_pos); + FROM_DEFAULT(stencil_dimension); + FROM_DEFAULT(mtex); + FROM_DEFAULT(mask_mtex); + +#undef FROM_DEFAULT +#undef FROM_DEFAULT_PTR +} + +/* Datablock add/copy/free/make_local */ + +Brush *BKE_brush_add(Main *bmain, const char *name, const eObjectMode ob_mode) +{ + Brush *brush = (Brush *)BKE_id_new(bmain, ID_BR, name); + + brush->ob_mode = ob_mode; + + if (ob_mode == OB_MODE_SCULPT_CURVES) { + BKE_brush_init_curves_sculpt_settings(brush); + } + + return brush; +} + +void BKE_brush_init_gpencil_settings(Brush *brush) +{ + if (brush->gpencil_settings == nullptr) { + brush->gpencil_settings = MEM_cnew("BrushGpencilSettings"); + } + + brush->gpencil_settings->draw_smoothlvl = 1; + brush->gpencil_settings->flag = 0; + brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; + brush->gpencil_settings->draw_strength = 1.0f; + brush->gpencil_settings->draw_jitter = 0.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_JITTER_PRESSURE; + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_PEN; + + /* curves */ + brush->gpencil_settings->curve_sensitivity = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); + brush->gpencil_settings->curve_strength = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); + brush->gpencil_settings->curve_jitter = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); + + brush->gpencil_settings->curve_rand_pressure = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); + brush->gpencil_settings->curve_rand_strength = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); + brush->gpencil_settings->curve_rand_uv = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); + brush->gpencil_settings->curve_rand_hue = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); + brush->gpencil_settings->curve_rand_saturation = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); + brush->gpencil_settings->curve_rand_value = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); +} + +Brush *BKE_brush_add_gpencil(Main *bmain, ToolSettings *ts, const char *name, eObjectMode mode) +{ + Paint *paint = nullptr; + Brush *brush; + switch (mode) { + case OB_MODE_PAINT_GPENCIL: { + paint = &ts->gp_paint->paint; + break; + } + case OB_MODE_SCULPT_GPENCIL: { + paint = &ts->gp_sculptpaint->paint; + break; + } + case OB_MODE_WEIGHT_GPENCIL: { + paint = &ts->gp_weightpaint->paint; + break; + } + case OB_MODE_VERTEX_GPENCIL: { + paint = &ts->gp_vertexpaint->paint; + break; + } + default: + paint = &ts->gp_paint->paint; + } + + brush = BKE_brush_add(bmain, name, mode); + + BKE_paint_brush_set(paint, brush); + id_us_min(&brush->id); + + brush->size = 3; + + /* grease pencil basic settings */ + BKE_brush_init_gpencil_settings(brush); + + /* return brush */ + return brush; +} + +bool BKE_brush_delete(Main *bmain, Brush *brush) +{ + if (brush->id.tag & LIB_TAG_INDIRECT) { + return false; + } + if (ID_REAL_USERS(brush) <= 1 && ID_EXTRA_USERS(brush) == 0 && + BKE_library_ID_is_indirectly_used(bmain, brush)) { + return false; + } + + BKE_id_delete(bmain, brush); + + return true; +} + +/* grease pencil cumapping->preset */ +typedef enum eGPCurveMappingPreset { + GPCURVE_PRESET_PENCIL = 0, + GPCURVE_PRESET_INK = 1, + GPCURVE_PRESET_INKNOISE = 2, + GPCURVE_PRESET_MARKER = 3, + GPCURVE_PRESET_CHISEL_SENSIVITY = 4, + GPCURVE_PRESET_CHISEL_STRENGTH = 5, +} eGPCurveMappingPreset; + +static void brush_gpencil_curvemap_reset(CurveMap *cuma, int tot, int preset) +{ + if (cuma->curve) { + MEM_freeN(cuma->curve); + } + + cuma->totpoint = tot; + cuma->curve = (CurveMapPoint *)MEM_callocN(cuma->totpoint * sizeof(CurveMapPoint), __func__); + + switch (preset) { + case GPCURVE_PRESET_PENCIL: + cuma->curve[0].x = 0.0f; + cuma->curve[0].y = 0.0f; + cuma->curve[1].x = 0.75115f; + cuma->curve[1].y = 0.25f; + cuma->curve[2].x = 1.0f; + cuma->curve[2].y = 1.0f; + break; + case GPCURVE_PRESET_INK: + cuma->curve[0].x = 0.0f; + cuma->curve[0].y = 0.0f; + cuma->curve[1].x = 0.63448f; + cuma->curve[1].y = 0.375f; + cuma->curve[2].x = 1.0f; + cuma->curve[2].y = 1.0f; + break; + case GPCURVE_PRESET_INKNOISE: + cuma->curve[0].x = 0.0f; + cuma->curve[0].y = 0.0f; + cuma->curve[1].x = 0.55f; + cuma->curve[1].y = 0.45f; + cuma->curve[2].x = 0.85f; + cuma->curve[2].y = 1.0f; + break; + case GPCURVE_PRESET_MARKER: + cuma->curve[0].x = 0.0f; + cuma->curve[0].y = 0.0f; + cuma->curve[1].x = 0.38f; + cuma->curve[1].y = 0.22f; + cuma->curve[2].x = 0.65f; + cuma->curve[2].y = 0.68f; + cuma->curve[3].x = 1.0f; + cuma->curve[3].y = 1.0f; + break; + case GPCURVE_PRESET_CHISEL_SENSIVITY: + cuma->curve[0].x = 0.0f; + cuma->curve[0].y = 0.0f; + cuma->curve[1].x = 0.25f; + cuma->curve[1].y = 0.40f; + cuma->curve[2].x = 1.0f; + cuma->curve[2].y = 1.0f; + break; + case GPCURVE_PRESET_CHISEL_STRENGTH: + cuma->curve[0].x = 0.0f; + cuma->curve[0].y = 0.0f; + cuma->curve[1].x = 0.31f; + cuma->curve[1].y = 0.22f; + cuma->curve[2].x = 0.61f; + cuma->curve[2].y = 0.88f; + cuma->curve[3].x = 1.0f; + cuma->curve[3].y = 1.0f; + break; + default: + break; + } + + MEM_SAFE_FREE(cuma->table); +} + +void BKE_gpencil_brush_preset_set(Main *bmain, Brush *brush, const short type) +{ +#define SMOOTH_STROKE_RADIUS 40 +#define SMOOTH_STROKE_FACTOR 0.9f +#define ACTIVE_SMOOTH 0.35f + + CurveMapping *custom_curve = nullptr; + + /* Optionally assign a material preset. */ + enum { + PRESET_MATERIAL_NONE = 0, + PRESET_MATERIAL_DOT_STROKE, + } material_preset = PRESET_MATERIAL_NONE; + + /* Set general defaults at brush level. */ + brush->smooth_stroke_radius = SMOOTH_STROKE_RADIUS; + brush->smooth_stroke_factor = SMOOTH_STROKE_FACTOR; + + brush->rgb[0] = 0.498f; + brush->rgb[1] = 1.0f; + brush->rgb[2] = 0.498f; + + brush->secondary_rgb[0] = 1.0f; + brush->secondary_rgb[1] = 1.0f; + brush->secondary_rgb[2] = 1.0f; + + brush->curve_preset = BRUSH_CURVE_SMOOTH; + + if (brush->gpencil_settings == nullptr) { + return; + } + + /* Set preset type. */ + brush->gpencil_settings->preset_type = type; + + /* Set vertex mix factor. */ + brush->gpencil_settings->vertex_mode = GPPAINT_MODE_BOTH; + brush->gpencil_settings->vertex_factor = 1.0f; + + switch (type) { + case GP_BRUSH_PRESET_AIRBRUSH: { + brush->size = 300.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 0.4f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; + + brush->gpencil_settings->input_samples = 10; + brush->gpencil_settings->active_smooth = ACTIVE_SMOOTH; + brush->gpencil_settings->draw_angle = 0.0f; + brush->gpencil_settings->draw_angle_factor = 0.0f; + brush->gpencil_settings->hardeness = 0.9f; + copy_v2_fl(brush->gpencil_settings->aspect_ratio, 1.0f); + + brush->gpencil_tool = GPAINT_TOOL_DRAW; + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_AIRBRUSH; + + zero_v3(brush->secondary_rgb); + + material_preset = PRESET_MATERIAL_DOT_STROKE; + + break; + } + case GP_BRUSH_PRESET_INK_PEN: { + + brush->size = 60.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 1.0f; + + brush->gpencil_settings->input_samples = 10; + brush->gpencil_settings->active_smooth = ACTIVE_SMOOTH; + brush->gpencil_settings->draw_angle = 0.0f; + brush->gpencil_settings->draw_angle_factor = 0.0f; + brush->gpencil_settings->hardeness = 1.0f; + copy_v2_fl(brush->gpencil_settings->aspect_ratio, 1.0f); + + brush->gpencil_settings->flag |= GP_BRUSH_GROUP_SETTINGS; + brush->gpencil_settings->draw_smoothfac = 0.1f; + brush->gpencil_settings->draw_smoothlvl = 1; + brush->gpencil_settings->draw_subdivide = 0; + brush->gpencil_settings->simplify_f = 0.002f; + + brush->gpencil_settings->draw_random_press = 0.0f; + brush->gpencil_settings->draw_jitter = 0.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_JITTER_PRESSURE; + + /* Curve. */ + custom_curve = brush->gpencil_settings->curve_sensitivity; + BKE_curvemapping_set_defaults(custom_curve, 0, 0.0f, 0.0f, 1.0f, 1.0f); + BKE_curvemapping_init(custom_curve); + brush_gpencil_curvemap_reset(custom_curve->cm, 3, GPCURVE_PRESET_INK); + + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_INK; + brush->gpencil_tool = GPAINT_TOOL_DRAW; + + zero_v3(brush->secondary_rgb); + break; + } + case GP_BRUSH_PRESET_INK_PEN_ROUGH: { + brush->size = 60.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 1.0f; + + brush->gpencil_settings->input_samples = 10; + brush->gpencil_settings->active_smooth = ACTIVE_SMOOTH; + brush->gpencil_settings->draw_angle = 0.0f; + brush->gpencil_settings->draw_angle_factor = 0.0f; + brush->gpencil_settings->hardeness = 1.0f; + copy_v2_fl(brush->gpencil_settings->aspect_ratio, 1.0f); + + brush->gpencil_settings->flag &= ~GP_BRUSH_GROUP_SETTINGS; + brush->gpencil_settings->draw_smoothfac = 0.0f; + brush->gpencil_settings->draw_smoothlvl = 2; + brush->gpencil_settings->draw_subdivide = 0; + brush->gpencil_settings->simplify_f = 0.000f; + + brush->gpencil_settings->flag |= GP_BRUSH_GROUP_RANDOM; + brush->gpencil_settings->draw_random_press = 0.6f; + brush->gpencil_settings->draw_random_strength = 0.0f; + brush->gpencil_settings->draw_jitter = 0.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_JITTER_PRESSURE; + + /* Curve. */ + custom_curve = brush->gpencil_settings->curve_sensitivity; + BKE_curvemapping_set_defaults(custom_curve, 0, 0.0f, 0.0f, 1.0f, 1.0f); + BKE_curvemapping_init(custom_curve); + brush_gpencil_curvemap_reset(custom_curve->cm, 3, GPCURVE_PRESET_INKNOISE); + + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_INKNOISE; + brush->gpencil_tool = GPAINT_TOOL_DRAW; + + zero_v3(brush->secondary_rgb); + break; + } + case GP_BRUSH_PRESET_MARKER_BOLD: { + brush->size = 150.0f; + brush->gpencil_settings->flag &= ~GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 0.3f; + + brush->gpencil_settings->input_samples = 10; + brush->gpencil_settings->active_smooth = ACTIVE_SMOOTH; + brush->gpencil_settings->draw_angle = 0.0f; + brush->gpencil_settings->draw_angle_factor = 0.0f; + brush->gpencil_settings->hardeness = 1.0f; + copy_v2_fl(brush->gpencil_settings->aspect_ratio, 1.0f); + + brush->gpencil_settings->flag |= GP_BRUSH_GROUP_SETTINGS; + brush->gpencil_settings->draw_smoothfac = 0.1f; + brush->gpencil_settings->draw_smoothlvl = 1; + brush->gpencil_settings->draw_subdivide = 0; + brush->gpencil_settings->simplify_f = 0.002f; + + brush->gpencil_settings->flag &= ~GP_BRUSH_GROUP_RANDOM; + brush->gpencil_settings->draw_random_press = 0.0f; + brush->gpencil_settings->draw_random_strength = 0.0f; + brush->gpencil_settings->draw_jitter = 0.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_JITTER_PRESSURE; + + /* Curve. */ + custom_curve = brush->gpencil_settings->curve_sensitivity; + BKE_curvemapping_set_defaults(custom_curve, 0, 0.0f, 0.0f, 1.0f, 1.0f); + BKE_curvemapping_init(custom_curve); + brush_gpencil_curvemap_reset(custom_curve->cm, 4, GPCURVE_PRESET_MARKER); + + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_MARKER; + brush->gpencil_tool = GPAINT_TOOL_DRAW; + + zero_v3(brush->secondary_rgb); + break; + } + case GP_BRUSH_PRESET_MARKER_CHISEL: { + brush->size = 150.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 1.0f; + + brush->gpencil_settings->input_samples = 10; + brush->gpencil_settings->active_smooth = 0.3f; + brush->gpencil_settings->draw_angle = DEG2RAD(35.0f); + brush->gpencil_settings->draw_angle_factor = 0.5f; + brush->gpencil_settings->hardeness = 1.0f; + copy_v2_fl(brush->gpencil_settings->aspect_ratio, 1.0f); + + brush->gpencil_settings->flag |= GP_BRUSH_GROUP_SETTINGS; + brush->gpencil_settings->draw_smoothfac = 0.0f; + brush->gpencil_settings->draw_smoothlvl = 1; + brush->gpencil_settings->draw_subdivide = 0; + brush->gpencil_settings->simplify_f = 0.002f; + + brush->gpencil_settings->flag &= ~GP_BRUSH_GROUP_RANDOM; + brush->gpencil_settings->draw_random_press = 0.0f; + brush->gpencil_settings->draw_jitter = 0.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_JITTER_PRESSURE; + + /* Curve. */ + custom_curve = brush->gpencil_settings->curve_sensitivity; + BKE_curvemapping_set_defaults(custom_curve, 0, 0.0f, 0.0f, 1.0f, 1.0f); + BKE_curvemapping_init(custom_curve); + brush_gpencil_curvemap_reset(custom_curve->cm, 3, GPCURVE_PRESET_CHISEL_SENSIVITY); + + custom_curve = brush->gpencil_settings->curve_strength; + BKE_curvemapping_set_defaults(custom_curve, 0, 0.0f, 0.0f, 1.0f, 1.0f); + BKE_curvemapping_init(custom_curve); + brush_gpencil_curvemap_reset(custom_curve->cm, 4, GPCURVE_PRESET_CHISEL_STRENGTH); + + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_CHISEL; + brush->gpencil_tool = GPAINT_TOOL_DRAW; + + zero_v3(brush->secondary_rgb); + break; + } + case GP_BRUSH_PRESET_PEN: { + brush->size = 25.0f; + brush->gpencil_settings->flag &= ~GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 1.0f; + brush->gpencil_settings->flag &= ~GP_BRUSH_USE_STRENGTH_PRESSURE; + + brush->gpencil_settings->input_samples = 10; + brush->gpencil_settings->active_smooth = ACTIVE_SMOOTH; + brush->gpencil_settings->draw_angle = 0.0f; + brush->gpencil_settings->draw_angle_factor = 0.0f; + brush->gpencil_settings->hardeness = 1.0f; + copy_v2_fl(brush->gpencil_settings->aspect_ratio, 1.0f); + + brush->gpencil_settings->flag |= GP_BRUSH_GROUP_SETTINGS; + brush->gpencil_settings->draw_smoothfac = 0.0f; + brush->gpencil_settings->draw_smoothlvl = 1; + brush->gpencil_settings->draw_subdivide = 1; + brush->gpencil_settings->simplify_f = 0.002f; + + brush->gpencil_settings->draw_random_press = 0.0f; + brush->gpencil_settings->draw_random_strength = 0.0f; + brush->gpencil_settings->draw_jitter = 0.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_JITTER_PRESSURE; + + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_PEN; + brush->gpencil_tool = GPAINT_TOOL_DRAW; + + zero_v3(brush->secondary_rgb); + break; + } + case GP_BRUSH_PRESET_PENCIL_SOFT: { + brush->size = 80.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 0.4f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; + + brush->gpencil_settings->input_samples = 10; + brush->gpencil_settings->active_smooth = ACTIVE_SMOOTH; + brush->gpencil_settings->draw_angle = 0.0f; + brush->gpencil_settings->draw_angle_factor = 0.0f; + brush->gpencil_settings->hardeness = 0.8f; + copy_v2_fl(brush->gpencil_settings->aspect_ratio, 1.0f); + + brush->gpencil_settings->flag |= GP_BRUSH_GROUP_SETTINGS; + brush->gpencil_settings->draw_smoothfac = 0.0f; + brush->gpencil_settings->draw_smoothlvl = 1; + brush->gpencil_settings->draw_subdivide = 0; + brush->gpencil_settings->simplify_f = 0.000f; + + brush->gpencil_settings->draw_random_press = 0.0f; + brush->gpencil_settings->draw_random_strength = 0.0f; + brush->gpencil_settings->draw_jitter = 0.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_JITTER_PRESSURE; + + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_PENCIL; + brush->gpencil_tool = GPAINT_TOOL_DRAW; + + zero_v3(brush->secondary_rgb); + + material_preset = PRESET_MATERIAL_DOT_STROKE; + + break; + } + case GP_BRUSH_PRESET_PENCIL: { + brush->size = 20.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 0.6f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; + + brush->gpencil_settings->input_samples = 10; + brush->gpencil_settings->active_smooth = ACTIVE_SMOOTH; + brush->gpencil_settings->draw_angle = 0.0f; + brush->gpencil_settings->draw_angle_factor = 0.0f; + brush->gpencil_settings->hardeness = 1.0f; + copy_v2_fl(brush->gpencil_settings->aspect_ratio, 1.0f); + + brush->gpencil_settings->flag |= GP_BRUSH_GROUP_SETTINGS; + brush->gpencil_settings->draw_smoothfac = 0.0f; + brush->gpencil_settings->draw_smoothlvl = 1; + brush->gpencil_settings->draw_subdivide = 0; + brush->gpencil_settings->simplify_f = 0.002f; + + brush->gpencil_settings->draw_random_press = 0.0f; + brush->gpencil_settings->draw_jitter = 0.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_JITTER_PRESSURE; + + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_PENCIL; + brush->gpencil_tool = GPAINT_TOOL_DRAW; + + zero_v3(brush->secondary_rgb); + break; + } + case GP_BRUSH_PRESET_FILL_AREA: { + brush->size = 5.0f; + + brush->gpencil_settings->fill_leak = 3; + brush->gpencil_settings->fill_threshold = 0.1f; + brush->gpencil_settings->fill_simplylvl = 1; + brush->gpencil_settings->fill_factor = 1.0f; + + brush->gpencil_settings->draw_strength = 1.0f; + brush->gpencil_settings->hardeness = 1.0f; + copy_v2_fl(brush->gpencil_settings->aspect_ratio, 1.0f); + brush->gpencil_settings->draw_smoothfac = 0.1f; + brush->gpencil_settings->draw_smoothlvl = 1; + brush->gpencil_settings->draw_subdivide = 1; + brush->gpencil_settings->dilate_pixels = 1; + + brush->gpencil_settings->flag |= GP_BRUSH_FILL_SHOW_EXTENDLINES; + + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_FILL; + brush->gpencil_tool = GPAINT_TOOL_FILL; + brush->gpencil_settings->vertex_mode = GPPAINT_MODE_FILL; + + zero_v3(brush->secondary_rgb); + break; + } + case GP_BRUSH_PRESET_ERASER_SOFT: { + brush->size = 30.0f; + brush->gpencil_settings->draw_strength = 0.5f; + brush->gpencil_settings->flag |= GP_BRUSH_DEFAULT_ERASER; + brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; + brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_ERASE_SOFT; + brush->gpencil_tool = GPAINT_TOOL_ERASE; + brush->gpencil_settings->eraser_mode = GP_BRUSH_ERASER_SOFT; + brush->gpencil_settings->era_strength_f = 100.0f; + brush->gpencil_settings->era_thickness_f = 10.0f; + + break; + } + case GP_BRUSH_PRESET_ERASER_HARD: { + brush->size = 30.0f; + brush->gpencil_settings->draw_strength = 1.0f; + brush->gpencil_settings->eraser_mode = GP_BRUSH_ERASER_SOFT; + brush->gpencil_settings->era_strength_f = 100.0f; + brush->gpencil_settings->era_thickness_f = 50.0f; + + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_ERASE_HARD; + brush->gpencil_tool = GPAINT_TOOL_ERASE; + + break; + } + case GP_BRUSH_PRESET_ERASER_POINT: { + brush->size = 30.0f; + brush->gpencil_settings->eraser_mode = GP_BRUSH_ERASER_HARD; + + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_ERASE_HARD; + brush->gpencil_tool = GPAINT_TOOL_ERASE; + + break; + } + case GP_BRUSH_PRESET_ERASER_STROKE: { + brush->size = 30.0f; + brush->gpencil_settings->eraser_mode = GP_BRUSH_ERASER_STROKE; + + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_ERASE_STROKE; + brush->gpencil_tool = GPAINT_TOOL_ERASE; + + break; + } + case GP_BRUSH_PRESET_TINT: { + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_TINT; + brush->gpencil_tool = GPAINT_TOOL_TINT; + + brush->size = 25.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 0.8f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; + + zero_v3(brush->secondary_rgb); + break; + } + case GP_BRUSH_PRESET_VERTEX_DRAW: { + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_VERTEX_DRAW; + brush->gpencil_vertex_tool = GPVERTEX_TOOL_DRAW; + + brush->size = 25.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 0.8f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; + + zero_v3(brush->secondary_rgb); + break; + } + case GP_BRUSH_PRESET_VERTEX_BLUR: { + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_VERTEX_BLUR; + brush->gpencil_vertex_tool = GPVERTEX_TOOL_BLUR; + + brush->size = 25.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 0.8f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; + + zero_v3(brush->secondary_rgb); + break; + } + case GP_BRUSH_PRESET_VERTEX_AVERAGE: { + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_VERTEX_AVERAGE; + brush->gpencil_vertex_tool = GPVERTEX_TOOL_AVERAGE; + + brush->size = 25.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 0.8f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; + + zero_v3(brush->secondary_rgb); + break; + } + case GP_BRUSH_PRESET_VERTEX_SMEAR: { + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_VERTEX_SMEAR; + brush->gpencil_vertex_tool = GPVERTEX_TOOL_SMEAR; + + brush->size = 25.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 0.8f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; + + zero_v3(brush->secondary_rgb); + break; + } + case GP_BRUSH_PRESET_VERTEX_REPLACE: { + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_VERTEX_REPLACE; + brush->gpencil_vertex_tool = GPVERTEX_TOOL_REPLACE; + + brush->size = 25.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 0.8f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; + + zero_v3(brush->secondary_rgb); + break; + } + case GP_BRUSH_PRESET_SMOOTH_STROKE: { + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_GPBRUSH_SMOOTH; + brush->gpencil_sculpt_tool = GPSCULPT_TOOL_SMOOTH; + + brush->size = 25.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 0.3f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; + brush->gpencil_settings->sculpt_flag = GP_SCULPT_FLAGMODE_APPLY_THICKNESS; + brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION; + + break; + } + case GP_BRUSH_PRESET_STRENGTH_STROKE: { + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_GPBRUSH_STRENGTH; + brush->gpencil_sculpt_tool = GPSCULPT_TOOL_STRENGTH; + + brush->size = 25.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 0.3f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; + brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION; + + break; + } + case GP_BRUSH_PRESET_THICKNESS_STROKE: { + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_GPBRUSH_THICKNESS; + brush->gpencil_sculpt_tool = GPSCULPT_TOOL_THICKNESS; + + brush->size = 25.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 0.5f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; + brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION; + + break; + } + case GP_BRUSH_PRESET_GRAB_STROKE: { + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_GPBRUSH_GRAB; + brush->gpencil_sculpt_tool = GPSCULPT_TOOL_GRAB; + brush->gpencil_settings->flag &= ~GP_BRUSH_USE_PRESSURE; + + brush->size = 25.0f; + + brush->gpencil_settings->draw_strength = 0.3f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; + brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION; + + break; + } + case GP_BRUSH_PRESET_PUSH_STROKE: { + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_GPBRUSH_PUSH; + brush->gpencil_sculpt_tool = GPSCULPT_TOOL_PUSH; + + brush->size = 25.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 0.3f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; + brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION; + + break; + } + case GP_BRUSH_PRESET_TWIST_STROKE: { + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_GPBRUSH_TWIST; + brush->gpencil_sculpt_tool = GPSCULPT_TOOL_TWIST; + + brush->size = 50.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 0.3f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; + brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION; + + break; + } + case GP_BRUSH_PRESET_PINCH_STROKE: { + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_GPBRUSH_PINCH; + brush->gpencil_sculpt_tool = GPSCULPT_TOOL_PINCH; + + brush->size = 50.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 0.5f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; + brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION; + + break; + } + case GP_BRUSH_PRESET_RANDOMIZE_STROKE: { + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_GPBRUSH_RANDOMIZE; + brush->gpencil_sculpt_tool = GPSCULPT_TOOL_RANDOMIZE; + + brush->size = 25.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 0.5f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; + brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION; + + break; + } + case GP_BRUSH_PRESET_CLONE_STROKE: { + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_GPBRUSH_CLONE; + brush->gpencil_sculpt_tool = GPSCULPT_TOOL_CLONE; + brush->gpencil_settings->flag &= ~GP_BRUSH_USE_PRESSURE; + + brush->size = 25.0f; + + brush->gpencil_settings->draw_strength = 1.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; + brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION; + + break; + } + case GP_BRUSH_PRESET_DRAW_WEIGHT: { + brush->gpencil_settings->icon_id = GP_BRUSH_ICON_GPBRUSH_WEIGHT; + brush->gpencil_weight_tool = GPWEIGHT_TOOL_DRAW; + + brush->size = 25.0f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_PRESSURE; + + brush->gpencil_settings->draw_strength = 0.8f; + brush->gpencil_settings->flag |= GP_BRUSH_USE_STRENGTH_PRESSURE; + brush->gpencil_settings->sculpt_mode_flag |= GP_SCULPT_FLAGMODE_APPLY_POSITION; + + break; + } + default: + break; + } + + switch (material_preset) { + case PRESET_MATERIAL_NONE: + break; + case PRESET_MATERIAL_DOT_STROKE: { + /* Create and link Black Dots material to brush. + * This material is required because the brush uses the material + * to define how the stroke is drawn. */ + const char *ma_id = "Dots Stroke"; + Material *ma = (Material *)BLI_findstring(&bmain->materials, ma_id, offsetof(ID, name) + 2); + if (ma == nullptr) { + ma = BKE_gpencil_material_add(bmain, ma_id); + ma->gp_style->mode = GP_MATERIAL_MODE_DOT; + BLI_assert(ma->id.us == 1); + id_us_min(&ma->id); + } + + BKE_gpencil_brush_material_set(brush, ma); + + /* Pin the material to the brush. */ + brush->gpencil_settings->flag |= GP_BRUSH_MATERIAL_PINNED; + break; + } + } +} + +static Brush *gpencil_brush_ensure( + Main *bmain, ToolSettings *ts, const char *brush_name, eObjectMode mode, bool *r_new) +{ + *r_new = false; + Brush *brush = (Brush *)BLI_findstring(&bmain->brushes, brush_name, offsetof(ID, name) + 2); + + /* If the brush exist, but the type is not GPencil or the mode is wrong, create a new one. */ + if ((brush != nullptr) && ((brush->gpencil_settings == nullptr) || (brush->ob_mode != mode))) { + brush = nullptr; + } + + if (brush == nullptr) { + brush = BKE_brush_add_gpencil(bmain, ts, brush_name, mode); + *r_new = true; + } + + if (brush->gpencil_settings == nullptr) { + BKE_brush_init_gpencil_settings(brush); + } + + return brush; +} + +void BKE_brush_gpencil_paint_presets(Main *bmain, ToolSettings *ts, const bool reset) +{ + bool r_new = false; + + Paint *paint = &ts->gp_paint->paint; + Brush *brush_prev = paint->brush; + Brush *brush, *deft_draw; + /* Airbrush brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Airbrush", OB_MODE_PAINT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_AIRBRUSH); + } + + /* Ink Pen brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Ink Pen", OB_MODE_PAINT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_INK_PEN); + } + + /* Ink Pen Rough brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Ink Pen Rough", OB_MODE_PAINT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_INK_PEN_ROUGH); + } + + /* Marker Bold brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Marker Bold", OB_MODE_PAINT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_MARKER_BOLD); + } + + /* Marker Chisel brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Marker Chisel", OB_MODE_PAINT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_MARKER_CHISEL); + } + + /* Pen brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Pen", OB_MODE_PAINT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_PEN); + } + + /* Pencil Soft brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Pencil Soft", OB_MODE_PAINT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_PENCIL_SOFT); + } + + /* Pencil brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Pencil", OB_MODE_PAINT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_PENCIL); + } + deft_draw = brush; /* save default brush. */ + + /* Fill brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Fill Area", OB_MODE_PAINT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_FILL_AREA); + } + + /* Soft Eraser brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Eraser Soft", OB_MODE_PAINT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_ERASER_SOFT); + } + + /* Hard Eraser brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Eraser Hard", OB_MODE_PAINT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_ERASER_HARD); + } + + /* Point Eraser brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Eraser Point", OB_MODE_PAINT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_ERASER_POINT); + } + + /* Stroke Eraser brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Eraser Stroke", OB_MODE_PAINT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_ERASER_STROKE); + } + + /* Tint brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Tint", OB_MODE_PAINT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_TINT); + } + + /* Set default Draw brush. */ + if ((reset == false) && (brush_prev != nullptr)) { + BKE_paint_brush_set(paint, brush_prev); + } + else { + BKE_paint_brush_set(paint, deft_draw); + } +} + +void BKE_brush_gpencil_vertex_presets(Main *bmain, ToolSettings *ts, const bool reset) +{ + bool r_new = false; + + Paint *vertexpaint = &ts->gp_vertexpaint->paint; + Brush *brush_prev = vertexpaint->brush; + Brush *brush, *deft_vertex; + /* Vertex Draw brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Vertex Draw", OB_MODE_VERTEX_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_VERTEX_DRAW); + } + deft_vertex = brush; /* save default brush. */ + + /* Vertex Blur brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Vertex Blur", OB_MODE_VERTEX_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_VERTEX_BLUR); + } + /* Vertex Average brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Vertex Average", OB_MODE_VERTEX_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_VERTEX_AVERAGE); + } + /* Vertex Smear brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Vertex Smear", OB_MODE_VERTEX_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_VERTEX_SMEAR); + } + /* Vertex Replace brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Vertex Replace", OB_MODE_VERTEX_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_VERTEX_REPLACE); + } + + /* Set default Vertex brush. */ + if (reset || brush_prev == nullptr) { + BKE_paint_brush_set(vertexpaint, deft_vertex); + } + else { + if (brush_prev != nullptr) { + BKE_paint_brush_set(vertexpaint, brush_prev); + } + } +} + +void BKE_brush_gpencil_sculpt_presets(Main *bmain, ToolSettings *ts, const bool reset) +{ + bool r_new = false; + + Paint *sculptpaint = &ts->gp_sculptpaint->paint; + Brush *brush_prev = sculptpaint->brush; + Brush *brush, *deft_sculpt; + + /* Smooth brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Smooth Stroke", OB_MODE_SCULPT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_SMOOTH_STROKE); + } + deft_sculpt = brush; + + /* Strength brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Strength Stroke", OB_MODE_SCULPT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_STRENGTH_STROKE); + } + + /* Thickness brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Thickness Stroke", OB_MODE_SCULPT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_THICKNESS_STROKE); + } + + /* Grab brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Grab Stroke", OB_MODE_SCULPT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_GRAB_STROKE); + } + + /* Push brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Push Stroke", OB_MODE_SCULPT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_PUSH_STROKE); + } + + /* Twist brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Twist Stroke", OB_MODE_SCULPT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_TWIST_STROKE); + } + + /* Pinch brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Pinch Stroke", OB_MODE_SCULPT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_PINCH_STROKE); + } + + /* Randomize brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Randomize Stroke", OB_MODE_SCULPT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_RANDOMIZE_STROKE); + } + + /* Clone brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Clone Stroke", OB_MODE_SCULPT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_CLONE_STROKE); + } + + /* Set default brush. */ + if (reset || brush_prev == nullptr) { + BKE_paint_brush_set(sculptpaint, deft_sculpt); + } + else { + if (brush_prev != nullptr) { + BKE_paint_brush_set(sculptpaint, brush_prev); + } + } +} + +void BKE_brush_gpencil_weight_presets(Main *bmain, ToolSettings *ts, const bool reset) +{ + bool r_new = false; + + Paint *weightpaint = &ts->gp_weightpaint->paint; + Brush *brush_prev = weightpaint->brush; + Brush *brush, *deft_weight; + /* Vertex Draw brush. */ + brush = gpencil_brush_ensure(bmain, ts, "Draw Weight", OB_MODE_WEIGHT_GPENCIL, &r_new); + if ((reset) || (r_new)) { + BKE_gpencil_brush_preset_set(bmain, brush, GP_BRUSH_PRESET_DRAW_WEIGHT); + } + deft_weight = brush; /* save default brush. */ + + /* Set default brush. */ + if (reset || brush_prev == nullptr) { + BKE_paint_brush_set(weightpaint, deft_weight); + } + else { + if (brush_prev != nullptr) { + BKE_paint_brush_set(weightpaint, brush_prev); + } + } +} + +void BKE_brush_init_curves_sculpt_settings(Brush *brush) +{ + if (brush->curves_sculpt_settings == nullptr) { + brush->curves_sculpt_settings = MEM_cnew(__func__); + } + BrushCurvesSculptSettings *settings = brush->curves_sculpt_settings; + settings->add_amount = 1; + settings->points_per_curve = 8; + settings->minimum_length = 0.01f; + settings->curve_length = 0.3f; + settings->density_add_attempts = 100; +} + +struct Brush *BKE_brush_first_search(struct Main *bmain, const eObjectMode ob_mode) +{ + LISTBASE_FOREACH (Brush *, brush, &bmain->brushes) { + if (brush->ob_mode & ob_mode) { + return brush; + } + } + return nullptr; +} + +void BKE_brush_debug_print_state(Brush *br) +{ + /* create a fake brush and set it to the defaults */ + Brush def = {{nullptr}}; + brush_defaults(&def); + +#define BR_TEST(field, t) \ + if (br->field != def.field) { \ + printf("br->" #field " = %" #t ";\n", br->field); \ + } \ + ((void)0) + +#define BR_TEST_FLAG(_f) \ + if ((br->flag & _f) && !(def.flag & _f)) { \ + printf("br->flag |= " #_f ";\n"); \ + } \ + else if (!(br->flag & _f) && (def.flag & _f)) { \ + printf("br->flag &= ~" #_f ";\n"); \ + } \ + ((void)0) + +#define BR_TEST_FLAG_OVERLAY(_f) \ + if ((br->overlay_flags & _f) && !(def.overlay_flags & _f)) { \ + printf("br->overlay_flags |= " #_f ";\n"); \ + } \ + else if (!(br->overlay_flags & _f) && (def.overlay_flags & _f)) { \ + printf("br->overlay_flags &= ~" #_f ";\n"); \ + } \ + ((void)0) + + /* print out any non-default brush state */ + BR_TEST(normal_weight, f); + + BR_TEST(blend, d); + BR_TEST(size, d); + + /* br->flag */ + BR_TEST_FLAG(BRUSH_AIRBRUSH); + BR_TEST_FLAG(BRUSH_ALPHA_PRESSURE); + BR_TEST_FLAG(BRUSH_SIZE_PRESSURE); + BR_TEST_FLAG(BRUSH_JITTER_PRESSURE); + BR_TEST_FLAG(BRUSH_SPACING_PRESSURE); + BR_TEST_FLAG(BRUSH_ANCHORED); + BR_TEST_FLAG(BRUSH_DIR_IN); + BR_TEST_FLAG(BRUSH_SPACE); + BR_TEST_FLAG(BRUSH_SMOOTH_STROKE); + BR_TEST_FLAG(BRUSH_PERSISTENT); + BR_TEST_FLAG(BRUSH_ACCUMULATE); + BR_TEST_FLAG(BRUSH_LOCK_ALPHA); + BR_TEST_FLAG(BRUSH_ORIGINAL_NORMAL); + BR_TEST_FLAG(BRUSH_OFFSET_PRESSURE); + BR_TEST_FLAG(BRUSH_SPACE_ATTEN); + BR_TEST_FLAG(BRUSH_ADAPTIVE_SPACE); + BR_TEST_FLAG(BRUSH_LOCK_SIZE); + BR_TEST_FLAG(BRUSH_EDGE_TO_EDGE); + BR_TEST_FLAG(BRUSH_DRAG_DOT); + BR_TEST_FLAG(BRUSH_INVERSE_SMOOTH_PRESSURE); + BR_TEST_FLAG(BRUSH_PLANE_TRIM); + BR_TEST_FLAG(BRUSH_FRONTFACE); + BR_TEST_FLAG(BRUSH_CUSTOM_ICON); + + BR_TEST_FLAG_OVERLAY(BRUSH_OVERLAY_CURSOR); + BR_TEST_FLAG_OVERLAY(BRUSH_OVERLAY_PRIMARY); + BR_TEST_FLAG_OVERLAY(BRUSH_OVERLAY_SECONDARY); + BR_TEST_FLAG_OVERLAY(BRUSH_OVERLAY_CURSOR_OVERRIDE_ON_STROKE); + BR_TEST_FLAG_OVERLAY(BRUSH_OVERLAY_PRIMARY_OVERRIDE_ON_STROKE); + BR_TEST_FLAG_OVERLAY(BRUSH_OVERLAY_SECONDARY_OVERRIDE_ON_STROKE); + + BR_TEST(jitter, f); + BR_TEST(spacing, d); + BR_TEST(smooth_stroke_radius, d); + BR_TEST(smooth_stroke_factor, f); + BR_TEST(rate, f); + + BR_TEST(alpha, f); + + BR_TEST(sculpt_plane, d); + + BR_TEST(plane_offset, f); + + BR_TEST(autosmooth_factor, f); + + BR_TEST(topology_rake_factor, f); + + BR_TEST(crease_pinch_factor, f); + + BR_TEST(plane_trim, f); + + BR_TEST(texture_sample_bias, f); + BR_TEST(texture_overlay_alpha, d); + + BR_TEST(add_col[0], f); + BR_TEST(add_col[1], f); + BR_TEST(add_col[2], f); + BR_TEST(add_col[3], f); + BR_TEST(sub_col[0], f); + BR_TEST(sub_col[1], f); + BR_TEST(sub_col[2], f); + BR_TEST(sub_col[3], f); + + printf("\n"); + +#undef BR_TEST +#undef BR_TEST_FLAG +} + +void BKE_brush_sculpt_reset(Brush *br) +{ + /* enable this to see any non-default + * settings used by a brush: */ + // BKE_brush_debug_print_state(br); + + brush_defaults(br); + BKE_brush_curve_preset(br, CURVE_PRESET_SMOOTH); + + /* Use the curve presets by default */ + br->curve_preset = BRUSH_CURVE_SMOOTH; + + /* Note that sculpt defaults where set when 0.5 was the default (now it's 1.0) + * assign this so logic below can remain the same. */ + br->alpha = 0.5f; + + /* Brush settings */ + switch (br->sculpt_tool) { + case SCULPT_TOOL_DRAW_SHARP: + br->flag |= BRUSH_DIR_IN; + br->curve_preset = BRUSH_CURVE_POW4; + br->spacing = 5; + break; + case SCULPT_TOOL_DISPLACEMENT_ERASER: + br->curve_preset = BRUSH_CURVE_SMOOTHER; + br->spacing = 10; + br->alpha = 1.0f; + break; + case SCULPT_TOOL_SLIDE_RELAX: + br->spacing = 10; + br->alpha = 1.0f; + br->slide_deform_type = BRUSH_SLIDE_DEFORM_DRAG; + break; + case SCULPT_TOOL_CLAY: + br->flag |= BRUSH_SIZE_PRESSURE; + br->spacing = 3; + br->autosmooth_factor = 0.25f; + br->normal_radius_factor = 0.75f; + br->hardness = 0.65f; + break; + case SCULPT_TOOL_CLAY_THUMB: + br->alpha = 0.5f; + br->normal_radius_factor = 1.0f; + br->spacing = 6; + br->hardness = 0.5f; + br->flag |= BRUSH_SIZE_PRESSURE; + br->flag &= ~BRUSH_SPACE_ATTEN; + break; + case SCULPT_TOOL_CLAY_STRIPS: + br->flag |= BRUSH_ACCUMULATE | BRUSH_SIZE_PRESSURE; + br->flag &= ~BRUSH_SPACE_ATTEN; + br->alpha = 0.6f; + br->spacing = 5; + br->normal_radius_factor = 1.55f; + br->tip_roundness = 0.18f; + br->curve_preset = BRUSH_CURVE_SMOOTHER; + break; + case SCULPT_TOOL_MULTIPLANE_SCRAPE: + br->flag2 |= BRUSH_MULTIPLANE_SCRAPE_DYNAMIC | BRUSH_MULTIPLANE_SCRAPE_PLANES_PREVIEW; + br->alpha = 0.7f; + br->normal_radius_factor = 0.70f; + br->multiplane_scrape_angle = 60; + br->curve_preset = BRUSH_CURVE_SMOOTH; + br->spacing = 5; + break; + case SCULPT_TOOL_CREASE: + br->flag |= BRUSH_DIR_IN; + br->alpha = 0.25; + break; + case SCULPT_TOOL_SCRAPE: + case SCULPT_TOOL_FILL: + br->alpha = 0.7f; + br->area_radius_factor = 0.5f; + br->spacing = 7; + br->flag |= BRUSH_ACCUMULATE; + br->flag |= BRUSH_INVERT_TO_SCRAPE_FILL; + break; + case SCULPT_TOOL_ROTATE: + br->alpha = 1.0; + break; + case SCULPT_TOOL_SMOOTH: + br->flag &= ~BRUSH_SPACE_ATTEN; + br->spacing = 5; + br->alpha = 0.7f; + br->surface_smooth_shape_preservation = 0.5f; + br->surface_smooth_current_vertex = 0.5f; + br->surface_smooth_iterations = 4; + break; + case SCULPT_TOOL_SNAKE_HOOK: + br->alpha = 1.0f; + br->rake_factor = 1.0f; + break; + case SCULPT_TOOL_THUMB: + br->size = 75; + br->flag &= ~BRUSH_ALPHA_PRESSURE; + br->flag &= ~BRUSH_SPACE; + br->flag &= ~BRUSH_SPACE_ATTEN; + break; + case SCULPT_TOOL_ELASTIC_DEFORM: + br->elastic_deform_volume_preservation = 0.4f; + br->elastic_deform_type = BRUSH_ELASTIC_DEFORM_GRAB_TRISCALE; + br->flag &= ~BRUSH_ALPHA_PRESSURE; + br->flag &= ~BRUSH_SPACE; + br->flag &= ~BRUSH_SPACE_ATTEN; + break; + case SCULPT_TOOL_POSE: + br->pose_smooth_iterations = 4; + br->pose_ik_segments = 1; + br->flag2 |= BRUSH_POSE_IK_ANCHORED | BRUSH_USE_CONNECTED_ONLY; + br->flag &= ~BRUSH_ALPHA_PRESSURE; + br->flag &= ~BRUSH_SPACE; + br->flag &= ~BRUSH_SPACE_ATTEN; + break; + case SCULPT_TOOL_BOUNDARY: + br->flag &= ~BRUSH_ALPHA_PRESSURE; + br->flag &= ~BRUSH_SPACE; + br->flag &= ~BRUSH_SPACE_ATTEN; + br->curve_preset = BRUSH_CURVE_CONSTANT; + break; + case SCULPT_TOOL_DRAW_FACE_SETS: + br->alpha = 0.5f; + br->flag &= ~BRUSH_ALPHA_PRESSURE; + br->flag &= ~BRUSH_SPACE; + br->flag &= ~BRUSH_SPACE_ATTEN; + break; + case SCULPT_TOOL_GRAB: + br->alpha = 0.4f; + br->size = 75; + br->flag &= ~BRUSH_ALPHA_PRESSURE; + br->flag &= ~BRUSH_SPACE; + br->flag &= ~BRUSH_SPACE_ATTEN; + break; + case SCULPT_TOOL_CLOTH: + br->cloth_mass = 1.0f; + br->cloth_damping = 0.01f; + br->cloth_sim_limit = 2.5f; + br->cloth_sim_falloff = 0.75f; + br->cloth_deform_type = BRUSH_CLOTH_DEFORM_DRAG; + br->flag &= ~(BRUSH_ALPHA_PRESSURE | BRUSH_SIZE_PRESSURE); + break; + case SCULPT_TOOL_LAYER: + br->flag &= ~BRUSH_SPACE_ATTEN; + br->hardness = 0.35f; + br->alpha = 1.0f; + br->height = 0.05f; + break; + case SCULPT_TOOL_PAINT: + br->hardness = 0.4f; + br->spacing = 10; + br->alpha = 0.6f; + br->flow = 1.0f; + br->tip_scale_x = 1.0f; + br->tip_roundness = 1.0f; + br->density = 1.0f; + br->flag &= ~BRUSH_SPACE_ATTEN; + copy_v3_fl(br->rgb, 1.0f); + zero_v3(br->secondary_rgb); + break; + case SCULPT_TOOL_SMEAR: + br->alpha = 1.0f; + br->spacing = 5; + br->flag &= ~BRUSH_ALPHA_PRESSURE; + br->flag &= ~BRUSH_SPACE_ATTEN; + br->curve_preset = BRUSH_CURVE_SPHERE; + break; + case SCULPT_TOOL_DISPLACEMENT_SMEAR: + br->alpha = 1.0f; + br->spacing = 5; + br->hardness = 0.7f; + br->flag &= ~BRUSH_ALPHA_PRESSURE; + br->flag &= ~BRUSH_SPACE_ATTEN; + br->curve_preset = BRUSH_CURVE_SMOOTHER; + break; + default: + break; + } + + /* Cursor colors */ + + /* Default Alpha */ + br->add_col[3] = 0.90f; + br->sub_col[3] = 0.90f; + + switch (br->sculpt_tool) { + case SCULPT_TOOL_DRAW: + case SCULPT_TOOL_DRAW_SHARP: + case SCULPT_TOOL_CLAY: + case SCULPT_TOOL_CLAY_STRIPS: + case SCULPT_TOOL_CLAY_THUMB: + case SCULPT_TOOL_LAYER: + case SCULPT_TOOL_INFLATE: + case SCULPT_TOOL_BLOB: + case SCULPT_TOOL_CREASE: + br->add_col[0] = 0.0f; + br->add_col[1] = 0.5f; + br->add_col[2] = 1.0f; + br->sub_col[0] = 0.0f; + br->sub_col[1] = 0.5f; + br->sub_col[2] = 1.0f; + break; + + case SCULPT_TOOL_SMOOTH: + case SCULPT_TOOL_FLATTEN: + case SCULPT_TOOL_FILL: + case SCULPT_TOOL_SCRAPE: + case SCULPT_TOOL_MULTIPLANE_SCRAPE: + br->add_col[0] = 0.877f; + br->add_col[1] = 0.142f; + br->add_col[2] = 0.117f; + br->sub_col[0] = 0.877f; + br->sub_col[1] = 0.142f; + br->sub_col[2] = 0.117f; + break; + + case SCULPT_TOOL_PINCH: + case SCULPT_TOOL_GRAB: + case SCULPT_TOOL_SNAKE_HOOK: + case SCULPT_TOOL_THUMB: + case SCULPT_TOOL_NUDGE: + case SCULPT_TOOL_ROTATE: + case SCULPT_TOOL_ELASTIC_DEFORM: + case SCULPT_TOOL_POSE: + case SCULPT_TOOL_BOUNDARY: + case SCULPT_TOOL_SLIDE_RELAX: + br->add_col[0] = 1.0f; + br->add_col[1] = 0.95f; + br->add_col[2] = 0.005f; + br->sub_col[0] = 1.0f; + br->sub_col[1] = 0.95f; + br->sub_col[2] = 0.005f; + break; + + case SCULPT_TOOL_SIMPLIFY: + case SCULPT_TOOL_PAINT: + case SCULPT_TOOL_MASK: + case SCULPT_TOOL_DRAW_FACE_SETS: + case SCULPT_TOOL_DISPLACEMENT_ERASER: + case SCULPT_TOOL_DISPLACEMENT_SMEAR: + br->add_col[0] = 0.75f; + br->add_col[1] = 0.75f; + br->add_col[2] = 0.75f; + br->sub_col[0] = 0.75f; + br->sub_col[1] = 0.75f; + br->sub_col[2] = 0.75f; + break; + + case SCULPT_TOOL_CLOTH: + br->add_col[0] = 1.0f; + br->add_col[1] = 0.5f; + br->add_col[2] = 0.1f; + br->sub_col[0] = 1.0f; + br->sub_col[1] = 0.5f; + br->sub_col[2] = 0.1f; + break; + default: + break; + } +} + +void BKE_brush_curve_preset(Brush *b, eCurveMappingPreset preset) +{ + CurveMapping *cumap = nullptr; + CurveMap *cuma = nullptr; + + if (!b->curve) { + b->curve = BKE_curvemapping_add(1, 0, 0, 1, 1); + } + cumap = b->curve; + cumap->flag &= ~CUMA_EXTEND_EXTRAPOLATE; + cumap->preset = preset; + + cuma = b->curve->cm; + BKE_curvemap_reset(cuma, &cumap->clipr, cumap->preset, CURVEMAP_SLOPE_NEGATIVE); + BKE_curvemapping_changed(cumap, false); +} + +float BKE_brush_sample_tex_3d(const Scene *scene, + const Brush *br, + const float point[3], + float rgba[4], + const int thread, + struct ImagePool *pool) +{ + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + const MTex *mtex = &br->mtex; + float intensity = 1.0; + bool hasrgb = false; + + if (!mtex->tex) { + intensity = 1; + } + else if (mtex->brush_map_mode == MTEX_MAP_MODE_3D) { + /* Get strength by feeding the vertex + * location directly into a texture */ + hasrgb = RE_texture_evaluate(mtex, point, thread, pool, false, false, &intensity, rgba); + } + else if (mtex->brush_map_mode == MTEX_MAP_MODE_STENCIL) { + float rotation = -mtex->rot; + const float point_2d[2] = {point[0], point[1]}; + float x, y; + float co[3]; + + x = point_2d[0] - br->stencil_pos[0]; + y = point_2d[1] - br->stencil_pos[1]; + + if (rotation > 0.001f || rotation < -0.001f) { + const float angle = atan2f(y, x) + rotation; + const float flen = sqrtf(x * x + y * y); + + x = flen * cosf(angle); + y = flen * sinf(angle); + } + + if (fabsf(x) > br->stencil_dimension[0] || fabsf(y) > br->stencil_dimension[1]) { + zero_v4(rgba); + return 0.0f; + } + x /= (br->stencil_dimension[0]); + y /= (br->stencil_dimension[1]); + + co[0] = x; + co[1] = y; + co[2] = 0.0f; + + hasrgb = RE_texture_evaluate(mtex, co, thread, pool, false, false, &intensity, rgba); + } + else { + float rotation = -mtex->rot; + const float point_2d[2] = {point[0], point[1]}; + float x = 0.0f, y = 0.0f; /* Quite warnings */ + float invradius = 1.0f; /* Quite warnings */ + float co[3]; + + if (mtex->brush_map_mode == MTEX_MAP_MODE_VIEW) { + /* keep coordinates relative to mouse */ + + rotation += ups->brush_rotation; + + x = point_2d[0] - ups->tex_mouse[0]; + y = point_2d[1] - ups->tex_mouse[1]; + + /* use pressure adjusted size for fixed mode */ + invradius = 1.0f / ups->pixel_radius; + } + else if (mtex->brush_map_mode == MTEX_MAP_MODE_TILED) { + /* leave the coordinates relative to the screen */ + + /* use unadjusted size for tiled mode */ + invradius = 1.0f / ups->start_pixel_radius; + + x = point_2d[0]; + y = point_2d[1]; + } + else if (mtex->brush_map_mode == MTEX_MAP_MODE_RANDOM) { + rotation += ups->brush_rotation; + /* these contain a random coordinate */ + x = point_2d[0] - ups->tex_mouse[0]; + y = point_2d[1] - ups->tex_mouse[1]; + + invradius = 1.0f / ups->pixel_radius; + } + + x *= invradius; + y *= invradius; + + /* it is probably worth optimizing for those cases where + * the texture is not rotated by skipping the calls to + * atan2, sqrtf, sin, and cos. */ + if (rotation > 0.001f || rotation < -0.001f) { + const float angle = atan2f(y, x) + rotation; + const float flen = sqrtf(x * x + y * y); + + x = flen * cosf(angle); + y = flen * sinf(angle); + } + + co[0] = x; + co[1] = y; + co[2] = 0.0f; + + hasrgb = RE_texture_evaluate(mtex, co, thread, pool, false, false, &intensity, rgba); + } + + intensity += br->texture_sample_bias; + + if (!hasrgb) { + rgba[0] = intensity; + rgba[1] = intensity; + rgba[2] = intensity; + rgba[3] = 1.0f; + } + /* For consistency, sampling always returns color in linear space */ + else if (ups->do_linear_conversion) { + IMB_colormanagement_colorspace_to_scene_linear_v3(rgba, ups->colorspace); + } + + return intensity; +} + +float BKE_brush_sample_masktex( + const Scene *scene, Brush *br, const float point[2], const int thread, struct ImagePool *pool) +{ + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + MTex *mtex = &br->mask_mtex; + float rgba[4], intensity; + + if (!mtex->tex) { + return 1.0f; + } + if (mtex->brush_map_mode == MTEX_MAP_MODE_STENCIL) { + float rotation = -mtex->rot; + const float point_2d[2] = {point[0], point[1]}; + float x, y; + float co[3]; + + x = point_2d[0] - br->mask_stencil_pos[0]; + y = point_2d[1] - br->mask_stencil_pos[1]; + + if (rotation > 0.001f || rotation < -0.001f) { + const float angle = atan2f(y, x) + rotation; + const float flen = sqrtf(x * x + y * y); + + x = flen * cosf(angle); + y = flen * sinf(angle); + } + + if (fabsf(x) > br->mask_stencil_dimension[0] || fabsf(y) > br->mask_stencil_dimension[1]) { + zero_v4(rgba); + return 0.0f; + } + x /= (br->mask_stencil_dimension[0]); + y /= (br->mask_stencil_dimension[1]); + + co[0] = x; + co[1] = y; + co[2] = 0.0f; + + RE_texture_evaluate(mtex, co, thread, pool, false, false, &intensity, rgba); + } + else { + float rotation = -mtex->rot; + const float point_2d[2] = {point[0], point[1]}; + float x = 0.0f, y = 0.0f; /* Quite warnings */ + float invradius = 1.0f; /* Quite warnings */ + float co[3]; + + if (mtex->brush_map_mode == MTEX_MAP_MODE_VIEW) { + /* keep coordinates relative to mouse */ + + rotation += ups->brush_rotation_sec; + + x = point_2d[0] - ups->mask_tex_mouse[0]; + y = point_2d[1] - ups->mask_tex_mouse[1]; + + /* use pressure adjusted size for fixed mode */ + invradius = 1.0f / ups->pixel_radius; + } + else if (mtex->brush_map_mode == MTEX_MAP_MODE_TILED) { + /* leave the coordinates relative to the screen */ + + /* use unadjusted size for tiled mode */ + invradius = 1.0f / ups->start_pixel_radius; + + x = point_2d[0]; + y = point_2d[1]; + } + else if (mtex->brush_map_mode == MTEX_MAP_MODE_RANDOM) { + rotation += ups->brush_rotation_sec; + /* these contain a random coordinate */ + x = point_2d[0] - ups->mask_tex_mouse[0]; + y = point_2d[1] - ups->mask_tex_mouse[1]; + + invradius = 1.0f / ups->pixel_radius; + } + + x *= invradius; + y *= invradius; + + /* it is probably worth optimizing for those cases where + * the texture is not rotated by skipping the calls to + * atan2, sqrtf, sin, and cos. */ + if (rotation > 0.001f || rotation < -0.001f) { + const float angle = atan2f(y, x) + rotation; + const float flen = sqrtf(x * x + y * y); + + x = flen * cosf(angle); + y = flen * sinf(angle); + } + + co[0] = x; + co[1] = y; + co[2] = 0.0f; + + RE_texture_evaluate(mtex, co, thread, pool, false, false, &intensity, rgba); + } + + CLAMP(intensity, 0.0f, 1.0f); + + switch (br->mask_pressure) { + case BRUSH_MASK_PRESSURE_CUTOFF: + intensity = ((1.0f - intensity) < ups->size_pressure_value) ? 1.0f : 0.0f; + break; + case BRUSH_MASK_PRESSURE_RAMP: + intensity = ups->size_pressure_value + intensity * (1.0f - ups->size_pressure_value); + break; + default: + break; + } + + return intensity; +} + +/* Unified Size / Strength / Color */ + +/* XXX: be careful about setting size and unprojected radius + * because they depend on one another + * these functions do not set the other corresponding value + * this can lead to odd behavior if size and unprojected + * radius become inconsistent. + * the biggest problem is that it isn't possible to change + * unprojected radius because a view context is not + * available. my usual solution to this is to use the + * ratio of change of the size to change the unprojected + * radius. Not completely convinced that is correct. + * In any case, a better solution is needed to prevent + * inconsistency. */ + +const float *BKE_brush_color_get(const struct Scene *scene, const struct Brush *brush) +{ + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + return (ups->flag & UNIFIED_PAINT_COLOR) ? ups->rgb : brush->rgb; +} + +const float *BKE_brush_secondary_color_get(const struct Scene *scene, const struct Brush *brush) +{ + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + return (ups->flag & UNIFIED_PAINT_COLOR) ? ups->secondary_rgb : brush->secondary_rgb; +} + +void BKE_brush_color_set(struct Scene *scene, struct Brush *brush, const float color[3]) +{ + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + + if (ups->flag & UNIFIED_PAINT_COLOR) { + copy_v3_v3(ups->rgb, color); + } + else { + copy_v3_v3(brush->rgb, color); + } +} + +void BKE_brush_size_set(Scene *scene, Brush *brush, int size) +{ + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + + /* make sure range is sane */ + CLAMP(size, 1, MAX_BRUSH_PIXEL_RADIUS); + + if (ups->flag & UNIFIED_PAINT_SIZE) { + ups->size = size; + } + else { + brush->size = size; + } +} + +int BKE_brush_size_get(const Scene *scene, const Brush *brush) +{ + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + int size = (ups->flag & UNIFIED_PAINT_SIZE) ? ups->size : brush->size; + + return size; +} + +bool BKE_brush_use_locked_size(const Scene *scene, const Brush *brush) +{ + const short us_flag = scene->toolsettings->unified_paint_settings.flag; + + return (us_flag & UNIFIED_PAINT_SIZE) ? (us_flag & UNIFIED_PAINT_BRUSH_LOCK_SIZE) : + (brush->flag & BRUSH_LOCK_SIZE); +} + +bool BKE_brush_use_size_pressure(const Brush *brush) +{ + return brush->flag & BRUSH_SIZE_PRESSURE; +} + +bool BKE_brush_use_alpha_pressure(const Brush *brush) +{ + return brush->flag & BRUSH_ALPHA_PRESSURE; +} + +bool BKE_brush_sculpt_has_secondary_color(const Brush *brush) +{ + return ELEM(brush->sculpt_tool, + SCULPT_TOOL_BLOB, + SCULPT_TOOL_DRAW, + SCULPT_TOOL_DRAW_SHARP, + SCULPT_TOOL_INFLATE, + SCULPT_TOOL_CLAY, + SCULPT_TOOL_CLAY_STRIPS, + SCULPT_TOOL_CLAY_THUMB, + SCULPT_TOOL_PINCH, + SCULPT_TOOL_CREASE, + SCULPT_TOOL_LAYER, + SCULPT_TOOL_FLATTEN, + SCULPT_TOOL_FILL, + SCULPT_TOOL_SCRAPE, + SCULPT_TOOL_MASK); +} + +void BKE_brush_unprojected_radius_set(Scene *scene, Brush *brush, float unprojected_radius) +{ + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + + if (ups->flag & UNIFIED_PAINT_SIZE) { + ups->unprojected_radius = unprojected_radius; + } + else { + brush->unprojected_radius = unprojected_radius; + } +} + +float BKE_brush_unprojected_radius_get(const Scene *scene, const Brush *brush) +{ + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + + return (ups->flag & UNIFIED_PAINT_SIZE) ? ups->unprojected_radius : brush->unprojected_radius; +} + +void BKE_brush_alpha_set(Scene *scene, Brush *brush, float alpha) +{ + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + + if (ups->flag & UNIFIED_PAINT_ALPHA) { + ups->alpha = alpha; + } + else { + brush->alpha = alpha; + } +} + +float BKE_brush_alpha_get(const Scene *scene, const Brush *brush) +{ + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + + return (ups->flag & UNIFIED_PAINT_ALPHA) ? ups->alpha : brush->alpha; +} + +float BKE_brush_weight_get(const Scene *scene, const Brush *brush) +{ + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + + return (ups->flag & UNIFIED_PAINT_WEIGHT) ? ups->weight : brush->weight; +} + +void BKE_brush_weight_set(const Scene *scene, Brush *brush, float value) +{ + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + + if (ups->flag & UNIFIED_PAINT_WEIGHT) { + ups->weight = value; + } + else { + brush->weight = value; + } +} + +void BKE_brush_scale_unprojected_radius(float *unprojected_radius, + int new_brush_size, + int old_brush_size) +{ + float scale = new_brush_size; + /* avoid division by zero */ + if (old_brush_size != 0) { + scale /= (float)old_brush_size; + } + (*unprojected_radius) *= scale; +} + +void BKE_brush_scale_size(int *r_brush_size, + float new_unprojected_radius, + float old_unprojected_radius) +{ + float scale = new_unprojected_radius; + /* avoid division by zero */ + if (old_unprojected_radius != 0) { + scale /= new_unprojected_radius; + } + (*r_brush_size) = (int)((float)(*r_brush_size) * scale); +} + +void BKE_brush_jitter_pos(const Scene *scene, Brush *brush, const float pos[2], float jitterpos[2]) +{ + float rand_pos[2]; + float spread; + int diameter; + + do { + rand_pos[0] = BLI_rng_get_float(brush_rng) - 0.5f; + rand_pos[1] = BLI_rng_get_float(brush_rng) - 0.5f; + } while (len_squared_v2(rand_pos) > square_f(0.5f)); + + if (brush->flag & BRUSH_ABSOLUTE_JITTER) { + diameter = 2 * brush->jitter_absolute; + spread = 1.0; + } + else { + diameter = 2 * BKE_brush_size_get(scene, brush); + spread = brush->jitter; + } + /* find random position within a circle of diameter 1 */ + jitterpos[0] = pos[0] + 2 * rand_pos[0] * diameter * spread; + jitterpos[1] = pos[1] + 2 * rand_pos[1] * diameter * spread; +} + +void BKE_brush_randomize_texture_coords(UnifiedPaintSettings *ups, bool mask) +{ + /* we multiply with brush radius as an optimization for the brush + * texture sampling functions */ + if (mask) { + ups->mask_tex_mouse[0] = BLI_rng_get_float(brush_rng) * ups->pixel_radius; + ups->mask_tex_mouse[1] = BLI_rng_get_float(brush_rng) * ups->pixel_radius; + } + else { + ups->tex_mouse[0] = BLI_rng_get_float(brush_rng) * ups->pixel_radius; + ups->tex_mouse[1] = BLI_rng_get_float(brush_rng) * ups->pixel_radius; + } +} + +float BKE_brush_curve_strength(const Brush *br, float p, const float len) +{ + float strength = 1.0f; + + if (p >= len) { + return 0; + } + + p = p / len; + p = 1.0f - p; + + switch (br->curve_preset) { + case BRUSH_CURVE_CUSTOM: + strength = BKE_curvemapping_evaluateF(br->curve, 0, 1.0f - p); + break; + case BRUSH_CURVE_SHARP: + strength = p * p; + break; + case BRUSH_CURVE_SMOOTH: + strength = 3.0f * p * p - 2.0f * p * p * p; + break; + case BRUSH_CURVE_SMOOTHER: + strength = pow3f(p) * (p * (p * 6.0f - 15.0f) + 10.0f); + break; + case BRUSH_CURVE_ROOT: + strength = sqrtf(p); + break; + case BRUSH_CURVE_LIN: + strength = p; + break; + case BRUSH_CURVE_CONSTANT: + strength = 1.0f; + break; + case BRUSH_CURVE_SPHERE: + strength = sqrtf(2 * p - p * p); + break; + case BRUSH_CURVE_POW4: + strength = p * p * p * p; + break; + case BRUSH_CURVE_INVSQUARE: + strength = p * (2.0f - p); + break; + } + + return strength; +} + +float BKE_brush_curve_strength_clamped(const Brush *br, float p, const float len) +{ + float strength = BKE_brush_curve_strength(br, p, len); + + CLAMP(strength, 0.0f, 1.0f); + + return strength; +} + +/* TODO: should probably be unified with BrushPainter stuff? */ +static bool brush_gen_texture(const Brush *br, + const int side, + const bool use_secondary, + float *rect) +{ + const MTex *mtex = (use_secondary) ? &br->mask_mtex : &br->mtex; + if (mtex->tex == nullptr) { + return false; + } + + const float step = 2.0 / side; + int ix, iy; + float x, y; + + /* Do normalized canonical view coords for texture. */ + for (y = -1.0, iy = 0; iy < side; iy++, y += step) { + for (x = -1.0, ix = 0; ix < side; ix++, x += step) { + const float co[3] = {x, y, 0.0f}; + + float intensity; + float rgba_dummy[4]; + RE_texture_evaluate(mtex, co, 0, nullptr, false, false, &intensity, rgba_dummy); + + rect[iy * side + ix] = intensity; + } + } + + return true; +} + +struct ImBuf *BKE_brush_gen_radial_control_imbuf(Brush *br, bool secondary, bool display_gradient) +{ + ImBuf *im = MEM_cnew("radial control texture"); + int side = 512; + int half = side / 2; + + BKE_curvemapping_init(br->curve); + im->rect_float = (float *)MEM_callocN(sizeof(float) * side * side, "radial control rect"); + im->x = im->y = side; + + const bool have_texture = brush_gen_texture(br, side, secondary, im->rect_float); + + if (display_gradient || have_texture) { + for (int i = 0; i < side; i++) { + for (int j = 0; j < side; j++) { + float magn = sqrtf(pow2f(i - half) + pow2f(j - half)); + im->rect_float[i * side + j] *= BKE_brush_curve_strength_clamped(br, magn, half); + } + } + } + + return im; +} -- cgit v1.2.3 From 8159e0a666d3d51936e50a36912f38386570af11 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 8 Jul 2022 15:53:57 +0200 Subject: Curves: use consistent default radius for Cycles, Eevee, Set Curve Radius node To avoid Cycles not showing any hair by default, and to avoid very slow render due to many overlaps with the previous 1 meter default in the node. Fixes T97584, T99319 Differential Revision: https://developer.blender.org/D15405 --- intern/cycles/blender/curves.cpp | 2 +- source/blender/nodes/geometry/nodes/node_geo_set_curve_radius.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/intern/cycles/blender/curves.cpp b/intern/cycles/blender/curves.cpp index b01cb85711a..10012720bd8 100644 --- a/intern/cycles/blender/curves.cpp +++ b/intern/cycles/blender/curves.cpp @@ -868,7 +868,7 @@ static void export_hair_curves(Scene *scene, for (int i = 0; i < num_points; i++) { const float3 co = get_float3(b_curves.position_data[first_point_index + i].vector()); const float radius = b_attr_radius ? b_attr_radius->data[first_point_index + i].value() : - 0.0f; + 0.005f; hair->add_curve_key(co, radius); if (attr_intercept) { diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_curve_radius.cc b/source/blender/nodes/geometry/nodes/node_geo_set_curve_radius.cc index 90411baac3e..e4fae95b5a5 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_curve_radius.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_curve_radius.cc @@ -10,7 +10,7 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_input(N_("Selection")).default_value(true).hide_value().supports_field(); b.add_input(N_("Radius")) .min(0.0f) - .default_value(1.0f) + .default_value(0.005f) .supports_field() .subtype(PROP_DISTANCE); b.add_output(N_("Curve")); -- cgit v1.2.3 From d8e980a4a6f132aba13713efb89ec3013976c4a1 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 8 Jul 2022 16:26:13 +0200 Subject: Fix bug in recently added MutableVArraySpan move constructor --- source/blender/blenlib/BLI_virtual_array.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/blenlib/BLI_virtual_array.hh b/source/blender/blenlib/BLI_virtual_array.hh index 56b2f9f6c6f..438fcc4b8f7 100644 --- a/source/blender/blenlib/BLI_virtual_array.hh +++ b/source/blender/blenlib/BLI_virtual_array.hh @@ -1221,7 +1221,7 @@ template class MutableVArraySpan final : public MutableSpan { MutableVArraySpan(MutableVArraySpan &&other) : varray_(std::move(other.varray_)), - owned_data_(std::move(owned_data_)), + owned_data_(std::move(other.owned_data_)), show_not_saved_warning_(other.show_not_saved_warning_) { if (!varray_) { -- cgit v1.2.3 From 2ee68917287627a3803a8eae91d4d788ac316185 Mon Sep 17 00:00:00 2001 From: Richard Antalik Date: Fri, 8 Jul 2022 17:44:24 +0200 Subject: Fix T99494: Transition effects not working correctly This was caused by strip content length and start position being incorrect. Previously this was set from strip boundary by update function, but it was removed. Add back code to set effect strip start and length. Previously content length was always 1 for effects, but now it must correspond to strip length. Because of this workaround for speed effect to get this apparent content length was removed. --- source/blender/sequencer/intern/effects.c | 23 +++-------------------- source/blender/sequencer/intern/strip_time.c | 2 ++ 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/source/blender/sequencer/intern/effects.c b/source/blender/sequencer/intern/effects.c index 2ab8b170ce9..25a6acb8975 100644 --- a/source/blender/sequencer/intern/effects.c +++ b/source/blender/sequencer/intern/effects.c @@ -2578,21 +2578,6 @@ static int early_out_speed(Sequence *UNUSED(seq), float UNUSED(fac)) return EARLY_DO_EFFECT; } -/** - * Generator strips with zero inputs have their length set to 1 permanently. In some cases it is - * useful to use speed effect on these strips because they can be animated. This can be done by - * using their length as is on timeline as content length. See T82698. - */ -static int seq_effect_speed_get_strip_content_length(Scene *scene, const Sequence *seq) -{ - if ((seq->type & SEQ_TYPE_EFFECT) != 0 && SEQ_effect_get_num_inputs(seq->type) == 0) { - return SEQ_time_right_handle_frame_get(scene, seq) - - SEQ_time_left_handle_frame_get(scene, seq); - } - - return SEQ_time_strip_length_get(scene, seq); -} - static FCurve *seq_effect_speed_speed_factor_curve_get(Scene *scene, Sequence *seq) { return id_data_find_fcurve(&scene->id, seq, &RNA_Sequence, "speed_factor", 0, NULL); @@ -2657,8 +2642,7 @@ float seq_speed_effect_target_frame_get(Scene *scene, switch (s->speed_control_type) { case SEQ_SPEED_STRETCH: { /* Only right handle controls effect speed! */ - const float target_content_length = seq_effect_speed_get_strip_content_length(scene, - source) - + const float target_content_length = SEQ_time_strip_length_get(scene, source) - source->startofs; const float speed_effetct_length = SEQ_time_right_handle_frame_get(scene, seq_speed) - SEQ_time_left_handle_frame_get(scene, seq_speed); @@ -2678,15 +2662,14 @@ float seq_speed_effect_target_frame_get(Scene *scene, break; } case SEQ_SPEED_LENGTH: - target_frame = seq_effect_speed_get_strip_content_length(scene, source) * - (s->speed_fader_length / 100.0f); + target_frame = SEQ_time_strip_length_get(scene, source) * (s->speed_fader_length / 100.0f); break; case SEQ_SPEED_FRAME_NUMBER: target_frame = s->speed_fader_frame_number; break; } - CLAMP(target_frame, 0, seq_effect_speed_get_strip_content_length(scene, source)); + CLAMP(target_frame, 0, SEQ_time_strip_length_get(scene, source)); target_frame += seq_speed->start; /* No interpolation. */ diff --git a/source/blender/sequencer/intern/strip_time.c b/source/blender/sequencer/intern/strip_time.c index aa51813c9b8..5d8266dbc6e 100644 --- a/source/blender/sequencer/intern/strip_time.c +++ b/source/blender/sequencer/intern/strip_time.c @@ -203,6 +203,8 @@ void seq_time_effect_range_set(const Scene *scene, Sequence *seq) /* Values unusable for effects, these should be always 0. */ seq->startofs = seq->endofs = seq->anim_startofs = seq->anim_endofs = 0; + seq->start = seq->startdisp; + seq->len = seq->enddisp - seq->startdisp; } /* Update strip startdisp and enddisp (n-input effects have no len to calculate these). */ -- cgit v1.2.3 From bc2121147f32f5ffde24bb35847f64f813e948ac Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 8 Jul 2022 18:47:31 -0500 Subject: Cleanup: Remove unused variable --- source/blender/editors/curves/intern/curves_ops.cc | 3 --- 1 file changed, 3 deletions(-) diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc index a4492a1d516..f4d1c8046f1 100644 --- a/source/blender/editors/curves/intern/curves_ops.cc +++ b/source/blender/editors/curves/intern/curves_ops.cc @@ -528,9 +528,6 @@ static void snap_curves_to_surface_exec_object(Object &curves_ob, Mesh &surface_mesh = *static_cast(surface_ob.data); - MeshComponent surface_mesh_component; - surface_mesh_component.replace(&surface_mesh, GeometryOwnershipType::ReadOnly); - VArraySpan surface_uv_map; if (curves_id.surface_uv_map != nullptr) { const bke::AttributeAccessor surface_attributes = bke::mesh_attributes(surface_mesh); -- cgit v1.2.3 From d9e00fbbf613dda92f3e635e0c13094749447cae Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sat, 9 Jul 2022 15:08:02 +1000 Subject: Cleanup: quiet class-memaccess warning --- source/blender/blenkernel/intern/brush.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/blender/blenkernel/intern/brush.cc b/source/blender/blenkernel/intern/brush.cc index ffa63f9792f..1c2408bab8a 100644 --- a/source/blender/blenkernel/intern/brush.cc +++ b/source/blender/blenkernel/intern/brush.cc @@ -444,7 +444,8 @@ static void brush_defaults(Brush *brush) const Brush *brush_def = DNA_struct_default_get(Brush); -#define FROM_DEFAULT(member) memcpy(&brush->member, &brush_def->member, sizeof(brush->member)) +#define FROM_DEFAULT(member) \ + memcpy((void *)&brush->member, (void *)&brush_def->member, sizeof(brush->member)) #define FROM_DEFAULT_PTR(member) memcpy(brush->member, brush_def->member, sizeof(brush->member)) FROM_DEFAULT(blend); -- cgit v1.2.3 From e3801a2bd417b63f8b30c4c2dfa19b2efdbf9ce7 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 19 Feb 2022 17:04:32 +0300 Subject: Weight & Vertex Paint: always respect edit mode hiding on faces. In some cases it is mandatory to be able to hide parts of the mesh in order to paint certain areas. The Mask modifier doesn't work in weight paint, and edit mode hiding requires using selection, which is not always convenient. This makes the weight and vertex paint modes always respect edit mode hiding like sculpt mode. The change in behavior affects drawing and building paint PBVH. Thus it affects brushes, but not menu operators like Smooth or Normalize. In addition, this makes the Alt-H shortcut available even without any selection enabled, and implements Hide for vertex selection. Differential Revision: https://developer.blender.org/D14163 --- .../keyconfig/keymap_data/blender_default.py | 5 +- .../keymap_data/industry_compatible_data.py | 7 ++- source/blender/blenkernel/BKE_paint.h | 5 ++ source/blender/blenkernel/intern/paint.c | 13 ++--- source/blender/draw/intern/draw_manager.c | 2 +- source/blender/editors/include/ED_mesh.h | 3 ++ source/blender/editors/mesh/editface.cc | 51 ++++++++++++++++++ source/blender/editors/sculpt_paint/paint_intern.h | 4 +- source/blender/editors/sculpt_paint/paint_ops.c | 4 +- source/blender/editors/sculpt_paint/paint_utils.c | 61 ++++++++++++++++++---- 10 files changed, 135 insertions(+), 20 deletions(-) diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index c0db6c5f523..9c7aa67ddad 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -4397,7 +4397,7 @@ def km_face_mask(params): items.extend([ *_template_items_select_actions(params, "paint.face_select_all"), - *_template_items_hide_reveal_actions("paint.face_select_hide", "paint.face_select_reveal"), + *_template_items_hide_reveal_actions("paint.face_select_hide", "paint.face_vert_reveal"), ("paint.face_select_linked", {"type": 'L', "value": 'PRESS', "ctrl": True}, None), ("paint.face_select_linked_pick", {"type": 'L', "value": 'PRESS'}, {"properties": [("deselect", False)]}), @@ -4418,6 +4418,7 @@ def km_weight_paint_vertex_selection(params): items.extend([ *_template_items_select_actions(params, "paint.vert_select_all"), + *_template_items_hide_reveal_actions("paint.vert_select_hide", "paint.face_vert_reveal"), ("view3d.select_box", {"type": 'B', "value": 'PRESS'}, None), ("view3d.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True}, {"properties": [("mode", 'ADD')]}), @@ -4968,6 +4969,7 @@ def km_vertex_paint(params): op_menu("VIEW3D_MT_angle_control", {"type": 'R', "value": 'PRESS'}), ("wm.context_menu_enum", {"type": 'E', "value": 'PRESS'}, {"properties": [("data_path", 'tool_settings.vertex_paint.brush.stroke_method')]}), + ("paint.face_vert_reveal", {"type": 'H', "value": 'PRESS', "alt": True}, None), *_template_items_context_panel("VIEW3D_PT_paint_vertex_context_menu", params.context_menu_event), ]) @@ -5015,6 +5017,7 @@ def km_weight_paint(params): ("wm.context_toggle", {"type": 'S', "value": 'PRESS', "shift": True}, {"properties": [("data_path", 'tool_settings.weight_paint.brush.use_smooth_stroke')]}), op_menu_pie("VIEW3D_MT_wpaint_vgroup_lock_pie", {"type": 'K', "value": 'PRESS'}), + ("paint.face_vert_reveal", {"type": 'H', "value": 'PRESS', "alt": True}, None), *_template_items_context_panel("VIEW3D_PT_paint_weight_context_menu", params.context_menu_event), ]) diff --git a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py index d60bbfed67a..8feaa7e928f 100644 --- a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py +++ b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py @@ -2948,7 +2948,7 @@ def km_face_mask(params): {"properties": [("unselected", False)]}), ("paint.face_select_hide", {"type": 'H', "value": 'PRESS', "shift": True}, {"properties": [("unselected", True)]}), - ("paint.face_select_reveal", {"type": 'H', "value": 'PRESS', "alt": True}, None), + ("paint.face_vert_reveal", {"type": 'H', "value": 'PRESS', "alt": True}, None), ("paint.face_select_linked", {"type": 'L', "value": 'PRESS', "ctrl": True}, None), ("paint.face_select_linked_pick", {"type": 'L', "value": 'PRESS'}, {"properties": [("deselect", False)]}), @@ -2969,6 +2969,9 @@ def km_weight_paint_vertex_selection(params): items.extend([ ("paint.vert_select_all", {"type": 'A', "value": 'PRESS', "ctrl": True}, None), + ("paint.vert_select_hide", {"type": 'H', "value": 'PRESS', "shift": True}, + {"properties": [("unselected", True)]}), + ("paint.face_vert_reveal", {"type": 'H', "value": 'PRESS', "alt": True}, None), ]) return keymap @@ -3330,6 +3333,7 @@ def km_vertex_paint(params): ("wm.context_toggle", {"type": 'S', "value": 'PRESS', "shift": True}, {"properties": [("data_path", 'tool_settings.vertex_paint.brush.use_smooth_stroke')]}), op_menu("VIEW3D_MT_angle_control", {"type": 'R', "value": 'PRESS'}), + ("paint.face_vert_reveal", {"type": 'H', "value": 'PRESS', "alt": True}, None), *_template_items_context_panel("VIEW3D_PT_paint_vertex_context_menu", {"type": 'RIGHTMOUSE', "value": 'PRESS'}), # Tools ("paint.brush_select", {"type": 'D', "value": 'PRESS'}, @@ -3362,6 +3366,7 @@ def km_weight_paint(params): {"properties": [("data_path", 'weight_paint_object.data.use_paint_mask')]}), ("wm.context_toggle", {"type": 'S', "value": 'PRESS', "shift": True}, {"properties": [("data_path", 'tool_settings.weight_paint.brush.use_smooth_stroke')]}), + ("paint.face_vert_reveal", {"type": 'H', "value": 'PRESS', "alt": True}, None), *_template_items_context_panel("VIEW3D_PT_paint_weight_context_menu", {"type": 'RIGHTMOUSE', "value": 'PRESS'}), # Bone selection for combined weight paint + pose mode. ("view3d.select", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True}, None), diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h index ffe80ff47b6..162459d2005 100644 --- a/source/blender/blenkernel/BKE_paint.h +++ b/source/blender/blenkernel/BKE_paint.h @@ -200,6 +200,11 @@ bool BKE_paint_select_vert_test(struct Object *ob); * (when we don't care if its face or vert) */ bool BKE_paint_select_elem_test(struct Object *ob); +/** + * Checks if face/vertex hiding is always applied in the current mode. + * Returns true in vertex/weight paint. + */ +bool BKE_paint_always_hide_test(struct Object *ob); /* Partial visibility. */ diff --git a/source/blender/blenkernel/intern/paint.c b/source/blender/blenkernel/intern/paint.c index 89cc25b31e6..9b0d15ac702 100644 --- a/source/blender/blenkernel/intern/paint.c +++ b/source/blender/blenkernel/intern/paint.c @@ -996,6 +996,12 @@ bool BKE_paint_select_elem_test(Object *ob) return (BKE_paint_select_vert_test(ob) || BKE_paint_select_face_test(ob)); } +bool BKE_paint_always_hide_test(Object *ob) +{ + return ((ob != NULL) && (ob->type == OB_MESH) && (ob->data != NULL) && + (ob->mode & OB_MODE_WEIGHT_PAINT || ob->mode & OB_MODE_VERTEX_PAINT)); +} + void BKE_paint_cavity_curve_preset(Paint *p, int preset) { CurveMapping *cumap = NULL; @@ -2255,12 +2261,7 @@ PBVH *BKE_sculpt_object_pbvh_ensure(Depsgraph *depsgraph, Object *ob) return NULL; } - bool respect_hide = true; - if (ob->mode & (OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT)) { - if (!(BKE_paint_select_vert_test(ob) || BKE_paint_select_face_test(ob))) { - respect_hide = false; - } - } + const bool respect_hide = true; PBVH *pbvh = ob->sculpt->pbvh; if (pbvh != NULL) { diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c index bc9d0a3d02a..5d21ab75650 100644 --- a/source/blender/draw/intern/draw_manager.c +++ b/source/blender/draw/intern/draw_manager.c @@ -235,7 +235,7 @@ bool DRW_object_use_hide_faces(const struct Object *ob) return (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; case OB_MODE_VERTEX_PAINT: case OB_MODE_WEIGHT_PAINT: - return (me->editflag & (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) != 0; + return true; } } diff --git a/source/blender/editors/include/ED_mesh.h b/source/blender/editors/include/ED_mesh.h index 30a98129ee6..52044109702 100644 --- a/source/blender/editors/include/ED_mesh.h +++ b/source/blender/editors/include/ED_mesh.h @@ -425,6 +425,9 @@ void paintvert_select_ungrouped(struct Object *ob, bool extend, bool flush_flags void paintvert_flush_flags(struct Object *ob); void paintvert_tag_select_update(struct bContext *C, struct Object *ob); +void paintvert_hide(struct bContext *C, struct Object *ob, bool unselected); +void paintvert_reveal(struct bContext *C, struct Object *ob, bool select); + /* mirrtopo */ typedef struct MirrTopoStore_t { intptr_t *index_lookup; diff --git a/source/blender/editors/mesh/editface.cc b/source/blender/editors/mesh/editface.cc index 69fe69fe117..b69cd8b8606 100644 --- a/source/blender/editors/mesh/editface.cc +++ b/source/blender/editors/mesh/editface.cc @@ -551,3 +551,54 @@ void paintvert_select_ungrouped(Object *ob, bool extend, bool flush_flags) paintvert_flush_flags(ob); } } + +void paintvert_hide(bContext *C, Object *ob, const bool unselected) +{ + Mesh *const me = BKE_mesh_from_object(ob); + + if (me == NULL || me->totvert == 0) { + return; + } + + for (int i = 0; i < me->totvert; i++) { + MVert *const mvert = &me->mvert[i]; + + if ((mvert->flag & ME_HIDE) == 0) { + if (((mvert->flag & SELECT) == 0) == unselected) { + mvert->flag |= ME_HIDE; + } + } + + if (mvert->flag & ME_HIDE) { + mvert->flag &= ~SELECT; + } + } + + BKE_mesh_flush_hidden_from_verts(me); + + paintvert_flush_flags(ob); + paintvert_tag_select_update(C, ob); +} + +void paintvert_reveal(bContext *C, Object *ob, const bool select) +{ + Mesh *const me = BKE_mesh_from_object(ob); + + if (me == NULL || me->totvert == 0) { + return; + } + + for (int i = 0; i < me->totvert; i++) { + MVert *const mvert = &me->mvert[i]; + + if (mvert->flag & ME_HIDE) { + SET_FLAG_FROM_TEST(mvert->flag, select, SELECT); + mvert->flag &= ~ME_HIDE; + } + } + + BKE_mesh_flush_hidden_from_verts(me); + + paintvert_flush_flags(ob); + paintvert_tag_select_update(C, ob); +} diff --git a/source/blender/editors/sculpt_paint/paint_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h index ea17114efa5..02c3b5be8b9 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.h +++ b/source/blender/editors/sculpt_paint/paint_intern.h @@ -370,10 +370,12 @@ void PAINT_OT_face_select_linked(struct wmOperatorType *ot); void PAINT_OT_face_select_linked_pick(struct wmOperatorType *ot); void PAINT_OT_face_select_all(struct wmOperatorType *ot); void PAINT_OT_face_select_hide(struct wmOperatorType *ot); -void PAINT_OT_face_select_reveal(struct wmOperatorType *ot); + +void PAINT_OT_face_vert_reveal(struct wmOperatorType *ot); void PAINT_OT_vert_select_all(struct wmOperatorType *ot); void PAINT_OT_vert_select_ungrouped(struct wmOperatorType *ot); +void PAINT_OT_vert_select_hide(struct wmOperatorType *ot); bool vert_paint_poll(struct bContext *C); bool mask_paint_poll(struct bContext *C); diff --git a/source/blender/editors/sculpt_paint/paint_ops.c b/source/blender/editors/sculpt_paint/paint_ops.c index ce6b397af15..994ae4011b4 100644 --- a/source/blender/editors/sculpt_paint/paint_ops.c +++ b/source/blender/editors/sculpt_paint/paint_ops.c @@ -1454,6 +1454,7 @@ void ED_operatortypes_paint(void) /* vertex selection */ WM_operatortype_append(PAINT_OT_vert_select_all); WM_operatortype_append(PAINT_OT_vert_select_ungrouped); + WM_operatortype_append(PAINT_OT_vert_select_hide); /* vertex */ WM_operatortype_append(PAINT_OT_vertex_paint_toggle); @@ -1472,7 +1473,8 @@ void ED_operatortypes_paint(void) WM_operatortype_append(PAINT_OT_face_select_linked_pick); WM_operatortype_append(PAINT_OT_face_select_all); WM_operatortype_append(PAINT_OT_face_select_hide); - WM_operatortype_append(PAINT_OT_face_select_reveal); + + WM_operatortype_append(PAINT_OT_face_vert_reveal); /* partial visibility */ WM_operatortype_append(PAINT_OT_hide_show); diff --git a/source/blender/editors/sculpt_paint/paint_utils.c b/source/blender/editors/sculpt_paint/paint_utils.c index 3f26f590b70..1f272882100 100644 --- a/source/blender/editors/sculpt_paint/paint_utils.c +++ b/source/blender/editors/sculpt_paint/paint_utils.c @@ -749,25 +749,68 @@ void PAINT_OT_face_select_hide(wmOperatorType *ot) ot->srna, "unselected", 0, "Unselected", "Hide unselected rather than selected objects"); } -static int face_select_reveal_exec(bContext *C, wmOperator *op) +static int vert_select_hide_exec(bContext *C, wmOperator *op) +{ + const bool unselected = RNA_boolean_get(op->ptr, "unselected"); + Object *ob = CTX_data_active_object(C); + paintvert_hide(C, ob, unselected); + ED_region_tag_redraw(CTX_wm_region(C)); + return OPERATOR_FINISHED; +} + +void PAINT_OT_vert_select_hide(wmOperatorType *ot) +{ + ot->name = "Vertex Select Hide"; + ot->description = "Hide selected vertices"; + ot->idname = "PAINT_OT_vert_select_hide"; + + ot->exec = vert_select_hide_exec; + ot->poll = vert_paint_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean( + ot->srna, "unselected", 0, "Unselected", "Hide unselected rather than selected vertices"); +} + +static int face_vert_reveal_exec(bContext *C, wmOperator *op) { const bool select = RNA_boolean_get(op->ptr, "select"); Object *ob = CTX_data_active_object(C); - paintface_reveal(C, ob, select); + + if (BKE_paint_select_vert_test(ob)) { + paintvert_reveal(C, ob, select); + } + else { + paintface_reveal(C, ob, select); + } + ED_region_tag_redraw(CTX_wm_region(C)); return OPERATOR_FINISHED; } -void PAINT_OT_face_select_reveal(wmOperatorType *ot) +static bool face_vert_reveal_poll(bContext *C) { - ot->name = "Face Select Reveal"; - ot->description = "Reveal hidden faces"; - ot->idname = "PAINT_OT_face_select_reveal"; + Object *ob = CTX_data_active_object(C); - ot->exec = face_select_reveal_exec; - ot->poll = facemask_paint_poll; + /* Allow using this operator when no selection is enabled but hiding is applied. */ + return BKE_paint_select_elem_test(ob) || BKE_paint_always_hide_test(ob); +} + +void PAINT_OT_face_vert_reveal(wmOperatorType *ot) +{ + ot->name = "Reveal Faces/Vertices"; + ot->description = "Reveal hidden faces and vertices"; + ot->idname = "PAINT_OT_face_vert_reveal"; + + ot->exec = face_vert_reveal_exec; + ot->poll = face_vert_reveal_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - RNA_def_boolean(ot->srna, "select", true, "Select", ""); + RNA_def_boolean(ot->srna, + "select", + true, + "Select", + "Specifies whether the newly revealed geometry should be selected"); } -- cgit v1.2.3 From 80f8b7cbbb562c6967ba81c76027a984cd9cd9b6 Mon Sep 17 00:00:00 2001 From: Daniel Salazar Date: Sat, 9 Jul 2022 02:06:32 -0600 Subject: UI: renaming fIle browser thumbnail sizes Rename the thumbnail size from Regular to Medium since it's the typical way to refer to sizing in American English Reviewed By: Campbell Barton Differential Revision: https://developer.blender.org/D15305 --- source/blender/makesrna/intern/rna_space.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index 969d1f2075e..e67d840eeac 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -6585,7 +6585,7 @@ static void rna_def_fileselect_params(BlenderRNA *brna) static const EnumPropertyItem display_size_items[] = { {64, "TINY", 0, "Tiny", ""}, {96, "SMALL", 0, "Small", ""}, - {128, "NORMAL", 0, "Regular", ""}, + {128, "NORMAL", 0, "Medium", ""}, {192, "LARGE", 0, "Large", ""}, {0, NULL, 0, NULL, NULL}, }; -- cgit v1.2.3 From b5d22a8134ac3bfc6c084b836729f4dd15e25bee Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sat, 9 Jul 2022 22:27:26 +1000 Subject: Fix resource leaks setting custom cursors in Wayland - Memory from the prior cursor was never un-mapped. - posix_fallocate failure left a file handle open.. --- intern/ghost/intern/GHOST_SystemWayland.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 08aa640c5cd..76e5329a410 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -3347,6 +3347,11 @@ GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap, cursor_t *cursor = &d->inputs[0]->cursor; + if (cursor->file_buffer->data) { + munmap(cursor->file_buffer->data, cursor->file_buffer->size); + cursor->file_buffer->data = nullptr; + } + static const int32_t stride = sizex * 4; /* ARGB */ cursor->file_buffer->size = (size_t)stride * sizey; @@ -3376,6 +3381,7 @@ GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap, } if (UNLIKELY(posix_fallocate(fd, 0, int32_t(cursor->file_buffer->size)) != 0)) { + close(fd); return GHOST_kFailure; } -- cgit v1.2.3 From ef970b7756259747d7b724ae7d3195ec5520e478 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sat, 9 Jul 2022 22:27:27 +1000 Subject: Cleanup: split memfd_create into it's own function for Wayland Avoid ifdef's in cursor loading by creating a memfd_create_sealed utility function that works irrespective of memfd_create availability. --- intern/ghost/intern/GHOST_SystemWayland.cpp | 47 +++++++++++++++-------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 76e5329a410..25123fe651f 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -747,7 +747,30 @@ static const std::vector mime_send = { "text/plain", }; -#undef LOG +static int memfd_create_sealed(const char *name) +{ +#ifdef HAVE_MEMFD_CREATE + const int fd = memfd_create(name, MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (fd >= 0) { + fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL); + } + return fd; +#else /* HAVE_MEMFD_CREATE */ + char *path = getenv("XDG_RUNTIME_DIR"); + if (!path) { + errno = ENOENT; + return -1; + } + char *tmpname; + asprintf(&tmpname, "%s/%s-XXXXXX", path, name); + const int fd = mkostemp(tmpname, O_CLOEXEC); + if (fd >= 0) { + unlink(tmpname); + } + free(tmpname); + return fd; +#endif /* !HAVE_MEMFD_CREATE */ +} /** \} */ @@ -3355,27 +3378,7 @@ GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap, static const int32_t stride = sizex * 4; /* ARGB */ cursor->file_buffer->size = (size_t)stride * sizey; -#ifdef HAVE_MEMFD_CREATE - const int fd = memfd_create("blender-cursor-custom", MFD_CLOEXEC | MFD_ALLOW_SEALING); - if (fd >= 0) { - fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL); - } -#else - char *path = getenv("XDG_RUNTIME_DIR"); - if (!path) { - errno = ENOENT; - return GHOST_kFailure; - } - - char *tmpname; - asprintf(&tmpname, "%s/%s", path, "blender-XXXXXX"); - const int fd = mkostemp(tmpname, O_CLOEXEC); - if (fd >= 0) { - unlink(tmpname); - } - free(tmpname); -#endif - + const int fd = memfd_create_sealed("blender-cursor-custom"); if (UNLIKELY(fd < 0)) { return GHOST_kFailure; } -- cgit v1.2.3 From 9a1d772339d6eafe3ddbc05f36075ee01f654610 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sat, 9 Jul 2022 22:27:28 +1000 Subject: Cleanup: remove buffer_t in GHOST/Wayland This was allocated and only used to store the custom cursor data. Use a pointer & size member instead for simplicity. --- intern/ghost/intern/GHOST_SystemWayland.cpp | 45 ++++++++++++++--------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 25123fe651f..98437f65e7e 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -128,11 +128,6 @@ static bool use_gnome_confine_hack = false; */ #define EVDEV_OFFSET 8 -struct buffer_t { - void *data = nullptr; - size_t size = 0; -}; - struct cursor_t { bool visible = false; /** @@ -147,7 +142,8 @@ struct cursor_t { struct wl_buffer *wl_buffer = nullptr; struct wl_cursor_image wl_image = {0}; struct wl_cursor_theme *wl_theme = nullptr; - struct buffer_t *file_buffer = nullptr; + void *custom_data = nullptr; + size_t custom_data_size = 0; int size = 0; std::string theme_name; /** Outputs on which the cursor is visible. */ @@ -449,11 +445,12 @@ static void display_destroy(display_t *d) if (input->data_device) { wl_data_device_release(input->data_device); } + + if (input->cursor.custom_data) { + munmap(input->cursor.custom_data, input->cursor.custom_data_size); + } + if (input->wl_pointer) { - if (input->cursor.file_buffer) { - munmap(input->cursor.file_buffer->data, input->cursor.file_buffer->size); - delete input->cursor.file_buffer; - } if (input->cursor.wl_surface) { wl_surface_destroy(input->cursor.wl_surface); } @@ -2305,7 +2302,6 @@ static void seat_handle_capabilities(void *data, input->cursor.wl_surface = wl_compositor_create_surface(input->system->compositor()); input->cursor.visible = true; input->cursor.wl_buffer = nullptr; - input->cursor.file_buffer = new buffer_t; if (!get_cursor_settings(input->cursor.theme_name, input->cursor.size)) { input->cursor.theme_name = std::string(); input->cursor.size = default_cursor_size; @@ -3370,34 +3366,35 @@ GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap, cursor_t *cursor = &d->inputs[0]->cursor; - if (cursor->file_buffer->data) { - munmap(cursor->file_buffer->data, cursor->file_buffer->size); - cursor->file_buffer->data = nullptr; + if (cursor->custom_data) { + munmap(cursor->custom_data, cursor->custom_data_size); + cursor->custom_data = nullptr; + cursor->custom_data_size = 0; /* Not needed, but the value is no longer meaningful. */ } static const int32_t stride = sizex * 4; /* ARGB */ - cursor->file_buffer->size = (size_t)stride * sizey; + cursor->custom_data_size = (size_t)stride * sizey; const int fd = memfd_create_sealed("blender-cursor-custom"); if (UNLIKELY(fd < 0)) { return GHOST_kFailure; } - if (UNLIKELY(posix_fallocate(fd, 0, int32_t(cursor->file_buffer->size)) != 0)) { + if (UNLIKELY(posix_fallocate(fd, 0, int32_t(cursor->custom_data_size)) != 0)) { close(fd); return GHOST_kFailure; } - cursor->file_buffer->data = mmap( - nullptr, cursor->file_buffer->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + cursor->custom_data = mmap( + nullptr, cursor->custom_data_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (UNLIKELY(cursor->file_buffer->data == MAP_FAILED)) { - cursor->file_buffer->data = nullptr; + if (UNLIKELY(cursor->custom_data == MAP_FAILED)) { + cursor->custom_data = nullptr; close(fd); return GHOST_kFailure; } - struct wl_shm_pool *pool = wl_shm_create_pool(d->shm, fd, int32_t(cursor->file_buffer->size)); + struct wl_shm_pool *pool = wl_shm_create_pool(d->shm, fd, int32_t(cursor->custom_data_size)); wl_buffer *buffer = wl_shm_pool_create_buffer( pool, 0, sizex, sizey, stride, WL_SHM_FORMAT_ARGB8888); @@ -3415,7 +3412,7 @@ GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap, uint32_t *pixel; for (int y = 0; y < sizey; ++y) { - pixel = &static_cast(cursor->file_buffer->data)[y * sizex]; + pixel = &static_cast(cursor->custom_data)[y * sizex]; for (int x = 0; x < sizex; ++x) { if ((x % 8) == 0) { datab = *bitmap++; @@ -3454,7 +3451,7 @@ GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap, GHOST_TSuccess GHOST_SystemWayland::getCursorBitmap(GHOST_CursorBitmapRef *bitmap) { cursor_t *cursor = &d->inputs[0]->cursor; - if (cursor->file_buffer->data == nullptr) { + if (cursor->custom_data == nullptr) { return GHOST_kFailure; } if (!cursor->is_custom) { @@ -3467,7 +3464,7 @@ GHOST_TSuccess GHOST_SystemWayland::getCursorBitmap(GHOST_CursorBitmapRef *bitma bitmap->hot_spot[0] = cursor->wl_image.hotspot_x; bitmap->hot_spot[1] = cursor->wl_image.hotspot_y; - bitmap->data = (uint8_t *)static_cast(cursor->file_buffer->data); + bitmap->data = (uint8_t *)static_cast(cursor->custom_data); return GHOST_kSuccess; } -- cgit v1.2.3 From 1de14061cbe33dd39ee48c65b609d550cac157ca Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sat, 9 Jul 2022 22:27:30 +1000 Subject: Cleanup: split out wl_buffer creation into a utility function Simplify logic for initializing the wl_buffer, ensure the cursors custom data is never heft in a half initialized state. Also remove the need for multiple calls to close when handling errors. --- intern/ghost/intern/GHOST_SystemWayland.cpp | 84 +++++++++++++++++++---------- 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 98437f65e7e..4b390615435 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -769,6 +769,59 @@ static int memfd_create_sealed(const char *name) #endif /* !HAVE_MEMFD_CREATE */ } +static size_t ghost_wl_shm_format_as_size(enum wl_shm_format format) +{ + switch (format) { + case WL_SHM_FORMAT_ARGB8888: { + return 4; + } + default: { + /* Support other formats as needed. */ + GHOST_ASSERT(0, "Unexpected format passed in!"); + return 4; + } + } +} + +/** + * Return a #wl_buffer, ready to have it's data filled in or NULL in case of failure. + * The caller is responsible for calling `unmap(buffer_data, buffer_size)`. + * + * \param r_buffer_data: The buffer to be filled. + * \param r_buffer_data_size: The size of `r_buffer_data` in bytes. + */ +static wl_buffer *ghost_wl_buffer_create_for_image(struct wl_shm *shm, + const int32_t size_xy[2], + enum wl_shm_format format, + void **r_buffer_data, + size_t *r_buffer_data_size) +{ + const int fd = memfd_create_sealed("ghost-wl-buffer"); + wl_buffer *buffer = nullptr; + if (fd >= 0) { + const int32_t buffer_stride = size_xy[0] * ghost_wl_shm_format_as_size(format); + const int32_t buffer_size = buffer_stride * size_xy[1]; + if (posix_fallocate(fd, 0, buffer_size) == 0) { + void *buffer_data = mmap(nullptr, buffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (buffer_data != MAP_FAILED) { + struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, buffer_size); + buffer = wl_shm_pool_create_buffer(pool, 0, UNPACK2(size_xy), buffer_stride, format); + wl_shm_pool_destroy(pool); + if (buffer) { + *r_buffer_data = buffer_data; + *r_buffer_data_size = (size_t)buffer_size; + } + else { + /* Highly unlikely. */ + munmap(buffer_data, buffer_size); + } + } + } + close(fd); + } + return buffer; +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -3372,36 +3425,13 @@ GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap, cursor->custom_data_size = 0; /* Not needed, but the value is no longer meaningful. */ } - static const int32_t stride = sizex * 4; /* ARGB */ - cursor->custom_data_size = (size_t)stride * sizey; - - const int fd = memfd_create_sealed("blender-cursor-custom"); - if (UNLIKELY(fd < 0)) { - return GHOST_kFailure; - } - - if (UNLIKELY(posix_fallocate(fd, 0, int32_t(cursor->custom_data_size)) != 0)) { - close(fd); - return GHOST_kFailure; - } - - cursor->custom_data = mmap( - nullptr, cursor->custom_data_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - - if (UNLIKELY(cursor->custom_data == MAP_FAILED)) { - cursor->custom_data = nullptr; - close(fd); + const int32_t size_xy[2] = {sizex, sizey}; + wl_buffer *buffer = ghost_wl_buffer_create_for_image( + d->shm, size_xy, WL_SHM_FORMAT_ARGB8888, &cursor->custom_data, &cursor->custom_data_size); + if (buffer == nullptr) { return GHOST_kFailure; } - struct wl_shm_pool *pool = wl_shm_create_pool(d->shm, fd, int32_t(cursor->custom_data_size)); - - wl_buffer *buffer = wl_shm_pool_create_buffer( - pool, 0, sizex, sizey, stride, WL_SHM_FORMAT_ARGB8888); - - wl_shm_pool_destroy(pool); - close(fd); - wl_buffer_add_listener(buffer, &cursor_buffer_listener, cursor); static constexpr uint32_t black = 0xFF000000; -- cgit v1.2.3 From 443690604f4ed5bdcf05cca40c8d6f1f88a1c29a Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sat, 9 Jul 2022 22:27:34 +1000 Subject: Fix cursor display size with tablet input in GHOST/Wayland The scale for tablet cursor surfaces was never set, making them display larger. Now the outputs scale is set for mouse & tablet cursors. --- intern/ghost/intern/GHOST_SystemWayland.cpp | 191 ++++++++++++++++++---------- intern/ghost/intern/GHOST_SystemWayland.h | 6 + 2 files changed, 133 insertions(+), 64 deletions(-) diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 4b390615435..876f77732c2 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -146,10 +146,7 @@ struct cursor_t { size_t custom_data_size = 0; int size = 0; std::string theme_name; - /** Outputs on which the cursor is visible. */ - std::unordered_set outputs; - int theme_scale = 1; int custom_scale = 1; }; @@ -231,6 +228,11 @@ struct input_state_pointer_t { */ wl_fixed_t xy[2] = {0, 0}; + /** Outputs on which the cursor is visible. */ + std::unordered_set outputs; + + int theme_scale = 1; + /** The serial of the last used pointer or tablet. */ uint32_t serial = 0; @@ -396,6 +398,19 @@ static input_state_pointer_t *input_state_pointer_active(input_t *input) return nullptr; } +static input_state_pointer_t *input_state_pointer_from_cursor_surface(input_t *input, + const wl_surface *wl_surface) +{ + if (ghost_wl_surface_own_cursor_pointer(wl_surface)) { + return &input->pointer; + } + if (ghost_wl_surface_own_cursor_tablet(wl_surface)) { + return &input->tablet; + } + GHOST_ASSERT(0, "Surface found without pointer/tablet tag"); + return nullptr; +} + static void display_destroy(display_t *d) { if (d->data_device_manager) { @@ -1358,19 +1373,22 @@ static const struct wl_buffer_listener cursor_buffer_listener = { static CLG_LogRef LOG_WL_CURSOR_SURFACE = {"ghost.wl.handle.cursor_surface"}; #define LOG (&LOG_WL_CURSOR_SURFACE) -static bool update_cursor_scale(cursor_t &cursor, wl_shm *shm) +static bool update_cursor_scale(cursor_t &cursor, + wl_shm *shm, + input_state_pointer_t *input_state, + wl_surface *cursor_surface) { int scale = 0; - for (const output_t *output : cursor.outputs) { + for (const output_t *output : input_state->outputs) { if (output->scale > scale) { scale = output->scale; } } - if (scale > 0 && cursor.theme_scale != scale) { - cursor.theme_scale = scale; + if (scale > 0 && input_state->theme_scale != scale) { + input_state->theme_scale = scale; if (!cursor.is_custom) { - wl_surface_set_buffer_scale(cursor.wl_surface, scale); + wl_surface_set_buffer_scale(cursor_surface, scale); } wl_cursor_theme_destroy(cursor.wl_theme); cursor.wl_theme = wl_cursor_theme_load(cursor.theme_name.c_str(), scale * cursor.size, shm); @@ -1380,7 +1398,7 @@ static bool update_cursor_scale(cursor_t &cursor, wl_shm *shm) } static void cursor_surface_handle_enter(void *data, - struct wl_surface * /*wl_surface*/, + struct wl_surface *wl_surface, struct wl_output *output) { if (!ghost_wl_output_own(output)) { @@ -1390,13 +1408,14 @@ static void cursor_surface_handle_enter(void *data, CLOG_INFO(LOG, 2, "handle_enter"); input_t *input = static_cast(data); + input_state_pointer_t *input_state = input_state_pointer_from_cursor_surface(input, wl_surface); const output_t *reg_output = ghost_wl_output_user_data(output); - input->cursor.outputs.insert(reg_output); - update_cursor_scale(input->cursor, input->system->shm()); + input_state->outputs.insert(reg_output); + update_cursor_scale(input->cursor, input->system->shm(), input_state, wl_surface); } static void cursor_surface_handle_leave(void *data, - struct wl_surface * /*wl_surface*/, + struct wl_surface *wl_surface, struct wl_output *output) { if (!(output && ghost_wl_output_own(output))) { @@ -1406,9 +1425,10 @@ static void cursor_surface_handle_leave(void *data, CLOG_INFO(LOG, 2, "handle_leave"); input_t *input = static_cast(data); + input_state_pointer_t *input_state = input_state_pointer_from_cursor_surface(input, wl_surface); const output_t *reg_output = ghost_wl_output_user_data(output); - input->cursor.outputs.erase(reg_output); - update_cursor_scale(input->cursor, input->system->shm()); + input_state->outputs.erase(reg_output); + update_cursor_scale(input->cursor, input->system->shm(), input_state, wl_surface); } static const struct wl_surface_listener cursor_surface_listener = { @@ -1952,6 +1972,8 @@ static void tablet_seat_handle_tool_added(void *data, /* Every tool has it's own cursor surface. */ tool_input->cursor_surface = wl_compositor_create_surface(input->system->compositor()); + ghost_wl_surface_tag_cursor_tablet(tool_input->cursor_surface); + wl_surface_add_listener(tool_input->cursor_surface, &cursor_surface_listener, (void *)input); zwp_tablet_tool_v2_add_listener(id, &tablet_tool_listner, tool_input); @@ -2360,7 +2382,9 @@ static void seat_handle_capabilities(void *data, input->cursor.size = default_cursor_size; } wl_pointer_add_listener(input->wl_pointer, &pointer_listener, data); + wl_surface_add_listener(input->cursor.wl_surface, &cursor_surface_listener, data); + ghost_wl_surface_tag_cursor_pointer(input->cursor.wl_surface); } if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { @@ -3211,19 +3235,30 @@ GHOST_IWindow *GHOST_SystemWayland::createWindow(const char *title, static void cursor_buffer_show(const input_t *input) { const cursor_t *c = &input->cursor; - const int scale = c->is_custom ? c->custom_scale : c->theme_scale; - const int32_t hotspot_x = int32_t(c->wl_image.hotspot_x) / scale; - const int32_t hotspot_y = int32_t(c->wl_image.hotspot_y) / scale; - wl_pointer_set_cursor( - input->wl_pointer, input->pointer.serial, c->wl_surface, hotspot_x, hotspot_y); - for (struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : input->tablet_tools) { - tablet_tool_input_t *tool_input = static_cast( - zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2)); - zwp_tablet_tool_v2_set_cursor(zwp_tablet_tool_v2, - input->tablet.serial, - tool_input->cursor_surface, - hotspot_x, - hotspot_y); + + if (input->wl_pointer) { + const int scale = c->is_custom ? c->custom_scale : input->pointer.theme_scale; + const int32_t hotspot_x = int32_t(c->wl_image.hotspot_x) / scale; + const int32_t hotspot_y = int32_t(c->wl_image.hotspot_y) / scale; + if (input->wl_pointer) { + wl_pointer_set_cursor( + input->wl_pointer, input->pointer.serial, c->wl_surface, hotspot_x, hotspot_y); + } + } + + if (!input->tablet_tools.empty()) { + const int scale = c->is_custom ? c->custom_scale : input->tablet.theme_scale; + const int32_t hotspot_x = int32_t(c->wl_image.hotspot_x) / scale; + const int32_t hotspot_y = int32_t(c->wl_image.hotspot_y) / scale; + for (struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : input->tablet_tools) { + tablet_tool_input_t *tool_input = static_cast( + zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2)); + zwp_tablet_tool_v2_set_cursor(zwp_tablet_tool_v2, + input->tablet.serial, + tool_input->cursor_surface, + hotspot_x, + hotspot_y); + } } } @@ -3241,52 +3276,58 @@ static void cursor_buffer_hide(const input_t *input) } } +static void cursor_buffer_set_surface_impl(const input_t *input, + wl_buffer *buffer, + struct wl_surface *wl_surface, + const int scale) +{ + const wl_cursor_image *wl_image = &input->cursor.wl_image; + const int32_t image_size_x = int32_t(wl_image->width); + const int32_t image_size_y = int32_t(wl_image->height); + GHOST_ASSERT((image_size_x % scale) == 0 && (image_size_y % scale) == 0, + "The size must be a multiple of the scale!"); + + wl_surface_set_buffer_scale(wl_surface, scale); + wl_surface_attach(wl_surface, buffer, 0, 0); + wl_surface_damage(wl_surface, 0, 0, image_size_x, image_size_y); + wl_surface_commit(wl_surface); +} + static void cursor_buffer_set(const input_t *input, wl_buffer *buffer) { const cursor_t *c = &input->cursor; - const int scale = c->is_custom ? c->custom_scale : c->theme_scale; - + const wl_cursor_image *wl_image = &input->cursor.wl_image; const bool visible = (c->visible && c->is_hardware); - const int32_t image_size_x = int32_t(c->wl_image.width); - const int32_t image_size_y = int32_t(c->wl_image.height); - /* This is a requirement of WAYLAND, when this isn't the case, * it causes Blender's window to close intermittently. */ - GHOST_ASSERT((image_size_x % scale) == 0 && (image_size_y % scale) == 0, - "The size must be a multiple of the scale!"); - - const int32_t hotspot_x = int32_t(c->wl_image.hotspot_x) / scale; - const int32_t hotspot_y = int32_t(c->wl_image.hotspot_y) / scale; - - wl_surface_set_buffer_scale(c->wl_surface, scale); - wl_surface_attach(c->wl_surface, buffer, 0, 0); - wl_surface_damage(c->wl_surface, 0, 0, image_size_x, image_size_y); - wl_surface_commit(c->wl_surface); - - wl_pointer_set_cursor(input->wl_pointer, - input->pointer.serial, - visible ? c->wl_surface : nullptr, - hotspot_x, - hotspot_y); + if (input->wl_pointer) { + const int scale = c->is_custom ? c->custom_scale : input->pointer.theme_scale; + const int32_t hotspot_x = int32_t(wl_image->hotspot_x) / scale; + const int32_t hotspot_y = int32_t(wl_image->hotspot_y) / scale; + cursor_buffer_set_surface_impl(input, buffer, c->wl_surface, scale); + wl_pointer_set_cursor(input->wl_pointer, + input->pointer.serial, + visible ? c->wl_surface : nullptr, + hotspot_x, + hotspot_y); + } /* Set the cursor for all tablet tools as well. */ - for (struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : input->tablet_tools) { - tablet_tool_input_t *tool_input = static_cast( - zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2)); - - /* FIXME: for some reason cursor scale is applied twice (when the scale isn't 1x), - * this happens both in gnome-shell & KDE. Setting the surface scale here doesn't help. */ - wl_surface_set_buffer_scale(tool_input->cursor_surface, scale); - wl_surface_attach(tool_input->cursor_surface, buffer, 0, 0); - wl_surface_damage(tool_input->cursor_surface, 0, 0, image_size_x, image_size_y); - wl_surface_commit(tool_input->cursor_surface); - - zwp_tablet_tool_v2_set_cursor(zwp_tablet_tool_v2, - input->tablet.serial, - visible ? tool_input->cursor_surface : nullptr, - hotspot_x, - hotspot_y); + if (!input->tablet_tools.empty()) { + const int scale = c->is_custom ? c->custom_scale : input->tablet.theme_scale; + const int32_t hotspot_x = int32_t(wl_image->hotspot_x) / scale; + const int32_t hotspot_y = int32_t(wl_image->hotspot_y) / scale; + for (struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : input->tablet_tools) { + tablet_tool_input_t *tool_input = static_cast( + zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2)); + cursor_buffer_set_surface_impl(input, buffer, tool_input->cursor_surface, scale); + zwp_tablet_tool_v2_set_cursor(zwp_tablet_tool_v2, + input->tablet.serial, + visible ? tool_input->cursor_surface : nullptr, + hotspot_x, + hotspot_y); + } } } @@ -3585,6 +3626,8 @@ static input_grab_state_t input_grab_state_from_mode(const GHOST_TGrabCursorMode static const char *ghost_wl_output_tag_id = "GHOST-output"; static const char *ghost_wl_surface_tag_id = "GHOST-window"; +static const char *ghost_wl_surface_cursor_pointer_tag_id = "GHOST-cursor-pointer"; +static const char *ghost_wl_surface_cursor_tablet_tag_id = "GHOST-cursor-tablet"; bool ghost_wl_output_own(const struct wl_output *output) { @@ -3596,6 +3639,16 @@ bool ghost_wl_surface_own(const struct wl_surface *surface) return wl_proxy_get_tag((struct wl_proxy *)surface) == &ghost_wl_surface_tag_id; } +bool ghost_wl_surface_own_cursor_pointer(const struct wl_surface *surface) +{ + return wl_proxy_get_tag((struct wl_proxy *)surface) == &ghost_wl_surface_cursor_pointer_tag_id; +} + +bool ghost_wl_surface_own_cursor_tablet(const struct wl_surface *surface) +{ + return wl_proxy_get_tag((struct wl_proxy *)surface) == &ghost_wl_surface_cursor_tablet_tag_id; +} + void ghost_wl_output_tag(struct wl_output *output) { wl_proxy_set_tag((struct wl_proxy *)output, &ghost_wl_output_tag_id); @@ -3606,6 +3659,16 @@ void ghost_wl_surface_tag(struct wl_surface *surface) wl_proxy_set_tag((struct wl_proxy *)surface, &ghost_wl_surface_tag_id); } +void ghost_wl_surface_tag_cursor_pointer(struct wl_surface *surface) +{ + wl_proxy_set_tag((struct wl_proxy *)surface, &ghost_wl_surface_cursor_pointer_tag_id); +} + +void ghost_wl_surface_tag_cursor_tablet(struct wl_surface *surface) +{ + wl_proxy_set_tag((struct wl_proxy *)surface, &ghost_wl_surface_cursor_tablet_tag_id); +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/intern/ghost/intern/GHOST_SystemWayland.h b/intern/ghost/intern/GHOST_SystemWayland.h index 0d51759aa2f..bdf5f2fc273 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.h +++ b/intern/ghost/intern/GHOST_SystemWayland.h @@ -41,6 +41,12 @@ bool ghost_wl_surface_own(const struct wl_surface *surface); void ghost_wl_surface_tag(struct wl_surface *surface); GHOST_WindowWayland *ghost_wl_surface_user_data(struct wl_surface *surface); +bool ghost_wl_surface_own_cursor_pointer(const struct wl_surface *surface); +void ghost_wl_surface_tag_cursor_pointer(struct wl_surface *surface); + +bool ghost_wl_surface_own_cursor_tablet(const struct wl_surface *surface); +void ghost_wl_surface_tag_cursor_tablet(struct wl_surface *surface); + #ifdef WITH_GHOST_WAYLAND_DYNLOAD /** * Return true when all required WAYLAND libraries are present, -- cgit v1.2.3 From 4114ace6169282390b199ea593c93445af748903 Mon Sep 17 00:00:00 2001 From: Aras Pranckevicius Date: Sun, 10 Jul 2022 18:27:07 +0300 Subject: Fix T99536: new 3.2 OBJ importer fails with trailing space after wrapped lines Address the issue by re-working line continuation handling: stop trying to parse sequences like "backslash, newline" (which is the bug: it should also handle "backslash, possible whitespace, newline") during parsing. Instead, fixup line continuations after reading chunks of input file data - turn backslash and the following newline into spaces. The rest of parsing code does not have to be aware of them at all then. Makes the file attached to T99536 load correctly now. Also will extend one of the test files in subversion tests repo to contain backslashes followed by newlines. --- .../importer/obj_import_file_reader.cc | 9 +++++--- .../importer/obj_import_string_utils.cc | 26 ++++++++++++++++++---- .../importer/obj_import_string_utils.hh | 14 +++++------- .../tests/obj_import_string_utils_tests.cc | 23 ++++++++++++++++--- 4 files changed, 54 insertions(+), 18 deletions(-) diff --git a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc index c2aa96713b1..6eb1bbc51f3 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc +++ b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc @@ -427,6 +427,11 @@ void OBJParser::parse(Vector> &r_all_geometries, break; /* No more data to read. */ } + /* Take care of line continuations now (turn them into spaces); + * the rest of the parsing code does not need to worry about them anymore. */ + fixup_line_continuations(buffer.data() + buffer_offset, + buffer.data() + buffer_offset + bytes_read); + /* Ensure buffer ends in a newline. */ if (bytes_read < read_buffer_size_) { if (bytes_read == 0 || buffer[buffer_offset + bytes_read - 1] != '\n') { @@ -445,9 +450,7 @@ void OBJParser::parse(Vector> &r_all_geometries, while (last_nl > 0) { --last_nl; if (buffer[last_nl] == '\n') { - if (last_nl < 1 || buffer[last_nl - 1] != '\\') { - break; - } + break; } } if (buffer[last_nl] != '\n') { diff --git a/source/blender/io/wavefront_obj/importer/obj_import_string_utils.cc b/source/blender/io/wavefront_obj/importer/obj_import_string_utils.cc index bc9006e1051..9a457167fca 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_string_utils.cc +++ b/source/blender/io/wavefront_obj/importer/obj_import_string_utils.cc @@ -18,14 +18,12 @@ StringRef read_next_line(StringRef &buffer) const char *start = buffer.begin(); const char *end = buffer.end(); size_t len = 0; - char prev = 0; const char *ptr = start; while (ptr < end) { char c = *ptr++; - if (c == '\n' && prev != '\\') { + if (c == '\n') { break; } - prev = c; ++len; } @@ -35,7 +33,27 @@ StringRef read_next_line(StringRef &buffer) static bool is_whitespace(char c) { - return c <= ' ' || c == '\\'; + return c <= ' '; +} + +void fixup_line_continuations(char *p, char *end) +{ + while (true) { + /* Find next backslash, if any. */ + char *backslash = std::find(p, end, '\\'); + if (backslash == end) + break; + /* Skip over possible whitespace right after it. */ + p = backslash + 1; + while (p < end && is_whitespace(*p) && *p != '\n') + ++p; + /* If then we have a newline, turn both backslash + * and the newline into regular spaces. */ + if (p < end && *p == '\n') { + *backslash = ' '; + *p = ' '; + } + } } const char *drop_whitespace(const char *p, const char *end) diff --git a/source/blender/io/wavefront_obj/importer/obj_import_string_utils.hh b/source/blender/io/wavefront_obj/importer/obj_import_string_utils.hh index f6dd1a6b675..e42f5080d25 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_string_utils.hh +++ b/source/blender/io/wavefront_obj/importer/obj_import_string_utils.hh @@ -6,9 +6,6 @@ /* * Various text parsing utilities used by OBJ importer. - * The utilities are not directly usable by other formats, since - * they treat backslash (\) as a whitespace character (OBJ format - * allows backslashes to function as a line-continuation character). * * Many of these functions take two pointers (p, end) indicating * which part of a string to operate on, and return a possibly @@ -27,21 +24,22 @@ namespace blender::io::obj { * The returned line will not have '\n' characters at the end; * the `buffer` is modified to contain remaining text without * the input line. - * - * Note that backslash (\) character is treated as a line - * continuation. */ StringRef read_next_line(StringRef &buffer); +/** + * Fix up OBJ line continuations by replacing backslash (\) and the + * following newline with spaces. + */ +void fixup_line_continuations(char *p, char *end); + /** * Drop leading white-space from a string part. - * Note that backslash character is considered white-space. */ const char *drop_whitespace(const char *p, const char *end); /** * Drop leading non-white-space from a string part. - * Note that backslash character is considered white-space. */ const char *drop_non_whitespace(const char *p, const char *end); diff --git a/source/blender/io/wavefront_obj/tests/obj_import_string_utils_tests.cc b/source/blender/io/wavefront_obj/tests/obj_import_string_utils_tests.cc index e9747b437cc..dc1cfd2b449 100644 --- a/source/blender/io/wavefront_obj/tests/obj_import_string_utils_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_import_string_utils_tests.cc @@ -10,17 +10,34 @@ namespace blender::io::obj { TEST(obj_import_string_utils, read_next_line) { - std::string str = "abc\n \n\nline with \\\ncontinuation\nCRLF ending:\r\na"; + std::string str = "abc\n \n\nline with \t spaces\nCRLF ending:\r\na"; StringRef s = str; EXPECT_STRREF_EQ("abc", read_next_line(s)); EXPECT_STRREF_EQ(" ", read_next_line(s)); EXPECT_STRREF_EQ("", read_next_line(s)); - EXPECT_STRREF_EQ("line with \\\ncontinuation", read_next_line(s)); + EXPECT_STRREF_EQ("line with \t spaces", read_next_line(s)); EXPECT_STRREF_EQ("CRLF ending:\r", read_next_line(s)); EXPECT_STRREF_EQ("a", read_next_line(s)); EXPECT_TRUE(s.is_empty()); } +TEST(obj_import_string_utils, fixup_line_continuations) +{ + const char *str = + "backslash \\\n eol\n" + "backslash spaces \\ \n eol\n" + "without eol \\ is \\\\ \\ left intact\n" + "\\"; + const char *exp = + "backslash eol\n" + "backslash spaces eol\n" + "without eol \\ is \\\\ \\ left intact\n" + "\\"; + std::string buf(str); + fixup_line_continuations(buf.data(), buf.data() + buf.size()); + EXPECT_STRREF_EQ(exp, buf); +} + static StringRef drop_whitespace(StringRef s) { return StringRef(drop_whitespace(s.begin(), s.end()), s.end()); @@ -54,7 +71,7 @@ TEST(obj_import_string_utils, drop_whitespace) /* No leading whitespace */ EXPECT_STRREF_EQ("c", drop_whitespace("c")); /* Case with backslash, should be treated as whitespace */ - EXPECT_STRREF_EQ("d", drop_whitespace(" \\ d")); + EXPECT_STRREF_EQ("d", drop_whitespace(" \t d")); } TEST(obj_import_string_utils, parse_int_valid) -- cgit v1.2.3 From fad857f47319166d3ff97029385b50059731a576 Mon Sep 17 00:00:00 2001 From: Aras Pranckevicius Date: Sun, 10 Jul 2022 20:09:29 +0300 Subject: Fix T99532: New OBJ importer in some cases fails to import faces The importer code was written under incorrect assumption that vertex data (v, vn, vt commands etc.) are grouped by object, i.e. follow the o command, and that each object has its own vertex data commands. This is not the case -- all the vertex data in the whole OBJ file is "global", with no relation to any objects/groups; it's just that the faces belong to the object, and then they pull in any vertices they like. This patch fixes this incorrect assumption in the importer: - Vertex data is now properly global; no need to track some sort of "offsets" per object like it was doing before. - For each object, face definitions track the minimum & maximum vertex indices referenced by the object, and then all that vertex range is created in the final Blender object. Note: it might be (unusual, but possible) that an object does not reference a sequential range of vertices, e.g. just a single face with vertex indices 1, 10, 100 -- the resulting Blender mesh will have all the 100 vertices (some "loose" without belonging to a face). It should be possible to track the used vertices exactly (e.g. with a vector set), but I haven't done that for performance reasons. Reviewed By: Howard Trickey Differential Revision: https://developer.blender.org/D15410 --- .../importer/obj_import_file_reader.cc | 106 +++++++++---------- .../io/wavefront_obj/importer/obj_import_mesh.cc | 57 +++++----- .../wavefront_obj/importer/obj_import_objects.hh | 52 +++++----- .../io/wavefront_obj/tests/obj_importer_tests.cc | 115 +++++++++++++++------ 4 files changed, 190 insertions(+), 140 deletions(-) diff --git a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc index 6eb1bbc51f3..a32fd90594d 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc +++ b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc @@ -22,32 +22,24 @@ using std::string; /** * Based on the properties of the given Geometry instance, create a new Geometry instance * or return the previous one. - * - * Also update index offsets which should always happen if a new Geometry instance is created. */ static Geometry *create_geometry(Geometry *const prev_geometry, const eGeometryType new_type, StringRef name, - const GlobalVertices &global_vertices, - Vector> &r_all_geometries, - VertexIndexOffset &r_offset) + Vector> &r_all_geometries) { auto new_geometry = [&]() { r_all_geometries.append(std::make_unique()); Geometry *g = r_all_geometries.last().get(); g->geom_type_ = new_type; g->geometry_name_ = name.is_empty() ? "New object" : name; - g->vertex_start_ = global_vertices.vertices.size(); - g->vertex_color_start_ = global_vertices.vertex_colors.size(); - r_offset.set_index_offset(g->vertex_start_); return g; }; if (prev_geometry && prev_geometry->geom_type_ == GEOM_MESH) { /* After the creation of a Geometry instance, at least one element has been found in the OBJ - * file that indicates that it is a mesh (basically anything but the vertex positions). */ - if (!prev_geometry->face_elements_.is_empty() || prev_geometry->has_vertex_normals_ || - !prev_geometry->edges_.is_empty()) { + * file that indicates that it is a mesh (faces or edges). */ + if (!prev_geometry->face_elements_.is_empty() || !prev_geometry->edges_.is_empty()) { return new_geometry(); } if (new_type == GEOM_MESH) { @@ -70,15 +62,11 @@ static Geometry *create_geometry(Geometry *const prev_geometry, return new_geometry(); } -static void geom_add_vertex(Geometry *geom, - const char *p, - const char *end, - GlobalVertices &r_global_vertices) +static void geom_add_vertex(const char *p, const char *end, GlobalVertices &r_global_vertices) { float3 vert; p = parse_floats(p, end, 0.0f, vert, 3); r_global_vertices.vertices.append(vert); - geom->vertex_count_++; /* OBJ extension: `xyzrgb` vertex colors, when the vertex position * is followed by 3 more RGB color components. See * http://paulbourke.net/dataformats/obj/colour.html */ @@ -88,16 +76,22 @@ static void geom_add_vertex(Geometry *geom, if (srgb.x >= 0 && srgb.y >= 0 && srgb.z >= 0) { float3 linear; srgb_to_linearrgb_v3_v3(linear, srgb); - r_global_vertices.vertex_colors.append(linear); - geom->vertex_color_count_++; + + auto &blocks = r_global_vertices.vertex_colors; + /* If we don't have vertex colors yet, or the previous vertex + * was without color, we need to start a new vertex colors block. */ + if (blocks.is_empty() || (blocks.last().start_vertex_index + blocks.last().colors.size() != + r_global_vertices.vertices.size() - 1)) { + GlobalVertices::VertexColorsBlock block; + block.start_vertex_index = r_global_vertices.vertices.size() - 1; + blocks.append(block); + } + blocks.last().colors.append(linear); } } } -static void geom_add_mrgb_colors(Geometry *geom, - const char *p, - const char *end, - GlobalVertices &r_global_vertices) +static void geom_add_mrgb_colors(const char *p, const char *end, GlobalVertices &r_global_vertices) { /* MRGB color extension, in the form of * "#MRGB MMRRGGBBMMRRGGBB ..." @@ -117,14 +111,26 @@ static void geom_add_mrgb_colors(Geometry *geom, srgb[3] = 0xFF; float linear[4]; srgb_to_linearrgb_uchar4(linear, srgb); - r_global_vertices.vertex_colors.append({linear[0], linear[1], linear[2]}); - geom->vertex_color_count_++; + + auto &blocks = r_global_vertices.vertex_colors; + /* If we don't have vertex colors yet, or the previous vertex + * was without color, we need to start a new vertex colors block. */ + if (blocks.is_empty() || (blocks.last().start_vertex_index + blocks.last().colors.size() != + r_global_vertices.vertices.size())) { + GlobalVertices::VertexColorsBlock block; + block.start_vertex_index = r_global_vertices.vertices.size(); + blocks.append(block); + } + blocks.last().colors.append({linear[0], linear[1], linear[2]}); + /* MRGB colors are specified after vertex positions; each new color + * "pushes" the vertex colors block further back into which vertices it is for. */ + blocks.last().start_vertex_index--; + p += mrgb_length; } } -static void geom_add_vertex_normal(Geometry *geom, - const char *p, +static void geom_add_vertex_normal(const char *p, const char *end, GlobalVertices &r_global_vertices) { @@ -135,7 +141,6 @@ static void geom_add_vertex_normal(Geometry *geom, * normalized. */ normalize_v3(normal); r_global_vertices.vertex_normals.append(normal); - geom->has_vertex_normals_ = true; } static void geom_add_uv_vertex(const char *p, const char *end, GlobalVertices &r_global_vertices) @@ -148,24 +153,24 @@ static void geom_add_uv_vertex(const char *p, const char *end, GlobalVertices &r static void geom_add_edge(Geometry *geom, const char *p, const char *end, - const VertexIndexOffset &offsets, GlobalVertices &r_global_vertices) { int edge_v1, edge_v2; p = parse_int(p, end, -1, edge_v1); p = parse_int(p, end, -1, edge_v2); /* Always keep stored indices non-negative and zero-based. */ - edge_v1 += edge_v1 < 0 ? r_global_vertices.vertices.size() : -offsets.get_index_offset() - 1; - edge_v2 += edge_v2 < 0 ? r_global_vertices.vertices.size() : -offsets.get_index_offset() - 1; + edge_v1 += edge_v1 < 0 ? r_global_vertices.vertices.size() : -1; + edge_v2 += edge_v2 < 0 ? r_global_vertices.vertices.size() : -1; BLI_assert(edge_v1 >= 0 && edge_v2 >= 0); geom->edges_.append({static_cast(edge_v1), static_cast(edge_v2)}); + geom->track_vertex_index(edge_v1); + geom->track_vertex_index(edge_v2); } static void geom_add_polygon(Geometry *geom, const char *p, const char *end, const GlobalVertices &global_vertices, - const VertexIndexOffset &offsets, const int material_index, const int group_index, const bool shaded_smooth) @@ -204,8 +209,7 @@ static void geom_add_polygon(Geometry *geom, } } /* Always keep stored indices non-negative and zero-based. */ - corner.vert_index += corner.vert_index < 0 ? global_vertices.vertices.size() : - -offsets.get_index_offset() - 1; + corner.vert_index += corner.vert_index < 0 ? global_vertices.vertices.size() : -1; if (corner.vert_index < 0 || corner.vert_index >= global_vertices.vertices.size()) { fprintf(stderr, "Invalid vertex index %i (valid range [0, %zu)), ignoring face\n", @@ -213,6 +217,9 @@ static void geom_add_polygon(Geometry *geom, (size_t)global_vertices.vertices.size()); face_valid = false; } + else { + geom->track_vertex_index(corner.vert_index); + } if (got_uv) { corner.uv_vert_index += corner.uv_vert_index < 0 ? global_vertices.uv_vertices.size() : -1; if (corner.uv_vert_index < 0 || corner.uv_vert_index >= global_vertices.uv_vertices.size()) { @@ -226,7 +233,7 @@ static void geom_add_polygon(Geometry *geom, /* Ignore corner normal index, if the geometry does not have any normals. * Some obj files out there do have face definitions that refer to normal indices, * without any normals being present (T98782). */ - if (got_normal && geom->has_vertex_normals_) { + if (got_normal && !global_vertices.vertex_normals.is_empty()) { corner.vertex_normal_index += corner.vertex_normal_index < 0 ? global_vertices.vertex_normals.size() : -1; @@ -260,9 +267,7 @@ static void geom_add_polygon(Geometry *geom, static Geometry *geom_set_curve_type(Geometry *geom, const char *p, const char *end, - const GlobalVertices &global_vertices, const StringRef group_name, - VertexIndexOffset &r_offsets, Vector> &r_all_geometries) { p = drop_whitespace(p, end); @@ -270,8 +275,7 @@ static Geometry *geom_set_curve_type(Geometry *geom, std::cerr << "Curve type not supported: '" << std::string(p, end) << "'" << std::endl; return geom; } - geom = create_geometry( - geom, GEOM_CURVE, group_name, global_vertices, r_all_geometries, r_offsets); + geom = create_geometry(geom, GEOM_CURVE, group_name, r_all_geometries); geom->nurbs_element_.group_ = group_name; return geom; } @@ -402,9 +406,7 @@ void OBJParser::parse(Vector> &r_all_geometries, BLI_strncpy(ob_name, BLI_path_basename(import_params_.filepath), FILE_MAXFILE); BLI_path_extension_replace(ob_name, FILE_MAXFILE, ""); - VertexIndexOffset offsets; - Geometry *curr_geom = create_geometry( - nullptr, GEOM_MESH, ob_name, r_global_vertices, r_all_geometries, offsets); + Geometry *curr_geom = create_geometry(nullptr, GEOM_MESH, ob_name, r_all_geometries); /* State variables: once set, they remain the same for the remaining * elements in the object. */ @@ -477,10 +479,10 @@ void OBJParser::parse(Vector> &r_all_geometries, /* Most common things that start with 'v': vertices, normals, UVs. */ if (*p == 'v') { if (parse_keyword(p, end, "v")) { - geom_add_vertex(curr_geom, p, end, r_global_vertices); + geom_add_vertex(p, end, r_global_vertices); } else if (parse_keyword(p, end, "vn")) { - geom_add_vertex_normal(curr_geom, p, end, r_global_vertices); + geom_add_vertex_normal(p, end, r_global_vertices); } else if (parse_keyword(p, end, "vt")) { geom_add_uv_vertex(p, end, r_global_vertices); @@ -492,26 +494,21 @@ void OBJParser::parse(Vector> &r_all_geometries, p, end, r_global_vertices, - offsets, state_material_index, - state_group_index, /* TODO was wrongly material name! */ + state_group_index, state_shaded_smooth); } /* Faces. */ else if (parse_keyword(p, end, "l")) { - geom_add_edge(curr_geom, p, end, offsets, r_global_vertices); + geom_add_edge(curr_geom, p, end, r_global_vertices); } /* Objects. */ else if (parse_keyword(p, end, "o")) { state_shaded_smooth = false; state_group_name = ""; state_material_name = ""; - curr_geom = create_geometry(curr_geom, - GEOM_MESH, - StringRef(p, end).trim(), - r_global_vertices, - r_all_geometries, - offsets); + curr_geom = create_geometry( + curr_geom, GEOM_MESH, StringRef(p, end).trim(), r_all_geometries); } /* Groups. */ else if (parse_keyword(p, end, "g")) { @@ -540,7 +537,7 @@ void OBJParser::parse(Vector> &r_all_geometries, add_mtl_library(StringRef(p, end).trim()); } else if (parse_keyword(p, end, "#MRGB")) { - geom_add_mrgb_colors(curr_geom, p, end, r_global_vertices); + geom_add_mrgb_colors(p, end, r_global_vertices); } /* Comments. */ else if (*p == '#') { @@ -548,8 +545,7 @@ void OBJParser::parse(Vector> &r_all_geometries, } /* Curve related things. */ else if (parse_keyword(p, end, "cstype")) { - curr_geom = geom_set_curve_type( - curr_geom, p, end, r_global_vertices, state_group_name, offsets, r_all_geometries); + curr_geom = geom_set_curve_type(curr_geom, p, end, state_group_name, r_all_geometries); } else if (parse_keyword(p, end, "deg")) { geom_set_curve_degree(curr_geom, p, end); diff --git a/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc b/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc index 8cef0700b3b..01f05466b3a 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc +++ b/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc @@ -31,13 +31,17 @@ Object *MeshFromGeometry::create_mesh(Main *bmain, Map &created_materials, const OBJImportParams &import_params) { + const int64_t tot_verts_object{mesh_geometry_.get_vertex_count()}; + if (tot_verts_object <= 0) { + /* Empty mesh */ + return nullptr; + } std::string ob_name{mesh_geometry_.geometry_name_}; if (ob_name.empty()) { ob_name = "Untitled"; } fixup_invalid_faces(); - const int64_t tot_verts_object{mesh_geometry_.vertex_count_}; /* Total explicitly imported edges, not the ones belonging the polygons to be created. */ const int64_t tot_edges{mesh_geometry_.edges_.size()}; const int64_t tot_face_elems{mesh_geometry_.face_elements_.size()}; @@ -153,9 +157,9 @@ void MeshFromGeometry::fixup_invalid_faces() void MeshFromGeometry::create_vertices(Mesh *mesh) { - const int tot_verts_object{mesh_geometry_.vertex_count_}; + const int tot_verts_object{mesh_geometry_.get_vertex_count()}; for (int i = 0; i < tot_verts_object; ++i) { - int vi = mesh_geometry_.vertex_start_ + i; + int vi = mesh_geometry_.vertex_index_min_ + i; if (vi < global_vertices_.vertices.size()) { copy_v3_v3(mesh->mvert[i].co, global_vertices_.vertices[vi]); } @@ -170,7 +174,7 @@ void MeshFromGeometry::create_vertices(Mesh *mesh) void MeshFromGeometry::create_polys_loops(Mesh *mesh, bool use_vertex_groups) { mesh->dvert = nullptr; - const int64_t total_verts = mesh_geometry_.vertex_count_; + const int64_t total_verts = mesh_geometry_.get_vertex_count(); if (use_vertex_groups && total_verts && mesh_geometry_.has_vertex_groups_) { mesh->dvert = static_cast( CustomData_add_layer(&mesh->vdata, CD_MDEFORMVERT, CD_CALLOC, nullptr, total_verts)); @@ -204,7 +208,7 @@ void MeshFromGeometry::create_polys_loops(Mesh *mesh, bool use_vertex_groups) const PolyCorner &curr_corner = mesh_geometry_.face_corners_[curr_face.start_index_ + idx]; MLoop &mloop = mesh->mloop[tot_loop_idx]; tot_loop_idx++; - mloop.v = curr_corner.vert_index; + mloop.v = curr_corner.vert_index - mesh_geometry_.vertex_index_min_; /* Setup vertex group data, if needed. */ if (!mesh->dvert) { @@ -231,14 +235,14 @@ void MeshFromGeometry::create_vertex_groups(Object *obj) void MeshFromGeometry::create_edges(Mesh *mesh) { const int64_t tot_edges{mesh_geometry_.edges_.size()}; - const int64_t total_verts{mesh_geometry_.vertex_count_}; + const int64_t total_verts{mesh_geometry_.get_vertex_count()}; UNUSED_VARS_NDEBUG(total_verts); for (int i = 0; i < tot_edges; ++i) { const MEdge &src_edge = mesh_geometry_.edges_[i]; MEdge &dst_edge = mesh->medge[i]; - BLI_assert(src_edge.v1 < total_verts && src_edge.v2 < total_verts); - dst_edge.v1 = src_edge.v1; - dst_edge.v2 = src_edge.v2; + dst_edge.v1 = src_edge.v1 - mesh_geometry_.vertex_index_min_; + dst_edge.v2 = src_edge.v2 - mesh_geometry_.vertex_index_min_; + BLI_assert(dst_edge.v1 < total_verts && dst_edge.v2 < total_verts); dst_edge.flag = ME_LOOSEEDGE; } @@ -312,10 +316,8 @@ void MeshFromGeometry::create_materials(Main *bmain, void MeshFromGeometry::create_normals(Mesh *mesh) { - /* NOTE: Needs more clarity about what is expected in the viewport if the function works. */ - /* No normal data: nothing to do. */ - if (global_vertices_.vertex_normals.is_empty() || !mesh_geometry_.has_vertex_normals_) { + if (global_vertices_.vertex_normals.is_empty()) { return; } @@ -341,23 +343,26 @@ void MeshFromGeometry::create_normals(Mesh *mesh) void MeshFromGeometry::create_colors(Mesh *mesh) { - /* Nothing to do if we don't have vertex colors. */ - if (mesh_geometry_.vertex_color_count_ < 1) { - return; - } - if (mesh_geometry_.vertex_color_count_ != mesh_geometry_.vertex_count_) { - std::cerr << "Mismatching number of vertices (" << mesh_geometry_.vertex_count_ - << ") and colors (" << mesh_geometry_.vertex_color_count_ << ") on object '" - << mesh_geometry_.geometry_name_ << "', ignoring colors." << std::endl; + /* Nothing to do if we don't have vertex colors at all. */ + if (global_vertices_.vertex_colors.is_empty()) { return; } - CustomDataLayer *color_layer = BKE_id_attribute_new( - &mesh->id, "Color", CD_PROP_COLOR, ATTR_DOMAIN_POINT, nullptr); - float4 *colors = (float4 *)color_layer->data; - for (int i = 0; i < mesh_geometry_.vertex_color_count_; ++i) { - float3 c = global_vertices_.vertex_colors[mesh_geometry_.vertex_color_start_ + i]; - colors[i] = float4(c.x, c.y, c.z, 1.0f); + /* Find which vertex color block is for this mesh (if any). */ + for (const auto &block : global_vertices_.vertex_colors) { + if (mesh_geometry_.vertex_index_min_ >= block.start_vertex_index && + mesh_geometry_.vertex_index_max_ < block.start_vertex_index + block.colors.size()) { + /* This block is suitable, use colors from it. */ + CustomDataLayer *color_layer = BKE_id_attribute_new( + &mesh->id, "Color", CD_PROP_COLOR, ATTR_DOMAIN_POINT, nullptr); + float4 *colors = (float4 *)color_layer->data; + int offset = mesh_geometry_.vertex_index_min_ - block.start_vertex_index; + for (int i = 0, n = mesh_geometry_.get_vertex_count(); i != n; ++i) { + float3 c = block.colors[offset + i]; + colors[i] = float4(c.x, c.y, c.z, 1.0f); + } + return; + } } } diff --git a/source/blender/io/wavefront_obj/importer/obj_import_objects.hh b/source/blender/io/wavefront_obj/importer/obj_import_objects.hh index 3d6733d661e..76436315465 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_objects.hh +++ b/source/blender/io/wavefront_obj/importer/obj_import_objects.hh @@ -19,35 +19,22 @@ namespace blender::io::obj { /** - * List of all vertex and UV vertex coordinates in an OBJ file accessible to any - * Geometry instance at any time. + * All vertex positions, normals, UVs, colors in the OBJ file. */ struct GlobalVertices { Vector vertices; Vector uv_vertices; Vector vertex_normals; - Vector vertex_colors; -}; - -/** - * Keeps track of the vertices that belong to other Geometries. - * Needed only for MLoop.v and MEdge.v1 which needs vertex indices ranging from (0 to total - * vertices in the mesh) as opposed to the other OBJ indices ranging from (0 to total vertices - * in the global list). - */ -struct VertexIndexOffset { - private: - int offset_ = 0; - public: - void set_index_offset(const int64_t total_vertices) - { - offset_ = total_vertices; - } - int64_t get_index_offset() const - { - return offset_; - } + /* Vertex colors might not be present in the file at all, or only + * provided for some meshes. Store them in chunks as they are + * spelled out in the file, e.g. if there are 10 vertices in sequence, all + * with "xyzrgb" colors, they will be one block. */ + struct VertexColorsBlock { + Vector colors; + int start_vertex_index; + }; + Vector vertex_colors; }; /** @@ -101,10 +88,8 @@ struct Geometry { Map material_indices_; Vector material_order_; - int vertex_start_ = 0; - int vertex_count_ = 0; - int vertex_color_start_ = 0; - int vertex_color_count_ = 0; + int vertex_index_min_ = INT_MAX; + int vertex_index_max_ = -1; /** Edges written in the file in addition to (or even without polygon) elements. */ Vector edges_; @@ -112,10 +97,21 @@ struct Geometry { Vector face_elements_; bool has_invalid_polys_ = false; - bool has_vertex_normals_ = false; bool has_vertex_groups_ = false; NurbsElement nurbs_element_; int total_loops_ = 0; + + int get_vertex_count() const + { + if (vertex_index_max_ < vertex_index_min_) + return 0; + return vertex_index_max_ - vertex_index_min_ + 1; + } + void track_vertex_index(int index) + { + vertex_index_min_ = std::min(vertex_index_min_, index); + vertex_index_max_ = std::max(vertex_index_max_, index); + } }; } // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc b/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc index eeb81f5e23e..02565556c37 100644 --- a/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc @@ -158,6 +158,36 @@ TEST_F(obj_importer_test, import_cube) import_and_check("cube.obj", expect, std::size(expect), 1); } +TEST_F(obj_importer_test, import_cube_o_after_verts) +{ + Expectation expect[] = { + {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, + { + "OBActualCube", + OB_MESH, + 8, + 12, + 6, + 24, + float3(-1, -1, 1), + float3(1, -1, -1), + float3(0, 0, 1), + }, + { + "OBSparseTri", + OB_MESH, + 6, + 3, + 1, + 3, + float3(1, -1, 1), + float3(-2, -2, 2), + float3(-0.2357f, 0.9428f, 0.2357f), + }, + }; + import_and_check("cube_o_after_verts.obj", expect, std::size(expect), 2); +} + TEST_F(obj_importer_test, import_suzanne_all_data) { Expectation expect[] = { @@ -293,13 +323,13 @@ TEST_F(obj_importer_test, import_faces_invalid_or_with_holes) float3(1, 0, -1)}, {"OBFaceQuadDupSomeVerts_BecomesOneQuadUsing4Verts", OB_MESH, - 8, + 4, 4, 1, 4, float3(3, 0, -2), - float3(6, 0, -1)}, - {"OBFaceTriDupVert_Becomes1Tri", OB_MESH, 8, 3, 1, 3, float3(-2, 0, 3), float3(1, 0, 4)}, + float3(7, 0, -2)}, + {"OBFaceTriDupVert_Becomes1Tri", OB_MESH, 3, 3, 1, 3, float3(-2, 0, 3), float3(2, 0, 7)}, {"OBFaceAllVertsDup_BecomesOneOverlappingFaceUsingAllVerts", OB_MESH, 8, @@ -316,7 +346,7 @@ TEST_F(obj_importer_test, import_faces_invalid_or_with_holes) 8, float3(8, 0, -2), float3(11, 0, -1)}, - {"OBFaceJustTwoVerts_IsSkipped", OB_MESH, 8, 0, 0, 0, float3(8, 0, 3), float3(11, 0, 4)}, + {"OBFaceJustTwoVerts_IsSkipped", OB_MESH, 2, 0, 0, 0, float3(8, 0, 3), float3(8, 0, 7)}, }; import_and_check("faces_invalid_or_with_holes.obj", expect, std::size(expect), 0); } @@ -327,12 +357,12 @@ TEST_F(obj_importer_test, import_invalid_indices) {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, {"OBQuad", OB_MESH, - 4, + 3, 3, 1, 3, float3(-2, 0, -2), - float3(2, 0, -2), + float3(2, 0, 2), float3(0, 1, 0), float2(0.5f, 0.25f)}, }; @@ -345,12 +375,12 @@ TEST_F(obj_importer_test, import_invalid_syntax) {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, {"OBObjectWithAReallyLongNameToCheckHowImportHandlesNamesThatAreLon", OB_MESH, - 10, /* NOTE: right now parses some invalid obj syntax as valid vertices. */ + 3, 3, 1, 3, float3(1, 2, 3), - float3(10, 11, 12), + float3(7, 8, 9), float3(0, 1, 0), float2(0.5f, 0.25f)}, }; @@ -585,29 +615,52 @@ TEST_F(obj_importer_test, import_cubes_vertex_colors) TEST_F(obj_importer_test, import_cubes_vertex_colors_mrgb) { - Expectation expect[] = {{"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, - {"OBCubeXYZRGB", - OB_MESH, - 8, - 12, - 6, - 24, - float3(1, 1, -1), - float3(-1, -1, 1), - float3(0, 0, 0), - float2(0, 0), - float4(0.6038f, 0.3185f, 0.1329f, 1.0f)}, - {"OBCubeMRGB", - OB_MESH, - 8, - 12, - 6, - 24, - float3(4, 1, -1), - float3(2, -1, 1), - float3(0, 0, 0), - float2(0, 0), - float4(0.8714f, 0.6308f, 0.5271f, 1.0f)}}; + Expectation expect[] = { + {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, + {"OBCubeXYZRGB", + OB_MESH, + 8, + 12, + 6, + 24, + float3(1, 1, -1), + float3(-1, -1, 1), + float3(0, 0, 0), + float2(0, 0), + float4(0.6038f, 0.3185f, 0.1329f, 1.0f)}, + {"OBCubeMRGB", + OB_MESH, + 8, + 12, + 6, + 24, + float3(4, 1, -1), + float3(2, -1, 1), + float3(0, 0, 0), + float2(0, 0), + float4(0.8714f, 0.6308f, 0.5271f, 1.0f)}, + { + "OBTriNoColors", + OB_MESH, + 3, + 3, + 1, + 3, + float3(8, 1, -1), + float3(6, 0, -1), + }, + {"OBTriMRGB", + OB_MESH, + 3, + 3, + 1, + 3, + float3(12, 1, -1), + float3(10, 0, -1), + float3(0, 0, 0), + float2(0, 0), + float4(1.0f, 0.0f, 0.0f, 1.0f)}, + }; import_and_check("cubes_vertex_colors_mrgb.obj", expect, std::size(expect), 0); } -- cgit v1.2.3 From 65432901162c0dff124d55a04875050fd0f1ac22 Mon Sep 17 00:00:00 2001 From: Howard Trickey Date: Sun, 10 Jul 2022 14:50:17 -0400 Subject: Fix an assert trip in boolean tickled by D11272 example. The face merging code in exact boolean made an assumption that the tesselated original face was manifold except at the boundaries. This should be true but sometimes (e.g., if the input faces have self-intersection, as happens in the example), it is not. This commit makes face merging tolerant of such a situation. It might leave some stray edges from triangulation, but it should only happen if the input is malformed. Note: the input may be malformed if there were previous booleans in the stack, since snapping the exact result to float coordinates is not guaranteed to leave the mesh without defects. --- source/blender/blenlib/intern/mesh_boolean.cc | 32 +++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/source/blender/blenlib/intern/mesh_boolean.cc b/source/blender/blenlib/intern/mesh_boolean.cc index 700c126ca4c..464b8f4139e 100644 --- a/source/blender/blenlib/intern/mesh_boolean.cc +++ b/source/blender/blenlib/intern/mesh_boolean.cc @@ -2966,6 +2966,11 @@ static std::ostream &operator<<(std::ostream &os, const FaceMergeState &fms) * \a tris all have the same original face. * Find the 2d edge/triangle topology for these triangles, but only the ones facing in the * norm direction, and whether each edge is dissolvable or not. + * If we did the initial triangulation properly, and any Delaunay triangulations of interections + * properly, then each triangle edge should have at most one neighbor. + * However, there can be anonalies. For example, if an input face is self-intersecting, we fall + * back on the floating poing polyfill triangulation, which, after which all bets are off. + * Hence, try to be tolerant of such unexpected topology. */ static void init_face_merge_state(FaceMergeState *fms, const Vector &tris, @@ -3053,16 +3058,35 @@ static void init_face_merge_state(FaceMergeState *fms, std::cout << "me.v1 == mf.vert[i] so set edge[" << me_index << "].left_face = " << f << "\n"; } - BLI_assert(me.left_face == -1); - fms->edge[me_index].left_face = f; + if (me.left_face != 1) { + /* Unexpected in the normal case: this means more than one triangle shares this + * edge in the same orientation. But be tolerant of this case. By making this + * edge not dissolvable, we'll avoid future problems due to this non-manifold topology. + */ + if (dbg_level > 1) { + std::cout << "me.left_face was already occupied, so triangulation wasn't good\n"; + } + me.dissolvable = false; + } + else { + fms->edge[me_index].left_face = f; + } } else { if (dbg_level > 1) { std::cout << "me.v1 != mf.vert[i] so set edge[" << me_index << "].right_face = " << f << "\n"; } - BLI_assert(me.right_face == -1); - fms->edge[me_index].right_face = f; + if (me.right_face != -1) { + /* Unexpected, analogous to the me.left_face != -1 case above. */ + if (dbg_level > 1) { + std::cout << "me.right_face was already occupied, so triangulation wasn't good\n"; + } + me.dissolvable = false; + } + else { + fms->edge[me_index].right_face = f; + } } fms->face[f].edge.append(me_index); } -- cgit v1.2.3 From 7f4ee97b9ef93b0c8f9fc89d47ee8535ab665327 Mon Sep 17 00:00:00 2001 From: Howard Trickey Date: Sun, 10 Jul 2022 18:50:11 -0400 Subject: Revert "Fix an assert trip in boolean tickled by D11272 example." This reverts commit 65432901162c0dff124d55a04875050fd0f1ac22. It broke tests and I don't know why, so reverting this while figuring that out. --- source/blender/blenlib/intern/mesh_boolean.cc | 32 ++++----------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/source/blender/blenlib/intern/mesh_boolean.cc b/source/blender/blenlib/intern/mesh_boolean.cc index 464b8f4139e..700c126ca4c 100644 --- a/source/blender/blenlib/intern/mesh_boolean.cc +++ b/source/blender/blenlib/intern/mesh_boolean.cc @@ -2966,11 +2966,6 @@ static std::ostream &operator<<(std::ostream &os, const FaceMergeState &fms) * \a tris all have the same original face. * Find the 2d edge/triangle topology for these triangles, but only the ones facing in the * norm direction, and whether each edge is dissolvable or not. - * If we did the initial triangulation properly, and any Delaunay triangulations of interections - * properly, then each triangle edge should have at most one neighbor. - * However, there can be anonalies. For example, if an input face is self-intersecting, we fall - * back on the floating poing polyfill triangulation, which, after which all bets are off. - * Hence, try to be tolerant of such unexpected topology. */ static void init_face_merge_state(FaceMergeState *fms, const Vector &tris, @@ -3058,35 +3053,16 @@ static void init_face_merge_state(FaceMergeState *fms, std::cout << "me.v1 == mf.vert[i] so set edge[" << me_index << "].left_face = " << f << "\n"; } - if (me.left_face != 1) { - /* Unexpected in the normal case: this means more than one triangle shares this - * edge in the same orientation. But be tolerant of this case. By making this - * edge not dissolvable, we'll avoid future problems due to this non-manifold topology. - */ - if (dbg_level > 1) { - std::cout << "me.left_face was already occupied, so triangulation wasn't good\n"; - } - me.dissolvable = false; - } - else { - fms->edge[me_index].left_face = f; - } + BLI_assert(me.left_face == -1); + fms->edge[me_index].left_face = f; } else { if (dbg_level > 1) { std::cout << "me.v1 != mf.vert[i] so set edge[" << me_index << "].right_face = " << f << "\n"; } - if (me.right_face != -1) { - /* Unexpected, analogous to the me.left_face != -1 case above. */ - if (dbg_level > 1) { - std::cout << "me.right_face was already occupied, so triangulation wasn't good\n"; - } - me.dissolvable = false; - } - else { - fms->edge[me_index].right_face = f; - } + BLI_assert(me.right_face == -1); + fms->edge[me_index].right_face = f; } fms->face[f].edge.append(me_index); } -- cgit v1.2.3 From a83502f05f017fc4ad5bd910aff32fa457ad6702 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 11 Jul 2022 10:38:02 +1000 Subject: Cleanup: remove unused GHOST function getAnyModifiedState. Remove unused GHOST_WindowManager::getAnyModifiedState() --- intern/ghost/intern/GHOST_WindowManager.cpp | 14 -------------- intern/ghost/intern/GHOST_WindowManager.h | 6 ------ 2 files changed, 20 deletions(-) diff --git a/intern/ghost/intern/GHOST_WindowManager.cpp b/intern/ghost/intern/GHOST_WindowManager.cpp index 19684a44169..e8785cbdb24 100644 --- a/intern/ghost/intern/GHOST_WindowManager.cpp +++ b/intern/ghost/intern/GHOST_WindowManager.cpp @@ -162,17 +162,3 @@ GHOST_IWindow *GHOST_WindowManager::getWindowAssociatedWithOSWindow(void *osWind } return nullptr; } - -bool GHOST_WindowManager::getAnyModifiedState() -{ - bool isAnyModified = false; - std::vector::iterator iter; - - for (iter = m_windows.begin(); iter != m_windows.end(); ++iter) { - if ((*iter)->getModifiedState()) { - isAnyModified = true; - } - } - - return isAnyModified; -} diff --git a/intern/ghost/intern/GHOST_WindowManager.h b/intern/ghost/intern/GHOST_WindowManager.h index 9d20413c433..bf7a0f4ec61 100644 --- a/intern/ghost/intern/GHOST_WindowManager.h +++ b/intern/ghost/intern/GHOST_WindowManager.h @@ -109,12 +109,6 @@ class GHOST_WindowManager { */ GHOST_IWindow *getWindowAssociatedWithOSWindow(void *osWindow); - /** - * Return true if any windows has a modified status - * \return True if any window has unsaved changes - */ - bool getAnyModifiedState(); - protected: /** The list of windows managed */ std::vector m_windows; -- cgit v1.2.3 From d4a4691c0c395967e7e12d2405b561d1fd0b6365 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 11 Jul 2022 10:38:04 +1000 Subject: Cleanup: spelling in comments --- source/blender/blenkernel/BKE_mesh.h | 2 +- source/blender/blenloader/intern/versioning_legacy.c | 2 +- source/blender/draw/engines/eevee/eevee_sampling.c | 5 +++-- source/blender/editors/screen/screen_ops.c | 2 +- source/blender/editors/space_file/file_ops.c | 2 +- source/blender/io/wavefront_obj/importer/obj_import_objects.hh | 6 ++++-- .../nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc | 2 +- 7 files changed, 12 insertions(+), 9 deletions(-) diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h index 366083fee7f..731c9872aae 100644 --- a/source/blender/blenkernel/BKE_mesh.h +++ b/source/blender/blenkernel/BKE_mesh.h @@ -986,7 +986,7 @@ void BKE_mesh_strip_loose_edges(struct Mesh *me); /** * If the mesh is from a very old blender version, - * convert mface->edcode to edge drawflags + * convert #MFace.edcode to edge #ME_EDGEDRAW. */ void BKE_mesh_calc_edges_legacy(struct Mesh *me, bool use_old); void BKE_mesh_calc_edges_loose(struct Mesh *mesh); diff --git a/source/blender/blenloader/intern/versioning_legacy.c b/source/blender/blenloader/intern/versioning_legacy.c index a3f17878f68..75cc333e4b5 100644 --- a/source/blender/blenloader/intern/versioning_legacy.c +++ b/source/blender/blenloader/intern/versioning_legacy.c @@ -1468,7 +1468,7 @@ void blo_do_versions_pre250(FileData *fd, Library *lib, Main *bmain) for (me = bmain->meshes.first; me; me = me->id.next) { if (!me->medge) { - BKE_mesh_calc_edges_legacy(me, true); /* true = use mface->edcode */ + BKE_mesh_calc_edges_legacy(me, true); /* true = use #MFace.edcode. */ } else { BKE_mesh_strip_loose_faces(me); diff --git a/source/blender/draw/engines/eevee/eevee_sampling.c b/source/blender/draw/engines/eevee/eevee_sampling.c index a1a3e98f34f..34d3cd74b36 100644 --- a/source/blender/draw/engines/eevee/eevee_sampling.c +++ b/source/blender/draw/engines/eevee/eevee_sampling.c @@ -74,7 +74,8 @@ void EEVEE_sample_ellipse(int sample_ofs, BLI_halton_2d(ht_primes, ht_offset, sample_ofs, ht_point); - /* Decorelate AA and shadow samples. (see T68594) */ + /* Decorrelate AA and shadow samples. (see T68594) */ + ht_point[0] = fmod(ht_point[0] * 1151.0, 1.0); ht_point[1] = fmod(ht_point[1] * 1069.0, 1.0); @@ -97,7 +98,7 @@ void EEVEE_random_rotation_m4(int sample_ofs, float scale, float r_mat[4][4]) BLI_halton_3d(ht_primes, ht_offset, sample_ofs, ht_point); - /* Decorelate AA and shadow samples. (see T68594) */ + /* Decorrelate AA and shadow samples. (see T68594) */ ht_point[0] = fmod(ht_point[0] * 1151.0, 1.0); ht_point[1] = fmod(ht_point[1] * 1069.0, 1.0); ht_point[2] = fmod(ht_point[2] * 1151.0, 1.0); diff --git a/source/blender/editors/screen/screen_ops.c b/source/blender/editors/screen/screen_ops.c index c616ca2b5eb..3618b933443 100644 --- a/source/blender/editors/screen/screen_ops.c +++ b/source/blender/editors/screen/screen_ops.c @@ -4718,7 +4718,7 @@ static int screen_animation_step_invoke(bContext *C, wmOperator *UNUSED(op), con #endif } - /* since we follow drawflags, we can't send notifier but tag regions ourselves */ + /* Since we follow draw-flags, we can't send notifier but tag regions ourselves. */ if (depsgraph != NULL) { ED_update_for_newframe(bmain, depsgraph); } diff --git a/source/blender/editors/space_file/file_ops.c b/source/blender/editors/space_file/file_ops.c index 62bdd583bc1..59d9a15fbab 100644 --- a/source/blender/editors/space_file/file_ops.c +++ b/source/blender/editors/space_file/file_ops.c @@ -1793,7 +1793,7 @@ static bool file_execute(bContext *C, SpaceFile *sfile) } ED_file_change_dir(C); } - /* opening file - sends events now, so things get handled on windowqueue level */ + /* Opening file, sends events now, so things get handled on window-queue level. */ else if (sfile->op) { wmOperator *op = sfile->op; char filepath[FILE_MAX]; diff --git a/source/blender/io/wavefront_obj/importer/obj_import_objects.hh b/source/blender/io/wavefront_obj/importer/obj_import_objects.hh index 76436315465..9f0079d7c53 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_objects.hh +++ b/source/blender/io/wavefront_obj/importer/obj_import_objects.hh @@ -26,10 +26,12 @@ struct GlobalVertices { Vector uv_vertices; Vector vertex_normals; - /* Vertex colors might not be present in the file at all, or only + /** + * Vertex colors might not be present in the file at all, or only * provided for some meshes. Store them in chunks as they are * spelled out in the file, e.g. if there are 10 vertices in sequence, all - * with "xyzrgb" colors, they will be one block. */ + * with `xyzrgb` colors, they will be one block. + */ struct VertexColorsBlock { Vector colors; int start_vertex_index; diff --git a/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc index ab7ddfa71f1..f08e857e9cc 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc @@ -136,7 +136,7 @@ static void deform_curves(CurvesGeometry &curves, * * TODO: Figure out if this can be smoothly interpolated across the surface as well. * Currently, this is a source of discontinuity in the deformation, because the vector - * changes intantly from one triangle to the next. */ + * changes instantly from one triangle to the next. */ const float3 tangent_reference_dir_old = rest_pos_1 - rest_pos_0; const float3 tangent_reference_dir_new = pos_1_new - pos_0_new; -- cgit v1.2.3 From d51bc8215f515144ae51cc5feec01b3dbc7900c4 Mon Sep 17 00:00:00 2001 From: YimingWu Date: Mon, 11 Jul 2022 10:12:49 +0800 Subject: GPencil: Dot-dash modifier rename segment bug fix. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch fixes naming and renaming issue with dot-dash modifier segment list. Before: when double clicking and exiting it would append number at the end regardless of name being changed or not. Now it works like in other areas. Authored by: Aleš Jelovčan (frogstomp) Reviewed By: YimingWu (NicksBest) Differential Revision: https://developer.blender.org/D15359 --- release/scripts/addons | 2 +- source/blender/makesrna/intern/rna_gpencil_modifier.c | 2 +- source/tools | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/release/scripts/addons b/release/scripts/addons index 403b95ef6ff..7ea2e74fc41 160000 --- a/release/scripts/addons +++ b/release/scripts/addons @@ -1 +1 @@ -Subproject commit 403b95ef6ff38918de966ed2a5843cfa3274a58b +Subproject commit 7ea2e74fc41b2eabdbf639b812082e73823b09d7 diff --git a/source/blender/makesrna/intern/rna_gpencil_modifier.c b/source/blender/makesrna/intern/rna_gpencil_modifier.c index dccf7d7a7a9..d3e1aab1ba0 100644 --- a/source/blender/makesrna/intern/rna_gpencil_modifier.c +++ b/source/blender/makesrna/intern/rna_gpencil_modifier.c @@ -774,7 +774,7 @@ static bool dash_segment_name_exists_fn(void *arg, const char *name) { const DashGpencilModifierData *dmd = (const DashGpencilModifierData *)arg; for (int i = 0; i < dmd->segments_len; i++) { - if (STREQ(dmd->segments[i].name, name)) { + if (STREQ(dmd->segments[i].name, name) && dmd->segments[i].name != name) { return true; } } diff --git a/source/tools b/source/tools index 01b4c0e4a17..da8bdd7244c 160000 --- a/source/tools +++ b/source/tools @@ -1 +1 @@ -Subproject commit 01b4c0e4a172819414229445c314be34527bf412 +Subproject commit da8bdd7244c7b6c2eadf4c949ff391d0cc430275 -- cgit v1.2.3 From 133d398120bfa5c6fe35e93b424cc86543747ccd Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 11 Jul 2022 12:43:24 +1000 Subject: PyAPI: add Matrix.is_identity read-only attribute Add a convenient way of checking if the matrix is an identity matrix. --- source/blender/python/mathutils/mathutils_Matrix.c | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/source/blender/python/mathutils/mathutils_Matrix.c b/source/blender/python/mathutils/mathutils_Matrix.c index 8cd7a5c7d87..1e85ece124d 100644 --- a/source/blender/python/mathutils/mathutils_Matrix.c +++ b/source/blender/python/mathutils/mathutils_Matrix.c @@ -290,6 +290,18 @@ static PyObject *matrix__apply_to_copy(PyObject *(*matrix_func)(MatrixObject *), return NULL; } +static bool matrix_is_identity(MatrixObject *self) +{ + for (int row = 0; row < self->row_num; row++) { + for (int col = 0; col < self->col_num; col++) { + if (MATRIX_ITEM(self, row, col) != ((row != col) ? 0.0f : 1.0f)) { + return false; + } + } + } + return true; +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -3104,6 +3116,16 @@ static PyObject *Matrix_median_scale_get(MatrixObject *self, void *UNUSED(closur return PyFloat_FromDouble(mat3_to_scale(mat)); } +PyDoc_STRVAR(Matrix_is_identity_doc, + "True if this is an identity matrix (read-only).\n\n:type: bool"); +static PyObject *Matrix_is_identity_get(MatrixObject *self, void *UNUSED(closure)) +{ + if (BaseMath_ReadCallback(self) == -1) { + return NULL; + } + return PyBool_FromLong(matrix_is_identity(self)); +} + PyDoc_STRVAR(Matrix_is_negative_doc, "True if this matrix results in a negative scale, 3x3 and 4x4 only, " "(read-only).\n\n:type: bool"); @@ -3187,6 +3209,7 @@ static PyGetSetDef Matrix_getseters[] = { NULL}, {"row", (getter)Matrix_row_get, (setter)NULL, Matrix_row_doc, NULL}, {"col", (getter)Matrix_col_get, (setter)NULL, Matrix_col_doc, NULL}, + {"is_identity", (getter)Matrix_is_identity_get, (setter)NULL, Matrix_is_identity_doc, NULL}, {"is_negative", (getter)Matrix_is_negative_get, (setter)NULL, Matrix_is_negative_doc, NULL}, {"is_orthogonal", (getter)Matrix_is_orthogonal_get, -- cgit v1.2.3 From cb39058f2f31815279ac72fce08ead7cbc979517 Mon Sep 17 00:00:00 2001 From: Joseph Eagar Date: Sun, 10 Jul 2022 23:39:45 -0700 Subject: Fix T94633: Sculpt mode missing check for hidden active object Note there is a bug in BKE_object_is_visible_in_viewport, it returns false when the object is in local mode. The transform operator poll should do a similar test. That would allow us to move the test from sculpt_brush_strok_invoke to SCULPT_mode_poll (at the moment we cannot do this due to the brush operator falling through to the translate keymap item in global view3d keymap). --- source/blender/editors/sculpt_paint/sculpt.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index 906c4fd35fe..ae65ca8178b 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -5499,10 +5499,23 @@ static int sculpt_brush_stroke_invoke(bContext *C, wmOperator *op, const wmEvent struct PaintStroke *stroke; int ignore_background_click; int retval; + Object *ob = CTX_data_active_object(C); + + /* Test that ob is visible; otherwise we won't be able to get evaluated data + * from the depsgraph. We do this here instead of SCULPT_mode_poll + * to avoid falling through to the translate operator in the + * global view3d keymap. + * + * Note: BKE_object_is_visible_in_viewport is not working here (it returns false + * if the object is in local view); instead, test for OB_HIDE_VIEWPORT directly. + */ + + if (ob->visibility_flag & OB_HIDE_VIEWPORT) { + return OPERATOR_CANCELLED; + } sculpt_brush_stroke_init(C, op); - Object *ob = CTX_data_active_object(C); Sculpt *sd = CTX_data_tool_settings(C)->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); -- cgit v1.2.3 From 7357176b5739ce9ea0d94d9ecdeaa0af7d64722e Mon Sep 17 00:00:00 2001 From: Joseph Eagar Date: Mon, 11 Jul 2022 00:19:53 -0700 Subject: Fix T99383: Wrong origdata type in color filter --- source/blender/editors/sculpt_paint/sculpt_filter_color.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_color.c b/source/blender/editors/sculpt_paint/sculpt_filter_color.c index a5d9f5306e2..a9186010a9f 100644 --- a/source/blender/editors/sculpt_paint/sculpt_filter_color.c +++ b/source/blender/editors/sculpt_paint/sculpt_filter_color.c @@ -93,7 +93,7 @@ static void color_filter_task_cb(void *__restrict userdata, const int mode = data->filter_type; SculptOrigVertData orig_data; - SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n], SCULPT_UNDO_COORDS); + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n], SCULPT_UNDO_COLOR); PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { -- cgit v1.2.3 From da101118d4a5b1fe39181c4caaf78ccd4d715239 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Mon, 11 Jul 2022 11:14:21 +0200 Subject: Cleanup: Remove unused operator name storage in UI lists --- source/blender/editors/interface/interface_handlers.c | 6 +++--- source/blender/makesdna/DNA_screen_types.h | 6 ------ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 7c00c4f1875..21fd14b86b7 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -9487,7 +9487,7 @@ static int ui_list_activate_hovered_row(bContext *C, } /* Simulate click on listrow button itself (which may be overlapped by another button). Also - * calls the custom activate operator (ui_list->custom_activate_opname). */ + * calls the custom activate operator (#uiListDyn::custom_activate_optype). */ UI_but_execute(C, region, listrow); ((uiList *)ui_list)->dyn_data->custom_activate_optype = custom_activate_optype; @@ -9558,13 +9558,13 @@ static void ui_list_activate_row_from_index( uiBut *new_active_row = ui_list_row_find_from_index(region, index, listbox); if (new_active_row) { /* Preferred way to update the active item, also calls the custom activate operator - * (#uiList.custom_activate_opname). */ + * (#uiListDyn::custom_activate_optype). */ UI_but_execute(C, region, new_active_row); } else { /* A bit ugly, set the active index in RNA directly. That's because a button that's * scrolled away in the list box isn't created at all. - * The custom activate operator (#uiList.custom_activate_opname) is not called in this case + * The custom activate operator (#uiListDyn::custom_activate_optype) is not called in this case * (which may need the row button context). */ RNA_property_int_set(&listbox->rnapoin, listbox->rnaprop, index); RNA_property_update(C, &listbox->rnapoin, listbox->rnaprop); diff --git a/source/blender/makesdna/DNA_screen_types.h b/source/blender/makesdna/DNA_screen_types.h index 8560f8a454e..e9178c0cbf5 100644 --- a/source/blender/makesdna/DNA_screen_types.h +++ b/source/blender/makesdna/DNA_screen_types.h @@ -305,12 +305,6 @@ typedef struct uiList { /* some list UI data need to be saved in file */ int filter_flag; int filter_sort_flag; - /** Operator executed when activating an item. */ - const char *custom_activate_opname; - /** Operator executed when dragging an item (item gets activated too, without running - * custom_activate_opname above). */ - const char *custom_drag_opname; - /* Custom sub-classes properties. */ IDProperty *properties; -- cgit v1.2.3 From 1c4c904786b55cf086a0536eda9594b0f49aefcf Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Mon, 11 Jul 2022 11:42:54 +0200 Subject: Deps Builder: Disable TermInfo and ncurses for DPC++ They are not strictly needed for compilation and disabling them makes the compiler more portable without any special trickery. This change aimed to solve problem which currently happens on the API documentation build which does not have terminfo installed, but needs to compile Cycles. Note that the DPC++ is to be re-compiled. --- build_files/build_environment/cmake/dpcpp.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build_files/build_environment/cmake/dpcpp.cmake b/build_files/build_environment/cmake/dpcpp.cmake index 563bc7aeff4..3c3fe201073 100644 --- a/build_files/build_environment/cmake/dpcpp.cmake +++ b/build_files/build_environment/cmake/dpcpp.cmake @@ -63,6 +63,8 @@ set(DPCPP_EXTRA_ARGS -DPython3_ROOT_DIR=${LIBDIR}/python/ -DPython3_EXECUTABLE=${PYTHON_BINARY} -DPYTHON_EXECUTABLE=${PYTHON_BINARY} + -DLLDB_ENABLE_CURSES=OFF + -DLLVM_ENABLE_TERMINFO=OFF ) if(WIN32) -- cgit v1.2.3 From 275419f6fd4cdc06a9f840e9031d4f5153da6b0a Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Mon, 11 Jul 2022 12:46:01 +0200 Subject: Fix/Cleanup UI messages. --- release/scripts/modules/bl_i18n_utils/settings.py | 8 ++++++++ .../scripts/modules/bl_i18n_utils/utils_spell_check.py | 8 +++++++- release/scripts/startup/bl_ui/space_sequencer.py | 2 +- source/blender/editors/space_node/node_add.cc | 2 +- .../gpencil_modifiers/intern/MOD_gpencillineart.c | 2 +- source/blender/makesrna/intern/rna_gpencil_modifier.c | 2 +- source/blender/nodes/NOD_static_types.h | 2 +- .../geometry/nodes/node_geo_deform_curves_on_surface.cc | 17 ++++++++--------- 8 files changed, 28 insertions(+), 15 deletions(-) diff --git a/release/scripts/modules/bl_i18n_utils/settings.py b/release/scripts/modules/bl_i18n_utils/settings.py index 408f8523b8d..7aeef80b0bd 100644 --- a/release/scripts/modules/bl_i18n_utils/settings.py +++ b/release/scripts/modules/bl_i18n_utils/settings.py @@ -297,6 +297,7 @@ WARN_MSGID_NOT_CAPITALIZED_ALLOWED = { "ascii", "author", # Addons' field. :/ "bItasc", + "blender.org", "color_index is invalid", "cos(A)", "cosh(A)", @@ -312,6 +313,7 @@ WARN_MSGID_NOT_CAPITALIZED_ALLOWED = { "glTF 2.0 (.glb/.gltf)", "glTF Binary (.glb)", "glTF Embedded (.gltf)", + "glTF Original PBR data", "glTF Separate (.gltf + .bin + textures)", "invoke() needs to be called before execute()", "iScale", @@ -330,6 +332,7 @@ WARN_MSGID_NOT_CAPITALIZED_ALLOWED = { "mp3", "normal", "ogg", + "oneAPI", "p0", "px", "re", @@ -340,6 +343,8 @@ WARN_MSGID_NOT_CAPITALIZED_ALLOWED = { "sinh(A)", "sqrt(x*x+y*y+z*z)", "sRGB", + "sRGB display space", + "sRGB display space with Filmic view transform", "tan(A)", "tanh(A)", "utf-8", @@ -356,7 +361,9 @@ WARN_MSGID_NOT_CAPITALIZED_ALLOWED = { "all and invert unselected", "and AMD driver version 22.10 or newer", "and AMD Radeon Pro 21.Q4 driver or newer", + "and Linux driver version xx.xx.28000 or newer", "and NVIDIA driver version 470 or newer", + "and Windows driver version 101.1660 or newer", "available with", "brown fox", "can't save image while rendering", @@ -431,6 +438,7 @@ WARN_MSGID_END_POINT_ALLOWED = { "The program will now close.", "Your graphics card or driver has limited support. It may work, but with issues.", "Your graphics card or driver is not supported.", + "Invalid surface UVs on %d curves.", } PARSER_CACHE_HASH = 'sha1' diff --git a/release/scripts/modules/bl_i18n_utils/utils_spell_check.py b/release/scripts/modules/bl_i18n_utils/utils_spell_check.py index 74785c81bfd..462c954d54a 100644 --- a/release/scripts/modules/bl_i18n_utils/utils_spell_check.py +++ b/release/scripts/modules/bl_i18n_utils/utils_spell_check.py @@ -433,6 +433,7 @@ class SpellChecker: "polyline", "polylines", "probabilistically", "pulldown", "pulldowns", + "quadratically", "quantized", "quartic", "quaternion", "quaternions", @@ -501,6 +502,7 @@ class SpellChecker: "luminance", "mantaflow", "matcap", + "microfacet", "midtones", "mipmap", "mipmaps", "mip", "ngon", "ngons", @@ -508,6 +510,7 @@ class SpellChecker: "nurb", "nurbs", "perlin", "phong", + "photorealistic", "pinlight", "posterize", "qi", @@ -675,7 +678,7 @@ class SpellChecker: "ascii", "atrac", "avx", - "bsdf", + "bsdf", "bsdfs", "bssrdf", "bw", "ccd", @@ -708,14 +711,17 @@ class SpellChecker: "hdc", "hdr", "hdri", "hdris", "hh", "mm", "ss", "ff", # hh:mm:ss:ff timecode + "hpg", # Intel Xe-HPG architecture "hsv", "hsva", "hsl", "id", "ies", "ior", "itu", "jonswap", + "lfe", "lhs", "lmb", "mmb", "rmb", + "lscm", "kb", "mocap", "msgid", "msgids", diff --git a/release/scripts/startup/bl_ui/space_sequencer.py b/release/scripts/startup/bl_ui/space_sequencer.py index 060c00c0443..a99df1164a0 100644 --- a/release/scripts/startup/bl_ui/space_sequencer.py +++ b/release/scripts/startup/bl_ui/space_sequencer.py @@ -1650,7 +1650,7 @@ class SEQUENCER_PT_source(SequencerButtonsPanel, Panel): if sound.samplerate <= 0: split.label(text="Unknown") else: - split.label(text="%d Hz." % sound.samplerate, translate=False) + split.label(text="%d Hz" % sound.samplerate, translate=False) split = col.split(factor=0.5, align=False) split.alignment = 'RIGHT' diff --git a/source/blender/editors/space_node/node_add.cc b/source/blender/editors/space_node/node_add.cc index 975d4eda7e3..04bf5ef469e 100644 --- a/source/blender/editors/space_node/node_add.cc +++ b/source/blender/editors/space_node/node_add.cc @@ -382,7 +382,7 @@ static bool node_add_group_poll(bContext *C) if (snode->edittree->type == NTREE_CUSTOM) { CTX_wm_operator_poll_msg_set(C, "This node editor displays a custom (Python defined) node tree. " - "Dropping node groups isn't supported for this."); + "Dropping node groups isn't supported for this"); return false; } return true; diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c index 4f00b997902..77616ae13b6 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c @@ -448,7 +448,7 @@ static void options_light_reference_draw(const bContext *UNUSED(C), Panel *panel uiItemR(col, ptr, "shadow_camera_near", 0, "Near", ICON_NONE); uiItemR(col, ptr, "shadow_camera_far", 0, "Far", ICON_NONE); - uiItemR(layout, ptr, "use_shadow_enclosed_shapes", 0, IFACE_("Eclosed Shapes"), ICON_NONE); + uiItemR(layout, ptr, "use_shadow_enclosed_shapes", 0, IFACE_("Enclosed Shapes"), ICON_NONE); } static void options_panel_draw(const bContext *UNUSED(C), Panel *panel) diff --git a/source/blender/makesrna/intern/rna_gpencil_modifier.c b/source/blender/makesrna/intern/rna_gpencil_modifier.c index d3e1aab1ba0..0647bc62081 100644 --- a/source/blender/makesrna/intern/rna_gpencil_modifier.c +++ b/source/blender/makesrna/intern/rna_gpencil_modifier.c @@ -3452,7 +3452,7 @@ static void rna_def_modifier_gpencillineart(BlenderRNA *brna) prop = RNA_def_property(srna, "use_shadow", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "edge_types", LRT_EDGE_FLAG_PROJECTED_SHADOW); RNA_def_property_ui_text( - prop, "Use Shadow", "Project contour lines using a light shource object"); + prop, "Use Shadow", "Project contour lines using a light source object"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); prop = RNA_def_property(srna, "shadow_region_filtering", PROP_ENUM, PROP_NONE); diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 7c7f114bb78..71d8f014399 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -64,7 +64,7 @@ DefNode(ShaderNode, SH_NODE_BSDF_DIFFUSE, 0, "BSD DefNode(ShaderNode, SH_NODE_BSDF_PRINCIPLED, def_principled, "BSDF_PRINCIPLED", BsdfPrincipled, "Principled BSDF", "Physically-based, easy-to-use shader for rendering surface materials, based on the Disney principled model also known as the \"PBR\" shader") DefNode(ShaderNode, SH_NODE_BSDF_GLOSSY, def_glossy, "BSDF_GLOSSY", BsdfGlossy, "Glossy BSDF", "Reflection with microfacet distribution, used for materials such as metal or mirrors") DefNode(ShaderNode, SH_NODE_BSDF_GLASS, def_glass, "BSDF_GLASS", BsdfGlass, "Glass BSDF", "Glass-like shader mixing refraction and reflection at grazing angles") -DefNode(ShaderNode, SH_NODE_BSDF_REFRACTION, def_refraction, "BSDF_REFRACTION", BsdfRefraction, "Refraction BSDF", "Glossy refraction with sharp or microfacet distribution,. Typically used for materials that transmit light") +DefNode(ShaderNode, SH_NODE_BSDF_REFRACTION, def_refraction, "BSDF_REFRACTION", BsdfRefraction, "Refraction BSDF", "Glossy refraction with sharp or microfacet distribution, typically used for materials that transmit light") DefNode(ShaderNode, SH_NODE_BSDF_TRANSLUCENT, 0, "BSDF_TRANSLUCENT", BsdfTranslucent, "Translucent BSDF", "Lambertian diffuse transmission") DefNode(ShaderNode, SH_NODE_BSDF_TRANSPARENT, 0, "BSDF_TRANSPARENT", BsdfTransparent, "Transparent BSDF", "Transparency without refraction, passing straight through the surface as if there were no geometry") DefNode(ShaderNode, SH_NODE_BSDF_VELVET, 0, "BSDF_VELVET", BsdfVelvet, "Velvet BSDF", "Reflection for materials such as cloth.\nTypically mixed with other shaders (such as a Diffuse Shader) and is not particularly useful on its own") diff --git a/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc index f08e857e9cc..0a446492c6b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc @@ -229,7 +229,7 @@ static void node_geo_exec(GeoNodeExecParams params) } if (surface_ob_eval == nullptr || surface_ob_eval->type != OB_MESH) { pass_through_input(); - params.error_message_add(NodeWarningType::Error, "Curves not attached to a surface."); + params.error_message_add(NodeWarningType::Error, "Curves not attached to a surface"); return; } Object *surface_ob_orig = DEG_get_original_object(surface_ob_eval); @@ -246,7 +246,7 @@ static void node_geo_exec(GeoNodeExecParams params) false); if (surface_mesh_eval == nullptr) { pass_through_input(); - params.error_message_add(NodeWarningType::Error, "Surface has no mesh."); + params.error_message_add(NodeWarningType::Error, "Surface has no mesh"); return; } @@ -260,13 +260,13 @@ static void node_geo_exec(GeoNodeExecParams params) if (uv_map_name.is_empty()) { pass_through_input(); - const char *message = TIP_("Surface UV map not defined."); + const char *message = TIP_("Surface UV map not defined"); params.error_message_add(NodeWarningType::Error, message); return; } if (!mesh_attributes_eval.contains(uv_map_name)) { pass_through_input(); - char *message = BLI_sprintfN(TIP_("Evaluated surface missing UV map: %s."), + char *message = BLI_sprintfN(TIP_("Evaluated surface missing UV map: %s"), uv_map_name.c_str()); params.error_message_add(NodeWarningType::Error, message); MEM_freeN(message); @@ -274,8 +274,7 @@ static void node_geo_exec(GeoNodeExecParams params) } if (!mesh_attributes_orig.contains(uv_map_name)) { pass_through_input(); - char *message = BLI_sprintfN(TIP_("Original surface missing UV map: %s."), - uv_map_name.c_str()); + char *message = BLI_sprintfN(TIP_("Original surface missing UV map: %s"), uv_map_name.c_str()); params.error_message_add(NodeWarningType::Error, message); MEM_freeN(message); return; @@ -283,13 +282,13 @@ static void node_geo_exec(GeoNodeExecParams params) if (!mesh_attributes_eval.contains(rest_position_name)) { pass_through_input(); params.error_message_add(NodeWarningType::Error, - TIP_("Evaluated surface missing attribute: rest_position.")); + TIP_("Evaluated surface missing attribute: rest_position")); return; } if (curves.surface_uv_coords().is_empty()) { pass_through_input(); params.error_message_add(NodeWarningType::Error, - TIP_("Curves are not attached to any UV map.")); + TIP_("Curves are not attached to any UV map")); return; } const VArraySpan uv_map_orig = mesh_attributes_orig.lookup(uv_map_name, @@ -337,7 +336,7 @@ static void node_geo_exec(GeoNodeExecParams params) curves.tag_positions_changed(); if (invalid_uv_count) { - char *message = BLI_sprintfN(TIP_("Invalid surface UVs on %d curves."), + char *message = BLI_sprintfN(TIP_("Invalid surface UVs on %d curves"), invalid_uv_count.load()); params.error_message_add(NodeWarningType::Warning, message); MEM_freeN(message); -- cgit v1.2.3 From 6ca5ac208477d89477c5c765bc0798700c7fc784 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Mon, 11 Jul 2022 13:36:29 +0200 Subject: GPU: Update shader builder stubs. Fixes workflow when using WITH_GPU_SHADER_BUILDER=On. --- .../blender/gpu/intern/gpu_shader_builder_stubs.cc | 64 ++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/source/blender/gpu/intern/gpu_shader_builder_stubs.cc b/source/blender/gpu/intern/gpu_shader_builder_stubs.cc index 515f65adb73..d8af2fc584d 100644 --- a/source/blender/gpu/intern/gpu_shader_builder_stubs.cc +++ b/source/blender/gpu/intern/gpu_shader_builder_stubs.cc @@ -12,6 +12,7 @@ #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" +#include "BKE_attribute.h" #include "BKE_customdata.h" #include "BKE_global.h" #include "BKE_material.h" @@ -100,6 +101,38 @@ void UI_GetThemeColorShadeAlpha4ubv(int UNUSED(colorid), /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Stubs of BKE_attribute.h + * \{ */ + +void BKE_id_attribute_copy_domains_temp(short UNUSED(id_type), + const struct CustomData *UNUSED(vdata), + const struct CustomData *UNUSED(edata), + const struct CustomData *UNUSED(ldata), + const struct CustomData *UNUSED(pdata), + const struct CustomData *UNUSED(cdata), + struct ID *UNUSED(i_id)) +{ +} + +struct CustomDataLayer *BKE_id_attributes_active_color_get(const struct ID *UNUSED(id)) +{ + return nullptr; +} + +struct CustomDataLayer *BKE_id_attributes_render_color_get(const struct ID *UNUSED(id)) +{ + return nullptr; +} + +eAttrDomain BKE_id_attribute_domain(const struct ID *UNUSED(id), + const struct CustomDataLayer *UNUSED(layer)) +{ + return ATTR_DOMAIN_AUTO; +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Stubs of BKE_paint.h * \{ */ @@ -170,6 +203,28 @@ int CustomData_get_offset(const struct CustomData *UNUSED(data), int UNUSED(type return 0; } +int CustomData_get_named_layer_index(const struct CustomData *UNUSED(data), + int UNUSED(type), + const char *UNUSED(name)) +{ + return -1; +} + +int CustomData_get_active_layer_index(const struct CustomData *UNUSED(data), int UNUSED(type)) +{ + return -1; +} + +int CustomData_get_render_layer_index(const struct CustomData *UNUSED(data), int UNUSED(type)) +{ + return -1; +} + +bool CustomData_has_layer(const struct CustomData *UNUSED(data), int UNUSED(type)) +{ + return false; +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -237,5 +292,14 @@ void DRW_deferred_shader_remove(struct GPUMaterial *UNUSED(mat)) BLI_assert_unreachable(); } +void DRW_cdlayer_attr_aliases_add(struct GPUVertFormat *UNUSED(format), + const char *UNUSED(base_name), + const struct CustomData *UNUSED(data), + const struct CustomDataLayer *UNUSED(cl), + bool UNUSED(is_active_render), + bool UNUSED(is_active_layer)) +{ +} + /** \} */ } -- cgit v1.2.3 From 6e426259b4de01598df564e5998043f5db43f1de Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Mon, 11 Jul 2022 14:00:49 +0200 Subject: Fix T99218: light group add button should be disabled when name is empty Previously it was inactive but still clickable. Ref D15316 --- intern/cycles/blender/addon/ui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index 77da3f36685..09aecb5cb81 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -1202,7 +1202,7 @@ class CYCLES_OBJECT_PT_lightgroup(CyclesButtonsPanel, Panel): sub.prop_search(ob, "lightgroup", view_layer, "lightgroups", text="Light Group", results_are_suggestions=True) sub = row.column(align=True) - sub.active = bool(ob.lightgroup) and not any(lg.name == ob.lightgroup for lg in view_layer.lightgroups) + sub.enabled = bool(ob.lightgroup) and not any(lg.name == ob.lightgroup for lg in view_layer.lightgroups) sub.operator("scene.view_layer_add_lightgroup", icon='ADD', text="").name = ob.lightgroup @@ -1640,7 +1640,7 @@ class CYCLES_WORLD_PT_settings_light_group(CyclesButtonsPanel, Panel): ) sub = row.column(align=True) - sub.active = bool(world.lightgroup) and not any(lg.name == world.lightgroup for lg in view_layer.lightgroups) + sub.enabled = bool(world.lightgroup) and not any(lg.name == world.lightgroup for lg in view_layer.lightgroups) sub.operator("scene.view_layer_add_lightgroup", icon='ADD', text="").name = world.lightgroup -- cgit v1.2.3 From 76d86142367e0eb67e7a423ae5a4884bbd21d6ac Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Mon, 11 Jul 2022 16:12:27 +0200 Subject: GPU: Do not allow GPU Shader builder when USD is enabled. Linking GPU shader builder requires stubs for many functions of the USD library. We don't want to rely on other modules to update the stubs for a tool that is only used by GPU developers. This patch raises an error when both WITH_GPU_SHADER_BUILDER and WITH_USD are enabled. This reduces the maintenance of updating the stubs when USD API changes. Reviewed By: LazyDodo Differential Revision: https://developer.blender.org/D15422 --- CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1416b5b4189..22f31d6af48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1340,6 +1340,12 @@ else() list(APPEND GL_DEFINITIONS -DWITH_GL_PROFILE_CORE) endif() +if (WITH_GPU_SHADER_BUILDER AND WITH_USD) + message(FATAL_ERROR + "Unable to compile WITH_GPU_SHADER_BUILDER and WITH_USD." + ) +endif() + #----------------------------------------------------------------------------- # Configure Metal. if (WITH_METAL_BACKEND) -- cgit v1.2.3 From 8ca09e6c5eb6ac5401f7e7d89185e341921e7d3a Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Mon, 11 Jul 2022 16:45:07 +0200 Subject: GPU: add BUIDTIME to WITH_GPU_SHADER_BUILDER Adds a better name that describes when it is used. The GPU_SHADER_BUILDER is a buildtime tool for developers to pre-validate GLSL (and in the overseen future pre-compile to SpirV). We don't see that this needs to become a required step in the future so WITH_GPU_BUILDTIME_SHADER_BUILDER is more descriptive name. --- CMakeLists.txt | 8 ++++---- release/datafiles/locale | 2 +- release/scripts/addons | 2 +- source/blender/gpu/CMakeLists.txt | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 22f31d6af48..6b7ee7f1887 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -565,13 +565,13 @@ endif() option(WITH_OPENGL "When off limits visibility of the opengl headers to just bf_gpu and gawain (temporary option for development purposes)" ON) option(WITH_GLEW_ES "Switches to experimental copy of GLEW that has support for OpenGL ES. (temporary option for development purposes)" OFF) option(WITH_GL_PROFILE_ES20 "Support using OpenGL ES 2.0. (through either EGL or the AGL/WGL/XGL 'es20' profile)" OFF) -option(WITH_GPU_SHADER_BUILDER "Shader builder is a developer option enabling linting on GLSL during compilation" OFF) +option(WITH_GPU_BUILDTIME_SHADER_BUILDER "Shader builder is a developer option enabling linting on GLSL during compilation" OFF) mark_as_advanced( WITH_OPENGL WITH_GLEW_ES WITH_GL_PROFILE_ES20 - WITH_GPU_SHADER_BUILDER + WITH_GPU_BUILDTIME_SHADER_BUILDER ) # Metal @@ -1340,9 +1340,9 @@ else() list(APPEND GL_DEFINITIONS -DWITH_GL_PROFILE_CORE) endif() -if (WITH_GPU_SHADER_BUILDER AND WITH_USD) +if (WITH_GPU_BUILDTIME_SHADER_BUILDER AND WITH_USD) message(FATAL_ERROR - "Unable to compile WITH_GPU_SHADER_BUILDER and WITH_USD." + "Unable to compile WITH_GPU_BUILDTIME_SHADER_BUILDER and WITH_USD." ) endif() diff --git a/release/datafiles/locale b/release/datafiles/locale index 055bc5223c1..a2eb5078914 160000 --- a/release/datafiles/locale +++ b/release/datafiles/locale @@ -1 +1 @@ -Subproject commit 055bc5223c1cd249e32ccbc8e8796ba9925c8c33 +Subproject commit a2eb507891449a0b67582be9561840075513661d diff --git a/release/scripts/addons b/release/scripts/addons index 7ea2e74fc41..7a8502871c3 160000 --- a/release/scripts/addons +++ b/release/scripts/addons @@ -1 +1 @@ -Subproject commit 7ea2e74fc41b2eabdbf639b812082e73823b09d7 +Subproject commit 7a8502871c34db0343cc7de52d6b49b15a84238a diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index 0f539d4b30a..1c883f7fa41 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -561,7 +561,7 @@ endif() -if(WITH_GPU_SHADER_BUILDER) +if(WITH_GPU_BUILDTIME_SHADER_BUILDER) # TODO(@fclem) Fix this mess. if(APPLE) add_executable(shader_builder -- cgit v1.2.3 From 995c904d00e1e0713a1549ca16b2775a9dcdd281 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Mon, 11 Jul 2022 19:04:24 +0200 Subject: Fix (unreported) crash in liboverride code on rare cases. When dealing with 'embedded' IDs (and the like, e.g. shape keys), liboverride code could fail in case the reference linked data (e.g. a mesh) would not have a shapekey anymore, while the override mesh would still have one. Found while investigating another issue in Heist production file `Heist/pro/animation_test/einar/einar_new_expression_shapes2.blend`, r1230. --- source/blender/blenkernel/intern/lib_override.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/blender/blenkernel/intern/lib_override.cc b/source/blender/blenkernel/intern/lib_override.cc index d816b5ede5f..cdea97b6c6a 100644 --- a/source/blender/blenkernel/intern/lib_override.cc +++ b/source/blender/blenkernel/intern/lib_override.cc @@ -1771,6 +1771,11 @@ static bool lib_override_library_resync(Main *bmain, break; } } + if (reference_id == nullptr) { + /* Can happen e.g. when there is a local override of a shapekey, but the matching linked + * obdata (mesh etc.) does not have any shapekey anymore. */ + continue; + } BLI_assert(GS(reference_id->name) == GS(id->name)); if (!BLI_ghash_haskey(linkedref_to_old_override, reference_id)) { -- cgit v1.2.3 From 2a1d12d7a0edd7ecffda3c5e1bf5c3b512976bbb Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Mon, 11 Jul 2022 19:09:56 +0200 Subject: Fix (studio-reported) crash in ID remapping code on rare cases. Some ID types did not have a filter value, even though they would be used in remapping code, leading to missing remappings. In that specific case, shape keys would actually never be properly remapped. Reproducible in r1230 of `Heist/pro/animation_test/einar/einar_new_expression_shapes2.blend`, --- source/blender/blenkernel/intern/idtype.c | 26 +++++++++++++++++----- source/blender/blenkernel/intern/key.c | 2 +- .../blender/blenkernel/intern/lib_id_remapper.cc | 1 + source/blender/blenkernel/intern/screen.c | 2 +- source/blender/makesdna/DNA_ID.h | 6 ++++- source/blender/windowmanager/intern/wm.c | 2 +- 6 files changed, 30 insertions(+), 9 deletions(-) diff --git a/source/blender/blenkernel/intern/idtype.c b/source/blender/blenkernel/intern/idtype.c index e55143d6852..edb6fe5d69b 100644 --- a/source/blender/blenkernel/intern/idtype.c +++ b/source/blender/blenkernel/intern/idtype.c @@ -209,7 +209,11 @@ uint64_t BKE_idtype_idcode_to_idfilter(const short idcode) case ID_##_id: \ return FILTER_ID_##_id - switch (idcode) { +#define CASE_IDFILTER_NONE(_id) \ + case ID_##_id: \ + return 0 + + switch ((ID_Type)idcode) { CASE_IDFILTER(AC); CASE_IDFILTER(AR); CASE_IDFILTER(BR); @@ -220,7 +224,10 @@ uint64_t BKE_idtype_idcode_to_idfilter(const short idcode) CASE_IDFILTER(GR); CASE_IDFILTER(CV); CASE_IDFILTER(IM); + CASE_IDFILTER_NONE(IP); + CASE_IDFILTER(KE); CASE_IDFILTER(LA); + CASE_IDFILTER_NONE(LI); CASE_IDFILTER(LS); CASE_IDFILTER(LT); CASE_IDFILTER(MA); @@ -236,6 +243,7 @@ uint64_t BKE_idtype_idcode_to_idfilter(const short idcode) CASE_IDFILTER(PT); CASE_IDFILTER(LP); CASE_IDFILTER(SCE); + CASE_IDFILTER(SCR); CASE_IDFILTER(SIM); CASE_IDFILTER(SPK); CASE_IDFILTER(SO); @@ -243,13 +251,16 @@ uint64_t BKE_idtype_idcode_to_idfilter(const short idcode) CASE_IDFILTER(TXT); CASE_IDFILTER(VF); CASE_IDFILTER(VO); + CASE_IDFILTER(WM); CASE_IDFILTER(WO); CASE_IDFILTER(WS); - default: - return 0; } + BLI_assert_unreachable(); + return 0; + #undef CASE_IDFILTER +#undef CASE_IDFILTER_NONE } short BKE_idtype_idcode_from_idfilter(const uint64_t idfilter) @@ -269,6 +280,7 @@ short BKE_idtype_idcode_from_idfilter(const uint64_t idfilter) CASE_IDFILTER(GR); CASE_IDFILTER(CV); CASE_IDFILTER(IM); + CASE_IDFILTER(KE); CASE_IDFILTER(LA); CASE_IDFILTER(LS); CASE_IDFILTER(LT); @@ -285,6 +297,7 @@ short BKE_idtype_idcode_from_idfilter(const uint64_t idfilter) CASE_IDFILTER(PT); CASE_IDFILTER(LP); CASE_IDFILTER(SCE); + CASE_IDFILTER(SCR); CASE_IDFILTER(SIM); CASE_IDFILTER(SPK); CASE_IDFILTER(SO); @@ -292,11 +305,14 @@ short BKE_idtype_idcode_from_idfilter(const uint64_t idfilter) CASE_IDFILTER(TXT); CASE_IDFILTER(VF); CASE_IDFILTER(VO); + CASE_IDFILTER(WM); CASE_IDFILTER(WO); - default: - return 0; + CASE_IDFILTER(WS); } + BLI_assert_unreachable(); + return 0; + #undef CASE_IDFILTER } diff --git a/source/blender/blenkernel/intern/key.c b/source/blender/blenkernel/intern/key.c index 594cffe6406..97eac0b9f91 100644 --- a/source/blender/blenkernel/intern/key.c +++ b/source/blender/blenkernel/intern/key.c @@ -191,7 +191,7 @@ static void shapekey_blend_read_expand(BlendExpander *expander, ID *id) IDTypeInfo IDType_ID_KE = { .id_code = ID_KE, - .id_filter = 0, + .id_filter = FILTER_ID_KE, .main_listbase_index = INDEX_ID_KE, .struct_size = sizeof(Key), .name = "Key", diff --git a/source/blender/blenkernel/intern/lib_id_remapper.cc b/source/blender/blenkernel/intern/lib_id_remapper.cc index 7e75e0f5d93..98ac9110a48 100644 --- a/source/blender/blenkernel/intern/lib_id_remapper.cc +++ b/source/blender/blenkernel/intern/lib_id_remapper.cc @@ -36,6 +36,7 @@ struct IDRemapper { BLI_assert(old_id != nullptr); BLI_assert(new_id == nullptr || (GS(old_id->name) == GS(new_id->name))); mappings.add(old_id, new_id); + BLI_assert(BKE_idtype_idcode_to_idfilter(GS(old_id->name)) != 0); source_types |= BKE_idtype_idcode_to_idfilter(GS(old_id->name)); } diff --git a/source/blender/blenkernel/intern/screen.c b/source/blender/blenkernel/intern/screen.c index 12dc1b6d1fa..c16e5ce5655 100644 --- a/source/blender/blenkernel/intern/screen.c +++ b/source/blender/blenkernel/intern/screen.c @@ -277,7 +277,7 @@ static void screen_blend_read_lib(BlendLibReader *reader, ID *id) IDTypeInfo IDType_ID_SCR = { .id_code = ID_SCR, - .id_filter = 0, + .id_filter = FILTER_ID_SCR, .main_listbase_index = INDEX_ID_SCR, .struct_size = sizeof(bScreen), .name = "Screen", diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index 451c921c4ef..f6032b71155 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -923,6 +923,9 @@ typedef enum IDRecalcFlag { #define FILTER_ID_PT (1ULL << 33) #define FILTER_ID_VO (1ULL << 34) #define FILTER_ID_SIM (1ULL << 35) +#define FILTER_ID_KE (1ULL << 36) +#define FILTER_ID_SCR (1ULL << 37) +#define FILTER_ID_WM (1ULL << 38) #define FILTER_ID_ALL \ (FILTER_ID_AC | FILTER_ID_AR | FILTER_ID_BR | FILTER_ID_CA | FILTER_ID_CU_LEGACY | \ @@ -930,7 +933,8 @@ typedef enum IDRecalcFlag { FILTER_ID_MA | FILTER_ID_MB | FILTER_ID_MC | FILTER_ID_ME | FILTER_ID_MSK | FILTER_ID_NT | \ FILTER_ID_OB | FILTER_ID_PA | FILTER_ID_PAL | FILTER_ID_PC | FILTER_ID_SCE | FILTER_ID_SPK | \ FILTER_ID_SO | FILTER_ID_TE | FILTER_ID_TXT | FILTER_ID_VF | FILTER_ID_WO | FILTER_ID_CF | \ - FILTER_ID_WS | FILTER_ID_LP | FILTER_ID_CV | FILTER_ID_PT | FILTER_ID_VO | FILTER_ID_SIM) + FILTER_ID_WS | FILTER_ID_LP | FILTER_ID_CV | FILTER_ID_PT | FILTER_ID_VO | FILTER_ID_SIM | \ + FILTER_ID_KE | FILTER_ID_SCR | FILTER_ID_WM) /** * This enum defines the index assigned to each type of IDs in the array returned by diff --git a/source/blender/windowmanager/intern/wm.c b/source/blender/windowmanager/intern/wm.c index 32cec8f779c..0d74bc259f4 100644 --- a/source/blender/windowmanager/intern/wm.c +++ b/source/blender/windowmanager/intern/wm.c @@ -257,7 +257,7 @@ static void window_manager_blend_read_lib(BlendLibReader *reader, ID *id) IDTypeInfo IDType_ID_WM = { .id_code = ID_WM, - .id_filter = 0, + .id_filter = FILTER_ID_WM, .main_listbase_index = INDEX_ID_WM, .struct_size = sizeof(wmWindowManager), .name = "WindowManager", -- cgit v1.2.3 From 00c7e760b323e5fa46703d0e4769c8f1d9c35f2e Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 12 Jul 2022 16:05:13 +1000 Subject: Python: add opcodes for safe py-drivers The following opcodes have been added, see [0] for details: - LIST_TO_TUPLE: convert a list to a tuple, use for constructing lists/tuples in some cases. - LIST_EXTEND: use for constructing lists with unpacking. - SET_UPDATE: use for constructing sets with unpacking. - CONTAINS_OP: check if `a in b` generally useful. When writing tests these op-codes where needed for basic operations and can be safely supported. Add note why dictionary manipulation op-codes have been left out. Also restrict namsepace access to anything with an underscore prefix since these may be undocumented. [0]: https://docs.python.org/3.10/library/dis.html --- source/blender/python/intern/bpy_driver.c | 98 +++++++++++++++++++++++-------- source/blender/python/intern/bpy_driver.h | 10 ++++ 2 files changed, 83 insertions(+), 25 deletions(-) diff --git a/source/blender/python/intern/bpy_driver.c b/source/blender/python/intern/bpy_driver.c index cc64af2a489..04aa203d198 100644 --- a/source/blender/python/intern/bpy_driver.c +++ b/source/blender/python/intern/bpy_driver.c @@ -318,6 +318,7 @@ static const bool secure_opcodes[255] = { OK_OP(UNARY_INVERT), OK_OP(BINARY_SUBSCR), OK_OP(GET_LEN), + OK_OP(LIST_TO_TUPLE), OK_OP(RETURN_VALUE), OK_OP(SWAP), OK_OP(BUILD_TUPLE), @@ -332,6 +333,7 @@ static const bool secure_opcodes[255] = { OK_OP(POP_JUMP_FORWARD_IF_TRUE), OK_OP(LOAD_GLOBAL), OK_OP(IS_OP), + OK_OP(CONTAINS_OP), OK_OP(BINARY_OP), OK_OP(LOAD_FAST), OK_OP(STORE_FAST), @@ -342,6 +344,14 @@ static const bool secure_opcodes[255] = { OK_OP(LOAD_DEREF), OK_OP(STORE_DEREF), OK_OP(RESUME), + OK_OP(LIST_EXTEND), + OK_OP(SET_UPDATE), +/* NOTE(@campbellbarton): Don't enable dict manipulation, unless we can prove there is not way it + * can be used to manipulate the name-space (potentially allowing malicious code). */ +# if 0 + OK_OP(DICT_MERGE), + OK_OP(DICT_UPDATE), +# endif OK_OP(POP_JUMP_BACKWARD_IF_NOT_NONE), OK_OP(POP_JUMP_BACKWARD_IF_NONE), OK_OP(POP_JUMP_BACKWARD_IF_FALSE), @@ -395,6 +405,7 @@ static const bool secure_opcodes[255] = { OK_OP(INPLACE_AND), OK_OP(INPLACE_XOR), OK_OP(INPLACE_OR), + OK_OP(LIST_TO_TUPLE), OK_OP(RETURN_VALUE), OK_OP(ROT_N), OK_OP(BUILD_TUPLE), @@ -410,12 +421,21 @@ static const bool secure_opcodes[255] = { OK_OP(POP_JUMP_IF_TRUE), OK_OP(LOAD_GLOBAL), OK_OP(IS_OP), + OK_OP(CONTAINS_OP), OK_OP(LOAD_FAST), OK_OP(STORE_FAST), OK_OP(DELETE_FAST), OK_OP(BUILD_SLICE), OK_OP(LOAD_DEREF), OK_OP(STORE_DEREF), + OK_OP(LIST_EXTEND), + OK_OP(SET_UPDATE), +/* NOTE(@campbellbarton): Don't enable dict manipulation, unless we can prove there is not way it + * can be used to manipulate the name-space (potentially allowing malicious code). */ +# if 0 + OK_OP(DICT_MERGE), + OK_OP(DICT_UPDATE), +# endif /* Special cases. */ OK_OP(LOAD_CONST), /* Ok because constants are accepted. */ @@ -429,9 +449,10 @@ static const bool secure_opcodes[255] = { # undef OK_OP -static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code, - PyObject *dict_arr[], - const char *error_prefix) +bool BPY_driver_secure_bytecode_test_ex(PyObject *expr_code, + PyObject *namespace_array[], + const bool verbose, + const char *error_prefix) { PyCodeObject *py_code = (PyCodeObject *)expr_code; @@ -439,21 +460,23 @@ static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code, { for (int i = 0; i < PyTuple_GET_SIZE(py_code->co_names); i++) { PyObject *name = PyTuple_GET_ITEM(py_code->co_names, i); - + const char *name_str = PyUnicode_AsUTF8(name); bool contains_name = false; - for (int j = 0; dict_arr[j]; j++) { - if (PyDict_Contains(dict_arr[j], name)) { + for (int j = 0; namespace_array[j]; j++) { + if (PyDict_Contains(namespace_array[j], name)) { contains_name = true; break; } } - if (contains_name == false) { - fprintf(stderr, - "\t%s: restricted access disallows name '%s', " - "enable auto-execution to support\n", - error_prefix, - PyUnicode_AsUTF8(name)); + if ((contains_name == false) || (name_str[0] == '_')) { + if (verbose) { + fprintf(stderr, + "\t%s: restricted access disallows name '%s', " + "enable auto-execution to support\n", + error_prefix, + name_str); + } return false; } } @@ -485,11 +508,13 @@ static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code, for (Py_ssize_t i = 0; i < code_len; i++) { const int opcode = _Py_OPCODE(codestr[i]); if (secure_opcodes[opcode] == false) { - fprintf(stderr, - "\t%s: restricted access disallows opcode '%d', " - "enable auto-execution to support\n", - error_prefix, - opcode); + if (verbose) { + fprintf(stderr, + "\t%s: restricted access disallows opcode '%d', " + "enable auto-execution to support\n", + error_prefix, + opcode); + } ok = false; break; } @@ -506,6 +531,26 @@ static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code, return true; } +bool BPY_driver_secure_bytecode_test(PyObject *expr_code, PyObject *namespace, const bool verbose) +{ + + if (!bpy_pydriver_Dict) { + if (bpy_pydriver_create_dict() != 0) { + fprintf(stderr, "%s: couldn't create Python dictionary\n", __func__); + return false; + } + } + return BPY_driver_secure_bytecode_test_ex(expr_code, + (PyObject *[]){ + bpy_pydriver_Dict, + bpy_pydriver_Dict__whitelist, + namespace, + NULL, + }, + verbose, + __func__); +} + #endif /* USE_BYTECODE_WHITELIST */ float BPY_driver_exec(struct PathResolvedRNA *anim_rna, ChannelDriver *driver, @@ -697,14 +742,17 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna, #ifdef USE_BYTECODE_WHITELIST if (is_recompile && expr_code) { if (!(G.f & G_FLAG_SCRIPT_AUTOEXEC)) { - if (!bpy_driver_secure_bytecode_validate(expr_code, - (PyObject *[]){ - bpy_pydriver_Dict, - bpy_pydriver_Dict__whitelist, - driver_vars, - NULL, - }, - __func__)) { + if (!BPY_driver_secure_bytecode_test_ex( + expr_code, + (PyObject *[]){ + bpy_pydriver_Dict, + bpy_pydriver_Dict__whitelist, + driver_vars, + NULL, + }, + /* Always be verbose since this can give hints to why evaluation fails. */ + true, + __func__)) { if (!(G.f & G_FLAG_SCRIPT_AUTOEXEC_FAIL_QUIET)) { G.f |= G_FLAG_SCRIPT_AUTOEXEC_FAIL; BLI_snprintf(G.autoexec_fail, sizeof(G.autoexec_fail), "Driver '%s'", expr); diff --git a/source/blender/python/intern/bpy_driver.h b/source/blender/python/intern/bpy_driver.h index 301c6b3662e..f0d9717dbbd 100644 --- a/source/blender/python/intern/bpy_driver.h +++ b/source/blender/python/intern/bpy_driver.h @@ -10,6 +10,8 @@ extern "C" { #endif +#include + /** * For faster execution we keep a special dictionary for py-drivers, with * the needed modules and aliases. @@ -21,6 +23,14 @@ int bpy_pydriver_create_dict(void); */ extern PyObject *bpy_pydriver_Dict; +extern bool BPY_driver_secure_bytecode_test_ex(PyObject *expr_code, + PyObject *namespace_array[], + const bool verbose, + const char *error_prefix); +extern bool BPY_driver_secure_bytecode_test(PyObject *expr_code, + PyObject *namespace, + const bool verbose); + #ifdef __cplusplus } #endif -- cgit v1.2.3 From ae6a4fcc7a707e419463c47e191afe54def26764 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 12 Jul 2022 16:05:15 +1000 Subject: Tests: add test to ensure restricted py-driver execution is working Add internal function (only used for testing at the moment) `_bpy._driver_secure_code_test`. Add test `script_pyapi_bpy_driver_secure_eval` to serves two purposes: - Ensure expressions that should be insecure remain so when upgrading Python or making any changes in this area. - Ensure new versions of Python don't introduce new byte-codes that prevent existing expressions from being executed (happened when upgrading from 3.7, see [0]). [0]: dfa52017638abdf59791e5588c439d3a558a191d --- source/blender/python/intern/bpy.c | 53 ++++++ tests/python/CMakeLists.txt | 5 + tests/python/bl_pyapi_bpy_driver_secure_eval.py | 220 ++++++++++++++++++++++++ 3 files changed, 278 insertions(+) create mode 100644 tests/python/bl_pyapi_bpy_driver_secure_eval.py diff --git a/source/blender/python/intern/bpy.c b/source/blender/python/intern/bpy.c index 2e97ae0fc1d..d1e8b894ac0 100644 --- a/source/blender/python/intern/bpy.c +++ b/source/blender/python/intern/bpy.c @@ -32,6 +32,7 @@ #include "bpy.h" #include "bpy_app.h" #include "bpy_capi_utils.h" +#include "bpy_driver.h" #include "bpy_library.h" #include "bpy_operator.h" #include "bpy_props.h" @@ -326,6 +327,49 @@ static PyObject *bpy_resource_path(PyObject *UNUSED(self), PyObject *args, PyObj return PyC_UnicodeFromByte(path ? path : ""); } +/* This is only exposed for tests, see: `tests/python/bl_pyapi_bpy_driver_secure_eval.py`. */ +PyDoc_STRVAR(bpy_driver_secure_code_test_doc, + ".. function:: _driver_secure_code_test(code)\n" + "\n" + " Test if the script should be considered trusted.\n" + "\n" + " :arg code: The code to test.\n" + " :type code: code\n" + " :arg namespace: The namespace of values which are allowed.\n" + " :type namespace: dict\n" + " :arg verbose: Print the reason for considering insecure to the ``stderr``.\n" + " :type verbose: bool\n" + " :return: True when the script is considered trusted.\n" + " :rtype: bool\n"); +static PyObject *bpy_driver_secure_code_test(PyObject *UNUSED(self), PyObject *args, PyObject *kw) +{ + PyObject *py_code; + PyObject *py_namespace = NULL; + const bool verbose = false; + static const char *_keywords[] = {"code", "namespace", "verbose", NULL}; + static _PyArg_Parser _parser = { + "O!" /* `expression` */ + "|$" /* Optional keyword only arguments. */ + "O!" /* `namespace` */ + "O&" /* `verbose` */ + ":driver_secure_code_test", + _keywords, + 0, + }; + if (!_PyArg_ParseTupleAndKeywordsFast(args, + kw, + &_parser, + &PyCode_Type, + &py_code, + &PyDict_Type, + &py_namespace, + PyC_ParseBool, + &verbose)) { + return NULL; + } + return PyBool_FromLong(BPY_driver_secure_bytecode_test(py_code, py_namespace, verbose)); +} + PyDoc_STRVAR(bpy_escape_identifier_doc, ".. function:: escape_identifier(string)\n" "\n" @@ -528,6 +572,12 @@ static PyMethodDef meth_bpy_resource_path = { METH_VARARGS | METH_KEYWORDS, bpy_resource_path_doc, }; +static PyMethodDef meth_bpy_driver_secure_code_test = { + "_driver_secure_code_test", + (PyCFunction)bpy_driver_secure_code_test, + METH_VARARGS | METH_KEYWORDS, + bpy_driver_secure_code_test_doc, +}; static PyMethodDef meth_bpy_escape_identifier = { "escape_identifier", (PyCFunction)bpy_escape_identifier, @@ -647,6 +697,9 @@ void BPy_init_modules(struct bContext *C) PyModule_AddObject(mod, meth_bpy_resource_path.ml_name, (PyObject *)PyCFunction_New(&meth_bpy_resource_path, NULL)); + PyModule_AddObject(mod, + meth_bpy_driver_secure_code_test.ml_name, + (PyObject *)PyCFunction_New(&meth_bpy_driver_secure_code_test, NULL)); PyModule_AddObject(mod, meth_bpy_escape_identifier.ml_name, (PyObject *)PyCFunction_New(&meth_bpy_escape_identifier, NULL)); diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 38c3fc4389a..d95f2cd2644 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -101,6 +101,11 @@ add_blender_test( --python ${CMAKE_CURRENT_LIST_DIR}/bl_pyapi_mathutils.py ) +add_blender_test( + script_pyapi_bpy_driver_secure_eval + --python ${CMAKE_CURRENT_LIST_DIR}/bl_pyapi_bpy_driver_secure_eval.py +) + add_blender_test( script_pyapi_idprop --python ${CMAKE_CURRENT_LIST_DIR}/bl_pyapi_idprop.py diff --git a/tests/python/bl_pyapi_bpy_driver_secure_eval.py b/tests/python/bl_pyapi_bpy_driver_secure_eval.py new file mode 100644 index 00000000000..953dbcd5381 --- /dev/null +++ b/tests/python/bl_pyapi_bpy_driver_secure_eval.py @@ -0,0 +1,220 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +# ./blender.bin --background -noaudio --python tests/python/bl_pyapi_bpy_driver_secure_eval.py -- --verbose +import bpy +import unittest +import builtins +from types import ModuleType + + +# ----------------------------------------------------------------------------- +# Mock Environment + + +expect_unreachable_msg = "This function should _NEVER_ run!" +# Internal check, to ensure this actually runs as expected. +expect_unreachable_count = 0 + + +def expect_os_unreachable(): + global expect_unreachable_count + expect_unreachable_count += 1 + raise Exception(expect_unreachable_msg) + + +__import__("os").expect_os_unreachable = expect_os_unreachable + + +expect_open_unreachable_count = 0 + + +def open_expect_unreachable(*args, **kwargs): + global expect_open_unreachable_count + expect_open_unreachable_count += 1 + raise Exception(expect_unreachable_msg) + + +mock_builtins = {**builtins.__dict__, **{"open": open_expect_unreachable}} + + +# ----------------------------------------------------------------------------- +# Utility Functions + + +def is_expression_secure(expr_str, verbose): + """ + Return (ok, code) where ok is true if expr_str is considered secure. + """ + # Internal function only for testing (not part of the public API). + from _bpy import _driver_secure_code_test + expr_code = compile(expr_str, "", 'eval') + ok = _driver_secure_code_test(expr_code, verbose=verbose) + return ok, expr_code + + +# ----------------------------------------------------------------------------- +# Tests (Accept) + + +class _TestExprMixIn: + """ + Sub-classes must define: + - expressions_expect_secure: boolean, the expected secure state. + - expressions: A sequence of expressions that must evaluate in the driver name-space. + + Optionally: + - expressions_expect_unreachable: + A boolean, when true, it's expected each expression should call + ``expect_os_unreachable`` or ``expect_open_unreachable``. + """ + + # Sub-class may override. + expressions_expect_unreachable = False + + def assertSecure(self, expect_secure, expr_str): + is_secure, expr_code = is_expression_secure( + expr_str, + # Only verbose when secure as this is will result in an failure, + # in that case it's useful to know which op-codes caused the test to unexpectedly fail. + verbose=expect_secure, + ) + if is_secure != expect_secure: + raise self.failureException( + "Expression \"%s\" was expected to be %s" % + (expr_str, "secure" if expect_secure else "insecure")) + # NOTE: executing is not essential, it's just better to ensure the expressions make sense. + try: + exec( + expr_code, + {"__builtins__": mock_builtins}, + {**bpy.app.driver_namespace, **{"__builtins__": mock_builtins}}, + ) + # exec(expr_code, {}, bpy.app.driver_namespace) + ex = None + except BaseException as ex_test: + ex = ex_test + + if self.expressions_expect_unreachable: + if ex and ex.args == (expect_unreachable_msg,): + ex = None + elif not ex: + raise self.failureException("Expression \"%s\" failed to run `os.expect_os_unreachable`" % (expr_str,)) + else: + # An unknown exception was raised, use the exception below. + pass + + if ex: + raise self.failureException("Expression \"%s\" failed to evaluate with error: %r" % (expr_str, ex)) + + def test_expr(self): + expect_secure = self.expressions_expect_secure + for expr_str in self.expressions: + self.assertSecure(expect_secure, expr_str) + + +class TestExprMixIn_Accept(_TestExprMixIn): + expressions_expect_secure = True + + +class TestExprMixIn_Reject(_TestExprMixIn): + expressions_expect_secure = False + + +class TestAcceptLiteralNumbers(unittest.TestCase, TestExprMixIn_Accept): + expressions = ("1", "1_1", "1.1", "1j", "0x1", "0o1", "0b1") + + +class TestAcceptLiteralStrings(unittest.TestCase, TestExprMixIn_Accept): + expressions = ("''", "'_'", "r''", "r'_'", "'''_'''") + + +class TestAcceptSequencesEmpty(unittest.TestCase, TestExprMixIn_Accept): + expressions = ("()", "[]", "{}", "[[]]", "(())") + + +class TestAcceptSequencesSimple(unittest.TestCase, TestExprMixIn_Accept): + expressions = ("('', '')", "['', '_']", "{'', '_'}", "{'': '_'}") + + +class TestAcceptSequencesExpand(unittest.TestCase, TestExprMixIn_Accept): + expressions = ("(*('', '_'),)", "[*(), *[]]", "{*{1, 2}}") + + +class TestAcceptSequencesComplex(unittest.TestCase, TestExprMixIn_Accept): + expressions = ("[1, 2, 3][-1:0:-1][0]", "1 in (1, 2)", "False if 1 in {1, 2} else True") + + +class TestAcceptMathOperators(unittest.TestCase, TestExprMixIn_Accept): + expressions = ("4 / 4", "4 * 4", "4 // 4", "2 ** 2", "4 ^ -1", "4 & 1", "4 % 1") + + +class TestAcceptMathFunctionsSimple(unittest.TestCase, TestExprMixIn_Accept): + expressions = ("sin(pi)", "degrees(pi / 2)", "clamp(4, 0, 1)") + + +class TestAcceptMathFunctionsComplex(unittest.TestCase, TestExprMixIn_Accept): + expressions = ("-(sin(pi) ** 2) / 2", "floor(22 / 7)", "ceil(pi + 1)") + + +# ----------------------------------------------------------------------------- +# Tests (Reject) + +class TestRejectLiteralFStrings(unittest.TestCase, TestExprMixIn_Reject): + # F-String's are not supported as `BUILD_STRING` op-code is disabled, + # while it may be safe to enable that needs to be double-checked. + # Further it doesn't seem useful for typical math expressions used in drivers. + expressions = ("f''", "f'{1}'", "f'{\"_\"}'") + + +class TestRejectModuleAccess(unittest.TestCase, TestExprMixIn_Reject): + # Each of these commands _must_ run `expect_os_unreachable`, + # and must also be rejected as insecure - otherwise we have problems. + expressions_expect_unreachable = True + expressions = ( + "__import__('os').expect_os_unreachable()", + "exec(\"__import__('os').expect_os_unreachable()\")", + "(globals().update(__import__('os').__dict__), expect_os_unreachable())", + ) + + # Ensure the functions are actually called. + def setUp(self): + self._count = expect_unreachable_count + + def tearDown(self): + count_actual = expect_unreachable_count - self._count + count_expect = len(self.expressions) + if count_actual != count_expect: + raise Exception( + "Expected 'os.expect_os_unreachable' to be called %d times but was called %d times" % + (count_expect, count_actual), + ) + + +class TestRejectOpenAccess(unittest.TestCase, TestExprMixIn_Reject): + # Each of these commands _must_ run `expect_open_unreachable`, + # and must also be rejected as insecure - otherwise we have problems. + expressions_expect_unreachable = True + expressions = ( + "open('file.txt', 'r')", + "exec(\"open('file.txt', 'r')\")", + "(globals().update({'fake_open': __builtins__['open']}), fake_open())", + ) + + # Ensure the functions are actually called. + def setUp(self): + self._count = expect_open_unreachable_count + + def tearDown(self): + count_actual = expect_open_unreachable_count - self._count + count_expect = len(self.expressions) + if count_actual != count_expect: + raise Exception( + "Expected 'open' to be called %d times but was called %d times" % + (count_expect, count_actual), + ) + + +if __name__ == '__main__': + import sys + sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []) + unittest.main() -- cgit v1.2.3 From b8d1e576bcabb4fad42ca9cf8f980240213ac0cf Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 12 Jul 2022 16:05:17 +1000 Subject: Cleanup: use array for internal _bpy methods --- source/blender/python/intern/bpy.c | 134 ++++++++++--------------------------- 1 file changed, 37 insertions(+), 97 deletions(-) diff --git a/source/blender/python/intern/bpy.c b/source/blender/python/intern/bpy.c index d1e8b894ac0..7fe0b9455e6 100644 --- a/source/blender/python/intern/bpy.c +++ b/source/blender/python/intern/bpy.c @@ -536,71 +536,37 @@ static PyObject *bpy_rna_enum_items_static(PyObject *UNUSED(self)) return result; } -static PyMethodDef meth_bpy_script_paths = { - "script_paths", - (PyCFunction)bpy_script_paths, - METH_NOARGS, - bpy_script_paths_doc, -}; -static PyMethodDef meth_bpy_blend_paths = { - "blend_paths", - (PyCFunction)bpy_blend_paths, - METH_VARARGS | METH_KEYWORDS, - bpy_blend_paths_doc, -}; -static PyMethodDef meth_bpy_flip_name = { - "flip_name", - (PyCFunction)bpy_flip_name, - METH_VARARGS | METH_KEYWORDS, - bpy_flip_name_doc, -}; -static PyMethodDef meth_bpy_user_resource = { - "user_resource", - (PyCFunction)bpy_user_resource, - METH_VARARGS | METH_KEYWORDS, - NULL, -}; -static PyMethodDef meth_bpy_system_resource = { - "system_resource", - (PyCFunction)bpy_system_resource, - METH_VARARGS | METH_KEYWORDS, - bpy_system_resource_doc, -}; -static PyMethodDef meth_bpy_resource_path = { - "resource_path", - (PyCFunction)bpy_resource_path, - METH_VARARGS | METH_KEYWORDS, - bpy_resource_path_doc, -}; -static PyMethodDef meth_bpy_driver_secure_code_test = { - "_driver_secure_code_test", - (PyCFunction)bpy_driver_secure_code_test, - METH_VARARGS | METH_KEYWORDS, - bpy_driver_secure_code_test_doc, -}; -static PyMethodDef meth_bpy_escape_identifier = { - "escape_identifier", - (PyCFunction)bpy_escape_identifier, - METH_O, - bpy_escape_identifier_doc, -}; -static PyMethodDef meth_bpy_unescape_identifier = { - "unescape_identifier", - (PyCFunction)bpy_unescape_identifier, - METH_O, - bpy_unescape_identifier_doc, -}; -static PyMethodDef meth_bpy_context_members = { - "context_members", - (PyCFunction)bpy_context_members, - METH_NOARGS, - bpy_context_members_doc, -}; -static PyMethodDef meth_bpy_rna_enum_items_static = { - "rna_enum_items_static", - (PyCFunction)bpy_rna_enum_items_static, - METH_NOARGS, - bpy_rna_enum_items_static_doc, +static PyMethodDef bpy_methods[] = { + {"script_paths", (PyCFunction)bpy_script_paths, METH_NOARGS, bpy_script_paths_doc}, + {"blend_paths", + (PyCFunction)bpy_blend_paths, + METH_VARARGS | METH_KEYWORDS, + bpy_blend_paths_doc}, + {"flip_name", (PyCFunction)bpy_flip_name, METH_VARARGS | METH_KEYWORDS, bpy_flip_name_doc}, + {"user_resource", (PyCFunction)bpy_user_resource, METH_VARARGS | METH_KEYWORDS, NULL}, + {"system_resource", + (PyCFunction)bpy_system_resource, + METH_VARARGS | METH_KEYWORDS, + bpy_system_resource_doc}, + {"resource_path", + (PyCFunction)bpy_resource_path, + METH_VARARGS | METH_KEYWORDS, + bpy_resource_path_doc}, + {"_driver_secure_code_test", + (PyCFunction)bpy_driver_secure_code_test, + METH_VARARGS | METH_KEYWORDS, + bpy_driver_secure_code_test_doc}, + {"escape_identifier", (PyCFunction)bpy_escape_identifier, METH_O, bpy_escape_identifier_doc}, + {"unescape_identifier", + (PyCFunction)bpy_unescape_identifier, + METH_O, + bpy_unescape_identifier_doc}, + {"context_members", (PyCFunction)bpy_context_members, METH_NOARGS, bpy_context_members_doc}, + {"rna_enum_items_static", + (PyCFunction)bpy_rna_enum_items_static, + METH_NOARGS, + bpy_rna_enum_items_static_doc}, + {NULL, NULL, 0, NULL}, }; static PyObject *bpy_import_test(const char *modname) @@ -682,38 +648,12 @@ void BPy_init_modules(struct bContext *C) /* Register methods and property get/set for RNA types. */ BPY_rna_types_extend_capi(); - /* utility func's that have nowhere else to go */ - PyModule_AddObject(mod, - meth_bpy_script_paths.ml_name, - (PyObject *)PyCFunction_New(&meth_bpy_script_paths, NULL)); - PyModule_AddObject( - mod, meth_bpy_blend_paths.ml_name, (PyObject *)PyCFunction_New(&meth_bpy_blend_paths, NULL)); - PyModule_AddObject(mod, - meth_bpy_user_resource.ml_name, - (PyObject *)PyCFunction_New(&meth_bpy_user_resource, NULL)); - PyModule_AddObject(mod, - meth_bpy_system_resource.ml_name, - (PyObject *)PyCFunction_New(&meth_bpy_system_resource, NULL)); - PyModule_AddObject(mod, - meth_bpy_resource_path.ml_name, - (PyObject *)PyCFunction_New(&meth_bpy_resource_path, NULL)); - PyModule_AddObject(mod, - meth_bpy_driver_secure_code_test.ml_name, - (PyObject *)PyCFunction_New(&meth_bpy_driver_secure_code_test, NULL)); - PyModule_AddObject(mod, - meth_bpy_escape_identifier.ml_name, - (PyObject *)PyCFunction_New(&meth_bpy_escape_identifier, NULL)); - PyModule_AddObject(mod, - meth_bpy_unescape_identifier.ml_name, - (PyObject *)PyCFunction_New(&meth_bpy_unescape_identifier, NULL)); - PyModule_AddObject( - mod, meth_bpy_flip_name.ml_name, (PyObject *)PyCFunction_New(&meth_bpy_flip_name, NULL)); - PyModule_AddObject(mod, - meth_bpy_context_members.ml_name, - (PyObject *)PyCFunction_New(&meth_bpy_context_members, NULL)); - PyModule_AddObject(mod, - meth_bpy_rna_enum_items_static.ml_name, - (PyObject *)PyCFunction_New(&meth_bpy_rna_enum_items_static, NULL)); + for (int i = 0; bpy_methods[i].ml_name; i++) { + PyMethodDef *m = &bpy_methods[i]; + /* Currently there is no need to support these. */ + BLI_assert((m->ml_flags & (METH_CLASS | METH_STATIC)) == 0); + PyModule_AddObject(mod, m->ml_name, (PyObject *)PyCFunction_New(m, NULL)); + } /* register funcs (bpy_rna.c) */ PyModule_AddObject(mod, -- cgit v1.2.3 From 6e6da22eb0f48c1189427040d765145766b366c0 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Tue, 12 Jul 2022 09:42:19 +0200 Subject: Fix: crash when iterating over all attributes --- source/blender/blenkernel/BKE_attribute.hh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/source/blender/blenkernel/BKE_attribute.hh b/source/blender/blenkernel/BKE_attribute.hh index b92d0d4326b..05ab4f1f1f1 100644 --- a/source/blender/blenkernel/BKE_attribute.hh +++ b/source/blender/blenkernel/BKE_attribute.hh @@ -351,8 +351,9 @@ class AttributeAccessor { /** * The data that actually owns the attributes, for example, a pointer to a #Mesh or #PointCloud * Most commonly this is a pointer to a #Mesh or #PointCloud. - * Under some circumstances this can be null. In that case most methods can't be used. Just e.g. - * the #domain_size method works and returns 0 for every domain. + * Under some circumstances this can be null. In that case most methods can't be used. Allowed + * methods are #domain_size, #for_all and #is_builtin. We could potentially make these methods + * accessible without #AttributeAccessor and then #owner_ could always be non-null. * * \note This class cannot modify the owner's attributes, but the pointer is still non-const, so * this class can be a base class for the mutable version. @@ -509,7 +510,10 @@ class AttributeAccessor { */ bool for_all(const AttributeForeachCallback fn) const { - return fn_->for_all(owner_, fn); + if (owner_ != nullptr) { + return fn_->for_all(owner_, fn); + } + return true; } /** -- cgit v1.2.3 From 8bd32019cad3cf0c5f3ce51c12612d76ee5bf04b Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Mon, 11 Jul 2022 17:25:13 +0200 Subject: Fix threading crash due to conflict in mesh wrapper type A mesh wrapper might be being accessed for read-only from one thread while another thread converts the wrapper type to something else. The proposes solution is to defer assignment of the mesh wrapper type until the wrapper is fully converted. The good thing about this approach is that it does not introduce extra synchronization (and, potentially, evaluation pipeline stalls). The downside is that it might not work with all possible wrapper types in the future. If a wrapper type which does not clear data separation is ever added in the future we will re-consider the threading safety then. Unfortunately, some changes outside of the mesh wrapper file are to be made to allow "incremental" construction of the mesh prior changing its wrapper type. Unfortunately, there is no simplified file which demonstrates the issue. It was investigated using Heist production file checked at the revision 1228: `pro/lib/char/einar/einar.shading.blend`. The repro case is simple: tab into edit mode, possibly few times. The gist is that there several surface deform and shrinkwrap modifiers which uses the same target. While one of them is building BVH tree (which changes wrapper type) the other one accesses it for read-only via `BKE_mesh_wrapper_vert_coords_copy_with_mat4()`. Differential Revision: https://developer.blender.org/D15424 --- source/blender/blenkernel/BKE_mesh.h | 12 ++++++++-- source/blender/blenkernel/intern/DerivedMesh.cc | 28 +++++++++++++++--------- source/blender/blenkernel/intern/mesh.cc | 5 +++++ source/blender/blenkernel/intern/mesh_wrapper.cc | 14 ++++++------ 4 files changed, 40 insertions(+), 19 deletions(-) diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h index 731c9872aae..6c61068b1c2 100644 --- a/source/blender/blenkernel/BKE_mesh.h +++ b/source/blender/blenkernel/BKE_mesh.h @@ -85,9 +85,17 @@ struct Mesh *BKE_mesh_from_bmesh_for_eval_nomain(struct BMesh *bm, * Add original index (#CD_ORIGINDEX) layers if they don't already exist. This is meant to be used * when creating an evaluated mesh from an original edit mode mesh, to allow mapping from the * evaluated vertices to the originals. + * + * The mesh is expected to of a `ME_WRAPPER_TYPE_MDATA` wrapper type. This is asserted. */ void BKE_mesh_ensure_default_orig_index_customdata(struct Mesh *mesh); +/** + * Same as #BKE_mesh_ensure_default_orig_index_customdata but does not perform any checks: they + * must be done by the caller. + */ +void BKE_mesh_ensure_default_orig_index_customdata_no_check(struct Mesh *mesh); + /** * Find the index of the loop in 'poly' which references vertex, * returns -1 if not found @@ -1002,8 +1010,8 @@ void BKE_mesh_calc_edges(struct Mesh *mesh, bool keep_existing_edges, bool selec void BKE_mesh_calc_edges_tessface(struct Mesh *mesh); /* In DerivedMesh.cc */ -void BKE_mesh_wrapper_deferred_finalize(struct Mesh *me_eval, - const struct CustomData_MeshMasks *cd_mask_finalize); +void BKE_mesh_wrapper_deferred_finalize_mdata(struct Mesh *me_eval, + const struct CustomData_MeshMasks *cd_mask_finalize); /* **** Depsgraph evaluation **** */ diff --git a/source/blender/blenkernel/intern/DerivedMesh.cc b/source/blender/blenkernel/intern/DerivedMesh.cc index c2ea01bcadf..a29d8726f21 100644 --- a/source/blender/blenkernel/intern/DerivedMesh.cc +++ b/source/blender/blenkernel/intern/DerivedMesh.cc @@ -84,6 +84,8 @@ static ThreadRWMutex loops_cache_lock = PTHREAD_RWLOCK_INITIALIZER; static void mesh_init_origspace(Mesh *mesh); static void editbmesh_calc_modifier_final_normals(Mesh *mesh_final, const CustomData_MeshMasks *final_datamask); +static void editbmesh_calc_modifier_final_normals_or_defer( + Mesh *mesh_final, const CustomData_MeshMasks *final_datamask); /* -------------------------------------------------------------------- */ @@ -663,8 +665,8 @@ static void mesh_calc_finalize(const Mesh *mesh_input, Mesh *mesh_eval) mesh_eval->edit_mesh = mesh_input->edit_mesh; } -void BKE_mesh_wrapper_deferred_finalize(Mesh *me_eval, - const CustomData_MeshMasks *cd_mask_finalize) +void BKE_mesh_wrapper_deferred_finalize_mdata(Mesh *me_eval, + const CustomData_MeshMasks *cd_mask_finalize) { if (me_eval->runtime.wrapper_type_finalize & (1 << ME_WRAPPER_TYPE_BMESH)) { editbmesh_calc_modifier_final_normals(me_eval, cd_mask_finalize); @@ -1286,12 +1288,6 @@ bool editbmesh_modifier_is_enabled(const Scene *scene, static void editbmesh_calc_modifier_final_normals(Mesh *mesh_final, const CustomData_MeshMasks *final_datamask) { - if (mesh_final->runtime.wrapper_type != ME_WRAPPER_TYPE_MDATA) { - /* Generated at draw time. */ - mesh_final->runtime.wrapper_type_finalize = (1 << mesh_final->runtime.wrapper_type); - return; - } - const bool calc_loop_normals = ((mesh_final->flag & ME_AUTOSMOOTH) != 0 || (final_datamask->lmask & CD_MASK_NORMAL) != 0); @@ -1319,6 +1315,18 @@ static void editbmesh_calc_modifier_final_normals(Mesh *mesh_final, } } +static void editbmesh_calc_modifier_final_normals_or_defer( + Mesh *mesh_final, const CustomData_MeshMasks *final_datamask) +{ + if (mesh_final->runtime.wrapper_type != ME_WRAPPER_TYPE_MDATA) { + /* Generated at draw time. */ + mesh_final->runtime.wrapper_type_finalize = (1 << mesh_final->runtime.wrapper_type); + return; + } + + editbmesh_calc_modifier_final_normals(mesh_final, final_datamask); +} + static void editbmesh_calc_modifiers(struct Depsgraph *depsgraph, const Scene *scene, Object *ob, @@ -1598,9 +1606,9 @@ static void editbmesh_calc_modifiers(struct Depsgraph *depsgraph, } /* Compute normals. */ - editbmesh_calc_modifier_final_normals(mesh_final, &final_datamask); + editbmesh_calc_modifier_final_normals_or_defer(mesh_final, &final_datamask); if (mesh_cage && (mesh_cage != mesh_final)) { - editbmesh_calc_modifier_final_normals(mesh_cage, &final_datamask); + editbmesh_calc_modifier_final_normals_or_defer(mesh_cage, &final_datamask); } /* Return final mesh. */ diff --git a/source/blender/blenkernel/intern/mesh.cc b/source/blender/blenkernel/intern/mesh.cc index 2a14370bf93..cf05dc0404e 100644 --- a/source/blender/blenkernel/intern/mesh.cc +++ b/source/blender/blenkernel/intern/mesh.cc @@ -1190,6 +1190,11 @@ static void ensure_orig_index_layer(CustomData &data, const int size) void BKE_mesh_ensure_default_orig_index_customdata(Mesh *mesh) { BLI_assert(mesh->runtime.wrapper_type == ME_WRAPPER_TYPE_MDATA); + BKE_mesh_ensure_default_orig_index_customdata_no_check(mesh); +} + +void BKE_mesh_ensure_default_orig_index_customdata_no_check(Mesh *mesh) +{ ensure_orig_index_layer(mesh->vdata, mesh->totvert); ensure_orig_index_layer(mesh->edata, mesh->totedge); ensure_orig_index_layer(mesh->pdata, mesh->totpoly); diff --git a/source/blender/blenkernel/intern/mesh_wrapper.cc b/source/blender/blenkernel/intern/mesh_wrapper.cc index 0362e4866e3..0b61b876abe 100644 --- a/source/blender/blenkernel/intern/mesh_wrapper.cc +++ b/source/blender/blenkernel/intern/mesh_wrapper.cc @@ -103,11 +103,7 @@ void BKE_mesh_wrapper_ensure_mdata(Mesh *me) /* Must isolate multithreaded tasks while holding a mutex lock. */ blender::threading::isolate_task([&]() { - const eMeshWrapperType geom_type_orig = static_cast( - me->runtime.wrapper_type); - me->runtime.wrapper_type = ME_WRAPPER_TYPE_MDATA; - - switch (geom_type_orig) { + switch (static_cast(me->runtime.wrapper_type)) { case ME_WRAPPER_TYPE_MDATA: case ME_WRAPPER_TYPE_SUBD: { break; /* Quiet warning. */ @@ -132,7 +128,7 @@ void BKE_mesh_wrapper_ensure_mdata(Mesh *me) * There is also a performance aspect, where this also assumes that original indices are * always needed when converting an edit mesh to a mesh. That might be wrong, but it's not * harmful. */ - BKE_mesh_ensure_default_orig_index_customdata(me); + BKE_mesh_ensure_default_orig_index_customdata_no_check(me); EditMeshData *edit_data = me->runtime.edit_data; if (edit_data->vertexCos) { @@ -144,8 +140,12 @@ void BKE_mesh_wrapper_ensure_mdata(Mesh *me) } if (me->runtime.wrapper_type_finalize) { - BKE_mesh_wrapper_deferred_finalize(me, &me->runtime.cd_mask_extra); + BKE_mesh_wrapper_deferred_finalize_mdata(me, &me->runtime.cd_mask_extra); } + + /* Keep type assignment last, so that read-only access only uses the mdata code paths after all + * the underlying data has been initialized. */ + me->runtime.wrapper_type = ME_WRAPPER_TYPE_MDATA; }); BLI_mutex_unlock(mesh_eval_mutex); -- cgit v1.2.3 From bb3a53884394908ba34459c38d419e6c4abe6107 Mon Sep 17 00:00:00 2001 From: Siddhartha Jejurkar Date: Tue, 12 Jul 2022 19:27:48 +1000 Subject: Fix: Incorrect coordinates used in BLI_rct*_isect_segment functions Ref D15330. --- source/blender/blenlib/intern/rct.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/blenlib/intern/rct.c b/source/blender/blenlib/intern/rct.c index 0bb606c288e..7248db5b718 100644 --- a/source/blender/blenlib/intern/rct.c +++ b/source/blender/blenlib/intern/rct.c @@ -265,7 +265,7 @@ bool BLI_rcti_isect_segment(const rcti *rect, const int s1[2], const int s2[2]) /* diagonal: [/] */ tvec1[0] = rect->xmin; tvec1[1] = rect->ymin; - tvec2[0] = rect->xmin; + tvec2[0] = rect->xmax; tvec2[1] = rect->ymax; if (isect_segments_i(s1, s2, tvec1, tvec2)) { return true; @@ -311,7 +311,7 @@ bool BLI_rctf_isect_segment(const rctf *rect, const float s1[2], const float s2[ /* diagonal: [/] */ tvec1[0] = rect->xmin; tvec1[1] = rect->ymin; - tvec2[0] = rect->xmin; + tvec2[0] = rect->xmax; tvec2[1] = rect->ymax; if (isect_segments_fl(s1, s2, tvec1, tvec2)) { return true; -- cgit v1.2.3 From 52b7f2b089071a6d2281e369ab820c9406e06c4e Mon Sep 17 00:00:00 2001 From: Siddhartha Jejurkar Date: Tue, 12 Jul 2022 19:39:57 +1000 Subject: UV: Box and lasso selection for partially intersecting edges In UV edge mode, box and lasso selections allow edge selections only when the entire edge is contained within the selection area. This doesn't consider any edges that partially overlap with the selection area. This is now fixed by adding a second pass, similar to how these operators work for edit-mesh selections. Now if both operators are unable to find any edges contained within the selection area, then they will perform a second pass which checks for edges that partially intersect with the selection area. Now edge selection in the UV editor matches edit-mesh edge-selection when drawing wire-frame. Resolves T99443. Ref D15362 --- source/blender/editors/include/UI_view2d.h | 6 +++ source/blender/editors/interface/view2d.cc | 35 ++++++++++++++ source/blender/editors/uvedit/uvedit_select.c | 69 +++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) diff --git a/source/blender/editors/include/UI_view2d.h b/source/blender/editors/include/UI_view2d.h index 5c4eb254462..e508c96b4f1 100644 --- a/source/blender/editors/include/UI_view2d.h +++ b/source/blender/editors/include/UI_view2d.h @@ -309,6 +309,12 @@ float UI_view2d_view_to_region_y(const struct View2D *v2d, float y); bool UI_view2d_view_to_region_clip( const struct View2D *v2d, float x, float y, int *r_region_x, int *r_region_y) ATTR_NONNULL(); +bool UI_view2d_view_to_region_segment_clip(const View2D *v2d, + const float xy_a[2], + const float xy_b[2], + int r_region_a[2], + int r_region_b[2]) ATTR_NONNULL(); + /** * Convert from 2d-view space to screen/region space * diff --git a/source/blender/editors/interface/view2d.cc b/source/blender/editors/interface/view2d.cc index ee4bfd351ae..1bf7e25b154 100644 --- a/source/blender/editors/interface/view2d.cc +++ b/source/blender/editors/interface/view2d.cc @@ -1695,6 +1695,41 @@ void UI_view2d_view_to_region_fl( *r_region_y = v2d->mask.ymin + (y * BLI_rcti_size_y(&v2d->mask)); } +bool UI_view2d_view_to_region_segment_clip(const View2D *v2d, + const float xy_a[2], + const float xy_b[2], + int r_region_a[2], + int r_region_b[2]) +{ + rctf rect_unit; + rect_unit.xmin = rect_unit.ymin = 0.0f; + rect_unit.xmax = rect_unit.ymax = 1.0f; + + /* Express given coordinates as proportional values. */ + const float s_a[2] = { + (xy_a[0] - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur), + (xy_a[1] - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur), + }; + const float s_b[2] = { + (xy_b[0] - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur), + (xy_b[1] - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur), + }; + + /* Set initial value in case coordinates lie outside bounds. */ + r_region_a[0] = r_region_b[0] = r_region_a[1] = r_region_b[1] = V2D_IS_CLIPPED; + + if (BLI_rctf_isect_segment(&rect_unit, s_a, s_b)) { + r_region_a[0] = (int)(v2d->mask.xmin + (s_a[0] * BLI_rcti_size_x(&v2d->mask))); + r_region_a[1] = (int)(v2d->mask.ymin + (s_a[1] * BLI_rcti_size_y(&v2d->mask))); + r_region_b[0] = (int)(v2d->mask.xmin + (s_b[0] * BLI_rcti_size_x(&v2d->mask))); + r_region_b[1] = (int)(v2d->mask.ymin + (s_b[1] * BLI_rcti_size_y(&v2d->mask))); + + return true; + } + + return false; +} + void UI_view2d_view_to_region_rcti(const View2D *v2d, const rctf *rect_src, rcti *rect_dst) { const float cur_size[2] = {BLI_rctf_size_x(&v2d->cur), BLI_rctf_size_y(&v2d->cur)}; diff --git a/source/blender/editors/uvedit/uvedit_select.c b/source/blender/editors/uvedit/uvedit_select.c index db834f6a0fd..b5a564fd984 100644 --- a/source/blender/editors/uvedit/uvedit_select.c +++ b/source/blender/editors/uvedit/uvedit_select.c @@ -3582,6 +3582,7 @@ static int uv_box_select_exec(bContext *C, wmOperator *op) } } else if (use_edge && !pinned) { + bool do_second_pass = true; BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; @@ -3596,11 +3597,35 @@ static int uv_box_select_exec(bContext *C, wmOperator *op) uvedit_edge_select_set_with_sticky( scene, em, l_prev, select, false, cd_loop_uv_offset); changed = true; + do_second_pass = false; } l_prev = l; luv_prev = luv; } } + /* Do a second pass if no complete edges could be selected. + * This matches wire-frame edit-mesh selection in the 3D view. */ + if (do_second_pass) { + /* Second pass to check if edges partially overlap with the selection area (box). */ + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + if (!uvedit_face_visible_test(scene, efa)) { + continue; + } + BMLoop *l_prev = BM_FACE_FIRST_LOOP(efa)->prev; + MLoopUV *luv_prev = BM_ELEM_CD_GET_VOID_P(l_prev, cd_loop_uv_offset); + + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + if (BLI_rctf_isect_segment(&rectf, luv_prev->uv, luv->uv)) { + uvedit_edge_select_set_with_sticky( + scene, em, l_prev, select, false, cd_loop_uv_offset); + changed = true; + } + l_prev = l; + luv_prev = luv; + } + } + } } else { /* other selection modes */ @@ -3920,6 +3945,24 @@ static bool do_lasso_select_mesh_uv_is_point_inside(const ARegion *region, return false; } +static bool do_lasso_select_mesh_uv_is_edge_inside(const ARegion *region, + const rcti *clip_rect, + const int mcoords[][2], + const int mcoords_len, + const float co_test_a[2], + const float co_test_b[2]) +{ + int co_screen_a[2], co_screen_b[2]; + if (UI_view2d_view_to_region_segment_clip( + ®ion->v2d, co_test_a, co_test_b, co_screen_a, co_screen_b) && + BLI_rcti_isect_segment(clip_rect, co_screen_a, co_screen_b) && + BLI_lasso_is_edge_inside( + mcoords, mcoords_len, UNPACK2(co_screen_a), UNPACK2(co_screen_b), V2D_IS_CLIPPED)) { + return true; + } + return false; +} + static bool do_lasso_select_mesh_uv(bContext *C, const int mcoords[][2], const int mcoords_len, @@ -3988,6 +4031,7 @@ static bool do_lasso_select_mesh_uv(bContext *C, } } else if (use_edge) { + bool do_second_pass = true; BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; @@ -4004,12 +4048,37 @@ static bool do_lasso_select_mesh_uv(bContext *C, region, &rect, mcoords, mcoords_len, luv_prev->uv)) { uvedit_edge_select_set_with_sticky( scene, em, l_prev, select, false, cd_loop_uv_offset); + do_second_pass = false; changed = true; } l_prev = l; luv_prev = luv; } } + /* Do a second pass if no complete edges could be selected. + * This matches wire-frame edit-mesh selection in the 3D view. */ + if (do_second_pass) { + /* Second pass to check if edges partially overlap with the selection area (lasso). */ + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + if (!uvedit_face_visible_test(scene, efa)) { + continue; + } + BMLoop *l_prev = BM_FACE_FIRST_LOOP(efa)->prev; + MLoopUV *luv_prev = BM_ELEM_CD_GET_VOID_P(l_prev, cd_loop_uv_offset); + + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + if (do_lasso_select_mesh_uv_is_edge_inside( + region, &rect, mcoords, mcoords_len, luv->uv, luv_prev->uv)) { + uvedit_edge_select_set_with_sticky( + scene, em, l_prev, select, false, cd_loop_uv_offset); + changed = true; + } + l_prev = l; + luv_prev = luv; + } + } + } } else { /* Vert Selection. */ BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT, BM_ELEM_TAG, false); -- cgit v1.2.3 From 4344b2bf19e5f678de0d2cd5311a10390f123578 Mon Sep 17 00:00:00 2001 From: RedMser Date: Tue, 12 Jul 2022 12:56:53 +0200 Subject: Markers: Make delete confirmation depend on key used Add a 'Delete Confirmation' operator property to the 'Delete Marker' operator. This determines whether the user is asked to confirm the deletion or not. Defaults so that only {key X} ({key Backspace} for industry compatible keymap) prompts for deletion, whereas {key Del} does not show the confirmation popup. This also makes the default keymap for marker deletion consistent with the common delete operators (such as objects and keyframes). Reviewed By: sybren Differential Revision: https://developer.blender.org/D13818 --- release/scripts/presets/keyconfig/keymap_data/blender_default.py | 2 +- .../scripts/presets/keyconfig/keymap_data/industry_compatible_data.py | 2 +- source/blender/editors/animation/anim_markers.c | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 9c7aa67ddad..e1e88a0e48d 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -1027,7 +1027,7 @@ def km_markers(params): ("marker.select_box", {"type": 'B', "value": 'PRESS'}, None), *_template_items_select_actions(params, "marker.select_all"), ("marker.delete", {"type": 'X', "value": 'PRESS'}, None), - ("marker.delete", {"type": 'DEL', "value": 'PRESS'}, None), + ("marker.delete", {"type": 'DEL', "value": 'PRESS'}, {"properties": [("confirm", False)]}), op_panel("TOPBAR_PT_name_marker", {"type": 'F2', "value": 'PRESS'}, [("keep_open", False)]), ("marker.move", {"type": 'G', "value": 'PRESS'}, None), ("marker.camera_bind", {"type": 'B', "value": 'PRESS', "ctrl": True}, None), diff --git a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py index 8feaa7e928f..c9d66afb796 100644 --- a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py +++ b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py @@ -844,7 +844,7 @@ def km_markers(params): "shift": True}, {"properties": [("action", 'DESELECT')]}), ("marker.select_all", {"type": 'I', "value": 'PRESS', "ctrl": True}, {"properties": [("action", 'INVERT')]}), ("marker.delete", {"type": 'BACK_SPACE', "value": 'PRESS'}, None), - ("marker.delete", {"type": 'DEL', "value": 'PRESS'}, None), + ("marker.delete", {"type": 'DEL', "value": 'PRESS'}, {"properties": [("confirm", False)]}), op_panel("TOPBAR_PT_name_marker", {"type": 'RET', "value": 'PRESS'}, [("keep_open", False)]), ("marker.move", {"type": 'W', "value": 'PRESS'}, None), ]) diff --git a/source/blender/editors/animation/anim_markers.c b/source/blender/editors/animation/anim_markers.c index 03a2caf4b7d..3608140a29d 100644 --- a/source/blender/editors/animation/anim_markers.c +++ b/source/blender/editors/animation/anim_markers.c @@ -1588,12 +1588,13 @@ static void MARKER_OT_delete(wmOperatorType *ot) ot->idname = "MARKER_OT_delete"; /* api callbacks */ - ot->invoke = WM_operator_confirm; + ot->invoke = WM_operator_confirm_or_exec; ot->exec = ed_marker_delete_exec; ot->poll = ed_markers_poll_selected_no_locked_markers; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + WM_operator_properties_confirm_or_exec(ot); } /** \} */ -- cgit v1.2.3 From 57097e9a8515048b580459354fa197f78a442409 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Tue, 12 Jul 2022 14:27:50 +0200 Subject: Fix T99261: partial LibOverride creation would not properly remap all needed data. When creating partial overrides, there may also be need to reamap usage of linked data towards already existing overrides, in newly created overrides. --- source/blender/blenkernel/intern/lib_override.cc | 51 ++++++++++++++++++------ 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/source/blender/blenkernel/intern/lib_override.cc b/source/blender/blenkernel/intern/lib_override.cc index cdea97b6c6a..1969ec9196c 100644 --- a/source/blender/blenkernel/intern/lib_override.cc +++ b/source/blender/blenkernel/intern/lib_override.cc @@ -428,14 +428,40 @@ static void lib_override_prefill_newid_from_existing_overrides(Main *bmain, ID * { ID *id_iter; FOREACH_MAIN_ID_BEGIN (bmain, id_iter) { - if (ID_IS_OVERRIDE_LIBRARY_REAL(id_iter) && - id_iter->override_library->hierarchy_root == id_hierarchy_root) { - id_iter->override_library->reference->newid = id_iter; + ID *id = id_iter; + if (GS(id_iter->name) == ID_KE) { + id = reinterpret_cast(id_iter)->from; + BLI_assert(id != nullptr); + } + if (ID_IS_OVERRIDE_LIBRARY_REAL(id) && + id->override_library->hierarchy_root == id_hierarchy_root) { + id->override_library->reference->newid = id; + if (GS(id_iter->name) == ID_KE) { + Key *reference_key = BKE_key_from_id(id->override_library->reference); + if (reference_key != nullptr) { + reference_key->id.newid = id_iter; + } + } } } FOREACH_MAIN_ID_END; } +static void lib_override_remapper_overrides_add(IDRemapper *id_remapper, + ID *reference_id, + ID *local_id) +{ + BKE_id_remapper_add(id_remapper, reference_id, local_id); + + Key *reference_key, *local_key = nullptr; + if ((reference_key = BKE_key_from_id(reference_id)) != nullptr) { + local_key = BKE_key_from_id(reference_id->newid); + BLI_assert(local_key != nullptr); + + BKE_id_remapper_add(id_remapper, &reference_key->id, &local_key->id); + } +} + /* TODO: Make this static local function instead? API is becoming complex, and it's not used * outside of this file anyway. */ bool BKE_lib_override_library_create_from_tag(Main *bmain, @@ -544,6 +570,7 @@ bool BKE_lib_override_library_create_from_tag(Main *bmain, BLI_assert(id_hierarchy_root != nullptr); LinkNode *relinked_ids = nullptr; + IDRemapper *id_remapper = BKE_id_remapper_create(); /* Still checking the whole Main, that way we can tag other local IDs as needing to be * remapped to use newly created overriding IDs, if needed. */ ID *id; @@ -568,6 +595,13 @@ bool BKE_lib_override_library_create_from_tag(Main *bmain, * consider we should also relink it, as part of recursive resync. */ if ((other_id->tag & LIB_TAG_DOIT) != 0 && other_id->lib != id_root_reference->lib) { BLI_linklist_prepend(&relinked_ids, other_id); + if (ID_IS_OVERRIDE_LIBRARY_REAL(other_id) && + other_id->override_library->hierarchy_root == id_hierarchy_root) { + reference_id = other_id->override_library->reference; + ID *local_id = reference_id->newid; + BLI_assert(other_id == local_id); + lib_override_remapper_overrides_add(id_remapper, reference_id, local_id); + } } if (other_id != id) { other_id->lib = id_root_reference->lib; @@ -575,7 +609,6 @@ bool BKE_lib_override_library_create_from_tag(Main *bmain, } FOREACH_MAIN_ID_END; - IDRemapper *id_remapper = BKE_id_remapper_create(); for (todo_id_iter = static_cast(todo_ids.first); todo_id_iter != nullptr; todo_id_iter = todo_id_iter->next) { reference_id = static_cast(todo_id_iter->data); @@ -587,15 +620,7 @@ bool BKE_lib_override_library_create_from_tag(Main *bmain, local_id->override_library->hierarchy_root = id_hierarchy_root; - BKE_id_remapper_add(id_remapper, reference_id, local_id); - - Key *reference_key, *local_key = nullptr; - if ((reference_key = BKE_key_from_id(reference_id)) != nullptr) { - local_key = BKE_key_from_id(reference_id->newid); - BLI_assert(local_key != nullptr); - - BKE_id_remapper_add(id_remapper, &reference_key->id, &local_key->id); - } + lib_override_remapper_overrides_add(id_remapper, reference_id, local_id); } BKE_libblock_relink_multiple(bmain, -- cgit v1.2.3 From 2d1fe736fabdcccdd46edfbdfc68720498c30e10 Mon Sep 17 00:00:00 2001 From: Germano Cavalcante Date: Tue, 12 Jul 2022 10:11:05 -0300 Subject: Fix T96238: shrinking to zero doesn't actually scales the radius to zero Probably to prevent the radius of a point from being stuck at zero, the `Shrink/Fatten` curve operator sets a minimum value of `0.001` for all points being transformed. This is an inconvenience as these points may have been purposely set to zero on the panel. And it also doesn't follow the convention seen in other operators (which keep the value zero). So remove this limitation. --- .../blender/editors/transform/transform_mode_curveshrinkfatten.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/source/blender/editors/transform/transform_mode_curveshrinkfatten.c b/source/blender/editors/transform/transform_mode_curveshrinkfatten.c index aa4d608de04..7b65417d32b 100644 --- a/source/blender/editors/transform/transform_mode_curveshrinkfatten.c +++ b/source/blender/editors/transform/transform_mode_curveshrinkfatten.c @@ -65,9 +65,7 @@ static void applyCurveShrinkFatten(TransInfo *t, const int UNUSED(mval[2])) *td->val = td->ival * ratio; /* apply PET */ *td->val = (*td->val * td->factor) + ((1.0f - td->factor) * td->ival); - if (*td->val <= 0.0f) { - *td->val = 0.001f; - } + CLAMP_MIN(*td->val, 0.0f); } } } @@ -93,10 +91,6 @@ void initCurveShrinkFatten(TransInfo *t) t->num.unit_sys = t->scene->unit.system; t->num.unit_type[0] = B_UNIT_NONE; -#ifdef USE_NUM_NO_ZERO - t->num.val_flag[0] |= NUM_NO_ZERO; -#endif - t->flag |= T_NO_CONSTRAINT; } -- cgit v1.2.3 From f72cedffb63e08abc71ae1c6d31408457b1005a9 Mon Sep 17 00:00:00 2001 From: Germano Cavalcante Date: Tue, 12 Jul 2022 10:20:01 -0300 Subject: Cleanup: Use interpf instead of repeating the logic This makes the code clearer. --- source/blender/editors/transform/transform_mode_curveshrinkfatten.c | 2 +- source/blender/editors/transform/transform_mode_gpopacity.c | 2 +- source/blender/editors/transform/transform_mode_gpshrinkfatten.c | 2 +- source/blender/editors/transform/transform_mode_maskshrinkfatten.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/blender/editors/transform/transform_mode_curveshrinkfatten.c b/source/blender/editors/transform/transform_mode_curveshrinkfatten.c index 7b65417d32b..f7f9e14b8ac 100644 --- a/source/blender/editors/transform/transform_mode_curveshrinkfatten.c +++ b/source/blender/editors/transform/transform_mode_curveshrinkfatten.c @@ -64,7 +64,7 @@ static void applyCurveShrinkFatten(TransInfo *t, const int UNUSED(mval[2])) if (td->val) { *td->val = td->ival * ratio; /* apply PET */ - *td->val = (*td->val * td->factor) + ((1.0f - td->factor) * td->ival); + *td->val = interpf(*td->val, td->ival, td->factor); CLAMP_MIN(*td->val, 0.0f); } } diff --git a/source/blender/editors/transform/transform_mode_gpopacity.c b/source/blender/editors/transform/transform_mode_gpopacity.c index 83dce17d104..8b9431b65ea 100644 --- a/source/blender/editors/transform/transform_mode_gpopacity.c +++ b/source/blender/editors/transform/transform_mode_gpopacity.c @@ -74,7 +74,7 @@ static void applyGPOpacity(TransInfo *t, const int UNUSED(mval[2])) if (td->val) { *td->val = td->ival * ratio; /* apply PET */ - *td->val = (*td->val * td->factor) + ((1.0f - td->factor) * td->ival); + *td->val = interpf(*td->val, td->ival, td->factor); CLAMP(*td->val, 0.0f, 1.0f); } } diff --git a/source/blender/editors/transform/transform_mode_gpshrinkfatten.c b/source/blender/editors/transform/transform_mode_gpshrinkfatten.c index 796d5c7ae9c..d8ec7d4ff50 100644 --- a/source/blender/editors/transform/transform_mode_gpshrinkfatten.c +++ b/source/blender/editors/transform/transform_mode_gpshrinkfatten.c @@ -74,7 +74,7 @@ static void applyGPShrinkFatten(TransInfo *t, const int UNUSED(mval[2])) if (td->val) { *td->val = td->ival * ratio; /* apply PET */ - *td->val = (*td->val * td->factor) + ((1.0f - td->factor) * td->ival); + *td->val = interpf(*td->val, td->ival, td->factor); if (*td->val <= 0.0f) { *td->val = 0.001f; } diff --git a/source/blender/editors/transform/transform_mode_maskshrinkfatten.c b/source/blender/editors/transform/transform_mode_maskshrinkfatten.c index 19a3deade63..e2ccf61796b 100644 --- a/source/blender/editors/transform/transform_mode_maskshrinkfatten.c +++ b/source/blender/editors/transform/transform_mode_maskshrinkfatten.c @@ -90,7 +90,7 @@ static void applyMaskShrinkFatten(TransInfo *t, const int UNUSED(mval[2])) } /* apply PET */ - *td->val = (*td->val * td->factor) + ((1.0f - td->factor) * td->ival); + *td->val = interpf(*td->val, td->ival, td->factor); if (*td->val <= 0.0f) { *td->val = 0.001f; } -- cgit v1.2.3 From 4a445c8dc0edfe793e547ac49194df72c372f95a Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Tue, 12 Jul 2022 15:34:40 +0200 Subject: LibOverride: Fix some issues from.revealed by recent previous commit. rB57097e9a8515 did not properly consider case where you have more than one override for a same reference linked ID. Also adds more security checks around shapekeys, since match between override and its linked reference is never ensured either way (fixes a crash reported by @Rik from Blender studio). --- source/blender/blenkernel/intern/lib_override.cc | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/source/blender/blenkernel/intern/lib_override.cc b/source/blender/blenkernel/intern/lib_override.cc index 1969ec9196c..aa3210b64ad 100644 --- a/source/blender/blenkernel/intern/lib_override.cc +++ b/source/blender/blenkernel/intern/lib_override.cc @@ -455,8 +455,10 @@ static void lib_override_remapper_overrides_add(IDRemapper *id_remapper, Key *reference_key, *local_key = nullptr; if ((reference_key = BKE_key_from_id(reference_id)) != nullptr) { - local_key = BKE_key_from_id(reference_id->newid); - BLI_assert(local_key != nullptr); + if (reference_id->newid != nullptr) { + local_key = BKE_key_from_id(reference_id->newid); + BLI_assert(local_key != nullptr); + } BKE_id_remapper_add(id_remapper, &reference_key->id, &local_key->id); } @@ -599,8 +601,9 @@ bool BKE_lib_override_library_create_from_tag(Main *bmain, other_id->override_library->hierarchy_root == id_hierarchy_root) { reference_id = other_id->override_library->reference; ID *local_id = reference_id->newid; - BLI_assert(other_id == local_id); - lib_override_remapper_overrides_add(id_remapper, reference_id, local_id); + if (other_id == local_id) { + lib_override_remapper_overrides_add(id_remapper, reference_id, local_id); + } } } if (other_id != id) { -- cgit v1.2.3 From 47dd42485e19106601167f6be7b5960c4be25167 Mon Sep 17 00:00:00 2001 From: Xavier Hallade Date: Tue, 12 Jul 2022 15:45:46 +0200 Subject: Cycles: fix and enable JIT oneAPI CentOS7 builds for drivers 23570+ The current specific CentOS7 workaround we have for AoT, which is to disable __FAST_MATH__ by using -fhonor-nans, now also fixes the compilation issue for JIT as well since at least driver 23570. --- intern/cycles/blender/addon/properties.py | 2 +- intern/cycles/kernel/CMakeLists.txt | 7 +++---- intern/cycles/kernel/device/oneapi/kernel.cpp | 5 +---- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/intern/cycles/blender/addon/properties.py b/intern/cycles/blender/addon/properties.py index e88b65b5119..4a8854fd868 100644 --- a/intern/cycles/blender/addon/properties.py +++ b/intern/cycles/blender/addon/properties.py @@ -1560,7 +1560,7 @@ class CyclesPreferences(bpy.types.AddonPreferences): if sys.platform.startswith("win"): col.label(text="and Windows driver version 101.1660 or newer", icon='BLANK1') elif sys.platform.startswith("linux"): - col.label(text="and Linux driver version xx.xx.28000 or newer", icon='BLANK1') + col.label(text="and Linux driver version xx.xx.23570 or newer", icon='BLANK1') elif device_type == 'METAL': col.label(text="Requires Apple Silicon with macOS 12.2 or newer", icon='BLANK1') col.label(text="or AMD with macOS 12.3 or newer", icon='BLANK1') diff --git a/intern/cycles/kernel/CMakeLists.txt b/intern/cycles/kernel/CMakeLists.txt index b893ff6ef24..57a26edff50 100644 --- a/intern/cycles/kernel/CMakeLists.txt +++ b/intern/cycles/kernel/CMakeLists.txt @@ -844,10 +844,9 @@ if(WITH_CYCLES_DEVICE_ONEAPI) else() list(APPEND sycl_compiler_flags -fPIC) - # avoid getting __FAST_MATH__ to be defined for the graphics compiler on CentOS 7 until the compile-time issue it triggers gets fixed. - if(WITH_CYCLES_ONEAPI_BINARIES) - list(APPEND sycl_compiler_flags -fhonor-nans) - endif() + # We avoid getting __FAST_MATH__ to be defined when building on CentOS 7 until the compilation crash + # it triggers at either AoT or JIT stages gets fixed. + list(APPEND sycl_compiler_flags -fhonor-nans) # add $ORIGIN to cycles_kernel_oneapi.so rpath so libsycl.so and # libpi_level_zero.so can be placed next to it and get found. diff --git a/intern/cycles/kernel/device/oneapi/kernel.cpp b/intern/cycles/kernel/device/oneapi/kernel.cpp index 82910d72105..300e201600c 100644 --- a/intern/cycles/kernel/device/oneapi/kernel.cpp +++ b/intern/cycles/kernel/device/oneapi/kernel.cpp @@ -670,10 +670,7 @@ bool oneapi_enqueue_kernel(KernelContext *kernel_context, } static const int lowest_supported_driver_version_win = 1011660; -/* TODO: once Linux JIT compilation crash from CentOS generated spv is fixed, adjust version below. - * Until then, set CYCLES_ONEAPI_ALL_DEVICES environment variable to avoid getting it filtered out. - */ -static const int lowest_supported_driver_version_neo = 28000; +static const int lowest_supported_driver_version_neo = 23570; static int parse_driver_build_version(const sycl::device &device) { -- cgit v1.2.3 From d58072caf4abb254d5e5f0e3b9de9bef775f287a Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Tue, 12 Jul 2022 16:26:50 +0200 Subject: Fix: missing geometry copy before modifying it A geometry component may reference read-only geometry. In this case it has to be copied before making changes to it. This was caused by rBb876ce2a4a4638142. --- source/blender/blenkernel/intern/geometry_component_curve.cc | 3 ++- source/blender/blenkernel/intern/geometry_component_curves.cc | 3 ++- source/blender/blenkernel/intern/geometry_component_mesh.cc | 3 ++- source/blender/blenkernel/intern/geometry_component_pointcloud.cc | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc index 0d899ec7b06..22f105af0f1 100644 --- a/source/blender/blenkernel/intern/geometry_component_curve.cc +++ b/source/blender/blenkernel/intern/geometry_component_curve.cc @@ -1452,7 +1452,8 @@ std::optional CurveComponentLegacy::attributes( std::optional CurveComponentLegacy::attributes_for_write() { - return blender::bke::MutableAttributeAccessor(curve_, + CurveEval *curve = this->get_for_write(); + return blender::bke::MutableAttributeAccessor(curve, blender::bke::get_curve_accessor_functions_ref()); } diff --git a/source/blender/blenkernel/intern/geometry_component_curves.cc b/source/blender/blenkernel/intern/geometry_component_curves.cc index 34c17bedc2c..f803b08e740 100644 --- a/source/blender/blenkernel/intern/geometry_component_curves.cc +++ b/source/blender/blenkernel/intern/geometry_component_curves.cc @@ -644,6 +644,7 @@ std::optional CurveComponent::attributes() cons std::optional CurveComponent::attributes_for_write() { - return blender::bke::MutableAttributeAccessor(curves_ ? &curves_->geometry : nullptr, + Curves *curves = this->get_for_write(); + return blender::bke::MutableAttributeAccessor(curves ? &curves->geometry : nullptr, blender::bke::get_curves_accessor_functions_ref()); } diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc index cb36b9b19f7..cf6681a69be 100644 --- a/source/blender/blenkernel/intern/geometry_component_mesh.cc +++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc @@ -1324,7 +1324,8 @@ std::optional MeshComponent::attributes() const std::optional MeshComponent::attributes_for_write() { - return blender::bke::MutableAttributeAccessor(mesh_, + Mesh *mesh = this->get_for_write(); + return blender::bke::MutableAttributeAccessor(mesh, blender::bke::get_mesh_accessor_functions_ref()); } diff --git a/source/blender/blenkernel/intern/geometry_component_pointcloud.cc b/source/blender/blenkernel/intern/geometry_component_pointcloud.cc index b439a9ba7f8..ccc97f92dbc 100644 --- a/source/blender/blenkernel/intern/geometry_component_pointcloud.cc +++ b/source/blender/blenkernel/intern/geometry_component_pointcloud.cc @@ -226,8 +226,9 @@ std::optional PointCloudComponent::attributes() std::optional PointCloudComponent::attributes_for_write() { + PointCloud *pointcloud = this->get_for_write(); return blender::bke::MutableAttributeAccessor( - pointcloud_, blender::bke::get_pointcloud_accessor_functions_ref()); + pointcloud, blender::bke::get_pointcloud_accessor_functions_ref()); } /** \} */ -- cgit v1.2.3 From 93253d5dcc2938903ae4de3a23fcdb724d202d44 Mon Sep 17 00:00:00 2001 From: Khoi Dau Date: Tue, 12 Jul 2022 11:30:41 -0300 Subject: Fix T99103: crash when displaying or rendering Grease Pencil object On some hardware/systems, blender may crash when adding, rendering or displaying Grease Pencil objects. In `/source/blender/draw/engines/gpencil/shaders/gpencil_vert.glsl`, line 35: ``` gpMaterialFlag gp_flag = floatBitsToInt(gp_mat._flag); ``` `gpMaterialFlag` is of type `uint`. This is a mismatched-type assignment that can cause crashes on some hardware/systems with GLSL that do not support implicit type casting. So use `floatBitsToUint` for type conversion. Differential Revision: https://developer.blender.org/D15433 --- source/blender/draw/engines/gpencil/shaders/gpencil_vert.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/draw/engines/gpencil/shaders/gpencil_vert.glsl b/source/blender/draw/engines/gpencil/shaders/gpencil_vert.glsl index 5f5419bac47..b0ee059cb9d 100644 --- a/source/blender/draw/engines/gpencil/shaders/gpencil_vert.glsl +++ b/source/blender/draw/engines/gpencil/shaders/gpencil_vert.glsl @@ -32,7 +32,7 @@ void main() vec3 vert_N; gpMaterial gp_mat = materials[ma1.x + gpMaterialOffset]; - gpMaterialFlag gp_flag = floatBitsToInt(gp_mat._flag); + gpMaterialFlag gp_flag = floatBitsToUint(gp_mat._flag); gl_Position = gpencil_vertex(ma, ma1, -- cgit v1.2.3 From b767628173446433f12b321d9209f9be11aee58c Mon Sep 17 00:00:00 2001 From: Sebastian Parborg Date: Tue, 12 Jul 2022 16:58:04 +0200 Subject: Fix: Memory leaks in indexer code Reviewed By: Richard Antalik Differential Revision: http://developer.blender.org/D15376 --- source/blender/imbuf/intern/indexer.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/blender/imbuf/intern/indexer.c b/source/blender/imbuf/intern/indexer.c index cbc5d984755..00396c01d99 100644 --- a/source/blender/imbuf/intern/indexer.c +++ b/source/blender/imbuf/intern/indexer.c @@ -1098,6 +1098,7 @@ static int indexer_performance_get_decode_rate(FFmpegIndexBuilderContext *contex while (av_read_frame(context->iFormatCtx, packet) >= 0) { if (packet->stream_index != context->videoStream) { + av_packet_unref(packet); continue; } @@ -1121,6 +1122,7 @@ static int indexer_performance_get_decode_rate(FFmpegIndexBuilderContext *contex if (end > start + time_period) { break; } + av_packet_unref(packet); } av_packet_free(&packet); @@ -1145,6 +1147,7 @@ static int indexer_performance_get_max_gop_size(FFmpegIndexBuilderContext *conte while (av_read_frame(context->iFormatCtx, packet) >= 0) { if (packet->stream_index != context->videoStream) { + av_packet_unref(packet); continue; } packet_index++; @@ -1158,6 +1161,7 @@ static int indexer_performance_get_max_gop_size(FFmpegIndexBuilderContext *conte if (packet_index > packets_max) { break; } + av_packet_unref(packet); } av_packet_free(&packet); -- cgit v1.2.3 From 02aefa7659630258f2eb2498947faf398cb375ff Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Tue, 12 Jul 2022 17:09:21 +0200 Subject: Fix: wrong node name in menu --- release/scripts/startup/nodeitems_builtins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py index e59c98163d7..6f3054100f8 100644 --- a/release/scripts/startup/nodeitems_builtins.py +++ b/release/scripts/startup/nodeitems_builtins.py @@ -73,7 +73,7 @@ def curve_node_items(context): yield NodeItem("GeometryNodeCurveLength") yield NodeItem("GeometryNodeCurveToMesh") yield NodeItem("GeometryNodeCurveToPoints") - yield NodeItem("GeometryNodeDeformCurvesWithSurface") + yield NodeItem("GeometryNodeDeformCurvesOnSurface") yield NodeItem("GeometryNodeFillCurve") yield NodeItem("GeometryNodeFilletCurve") yield NodeItem("GeometryNodeResampleCurve") -- cgit v1.2.3 From 1c382a4940c6e609786c92c795a25eb8ff7ea608 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Tue, 12 Jul 2022 17:10:04 +0200 Subject: Curves: improve error checking in deform curves on surface node --- .../geometry/nodes/node_geo_deform_curves_on_surface.cc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc index 0a446492c6b..7f03d025db7 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc @@ -218,6 +218,12 @@ static void node_geo_exec(GeoNodeExecParams params) return; } const Curves *self_curves_eval = static_cast(self_ob_eval->data); + if (self_curves_eval->surface_uv_map == nullptr || self_curves_eval->surface_uv_map[0] == '\0') { + pass_through_input(); + const char *message = TIP_("Surface UV map not defined"); + params.error_message_add(NodeWarningType::Error, message); + return; + } /* Take surface information from self-object. */ Object *surface_ob_eval = self_curves_eval->surface; const StringRefNull uv_map_name = self_curves_eval->surface_uv_map; @@ -258,12 +264,6 @@ static void node_geo_exec(GeoNodeExecParams params) Curves &curves_id = *curves_geometry.get_curves_for_write(); CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); - if (uv_map_name.is_empty()) { - pass_through_input(); - const char *message = TIP_("Surface UV map not defined"); - params.error_message_add(NodeWarningType::Error, message); - return; - } if (!mesh_attributes_eval.contains(uv_map_name)) { pass_through_input(); char *message = BLI_sprintfN(TIP_("Evaluated surface missing UV map: %s"), @@ -285,7 +285,7 @@ static void node_geo_exec(GeoNodeExecParams params) TIP_("Evaluated surface missing attribute: rest_position")); return; } - if (curves.surface_uv_coords().is_empty()) { + if (curves.surface_uv_coords().is_empty() && curves.curves_num() > 0) { pass_through_input(); params.error_message_add(NodeWarningType::Error, TIP_("Curves are not attached to any UV map")); -- cgit v1.2.3 From 9f153949f9ee1cbea04687c2a99d9e1e2f891962 Mon Sep 17 00:00:00 2001 From: Erik Abrahamsson Date: Tue, 12 Jul 2022 18:31:33 +0200 Subject: Enable "copy to selected" for new Curves modifiers The operator bpy.ops.object.modifier_copy_to_selected() does not work for the new Curves objects. This is because it isn't added to BKE_object_supports_modifiers. Differential Revision: https://developer.blender.org/D15439 --- source/blender/blenkernel/intern/object.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/source/blender/blenkernel/intern/object.cc b/source/blender/blenkernel/intern/object.cc index f7436b6112c..62ebb45b0ed 100644 --- a/source/blender/blenkernel/intern/object.cc +++ b/source/blender/blenkernel/intern/object.cc @@ -1402,6 +1402,7 @@ bool BKE_object_supports_modifiers(const Object *ob) { return (ELEM(ob->type, OB_MESH, + OB_CURVES, OB_CURVES_LEGACY, OB_SURF, OB_FONT, -- cgit v1.2.3 From 5f09440d5a6ca914651700c31cd8873fe4e496d0 Mon Sep 17 00:00:00 2001 From: Xavier Hallade Date: Tue, 12 Jul 2022 18:29:58 +0200 Subject: Cycles: Make not-compact BVH the default for embree Measurements shown on average a 1.08x speedup for a 1.04x increase in memory usage which is an acceptable trade off for a default setting, although discoverability of such settings influencing memory usage could be improved. Reviewed By: brecht Differential Revision: https://developer.blender.org/D15429 --- intern/cycles/blender/addon/properties.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intern/cycles/blender/addon/properties.py b/intern/cycles/blender/addon/properties.py index 4a8854fd868..2c926893f9d 100644 --- a/intern/cycles/blender/addon/properties.py +++ b/intern/cycles/blender/addon/properties.py @@ -693,7 +693,7 @@ class CyclesRenderSettings(bpy.types.PropertyGroup): debug_use_compact_bvh: BoolProperty( name="Use Compact BVH", description="Use compact BVH structure (uses less ram but renders slower)", - default=True, + default=False, ) debug_bvh_time_steps: IntProperty( name="BVH Time Steps", -- cgit v1.2.3 From d0a552b5c602479f3a4f542f3735bc94b301139e Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Tue, 12 Jul 2022 18:47:32 +0200 Subject: Fix: set dangling pointer to null The data has been moved somewhere else, the span should not keep a pointer to it. --- source/blender/blenlib/intern/generic_virtual_array.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/blender/blenlib/intern/generic_virtual_array.cc b/source/blender/blenlib/intern/generic_virtual_array.cc index 28bc66e8524..1e548d006b2 100644 --- a/source/blender/blenlib/intern/generic_virtual_array.cc +++ b/source/blender/blenlib/intern/generic_virtual_array.cc @@ -323,6 +323,7 @@ GVArraySpan::GVArraySpan(GVArraySpan &&other) else { data_ = owned_data_; } + other.owned_data_ = nullptr; other.data_ = nullptr; other.size_ = 0; } @@ -393,6 +394,7 @@ GMutableVArraySpan::GMutableVArraySpan(GMutableVArraySpan &&other) else { data_ = owned_data_; } + other.owned_data_ = nullptr; other.data_ = nullptr; other.size_ = 0; } -- cgit v1.2.3 From 8f543a73abc42843fb924fc6d849e3055e3ae011 Mon Sep 17 00:00:00 2001 From: Chris Blackbourn Date: Wed, 13 Jul 2022 11:39:06 +1200 Subject: Fix T99659: Improve UV Island calculation with hidden faces. Simplify interface, regularize implementation and some light cleanup. See also: T79304 and D15419. --- source/blender/blenkernel/BKE_mesh_mapping.h | 2 - source/blender/editors/include/ED_mesh.h | 1 - source/blender/editors/mesh/editmesh_utils.c | 84 ++++++++++++---------- source/blender/editors/sculpt_paint/sculpt_uv.c | 14 +--- .../editors/transform/transform_convert_mesh_uv.c | 4 +- source/blender/editors/uvedit/uvedit_ops.c | 2 +- source/blender/editors/uvedit/uvedit_select.c | 2 +- .../blender/editors/uvedit/uvedit_smart_stitch.c | 10 +-- 8 files changed, 54 insertions(+), 65 deletions(-) diff --git a/source/blender/blenkernel/BKE_mesh_mapping.h b/source/blender/blenkernel/BKE_mesh_mapping.h index 163acf062e0..c58bcbea242 100644 --- a/source/blender/blenkernel/BKE_mesh_mapping.h +++ b/source/blender/blenkernel/BKE_mesh_mapping.h @@ -69,8 +69,6 @@ typedef struct UvElementMap { int *islandIndices; } UvElementMap; -#define INVALID_ISLAND ((unsigned int)-1) - /* Connectivity data */ typedef struct MeshElemMap { int *indices; diff --git a/source/blender/editors/include/ED_mesh.h b/source/blender/editors/include/ED_mesh.h index 52044109702..b73b62d5a9d 100644 --- a/source/blender/editors/include/ED_mesh.h +++ b/source/blender/editors/include/ED_mesh.h @@ -137,7 +137,6 @@ void EDBM_update_extern(struct Mesh *me, bool do_tessellation, bool is_destructi */ struct UvElementMap *BM_uv_element_map_create(struct BMesh *bm, const struct Scene *scene, - bool face_selected, bool uv_selected, bool use_winding, bool do_islands); diff --git a/source/blender/editors/mesh/editmesh_utils.c b/source/blender/editors/mesh/editmesh_utils.c index 83968955583..60a666ee755 100644 --- a/source/blender/editors/mesh/editmesh_utils.c +++ b/source/blender/editors/mesh/editmesh_utils.c @@ -593,13 +593,17 @@ UvMapVert *BM_uv_vert_map_at_index(UvVertMap *vmap, uint v) return vmap->vert[v]; } +#define INVALID_ISLAND ((unsigned int)-1) + UvElementMap *BM_uv_element_map_create(BMesh *bm, const Scene *scene, - const bool face_selected, const bool uv_selected, const bool use_winding, const bool do_islands) { + /* In uv sync selection, all UVs are visible. */ + const bool face_selected = !(scene->toolsettings->uv_flag & UV_SYNC_SELECTION); + BMVert *ev; BMFace *efa; BMLoop *l; @@ -623,15 +627,21 @@ UvElementMap *BM_uv_element_map_create(BMesh *bm, /* generate UvElement array */ BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { - if (!face_selected || BM_elem_flag_test(efa, BM_ELEM_SELECT)) { - if (!uv_selected) { - totuv += efa->len; - } - else { - BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { - totuv++; - } + if (BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) { + continue; + } + + if (face_selected && !BM_elem_flag_test(efa, BM_ELEM_SELECT)) { + continue; + } + + if (!uv_selected) { + totuv += efa->len; + } + else { + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { + totuv++; } } } @@ -649,46 +659,48 @@ UvElementMap *BM_uv_element_map_create(BMesh *bm, "UvElement"); if (use_winding) { - winding = MEM_mallocN(sizeof(*winding) * totfaces, "winding"); + winding = MEM_callocN(sizeof(*winding) * totfaces, "winding"); } BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, j) { - if (use_winding) { - winding[j] = false; + if (BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) { + continue; } - if (!face_selected || BM_elem_flag_test(efa, BM_ELEM_SELECT)) { - float(*tf_uv)[2] = NULL; - - if (use_winding) { - tf_uv = (float(*)[2])BLI_buffer_reinit_data(&tf_uv_buf, vec2f, efa->len); - } + if (face_selected && !BM_elem_flag_test(efa, BM_ELEM_SELECT)) { + continue; + } - BM_ITER_ELEM_INDEX (l, &liter, efa, BM_LOOPS_OF_FACE, i) { - if (uv_selected && !uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { - continue; - } + float(*tf_uv)[2] = NULL; - buf->l = l; - buf->separate = 0; - buf->island = INVALID_ISLAND; - buf->loop_of_poly_index = i; + if (use_winding) { + tf_uv = (float(*)[2])BLI_buffer_reinit_data(&tf_uv_buf, vec2f, efa->len); + } - buf->next = element_map->vert[BM_elem_index_get(l->v)]; - element_map->vert[BM_elem_index_get(l->v)] = buf; + BM_ITER_ELEM_INDEX (l, &liter, efa, BM_LOOPS_OF_FACE, i) { + if (uv_selected && !uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { + continue; + } - if (use_winding) { - luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - copy_v2_v2(tf_uv[i], luv->uv); - } + buf->l = l; + buf->separate = 0; + buf->island = INVALID_ISLAND; + buf->loop_of_poly_index = i; - buf++; - } + buf->next = element_map->vert[BM_elem_index_get(l->v)]; + element_map->vert[BM_elem_index_get(l->v)] = buf; if (use_winding) { - winding[j] = cross_poly_v2(tf_uv, efa->len) > 0; + luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + copy_v2_v2(tf_uv[i], luv->uv); } + + buf++; + } + + if (winding) { + winding[j] = cross_poly_v2(tf_uv, efa->len) > 0; } } diff --git a/source/blender/editors/sculpt_paint/sculpt_uv.c b/source/blender/editors/sculpt_paint/sculpt_uv.c index 8e1f4f4d495..5c2dff7b252 100644 --- a/source/blender/editors/sculpt_paint/sculpt_uv.c +++ b/source/blender/editors/sculpt_paint/sculpt_uv.c @@ -500,20 +500,10 @@ static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wm if (do_island_optimization) { /* We will need island information */ - if (ts->uv_flag & UV_SYNC_SELECTION) { - data->elementMap = BM_uv_element_map_create(bm, scene, false, false, true, true); - } - else { - data->elementMap = BM_uv_element_map_create(bm, scene, true, false, true, true); - } + data->elementMap = BM_uv_element_map_create(bm, scene, false, true, true); } else { - if (ts->uv_flag & UV_SYNC_SELECTION) { - data->elementMap = BM_uv_element_map_create(bm, scene, false, false, true, false); - } - else { - data->elementMap = BM_uv_element_map_create(bm, scene, true, false, true, false); - } + data->elementMap = BM_uv_element_map_create(bm, scene, false, true, false); } if (!data->elementMap) { diff --git a/source/blender/editors/transform/transform_convert_mesh_uv.c b/source/blender/editors/transform/transform_convert_mesh_uv.c index 18868643b6d..bb550909407 100644 --- a/source/blender/editors/transform/transform_convert_mesh_uv.c +++ b/source/blender/editors/transform/transform_convert_mesh_uv.c @@ -238,7 +238,6 @@ void createTransUVs(bContext *C, TransInfo *t) { SpaceImage *sima = CTX_wm_space_image(C); Scene *scene = t->scene; - ToolSettings *ts = CTX_data_tool_settings(C); const bool is_prop_edit = (t->flag & T_PROP_EDIT) != 0; const bool is_prop_connected = (t->flag & T_PROP_CONNECTED) != 0; @@ -266,8 +265,7 @@ void createTransUVs(bContext *C, TransInfo *t) /* count */ if (is_island_center) { /* create element map with island information */ - const bool use_facesel = (ts->uv_flag & UV_SYNC_SELECTION) == 0; - elementmap = BM_uv_element_map_create(em->bm, scene, use_facesel, true, false, true); + elementmap = BM_uv_element_map_create(em->bm, scene, true, false, true); if (elementmap == NULL) { continue; } diff --git a/source/blender/editors/uvedit/uvedit_ops.c b/source/blender/editors/uvedit/uvedit_ops.c index 4844ff22b68..4e99eb3fc0f 100644 --- a/source/blender/editors/uvedit/uvedit_ops.c +++ b/source/blender/editors/uvedit/uvedit_ops.c @@ -544,7 +544,7 @@ static bool uvedit_uv_straighten(Scene *scene, BMesh *bm, eUVWeldAlign tool) return false; } - UvElementMap *element_map = BM_uv_element_map_create(bm, scene, false, true, false, true); + UvElementMap *element_map = BM_uv_element_map_create(bm, scene, true, false, true); if (element_map == NULL) { return false; } diff --git a/source/blender/editors/uvedit/uvedit_select.c b/source/blender/editors/uvedit/uvedit_select.c index b5a564fd984..d59dcb4f4ed 100644 --- a/source/blender/editors/uvedit/uvedit_select.c +++ b/source/blender/editors/uvedit/uvedit_select.c @@ -5223,7 +5223,7 @@ static void uv_isolate_selected_islands(const Scene *scene, BLI_assert((scene->toolsettings->uv_flag & UV_SYNC_SELECTION) == 0); BMFace *efa; BMIter iter, liter; - UvElementMap *elementmap = BM_uv_element_map_create(em->bm, scene, true, false, false, true); + UvElementMap *elementmap = BM_uv_element_map_create(em->bm, scene, false, false, true); if (elementmap == NULL) { return; } diff --git a/source/blender/editors/uvedit/uvedit_smart_stitch.c b/source/blender/editors/uvedit/uvedit_smart_stitch.c index 55e44607f6f..579674930a6 100644 --- a/source/blender/editors/uvedit/uvedit_smart_stitch.c +++ b/source/blender/editors/uvedit/uvedit_smart_stitch.c @@ -1902,15 +1902,7 @@ static StitchState *stitch_init(bContext *C, * for stitch this isn't useful behavior, see T86924. */ const int selectmode_orig = scene->toolsettings->selectmode; scene->toolsettings->selectmode = SCE_SELECT_VERTEX; - - /* in uv synch selection, all uv's are visible */ - if (ts->uv_flag & UV_SYNC_SELECTION) { - state->element_map = BM_uv_element_map_create(state->em->bm, scene, false, false, true, true); - } - else { - state->element_map = BM_uv_element_map_create(state->em->bm, scene, true, false, true, true); - } - + state->element_map = BM_uv_element_map_create(state->em->bm, scene, false, true, true); scene->toolsettings->selectmode = selectmode_orig; if (!state->element_map) { -- cgit v1.2.3 From 94226271556baacc5a77432fc9c62638901ce664 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 13 Jul 2022 16:18:14 +1000 Subject: Fix T99653: "Align Active Camera to Selected" fails with ortho camera There were two bugs, a regression in [0] and the object-data wasn't tagged for depsgraph updating. [0]: 19df0e3cfd5b9fed891ed81dd1123b2351605a7d --- source/blender/blenkernel/intern/camera.c | 16 +++++++--------- source/blender/editors/space_view3d/view3d_utils.c | 5 +++++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/source/blender/blenkernel/intern/camera.c b/source/blender/blenkernel/intern/camera.c index fbe03ac365c..1c4267cac6d 100644 --- a/source/blender/blenkernel/intern/camera.c +++ b/source/blender/blenkernel/intern/camera.c @@ -622,15 +622,13 @@ static void camera_frame_fit_data_init(const Scene *scene, invert_m4(camera_rotmat_transposed_inversed); /* Extract frustum planes from projection matrix. */ - planes_from_projmat( - params->winmat, - /* left right top bottom near far */ - data->plane_tx[2], - data->plane_tx[0], - data->plane_tx[3], - data->plane_tx[1], - NULL, - NULL); + planes_from_projmat(params->winmat, + data->plane_tx[2], + data->plane_tx[0], + data->plane_tx[1], + data->plane_tx[3], + NULL, + NULL); /* Rotate planes and get normals from them */ for (uint i = 0; i < CAMERA_VIEWFRAME_NUM_PLANES; i++) { diff --git a/source/blender/editors/space_view3d/view3d_utils.c b/source/blender/editors/space_view3d/view3d_utils.c index 1fabdef8da2..85b1af8e55d 100644 --- a/source/blender/editors/space_view3d/view3d_utils.c +++ b/source/blender/editors/space_view3d/view3d_utils.c @@ -1492,10 +1492,12 @@ static bool view3d_camera_to_view_selected_impl(struct Main *bmain, depsgraph, scene, camera_ob_eval, co, &scale, r_clip_start, r_clip_end)) { ObjectTfmProtectedChannels obtfm; float obmat_new[4][4]; + bool is_ortho_camera = false; if ((camera_ob_eval->type == OB_CAMERA) && (((Camera *)camera_ob_eval->data)->type == CAM_ORTHO)) { ((Camera *)camera_ob->data)->ortho_scale = scale; + is_ortho_camera = true; } copy_m4_m4(obmat_new, camera_ob_eval->obmat); @@ -1508,6 +1510,9 @@ static bool view3d_camera_to_view_selected_impl(struct Main *bmain, /* notifiers */ DEG_id_tag_update_ex(bmain, &camera_ob->id, ID_RECALC_TRANSFORM); + if (is_ortho_camera) { + DEG_id_tag_update_ex(bmain, camera_ob->data, ID_RECALC_PARAMETERS); + } return true; } -- cgit v1.2.3 From b3913d755117c99af2e4b10deb4edcc846604b38 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 13 Jul 2022 16:18:16 +1000 Subject: Cleanup: use defines for camera axes for view frame fitting The values used for axes weren't in any meaningful order, use defines to improve readability. --- source/blender/blenkernel/intern/camera.c | 36 +++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/source/blender/blenkernel/intern/camera.c b/source/blender/blenkernel/intern/camera.c index 1c4267cac6d..84551f8e782 100644 --- a/source/blender/blenkernel/intern/camera.c +++ b/source/blender/blenkernel/intern/camera.c @@ -559,6 +559,11 @@ void BKE_camera_view_frame(const Scene *scene, const Camera *camera, float r_vec #define CAMERA_VIEWFRAME_NUM_PLANES 4 +#define Y_MAX 0 +#define Z_MIN 1 +#define Y_MIN 2 +#define Z_MAX 3 + typedef struct CameraViewFrameData { float plane_tx[CAMERA_VIEWFRAME_NUM_PLANES][4]; /* 4 planes normalized */ float dist_vals[CAMERA_VIEWFRAME_NUM_PLANES]; /* distance (signed) */ @@ -623,10 +628,10 @@ static void camera_frame_fit_data_init(const Scene *scene, /* Extract frustum planes from projection matrix. */ planes_from_projmat(params->winmat, - data->plane_tx[2], - data->plane_tx[0], - data->plane_tx[1], - data->plane_tx[3], + data->plane_tx[Y_MIN], + data->plane_tx[Y_MAX], + data->plane_tx[Z_MIN], + data->plane_tx[Z_MAX], NULL, NULL); @@ -670,19 +675,21 @@ static bool camera_frame_fit_calc_from_data(CameraParams *params, const float *dists = data->dist_vals; float scale_diff; - if ((dists[0] + dists[2]) > (dists[1] + dists[3])) { - scale_diff = (dists[1] + dists[3]) * + if ((dists[Y_MAX] + dists[Y_MIN]) > (dists[Z_MIN] + dists[Z_MAX])) { + scale_diff = (dists[Z_MIN] + dists[Z_MAX]) * (BLI_rctf_size_x(¶ms->viewplane) / BLI_rctf_size_y(¶ms->viewplane)); } else { - scale_diff = (dists[0] + dists[2]) * + scale_diff = (dists[Y_MAX] + dists[Y_MIN]) * (BLI_rctf_size_y(¶ms->viewplane) / BLI_rctf_size_x(¶ms->viewplane)); } *r_scale = params->ortho_scale - scale_diff; zero_v3(r_co); - madd_v3_v3fl(r_co, cam_axis_x, (dists[2] - dists[0]) * 0.5f + params->shiftx * scale_diff); - madd_v3_v3fl(r_co, cam_axis_y, (dists[1] - dists[3]) * 0.5f + params->shifty * scale_diff); + madd_v3_v3fl( + r_co, cam_axis_x, (dists[Y_MIN] - dists[Y_MAX]) * 0.5f + params->shiftx * scale_diff); + madd_v3_v3fl( + r_co, cam_axis_y, (dists[Z_MIN] - dists[Z_MAX]) * 0.5f + params->shifty * scale_diff); madd_v3_v3fl(r_co, cam_axis_z, -(data->z_range[0] - 1.0f - params->clip_start)); } else { @@ -698,8 +705,10 @@ static bool camera_frame_fit_calc_from_data(CameraParams *params, plane_from_point_normal_v3(plane_tx[i], co, data->plane_tx[i]); } - if ((!isect_plane_plane_v3(plane_tx[0], plane_tx[2], plane_isect_1, plane_isect_1_no)) || - (!isect_plane_plane_v3(plane_tx[1], plane_tx[3], plane_isect_2, plane_isect_2_no))) { + if ((!isect_plane_plane_v3( + plane_tx[Y_MAX], plane_tx[Y_MIN], plane_isect_1, plane_isect_1_no)) || + (!isect_plane_plane_v3( + plane_tx[Z_MIN], plane_tx[Z_MAX], plane_isect_2, plane_isect_2_no))) { return false; } @@ -753,6 +762,11 @@ static bool camera_frame_fit_calc_from_data(CameraParams *params, return true; } +#undef Y_MIN +#undef Y_MAX +#undef Z_MIN +#undef Z_MAX + bool BKE_camera_view_frame_fit_to_scene(Depsgraph *depsgraph, const Scene *scene, Object *camera_ob, -- cgit v1.2.3 From a0848396051bd4aa8f611013f0ed722c9d85ec63 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 13 Jul 2022 16:18:17 +1000 Subject: Cleanup: logical order of axis defines, assign variables for readability --- source/blender/blenkernel/intern/camera.c | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/source/blender/blenkernel/intern/camera.c b/source/blender/blenkernel/intern/camera.c index 84551f8e782..48a48d6e2b4 100644 --- a/source/blender/blenkernel/intern/camera.c +++ b/source/blender/blenkernel/intern/camera.c @@ -559,9 +559,9 @@ void BKE_camera_view_frame(const Scene *scene, const Camera *camera, float r_vec #define CAMERA_VIEWFRAME_NUM_PLANES 4 -#define Y_MAX 0 -#define Z_MIN 1 -#define Y_MIN 2 +#define Y_MIN 0 +#define Y_MAX 1 +#define Z_MIN 2 #define Z_MAX 3 typedef struct CameraViewFrameData { @@ -673,23 +673,21 @@ static bool camera_frame_fit_calc_from_data(CameraParams *params, const float *cam_axis_y = data->camera_rotmat[1]; const float *cam_axis_z = data->camera_rotmat[2]; const float *dists = data->dist_vals; - float scale_diff; + const float dist_span_y = dists[Y_MIN] + dists[Y_MAX]; + const float dist_span_z = dists[Z_MIN] + dists[Z_MAX]; + const float dist_mid_y = (dists[Y_MIN] - dists[Y_MAX]) * 0.5f; + const float dist_mid_z = (dists[Z_MIN] - dists[Z_MAX]) * 0.5f; + const float scale_diff = (dist_span_z < dist_span_y) ? + (dist_span_z * (BLI_rctf_size_x(¶ms->viewplane) / + BLI_rctf_size_y(¶ms->viewplane))) : + (dist_span_y * (BLI_rctf_size_y(¶ms->viewplane) / + BLI_rctf_size_x(¶ms->viewplane))); - if ((dists[Y_MAX] + dists[Y_MIN]) > (dists[Z_MIN] + dists[Z_MAX])) { - scale_diff = (dists[Z_MIN] + dists[Z_MAX]) * - (BLI_rctf_size_x(¶ms->viewplane) / BLI_rctf_size_y(¶ms->viewplane)); - } - else { - scale_diff = (dists[Y_MAX] + dists[Y_MIN]) * - (BLI_rctf_size_y(¶ms->viewplane) / BLI_rctf_size_x(¶ms->viewplane)); - } *r_scale = params->ortho_scale - scale_diff; zero_v3(r_co); - madd_v3_v3fl( - r_co, cam_axis_x, (dists[Y_MIN] - dists[Y_MAX]) * 0.5f + params->shiftx * scale_diff); - madd_v3_v3fl( - r_co, cam_axis_y, (dists[Z_MIN] - dists[Z_MAX]) * 0.5f + params->shifty * scale_diff); + madd_v3_v3fl(r_co, cam_axis_x, dist_mid_y + (params->shiftx * scale_diff)); + madd_v3_v3fl(r_co, cam_axis_y, dist_mid_z + (params->shifty * scale_diff)); madd_v3_v3fl(r_co, cam_axis_z, -(data->z_range[0] - 1.0f - params->clip_start)); } else { -- cgit v1.2.3 From 74888cdbfd523d70ea0573e4452bf65c25b3a888 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 13 Jul 2022 11:15:19 +0200 Subject: Fix (studio-reported) issue in remapping code. Not clearing runtime remapping data for the new ID as well as the old one can lead to false stale data there, wichi could e.g. make indirectly linked data be tagged as directly linked. This would generate an error report on file write when hapening on ShapeKey ID, since that type is not allowed to be directly linked. --- source/blender/blenkernel/intern/lib_remap.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/blender/blenkernel/intern/lib_remap.c b/source/blender/blenkernel/intern/lib_remap.c index 246999a1179..28b0337d9a2 100644 --- a/source/blender/blenkernel/intern/lib_remap.c +++ b/source/blender/blenkernel/intern/lib_remap.c @@ -428,10 +428,13 @@ static void libblock_remap_data_update_tags(ID *old_id, ID *new_id, void *user_d } static void libblock_remap_reset_remapping_status_callback(ID *old_id, - ID *UNUSED(new_id), + ID *new_id, void *UNUSED(user_data)) { BKE_libblock_runtime_reset_remapping_status(old_id); + if (new_id != NULL) { + BKE_libblock_runtime_reset_remapping_status(new_id); + } } /** -- cgit v1.2.3 From c8be3d3b27d49a86772b1bc970fb5f5f79b891b4 Mon Sep 17 00:00:00 2001 From: Antonio Vazquez Date: Wed, 13 Jul 2022 12:56:48 +0200 Subject: Fix T99654: Applying Mirror modifier breaks the erase tool The problem was the new generated strokes were copied from original and the location was offset to mirror, but the internal geometry data was not updated and the collision check done by brushes was not working. Now, the internal geometry data is recalculated when the modifier is applied. --- source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c index 1d62e930caa..1a8d1e75746 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c @@ -23,6 +23,7 @@ #include "BKE_context.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_gpencil_modifier.h" #include "BKE_lib_query.h" #include "BKE_main.h" @@ -100,9 +101,11 @@ static void update_position(Object *ob, MirrorGpencilModifierData *mmd, bGPDstro } } -static void generate_geometry(GpencilModifierData *md, Object *ob, bGPDlayer *gpl, bGPDframe *gpf) +static void generate_geometry( + GpencilModifierData *md, Object *ob, bGPDlayer *gpl, bGPDframe *gpf, const bool update) { MirrorGpencilModifierData *mmd = (MirrorGpencilModifierData *)md; + bGPdata *gpd = ob->data; bGPDstroke *gps, *gps_new = NULL; int tot_strokes; int i; @@ -129,6 +132,9 @@ static void generate_geometry(GpencilModifierData *md, Object *ob, bGPDlayer *gp mmd->flag & GP_MIRROR_INVERT_MATERIAL)) { gps_new = BKE_gpencil_stroke_duplicate(gps, true, true); update_position(ob, mmd, gps_new, xi); + if (update) { + BKE_gpencil_stroke_geometry_update(gpd, gps_new); + } BLI_addtail(&gpf->strokes, gps_new); } } @@ -147,7 +153,7 @@ static void generateStrokes(GpencilModifierData *md, Depsgraph *depsgraph, Objec if (gpf == NULL) { continue; } - generate_geometry(md, ob, gpl, gpf); + generate_geometry(md, ob, gpl, gpf, false); } } @@ -167,7 +173,7 @@ static void bakeModifier(Main *UNUSED(bmain), BKE_scene_graph_update_for_newframe(depsgraph); /* compute mirror effects on this frame */ - generate_geometry(md, ob, gpl, gpf); + generate_geometry(md, ob, gpl, gpf, true); } } -- cgit v1.2.3 From c484599687ba1757e3092f7fe2ffd0db56e9cbb6 Mon Sep 17 00:00:00 2001 From: jon denning Date: Wed, 13 Jul 2022 07:07:43 -0400 Subject: Expose snap options in transform operators This commit exposes snap options in transform operators. These options are needed for Python tools to control snapping without requiring the tool settings to be adjusted. The newly exposed options are: - `snap_elements` for choosing which element(s) of target the source geometry should snap to (ex: Face Raycast). - `use_snap_self`, `use_snap_edit`, `use_snap_nonedit`, `use_snap_selectable_only` for controlling target selection. - `use_snap_project` for controlling Face Raycast snapping. - `use_snap_to_same_target` and `snap_face_nearest_steps` for controlling Face Nearest snapping. Reviewed by: Campbell Barton (campbellbarton) Differential Revision: https://developer.blender.org/D15398 --- source/blender/editors/transform/transform_ops.c | 60 +++++++++++++++++++++--- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/source/blender/editors/transform/transform_ops.c b/source/blender/editors/transform/transform_ops.c index a64eff8f981..7c94241f3e3 100644 --- a/source/blender/editors/transform/transform_ops.c +++ b/source/blender/editors/transform/transform_ops.c @@ -566,6 +566,17 @@ static bool transform_poll_property(const bContext *UNUSED(C), } } + /* Snapping. */ + { + PropertyRNA *prop_snap = RNA_struct_find_property(op->ptr, "snap"); + if (prop_snap && (prop_snap != prop) && + (RNA_property_boolean_get(op->ptr, prop_snap) == false)) { + if (STRPREFIX(prop_id, "snap") || STRPREFIX(prop_id, "use_snap")) { + return false; + } + } + } + return true; } @@ -644,28 +655,63 @@ void Transform_Properties(struct wmOperatorType *ot, int flags) } if (flags & P_SNAP) { - prop = RNA_def_boolean(ot->srna, "snap", 0, "Use Snapping Options", ""); + prop = RNA_def_boolean(ot->srna, "snap", false, "Use Snapping Options", ""); RNA_def_property_flag(prop, PROP_HIDDEN); + prop = RNA_def_enum(ot->srna, + "snap_elements", + rna_enum_snap_element_items, + SCE_SNAP_MODE_INCREMENT, + "Snap to Elements", + ""); + RNA_def_property_flag(prop, PROP_ENUM_FLAG); + + RNA_def_boolean(ot->srna, "use_snap_project", false, "Project Individual Elements", ""); + if (flags & P_GEO_SNAP) { - /* TODO(@gfxcoder): Rename `snap_target` to `snap_source` to avoid - * previous ambiguity of "target" (now, "source" is geometry to be moved and "target" is - * geometry to which moved geometry is snapped). Use "Source snap point" and "Point on - * source that will snap to target" for name and description, respectively. */ - prop = RNA_def_enum(ot->srna, "snap_target", rna_enum_snap_source_items, 0, "Target", ""); + /* TODO(@gfxcoder): Rename `snap_target` to `snap_source` to avoid previous ambiguity of + * "target" (now, "source" is geometry to be moved and "target" is geometry to which moved + * geometry is snapped). Use "Source snap point" and "Point on source that will snap to + * target" for name and description, respectively. */ + prop = RNA_def_enum(ot->srna, "snap_target", rna_enum_snap_source_items, 0, "Snap With", ""); + RNA_def_property_flag(prop, PROP_HIDDEN); + + /* Target selection. */ + prop = RNA_def_boolean(ot->srna, "use_snap_self", true, "Target: Include Active", ""); RNA_def_property_flag(prop, PROP_HIDDEN); + prop = RNA_def_boolean(ot->srna, "use_snap_edit", true, "Target: Include Edit", ""); + RNA_def_property_flag(prop, PROP_HIDDEN); + prop = RNA_def_boolean(ot->srna, "use_snap_nonedit", true, "Target: Include Non-Edited", ""); + RNA_def_property_flag(prop, PROP_HIDDEN); + prop = RNA_def_boolean( + ot->srna, "use_snap_selectable_only", false, "Target: Exclude Non-Selectable", ""); + RNA_def_property_flag(prop, PROP_HIDDEN); + + /* Face Nearest options */ + prop = RNA_def_boolean( + ot->srna, "use_snap_to_same_target", false, "Snap to Same Target", ""); + RNA_def_property_flag(prop, PROP_HIDDEN); + prop = RNA_def_int( + ot->srna, "snap_face_nearest_steps", 1, 1, 32767, "Face Nearest Steps", "", 1, 32767); + RNA_def_property_flag(prop, PROP_HIDDEN); + prop = RNA_def_float_vector( ot->srna, "snap_point", 3, NULL, -FLT_MAX, FLT_MAX, "Point", "", -FLT_MAX, FLT_MAX); RNA_def_property_flag(prop, PROP_HIDDEN); if (flags & P_ALIGN_SNAP) { - prop = RNA_def_boolean(ot->srna, "snap_align", 0, "Align with Point Normal", ""); + prop = RNA_def_boolean(ot->srna, "snap_align", false, "Align with Point Normal", ""); RNA_def_property_flag(prop, PROP_HIDDEN); prop = RNA_def_float_vector( ot->srna, "snap_normal", 3, NULL, -FLT_MAX, FLT_MAX, "Normal", "", -FLT_MAX, FLT_MAX); RNA_def_property_flag(prop, PROP_HIDDEN); } } + else { + prop = RNA_def_boolean( + ot->srna, "use_snap_selectable_only", false, "Target: Exclude Non-Selectable", ""); + RNA_def_property_flag(prop, PROP_HIDDEN); + } } if (flags & P_GPENCIL_EDIT) { -- cgit v1.2.3 From 441dd08dba34d22e8a34d54b252e8cdd38f2c56c Mon Sep 17 00:00:00 2001 From: jon denning Date: Wed, 13 Jul 2022 08:30:40 -0400 Subject: Expose option for fallback tools keymap in GizmoGroup type This patch allows new GizmoGroup classes to support tool fallback keymap. With this patch, when new gizmo groups add `'TOOL_FALLBACK_KEYMAP'` to its `bl_options`, the fallback tools are added to the group. This allows a `WorkSpaceTool` (for example) to have selection be a fallback tool if the user LeftMouse drags away from other gizmos in the group. Reviewed by: Campbell Barton (campbellbarton) Differential Revision: https://developer.blender.org/D15154 --- source/blender/makesrna/intern/rna_wm_gizmo.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/blender/makesrna/intern/rna_wm_gizmo.c b/source/blender/makesrna/intern/rna_wm_gizmo.c index 6c3d96726bb..0e307f5b424 100644 --- a/source/blender/makesrna/intern/rna_wm_gizmo.c +++ b/source/blender/makesrna/intern/rna_wm_gizmo.c @@ -1398,6 +1398,11 @@ static void rna_def_gizmogroup(BlenderRNA *brna) 0, "Tool Init", "Postpone running until tool operator run (when used with a tool)"}, + {WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP, + "TOOL_FALLBACK_KEYMAP", + 0, + "Use fallback tools keymap", + "Add fallback tools keymap to this gizmo type."}, {WM_GIZMOGROUPTYPE_VR_REDRAWS, "VR_REDRAWS", 0, -- cgit v1.2.3 From ccdf189d3c5c3b90dbb95b704f684f1cd1bd192c Mon Sep 17 00:00:00 2001 From: jon denning Date: Wed, 13 Jul 2022 08:43:57 -0400 Subject: Documentation: Update Docs for Gizmo This patch updates the documentation for arguments regarding the `Gizmo` type. - Corrected `select_id` doc for draw_preset_ functions. `-1` indicates that no selection ID is to be written, but previous docs incorrectly specified `0` instead. - Added missing doc for `target` argument for `target_set_handler` function. Reviewed by: Aaron Carlisle (Blendify) Differential Revision: https://developer.blender.org/D14834 --- source/blender/makesrna/intern/rna_wm_gizmo_api.c | 40 ++++++++++++++++++++--- source/blender/python/intern/bpy_rna_gizmo.c | 2 ++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/source/blender/makesrna/intern/rna_wm_gizmo_api.c b/source/blender/makesrna/intern/rna_wm_gizmo_api.c index 419dfa68305..760121d2279 100644 --- a/source/blender/makesrna/intern/rna_wm_gizmo_api.c +++ b/source/blender/makesrna/intern/rna_wm_gizmo_api.c @@ -211,7 +211,15 @@ void RNA_api_gizmo(StructRNA *srna) RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); RNA_def_property_multi_array(parm, 2, rna_matrix_dimsize_4x4); RNA_def_property_ui_text(parm, "", "The matrix to transform"); - RNA_def_int(func, "select_id", -1, -1, INT_MAX, "Zero when not selecting", "", -1, INT_MAX); + RNA_def_int(func, + "select_id", + -1, + -1, + INT_MAX, + "ID to use when gizmo is selectable. Use -1 when not selecting", + "", + -1, + INT_MAX); /* draw_preset_box */ func = RNA_def_function(srna, "draw_preset_arrow", "rna_gizmo_draw_preset_arrow"); @@ -221,7 +229,15 @@ void RNA_api_gizmo(StructRNA *srna) RNA_def_property_multi_array(parm, 2, rna_matrix_dimsize_4x4); RNA_def_property_ui_text(parm, "", "The matrix to transform"); RNA_def_enum(func, "axis", rna_enum_object_axis_items, 2, "", "Arrow Orientation"); - RNA_def_int(func, "select_id", -1, -1, INT_MAX, "Zero when not selecting", "", -1, INT_MAX); + RNA_def_int(func, + "select_id", + -1, + -1, + INT_MAX, + "ID to use when gizmo is selectable. Use -1 when not selecting", + "", + -1, + INT_MAX); func = RNA_def_function(srna, "draw_preset_circle", "rna_gizmo_draw_preset_circle"); RNA_def_function_ui_description(func, "Draw a box"); @@ -230,7 +246,15 @@ void RNA_api_gizmo(StructRNA *srna) RNA_def_property_multi_array(parm, 2, rna_matrix_dimsize_4x4); RNA_def_property_ui_text(parm, "", "The matrix to transform"); RNA_def_enum(func, "axis", rna_enum_object_axis_items, 2, "", "Arrow Orientation"); - RNA_def_int(func, "select_id", -1, -1, INT_MAX, "Zero when not selecting", "", -1, INT_MAX); + RNA_def_int(func, + "select_id", + -1, + -1, + INT_MAX, + "ID to use when gizmo is selectable. Use -1 when not selecting", + "", + -1, + INT_MAX); /* -------------------------------------------------------------------- */ /* Other Shapes */ @@ -243,7 +267,15 @@ void RNA_api_gizmo(StructRNA *srna) RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); parm = RNA_def_int(func, "face_map", 0, 0, INT_MAX, "Face map index", "", 0, INT_MAX); RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); - RNA_def_int(func, "select_id", -1, -1, INT_MAX, "Zero when not selecting", "", -1, INT_MAX); + RNA_def_int(func, + "select_id", + -1, + -1, + INT_MAX, + "ID to use when gizmo is selectable. Use -1 when not selecting", + "", + -1, + INT_MAX); /* -------------------------------------------------------------------- */ /* Property API */ diff --git a/source/blender/python/intern/bpy_rna_gizmo.c b/source/blender/python/intern/bpy_rna_gizmo.c index 61f439e5152..32ef7865e84 100644 --- a/source/blender/python/intern/bpy_rna_gizmo.c +++ b/source/blender/python/intern/bpy_rna_gizmo.c @@ -314,6 +314,8 @@ PyDoc_STRVAR( "\n" " Assigns callbacks to a gizmos property.\n" "\n" + " :arg target: Target property name.\n" + " :type target: string\n" " :arg get: Function that returns the value for this property (single value or sequence).\n" " :type get: callable\n" " :arg set: Function that takes a single value argument and applies it.\n" -- cgit v1.2.3 From 88fbf0a8fc1c4192279ebd4c31b66acf05117aa6 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 13 Jul 2022 16:10:03 +0200 Subject: Fix (studio-reported) bad remapping of libraries. New remapper code would also fail in some cases when remapping libraries, similar to the issue yesterday, because ID_LI type had no mask value. That would fail to remap `parent` member of a library to NULL when deleting that parent, leading to a crash e.g. in Outliner tree building code. Reported by @JulienKaspar from Blender studio. --- source/blender/blenkernel/intern/idtype.c | 15 ++++++++++----- source/blender/blenkernel/intern/lib_query.c | 5 +++-- source/blender/blenkernel/intern/library.c | 2 +- source/blender/makesdna/DNA_ID.h | 3 ++- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/source/blender/blenkernel/intern/idtype.c b/source/blender/blenkernel/intern/idtype.c index edb6fe5d69b..923582dff4c 100644 --- a/source/blender/blenkernel/intern/idtype.c +++ b/source/blender/blenkernel/intern/idtype.c @@ -227,7 +227,8 @@ uint64_t BKE_idtype_idcode_to_idfilter(const short idcode) CASE_IDFILTER_NONE(IP); CASE_IDFILTER(KE); CASE_IDFILTER(LA); - CASE_IDFILTER_NONE(LI); + CASE_IDFILTER(LI); + CASE_IDFILTER(LP); CASE_IDFILTER(LS); CASE_IDFILTER(LT); CASE_IDFILTER(MA); @@ -241,12 +242,11 @@ uint64_t BKE_idtype_idcode_to_idfilter(const short idcode) CASE_IDFILTER(PAL); CASE_IDFILTER(PC); CASE_IDFILTER(PT); - CASE_IDFILTER(LP); CASE_IDFILTER(SCE); CASE_IDFILTER(SCR); CASE_IDFILTER(SIM); - CASE_IDFILTER(SPK); CASE_IDFILTER(SO); + CASE_IDFILTER(SPK); CASE_IDFILTER(TE); CASE_IDFILTER(TXT); CASE_IDFILTER(VF); @@ -269,6 +269,8 @@ short BKE_idtype_idcode_from_idfilter(const uint64_t idfilter) case FILTER_ID_##_id: \ return ID_##_id +#define CASE_IDFILTER_NONE(_id) (void)0 + switch (idfilter) { CASE_IDFILTER(AC); CASE_IDFILTER(AR); @@ -280,8 +282,11 @@ short BKE_idtype_idcode_from_idfilter(const uint64_t idfilter) CASE_IDFILTER(GR); CASE_IDFILTER(CV); CASE_IDFILTER(IM); + CASE_IDFILTER_NONE(IP); CASE_IDFILTER(KE); CASE_IDFILTER(LA); + CASE_IDFILTER(LI); + CASE_IDFILTER(LP); CASE_IDFILTER(LS); CASE_IDFILTER(LT); CASE_IDFILTER(MA); @@ -295,12 +300,11 @@ short BKE_idtype_idcode_from_idfilter(const uint64_t idfilter) CASE_IDFILTER(PAL); CASE_IDFILTER(PC); CASE_IDFILTER(PT); - CASE_IDFILTER(LP); CASE_IDFILTER(SCE); CASE_IDFILTER(SCR); CASE_IDFILTER(SIM); - CASE_IDFILTER(SPK); CASE_IDFILTER(SO); + CASE_IDFILTER(SPK); CASE_IDFILTER(TE); CASE_IDFILTER(TXT); CASE_IDFILTER(VF); @@ -314,6 +318,7 @@ short BKE_idtype_idcode_from_idfilter(const uint64_t idfilter) return 0; #undef CASE_IDFILTER +#undef CASE_IDFILTER_NONE } int BKE_idtype_idcode_to_index(const short idcode) diff --git a/source/blender/blenkernel/intern/lib_query.c b/source/blender/blenkernel/intern/lib_query.c index 38252a46b93..a869bf4c4b0 100644 --- a/source/blender/blenkernel/intern/lib_query.c +++ b/source/blender/blenkernel/intern/lib_query.c @@ -391,8 +391,7 @@ uint64_t BKE_library_id_can_use_filter_id(const ID *id_owner) switch ((ID_Type)id_type_owner) { case ID_LI: - /* ID_LI doesn't exist as filter_id. */ - return 0; + return FILTER_ID_LI; case ID_SCE: return FILTER_ID_OB | FILTER_ID_WO | FILTER_ID_SCE | FILTER_ID_MC | FILTER_ID_MA | FILTER_ID_GR | FILTER_ID_TXT | FILTER_ID_LS | FILTER_ID_MSK | FILTER_ID_SO | @@ -472,6 +471,8 @@ uint64_t BKE_library_id_can_use_filter_id(const ID *id_owner) /* Deprecated... */ return 0; } + + BLI_assert_unreachable(); return 0; } diff --git a/source/blender/blenkernel/intern/library.c b/source/blender/blenkernel/intern/library.c index 03a17b2ecc5..4962b1c448e 100644 --- a/source/blender/blenkernel/intern/library.c +++ b/source/blender/blenkernel/intern/library.c @@ -63,7 +63,7 @@ static void library_foreach_path(ID *id, BPathForeachPathData *bpath_data) IDTypeInfo IDType_ID_LI = { .id_code = ID_LI, - .id_filter = 0, + .id_filter = FILTER_ID_LI, .main_listbase_index = INDEX_ID_LI, .struct_size = sizeof(Library), .name = "Library", diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index f6032b71155..d3fc279381f 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -926,6 +926,7 @@ typedef enum IDRecalcFlag { #define FILTER_ID_KE (1ULL << 36) #define FILTER_ID_SCR (1ULL << 37) #define FILTER_ID_WM (1ULL << 38) +#define FILTER_ID_LI (1ULL << 39) #define FILTER_ID_ALL \ (FILTER_ID_AC | FILTER_ID_AR | FILTER_ID_BR | FILTER_ID_CA | FILTER_ID_CU_LEGACY | \ @@ -934,7 +935,7 @@ typedef enum IDRecalcFlag { FILTER_ID_OB | FILTER_ID_PA | FILTER_ID_PAL | FILTER_ID_PC | FILTER_ID_SCE | FILTER_ID_SPK | \ FILTER_ID_SO | FILTER_ID_TE | FILTER_ID_TXT | FILTER_ID_VF | FILTER_ID_WO | FILTER_ID_CF | \ FILTER_ID_WS | FILTER_ID_LP | FILTER_ID_CV | FILTER_ID_PT | FILTER_ID_VO | FILTER_ID_SIM | \ - FILTER_ID_KE | FILTER_ID_SCR | FILTER_ID_WM) + FILTER_ID_KE | FILTER_ID_SCR | FILTER_ID_WM | FILTER_ID_LI) /** * This enum defines the index assigned to each type of IDs in the array returned by -- cgit v1.2.3 From 144d9f2b2e80cf543e228ba5b0c0279aeca2c574 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 13 Jul 2022 17:39:19 +0200 Subject: Cleanup: Do not use spaces in default data names. Using white spaces in data names should not be encouraged in general, better not give wrong example here. Originally part of D15441. --- source/blender/editors/object/object_add.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc index 66e76addd6f..671a32ce438 100644 --- a/source/blender/editors/object/object_add.cc +++ b/source/blender/editors/object/object_add.cc @@ -1349,7 +1349,7 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) case GP_LRT_OBJECT: case GP_LRT_SCENE: case GP_LRT_COLLECTION: { - ob_name = "Line Art"; + ob_name = "LineArt"; break; } default: { -- cgit v1.2.3 From 50d832634eaa1b309306c4596f4213244213e104 Mon Sep 17 00:00:00 2001 From: Aaron Carlisle Date: Wed, 13 Jul 2022 16:25:57 -0400 Subject: Docs: Fix out of order parameters Fixes T99672 --- source/blender/python/gpu/gpu_py_vertex_buffer.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/blender/python/gpu/gpu_py_vertex_buffer.c b/source/blender/python/gpu/gpu_py_vertex_buffer.c index a295bedeae2..ac050128a1d 100644 --- a/source/blender/python/gpu/gpu_py_vertex_buffer.c +++ b/source/blender/python/gpu/gpu_py_vertex_buffer.c @@ -323,14 +323,14 @@ static void pygpu_vertbuf__tp_dealloc(BPyGPUVertBuf *self) } PyDoc_STRVAR(pygpu_vertbuf__tp_doc, - ".. class:: GPUVertBuf(len, format)\n" + ".. class:: GPUVertBuf(format, len)\n" "\n" " Contains a VBO.\n" "\n" - " :param len: Amount of vertices that will fit into this buffer.\n" - " :type type: `int`\n" " :param format: Vertex format.\n" - " :type buf: :class:`gpu.types.GPUVertFormat`\n"); + " :type buf: :class:`gpu.types.GPUVertFormat`\n" + " :param len: Amount of vertices that will fit into this buffer.\n" + " :type type: `int`\n"); PyTypeObject BPyGPUVertBuf_Type = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "GPUVertBuf", .tp_basicsize = sizeof(BPyGPUVertBuf), -- cgit v1.2.3 From 09a74ff8b6e815f3642229355be0a08dc1bcb391 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 14 Jul 2022 09:46:14 +1000 Subject: GHOST/SDL: add support for the key repeat flag Now all ghost back-ends support the key repeat flag (accessed as WM_EVENT_IS_REPEAT from wmEvent.flag). --- intern/ghost/intern/GHOST_SystemSDL.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/intern/ghost/intern/GHOST_SystemSDL.cpp b/intern/ghost/intern/GHOST_SystemSDL.cpp index a29bfd9489d..0ca37adced6 100644 --- a/intern/ghost/intern/GHOST_SystemSDL.cpp +++ b/intern/ghost/intern/GHOST_SystemSDL.cpp @@ -457,6 +457,7 @@ void GHOST_SystemSDL::processEvent(SDL_Event *sdl_event) SDL_Keycode sym = sdl_sub_evt.keysym.sym; GHOST_TEventType type = (sdl_sub_evt.state == SDL_PRESSED) ? GHOST_kEventKeyDown : GHOST_kEventKeyUp; + const bool is_repeat = sdl_sub_evt.repeat != 0; GHOST_WindowSDL *window = findGhostWindow( SDL_GetWindowFromID_fallback(sdl_sub_evt.windowID)); @@ -596,7 +597,7 @@ void GHOST_SystemSDL::processEvent(SDL_Event *sdl_event) } } - g_event = new GHOST_EventKey(getMilliSeconds(), type, window, gkey, sym, nullptr, false); + g_event = new GHOST_EventKey(getMilliSeconds(), type, window, gkey, sym, nullptr, is_repeat); break; } } -- cgit v1.2.3 From 931779197a9ce141eccc8b8c500f9ef726a833eb Mon Sep 17 00:00:00 2001 From: Chris Blackbourn Date: Thu, 14 Jul 2022 14:40:07 +1200 Subject: Fix T99684: Upgrade Averages Island Scale with options Scale UV and Shear Differential Revision: https://developer.blender.org/D15421 --- source/blender/blenlib/BLI_math_matrix.h | 1 + source/blender/blenlib/intern/math_matrix.c | 16 ++++ source/blender/editors/uvedit/uvedit_unwrap_ops.c | 14 ++- source/blender/geometry/GEO_uv_parametrizer.h | 5 +- source/blender/geometry/intern/uv_parametrizer.c | 103 +++++++++++++++++++--- 5 files changed, 123 insertions(+), 16 deletions(-) diff --git a/source/blender/blenlib/BLI_math_matrix.h b/source/blender/blenlib/BLI_math_matrix.h index 2cd2a299d53..c2dafbe3a1a 100644 --- a/source/blender/blenlib/BLI_math_matrix.h +++ b/source/blender/blenlib/BLI_math_matrix.h @@ -238,6 +238,7 @@ bool invert_m3_ex(float m[3][3], float epsilon); bool invert_m3_m3_ex(float m1[3][3], const float m2[3][3], float epsilon); bool invert_m3(float R[3][3]); +bool invert_m2_m2(float R[2][2], const float A[2][2]); bool invert_m3_m3(float R[3][3], const float A[3][3]); bool invert_m4(float R[4][4]); bool invert_m4_m4(float R[4][4], const float A[4][4]); diff --git a/source/blender/blenlib/intern/math_matrix.c b/source/blender/blenlib/intern/math_matrix.c index ce9abc36cad..fcd017b3082 100644 --- a/source/blender/blenlib/intern/math_matrix.c +++ b/source/blender/blenlib/intern/math_matrix.c @@ -1116,6 +1116,22 @@ double determinant_m3_array_db(const double m[3][3]) m[2][0] * (m[0][1] * m[1][2] - m[0][2] * m[1][1])); } +bool invert_m2_m2(float m1[2][2], const float m2[2][2]) +{ + adjoint_m2_m2(m1, m2); + float det = determinant_m2(m2[0][0], m2[1][0], m2[0][1], m2[1][1]); + + bool success = (det != 0.0f); + if (success) { + m1[0][0] /= det; + m1[1][0] /= det; + m1[0][1] /= det; + m1[1][1] /= det; + } + + return success; +} + bool invert_m3_ex(float m[3][3], const float epsilon) { float tmp[3][3]; diff --git a/source/blender/editors/uvedit/uvedit_unwrap_ops.c b/source/blender/editors/uvedit/uvedit_unwrap_ops.c index ae81aaffeb2..7ec80a05f6d 100644 --- a/source/blender/editors/uvedit/uvedit_unwrap_ops.c +++ b/source/blender/editors/uvedit/uvedit_unwrap_ops.c @@ -1191,7 +1191,7 @@ void UV_OT_pack_islands(wmOperatorType *ot) /** \name Average UV Islands Scale Operator * \{ */ -static int average_islands_scale_exec(bContext *C, wmOperator *UNUSED(op)) +static int average_islands_scale_exec(bContext *C, wmOperator *op) { const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); @@ -1215,8 +1215,12 @@ static int average_islands_scale_exec(bContext *C, wmOperator *UNUSED(op)) return OPERATOR_CANCELLED; } + /* RNA props */ + const bool scale_uv = RNA_boolean_get(op->ptr, "scale_uv"); + const bool shear = RNA_boolean_get(op->ptr, "shear"); + ParamHandle *handle = construct_param_handle_multi(scene, objects, objects_len, &options); - GEO_uv_parametrizer_average(handle, false); + GEO_uv_parametrizer_average(handle, false, scale_uv, shear); GEO_uv_parametrizer_flush(handle); GEO_uv_parametrizer_delete(handle); @@ -1247,6 +1251,10 @@ void UV_OT_average_islands_scale(wmOperatorType *ot) /* api callbacks */ ot->exec = average_islands_scale_exec; ot->poll = ED_operator_uvedit; + + /* properties */ + RNA_def_boolean(ot->srna, "scale_uv", false, "Non-Uniform", "Scale U and V independently"); + RNA_def_boolean(ot->srna, "shear", false, "Shear", "Reduce shear within islands"); } /** \} */ @@ -1845,7 +1853,7 @@ static void uvedit_unwrap(const Scene *scene, result_info ? &result_info->count_failed : NULL); GEO_uv_parametrizer_lscm_end(handle); - GEO_uv_parametrizer_average(handle, true); + GEO_uv_parametrizer_average(handle, true, false, false); GEO_uv_parametrizer_flush(handle); diff --git a/source/blender/geometry/GEO_uv_parametrizer.h b/source/blender/geometry/GEO_uv_parametrizer.h index 2181f95945e..5285aefbd4c 100644 --- a/source/blender/geometry/GEO_uv_parametrizer.h +++ b/source/blender/geometry/GEO_uv_parametrizer.h @@ -103,7 +103,10 @@ void GEO_uv_parametrizer_pack(ParamHandle *handle, /** \name Average area for all charts * \{ */ -void GEO_uv_parametrizer_average(ParamHandle *handle, bool ignore_pinned); +void GEO_uv_parametrizer_average(ParamHandle *handle, + bool ignore_pinned, + bool scale_uv, + bool shear); /** \} */ diff --git a/source/blender/geometry/intern/uv_parametrizer.c b/source/blender/geometry/intern/uv_parametrizer.c index c75a302f8dc..38eb50722cf 100644 --- a/source/blender/geometry/intern/uv_parametrizer.c +++ b/source/blender/geometry/intern/uv_parametrizer.c @@ -148,7 +148,8 @@ typedef struct PChart { } lscm; struct PChartPack { float rescale, area; - float size[2] /* , trans[2] */; + float size[2]; + float origin[2]; } pack; } u; @@ -4243,7 +4244,10 @@ void GEO_uv_parametrizer_pack(ParamHandle *handle, } } -void GEO_uv_parametrizer_average(ParamHandle *phandle, bool ignore_pinned) +void GEO_uv_parametrizer_average(ParamHandle *phandle, + bool ignore_pinned, + bool scale_uv, + bool shear) { PChart *chart; int i; @@ -4263,6 +4267,83 @@ void GEO_uv_parametrizer_average(ParamHandle *phandle, bool ignore_pinned) continue; } + p_chart_uv_bbox(chart, minv, maxv); + mid_v2_v2v2(chart->u.pack.origin, minv, maxv); + + if (scale_uv || shear) { + /* It's possible that for some "bad" inputs, the following iteration will converge slowly or + * perhaps even diverge. Rather than infinite loop, we only iterate a maximum of `max_iter` + * times. (Also useful when making changes to the calculation.) */ + int max_iter = 10; + for (int j = 0; j < max_iter; j++) { + /* An island could contain millions of polygons. When summing many small values, we need to + * use double precision in the accumulator to maintain accuracy. Note that the individual + * calculations only need to be at single precision.*/ + double scale_cou = 0; + double scale_cov = 0; + double scale_cross = 0; + double weight_sum = 0; + for (PFace *f = chart->faces; f; f = f->nextlink) { + float m[2][2], s[2][2]; + PVert *va = f->edge->vert; + PVert *vb = f->edge->next->vert; + PVert *vc = f->edge->next->next->vert; + s[0][0] = va->uv[0] - vc->uv[0]; + s[0][1] = va->uv[1] - vc->uv[1]; + s[1][0] = vb->uv[0] - vc->uv[0]; + s[1][1] = vb->uv[1] - vc->uv[1]; + /* Find the "U" axis and "V" axis in triangle co-ordinates. Normally this would require + * SVD, but in 2D we can use a cheaper matrix inversion instead.*/ + if (!invert_m2_m2(m, s)) { + continue; + } + float cou[3], cov[3]; /* i.e. Texture "U" and texture "V" in 3D co-ordinates.*/ + for (int i = 0; i < 3; i++) { + cou[i] = m[0][0] * (va->co[i] - vc->co[i]) + m[0][1] * (vb->co[i] - vc->co[i]); + cov[i] = m[1][0] * (va->co[i] - vc->co[i]) + m[1][1] * (vb->co[i] - vc->co[i]); + } + const float weight = p_face_area(f); + scale_cou += len_v3(cou) * weight; + scale_cov += len_v3(cov) * weight; + if (shear) { + normalize_v3(cov); + normalize_v3(cou); + + /* Why is scale_cross called `cross` when we call `dot`? The next line calculates: + * `scale_cross += length(cross(cross(cou, face_normal), cov))` + * By construction, both `cou` and `cov` are orthogonal to the face normal. + * By definition, the normal vector has unit length. */ + scale_cross += dot_v3v3(cou, cov) * weight; + } + weight_sum += weight; + } + if (scale_cou * scale_cov < 1e-10f) { + break; + } + const float scale_factor_u = scale_uv ? sqrtf(scale_cou / scale_cov) : 1.0f; + + /* Compute correction transform. */ + float t[2][2]; + t[0][0] = scale_factor_u; + t[1][0] = clamp_f((float)(scale_cross / weight_sum), -0.5f, 0.5f); + t[0][1] = 0; + t[1][1] = 1.0f / scale_factor_u; + + /* Apply the correction. */ + p_chart_uv_transform(chart, t); + + /* How far from the identity transform are we? [[1,0],[0,1]] */ + const float err = fabsf(t[0][0] - 1.0f) + fabsf(t[1][0]) + fabsf(t[0][1]) + + fabsf(t[1][1] - 1.0f); + + const float tolerance = 1e-6f; /* Trade accuracy for performance. */ + if (err < tolerance) { + /* Too slow? Use Richardson Extrapolation to accelerate the convergence.*/ + break; + } + } + } + chart->u.pack.area = 0.0f; /* 3d area */ chart->u.pack.rescale = 0.0f; /* UV area, abusing rescale for tmp storage, oh well :/ */ @@ -4292,18 +4373,16 @@ void GEO_uv_parametrizer_average(ParamHandle *phandle, bool ignore_pinned) if (chart->u.pack.area != 0.0f && chart->u.pack.rescale != 0.0f) { fac = chart->u.pack.area / chart->u.pack.rescale; - /* Get the island center */ - p_chart_uv_bbox(chart, minv, maxv); - trans[0] = (minv[0] + maxv[0]) / -2.0f; - trans[1] = (minv[1] + maxv[1]) / -2.0f; - - /* Move center to 0,0 */ - p_chart_uv_translate(chart, trans); + /* Average scale. */ p_chart_uv_scale(chart, sqrtf(fac / tot_fac)); - /* Move to original center */ - trans[0] = -trans[0]; - trans[1] = -trans[1]; + /* Get the current island center. */ + p_chart_uv_bbox(chart, minv, maxv); + + /* Move to original center. */ + mid_v2_v2v2(trans, minv, maxv); + negate_v2(trans); + add_v2_v2(trans, chart->u.pack.origin); p_chart_uv_translate(chart, trans); } } -- cgit v1.2.3 From d3374e5337d5d50b842e274348813c15d4f57705 Mon Sep 17 00:00:00 2001 From: Chris Blackbourn Date: Thu, 14 Jul 2022 16:19:21 +1200 Subject: Fix build and warnings from previous commit. --- source/blender/geometry/intern/uv_parametrizer.c | 9 ++++----- source/blender/nodes/geometry/nodes/node_geo_uv_unwrap.cc | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/source/blender/geometry/intern/uv_parametrizer.c b/source/blender/geometry/intern/uv_parametrizer.c index 38eb50722cf..7ef17d4e9d0 100644 --- a/source/blender/geometry/intern/uv_parametrizer.c +++ b/source/blender/geometry/intern/uv_parametrizer.c @@ -4260,7 +4260,6 @@ void GEO_uv_parametrizer_average(ParamHandle *phandle, } for (i = 0; i < phandle->ncharts; i++) { - PFace *f; chart = phandle->charts[i]; if (ignore_pinned && (chart->flag & PCHART_HAS_PINS)) { @@ -4298,9 +4297,9 @@ void GEO_uv_parametrizer_average(ParamHandle *phandle, continue; } float cou[3], cov[3]; /* i.e. Texture "U" and texture "V" in 3D co-ordinates.*/ - for (int i = 0; i < 3; i++) { - cou[i] = m[0][0] * (va->co[i] - vc->co[i]) + m[0][1] * (vb->co[i] - vc->co[i]); - cov[i] = m[1][0] * (va->co[i] - vc->co[i]) + m[1][1] * (vb->co[i] - vc->co[i]); + for (int k = 0; k < 3; k++) { + cou[k] = m[0][0] * (va->co[k] - vc->co[k]) + m[0][1] * (vb->co[k] - vc->co[k]); + cov[k] = m[1][0] * (va->co[k] - vc->co[k]) + m[1][1] * (vb->co[k] - vc->co[k]); } const float weight = p_face_area(f); scale_cou += len_v3(cou) * weight; @@ -4347,7 +4346,7 @@ void GEO_uv_parametrizer_average(ParamHandle *phandle, chart->u.pack.area = 0.0f; /* 3d area */ chart->u.pack.rescale = 0.0f; /* UV area, abusing rescale for tmp storage, oh well :/ */ - for (f = chart->faces; f; f = f->nextlink) { + for (PFace *f = chart->faces; f; f = f->nextlink) { chart->u.pack.area += p_face_area(f); chart->u.pack.rescale += fabsf(p_face_uv_area_signed(f)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_uv_unwrap.cc b/source/blender/nodes/geometry/nodes/node_geo_uv_unwrap.cc index 2ec14ad2d29..03657f3e016 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_uv_unwrap.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_uv_unwrap.cc @@ -121,7 +121,7 @@ static VArray construct_uv_gvarray(const MeshComponent &component, GEO_uv_parametrizer_lscm_begin(handle, false, method == GEO_NODE_UV_UNWRAP_METHOD_ANGLE_BASED); GEO_uv_parametrizer_lscm_solve(handle, nullptr, nullptr); GEO_uv_parametrizer_lscm_end(handle); - GEO_uv_parametrizer_average(handle, true); + GEO_uv_parametrizer_average(handle, true, false, false); GEO_uv_parametrizer_pack(handle, margin, true, true); GEO_uv_parametrizer_flush(handle); GEO_uv_parametrizer_delete(handle); -- cgit v1.2.3 From 816a73891b69e2060c5b99d599e2a99e273db124 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 14 Jul 2022 13:23:35 +1000 Subject: GHOST/SDL: pass in utf8 buffer for keyboard events While GHOST/SDL doesn't support non-ASCII text input, use the UTF8 buffer to be consistent with all other back-ends. Move the conversion from SDL_KeyboardEvent to ASCII into a function. Also only lookup this value on key press (not release). --- intern/ghost/intern/GHOST_SystemSDL.cpp | 274 +++++++++++++++++--------------- 1 file changed, 143 insertions(+), 131 deletions(-) diff --git a/intern/ghost/intern/GHOST_SystemSDL.cpp b/intern/ghost/intern/GHOST_SystemSDL.cpp index 0ca37adced6..55a15ae470e 100644 --- a/intern/ghost/intern/GHOST_SystemSDL.cpp +++ b/intern/ghost/intern/GHOST_SystemSDL.cpp @@ -279,6 +279,141 @@ static GHOST_TKey convertSDLKey(SDL_Scancode key) } #undef GXMAP +static char convert_keyboard_event_to_ascii(const SDL_KeyboardEvent &sdl_sub_evt) +{ + SDL_Keycode sym = sdl_sub_evt.keysym.sym; + if (sym > 127) { + switch (sym) { + case SDLK_KP_DIVIDE: + sym = '/'; + break; + case SDLK_KP_MULTIPLY: + sym = '*'; + break; + case SDLK_KP_MINUS: + sym = '-'; + break; + case SDLK_KP_PLUS: + sym = '+'; + break; + case SDLK_KP_1: + sym = '1'; + break; + case SDLK_KP_2: + sym = '2'; + break; + case SDLK_KP_3: + sym = '3'; + break; + case SDLK_KP_4: + sym = '4'; + break; + case SDLK_KP_5: + sym = '5'; + break; + case SDLK_KP_6: + sym = '6'; + break; + case SDLK_KP_7: + sym = '7'; + break; + case SDLK_KP_8: + sym = '8'; + break; + case SDLK_KP_9: + sym = '9'; + break; + case SDLK_KP_0: + sym = '0'; + break; + case SDLK_KP_PERIOD: + sym = '.'; + break; + default: + sym = 0; + break; + } + } + else { + if (sdl_sub_evt.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT)) { + /* Weak US keyboard assumptions. */ + if (sym >= 'a' && sym <= ('a' + 32)) { + sym -= 32; + } + else { + switch (sym) { + case '`': + sym = '~'; + break; + case '1': + sym = '!'; + break; + case '2': + sym = '@'; + break; + case '3': + sym = '#'; + break; + case '4': + sym = '$'; + break; + case '5': + sym = '%'; + break; + case '6': + sym = '^'; + break; + case '7': + sym = '&'; + break; + case '8': + sym = '*'; + break; + case '9': + sym = '('; + break; + case '0': + sym = ')'; + break; + case '-': + sym = '_'; + break; + case '=': + sym = '+'; + break; + case '[': + sym = '{'; + break; + case ']': + sym = '}'; + break; + case '\\': + sym = '|'; + break; + case ';': + sym = ':'; + break; + case '\'': + sym = '"'; + break; + case ',': + sym = '<'; + break; + case '.': + sym = '>'; + break; + case '/': + sym = '?'; + break; + default: + break; + } + } + } + } + return (char)sym; +} + /** * Events don't always have valid windows, * but GHOST needs a window _always_. fallback to the GL window. @@ -454,7 +589,6 @@ void GHOST_SystemSDL::processEvent(SDL_Event *sdl_event) case SDL_KEYDOWN: case SDL_KEYUP: { SDL_KeyboardEvent &sdl_sub_evt = sdl_event->key; - SDL_Keycode sym = sdl_sub_evt.keysym.sym; GHOST_TEventType type = (sdl_sub_evt.state == SDL_PRESSED) ? GHOST_kEventKeyDown : GHOST_kEventKeyUp; const bool is_repeat = sdl_sub_evt.repeat != 0; @@ -466,138 +600,16 @@ void GHOST_SystemSDL::processEvent(SDL_Event *sdl_event) GHOST_TKey gkey = convertSDLKey(sdl_sub_evt.keysym.scancode); /* NOTE: the `sdl_sub_evt.keysym.sym` is truncated, * for unicode support ghost has to be modified. */ - // printf("%d\n", sym); - if (sym > 127) { - switch (sym) { - case SDLK_KP_DIVIDE: - sym = '/'; - break; - case SDLK_KP_MULTIPLY: - sym = '*'; - break; - case SDLK_KP_MINUS: - sym = '-'; - break; - case SDLK_KP_PLUS: - sym = '+'; - break; - case SDLK_KP_1: - sym = '1'; - break; - case SDLK_KP_2: - sym = '2'; - break; - case SDLK_KP_3: - sym = '3'; - break; - case SDLK_KP_4: - sym = '4'; - break; - case SDLK_KP_5: - sym = '5'; - break; - case SDLK_KP_6: - sym = '6'; - break; - case SDLK_KP_7: - sym = '7'; - break; - case SDLK_KP_8: - sym = '8'; - break; - case SDLK_KP_9: - sym = '9'; - break; - case SDLK_KP_0: - sym = '0'; - break; - case SDLK_KP_PERIOD: - sym = '.'; - break; - default: - sym = 0; - break; - } - } - else { - if (sdl_sub_evt.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT)) { - /* lame US keyboard assumptions */ - if (sym >= 'a' && sym <= ('a' + 32)) { - sym -= 32; - } - else { - switch (sym) { - case '`': - sym = '~'; - break; - case '1': - sym = '!'; - break; - case '2': - sym = '@'; - break; - case '3': - sym = '#'; - break; - case '4': - sym = '$'; - break; - case '5': - sym = '%'; - break; - case '6': - sym = '^'; - break; - case '7': - sym = '&'; - break; - case '8': - sym = '*'; - break; - case '9': - sym = '('; - break; - case '0': - sym = ')'; - break; - case '-': - sym = '_'; - break; - case '=': - sym = '+'; - break; - case '[': - sym = '{'; - break; - case ']': - sym = '}'; - break; - case '\\': - sym = '|'; - break; - case ';': - sym = ':'; - break; - case '\'': - sym = '"'; - break; - case ',': - sym = '<'; - break; - case '.': - sym = '>'; - break; - case '/': - sym = '?'; - break; - default: - break; - } - } - } + + /* TODO(@campbellbarton): support full unicode, SDL supports this but it needs to be + * explicitly enabled via #SDL_StartTextInput which GHOST would have to wrap. */ + char utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)] = {'\0'}; + if (type == GHOST_kEventKeyDown) { + utf8_buf[0] = convert_keyboard_event_to_ascii(sdl_sub_evt); } - g_event = new GHOST_EventKey(getMilliSeconds(), type, window, gkey, sym, nullptr, is_repeat); + g_event = new GHOST_EventKey( + getMilliSeconds(), type, window, gkey, '\0', utf8_buf, is_repeat); break; } } -- cgit v1.2.3 From d6fef73ef110eb43756b7b87c2cba80abae3b39f Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 14 Jul 2022 13:54:26 +1000 Subject: WM: Remove ASCII members from wmEvent & GHOST_TEventKeyData The `ascii` member was only kept for historic reason as some platforms didn't support utf8 when it was first introduced. Remove the `ascii` struct members since many checks used this as a fall-back for utf8_buf not being set which isn't needed. There are a few cases where it's convenient to access the ASCII value of an event (or nil) so a function has been added to do that. *Details* - WM_event_utf8_to_ascii() has been added for the few cases an events ASCII value needs to be accessed, this just avoids having to do multi-byte character checks in-line. - RNA Event.ascii remains, using utf8_buf[0] for single byte characters. - GHOST_TEventKeyData.ascii has been removed. - To avoid regressions non-ASCII Latin1 characters from GHOST are converted into multi-byte UTF8, when building X11 without XInput & X_HAVE_UTF8_STRING it seems like could still occur. --- intern/ghost/GHOST_Types.h | 11 -------- intern/ghost/intern/GHOST_EventKey.h | 13 ++++++--- intern/ghost/test/multitest/EventToBuf.c | 6 ++-- source/blender/editors/armature/pose_lib.c | 4 +-- source/blender/editors/curve/editfont.c | 2 +- .../blender/editors/interface/interface_handlers.c | 32 +++++----------------- source/blender/editors/space_console/console_ops.c | 12 ++------ source/blender/editors/space_text/text_ops.c | 17 ++++-------- source/blender/editors/util/numinput.c | 13 +++++---- source/blender/makesrna/intern/rna_wm.c | 4 +-- source/blender/makesrna/intern/rna_wm_api.c | 6 ---- source/blender/windowmanager/WM_api.h | 1 + source/blender/windowmanager/WM_types.h | 8 ++---- .../blender/windowmanager/intern/wm_event_query.c | 17 ++++++++++-- .../windowmanager/intern/wm_event_system.cc | 25 +++++++++++------ source/blender/windowmanager/intern/wm_window.c | 1 - 16 files changed, 75 insertions(+), 97 deletions(-) diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h index 2fc84349eb9..fa74bfde866 100644 --- a/intern/ghost/GHOST_Types.h +++ b/intern/ghost/GHOST_Types.h @@ -547,17 +547,6 @@ typedef struct { /** The key code. */ GHOST_TKey key; - /* ascii / utf8: both should always be set when possible, - * - ascii may be '\0' however if the user presses a non ascii key - * - unicode may not be set if the system has no unicode support - * - * These values are intended to be used as follows. - * For text input use unicode when available, fallback to ascii. - * For areas where unicode is not needed, number input for example, always - * use ascii, unicode is ignored - campbell. - */ - /** The ascii code for the key event ('\0' if none). */ - char ascii; /** The unicode character. if the length is 6, not NULL terminated if all 6 are set. */ char utf8_buf[6]; diff --git a/intern/ghost/intern/GHOST_EventKey.h b/intern/ghost/intern/GHOST_EventKey.h index 1c3156c27d2..d3cfbbeddd7 100644 --- a/intern/ghost/intern/GHOST_EventKey.h +++ b/intern/ghost/intern/GHOST_EventKey.h @@ -28,7 +28,6 @@ class GHOST_EventKey : public GHOST_Event { : GHOST_Event(msec, type, window) { m_keyEventData.key = key; - m_keyEventData.ascii = '\0'; m_keyEventData.utf8_buf[0] = '\0'; m_keyEventData.is_repeat = is_repeat; m_data = &m_keyEventData; @@ -51,11 +50,17 @@ class GHOST_EventKey : public GHOST_Event { : GHOST_Event(msec, type, window) { m_keyEventData.key = key; - m_keyEventData.ascii = ascii; - if (utf8_buf) + if (utf8_buf) { memcpy(m_keyEventData.utf8_buf, utf8_buf, sizeof(m_keyEventData.utf8_buf)); - else + } + else { m_keyEventData.utf8_buf[0] = '\0'; + } + /* TODO(@campbellbarton): phase out `ascii` and always use `utf8_buf`, this needs to be done + * on all platforms though, so for now write the ascii into the utf8_buf. */ + if (m_keyEventData.utf8_buf[0] == '\0' && ascii) { + m_keyEventData.utf8_buf[0] = ascii; + } m_keyEventData.is_repeat = is_repeat; m_data = &m_keyEventData; } diff --git a/intern/ghost/test/multitest/EventToBuf.c b/intern/ghost/test/multitest/EventToBuf.c index baab32328c3..846a867a371 100644 --- a/intern/ghost/test/multitest/EventToBuf.c +++ b/intern/ghost/test/multitest/EventToBuf.c @@ -218,8 +218,10 @@ void event_to_buf(GHOST_EventHandle evt, char buf[128]) case GHOST_kEventKeyUp: { GHOST_TEventKeyData *kd = data; pos += sprintf(pos, " - key: %s (%d)", keytype_to_string(kd->key), kd->key); - if (kd->ascii) - pos += sprintf(pos, " ascii: '%c' (%d)", kd->ascii, kd->ascii); + /* TODO: ideally this would print the unicode character. */ + if (kd->utf8_buf[0]) { + pos += sprintf(pos, " ascii: '%c' (%d)", kd->utf8_buf[0], kd->utf8_buf[0]); + } break; } } diff --git a/source/blender/editors/armature/pose_lib.c b/source/blender/editors/armature/pose_lib.c index b8e7c2624fd..a6742fbe2bf 100644 --- a/source/blender/editors/armature/pose_lib.c +++ b/source/blender/editors/armature/pose_lib.c @@ -1578,7 +1578,7 @@ static int poselib_preview_handle_event(bContext *UNUSED(C), wmOperator *op, con case EVT_PADMINUS: if (pld->searchstr[0]) { /* searching... */ - poselib_preview_handle_search(pld, event->type, event->ascii); + poselib_preview_handle_search(pld, event->type, WM_event_utf8_to_ascii(event)); } else { /* view manipulation (see above) */ @@ -1589,7 +1589,7 @@ static int poselib_preview_handle_event(bContext *UNUSED(C), wmOperator *op, con /* otherwise, assume that searching might be able to handle it */ default: - poselib_preview_handle_search(pld, event->type, event->ascii); + poselib_preview_handle_search(pld, event->type, WM_event_utf8_to_ascii(event)); break; } diff --git a/source/blender/editors/curve/editfont.c b/source/blender/editors/curve/editfont.c index 611dbb2e80c..33e3837c9d4 100644 --- a/source/blender/editors/curve/editfont.c +++ b/source/blender/editors/curve/editfont.c @@ -1639,7 +1639,7 @@ static int insert_text_invoke(bContext *C, wmOperator *op, const wmEvent *event) Curve *cu = obedit->data; EditFont *ef = cu->editfont; static int accentcode = 0; - uintptr_t ascii = event->ascii; + uintptr_t ascii = WM_event_utf8_to_ascii(event); const bool alt = event->modifier & KM_ALT; const bool shift = event->modifier & KM_SHIFT; const bool ctrl = event->modifier & KM_CTRL; diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 21fd14b86b7..2ad2cd15c43 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -3172,21 +3172,6 @@ static bool ui_textedit_insert_buf(uiBut *but, return changed; } -static bool ui_textedit_insert_ascii(uiBut *but, uiHandleButtonData *data, char ascii) -{ - const char buf[2] = {ascii, '\0'}; - - if (UI_but_is_utf8(but) && (BLI_str_utf8_size(buf) == -1)) { - printf( - "%s: entering invalid ascii char into an ascii key (%d)\n", __func__, (int)(uchar)ascii); - - return false; - } - - /* in some cases we want to allow invalid utf8 chars */ - return ui_textedit_insert_buf(but, data, buf, 1); -} - static void ui_textedit_move(uiBut *but, uiHandleButtonData *data, eStrCursorJumpDirection direction, @@ -3897,30 +3882,27 @@ static void ui_do_but_textedit( } } - if ((event->ascii || event->utf8_buf[0]) && (retval == WM_UI_HANDLER_CONTINUE) + if ((event->utf8_buf[0]) && (retval == WM_UI_HANDLER_CONTINUE) #ifdef WITH_INPUT_IME && !is_ime_composing && !WM_event_is_ime_switch(event) #endif ) { - char ascii = event->ascii; + char utf8_buf_override[2] = {'\0', '\0'}; const char *utf8_buf = event->utf8_buf; /* Exception that's useful for number buttons, some keyboard * numpads have a comma instead of a period. */ if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER)) { /* Could use `data->min`. */ - if (event->type == EVT_PADPERIOD && ascii == ',') { - ascii = '.'; - utf8_buf = NULL; /* force ascii fallback */ + if ((event->type == EVT_PADPERIOD) && (utf8_buf[0] == ',')) { + utf8_buf_override[0] = '.'; + utf8_buf = utf8_buf_override; } } - if (utf8_buf && utf8_buf[0]) { + if (utf8_buf[0]) { const int utf8_buf_len = BLI_str_utf8_size(utf8_buf); BLI_assert(utf8_buf_len != -1); - changed = ui_textedit_insert_buf(but, data, event->utf8_buf, utf8_buf_len); - } - else { - changed = ui_textedit_insert_ascii(but, data, ascii); + changed = ui_textedit_insert_buf(but, data, utf8_buf, utf8_buf_len); } retval = WM_UI_HANDLER_BREAK; diff --git a/source/blender/editors/space_console/console_ops.c b/source/blender/editors/space_console/console_ops.c index 17fbef23eac..ef22b1b9f0b 100644 --- a/source/blender/editors/space_console/console_ops.c +++ b/source/blender/editors/space_console/console_ops.c @@ -413,16 +413,8 @@ static int console_insert_invoke(bContext *C, wmOperator *op, const wmEvent *eve } char str[BLI_UTF8_MAX + 1]; - size_t len; - - if (event->utf8_buf[0]) { - len = BLI_str_utf8_size_safe(event->utf8_buf); - memcpy(str, event->utf8_buf, len); - } - else { - /* in theory, ghost can set value to extended ascii here */ - len = BLI_str_utf8_from_unicode(event->ascii, str, sizeof(str) - 1); - } + const size_t len = BLI_str_utf8_size_safe(event->utf8_buf); + memcpy(str, event->utf8_buf, len); str[len] = '\0'; RNA_string_set(op->ptr, "text", str); } diff --git a/source/blender/editors/space_text/text_ops.c b/source/blender/editors/space_text/text_ops.c index 05d51cf6362..33219092d20 100644 --- a/source/blender/editors/space_text/text_ops.c +++ b/source/blender/editors/space_text/text_ops.c @@ -3396,7 +3396,8 @@ static int text_line_number_invoke(bContext *C, wmOperator *UNUSED(op), const wm return OPERATOR_PASS_THROUGH; } - if (!(event->ascii >= '0' && event->ascii <= '9')) { + const char event_ascii = WM_event_utf8_to_ascii(event); + if (!(event_ascii >= '0' && event_ascii <= '9')) { return OPERATOR_PASS_THROUGH; } @@ -3406,7 +3407,7 @@ static int text_line_number_invoke(bContext *C, wmOperator *UNUSED(op), const wm } jump_to *= 10; - jump_to += (int)(event->ascii - '0'); + jump_to += (int)(event_ascii - '0'); txt_move_toline(text, jump_to - 1, 0); last_jump = time; @@ -3495,16 +3496,8 @@ static int text_insert_invoke(bContext *C, wmOperator *op, const wmEvent *event) } char str[BLI_UTF8_MAX + 1]; - size_t len; - - if (event->utf8_buf[0]) { - len = BLI_str_utf8_size_safe(event->utf8_buf); - memcpy(str, event->utf8_buf, len); - } - else { - /* in theory, ghost can set value to extended ascii here */ - len = BLI_str_utf8_from_unicode(event->ascii, str, sizeof(str) - 1); - } + const size_t len = BLI_str_utf8_size_safe(event->utf8_buf); + memcpy(str, event->utf8_buf, len); str[len] = '\0'; RNA_string_set(op->ptr, "text", str); diff --git a/source/blender/editors/util/numinput.c b/source/blender/editors/util/numinput.c index be6ac6e13e6..60cbc2a2df6 100644 --- a/source/blender/editors/util/numinput.c +++ b/source/blender/editors/util/numinput.c @@ -311,6 +311,7 @@ static bool editstr_is_simple_numinput(const char ascii) bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event) { const char *utf8_buf = NULL; + const char event_ascii = WM_event_utf8_to_ascii(event); char ascii[2] = {'\0', '\0'}; bool updated = false; short idx = n->idx, idx_max = n->idx_max; @@ -321,8 +322,8 @@ bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event) if (U.flag & USER_FLAG_NUMINPUT_ADVANCED) #endif { - if (((event->modifier & (KM_CTRL | KM_ALT)) == 0) && (event->ascii != '\0') && - strchr("01234567890@%^&*-+/{}()[]<>.|", event->ascii)) { + if (((event->modifier & (KM_CTRL | KM_ALT)) == 0) && (event_ascii != '\0') && + strchr("01234567890@%^&*-+/{}()[]<>.|", event_ascii)) { if (!(n->flag & NUM_EDIT_FULL)) { n->flag |= NUM_EDITED; n->flag |= NUM_EDIT_FULL; @@ -333,7 +334,7 @@ bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event) #ifdef USE_FAKE_EDIT /* XXX Hack around keyboards without direct access to '=' nor '*'... */ - if (ELEM(event->ascii, '=', '*')) { + if (ELEM(event_ascii, '=', '*')) { if (!(n->flag & NUM_EDIT_FULL)) { n->flag |= NUM_EDIT_FULL; n->val_flag[idx] |= NUM_EDITED; @@ -357,7 +358,7 @@ bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event) else { /* might be a char too... */ utf8_buf = event->utf8_buf; - ascii[0] = event->ascii; + ascii[0] = event_ascii; } break; case EVT_BACKSPACEKEY: @@ -523,9 +524,9 @@ bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event) break; } - if (!updated && !utf8_buf && (event->utf8_buf[0] || event->ascii)) { + if (!updated && !utf8_buf && event->utf8_buf[0]) { utf8_buf = event->utf8_buf; - ascii[0] = event->ascii; + ascii[0] = event_ascii; } /* Up to this point, if we have a ctrl modifier, skip. diff --git a/source/blender/makesrna/intern/rna_wm.c b/source/blender/makesrna/intern/rna_wm.c index 7893a2f4bc0..2009f51e1f2 100644 --- a/source/blender/makesrna/intern/rna_wm.c +++ b/source/blender/makesrna/intern/rna_wm.c @@ -607,14 +607,14 @@ static PointerRNA rna_OperatorMacro_properties_get(PointerRNA *ptr) static void rna_Event_ascii_get(PointerRNA *ptr, char *value) { const wmEvent *event = ptr->data; - value[0] = event->ascii; + value[0] = WM_event_utf8_to_ascii(event); value[1] = '\0'; } static int rna_Event_ascii_length(PointerRNA *ptr) { const wmEvent *event = ptr->data; - return (event->ascii) ? 1 : 0; + return WM_event_utf8_to_ascii(event) ? 1 : 0; } static void rna_Event_unicode_get(PointerRNA *ptr, char *value) diff --git a/source/blender/makesrna/intern/rna_wm_api.c b/source/blender/makesrna/intern/rna_wm_api.c index b9f36d35ee8..eb5e2549b1d 100644 --- a/source/blender/makesrna/intern/rna_wm_api.c +++ b/source/blender/makesrna/intern/rna_wm_api.c @@ -639,16 +639,12 @@ static wmEvent *rna_Window_event_add_simulate(wmWindow *win, } /* TODO: validate NDOF. */ - char ascii = 0; if (unicode != NULL) { int len = BLI_str_utf8_size(unicode); if (len == -1 || unicode[len] != '\0') { BKE_report(reports, RPT_ERROR, "Only a single character supported"); return NULL; } - if (len == 1 && isascii(unicode[0])) { - ascii = unicode[0]; - } } wmEvent e = *win->eventstate; @@ -672,10 +668,8 @@ static wmEvent *rna_Window_event_add_simulate(wmWindow *win, e.modifier |= KM_OSKEY; } - e.ascii = '\0'; e.utf8_buf[0] = '\0'; if (unicode != NULL) { - e.ascii = ascii; STRNCPY(e.utf8_buf, unicode); } diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index d852b85a3d0..f337dd9d89a 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -1530,6 +1530,7 @@ bool WM_event_is_modal_drag_exit(const struct wmEvent *event, bool WM_event_is_mouse_drag(const struct wmEvent *event); bool WM_event_is_mouse_drag_or_press(const wmEvent *event); int WM_event_drag_direction(const wmEvent *event); +char WM_event_utf8_to_ascii(const struct wmEvent *event) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT; /** * Detect motion between selection (callers should only use this for selection picking), diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index 32bc2f96365..e7cbe936607 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -683,13 +683,11 @@ typedef struct wmEvent { /** Region relative mouse position (name convention before Blender 2.5). */ int mval[2]; /** - * From, ghost if utf8 is enabled for the platform, - * #BLI_str_utf8_size() must _always_ be valid, check - * when assigning s we don't need to check on every access after. + * A single UTF8 encoded character. + * #BLI_str_utf8_size() must _always_ return a valid value, + * check when assigning so we don't need to check on every access after. */ char utf8_buf[6]; - /** From ghost, fallback if utf8 isn't set. */ - char ascii; /** Modifier states: #KM_SHIFT, #KM_CTRL, #KM_ALT & #KM_OSKEY. */ uint8_t modifier; diff --git a/source/blender/windowmanager/intern/wm_event_query.c b/source/blender/windowmanager/intern/wm_event_query.c index 221073d288a..81044197ae7 100644 --- a/source/blender/windowmanager/intern/wm_event_query.c +++ b/source/blender/windowmanager/intern/wm_event_query.c @@ -112,7 +112,7 @@ void WM_event_print(const wmEvent *event) "wmEvent type:%d/%s, val:%d/%s, " "prev_type:%d/%s, prev_val:%d/%s, " "modifier=%s, keymodifier:%d, flag:%s, " - "mouse:(%d,%d), ascii:'%c', utf8:'%.*s', pointer:%p", + "mouse:(%d,%d), utf8:'%.*s', pointer:%p", event->type, type_id, event->val, @@ -126,7 +126,6 @@ void WM_event_print(const wmEvent *event) flag_id, event->xy[0], event->xy[1], - event->ascii, BLI_str_utf8_size(event->utf8_buf), event->utf8_buf, (const void *)event); @@ -400,6 +399,20 @@ void WM_event_drag_start_xy(const wmEvent *event, int r_xy[2]) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Event Text Queries + * \{ */ + +char WM_event_utf8_to_ascii(const struct wmEvent *event) +{ + if (BLI_str_utf8_size(event->utf8_buf) == 1) { + return event->utf8_buf[0]; + } + return '\0'; +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Event Preference Mapping * \{ */ diff --git a/source/blender/windowmanager/intern/wm_event_system.cc b/source/blender/windowmanager/intern/wm_event_system.cc index 0395e8bda7a..a371fa7e185 100644 --- a/source/blender/windowmanager/intern/wm_event_system.cc +++ b/source/blender/windowmanager/intern/wm_event_system.cc @@ -2088,7 +2088,7 @@ static bool wm_eventmatch(const wmEvent *winevent, const wmKeyMapItem *kmi) if (winevent->val == KM_PRESS) { /* Prevent double clicks. */ /* Not using #ISTEXTINPUT anymore because (at least on Windows) some key codes above 255 * could have printable ascii keys, See T30479. */ - if (ISKEYBOARD(winevent->type) && (winevent->ascii || winevent->utf8_buf[0])) { + if (ISKEYBOARD(winevent->type) && winevent->utf8_buf[0]) { return true; } } @@ -5042,7 +5042,6 @@ static wmEvent *wm_event_add_mousemove_to_head(wmWindow *win) tevent = *event_last; tevent.flag = (eWM_EventFlag)0; - tevent.ascii = '\0'; tevent.utf8_buf[0] = '\0'; wm_event_custom_clear(&tevent); @@ -5329,7 +5328,6 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void break; } - event.ascii = kd->ascii; /* Might be not nullptr terminated. */ memcpy(event.utf8_buf, kd->utf8_buf, sizeof(event.utf8_buf)); if (kd->is_repeat) { @@ -5341,8 +5339,6 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void /* Exclude arrow keys, escape, etc from text input. */ if (type == GHOST_kEventKeyUp) { - event.ascii = '\0'; - /* Ghost should do this already for key up. */ if (event.utf8_buf[0]) { CLOG_ERROR(WM_LOG_EVENTS, @@ -5351,15 +5347,28 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void event.utf8_buf[0] = '\0'; } else { - if (event.ascii < 32 && event.ascii > 0) { - event.ascii = '\0'; - } if (event.utf8_buf[0] < 32 && event.utf8_buf[0] > 0) { event.utf8_buf[0] = '\0'; } } if (event.utf8_buf[0]) { + /* NOTE(@campbellbarton): Detect non-ASCII characters stored in `utf8_buf`, + * ideally this would never happen but it can't be ruled out for X11 which has + * special handling of Latin1 when building without UTF8 support. + * Avoid regressions by adding this conversions, it should eventually be removed. */ + if ((event.utf8_buf[0] >= 0x80) && (event.utf8_buf[1] == '\0')) { + const uint c = (uint)event.utf8_buf[0]; + int utf8_buf_len = BLI_str_utf8_from_unicode(c, event.utf8_buf, sizeof(event.utf8_buf)); + CLOG_ERROR(WM_LOG_EVENTS, + "ghost detected non-ASCII single byte character '%u', converting to utf8 " + "('%.*s', length=%d)", + c, + utf8_buf_len, + event.utf8_buf, + utf8_buf_len); + } + if (BLI_str_utf8_size(event.utf8_buf) == -1) { CLOG_ERROR(WM_LOG_EVENTS, "ghost detected an invalid unicode character '%d'", diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c index 36f91f8414a..70640eda605 100644 --- a/source/blender/windowmanager/intern/wm_window.c +++ b/source/blender/windowmanager/intern/wm_window.c @@ -1148,7 +1148,6 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_pt */ GHOST_TEventKeyData kdata = { .key = GHOST_kKeyUnknown, - .ascii = '\0', .utf8_buf = {'\0'}, .is_repeat = false, }; -- cgit v1.2.3 From b35e33317dd3c1b0b4ceb3aa0b55f805661fdb05 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 14 Jul 2022 16:10:13 +1000 Subject: Cleanup: update & correct comments for event handling - Remove references to `ISTEXTINPUT` as any keyboard event with it's utf8_buf set can be handled as text input. - Update references to the key repeat flag. --- intern/ghost/GHOST_Types.h | 7 ++++++- source/blender/editors/interface/interface_region_menu_pie.cc | 2 +- source/blender/makesdna/DNA_windowmanager_types.h | 4 ++-- source/blender/windowmanager/intern/wm_event_system.cc | 4 +--- source/blender/windowmanager/wm_event_types.h | 6 ------ 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h index fa74bfde866..495fb739978 100644 --- a/intern/ghost/GHOST_Types.h +++ b/intern/ghost/GHOST_Types.h @@ -550,7 +550,12 @@ typedef struct { /** The unicode character. if the length is 6, not NULL terminated if all 6 are set. */ char utf8_buf[6]; - /** Generated by auto-repeat. */ + /** + * Enabled when the key is held (auto-repeat). + * In this case press events are sent without a corresponding release/up event. + * + * All back-ends must set this variable for correct behavior regarding repeatable keys. + */ char is_repeat; } GHOST_TEventKeyData; diff --git a/source/blender/editors/interface/interface_region_menu_pie.cc b/source/blender/editors/interface/interface_region_menu_pie.cc index b11564f09c5..09902dd6c35 100644 --- a/source/blender/editors/interface/interface_region_menu_pie.cc +++ b/source/blender/editors/interface/interface_region_menu_pie.cc @@ -371,7 +371,7 @@ void ui_pie_menu_level_create(uiBlock *block, EnumPropertyItem *remaining = static_cast( MEM_mallocN(array_size + sizeof(EnumPropertyItem), "pie_level_item_array")); memcpy(remaining, items + totitem_parent, array_size); - /* A nullptr terminating sentinel element is required. */ + /* A null terminating sentinel element is required. */ memset(&remaining[totitem_remain], 0, sizeof(EnumPropertyItem)); /* yuk, static... issue is we can't reliably free this without doing dangerous changes */ diff --git a/source/blender/makesdna/DNA_windowmanager_types.h b/source/blender/makesdna/DNA_windowmanager_types.h index a3d9b5fc7b6..116ea4821cb 100644 --- a/source/blender/makesdna/DNA_windowmanager_types.h +++ b/source/blender/makesdna/DNA_windowmanager_types.h @@ -407,13 +407,13 @@ enum { KMI_USER_MODIFIED = (1 << 2), KMI_UPDATE = (1 << 3), /** - * When set, ignore events with #wmEvent.is_repeat enabled. + * When set, ignore events with `wmEvent.flag & WM_EVENT_IS_REPEAT` enabled. * * \note this flag isn't cleared when editing/loading the key-map items, * so it may be set in cases which don't make sense (modifier-keys or mouse-motion for example). * * Knowing if an event may repeat is something set at the operating-systems event handling level - * so rely on #wmEvent.is_repeat being false non keyboard events instead of checking if this + * so rely on #WM_EVENT_IS_REPEAT being false non keyboard events instead of checking if this * flag makes sense. * * Only used when: `ISKEYBOARD(kmi->type) || (kmi->type == KM_TEXTINPUT)` diff --git a/source/blender/windowmanager/intern/wm_event_system.cc b/source/blender/windowmanager/intern/wm_event_system.cc index a371fa7e185..757c3aae00c 100644 --- a/source/blender/windowmanager/intern/wm_event_system.cc +++ b/source/blender/windowmanager/intern/wm_event_system.cc @@ -2086,8 +2086,6 @@ static bool wm_eventmatch(const wmEvent *winevent, const wmKeyMapItem *kmi) /* The matching rules. */ if (kmitype == KM_TEXTINPUT) { if (winevent->val == KM_PRESS) { /* Prevent double clicks. */ - /* Not using #ISTEXTINPUT anymore because (at least on Windows) some key codes above 255 - * could have printable ascii keys, See T30479. */ if (ISKEYBOARD(winevent->type) && winevent->utf8_buf[0]) { return true; } @@ -5328,7 +5326,7 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void break; } - /* Might be not nullptr terminated. */ + /* Might be not null terminated. */ memcpy(event.utf8_buf, kd->utf8_buf, sizeof(event.utf8_buf)); if (kd->is_repeat) { event.flag |= WM_EVENT_IS_REPEAT; diff --git a/source/blender/windowmanager/wm_event_types.h b/source/blender/windowmanager/wm_event_types.h index 2d3624704d0..edac3ada73b 100644 --- a/source/blender/windowmanager/wm_event_types.h +++ b/source/blender/windowmanager/wm_event_types.h @@ -358,12 +358,6 @@ enum { /** Test whether the event is timer event. */ #define ISTIMER(event_type) ((event_type) >= TIMER && (event_type) <= TIMERF) -/* for event checks */ -/* only used for KM_TEXTINPUT, so assume that we want all user-inputtable ascii codes included */ -/* Unused, see #wm_eventmatch, see: T30479. */ -// #define ISTEXTINPUT(event_type) ((event_type) >= ' ' && (event_type) <= 255) -/* NOTE: an alternative could be to check `event->utf8_buf`. */ - /** Test whether the event is a key on the keyboard (including modifier keys). */ #define ISKEYBOARD(event_type) \ (((event_type) >= _EVT_KEYBOARD_MIN && (event_type) <= _EVT_KEYBOARD_MAX) || \ -- cgit v1.2.3 From 411bcf1fe7345f2e513b49249c34d3601631a5d7 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 14 Jul 2022 16:26:24 +1000 Subject: Cleanup: simplify 3D text insert, remove checks for ascii escape codes 3D text included checks for escape codes such as \n\r\b which have not been included in wmEvent.ascii since [0] (2009). Remove these and use more straightforward logic for overriding the events text input. [0] 66437a62a73966de8ccb673473ba69d6c1ed66a3 --- source/blender/editors/curve/editfont.c | 60 +++++++++++++++------------------ 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/source/blender/editors/curve/editfont.c b/source/blender/editors/curve/editfont.c index 33e3837c9d4..623f41dcd20 100644 --- a/source/blender/editors/curve/editfont.c +++ b/source/blender/editors/curve/editfont.c @@ -59,7 +59,7 @@ static int kill_selection(Object *obedit, int ins); /** \name Internal Utilities * \{ */ -static char32_t findaccent(char32_t char1, uint code) +static char32_t findaccent(char32_t char1, const char code) { char32_t new = 0; @@ -1638,12 +1638,12 @@ static int insert_text_invoke(bContext *C, wmOperator *op, const wmEvent *event) Object *obedit = CTX_data_edit_object(C); Curve *cu = obedit->data; EditFont *ef = cu->editfont; - static int accentcode = 0; - uintptr_t ascii = WM_event_utf8_to_ascii(event); + static bool accentcode = false; const bool alt = event->modifier & KM_ALT; const bool shift = event->modifier & KM_SHIFT; const bool ctrl = event->modifier & KM_CTRL; int event_type = event->type, event_val = event->val; + char32_t insert_char_override = 0; char32_t inserted_text[2] = {0}; if (RNA_struct_property_is_set(op->ptr, "text")) { @@ -1652,48 +1652,47 @@ static int insert_text_invoke(bContext *C, wmOperator *op, const wmEvent *event) if (RNA_struct_property_is_set(op->ptr, "accent")) { if (ef->len != 0 && ef->pos > 0) { - accentcode = 1; + accentcode = true; } return OPERATOR_FINISHED; } - /* tab should exit editmode, but we allow it to be typed using modifier keys */ - if (event_type == EVT_TABKEY) { - if ((alt || ctrl || shift) == 0) { - return OPERATOR_PASS_THROUGH; - } - - ascii = 9; - } - if (event_type == EVT_BACKSPACEKEY) { if (alt && ef->len != 0 && ef->pos > 0) { - accentcode = 1; + accentcode = true; } return OPERATOR_PASS_THROUGH; } - if (event_val && (ascii || event->utf8_buf[0])) { - /* handle case like TAB (== 9) */ - if ((ascii > 31 && ascii < 254 && ascii != 127) || (ELEM(ascii, 13, 10)) || (ascii == 8) || - (event->utf8_buf[0])) { + /* Tab typically exit edit-mode, but we allow it to be typed using modifier keys. */ + if (event_type == EVT_TABKEY) { + if ((alt || ctrl || shift) == 0) { + return OPERATOR_PASS_THROUGH; + } + insert_char_override = '\t'; + } + if (event_val && (insert_char_override || event->utf8_buf[0])) { + if (insert_char_override) { + /* Handle case like TAB ('\t'). */ + inserted_text[0] = insert_char_override; + insert_into_textbuf(obedit, insert_char_override); + text_update_edited(C, obedit, FO_EDIT); + } + else { + BLI_assert(event->utf8_buf[0]); if (accentcode) { if (ef->pos > 0) { - inserted_text[0] = findaccent(ef->textbuf[ef->pos - 1], ascii); + inserted_text[0] = findaccent(ef->textbuf[ef->pos - 1], + BLI_str_utf8_as_unicode(event->utf8_buf)); ef->textbuf[ef->pos - 1] = inserted_text[0]; } - accentcode = 0; + accentcode = false; } else if (event->utf8_buf[0]) { inserted_text[0] = BLI_str_utf8_as_unicode(event->utf8_buf); - ascii = inserted_text[0]; - insert_into_textbuf(obedit, ascii); - accentcode = 0; - } - else if (ascii) { - insert_into_textbuf(obedit, ascii); - accentcode = 0; + insert_into_textbuf(obedit, inserted_text[0]); + accentcode = false; } else { BLI_assert(0); @@ -1702,11 +1701,6 @@ static int insert_text_invoke(bContext *C, wmOperator *op, const wmEvent *event) kill_selection(obedit, 1); text_update_edited(C, obedit, FO_EDIT); } - else { - inserted_text[0] = ascii; - insert_into_textbuf(obedit, ascii); - text_update_edited(C, obedit, FO_EDIT); - } } else { return OPERATOR_PASS_THROUGH; @@ -1722,7 +1716,7 @@ static int insert_text_invoke(bContext *C, wmOperator *op, const wmEvent *event) /* reset property? */ if (event_val == 0) { - accentcode = 0; + accentcode = false; } return OPERATOR_FINISHED; -- cgit v1.2.3 From 3935bf255e3313534bc16090aac8118939a1b333 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Thu, 14 Jul 2022 09:57:08 +0200 Subject: Fix T99677: crash when convex hull node is used on empty mesh Fundamental issue is that the attribute api returns none, because the custom data api returns null for a layer when the size of 0. This should be improved separately. --- source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc index 489b618b8be..3394a7cad62 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc @@ -183,8 +183,10 @@ static Mesh *compute_hull(const GeometrySet &geometry_set) const MeshComponent *component = geometry_set.get_component_for_read(); const VArray varray = component->attributes()->lookup("position", ATTR_DOMAIN_POINT); - varray.materialize(positions.as_mutable_span().slice(offset, varray.size())); - offset += varray.size(); + if (varray) { + varray.materialize(positions.as_mutable_span().slice(offset, varray.size())); + offset += varray.size(); + } } if (geometry_set.has_pointcloud()) { @@ -192,8 +194,10 @@ static Mesh *compute_hull(const GeometrySet &geometry_set) geometry_set.get_component_for_read(); const VArray varray = component->attributes()->lookup("position", ATTR_DOMAIN_POINT); - varray.materialize(positions.as_mutable_span().slice(offset, varray.size())); - offset += varray.size(); + if (varray) { + varray.materialize(positions.as_mutable_span().slice(offset, varray.size())); + offset += varray.size(); + } } if (geometry_set.has_curves()) { -- cgit v1.2.3 From 1571ee66b51d5a8cd700228d01577608e62c0be9 Mon Sep 17 00:00:00 2001 From: Damien Picard Date: Thu, 14 Jul 2022 09:40:55 +0200 Subject: I18N: Allow translating newly added GP data names, and a missing Surface one. --- source/blender/editors/curve/editcurve_add.c | 2 ++ source/blender/editors/object/object_add.cc | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/source/blender/editors/curve/editcurve_add.c b/source/blender/editors/curve/editcurve_add.c index ba5a7409ba7..ee6376ca95f 100644 --- a/source/blender/editors/curve/editcurve_add.c +++ b/source/blender/editors/curve/editcurve_add.c @@ -87,6 +87,8 @@ static const char *get_surf_defname(int type) return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "SurfCircle"); case CU_PRIM_PATCH: return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "SurfPatch"); + case CU_PRIM_TUBE: + return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "SurfCylinder"); case CU_PRIM_SPHERE: return CTX_DATA_(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "SurfSphere"); case CU_PRIM_DONUT: diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc index 671a32ce438..35d23edfbf0 100644 --- a/source/blender/editors/object/object_add.cc +++ b/source/blender/editors/object/object_add.cc @@ -1335,21 +1335,21 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) const char *ob_name = nullptr; switch (type) { case GP_EMPTY: { - ob_name = "GPencil"; + ob_name = CTX_DATA_(BLT_I18NCONTEXT_ID_GPENCIL, "GPencil"); break; } case GP_MONKEY: { - ob_name = "Suzanne"; + ob_name = CTX_DATA_(BLT_I18NCONTEXT_ID_GPENCIL, "Suzanne"); break; } case GP_STROKE: { - ob_name = "Stroke"; + ob_name = CTX_DATA_(BLT_I18NCONTEXT_ID_GPENCIL, "Stroke"); break; } case GP_LRT_OBJECT: case GP_LRT_SCENE: case GP_LRT_COLLECTION: { - ob_name = "LineArt"; + ob_name = CTX_DATA_(BLT_I18NCONTEXT_ID_GPENCIL, "LineArt"); break; } default: { -- cgit v1.2.3 From f48fadc953f68ba24084bb277ff2ca05ddaa289d Mon Sep 17 00:00:00 2001 From: Damien Picard Date: Thu, 14 Jul 2022 09:42:48 +0200 Subject: UI: translate quick favorites menu operator names Some operator titles were not translated in the quick favorites menu. Before: {F13283724} After: {F13283725} Reviewed By: mont29 Differential Revision: https://developer.blender.org/D15416 --- source/blender/editors/screen/screen_user_menu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/editors/screen/screen_user_menu.c b/source/blender/editors/screen/screen_user_menu.c index 1156452310c..67ca6a83b2b 100644 --- a/source/blender/editors/screen/screen_user_menu.c +++ b/source/blender/editors/screen/screen_user_menu.c @@ -214,7 +214,7 @@ static void screen_user_menu_draw(const bContext *C, Menu *menu) wmOperatorType *ot = WM_operatortype_find(umi_op->op_idname, false); if (ot != NULL) { IDProperty *prop = umi_op->prop ? IDP_CopyProperty(umi_op->prop) : NULL; - uiItemFullO_ptr(menu->layout, ot, ui_name, ICON_NONE, prop, umi_op->opcontext, 0, NULL); + uiItemFullO_ptr(menu->layout, ot, CTX_IFACE_(ot->translation_context, ui_name), ICON_NONE, prop, umi_op->opcontext, 0, NULL); is_empty = false; } else { -- cgit v1.2.3 From 77df9d788a2bb9d7173f10edf3631dc26ccce8ed Mon Sep 17 00:00:00 2001 From: Iliay Katueshenock Date: Thu, 14 Jul 2022 10:04:35 +0200 Subject: Fix T99239: weird behavior in Field on Domain node --- .../blender/nodes/geometry/nodes/node_geo_field_on_domain.cc | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_field_on_domain.cc b/source/blender/nodes/geometry/nodes/node_geo_field_on_domain.cc index 5939ed5334d..59e243db4a2 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_field_on_domain.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_field_on_domain.cc @@ -85,12 +85,13 @@ class FieldOnDomain final : public GeometryFieldInput { IndexMask /* mask */) const final { const GeometryComponentFieldContext context{component, src_domain_}; - FieldEvaluator value_evaluator{context, component.attribute_domain_size(src_domain_)}; - value_evaluator.add(src_field_); + const int64_t src_domain_size = component.attribute_domain_size(src_domain_); + GArray values(src_field_.cpp_type(), src_domain_size); + FieldEvaluator value_evaluator{context, src_domain_size}; + value_evaluator.add_with_destination(src_field_, values.as_mutable_span()); value_evaluator.evaluate(); - const GVArray &values = value_evaluator.get_evaluated(0); - - return component.attributes()->adapt_domain(values, src_domain_, domain); + return component.attributes()->adapt_domain( + GVArray::ForGArray(std::move(values)), src_domain_, domain); } }; -- cgit v1.2.3 From 8e3879ab5276cf24ebd59a7bf0df69232fd5e4e6 Mon Sep 17 00:00:00 2001 From: Colin Basnett Date: Thu, 14 Jul 2022 10:22:30 +0200 Subject: Cleanup: Rename & refactor several F-curve functions Rename and refactor several F-curve key manipulation functions, and move them from `editors` to `blenkernel`. The functions formerly known as `delete_fcurve_key`, `delete_fcurve_keys`, and `clear_fcurve_keys` have been moved from `ED_keyframes_edit.h` to `BKE_fcurve.h` and have been renamed according to hierarchical naming rules. Below is a table of the naming changes. | From | To | | -- | -- | | `delete_fcurve_key(fcu, index, do_recalc)` | `BKE_fcurve_delete_key(fcu, index)` | | `delete_fcurve_keys(fcu)` | `BKE_fcurve_delete_keys_selected(fcu)` | | `clear_fcurve_keys(fcu)` | `BKE_fcurve_delete_keys_all(fcu)` | | `calchandles_fcurve()` | `BKE_fcurve_handles_recalc()` | | `calchandles_fcurve_ex()`| `BKE_fcurve_handles_recalc_ex()` | The function formerly known as `delete_fcurve_key` no longer takes a `do_fast` parameter, which determined whether or not to call `calchandles_fcurve`. Now, the responsibility is on the caller to run the new `BKE_fcurve_handles_recalc` function if they have want to recalculate the handles. In addition, there is now a new static private function called `fcurve_bezt_free` which sets the key count to zero and frees the key array. This function is now used in couple of instances of functionally equivalent code. Note that `BKE_fcurve_delete_keys_all` is just a wrapper around `fcurve_bezt_free`. This change was initially spurred by the fact that `delete_fcurve_keys` was improperly named; this was a good opportunity to fix the location and naming of a few of these functions. Reviewed By: sybren Differential Revision: https://developer.blender.org/D15282 --- source/blender/blenkernel/BKE_fcurve.h | 23 ++++-- source/blender/blenkernel/intern/action_mirror.c | 2 +- source/blender/blenkernel/intern/fcurve.c | 79 ++++++++++++++++++-- source/blender/blenkernel/intern/fmodifier.c | 4 +- source/blender/editors/animation/anim_deps.c | 2 +- source/blender/editors/animation/drivers.c | 2 +- source/blender/editors/animation/fmodifier_ui.c | 2 +- source/blender/editors/animation/keyframes_edit.c | 2 +- .../blender/editors/animation/keyframes_general.c | 83 ++-------------------- source/blender/editors/animation/keyframing.c | 14 ++-- source/blender/editors/armature/pose_lib.c | 4 +- source/blender/editors/gpencil/gpencil_convert.c | 2 +- source/blender/editors/include/ED_keyframes_edit.h | 3 - source/blender/editors/space_action/action_edit.c | 12 ++-- source/blender/editors/space_graph/graph_buttons.c | 2 +- source/blender/editors/space_graph/graph_edit.c | 16 ++--- source/blender/editors/space_nla/nla_edit.c | 2 +- .../blender/editors/transform/transform_convert.c | 4 +- .../editors/transform/transform_convert_graph.c | 2 +- source/blender/io/collada/AnimationImporter.cpp | 4 +- source/blender/io/collada/BCAnimationCurve.cpp | 2 +- source/blender/makesrna/intern/rna_fcurve.c | 10 ++- source/blender/python/intern/bpy_rna_anim.c | 3 +- 23 files changed, 150 insertions(+), 129 deletions(-) diff --git a/source/blender/blenkernel/BKE_fcurve.h b/source/blender/blenkernel/BKE_fcurve.h index 10d9ce3364d..3ccbd2ac1da 100644 --- a/source/blender/blenkernel/BKE_fcurve.h +++ b/source/blender/blenkernel/BKE_fcurve.h @@ -463,23 +463,38 @@ bool BKE_fcurve_bezt_subdivide_handles(struct BezTriple *bezt, struct BezTriple *next, float *r_pdelta); +/** + * Delete a keyframe from an F-curve at a specific index. + */ +void BKE_fcurve_delete_key(struct FCurve *fcu, int index); + +/** + * Delete selected keyframes from an F-curve. + */ +bool BKE_fcurve_delete_keys_selected(struct FCurve *fcu); + +/** + * Delete all keyframes from an F-curve. + */ +void BKE_fcurve_delete_keys_all(struct FCurve *fcu); + /* -------- Curve Sanity -------- */ /** * This function recalculates the handles of an F-Curve. Acts based on selection with `SELECT` - * flag. To use a different flag, use #calchandles_fcurve_ex(). + * flag. To use a different flag, use #BKE_fcurve_handles_recalc_ex(). * * If the BezTriples have been rearranged, sort them first before using this. */ -void calchandles_fcurve(struct FCurve *fcu); +void BKE_fcurve_handles_recalc(struct FCurve *fcu); /** - * Variant of #calchandles_fcurve() that allows calculating based on a different select flag. + * Variant of #BKE_fcurve_handles_recalc() that allows calculating based on a different select flag. * * \param handle_sel_flag: The flag (bezt.f1/2/3) value to use to determine selection. * Usually `SELECT`, but may want to use a different one at times * (if caller does not operate on selection). */ -void calchandles_fcurve_ex(struct FCurve *fcu, eBezTriple_Flag handle_sel_flag); +void BKE_fcurve_handles_recalc_ex(struct FCurve *fcu, eBezTriple_Flag handle_sel_flag); /** * Update handles, making sure the handle-types are valid (e.g. correctly deduced from an "Auto" * type), and recalculating their position vectors. diff --git a/source/blender/blenkernel/intern/action_mirror.c b/source/blender/blenkernel/intern/action_mirror.c index 7ef561c8216..0663538f3bb 100644 --- a/source/blender/blenkernel/intern/action_mirror.c +++ b/source/blender/blenkernel/intern/action_mirror.c @@ -363,7 +363,7 @@ static void action_flip_pchan(Object *ob_arm, /* Recalculate handles. */ for (int i = 0; i < fcurve_array_len; i++) { - calchandles_fcurve_ex(fcurve_array[i], 0); + BKE_fcurve_handles_recalc_ex(fcurve_array[i], 0); } MEM_freeN((void *)keyed_frames); diff --git a/source/blender/blenkernel/intern/fcurve.c b/source/blender/blenkernel/intern/fcurve.c index 0203620df84..972ff377519 100644 --- a/source/blender/blenkernel/intern/fcurve.c +++ b/source/blender/blenkernel/intern/fcurve.c @@ -1146,7 +1146,7 @@ void fcurve_samples_to_keyframes(FCurve *fcu, const int start, const int end) MEM_SAFE_FREE(fcu->fpt); /* Not strictly needed since we use linear interpolation, but better be consistent here. */ - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); } /* ***************************** F-Curve Sanity ********************************* */ @@ -1216,7 +1216,7 @@ static BezTriple *cycle_offset_triple( return out; } -void calchandles_fcurve_ex(FCurve *fcu, eBezTriple_Flag handle_sel_flag) +void BKE_fcurve_handles_recalc_ex(FCurve *fcu, eBezTriple_Flag handle_sel_flag) { BezTriple *bezt, *prev, *next; int a = fcu->totvert; @@ -1299,9 +1299,9 @@ void calchandles_fcurve_ex(FCurve *fcu, eBezTriple_Flag handle_sel_flag) } } -void calchandles_fcurve(FCurve *fcu) +void BKE_fcurve_handles_recalc(FCurve *fcu) { - calchandles_fcurve_ex(fcu, SELECT); + BKE_fcurve_handles_recalc_ex(fcu, SELECT); } void testhandles_fcurve(FCurve *fcu, eBezTriple_Flag sel_flag, const bool use_handle) @@ -1320,7 +1320,7 @@ void testhandles_fcurve(FCurve *fcu, eBezTriple_Flag sel_flag, const bool use_ha } /* Recalculate handles. */ - calchandles_fcurve_ex(fcu, sel_flag); + BKE_fcurve_handles_recalc_ex(fcu, sel_flag); } void sort_time_fcurve(FCurve *fcu) @@ -1590,6 +1590,12 @@ static void berekeny(float f1, float f2, float f3, float f4, float *o, int b) } } +static void fcurve_bezt_free(FCurve *fcu) +{ + MEM_SAFE_FREE(fcu->bezt); + fcu->totvert = 0; +} + bool BKE_fcurve_bezt_subdivide_handles(struct BezTriple *bezt, struct BezTriple *prev, struct BezTriple *next, @@ -1651,6 +1657,69 @@ bool BKE_fcurve_bezt_subdivide_handles(struct BezTriple *bezt, return true; } +void BKE_fcurve_delete_key(FCurve *fcu, int index) +{ + /* sanity check */ + if (fcu == NULL) { + return; + } + + /* verify the index: + * 1) cannot be greater than the number of available keyframes + * 2) negative indices are for specifying a value from the end of the array + */ + if (abs(index) >= fcu->totvert) { + return; + } + if (index < 0) { + index += fcu->totvert; + } + + /* Delete this keyframe */ + memmove( + &fcu->bezt[index], &fcu->bezt[index + 1], sizeof(BezTriple) * (fcu->totvert - index - 1)); + fcu->totvert--; + + /* Free the array of BezTriples if there are not keyframes */ + if (fcu->totvert == 0) { + fcurve_bezt_free(fcu); + } +} + +bool BKE_fcurve_delete_keys_selected(FCurve *fcu) +{ + bool changed = false; + + if (fcu->bezt == NULL) { /* ignore baked curves */ + return false; + } + + /* Delete selected BezTriples */ + for (int i = 0; i < fcu->totvert; i++) { + if (fcu->bezt[i].f2 & SELECT) { + if (i == fcu->active_keyframe_index) { + BKE_fcurve_active_keyframe_set(fcu, NULL); + } + memmove(&fcu->bezt[i], &fcu->bezt[i + 1], sizeof(BezTriple) * (fcu->totvert - i - 1)); + fcu->totvert--; + i--; + changed = true; + } + } + + /* Free the array of BezTriples if there are not keyframes */ + if (fcu->totvert == 0) { + fcurve_bezt_free(fcu); + } + + return changed; +} + +void BKE_fcurve_delete_keys_all(FCurve *fcu) +{ + fcurve_bezt_free(fcu); +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/intern/fmodifier.c b/source/blender/blenkernel/intern/fmodifier.c index 1bb7c49d616..e4c7572b9e4 100644 --- a/source/blender/blenkernel/intern/fmodifier.c +++ b/source/blender/blenkernel/intern/fmodifier.c @@ -1127,7 +1127,7 @@ FModifier *add_fmodifier(ListBase *modifiers, int type, FCurve *owner_fcu) /* update the fcurve if the Cycles modifier is added */ if ((owner_fcu) && (type == FMODIFIER_TYPE_CYCLES)) { - calchandles_fcurve(owner_fcu); + BKE_fcurve_handles_recalc(owner_fcu); } /* return modifier for further editing */ @@ -1215,7 +1215,7 @@ bool remove_fmodifier(ListBase *modifiers, FModifier *fcm) /* update the fcurve if the Cycles modifier is removed */ if (update_fcu) { - calchandles_fcurve(update_fcu); + BKE_fcurve_handles_recalc(update_fcu); } return true; diff --git a/source/blender/editors/animation/anim_deps.c b/source/blender/editors/animation/anim_deps.c index d80eac2422e..ff53ad42e84 100644 --- a/source/blender/editors/animation/anim_deps.c +++ b/source/blender/editors/animation/anim_deps.c @@ -356,7 +356,7 @@ void ANIM_animdata_update(bAnimContext *ac, ListBase *anim_data) if (ale->update & ANIM_UPDATE_HANDLES) { ale->update &= ~ANIM_UPDATE_HANDLES; if (fcu) { - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); } } diff --git a/source/blender/editors/animation/drivers.c b/source/blender/editors/animation/drivers.c index c6f68228807..effedd4307d 100644 --- a/source/blender/editors/animation/drivers.c +++ b/source/blender/editors/animation/drivers.c @@ -124,7 +124,7 @@ struct FCurve *alloc_driver_fcurve(const char rna_path[], insert_vert_fcurve( fcu, 1.0f, 1.0f, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_FAST | INSERTKEY_NO_USERPREF); fcu->extend = FCURVE_EXTRAPOLATE_LINEAR; - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); } } diff --git a/source/blender/editors/animation/fmodifier_ui.c b/source/blender/editors/animation/fmodifier_ui.c index 6f31472907b..d2f0ee622c4 100644 --- a/source/blender/editors/animation/fmodifier_ui.c +++ b/source/blender/editors/animation/fmodifier_ui.c @@ -1020,7 +1020,7 @@ bool ANIM_fmodifiers_paste_from_buf(ListBase *modifiers, bool replace, FCurve *c /* adding or removing the Cycles modifier requires an update to handles */ if (curve && BKE_fcurve_is_cyclic(curve) != was_cyclic) { - calchandles_fcurve(curve); + BKE_fcurve_handles_recalc(curve); } /* did we succeed? */ diff --git a/source/blender/editors/animation/keyframes_edit.c b/source/blender/editors/animation/keyframes_edit.c index 88207f7d514..706db498a82 100644 --- a/source/blender/editors/animation/keyframes_edit.c +++ b/source/blender/editors/animation/keyframes_edit.c @@ -444,7 +444,7 @@ void ANIM_animdata_keyframe_callback(bAnimContext *ac, ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); for (ale = anim_data.first; ale; ale = ale->next) { - ANIM_fcurve_keyframes_loop(NULL, ale->key_data, NULL, callback_fn, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(NULL, ale->key_data, NULL, callback_fn, BKE_fcurve_handles_recalc); ale->update |= ANIM_UPDATE_DEFAULT; } diff --git a/source/blender/editors/animation/keyframes_general.c b/source/blender/editors/animation/keyframes_general.c index dd88752af14..7723c221a40 100644 --- a/source/blender/editors/animation/keyframes_general.c +++ b/source/blender/editors/animation/keyframes_general.c @@ -47,77 +47,6 @@ /* **************************************************** */ -void delete_fcurve_key(FCurve *fcu, int index, bool do_recalc) -{ - /* sanity check */ - if (fcu == NULL) { - return; - } - - /* verify the index: - * 1) cannot be greater than the number of available keyframes - * 2) negative indices are for specifying a value from the end of the array - */ - if (abs(index) >= fcu->totvert) { - return; - } - if (index < 0) { - index += fcu->totvert; - } - - /* Delete this keyframe */ - memmove( - &fcu->bezt[index], &fcu->bezt[index + 1], sizeof(BezTriple) * (fcu->totvert - index - 1)); - fcu->totvert--; - - if (fcu->totvert == 0) { - MEM_SAFE_FREE(fcu->bezt); - } - - /* recalc handles - only if it won't cause problems */ - if (do_recalc) { - calchandles_fcurve(fcu); - } -} - -bool delete_fcurve_keys(FCurve *fcu) -{ - bool changed = false; - - if (fcu->bezt == NULL) { /* ignore baked curves */ - return false; - } - - /* Delete selected BezTriples */ - for (int i = 0; i < fcu->totvert; i++) { - if (fcu->bezt[i].f2 & SELECT) { - if (i == fcu->active_keyframe_index) { - BKE_fcurve_active_keyframe_set(fcu, NULL); - } - memmove(&fcu->bezt[i], &fcu->bezt[i + 1], sizeof(BezTriple) * (fcu->totvert - i - 1)); - fcu->totvert--; - i--; - changed = true; - } - } - - /* Free the array of BezTriples if there are not keyframes */ - if (fcu->totvert == 0) { - clear_fcurve_keys(fcu); - } - - return changed; -} - -void clear_fcurve_keys(FCurve *fcu) -{ - MEM_SAFE_FREE(fcu->bezt); - - fcu->totvert = 0; -} - -/* ---------------- */ - bool duplicate_fcurve_keys(FCurve *fcu) { bool changed = false; @@ -282,7 +211,7 @@ void clean_fcurve(struct bAnimContext *ac, bAnimListElem *ale, float thresh, boo } if (fcu->bezt->vec[1][1] == default_value) { - clear_fcurve_keys(fcu); + BKE_fcurve_delete_keys_all(fcu); /* check if curve is really unused and if it is, return signal for deletion */ if (BKE_fcurve_is_empty(fcu)) { @@ -679,7 +608,7 @@ void smooth_fcurve(FCurve *fcu) } /* recalculate handles */ - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); } /* ---------------- */ @@ -762,7 +691,7 @@ void sample_fcurve(FCurve *fcu) } /* recalculate channel's handles? */ - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); } /* **************************************************** */ @@ -1121,7 +1050,7 @@ static void paste_animedit_keys_fcurve( case KEYFRAME_PASTE_MERGE_OVER: /* remove all keys */ - clear_fcurve_keys(fcu); + BKE_fcurve_delete_keys_all(fcu); break; case KEYFRAME_PASTE_MERGE_OVER_RANGE: @@ -1148,7 +1077,7 @@ static void paste_animedit_keys_fcurve( } /* remove frames in the range */ - delete_fcurve_keys(fcu); + BKE_fcurve_delete_keys_selected(fcu); } break; } @@ -1182,7 +1111,7 @@ static void paste_animedit_keys_fcurve( } /* recalculate F-Curve's handles? */ - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); } const EnumPropertyItem rna_enum_keyframe_paste_offset_items[] = { diff --git a/source/blender/editors/animation/keyframing.c b/source/blender/editors/animation/keyframing.c index 2fa8907de71..9084b9bb214 100644 --- a/source/blender/editors/animation/keyframing.c +++ b/source/blender/editors/animation/keyframing.c @@ -639,7 +639,7 @@ int insert_vert_fcurve( * - we may calculate twice (due to auto-handle needing to be calculated twice) */ if ((flag & INSERTKEY_FAST) == 0) { - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); } /* return the index at which the keyframe was added */ @@ -1282,10 +1282,12 @@ static bool insert_keyframe_value(ReportList *reports, /* delete keyframe immediately before/after newly added */ switch (insert_mode) { case KEYNEEDED_DELPREV: - delete_fcurve_key(fcu, fcu->totvert - 2, 1); + BKE_fcurve_delete_key(fcu, fcu->totvert - 2); + BKE_fcurve_handles_recalc(fcu); break; case KEYNEEDED_DELNEXT: - delete_fcurve_key(fcu, 1, 1); + BKE_fcurve_delete_key(fcu, 1); + BKE_fcurve_handles_recalc(fcu); break; } @@ -1683,7 +1685,8 @@ static bool delete_keyframe_fcurve(AnimData *adt, FCurve *fcu, float cfra) i = BKE_fcurve_bezt_binarysearch_index(fcu->bezt, cfra, fcu->totvert, &found); if (found) { /* delete the key at the index (will sanity check + do recalc afterwards) */ - delete_fcurve_key(fcu, i, 1); + BKE_fcurve_delete_key(fcu, i); + BKE_fcurve_handles_recalc(fcu); /* Only delete curve too if it won't be doing anything anymore */ if (BKE_fcurve_is_empty(fcu)) { @@ -2709,7 +2712,8 @@ static int delete_key_button_exec(bContext *C, wmOperator *op) i = BKE_fcurve_bezt_binarysearch_index(fcu->bezt, cfra, fcu->totvert, &found); if (found) { /* delete the key at the index (will sanity check + do recalc afterwards) */ - delete_fcurve_key(fcu, i, 1); + BKE_fcurve_delete_key(fcu, i); + BKE_fcurve_handles_recalc(fcu); changed = true; } } diff --git a/source/blender/editors/armature/pose_lib.c b/source/blender/editors/armature/pose_lib.c index a6742fbe2bf..ff187a52154 100644 --- a/source/blender/editors/armature/pose_lib.c +++ b/source/blender/editors/armature/pose_lib.c @@ -24,6 +24,7 @@ #include "BKE_action.h" #include "BKE_animsys.h" #include "BKE_armature.h" +#include "BKE_fcurve.h" #include "BKE_idprop.h" #include "BKE_lib_id.h" #include "BKE_main.h" @@ -615,7 +616,8 @@ static int poselib_remove_exec(bContext *C, wmOperator *op) for (i = 0, bezt = fcu->bezt; i < fcu->totvert; i++, bezt++) { /* check if remove */ if (IS_EQF(bezt->vec[1][0], (float)marker->frame)) { - delete_fcurve_key(fcu, i, 1); + BKE_fcurve_delete_key(fcu, i); + BKE_fcurve_handles_recalc(fcu); break; } } diff --git a/source/blender/editors/gpencil/gpencil_convert.c b/source/blender/editors/gpencil/gpencil_convert.c index 25b5466e260..e02a82f4555 100644 --- a/source/blender/editors/gpencil/gpencil_convert.c +++ b/source/blender/editors/gpencil/gpencil_convert.c @@ -588,7 +588,7 @@ static void gpencil_stroke_path_animation(bContext *C, } /* As we used INSERTKEY_FAST mode, we need to recompute all curve's handles now */ - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL); diff --git a/source/blender/editors/include/ED_keyframes_edit.h b/source/blender/editors/include/ED_keyframes_edit.h index 9596c3a7fed..1d63e01c84b 100644 --- a/source/blender/editors/include/ED_keyframes_edit.h +++ b/source/blender/editors/include/ED_keyframes_edit.h @@ -388,9 +388,6 @@ bool keyframe_region_circle_test(const KeyframeEdit_CircleData *data_circle, con /* ************************************************ */ /* Destructive Editing API (keyframes_general.c) */ -void delete_fcurve_key(struct FCurve *fcu, int index, bool do_recalc); -bool delete_fcurve_keys(struct FCurve *fcu); -void clear_fcurve_keys(struct FCurve *fcu); bool duplicate_fcurve_keys(struct FCurve *fcu); float get_default_rna_value(struct FCurve *fcu, struct PropertyRNA *prop, struct PointerRNA *ptr); diff --git a/source/blender/editors/space_action/action_edit.c b/source/blender/editors/space_action/action_edit.c index e97b666810c..23c92cbdaa0 100644 --- a/source/blender/editors/space_action/action_edit.c +++ b/source/blender/editors/space_action/action_edit.c @@ -1006,7 +1006,7 @@ static bool delete_action_keys(bAnimContext *ac) AnimData *adt = ale->adt; /* delete selected keyframes only */ - changed = delete_fcurve_keys(fcu); + changed = BKE_fcurve_delete_keys_selected(fcu); /* Only delete curve too if it won't be doing anything anymore */ if (BKE_fcurve_is_empty(fcu)) { @@ -1473,7 +1473,7 @@ static void sethandles_action_keys(bAnimContext *ac, short mode) /* any selected keyframes for editing? */ if (ANIM_fcurve_keyframes_loop(NULL, fcu, NULL, sel_cb, NULL)) { /* change type of selected handles */ - ANIM_fcurve_keyframes_loop(NULL, fcu, NULL, edit_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(NULL, fcu, NULL, edit_cb, BKE_fcurve_handles_recalc); ale->update |= ANIM_UPDATE_DEFAULT; } @@ -1790,11 +1790,11 @@ static void snap_action_keys(bAnimContext *ac, short mode) } else if (adt) { ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 0); - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, BKE_fcurve_handles_recalc); ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 0); } else { - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, BKE_fcurve_handles_recalc); } ale->update |= ANIM_UPDATE_DEFAULT; @@ -1914,11 +1914,11 @@ static void mirror_action_keys(bAnimContext *ac, short mode) } else if (adt) { ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 0); - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, BKE_fcurve_handles_recalc); ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 0); } else { - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, BKE_fcurve_handles_recalc); } ale->update |= ANIM_UPDATE_DEFAULT; diff --git a/source/blender/editors/space_graph/graph_buttons.c b/source/blender/editors/space_graph/graph_buttons.c index bc2705df314..3b8c6cbd1d0 100644 --- a/source/blender/editors/space_graph/graph_buttons.c +++ b/source/blender/editors/space_graph/graph_buttons.c @@ -277,7 +277,7 @@ static void graphedit_activekey_update_cb(bContext *UNUSED(C), /* make sure F-Curve and its handles are still valid after this editing */ sort_time_fcurve(fcu); - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); } /* update callback for active keyframe properties - handle-editing wrapper */ diff --git a/source/blender/editors/space_graph/graph_edit.c b/source/blender/editors/space_graph/graph_edit.c index 7e8bca88744..64a3c603e73 100644 --- a/source/blender/editors/space_graph/graph_edit.c +++ b/source/blender/editors/space_graph/graph_edit.c @@ -723,7 +723,7 @@ static bool delete_graph_keys(bAnimContext *ac) bool changed; /* Delete selected keyframes only. */ - changed = delete_fcurve_keys(fcu); + changed = BKE_fcurve_delete_keys_selected(fcu); if (changed) { ale->update |= ANIM_UPDATE_DEFAULT; @@ -1488,7 +1488,7 @@ static void setipo_graph_keys(bAnimContext *ac, short mode) * Currently that's not necessary here. */ for (ale = anim_data.first; ale; ale = ale->next) { - ANIM_fcurve_keyframes_loop(NULL, ale->key_data, NULL, set_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(NULL, ale->key_data, NULL, set_cb, BKE_fcurve_handles_recalc); ale->update |= ANIM_UPDATE_DEFAULT_NOHANDLES; } @@ -1566,7 +1566,7 @@ static void seteasing_graph_keys(bAnimContext *ac, short mode) * Currently that's not necessary here. */ for (ale = anim_data.first; ale; ale = ale->next) { - ANIM_fcurve_keyframes_loop(NULL, ale->key_data, NULL, set_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(NULL, ale->key_data, NULL, set_cb, BKE_fcurve_handles_recalc); ale->update |= ANIM_UPDATE_DEFAULT_NOHANDLES; } @@ -1649,7 +1649,7 @@ static void sethandles_graph_keys(bAnimContext *ac, short mode) /* Any selected keyframes for editing? */ if (ANIM_fcurve_keyframes_loop(NULL, fcu, NULL, sel_cb, NULL)) { /* Change type of selected handles. */ - ANIM_fcurve_keyframes_loop(NULL, fcu, NULL, edit_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(NULL, fcu, NULL, edit_cb, BKE_fcurve_handles_recalc); ale->update |= ANIM_UPDATE_DEFAULT; } @@ -2295,11 +2295,11 @@ static void snap_graph_keys(bAnimContext *ac, short mode) /* Perform snapping. */ if (adt) { ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 0); - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, BKE_fcurve_handles_recalc); ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 0); } else { - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, BKE_fcurve_handles_recalc); } ale->update |= ANIM_UPDATE_DEFAULT; @@ -2555,11 +2555,11 @@ static void mirror_graph_keys(bAnimContext *ac, short mode) /* Perform actual mirroring. */ if (adt) { ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 0); - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, BKE_fcurve_handles_recalc); ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 0); } else { - ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, calchandles_fcurve); + ANIM_fcurve_keyframes_loop(&ked, ale->key_data, NULL, edit_cb, BKE_fcurve_handles_recalc); } ale->update |= ANIM_UPDATE_DEFAULT; diff --git a/source/blender/editors/space_nla/nla_edit.c b/source/blender/editors/space_nla/nla_edit.c index cc45c86ddcd..42509f17b92 100644 --- a/source/blender/editors/space_nla/nla_edit.c +++ b/source/blender/editors/space_nla/nla_edit.c @@ -2205,7 +2205,7 @@ static int nlaedit_apply_scale_exec(bContext *C, wmOperator *UNUSED(op)) * applying this scaling */ ked.data = strip; ANIM_animchanneldata_keyframes_loop( - &ked, ac.ads, strip->act, ALE_ACT, NULL, bezt_apply_nlamapping, calchandles_fcurve); + &ked, ac.ads, strip->act, ALE_ACT, NULL, bezt_apply_nlamapping, BKE_fcurve_handles_recalc); /* clear scale of strip now that it has been applied, * and recalculate the extents of the action now that it has been scaled diff --git a/source/blender/editors/transform/transform_convert.c b/source/blender/editors/transform/transform_convert.c index d9b971c5478..a23689166c4 100644 --- a/source/blender/editors/transform/transform_convert.c +++ b/source/blender/editors/transform/transform_convert.c @@ -769,7 +769,7 @@ void posttrans_fcurve_clean(FCurve *fcu, const int sel_flag, const bool use_hand } else { /* Delete Keyframe */ - delete_fcurve_key(fcu, i, 0); + BKE_fcurve_delete_key(fcu, i); } /* Update count of how many we've deleted @@ -779,7 +779,7 @@ void posttrans_fcurve_clean(FCurve *fcu, const int sel_flag, const bool use_hand } else { /* Always delete - Unselected keys don't matter */ - delete_fcurve_key(fcu, i, 0); + BKE_fcurve_delete_key(fcu, i); } /* Stop the RK search... we've found our match now */ diff --git a/source/blender/editors/transform/transform_convert_graph.c b/source/blender/editors/transform/transform_convert_graph.c index d93fff72de6..b57f5b78939 100644 --- a/source/blender/editors/transform/transform_convert_graph.c +++ b/source/blender/editors/transform/transform_convert_graph.c @@ -934,7 +934,7 @@ void recalcData_graphedit(TransInfo *t) dosort++; } else { - calchandles_fcurve_ex(fcu, BEZT_FLAG_TEMP_TAG); + BKE_fcurve_handles_recalc_ex(fcu, BEZT_FLAG_TEMP_TAG); } /* set refresh tags for objects using this animation, diff --git a/source/blender/io/collada/AnimationImporter.cpp b/source/blender/io/collada/AnimationImporter.cpp index 923a392dbde..cc91c3eeac9 100644 --- a/source/blender/io/collada/AnimationImporter.cpp +++ b/source/blender/io/collada/AnimationImporter.cpp @@ -64,7 +64,7 @@ void AnimationImporter::add_bezt(FCurve *fcu, bez.f1 = bez.f2 = bez.f3 = SELECT; bez.h1 = bez.h2 = HD_AUTO; insert_bezt_fcurve(fcu, &bez, INSERTKEY_NOFLAGS); - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); } void AnimationImporter::animation_to_fcurves(COLLADAFW::AnimationCurve *curve) @@ -132,7 +132,7 @@ void AnimationImporter::animation_to_fcurves(COLLADAFW::AnimationCurve *curve) insert_bezt_fcurve(fcu, &bez, INSERTKEY_NOFLAGS); } - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); fcurves.push_back(fcu); unused_curves.push_back(fcu); diff --git a/source/blender/io/collada/BCAnimationCurve.cpp b/source/blender/io/collada/BCAnimationCurve.cpp index fbb2ba499a5..04a7a81c0a6 100644 --- a/source/blender/io/collada/BCAnimationCurve.cpp +++ b/source/blender/io/collada/BCAnimationCurve.cpp @@ -96,7 +96,7 @@ void BCAnimationCurve::create_bezt(float frame, float output) bez.f1 = bez.f2 = bez.f3 = SELECT; bez.h1 = bez.h2 = HD_AUTO; insert_bezt_fcurve(fcu, &bez, INSERTKEY_NOFLAGS); - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); } BCAnimationCurve::~BCAnimationCurve() diff --git a/source/blender/makesrna/intern/rna_fcurve.c b/source/blender/makesrna/intern/rna_fcurve.c index ac8aebd2fdd..461536ffb8a 100644 --- a/source/blender/makesrna/intern/rna_fcurve.c +++ b/source/blender/makesrna/intern/rna_fcurve.c @@ -612,7 +612,7 @@ static void rna_tag_animation_update(Main *bmain, ID *id) static void rna_FCurve_update_data_ex(ID *id, FCurve *fcu, Main *bmain) { sort_time_fcurve(fcu); - calchandles_fcurve(fcu); + BKE_fcurve_handles_recalc(fcu); rna_tag_animation_update(bmain, id); } @@ -752,7 +752,7 @@ static void rna_FModifier_update(Main *bmain, Scene *UNUSED(scene), PointerRNA * FModifier *fcm = (FModifier *)ptr->data; if (fcm->curve && fcm->type == FMODIFIER_TYPE_CYCLES) { - calchandles_fcurve(fcm->curve); + BKE_fcurve_handles_recalc(fcm->curve); } rna_tag_animation_update(bmain, id); @@ -1021,9 +1021,13 @@ static void rna_FKeyframe_points_remove( return; } - delete_fcurve_key(fcu, index, !do_fast); + BKE_fcurve_delete_key(fcu, index); RNA_POINTER_INVALIDATE(bezt_ptr); + if (!do_fast) { + BKE_fcurve_handles_recalc(fcu); + } + rna_tag_animation_update(bmain, id); } diff --git a/source/blender/python/intern/bpy_rna_anim.c b/source/blender/python/intern/bpy_rna_anim.c index fb744432d4b..f25e9d0bbbc 100644 --- a/source/blender/python/intern/bpy_rna_anim.c +++ b/source/blender/python/intern/bpy_rna_anim.c @@ -487,7 +487,8 @@ PyObject *pyrna_struct_keyframe_delete(BPy_StructRNA *self, PyObject *args, PyOb i = BKE_fcurve_bezt_binarysearch_index(fcu->bezt, cfra, fcu->totvert, &found); if (found) { /* delete the key at the index (will sanity check + do recalc afterwards) */ - delete_fcurve_key(fcu, i, 1); + BKE_fcurve_delete_key(fcu, i); + BKE_fcurve_handles_recalc(fcu); result = true; } } -- cgit v1.2.3 From 9d73bbd9668aaa9fd407780309f5b63b05655fcd Mon Sep 17 00:00:00 2001 From: Damien Picard Date: Thu, 14 Jul 2022 10:28:52 +0200 Subject: UI: translate tooltips coming from menu descriptions Many menus get their labels exported to the .po file, but then are not actually translated in the UI. Before: {F13283752} After: {F13283750} Reviewed By: mont29 Differential Revision: https://developer.blender.org/D15417 --- source/blender/editors/interface/interface.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/editors/interface/interface.cc b/source/blender/editors/interface/interface.cc index 3f623566807..c0df193de87 100644 --- a/source/blender/editors/interface/interface.cc +++ b/source/blender/editors/interface/interface.cc @@ -6623,7 +6623,7 @@ void UI_but_string_info_get(bContext *C, uiBut *but, ...) MenuType *mt = UI_but_menutype_get(but); if (mt) { if (type == BUT_GET_RNA_LABEL) { - tmp = BLI_strdup(mt->label); + tmp = BLI_strdup(CTX_TIP_(mt->translation_context, mt->label)); } else { /* Not all menus are from Python. */ @@ -6653,7 +6653,7 @@ void UI_but_string_info_get(bContext *C, uiBut *but, ...) PanelType *pt = UI_but_paneltype_get(but); if (pt) { if (type == BUT_GET_RNA_LABEL) { - tmp = BLI_strdup(pt->label); + tmp = BLI_strdup(CTX_TIP_(pt->translation_context, pt->label)); } else { /* Not all panels are from Python. */ -- cgit v1.2.3 From 44e530e1b107fd0d91f472f9a58642ab59fd5422 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Thu, 14 Jul 2022 10:35:46 +0200 Subject: Geometry Nodes: fix face corner to edge attribute interpolation Looks like this was wrong all the time.. Luckily, this conversion is not very common. Found when testing D15274. --- source/blender/blenkernel/intern/geometry_component_mesh.cc | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc index cf6681a69be..83bf2a5d27b 100644 --- a/source/blender/blenkernel/intern/geometry_component_mesh.cc +++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc @@ -298,12 +298,14 @@ static void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh, const MPoly &poly = mesh.mpoly[poly_index]; /* For every edge, mix values from the two adjacent corners (the current and next corner). */ - for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { - const int loop_index_next = (loop_index + 1) % poly.totloop; - const MLoop &loop = mesh.mloop[loop_index]; + for (const int i : IndexRange(poly.totloop)) { + const int next_i = (i + 1) % poly.totloop; + const int loop_i = poly.loopstart + i; + const int next_loop_i = poly.loopstart + next_i; + const MLoop &loop = mesh.mloop[loop_i]; const int edge_index = loop.e; - mixer.mix_in(edge_index, old_values[loop_index]); - mixer.mix_in(edge_index, old_values[loop_index_next]); + mixer.mix_in(edge_index, old_values[loop_i]); + mixer.mix_in(edge_index, old_values[next_loop_i]); } } -- cgit v1.2.3 From bcdce4ffd848d5b47c467bedeb221645ec033f20 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Thu, 14 Jul 2022 10:47:26 +0200 Subject: Geometry Nodes: fix face corner to edge boolean interpolation This is a follow up for rB44e530e1b107fd0d91f472f9a58642ab59fd5422 which did not fix the function that interpolates boolean attributes. --- source/blender/blenkernel/intern/geometry_component_mesh.cc | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc index 83bf2a5d27b..436868ba375 100644 --- a/source/blender/blenkernel/intern/geometry_component_mesh.cc +++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc @@ -327,13 +327,16 @@ void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh, for (const int poly_index : IndexRange(mesh.totpoly)) { const MPoly &poly = mesh.mpoly[poly_index]; - for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { - const int loop_index_next = (loop_index == poly.totloop) ? poly.loopstart : (loop_index + 1); - const MLoop &loop = mesh.mloop[loop_index]; + for (const int i : IndexRange(poly.totloop)) { + const int next_i = (i + 1) % poly.totloop; + const int loop_i = poly.loopstart + i; + const int next_loop_i = poly.loopstart + next_i; + const MLoop &loop = mesh.mloop[loop_i]; const int edge_index = loop.e; + loose_edges[edge_index] = false; - if (!old_values[loop_index] || !old_values[loop_index_next]) { + if (!old_values[loop_i] || !old_values[next_loop_i]) { r_values[edge_index] = false; } } -- cgit v1.2.3 From c8a07ef66311a31cc45901717845139ae0682f2f Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Thu, 14 Jul 2022 11:32:01 +0200 Subject: BLI: fix finding indices from virtual array The sorting of index vectors assumed that all vectors have at least one element. Now this is checked for more explicitely. --- source/blender/blenlib/intern/index_mask.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/blender/blenlib/intern/index_mask.cc b/source/blender/blenlib/intern/index_mask.cc index f3590e4a41c..e9af183d60d 100644 --- a/source/blender/blenlib/intern/index_mask.cc +++ b/source/blender/blenlib/intern/index_mask.cc @@ -142,6 +142,7 @@ IndexMask find_indices_based_on_predicate__merge( int64_t result_mask_size = 0; for (Vector> &local_sub_masks : sub_masks) { for (Vector &sub_mask : local_sub_masks) { + BLI_assert(!sub_mask.is_empty()); all_vectors.append(&sub_mask); result_mask_size += sub_mask.size(); } @@ -232,7 +233,9 @@ IndexMask find_indices_from_virtual_array(const IndexMask indices_to_check, } } }); - sub_masks.local().append(std::move(masked_indices)); + if (!masked_indices.is_empty()) { + sub_masks.local().append(std::move(masked_indices)); + } }); return detail::find_indices_based_on_predicate__merge(indices_to_check, sub_masks, r_indices); -- cgit v1.2.3 From 96cc6030375b9173ae6b234a12d3af25bfbc0aa6 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Thu, 14 Jul 2022 11:49:28 +0200 Subject: Geometry Nodes: update curve type counts after realizing instances The type counts have to be updated eagerly. This was missing from the realize-instances code before, leading to bugs further down the line. --- source/blender/geometry/intern/realize_instances.cc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/source/blender/geometry/intern/realize_instances.cc b/source/blender/geometry/intern/realize_instances.cc index 25bf00c5783..66b856ee0c4 100644 --- a/source/blender/geometry/intern/realize_instances.cc +++ b/source/blender/geometry/intern/realize_instances.cc @@ -1295,6 +1295,15 @@ static void execute_realize_curve_tasks(const RealizeInstancesOptions &options, } }); + /* Type counts have to be updated eagerly. */ + dst_curves.runtime->type_counts.fill(0); + for (const RealizeCurveTask &task : tasks) { + for (const int i : IndexRange(CURVE_TYPES_NUM)) { + dst_curves.runtime->type_counts[i] += + task.curve_info->curves->geometry.runtime->type_counts[i]; + } + } + /* Tag modified attributes. */ for (GSpanAttributeWriter &dst_attribute : dst_attribute_writers) { dst_attribute.finish(); -- cgit v1.2.3 From cb62095c1c8afc4e2b5f28904e3ac4cbd47503b2 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 14 Jul 2022 20:07:06 +1000 Subject: Correct error with IME from d6fef73ef110eb43756b7b87c2cba80abae3b39f --- source/blender/editors/interface/interface_handlers.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 2ad2cd15c43..2496136883d 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -3172,6 +3172,13 @@ static bool ui_textedit_insert_buf(uiBut *but, return changed; } +static bool ui_textedit_insert_ascii(uiBut *but, uiHandleButtonData *data, const char ascii) +{ + BLI_assert(isascii(ascii)); + const char buf[2] = {ascii, '\0'}; + return ui_textedit_insert_buf(but, data, buf, sizeof(buf) - 1); +} + static void ui_textedit_move(uiBut *but, uiHandleButtonData *data, eStrCursorJumpDirection direction, @@ -3934,6 +3941,9 @@ static void ui_do_but_textedit( else if (event->type == WM_IME_COMPOSITE_END) { changed = true; } +#else + /* Prevent the function from being unused. */ + (void)ui_textedit_insert_ascii; #endif if (changed) { -- cgit v1.2.3 From 9024ac31be5c883b772c50d81d001cdd8fdb3388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20T=C3=B6nne?= Date: Thu, 14 Jul 2022 11:17:59 +0100 Subject: Fix curve drawing crash after changing geometry nodes output. Using geometry nodes with attributes on a curve object and changing the output is crashing. This is because the `render_mutex` in the curve drawing cache is cleared after changes in `curves_batch_cache_init` and set to null. The cache isn't actually needed currently because all draw updates are single-threaded, but the new `drw_attributes_merge` function still tries to access it (this seems to be tolerated on Linux platforms but crashes on Windows). Make sure the render_mutex is always initialized after (re-)initializing the cache. --- source/blender/draw/intern/draw_cache_impl_curves.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/blender/draw/intern/draw_cache_impl_curves.cc b/source/blender/draw/intern/draw_cache_impl_curves.cc index d5f188402d6..1d3d6222f8f 100644 --- a/source/blender/draw/intern/draw_cache_impl_curves.cc +++ b/source/blender/draw/intern/draw_cache_impl_curves.cc @@ -75,13 +75,14 @@ static void curves_batch_cache_init(Curves &curves) if (!cache) { cache = MEM_cnew(__func__); - BLI_mutex_init(&cache->render_mutex); curves.batch_cache = cache; } else { memset(cache, 0, sizeof(*cache)); } + BLI_mutex_init(&cache->render_mutex); + cache->is_dirty = false; } -- cgit v1.2.3 From 6cd30d5ff04afd690e396f4aff14b38a6bc9e466 Mon Sep 17 00:00:00 2001 From: Aras Pranckevicius Date: Thu, 14 Jul 2022 13:41:43 +0300 Subject: IDManagement: add more ID naming tests As part of a larger change (https://developer.blender.org/D14162), adding more test coverage for existing functionality separately. New tests: - ids_sorted_by_default - ids_sorted_by_default_with_libraries - name_too_long_handling - create_equivalent_numeric_suffixes - zero_suffix_is_never_assigned - remove_after_dup_get_original_name - name_number_suffix_assignment - renames_with_duplicates - names_are_unique_per_id_type --- source/blender/blenkernel/intern/lib_id_test.cc | 316 +++++++++++++++++++++--- 1 file changed, 275 insertions(+), 41 deletions(-) diff --git a/source/blender/blenkernel/intern/lib_id_test.cc b/source/blender/blenkernel/intern/lib_id_test.cc index d6101d71be5..96718b3a65b 100644 --- a/source/blender/blenkernel/intern/lib_id_test.cc +++ b/source/blender/blenkernel/intern/lib_id_test.cc @@ -18,19 +18,18 @@ namespace blender::bke::tests { struct LibIDMainSortTestContext { - Main *bmain; -}; + Main *bmain = nullptr; -static void test_lib_id_main_sort_init(LibIDMainSortTestContext *ctx) -{ - BKE_idtype_init(); - ctx->bmain = BKE_main_new(); -} - -static void test_lib_id_main_sort_free(LibIDMainSortTestContext *ctx) -{ - BKE_main_free(ctx->bmain); -} + LibIDMainSortTestContext() + { + BKE_idtype_init(); + bmain = BKE_main_new(); + } + ~LibIDMainSortTestContext() + { + BKE_main_free(bmain); + } +}; static void test_lib_id_main_sort_check_order(std::initializer_list list) { @@ -47,8 +46,7 @@ static void test_lib_id_main_sort_check_order(std::initializer_list list) TEST(lib_id_main_sort, local_ids_1) { - LibIDMainSortTestContext ctx = {nullptr}; - test_lib_id_main_sort_init(&ctx); + LibIDMainSortTestContext ctx; EXPECT_TRUE(BLI_listbase_is_empty(&ctx.bmain->libraries)); ID *id_c = static_cast(BKE_id_new(ctx.bmain, ID_OB, "OB_C")); @@ -57,14 +55,22 @@ TEST(lib_id_main_sort, local_ids_1) EXPECT_TRUE(ctx.bmain->objects.first == id_a); EXPECT_TRUE(ctx.bmain->objects.last == id_c); test_lib_id_main_sort_check_order({id_a, id_b, id_c}); +} - test_lib_id_main_sort_free(&ctx); +static void change_lib(Main *bmain, ID *id, Library *lib) +{ + id->lib = lib; +} + +static void change_name(Main *bmain, ID *id, const char *name) +{ + BLI_strncpy(id->name + 2, name, MAX_NAME); + BKE_id_new_name_validate(&bmain->objects, id, nullptr, true); } TEST(lib_id_main_sort, linked_ids_1) { - LibIDMainSortTestContext ctx = {nullptr}; - test_lib_id_main_sort_init(&ctx); + LibIDMainSortTestContext ctx; EXPECT_TRUE(BLI_listbase_is_empty(&ctx.bmain->libraries)); Library *lib_a = static_cast(BKE_id_new(ctx.bmain, ID_LI, "LI_A")); @@ -92,14 +98,11 @@ TEST(lib_id_main_sort, linked_ids_1) EXPECT_TRUE(ctx.bmain->objects.first == id_c); EXPECT_TRUE(ctx.bmain->objects.last == id_b); test_lib_id_main_sort_check_order({id_c, id_a, id_b}); - - test_lib_id_main_sort_free(&ctx); } TEST(lib_id_main_unique_name, local_ids_1) { - LibIDMainSortTestContext ctx = {nullptr}; - test_lib_id_main_sort_init(&ctx); + LibIDMainSortTestContext ctx; EXPECT_TRUE(BLI_listbase_is_empty(&ctx.bmain->libraries)); ID *id_c = static_cast(BKE_id_new(ctx.bmain, ID_OB, "OB_C")); @@ -107,21 +110,18 @@ TEST(lib_id_main_unique_name, local_ids_1) ID *id_b = static_cast(BKE_id_new(ctx.bmain, ID_OB, "OB_B")); test_lib_id_main_sort_check_order({id_a, id_b, id_c}); - BLI_strncpy(id_c->name, id_a->name, sizeof(id_c->name)); - BKE_id_new_name_validate(&ctx.bmain->objects, id_c, nullptr, false); - EXPECT_TRUE(strcmp(id_c->name + 2, "OB_A.001") == 0); - EXPECT_TRUE(strcmp(id_a->name + 2, "OB_A") == 0); + change_name(ctx.bmain, id_c, "OB_A"); + + EXPECT_STREQ(id_c->name + 2, "OB_A.001"); + EXPECT_STREQ(id_a->name + 2, "OB_A"); EXPECT_TRUE(ctx.bmain->objects.first == id_a); EXPECT_TRUE(ctx.bmain->objects.last == id_b); test_lib_id_main_sort_check_order({id_a, id_c, id_b}); - - test_lib_id_main_sort_free(&ctx); } TEST(lib_id_main_unique_name, linked_ids_1) { - LibIDMainSortTestContext ctx = {nullptr}; - test_lib_id_main_sort_init(&ctx); + LibIDMainSortTestContext ctx; EXPECT_TRUE(BLI_listbase_is_empty(&ctx.bmain->libraries)); Library *lib_a = static_cast(BKE_id_new(ctx.bmain, ID_LI, "LI_A")); @@ -130,29 +130,263 @@ TEST(lib_id_main_unique_name, linked_ids_1) ID *id_a = static_cast(BKE_id_new(ctx.bmain, ID_OB, "OB_A")); ID *id_b = static_cast(BKE_id_new(ctx.bmain, ID_OB, "OB_B")); - id_a->lib = lib_a; + change_lib(ctx.bmain, id_a, lib_a); id_sort_by_name(&ctx.bmain->objects, id_a, nullptr); - id_b->lib = lib_a; + change_lib(ctx.bmain, id_b, lib_a); id_sort_by_name(&ctx.bmain->objects, id_b, nullptr); - BLI_strncpy(id_b->name, id_a->name, sizeof(id_b->name)); - BKE_id_new_name_validate(&ctx.bmain->objects, id_b, nullptr, true); - EXPECT_TRUE(strcmp(id_b->name + 2, "OB_A.001") == 0); - EXPECT_TRUE(strcmp(id_a->name + 2, "OB_A") == 0); + + change_name(ctx.bmain, id_b, "OB_A"); + EXPECT_STREQ(id_b->name + 2, "OB_A.001"); + EXPECT_STREQ(id_a->name + 2, "OB_A"); EXPECT_TRUE(ctx.bmain->objects.first == id_c); EXPECT_TRUE(ctx.bmain->objects.last == id_b); test_lib_id_main_sort_check_order({id_c, id_a, id_b}); - id_b->lib = lib_b; + change_lib(ctx.bmain, id_b, lib_b); id_sort_by_name(&ctx.bmain->objects, id_b, nullptr); - BLI_strncpy(id_b->name, id_a->name, sizeof(id_b->name)); - BKE_id_new_name_validate(&ctx.bmain->objects, id_b, nullptr, true); - EXPECT_TRUE(strcmp(id_b->name + 2, "OB_A") == 0); - EXPECT_TRUE(strcmp(id_a->name + 2, "OB_A") == 0); + change_name(ctx.bmain, id_b, "OB_A"); + EXPECT_STREQ(id_b->name + 2, "OB_A"); + EXPECT_STREQ(id_a->name + 2, "OB_A"); EXPECT_TRUE(ctx.bmain->objects.first == id_c); EXPECT_TRUE(ctx.bmain->objects.last == id_b); test_lib_id_main_sort_check_order({id_c, id_a, id_b}); +} + +TEST(lib_id_main_unique_name, ids_sorted_by_default) +{ + LibIDMainSortTestContext ctx; + + ID *id_foo = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); + ID *id_bar = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Bar")); + ID *id_baz = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Baz")); + ID *id_yes = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Yes")); + test_lib_id_main_sort_check_order({id_bar, id_baz, id_foo, id_yes}); +} + +static ID *add_id_in_library(Main *bmain, const char *name, Library *lib) +{ + ID *id = static_cast(BKE_id_new(bmain, ID_OB, name)); + id->lib = lib; + id_sort_by_name(&bmain->objects, id, nullptr); + return id; +} + +TEST(lib_id_main_unique_name, ids_sorted_by_default_with_libraries) +{ + LibIDMainSortTestContext ctx; + + Library *lib_one = static_cast(BKE_id_new(ctx.bmain, ID_LI, "LibOne")); + Library *lib_two = static_cast(BKE_id_new(ctx.bmain, ID_LI, "LibTwo")); + + ID *id_foo = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); + ID *id_bar = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Bar")); + + ID *id_l1c = add_id_in_library(ctx.bmain, "C", lib_one); + ID *id_l2b = add_id_in_library(ctx.bmain, "B", lib_two); + ID *id_l1a = add_id_in_library(ctx.bmain, "A", lib_one); + + ID *id_baz = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Baz")); + ID *id_yes = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Yes")); + + test_lib_id_main_sort_check_order({id_bar, id_baz, id_foo, id_yes, id_l1a, id_l1c, id_l2b}); +} + +TEST(lib_id_main_unique_name, name_too_long_handling) +{ + LibIDMainSortTestContext ctx; + const char *name_a = "Long_Name_That_Does_Not_Fit_Into_Max_Name_Limit_And_Should_Get_Truncated"; + const char *name_b = "Another_Long_Name_That_Does_Not_Fit_And_Has_A_Number_Suffix.123456"; + const char *name_c = "Name_That_Has_Too_Long_Number_Suffix.1234567890"; + + ID *id_a = static_cast(BKE_id_new(ctx.bmain, ID_OB, name_a)); + ID *id_b = static_cast(BKE_id_new(ctx.bmain, ID_OB, name_b)); + ID *id_c = static_cast(BKE_id_new(ctx.bmain, ID_OB, name_c)); + + EXPECT_STREQ(id_a->name + 2, "Long_Name_That_Does_Not_Fit_Into_Max_Name_Limit_And_Should_Get_"); + EXPECT_STREQ(id_b->name + 2, "Another_Long_Name_That_Does_Not_Fit_And_Has_A_Number_Suffix.123"); + EXPECT_STREQ(id_c->name + 2, "Name_That_Has_Too_Long_Number_Suffix.1234567890"); /* Unchanged */ +} + +TEST(lib_id_main_unique_name, create_equivalent_numeric_suffixes) +{ + LibIDMainSortTestContext ctx; + + /* Create names where many of their numeric suffixes are + * the same number, yet the names are different and thus + * should be allowed as-is. */ + ID *id_a = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.123")); + ID *id_b = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.000")); + ID *id_c = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.003")); + ID *id_d = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.3")); + ID *id_e = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.0")); + ID *id_f = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.")); + ID *id_g = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.0123")); + ID *id_h = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); + ID *id_i = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo..")); + ID *id_j = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo..001")); + ID *id_k = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo..000")); + + EXPECT_STREQ(id_a->name + 2, "Foo.123"); + EXPECT_STREQ(id_b->name + 2, "Foo.000"); + EXPECT_STREQ(id_c->name + 2, "Foo.003"); + EXPECT_STREQ(id_d->name + 2, "Foo.3"); + EXPECT_STREQ(id_e->name + 2, "Foo.0"); + EXPECT_STREQ(id_f->name + 2, "Foo."); + EXPECT_STREQ(id_g->name + 2, "Foo.0123"); + EXPECT_STREQ(id_h->name + 2, "Foo"); + EXPECT_STREQ(id_i->name + 2, "Foo.."); + EXPECT_STREQ(id_j->name + 2, "Foo..001"); + EXPECT_STREQ(id_k->name + 2, "Foo..000"); + + /* Now create their exact duplicates again, and check what happens. */ + id_a = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.123")); + id_b = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.000")); + id_c = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.003")); + id_d = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.3")); + id_e = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.0")); + id_f = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.")); + id_g = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.0123")); + id_h = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); + id_i = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo..")); + id_j = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo..001")); + id_k = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo..000")); + + EXPECT_STREQ(id_a->name + 2, "Foo.001"); + EXPECT_STREQ(id_b->name + 2, "Foo.002"); + EXPECT_STREQ(id_c->name + 2, "Foo.004"); + EXPECT_STREQ(id_d->name + 2, "Foo.005"); + EXPECT_STREQ(id_e->name + 2, "Foo.006"); + EXPECT_STREQ(id_f->name + 2, "Foo..002"); + EXPECT_STREQ(id_g->name + 2, "Foo.007"); + EXPECT_STREQ(id_h->name + 2, "Foo.008"); + EXPECT_STREQ(id_i->name + 2, "Foo...001"); + EXPECT_STREQ(id_j->name + 2, "Foo..003"); + EXPECT_STREQ(id_k->name + 2, "Foo..004"); +} + +TEST(lib_id_main_unique_name, zero_suffix_is_never_assigned) +{ + LibIDMainSortTestContext ctx; + + /* Creating these should assign 002 to the first one, but the next + * ones should start numbers starting from 1: 001 and 003. */ + ID *id_002 = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.002")); + ID *id_001 = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.002")); + ID *id_003 = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.002")); + + EXPECT_STREQ(id_002->name + 2, "Foo.002"); + EXPECT_STREQ(id_001->name + 2, "Foo.001"); + EXPECT_STREQ(id_003->name + 2, "Foo.003"); +} + +TEST(lib_id_main_unique_name, remove_after_dup_get_original_name) +{ + LibIDMainSortTestContext ctx; + + ID *id_a = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); + ID *id_b = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); + + EXPECT_STREQ(id_a->name + 2, "Foo"); + EXPECT_STREQ(id_b->name + 2, "Foo.001"); + BKE_id_free(ctx.bmain, id_a); + + id_a = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); + EXPECT_STREQ(id_a->name + 2, "Foo"); +} + +TEST(lib_id_main_unique_name, name_number_suffix_assignment) +{ + LibIDMainSortTestContext ctx; + + /* Create <1k objects first. */ + const int total_object_count = 1200; + ID *ids[total_object_count] = {}; + for (int i = 0; i < total_object_count / 2; ++i) { + ids[i] = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); + } + + /* They should get assigned sequential numeric suffixes. */ + EXPECT_STREQ(ids[0]->name + 2, "Foo"); + EXPECT_STREQ(ids[1]->name + 2, "Foo.001"); + EXPECT_STREQ(ids[total_object_count / 2 - 1]->name + 2, "Foo.599"); + + /* Free some of the objects. */ + BKE_id_free(ctx.bmain, ids[10]); + BKE_id_free(ctx.bmain, ids[20]); + BKE_id_free(ctx.bmain, ids[30]); + + /* Create objects again; they should get suffixes that were just free'd up. */ + ID *id_010 = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); + EXPECT_STREQ(id_010->name + 2, "Foo.010"); + ID *id_020 = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.123")); + EXPECT_STREQ(id_020->name + 2, "Foo.020"); + /* Suffixes >1k do not get the "use the most proper free one" treatment. */ + ID *id_2000 = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.2000")); + EXPECT_STREQ(id_2000->name + 2, "Foo.2000"); + /* But smaller than 1k suffixes do get proper empty spots. */ + ID *id_030 = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); + EXPECT_STREQ(id_030->name + 2, "Foo.030"); + ID *id_600 = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); + EXPECT_STREQ(id_600->name + 2, "Foo.600"); + + /* Max possible numeric suffix. */ + ID *id_max = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.999999999")); + EXPECT_STREQ(id_max->name + 2, "Foo.999999999"); + /* Try with max. possible suffix again: will assign free suffix under 1k. */ + ID *id_max1 = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.999999999")); + EXPECT_STREQ(id_max1->name + 2, "Foo.601"); + + /* Now create the rest of objects, to use all the suffixes up to 1k. + * Once all the ones up to 1k are used, the logic will fall back to + * "use largest number seen + 1", but the largest one is already the max + * possible. So it will shorten the name part and restart the counter, + * i.e. "Fo.001". */ + for (int i = total_object_count / 2; i < total_object_count; ++i) { + ids[i] = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); + } + /* At this point creating "Foo" based objects will fall always + * result in shortened name to "Fo". */ + ID *id_fo178 = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); + EXPECT_STREQ(id_fo178->name + 2, "Fo.178"); + ID *id_fo179 = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.2000")); + EXPECT_STREQ(id_fo179->name + 2, "Fo.179"); + ID *id_fo180 = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo.999999999")); + EXPECT_STREQ(id_fo180->name + 2, "Fo.180"); +} + +TEST(lib_id_main_unique_name, renames_with_duplicates) +{ + LibIDMainSortTestContext ctx; + + ID *id_a = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); + ID *id_b = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); + ID *id_c = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Bar")); + + EXPECT_STREQ(id_a->name + 2, "Foo"); + EXPECT_STREQ(id_b->name + 2, "Foo.001"); + EXPECT_STREQ(id_c->name + 2, "Bar"); + + BKE_libblock_rename(ctx.bmain, id_a, "Foo.002"); + EXPECT_STREQ(id_a->name + 2, "Foo.002"); + BKE_libblock_rename(ctx.bmain, id_b, "Bar"); + EXPECT_STREQ(id_b->name + 2, "Bar.001"); + BKE_libblock_rename(ctx.bmain, id_c, "Foo"); + EXPECT_STREQ(id_c->name + 2, "Foo"); + BKE_libblock_rename(ctx.bmain, id_b, "Bar"); + EXPECT_STREQ(id_b->name + 2, "Bar"); +} + +TEST(lib_id_main_unique_name, names_are_unique_per_id_type) +{ + LibIDMainSortTestContext ctx; + + ID *id_a = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); + ID *id_b = static_cast(BKE_id_new(ctx.bmain, ID_CA, "Foo")); + ID *id_c = static_cast(BKE_id_new(ctx.bmain, ID_OB, "Foo")); - test_lib_id_main_sort_free(&ctx); + EXPECT_STREQ(id_a->name + 2, "Foo"); + EXPECT_STREQ(id_b->name + 2, "Foo"); /* Different types (OB & CA) can have the same name. */ + EXPECT_STREQ(id_c->name + 2, "Foo.001"); } } // namespace blender::bke::tests -- cgit v1.2.3 From 7fa7722350b471e3ce6b369137c5b41574a4bda2 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 14 Jul 2022 20:52:47 +1000 Subject: Cleanup: format, unused argument --- source/blender/blenkernel/BKE_fcurve.h | 3 ++- source/blender/blenkernel/intern/lib_id_test.cc | 2 +- source/blender/editors/screen/screen_user_menu.c | 9 ++++++++- source/blender/editors/space_nla/nla_edit.c | 9 +++++++-- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/source/blender/blenkernel/BKE_fcurve.h b/source/blender/blenkernel/BKE_fcurve.h index 3ccbd2ac1da..c5788c07336 100644 --- a/source/blender/blenkernel/BKE_fcurve.h +++ b/source/blender/blenkernel/BKE_fcurve.h @@ -488,7 +488,8 @@ void BKE_fcurve_delete_keys_all(struct FCurve *fcu); */ void BKE_fcurve_handles_recalc(struct FCurve *fcu); /** - * Variant of #BKE_fcurve_handles_recalc() that allows calculating based on a different select flag. + * Variant of #BKE_fcurve_handles_recalc() that allows calculating based on a different select + * flag. * * \param handle_sel_flag: The flag (bezt.f1/2/3) value to use to determine selection. * Usually `SELECT`, but may want to use a different one at times diff --git a/source/blender/blenkernel/intern/lib_id_test.cc b/source/blender/blenkernel/intern/lib_id_test.cc index 96718b3a65b..1aba78eed8f 100644 --- a/source/blender/blenkernel/intern/lib_id_test.cc +++ b/source/blender/blenkernel/intern/lib_id_test.cc @@ -57,7 +57,7 @@ TEST(lib_id_main_sort, local_ids_1) test_lib_id_main_sort_check_order({id_a, id_b, id_c}); } -static void change_lib(Main *bmain, ID *id, Library *lib) +static void change_lib(Main * /*bmain*/, ID *id, Library *lib) { id->lib = lib; } diff --git a/source/blender/editors/screen/screen_user_menu.c b/source/blender/editors/screen/screen_user_menu.c index 67ca6a83b2b..9d66debda6f 100644 --- a/source/blender/editors/screen/screen_user_menu.c +++ b/source/blender/editors/screen/screen_user_menu.c @@ -214,7 +214,14 @@ static void screen_user_menu_draw(const bContext *C, Menu *menu) wmOperatorType *ot = WM_operatortype_find(umi_op->op_idname, false); if (ot != NULL) { IDProperty *prop = umi_op->prop ? IDP_CopyProperty(umi_op->prop) : NULL; - uiItemFullO_ptr(menu->layout, ot, CTX_IFACE_(ot->translation_context, ui_name), ICON_NONE, prop, umi_op->opcontext, 0, NULL); + uiItemFullO_ptr(menu->layout, + ot, + CTX_IFACE_(ot->translation_context, ui_name), + ICON_NONE, + prop, + umi_op->opcontext, + 0, + NULL); is_empty = false; } else { diff --git a/source/blender/editors/space_nla/nla_edit.c b/source/blender/editors/space_nla/nla_edit.c index 42509f17b92..d1a667c6e4e 100644 --- a/source/blender/editors/space_nla/nla_edit.c +++ b/source/blender/editors/space_nla/nla_edit.c @@ -2204,8 +2204,13 @@ static int nlaedit_apply_scale_exec(bContext *C, wmOperator *UNUSED(op)) /* setup iterator, and iterate over all the keyframes in the action, * applying this scaling */ ked.data = strip; - ANIM_animchanneldata_keyframes_loop( - &ked, ac.ads, strip->act, ALE_ACT, NULL, bezt_apply_nlamapping, BKE_fcurve_handles_recalc); + ANIM_animchanneldata_keyframes_loop(&ked, + ac.ads, + strip->act, + ALE_ACT, + NULL, + bezt_apply_nlamapping, + BKE_fcurve_handles_recalc); /* clear scale of strip now that it has been applied, * and recalculate the extents of the action now that it has been scaled -- cgit v1.2.3 From 64e196422e85e5177e3b30d2646fc6e9c34e6628 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 14 Jul 2022 20:55:02 +1000 Subject: GHOST/X11: Quiet warning about key-release events having their utf8 set Quiet warning from [0], no functional change as the this information was always ignored. Key release events shouldn't have associated text, this was cleared for wmEvent's, so there is no reason to pass it from GHOST. [0]: d6fef73ef110eb43756b7b87c2cba80abae3b39f --- intern/ghost/intern/GHOST_SystemX11.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/intern/ghost/intern/GHOST_SystemX11.cpp b/intern/ghost/intern/GHOST_SystemX11.cpp index 5e549b54afd..6b28c271059 100644 --- a/intern/ghost/intern/GHOST_SystemX11.cpp +++ b/intern/ghost/intern/GHOST_SystemX11.cpp @@ -1194,6 +1194,11 @@ void GHOST_SystemX11::processEvent(XEvent *xe) } #endif + if (type != GHOST_kEventKeyDown) { + ascii = 0; + utf8_buf = nullptr; + } + g_event = new GHOST_EventKey( getMilliSeconds(), type, window, gkey, ascii, utf8_buf, is_repeat); -- cgit v1.2.3 From db80cf6ad79cad93ce5638d6e406e674775fc5dc Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 14 Jul 2022 21:07:37 +1000 Subject: GHOST/X11: avoid redundant utf8 text lookups for release events The text representation of release events is never used, so only calculate this for press events. --- intern/ghost/intern/GHOST_SystemX11.cpp | 91 ++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 42 deletions(-) diff --git a/intern/ghost/intern/GHOST_SystemX11.cpp b/intern/ghost/intern/GHOST_SystemX11.cpp index 6b28c271059..4145856ee16 100644 --- a/intern/ghost/intern/GHOST_SystemX11.cpp +++ b/intern/ghost/intern/GHOST_SystemX11.cpp @@ -1017,7 +1017,9 @@ void GHOST_SystemX11::processEvent(XEvent *xe) case KeyRelease: { XKeyEvent *xke = &(xe->xkey); KeySym key_sym; + char *utf8_buf = nullptr; char ascii; + #if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING) /* utf8_array[] is initial buffer used for Xutf8LookupString(). * if the length of the utf8 string exceeds this array, allocate @@ -1026,12 +1028,10 @@ void GHOST_SystemX11::processEvent(XEvent *xe) * at the end of this buffer when the constructor of GHOST_EventKey * reads 6 bytes regardless of the effective data length. */ char utf8_array[16 * 6 + 5]; /* 16 utf8 characters */ - char *utf8_buf = utf8_array; - int len = 1; /* at least one null character will be stored */ + int len = 1; /* at least one null character will be stored */ #else - char *utf8_buf = nullptr; + char utf8_array[sizeof(GHOST_TEventKeyData::utf8_buf)] = {'\0'}; #endif - GHOST_TEventType type = (xke->type == KeyPress) ? GHOST_kEventKeyDown : GHOST_kEventKeyUp; GHOST_TKey gkey; @@ -1151,61 +1151,68 @@ void GHOST_SystemX11::processEvent(XEvent *xe) #endif #if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING) - /* Setting unicode on key-up events gives #XLookupNone status. */ - XIC xic = window->getX11_XIC(); - if (xic && xke->type == KeyPress) { - Status status; + /* Only used for key-press. */ + XIC xic = nullptr; +#endif - /* Use utf8 because its not locale repentant, from XORG docs. */ - if (!(len = Xutf8LookupString( - xic, xke, utf8_buf, sizeof(utf8_array) - 5, &key_sym, &status))) { - utf8_buf[0] = '\0'; - } + if (xke->type == KeyPress) { + utf8_buf = utf8_array; +#if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING) + /* Setting unicode on key-up events gives #XLookupNone status. */ + xic = window->getX11_XIC(); + if (xic && xke->type == KeyPress) { + Status status; + + /* Use utf8 because its not locale repentant, from XORG docs. */ + if (!(len = Xutf8LookupString( + xic, xke, utf8_buf, sizeof(utf8_array) - 5, &key_sym, &status))) { + utf8_buf[0] = '\0'; + } - if (status == XBufferOverflow) { - utf8_buf = (char *)malloc(len + 5); - len = Xutf8LookupString(xic, xke, utf8_buf, len, &key_sym, &status); - } + if (status == XBufferOverflow) { + utf8_buf = (char *)malloc(len + 5); + len = Xutf8LookupString(xic, xke, utf8_buf, len, &key_sym, &status); + } - if (ELEM(status, XLookupChars, XLookupBoth)) { - if ((unsigned char)utf8_buf[0] >= 32) { /* not an ascii control character */ - /* do nothing for now, this is valid utf8 */ + if (ELEM(status, XLookupChars, XLookupBoth)) { + if ((unsigned char)utf8_buf[0] >= 32) { /* not an ascii control character */ + /* do nothing for now, this is valid utf8 */ + } + else { + utf8_buf[0] = '\0'; + } + } + else if (status == XLookupKeySym) { + /* this key doesn't have a text representation, it is a command + * key of some sort */ } else { - utf8_buf[0] = '\0'; + printf("Bad keycode lookup. Keysym 0x%x Status: %s\n", + (unsigned int)key_sym, + (status == XLookupNone ? "XLookupNone" : + status == XLookupKeySym ? "XLookupKeySym" : + "Unknown status")); + + printf("'%.*s' %p %p\n", len, utf8_buf, xic, m_xim); } } - else if (status == XLookupKeySym) { - /* this key doesn't have a text representation, it is a command - * key of some sort */ - } else { - printf("Bad keycode lookup. Keysym 0x%x Status: %s\n", - (unsigned int)key_sym, - (status == XLookupNone ? "XLookupNone" : - status == XLookupKeySym ? "XLookupKeySym" : - "Unknown status")); - - printf("'%.*s' %p %p\n", len, utf8_buf, xic, m_xim); + utf8_buf[0] = '\0'; } - } - else { - utf8_buf[0] = '\0'; - } #endif - - if (type != GHOST_kEventKeyDown) { - ascii = 0; - utf8_buf = nullptr; + if (!utf8_buf[0] && ascii) { + utf8_buf[0] = ascii; + utf8_buf[1] = '\0'; + } } g_event = new GHOST_EventKey( - getMilliSeconds(), type, window, gkey, ascii, utf8_buf, is_repeat); + getMilliSeconds(), type, window, gkey, '\0', utf8_buf, is_repeat); #if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING) /* when using IM for some languages such as Japanese, * one event inserts multiple utf8 characters */ - if (xic && xke->type == KeyPress) { + if (xke->type == KeyPress && xic) { unsigned char c; int i = 0; while (1) { -- cgit v1.2.3 From eb3e56a36e18fd4d560a31c2b60ceb235fd0da09 Mon Sep 17 00:00:00 2001 From: Omar Emara Date: Thu, 14 Jul 2022 13:52:44 +0200 Subject: Fix: Wrong output types for some compositor nodes The Difference Matte and RGB To BW nodes have a wrong output type. They should be floats but are of type color. This is a regression that was introduced during the migration to the socket builder API in D13266. Reviewed By: Blendify, fclem Differential Revision: https://developer.blender.org/D15232 --- source/blender/nodes/composite/nodes/node_composite_diff_matte.cc | 2 +- source/blender/nodes/composite/nodes/node_composite_val_to_rgb.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/nodes/composite/nodes/node_composite_diff_matte.cc b/source/blender/nodes/composite/nodes/node_composite_diff_matte.cc index 20dd61a9725..b87bbe439db 100644 --- a/source/blender/nodes/composite/nodes/node_composite_diff_matte.cc +++ b/source/blender/nodes/composite/nodes/node_composite_diff_matte.cc @@ -19,7 +19,7 @@ static void cmp_node_diff_matte_declare(NodeDeclarationBuilder &b) b.add_input(N_("Image 1")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); b.add_input(N_("Image 2")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); b.add_output(N_("Image")); - b.add_output(N_("Matte")); + b.add_output(N_("Matte")); } static void node_composit_init_diff_matte(bNodeTree *UNUSED(ntree), bNode *node) diff --git a/source/blender/nodes/composite/nodes/node_composite_val_to_rgb.cc b/source/blender/nodes/composite/nodes/node_composite_val_to_rgb.cc index 0dfdeda24e6..df669d5beda 100644 --- a/source/blender/nodes/composite/nodes/node_composite_val_to_rgb.cc +++ b/source/blender/nodes/composite/nodes/node_composite_val_to_rgb.cc @@ -49,7 +49,7 @@ namespace blender::nodes::node_composite_val_to_rgb_cc { static void cmp_node_rgbtobw_declare(NodeDeclarationBuilder &b) { b.add_input(N_("Image")).default_value({0.8f, 0.8f, 0.8f, 1.0f}); - b.add_output(N_("Val")); + b.add_output(N_("Val")); } } // namespace blender::nodes::node_composite_val_to_rgb_cc -- cgit v1.2.3 From cdd8b96e3b476df38847121102c6a267b5522a85 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 14 Jul 2022 21:39:31 +1000 Subject: GHOST: remove redundant ascii argument to GHOST_EventKey Now only the utf8 buffer is used there is no reason to pass both. --- intern/ghost/intern/GHOST_EventKey.h | 14 +++++--------- intern/ghost/intern/GHOST_SystemCocoa.mm | 23 +++++------------------ intern/ghost/intern/GHOST_SystemSDL.cpp | 3 +-- intern/ghost/intern/GHOST_SystemWayland.cpp | 9 ++++----- intern/ghost/intern/GHOST_SystemWin32.cpp | 5 ++--- intern/ghost/intern/GHOST_SystemX11.cpp | 10 ++++------ 6 files changed, 21 insertions(+), 43 deletions(-) diff --git a/intern/ghost/intern/GHOST_EventKey.h b/intern/ghost/intern/GHOST_EventKey.h index d3cfbbeddd7..f85a674be9f 100644 --- a/intern/ghost/intern/GHOST_EventKey.h +++ b/intern/ghost/intern/GHOST_EventKey.h @@ -22,6 +22,7 @@ class GHOST_EventKey : public GHOST_Event { * \param msec: The time this event was generated. * \param type: The type of key event. * \param key: The key code of the key. + * \param is_repeat: Enabled for key repeat events (only for press events). */ GHOST_EventKey( uint64_t msec, GHOST_TEventType type, GHOST_IWindow *window, GHOST_TKey key, bool is_repeat) @@ -38,15 +39,15 @@ class GHOST_EventKey : public GHOST_Event { * \param msec: The time this event was generated. * \param type: The type of key event. * \param key: The key code of the key. - * \param ascii: The ascii code for the key event. + * \param is_repeat: Enabled for key repeat events (only for press events). + * \param utf8_buf: The text associated with this key event (only for press events). */ GHOST_EventKey(uint64_t msec, GHOST_TEventType type, GHOST_IWindow *window, GHOST_TKey key, - char ascii, - const char utf8_buf[6], - bool is_repeat) + bool is_repeat, + const char utf8_buf[6]) : GHOST_Event(msec, type, window) { m_keyEventData.key = key; @@ -56,11 +57,6 @@ class GHOST_EventKey : public GHOST_Event { else { m_keyEventData.utf8_buf[0] = '\0'; } - /* TODO(@campbellbarton): phase out `ascii` and always use `utf8_buf`, this needs to be done - * on all platforms though, so for now write the ascii into the utf8_buf. */ - if (m_keyEventData.utf8_buf[0] == '\0' && ascii) { - m_keyEventData.utf8_buf[0] = ascii; - } m_keyEventData.is_repeat = is_repeat; m_data = &m_keyEventData; } diff --git a/intern/ghost/intern/GHOST_SystemCocoa.mm b/intern/ghost/intern/GHOST_SystemCocoa.mm index 0a905f5093e..c247ef3daa0 100644 --- a/intern/ghost/intern/GHOST_SystemCocoa.mm +++ b/intern/ghost/intern/GHOST_SystemCocoa.mm @@ -1779,7 +1779,6 @@ GHOST_TSuccess GHOST_SystemCocoa::handleKeyEvent(void *eventPtr) NSString *characters; NSData *convertedCharacters; GHOST_TKey keyCode; - unsigned char ascii; NSString *charsIgnoringModifiers; window = m_windowManager->getWindowAssociatedWithOSWindow((void *)[event window]); @@ -1789,7 +1788,6 @@ GHOST_TSuccess GHOST_SystemCocoa::handleKeyEvent(void *eventPtr) } char utf8_buf[6] = {'\0'}; - ascii = 0; switch ([event type]) { @@ -1809,7 +1807,6 @@ GHOST_TSuccess GHOST_SystemCocoa::handleKeyEvent(void *eventPtr) kUCKeyActionUp); } - /* handling both unicode or ascii */ characters = [event characters]; if ([characters length] > 0) { convertedCharacters = [characters dataUsingEncoding:NSUTF8StringEncoding]; @@ -1835,41 +1832,31 @@ GHOST_TSuccess GHOST_SystemCocoa::handleKeyEvent(void *eventPtr) if ((keyCode == GHOST_kKeyQ) && (m_modifierMask & NSEventModifierFlagCommand)) break; // Cmd-Q is directly handled by Cocoa - /* ascii is a subset of unicode */ - if (utf8_buf[0] && !utf8_buf[1]) { - ascii = utf8_buf[0]; - } - if ([event type] == NSEventTypeKeyDown) { pushEvent(new GHOST_EventKey([event timestamp] * 1000, GHOST_kEventKeyDown, window, keyCode, - ascii, - utf8_buf, - [event isARepeat])); + [event isARepeat], + utf8_buf)); #if 0 - printf("Key down rawCode=0x%x charsIgnoringModifiers=%c keyCode=%u ascii=%i %c utf8=%s\n", + printf("Key down rawCode=0x%x charsIgnoringModifiers=%c keyCode=%u utf8=%s\n", [event keyCode], [charsIgnoringModifiers length] > 0 ? [charsIgnoringModifiers characterAtIndex:0] : ' ', keyCode, - ascii, - ascii, utf8_buf); #endif } else { pushEvent(new GHOST_EventKey( - [event timestamp] * 1000, GHOST_kEventKeyUp, window, keyCode, 0, NULL, false)); + [event timestamp] * 1000, GHOST_kEventKeyUp, window, keyCode, false, NULL)); #if 0 - printf("Key up rawCode=0x%x charsIgnoringModifiers=%c keyCode=%u ascii=%i %c utf8=%s\n", + printf("Key up rawCode=0x%x charsIgnoringModifiers=%c keyCode=%u utf8=%s\n", [event keyCode], [charsIgnoringModifiers length] > 0 ? [charsIgnoringModifiers characterAtIndex:0] : ' ', keyCode, - ascii, - ascii, utf8_buf); #endif } diff --git a/intern/ghost/intern/GHOST_SystemSDL.cpp b/intern/ghost/intern/GHOST_SystemSDL.cpp index 55a15ae470e..d912b57f049 100644 --- a/intern/ghost/intern/GHOST_SystemSDL.cpp +++ b/intern/ghost/intern/GHOST_SystemSDL.cpp @@ -608,8 +608,7 @@ void GHOST_SystemSDL::processEvent(SDL_Event *sdl_event) utf8_buf[0] = convert_keyboard_event_to_ascii(sdl_sub_evt); } - g_event = new GHOST_EventKey( - getMilliSeconds(), type, window, gkey, '\0', utf8_buf, is_repeat); + g_event = new GHOST_EventKey(getMilliSeconds(), type, window, gkey, is_repeat, utf8_buf); break; } } diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 876f77732c2..099792a4d06 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -2257,8 +2257,8 @@ static void keyboard_handle_key(void *data, if (wl_surface *focus_surface = input->keyboard.wl_surface) { GHOST_IWindow *win = ghost_wl_surface_user_data(focus_surface); - input->system->pushEvent(new GHOST_EventKey( - input->system->getMilliSeconds(), etype, win, gkey, '\0', utf8_buf, false)); + input->system->pushEvent( + new GHOST_EventKey(input->system->getMilliSeconds(), etype, win, gkey, false, utf8_buf)); } /* An existing payload means the key repeat timer is reset and will be added again. */ @@ -2290,9 +2290,8 @@ static void keyboard_handle_key(void *data, GHOST_kEventKeyDown, win, payload->key_data.gkey, - '\0', - utf8_buf, - true)); + true, + utf8_buf)); } }; input->key_repeat.timer = input->system->installTimer( diff --git a/intern/ghost/intern/GHOST_SystemWin32.cpp b/intern/ghost/intern/GHOST_SystemWin32.cpp index 1365c281ab4..5a930209376 100644 --- a/intern/ghost/intern/GHOST_SystemWin32.cpp +++ b/intern/ghost/intern/GHOST_SystemWin32.cpp @@ -1256,9 +1256,8 @@ GHOST_EventKey *GHOST_SystemWin32::processKeyEvent(GHOST_WindowWin32 *window, RA keyDown ? GHOST_kEventKeyDown : GHOST_kEventKeyUp, window, key, - ascii, - utf8_char, - is_repeat); + is_repeat, + utf8_char); // GHOST_PRINTF("%c\n", ascii); // we already get this info via EventPrinter } diff --git a/intern/ghost/intern/GHOST_SystemX11.cpp b/intern/ghost/intern/GHOST_SystemX11.cpp index 4145856ee16..70a9687672c 100644 --- a/intern/ghost/intern/GHOST_SystemX11.cpp +++ b/intern/ghost/intern/GHOST_SystemX11.cpp @@ -745,9 +745,8 @@ bool GHOST_SystemX11::processEvents(bool waitForEvent) GHOST_kEventKeyDown, window, ghost_key_from_keysym(modifiers[i]), - '\0', - nullptr, - false)); + false, + nullptr)); } } } @@ -1206,8 +1205,7 @@ void GHOST_SystemX11::processEvent(XEvent *xe) } } - g_event = new GHOST_EventKey( - getMilliSeconds(), type, window, gkey, '\0', utf8_buf, is_repeat); + g_event = new GHOST_EventKey(getMilliSeconds(), type, window, gkey, is_repeat, utf8_buf); #if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING) /* when using IM for some languages such as Japanese, @@ -1232,7 +1230,7 @@ void GHOST_SystemX11::processEvent(XEvent *xe) pushEvent(g_event); g_event = new GHOST_EventKey( - getMilliSeconds(), type, window, gkey, '\0', &utf8_buf[i], is_repeat); + getMilliSeconds(), type, window, gkey, is_repeat, &utf8_buf[i]); } } -- cgit v1.2.3 From 93f74299f07585d5c922aa048055141e7a487e07 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 14 Jul 2022 21:55:46 +1000 Subject: Cleanup: clang-tidy changes to GHOST_SystemX11 Also remove redundant check. --- intern/ghost/intern/GHOST_SystemX11.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/intern/ghost/intern/GHOST_SystemX11.cpp b/intern/ghost/intern/GHOST_SystemX11.cpp index 70a9687672c..00cc5f3af8f 100644 --- a/intern/ghost/intern/GHOST_SystemX11.cpp +++ b/intern/ghost/intern/GHOST_SystemX11.cpp @@ -1159,7 +1159,7 @@ void GHOST_SystemX11::processEvent(XEvent *xe) #if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING) /* Setting unicode on key-up events gives #XLookupNone status. */ xic = window->getX11_XIC(); - if (xic && xke->type == KeyPress) { + if (xic) { Status status; /* Use utf8 because its not locale repentant, from XORG docs. */ @@ -1213,20 +1213,21 @@ void GHOST_SystemX11::processEvent(XEvent *xe) if (xke->type == KeyPress && xic) { unsigned char c; int i = 0; - while (1) { - /* search character boundary */ - if ((unsigned char)utf8_buf[i++] > 0x7f) { + while (true) { + /* Search character boundary. */ + if ((uchar)utf8_buf[i++] > 0x7f) { for (; i < len; ++i) { c = utf8_buf[i]; - if (c < 0x80 || c > 0xbf) + if (c < 0x80 || c > 0xbf) { break; + } } } - if (i >= len) + if (i >= len) { break; - - /* enqueue previous character */ + } + /* Enqueue previous character. */ pushEvent(g_event); g_event = new GHOST_EventKey( @@ -1234,8 +1235,9 @@ void GHOST_SystemX11::processEvent(XEvent *xe) } } - if (utf8_buf != utf8_array) + if (utf8_buf != utf8_array) { free(utf8_buf); + } #endif break; -- cgit v1.2.3 From 9dfabc1de33f555d3e2856974952fc5c646b3b85 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 14 Jul 2022 22:01:43 +1000 Subject: Cleanup: remove redundant `event->val` check for 3D text insertion --- source/blender/editors/curve/editfont.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/source/blender/editors/curve/editfont.c b/source/blender/editors/curve/editfont.c index 623f41dcd20..ceed12dcff1 100644 --- a/source/blender/editors/curve/editfont.c +++ b/source/blender/editors/curve/editfont.c @@ -1642,7 +1642,6 @@ static int insert_text_invoke(bContext *C, wmOperator *op, const wmEvent *event) const bool alt = event->modifier & KM_ALT; const bool shift = event->modifier & KM_SHIFT; const bool ctrl = event->modifier & KM_CTRL; - int event_type = event->type, event_val = event->val; char32_t insert_char_override = 0; char32_t inserted_text[2] = {0}; @@ -1657,7 +1656,7 @@ static int insert_text_invoke(bContext *C, wmOperator *op, const wmEvent *event) return OPERATOR_FINISHED; } - if (event_type == EVT_BACKSPACEKEY) { + if (event->type == EVT_BACKSPACEKEY) { if (alt && ef->len != 0 && ef->pos > 0) { accentcode = true; } @@ -1665,14 +1664,14 @@ static int insert_text_invoke(bContext *C, wmOperator *op, const wmEvent *event) } /* Tab typically exit edit-mode, but we allow it to be typed using modifier keys. */ - if (event_type == EVT_TABKEY) { + if (event->type == EVT_TABKEY) { if ((alt || ctrl || shift) == 0) { return OPERATOR_PASS_THROUGH; } insert_char_override = '\t'; } - if (event_val && (insert_char_override || event->utf8_buf[0])) { + if (insert_char_override || event->utf8_buf[0]) { if (insert_char_override) { /* Handle case like TAB ('\t'). */ inserted_text[0] = insert_char_override; @@ -1714,11 +1713,6 @@ static int insert_text_invoke(bContext *C, wmOperator *op, const wmEvent *event) RNA_string_set(op->ptr, "text", inserted_utf8); } - /* reset property? */ - if (event_val == 0) { - accentcode = false; - } - return OPERATOR_FINISHED; } -- cgit v1.2.3 From 2d04012e57a75254ccfe97ed2df747e03dcd4da5 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 14 Jul 2022 22:02:52 +1000 Subject: Cleanup: spelling in comments Also remove duplicate comments in bmesh_log.h, caused by automated comment relocation in [0]. [0]: c4e041da23b9c45273fcd4874308c536b6a315d1 --- intern/cycles/scene/scene.h | 4 +- source/blender/blenkernel/intern/brush.cc | 6 +- .../blenkernel/intern/mesh_merge_customdata.cc | 6 +- source/blender/bmesh/intern/bmesh_log.c | 32 +++--- source/blender/bmesh/intern/bmesh_log.h | 113 +++++++++++---------- source/blender/gpu/metal/mtl_texture.hh | 2 +- 6 files changed, 84 insertions(+), 79 deletions(-) diff --git a/intern/cycles/scene/scene.h b/intern/cycles/scene/scene.h index d04c6a27f11..d1004bb7b66 100644 --- a/intern/cycles/scene/scene.h +++ b/intern/cycles/scene/scene.h @@ -82,7 +82,7 @@ class DeviceScene { device_vector patches; - /* pointcloud */ + /* point-cloud */ device_vector points; device_vector points_shader; @@ -124,7 +124,7 @@ class DeviceScene { /* integrator */ device_vector sample_pattern_lut; - /* ies lights */ + /* IES lights */ device_vector ies_lights; KernelData data; diff --git a/source/blender/blenkernel/intern/brush.cc b/source/blender/blenkernel/intern/brush.cc index 1c2408bab8a..99733c8edb3 100644 --- a/source/blender/blenkernel/intern/brush.cc +++ b/source/blender/blenkernel/intern/brush.cc @@ -587,15 +587,15 @@ bool BKE_brush_delete(Main *bmain, Brush *brush) return true; } -/* grease pencil cumapping->preset */ -typedef enum eGPCurveMappingPreset { +/** Local grease pencil curve mapping preset. */ +using eGPCurveMappingPreset = enum eGPCurveMappingPreset { GPCURVE_PRESET_PENCIL = 0, GPCURVE_PRESET_INK = 1, GPCURVE_PRESET_INKNOISE = 2, GPCURVE_PRESET_MARKER = 3, GPCURVE_PRESET_CHISEL_SENSIVITY = 4, GPCURVE_PRESET_CHISEL_STRENGTH = 5, -} eGPCurveMappingPreset; +}; static void brush_gpencil_curvemap_reset(CurveMap *cuma, int tot, int preset) { diff --git a/source/blender/blenkernel/intern/mesh_merge_customdata.cc b/source/blender/blenkernel/intern/mesh_merge_customdata.cc index adaf378ed27..7bc429954b0 100644 --- a/source/blender/blenkernel/intern/mesh_merge_customdata.cc +++ b/source/blender/blenkernel/intern/mesh_merge_customdata.cc @@ -33,11 +33,11 @@ static int compare_v2_classify(const float uv_a[2], const float uv_b[2]) if (uv_a[0] == uv_b[0] && uv_a[1] == uv_b[1]) { return CMP_EQUAL; } - /* Note that the ULP value is the primary value used to compare relative values - * as the absolute value doesn't account for float precision at difference scales. + /* NOTE(@campbellbarton): that the ULP value is the primary value used to compare relative + * values as the absolute value doesn't account for float precision at difference scales. * - For subdivision-surface ULP of 3 is sufficient, * although this value is extremely small. - * - For bevel the URL of 12 is sufficient to merge UV's that appear to be connected + * - For bevel the ULP of 12 is sufficient to merge UV's that appear to be connected * with bevel on Suzanne beveled 15% with 6 segments. * * These values could be tweaked but should be kept on the small side to prevent diff --git a/source/blender/bmesh/intern/bmesh_log.c b/source/blender/bmesh/intern/bmesh_log.c index a81ae934629..64e6c63e9f0 100644 --- a/source/blender/bmesh/intern/bmesh_log.c +++ b/source/blender/bmesh/intern/bmesh_log.c @@ -34,42 +34,41 @@ struct BMLogEntry { struct BMLogEntry *next, *prev; - /* The following GHashes map from an element ID to one of the log - * types above */ + /* The following #GHash members map from an element ID to one of the log types above. */ - /* Elements that were in the previous entry, but have been - * deleted */ + /** Elements that were in the previous entry, but have been deleted. */ GHash *deleted_verts; GHash *deleted_faces; - /* Elements that were not in the previous entry, but are in the - * result of this entry */ + /** Elements that were not in the previous entry, but are in the result of this entry. */ GHash *added_verts; GHash *added_faces; - /* Vertices whose coordinates, mask value, or hflag have changed */ + /** Vertices whose coordinates, mask value, or hflag have changed. */ GHash *modified_verts; GHash *modified_faces; BLI_mempool *pool_verts; BLI_mempool *pool_faces; - /* This is only needed for dropping BMLogEntries while still in + /** + * This is only needed for dropping BMLogEntries while still in * dynamic-topology mode, as that should release vert/face IDs - * back to the BMLog but no BMLog pointer is available at that - * time. + * back to the BMLog but no BMLog pointer is available at that time. * * This field is not guaranteed to be valid, any use of it should - * check for NULL. */ + * check for NULL. + */ BMLog *log; }; struct BMLog { - /* Tree of free IDs */ + /** Tree of free IDs */ struct RangeTreeUInt *unused_ids; - /* Mapping from unique IDs to vertices and faces + /** + * Mapping from unique IDs to vertices and faces * - * Each vertex and face in the log gets a unique uinteger + * Each vertex and face in the log gets a unique `uint` * assigned. That ID is taken from the set managed by the * unused_ids range tree. * @@ -79,10 +78,11 @@ struct BMLog { GHash *id_to_elem; GHash *elem_to_id; - /* All BMLogEntrys, ordered from earliest to most recent */ + /** All #BMLogEntrys, ordered from earliest to most recent. */ ListBase entries; - /* The current log entry from entries list + /** + * The current log entry from entries list * * If null, then the original mesh from before any of the log * entries is current (i.e. there is nothing left to undo.) diff --git a/source/blender/bmesh/intern/bmesh_log.h b/source/blender/bmesh/intern/bmesh_log.h index 189aa97509f..5daa5dd9a68 100644 --- a/source/blender/bmesh/intern/bmesh_log.h +++ b/source/blender/bmesh/intern/bmesh_log.h @@ -14,12 +14,13 @@ struct RangeTreeUInt; typedef struct BMLog BMLog; typedef struct BMLogEntry BMLogEntry; -/* Allocate and initialize a new BMLog */ -/* Allocate, initialize, and assign a new BMLog */ +/** + * Allocate, initialize, and assign a new BMLog. + */ BMLog *BM_log_create(BMesh *bm); -/* Allocate and initialize a new BMLog using existing BMLogEntries */ -/* Allocate and initialize a new BMLog using existing BMLogEntries +/** + * Allocate and initialize a new #BMLog using existing #BMLogEntries * * The 'entry' should be the last entry in the BMLog. Its prev pointer * will be followed back to find the first entry. @@ -29,20 +30,21 @@ BMLog *BM_log_create(BMesh *bm); */ BMLog *BM_log_from_existing_entries_create(BMesh *bm, BMLogEntry *entry); -/* Free all the data in a BMLog including the log itself */ -/* Free all the data in a BMLog including the log itself */ +/** + * Free all the data in a BMLog including the log itself. + */ void BM_log_free(BMLog *log); -/* Get the number of log entries */ -/* Get the number of log entries */ +/** + * Get the number of log entries. + */ int BM_log_length(const BMLog *log); -/* Apply a consistent ordering to BMesh vertices and faces */ -/* Apply a consistent ordering to BMesh vertices */ +/** Apply a consistent ordering to BMesh vertices and faces. */ void BM_log_mesh_elems_reorder(BMesh *bm, BMLog *log); -/* Start a new log entry and update the log entry list */ -/* Start a new log entry and update the log entry list +/** + * Start a new log entry and update the log entry list. * * If the log entry list is empty, or if the current log entry is the * last entry, the new entry is simply appended to the end. @@ -54,35 +56,36 @@ void BM_log_mesh_elems_reorder(BMesh *bm, BMLog *log); */ BMLogEntry *BM_log_entry_add(BMLog *log); -/* Mark all used ids as unused for this node */ +/** Mark all used ids as unused for this node */ void BM_log_cleanup_entry(BMLogEntry *entry); -/* Remove an entry from the log */ -/* Remove an entry from the log +/** + * Remove an entry from the log. * * Uses entry->log as the log. If the log is NULL, the entry will be - * free'd but not removed from any list, nor shall its IDs be - * released. + * free'd but not removed from any list, nor shall its IDs be released. * * This operation is only valid on the first and last entries in the * log. Deleting from the middle will assert. */ void BM_log_entry_drop(BMLogEntry *entry); -/* Undo one BMLogEntry */ -/* Undo one BMLogEntry +/** + * Undo one #BMLogEntry. * - * Has no effect if there's nothing left to undo */ + * Has no effect if there's nothing left to undo. + */ void BM_log_undo(BMesh *bm, BMLog *log); -/* Redo one BMLogEntry */ -/* Redo one BMLogEntry +/** + * Redo one #BMLogEntry. * - * Has no effect if there's nothing left to redo */ + * Has no effect if there's nothing left to redo. + */ void BM_log_redo(BMesh *bm, BMLog *log); -/* Log a vertex before it is modified */ -/* Log a vertex before it is modified +/** + * Log a vertex before it is modified. * * Before modifying vertex coordinates, masks, or hflags, call this * function to log its current values. This is better than logging @@ -107,8 +110,8 @@ void BM_log_redo(BMesh *bm, BMLog *log); */ void BM_log_vert_before_modified(BMLog *log, struct BMVert *v, int cd_vert_mask_offset); -/* Log a new vertex as added to the BMesh */ -/* Log a new vertex as added to the BMesh +/** + * Log a new vertex as added to the #BMesh. * * The new vertex gets a unique ID assigned. It is then added to a map * of added vertices, with the key being its ID and the value @@ -116,16 +119,16 @@ void BM_log_vert_before_modified(BMLog *log, struct BMVert *v, int cd_vert_mask_ */ void BM_log_vert_added(BMLog *log, struct BMVert *v, int cd_vert_mask_offset); -/* Log a face before it is modified */ -/* Log a face before it is modified +/** + * Log a face before it is modified. * * This is intended to handle only header flags and we always - * assume face has been added before + * assume face has been added before. */ void BM_log_face_modified(BMLog *log, struct BMFace *f); -/* Log a new face as added to the BMesh */ -/* Log a new face as added to the BMesh +/** + * Log a new face as added to the #BMesh. * * The new face gets a unique ID assigned. It is then added to a map * of added faces, with the key being its ID and the value containing @@ -133,8 +136,8 @@ void BM_log_face_modified(BMLog *log, struct BMFace *f); */ void BM_log_face_added(BMLog *log, struct BMFace *f); -/* Log a vertex as removed from the BMesh */ -/* Log a vertex as removed from the BMesh +/** + * Log a vertex as removed from the #BMesh. * * A couple things can happen here: * @@ -142,7 +145,7 @@ void BM_log_face_added(BMLog *log, struct BMFace *f); * deleted and forgotten about entirely. Its unique ID is returned to * the unused pool. * - * If the vertex was already part of the BMesh before the current log + * If the vertex was already part of the #BMesh before the current log * entry, it is added to a map of deleted vertices, with the key being * its ID and the value containing everything needed to reconstruct * that vertex. @@ -152,8 +155,8 @@ void BM_log_face_added(BMLog *log, struct BMFace *f); */ void BM_log_vert_removed(BMLog *log, struct BMVert *v, int cd_vert_mask_offset); -/* Log a face as removed from the BMesh */ -/* Log a face as removed from the BMesh +/** + * Log a face as removed from the #BMesh. * * A couple things can happen here: * @@ -161,43 +164,45 @@ void BM_log_vert_removed(BMLog *log, struct BMVert *v, int cd_vert_mask_offset); * deleted and forgotten about entirely. Its unique ID is returned to * the unused pool. * - * If the face was already part of the BMesh before the current log + * If the face was already part of the #BMesh before the current log * entry, it is added to a map of deleted faces, with the key being * its ID and the value containing everything needed to reconstruct * that face. */ void BM_log_face_removed(BMLog *log, struct BMFace *f); -/* Log all vertices/faces in the BMesh as added */ -/* Log all vertices/faces in the BMesh as added */ +/** + * Log all vertices/faces in the #BMesh as added. + */ void BM_log_all_added(BMesh *bm, BMLog *log); -/* Log all vertices/faces in the BMesh as removed */ -/* Log all vertices/faces in the BMesh as removed */ +/** Log all vertices/faces in the #BMesh as removed. */ void BM_log_before_all_removed(BMesh *bm, BMLog *log); -/* Get the logged coordinates of a vertex */ -/* Get the logged coordinates of a vertex +/** + * Get the logged coordinates of a vertex. * - * Does not modify the log or the vertex */ + * Does not modify the log or the vertex. + */ const float *BM_log_original_vert_co(BMLog *log, BMVert *v); -/* Get the logged normal of a vertex +/** + * Get the logged normal of a vertex * - * Does not modify the log or the vertex */ + * Does not modify the log or the vertex. + */ const float *BM_log_original_vert_no(BMLog *log, BMVert *v); -/* Get the logged mask of a vertex */ -/* Get the logged mask of a vertex +/** Get the logged mask of a vertex * - * Does not modify the log or the vertex */ + * Does not modify the log or the vertex. + */ float BM_log_original_mask(BMLog *log, BMVert *v); -/* Get the logged data of a vertex (avoid multiple lookups) */ +/** Get the logged data of a vertex (avoid multiple lookups). */ void BM_log_original_vert_data(BMLog *log, BMVert *v, const float **r_co, const float **r_no); -/* For internal use only (unit testing) */ -/* For internal use only (unit testing) */ +/** For internal use only (unit testing). */ BMLogEntry *BM_log_current_entry(BMLog *log); -/* For internal use only (unit testing) */ +/** For internal use only (unit testing) */ struct RangeTreeUInt *BM_log_unused_ids(BMLog *log); diff --git a/source/blender/gpu/metal/mtl_texture.hh b/source/blender/gpu/metal/mtl_texture.hh index 9387d5af814..82a7a20a310 100644 --- a/source/blender/gpu/metal/mtl_texture.hh +++ b/source/blender/gpu/metal/mtl_texture.hh @@ -93,7 +93,7 @@ struct TextureReadRoutineSpecialisation { * 0 = Not a Depth format, * 1 = FLOAT DEPTH, * 2 = 24Bit Integer Depth, - * 4 = 32bit uinteger Depth. */ + * 4 = 32bit Unsigned-Integer Depth. */ int depth_format_mode; bool operator==(const TextureReadRoutineSpecialisation &other) const -- cgit v1.2.3 From 47d4ce498e3f5a11a0210b1efd57053f0b1c85bd Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 14 Jul 2022 22:05:31 +1000 Subject: Cleanup: minor changes to camera frame fitting Use const vars & make order of min/max checks consistent. --- source/blender/blenkernel/intern/camera.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/source/blender/blenkernel/intern/camera.c b/source/blender/blenkernel/intern/camera.c index 48a48d6e2b4..7cfac0cb75a 100644 --- a/source/blender/blenkernel/intern/camera.c +++ b/source/blender/blenkernel/intern/camera.c @@ -704,7 +704,7 @@ static bool camera_frame_fit_calc_from_data(CameraParams *params, } if ((!isect_plane_plane_v3( - plane_tx[Y_MAX], plane_tx[Y_MIN], plane_isect_1, plane_isect_1_no)) || + plane_tx[Y_MIN], plane_tx[Y_MAX], plane_isect_1, plane_isect_1_no)) || (!isect_plane_plane_v3( plane_tx[Z_MIN], plane_tx[Z_MAX], plane_isect_2, plane_isect_2_no))) { return false; @@ -713,28 +713,27 @@ static bool camera_frame_fit_calc_from_data(CameraParams *params, add_v3_v3v3(plane_isect_1_other, plane_isect_1, plane_isect_1_no); add_v3_v3v3(plane_isect_2_other, plane_isect_2, plane_isect_2_no); - if (isect_line_line_v3(plane_isect_1, - plane_isect_1_other, - plane_isect_2, - plane_isect_2_other, - plane_isect_pt_1, - plane_isect_pt_2) == 0) { + if (!isect_line_line_v3(plane_isect_1, + plane_isect_1_other, + plane_isect_2, + plane_isect_2_other, + plane_isect_pt_1, + plane_isect_pt_2)) { return false; } float cam_plane_no[3]; float plane_isect_delta[3]; - float plane_isect_delta_len; - float shift_fac = BKE_camera_sensor_size( - params->sensor_fit, params->sensor_x, params->sensor_y) / - params->lens; + const float shift_fac = BKE_camera_sensor_size( + params->sensor_fit, params->sensor_x, params->sensor_y) / + params->lens; /* we want (0, 0, -1) transformed by camera_rotmat, this is a quicker shortcut. */ negate_v3_v3(cam_plane_no, data->camera_rotmat[2]); sub_v3_v3v3(plane_isect_delta, plane_isect_pt_2, plane_isect_pt_1); - plane_isect_delta_len = len_v3(plane_isect_delta); + const float plane_isect_delta_len = len_v3(plane_isect_delta); if (dot_v3v3(plane_isect_delta, cam_plane_no) > 0.0f) { copy_v3_v3(r_co, plane_isect_pt_1); -- cgit v1.2.3 From 4b1d315017ef103f3034160d349b3c3c21a4cd6a Mon Sep 17 00:00:00 2001 From: Michael Jones Date: Wed, 13 Jul 2022 20:56:57 +0100 Subject: Cycles: Improve cache usage on Apple GPUs by chunking active indices This patch partitions the active indices into chunks prior to sorting by material in order to tradeoff some material coherence for better locality. On Apple Silicon GPUs (particularly higher end M1-family GPUs), we observe overall render time speedups of up to 15%. The partitioning is implemented by repeating the range of `shader_sort_key` for each partition, and encoding a "locator" key which distributes the indices into sorted chunks. Reviewed By: brecht Differential Revision: https://developer.blender.org/D15331 --- intern/cycles/device/metal/device_impl.mm | 6 +++++- intern/cycles/device/metal/queue.h | 1 + intern/cycles/device/metal/queue.mm | 21 +++++++++++++++++++-- intern/cycles/device/metal/util.h | 1 + intern/cycles/device/metal/util.mm | 18 ++++++++++++++++++ intern/cycles/device/queue.h | 8 ++++++++ intern/cycles/integrator/path_trace.cpp | 2 +- intern/cycles/integrator/path_trace_work_gpu.cpp | 21 +++++++++++++-------- intern/cycles/integrator/path_trace_work_gpu.h | 3 +++ intern/cycles/kernel/integrator/state.h | 3 +++ intern/cycles/kernel/integrator/state_flow.h | 12 ++++++++++-- 11 files changed, 82 insertions(+), 14 deletions(-) diff --git a/intern/cycles/device/metal/device_impl.mm b/intern/cycles/device/metal/device_impl.mm index 87c83242240..ba9317e3204 100644 --- a/intern/cycles/device/metal/device_impl.mm +++ b/intern/cycles/device/metal/device_impl.mm @@ -217,6 +217,10 @@ string MetalDevice::get_source(const uint kernel_features) build_options += " -D__KERNEL_FEATURES__=" + to_string(kernel_features); } + if (MetalInfo::optimal_sort_partition_elements(mtlDevice) > 0) { + build_options += " -D__KERNEL_SORT_PARTITIONING__ "; + } + if (use_metalrt) { build_options += "-D__METALRT__ "; if (motion_blur) { @@ -652,7 +656,7 @@ void MetalDevice::const_copy_to(const char *name, void *host, size_t size) /* Update data storage pointers in launch parameters. */ if (strcmp(name, "integrator_state") == 0) { /* IntegratorStateGPU is contiguous pointers */ - const size_t pointer_block_size = sizeof(IntegratorStateGPU); + const size_t pointer_block_size = offsetof(IntegratorStateGPU, sort_partition_divisor); update_launch_pointers( offsetof(KernelParamsMetal, integrator_state), host, size, pointer_block_size); } diff --git a/intern/cycles/device/metal/queue.h b/intern/cycles/device/metal/queue.h index b0bd487c86d..836289172f7 100644 --- a/intern/cycles/device/metal/queue.h +++ b/intern/cycles/device/metal/queue.h @@ -24,6 +24,7 @@ class MetalDeviceQueue : public DeviceQueue { virtual int num_concurrent_states(const size_t) const override; virtual int num_concurrent_busy_states() const override; + virtual int num_sort_partitions(const size_t) const override; virtual void init_execution() override; diff --git a/intern/cycles/device/metal/queue.mm b/intern/cycles/device/metal/queue.mm index 03e60b6bb6e..6a9cc552098 100644 --- a/intern/cycles/device/metal/queue.mm +++ b/intern/cycles/device/metal/queue.mm @@ -293,6 +293,23 @@ int MetalDeviceQueue::num_concurrent_busy_states() const return result; } +int MetalDeviceQueue::num_sort_partitions(const size_t state_size) const +{ + /* Sort partitioning becomes less effective when more shaders are in the wavefront. In lieu of a + * more sophisticated heuristic we simply disable sort partitioning if the shader count is high. + */ + if (metal_device_->launch_params.data.max_shaders >= 300) { + return 1; + } + + const int optimal_partition_elements = MetalInfo::optimal_sort_partition_elements( + metal_device_->mtlDevice); + if (optimal_partition_elements) { + return num_concurrent_states(state_size) / optimal_partition_elements; + } + return 1; +} + void MetalDeviceQueue::init_execution() { /* Synchronize all textures and memory copies before executing task. */ @@ -359,7 +376,7 @@ bool MetalDeviceQueue::enqueue(DeviceKernel kernel, /* Prepare any non-pointer (i.e. plain-old-data) KernelParamsMetal data */ /* The plain-old-data is contiguous, continuing to the end of KernelParamsMetal */ size_t plain_old_launch_data_offset = offsetof(KernelParamsMetal, integrator_state) + - sizeof(IntegratorStateGPU); + offsetof(IntegratorStateGPU, sort_partition_divisor); size_t plain_old_launch_data_size = sizeof(KernelParamsMetal) - plain_old_launch_data_offset; memcpy(init_arg_buffer + globals_offsets + plain_old_launch_data_offset, (uint8_t *)&metal_device_->launch_params + plain_old_launch_data_offset, @@ -416,7 +433,7 @@ bool MetalDeviceQueue::enqueue(DeviceKernel kernel, /* this relies on IntegratorStateGPU layout being contiguous device_ptrs */ const size_t pointer_block_end = offsetof(KernelParamsMetal, integrator_state) + - sizeof(IntegratorStateGPU); + offsetof(IntegratorStateGPU, sort_partition_divisor); for (size_t offset = 0; offset < pointer_block_end; offset += sizeof(device_ptr)) { int pointer_index = int(offset / sizeof(device_ptr)); MetalDevice::MetalMem *mmem = *( diff --git a/intern/cycles/device/metal/util.h b/intern/cycles/device/metal/util.h index fd32d8a260f..a988d01d361 100644 --- a/intern/cycles/device/metal/util.h +++ b/intern/cycles/device/metal/util.h @@ -37,6 +37,7 @@ struct MetalInfo { static int get_apple_gpu_core_count(id device); static MetalGPUVendor get_device_vendor(id device); static AppleGPUArchitecture get_apple_gpu_architecture(id device); + static int optimal_sort_partition_elements(id device); static string get_device_name(id device); }; diff --git a/intern/cycles/device/metal/util.mm b/intern/cycles/device/metal/util.mm index a7a5b596b8f..c336dc310c8 100644 --- a/intern/cycles/device/metal/util.mm +++ b/intern/cycles/device/metal/util.mm @@ -72,6 +72,24 @@ MetalGPUVendor MetalInfo::get_device_vendor(id device) return METAL_GPU_UNKNOWN; } +int MetalInfo::optimal_sort_partition_elements(id device) +{ + if (auto str = getenv("CYCLES_METAL_SORT_PARTITION_ELEMENTS")) { + return atoi(str); + } + + /* On M1 and M2 GPUs, we see better cache utilization if we partition the active indices before + * sorting each partition by material. Partitioning into chunks of 65536 elements results in an + * overall render time speedup of up to 15%. */ + if (get_device_vendor(device) == METAL_GPU_APPLE) { + AppleGPUArchitecture arch = get_apple_gpu_architecture(device); + if (arch == APPLE_M1 || arch == APPLE_M2) { + return 65536; + } + } + return 0; +} + vector> const &MetalInfo::get_usable_devices() { static vector> usable_devices; diff --git a/intern/cycles/device/queue.h b/intern/cycles/device/queue.h index 14a5db3a204..20308e4a106 100644 --- a/intern/cycles/device/queue.h +++ b/intern/cycles/device/queue.h @@ -105,6 +105,14 @@ class DeviceQueue { * value. */ virtual int num_concurrent_busy_states() const = 0; + /* Number of partitions within which active indices are sorted by material ID. + * Using more partitions lets us trade off material coherence for better integrator state fetch + * locality. */ + virtual int num_sort_partitions(const size_t /*state_size*/) const + { + return 1; + } + /* Initialize execution of kernels on this queue. * * Will, for example, load all data required by the kernels from Device to global or path state. diff --git a/intern/cycles/integrator/path_trace.cpp b/intern/cycles/integrator/path_trace.cpp index 6912bf928cd..ed278821b46 100644 --- a/intern/cycles/integrator/path_trace.cpp +++ b/intern/cycles/integrator/path_trace.cpp @@ -373,7 +373,7 @@ void PathTrace::path_trace(RenderWork &render_work) work_balance_infos_[i].time_spent += work_time; work_balance_infos_[i].occupancy = statistics.occupancy; - VLOG_WORK << "Rendered " << num_samples << " samples in " << work_time << " seconds (" + VLOG_INFO << "Rendered " << num_samples << " samples in " << work_time << " seconds (" << work_time / num_samples << " seconds per sample), occupancy: " << statistics.occupancy; }); diff --git a/intern/cycles/integrator/path_trace_work_gpu.cpp b/intern/cycles/integrator/path_trace_work_gpu.cpp index e262c252ce3..d51e8a28bb4 100644 --- a/intern/cycles/integrator/path_trace_work_gpu.cpp +++ b/intern/cycles/integrator/path_trace_work_gpu.cpp @@ -182,18 +182,19 @@ void PathTraceWorkGPU::alloc_integrator_queue() void PathTraceWorkGPU::alloc_integrator_sorting() { /* Allocate arrays for shader sorting. */ - const int max_shaders = device_scene_->data.max_shaders; - if (integrator_shader_sort_counter_.size() < max_shaders) { - integrator_shader_sort_counter_.alloc(max_shaders); + num_sort_partitions_ = queue_->num_sort_partitions(estimate_single_state_size()); + const int sort_buckets = device_scene_->data.max_shaders * num_sort_partitions_; + if (integrator_shader_sort_counter_.size() < sort_buckets) { + integrator_shader_sort_counter_.alloc(sort_buckets); integrator_shader_sort_counter_.zero_to_device(); - integrator_shader_raytrace_sort_counter_.alloc(max_shaders); + integrator_shader_raytrace_sort_counter_.alloc(sort_buckets); integrator_shader_raytrace_sort_counter_.zero_to_device(); - integrator_shader_mnee_sort_counter_.alloc(max_shaders); + integrator_shader_mnee_sort_counter_.alloc(sort_buckets); integrator_shader_mnee_sort_counter_.zero_to_device(); - integrator_shader_sort_prefix_sum_.alloc(max_shaders); + integrator_shader_sort_prefix_sum_.alloc(sort_buckets); integrator_shader_sort_prefix_sum_.zero_to_device(); integrator_state_gpu_.sort_key_counter[DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE] = @@ -237,6 +238,10 @@ void PathTraceWorkGPU::init_execution() { queue_->init_execution(); + /* Setup sort partitioning divisor for better cache utilization. */ + integrator_state_gpu_.sort_partition_divisor = (int)divide_up(max_num_paths_, + num_sort_partitions_); + /* Copy to device side struct in constant memory. */ device_->const_copy_to( "integrator_state", &integrator_state_gpu_, sizeof(integrator_state_gpu_)); @@ -486,9 +491,9 @@ void PathTraceWorkGPU::compute_sorted_queued_paths(DeviceKernel kernel, /* Compute prefix sum of number of active paths with each shader. */ { const int work_size = 1; - int max_shaders = device_scene_->data.max_shaders; + int sort_buckets = device_scene_->data.max_shaders * num_sort_partitions_; - DeviceKernelArguments args(&d_counter, &d_prefix_sum, &max_shaders); + DeviceKernelArguments args(&d_counter, &d_prefix_sum, &sort_buckets); queue_->enqueue(DEVICE_KERNEL_PREFIX_SUM, work_size, args); } diff --git a/intern/cycles/integrator/path_trace_work_gpu.h b/intern/cycles/integrator/path_trace_work_gpu.h index 4c10a221a30..a805258d1b5 100644 --- a/intern/cycles/integrator/path_trace_work_gpu.h +++ b/intern/cycles/integrator/path_trace_work_gpu.h @@ -156,6 +156,9 @@ class PathTraceWorkGPU : public PathTraceWork { bool interop_use_checked_ = false; bool interop_use_ = false; + /* Number of partitions to sort state indices into prior to material sort. */ + int num_sort_partitions_; + /* Maximum number of concurrent integrator states. */ int max_num_paths_; diff --git a/intern/cycles/kernel/integrator/state.h b/intern/cycles/kernel/integrator/state.h index d6fef27f344..d10d31e930e 100644 --- a/intern/cycles/kernel/integrator/state.h +++ b/intern/cycles/kernel/integrator/state.h @@ -127,6 +127,9 @@ typedef struct IntegratorStateGPU { /* Index of main path which will be used by a next shadow catcher split. */ ccl_global int *next_main_path_index; + + /* Divisor used to partition active indices by locality when sorting by material. */ + uint sort_partition_divisor; } IntegratorStateGPU; /* Abstraction diff --git a/intern/cycles/kernel/integrator/state_flow.h b/intern/cycles/kernel/integrator/state_flow.h index fed74d49434..d397ef385e7 100644 --- a/intern/cycles/kernel/integrator/state_flow.h +++ b/intern/cycles/kernel/integrator/state_flow.h @@ -67,9 +67,17 @@ CCL_NAMESPACE_BEGIN &kernel_integrator_state.queue_counter->num_queued[current_kernel], 1); \ INTEGRATOR_STATE_WRITE(state, shadow_path, queued_kernel) = 0; +# ifdef __KERNEL_SORT_PARTITIONING__ +/* Sort first by truncated state index (for good locality), then by key (for good coherence). */ +# define INTEGRATOR_SORT_KEY(key, state) \ + (key + kernel_data.max_shaders * (state / kernel_integrator_state.sort_partition_divisor)) +# else +# define INTEGRATOR_SORT_KEY(key, state) (key) +# endif + # define INTEGRATOR_PATH_INIT_SORTED(next_kernel, key) \ { \ - const int key_ = key; \ + const int key_ = INTEGRATOR_SORT_KEY(key, state); \ atomic_fetch_and_add_uint32( \ &kernel_integrator_state.queue_counter->num_queued[next_kernel], 1); \ INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel; \ @@ -79,7 +87,7 @@ CCL_NAMESPACE_BEGIN } # define INTEGRATOR_PATH_NEXT_SORTED(current_kernel, next_kernel, key) \ { \ - const int key_ = key; \ + const int key_ = INTEGRATOR_SORT_KEY(key, state); \ atomic_fetch_and_sub_uint32( \ &kernel_integrator_state.queue_counter->num_queued[current_kernel], 1); \ atomic_fetch_and_add_uint32( \ -- cgit v1.2.3 From 02ce29c6ee82a22d1fce126188b8b9db1a221d4b Mon Sep 17 00:00:00 2001 From: Gaia Clary Date: Sun, 10 Jul 2022 10:35:05 +0200 Subject: Improve Tool tip for Add-on search Differential Revision: https://developer.blender.org/D15411 --- release/scripts/startup/bl_ui/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/release/scripts/startup/bl_ui/__init__.py b/release/scripts/startup/bl_ui/__init__.py index c61dde87d7c..c4e3df469b7 100644 --- a/release/scripts/startup/bl_ui/__init__.py +++ b/release/scripts/startup/bl_ui/__init__.py @@ -128,8 +128,8 @@ def register(): return items WindowManager.addon_search = StringProperty( - name="Search", - description="Search within the selected filter", + name="Filter", + description="Filter by add-on name, author & category", options={'TEXTEDIT_UPDATE'}, ) WindowManager.addon_filter = EnumProperty( -- cgit v1.2.3 From 5539fb3121313f91b6e46d982ef62ff97763d2c2 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 13 Jul 2022 15:23:50 +0200 Subject: Cycles: add presets to the Performance panel With choices Default, Lower Memory and Faster Render. For convenience, and to help communicate what the various settings do. Differential Revision: https://developer.blender.org/D15446 --- intern/cycles/blender/addon/presets.py | 26 ++++++++++++++++++++++ intern/cycles/blender/addon/ui.py | 10 +++++++++ .../scripts/presets/cycles/performance/Default.py | 12 ++++++++++ .../presets/cycles/performance/Faster_Render.py | 12 ++++++++++ .../presets/cycles/performance/Lower_Memory.py | 12 ++++++++++ 5 files changed, 72 insertions(+) create mode 100644 release/scripts/presets/cycles/performance/Default.py create mode 100644 release/scripts/presets/cycles/performance/Faster_Render.py create mode 100644 release/scripts/presets/cycles/performance/Lower_Memory.py diff --git a/intern/cycles/blender/addon/presets.py b/intern/cycles/blender/addon/presets.py index cc6d574da99..e1f08c07eaf 100644 --- a/intern/cycles/blender/addon/presets.py +++ b/intern/cycles/blender/addon/presets.py @@ -84,10 +84,36 @@ class AddPresetViewportSampling(AddPresetBase, Operator): preset_subdir = "cycles/viewport_sampling" +class AddPresetPerformance(AddPresetBase, Operator): + '''Add an Performance Preset''' + bl_idname = "render.cycles_performance_preset_add" + bl_label = "Add Performance Preset" + preset_menu = "CYCLES_PT_performance_presets" + + preset_defines = [ + "render = bpy.context.scene.render" + "cycles = bpy.context.scene.cycles" + ] + + preset_values = [ + "render.threads_mode", + "render.use_persistent_data", + "cycles.debug_use_spatial_splits", + "cycles.debug_use_compact_bvh", + "cycles.debug_use_hair_bvh", + "cycles.debug_bvh_time_steps", + "cycles.use_auto_tile", + "cycles.tile_size", + ] + + preset_subdir = "cycles/performance" + + classes = ( AddPresetIntegrator, AddPresetSampling, AddPresetViewportSampling, + AddPresetPerformance, ) diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index 09aecb5cb81..3c898d4be73 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -43,6 +43,12 @@ class CYCLES_PT_integrator_presets(CyclesPresetPanel): preset_add_operator = "render.cycles_integrator_preset_add" +class CYCLES_PT_performance_presets(CyclesPresetPanel): + bl_label = "Performance Presets" + preset_subdir = "cycles/performance" + preset_add_operator = "render.cycles_performance_preset_add" + + class CyclesButtonsPanel: bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -624,6 +630,9 @@ class CYCLES_RENDER_PT_performance(CyclesButtonsPanel, Panel): bl_label = "Performance" bl_options = {'DEFAULT_CLOSED'} + def draw_header_preset(self, context): + CYCLES_PT_performance_presets.draw_panel_header(self.layout) + def draw(self, context): pass @@ -2269,6 +2278,7 @@ classes = ( CYCLES_PT_sampling_presets, CYCLES_PT_viewport_sampling_presets, CYCLES_PT_integrator_presets, + CYCLES_PT_performance_presets, CYCLES_RENDER_PT_sampling, CYCLES_RENDER_PT_sampling_viewport, CYCLES_RENDER_PT_sampling_viewport_denoise, diff --git a/release/scripts/presets/cycles/performance/Default.py b/release/scripts/presets/cycles/performance/Default.py new file mode 100644 index 00000000000..5c25f23eca0 --- /dev/null +++ b/release/scripts/presets/cycles/performance/Default.py @@ -0,0 +1,12 @@ +import bpy +render = bpy.context.scene.render +cycles = bpy.context.scene.cycles + +render.threads_mode = 'AUTO' +render.use_persistent_data = False +cycles.debug_use_spatial_splits = False +cycles.debug_use_compact_bvh = False +cycles.debug_use_hair_bvh = True +cycles.debug_bvh_time_steps = 0 +cycles.use_auto_tile = True +cycles.tile_size = 2048 diff --git a/release/scripts/presets/cycles/performance/Faster_Render.py b/release/scripts/presets/cycles/performance/Faster_Render.py new file mode 100644 index 00000000000..7f1e3c68f1f --- /dev/null +++ b/release/scripts/presets/cycles/performance/Faster_Render.py @@ -0,0 +1,12 @@ +import bpy +render = bpy.context.scene.render +cycles = bpy.context.scene.cycles + +render.threads_mode = 'AUTO' +render.use_persistent_data = True +cycles.debug_use_spatial_splits = True +cycles.debug_use_compact_bvh = False +cycles.debug_use_hair_bvh = True +cycles.debug_bvh_time_steps = 2 +cycles.use_auto_tile = True +cycles.tile_size = 2048 diff --git a/release/scripts/presets/cycles/performance/Lower_Memory.py b/release/scripts/presets/cycles/performance/Lower_Memory.py new file mode 100644 index 00000000000..d1a45f1888d --- /dev/null +++ b/release/scripts/presets/cycles/performance/Lower_Memory.py @@ -0,0 +1,12 @@ +import bpy +render = bpy.context.scene.render +cycles = bpy.context.scene.cycles + +render.threads_mode = 'AUTO' +render.use_persistent_data = False +cycles.debug_use_spatial_splits = False +cycles.debug_use_compact_bvh = True +cycles.debug_use_hair_bvh = True +cycles.debug_bvh_time_steps = 0 +cycles.use_auto_tile = True +cycles.tile_size = 512 -- cgit v1.2.3 From 28c3739a9bc055b3a3e5ba2f5cfc219f70d64dfa Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 13 Jul 2022 14:22:42 +0200 Subject: Cleanup: replace state flow macros in the kernel with functions --- intern/cycles/kernel/integrator/init_from_bake.h | 8 +- intern/cycles/kernel/integrator/init_from_camera.h | 4 +- .../cycles/kernel/integrator/intersect_closest.h | 71 +++--- intern/cycles/kernel/integrator/intersect_shadow.h | 4 +- .../kernel/integrator/intersect_subsurface.h | 2 +- .../kernel/integrator/intersect_volume_stack.h | 2 +- intern/cycles/kernel/integrator/shade_background.h | 2 +- intern/cycles/kernel/integrator/shade_light.h | 4 +- intern/cycles/kernel/integrator/shade_shadow.h | 6 +- intern/cycles/kernel/integrator/shade_surface.h | 16 +- intern/cycles/kernel/integrator/shade_volume.h | 10 +- intern/cycles/kernel/integrator/shadow_catcher.h | 2 +- intern/cycles/kernel/integrator/state_flow.h | 267 +++++++++++++-------- intern/cycles/kernel/integrator/subsurface.h | 6 +- 14 files changed, 239 insertions(+), 165 deletions(-) diff --git a/intern/cycles/kernel/integrator/init_from_bake.h b/intern/cycles/kernel/integrator/init_from_bake.h index c63684d58e6..4d75dcea190 100644 --- a/intern/cycles/kernel/integrator/init_from_bake.h +++ b/intern/cycles/kernel/integrator/init_from_bake.h @@ -181,7 +181,7 @@ ccl_device bool integrator_init_from_bake(KernelGlobals kg, integrator_state_write_ray(kg, state, &ray); /* Setup next kernel to execute. */ - INTEGRATOR_PATH_INIT(DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND); + integrator_path_init(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND); } else { /* Surface baking. */ @@ -247,13 +247,13 @@ ccl_device bool integrator_init_from_bake(KernelGlobals kg, const bool use_raytrace_kernel = (shader_flags & SD_HAS_RAYTRACE); if (use_caustics) { - INTEGRATOR_PATH_INIT_SORTED(DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE, shader_index); + integrator_path_init_sorted(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE, shader_index); } else if (use_raytrace_kernel) { - INTEGRATOR_PATH_INIT_SORTED(DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE, shader_index); + integrator_path_init_sorted(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE, shader_index); } else { - INTEGRATOR_PATH_INIT_SORTED(DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE, shader_index); + integrator_path_init_sorted(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE, shader_index); } } diff --git a/intern/cycles/kernel/integrator/init_from_camera.h b/intern/cycles/kernel/integrator/init_from_camera.h index 9fe27cdda9a..73db13be697 100644 --- a/intern/cycles/kernel/integrator/init_from_camera.h +++ b/intern/cycles/kernel/integrator/init_from_camera.h @@ -100,10 +100,10 @@ ccl_device bool integrator_init_from_camera(KernelGlobals kg, /* Continue with intersect_closest kernel, optionally initializing volume * stack before that if the camera may be inside a volume. */ if (kernel_data.cam.is_inside_volume) { - INTEGRATOR_PATH_INIT(DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK); + integrator_path_init(kg, state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK); } else { - INTEGRATOR_PATH_INIT(DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST); + integrator_path_init(kg, state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST); } return true; diff --git a/intern/cycles/kernel/integrator/intersect_closest.h b/intern/cycles/kernel/integrator/intersect_closest.h index 621aa05f46b..923dab9591a 100644 --- a/intern/cycles/kernel/integrator/intersect_closest.h +++ b/intern/cycles/kernel/integrator/intersect_closest.h @@ -109,14 +109,14 @@ ccl_device_forceinline void integrator_split_shadow_catcher( /* If using background pass, schedule background shading kernel so that we have a background * to alpha-over on. The background kernel will then continue the path afterwards. */ INTEGRATOR_STATE_WRITE(state, path, flag) |= PATH_RAY_SHADOW_CATCHER_BACKGROUND; - INTEGRATOR_PATH_INIT(DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND); + integrator_path_init(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND); return; } if (!integrator_state_volume_stack_is_empty(kg, state)) { /* Volume stack is not empty. Re-init the volume stack to exclude any non-shadow catcher * objects from it, and then continue shading volume and shadow catcher surface after. */ - INTEGRATOR_PATH_INIT(DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK); + integrator_path_init(kg, state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK); return; } @@ -128,18 +128,19 @@ ccl_device_forceinline void integrator_split_shadow_catcher( const bool use_raytrace_kernel = (flags & SD_HAS_RAYTRACE); if (use_caustics) { - INTEGRATOR_PATH_INIT_SORTED(DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE, shader); + integrator_path_init_sorted(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE, shader); } else if (use_raytrace_kernel) { - INTEGRATOR_PATH_INIT_SORTED(DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE, shader); + integrator_path_init_sorted( + kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE, shader); } else { - INTEGRATOR_PATH_INIT_SORTED(DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE, shader); + integrator_path_init_sorted(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE, shader); } } /* Schedule next kernel to be executed after updating volume stack for shadow catcher. */ -template +template ccl_device_forceinline void integrator_intersect_next_kernel_after_shadow_catcher_volume( KernelGlobals kg, IntegratorState state) { @@ -156,20 +157,21 @@ ccl_device_forceinline void integrator_intersect_next_kernel_after_shadow_catche const bool use_raytrace_kernel = (flags & SD_HAS_RAYTRACE); if (use_caustics) { - INTEGRATOR_PATH_NEXT_SORTED( - current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE, shader); + integrator_path_next_sorted( + kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE, shader); } else if (use_raytrace_kernel) { - INTEGRATOR_PATH_NEXT_SORTED( - current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE, shader); + integrator_path_next_sorted( + kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE, shader); } else { - INTEGRATOR_PATH_NEXT_SORTED(current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE, shader); + integrator_path_next_sorted( + kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE, shader); } } /* Schedule next kernel to be executed after executing background shader for shadow catcher. */ -template +template ccl_device_forceinline void integrator_intersect_next_kernel_after_shadow_catcher_background( KernelGlobals kg, IntegratorState state) { @@ -177,7 +179,8 @@ ccl_device_forceinline void integrator_intersect_next_kernel_after_shadow_catche if (!integrator_state_volume_stack_is_empty(kg, state)) { /* Volume stack is not empty. Re-init the volume stack to exclude any non-shadow catcher * objects from it, and then continue shading volume and shadow catcher surface after. */ - INTEGRATOR_PATH_NEXT(current_kernel, DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK); + integrator_path_next( + kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK); return; } @@ -190,7 +193,7 @@ ccl_device_forceinline void integrator_intersect_next_kernel_after_shadow_catche * * Note that current_kernel is a template value since making this a variable * leads to poor performance with CUDA atomics. */ -template +template ccl_device_forceinline void integrator_intersect_next_kernel( KernelGlobals kg, IntegratorState state, @@ -206,10 +209,10 @@ ccl_device_forceinline void integrator_intersect_next_kernel( const int flags = (hit_surface) ? kernel_data_fetch(shaders, shader).flags : 0; if (!integrator_intersect_terminate(kg, state, flags)) { - INTEGRATOR_PATH_NEXT(current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_VOLUME); + integrator_path_next(kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_VOLUME); } else { - INTEGRATOR_PATH_TERMINATE(current_kernel); + integrator_path_terminate(kg, state, current_kernel); } return; } @@ -218,7 +221,7 @@ ccl_device_forceinline void integrator_intersect_next_kernel( if (hit) { /* Hit a surface, continue with light or surface kernel. */ if (isect->type & PRIMITIVE_LAMP) { - INTEGRATOR_PATH_NEXT(current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT); + integrator_path_next(kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT); } else { /* Hit a surface, continue with surface kernel unless terminated. */ @@ -231,16 +234,16 @@ ccl_device_forceinline void integrator_intersect_next_kernel( (object_flags & SD_OBJECT_CAUSTICS); const bool use_raytrace_kernel = (flags & SD_HAS_RAYTRACE); if (use_caustics) { - INTEGRATOR_PATH_NEXT_SORTED( - current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE, shader); + integrator_path_next_sorted( + kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE, shader); } else if (use_raytrace_kernel) { - INTEGRATOR_PATH_NEXT_SORTED( - current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE, shader); + integrator_path_next_sorted( + kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE, shader); } else { - INTEGRATOR_PATH_NEXT_SORTED( - current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE, shader); + integrator_path_next_sorted( + kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE, shader); } #ifdef __SHADOW_CATCHER__ @@ -249,13 +252,13 @@ ccl_device_forceinline void integrator_intersect_next_kernel( #endif } else { - INTEGRATOR_PATH_TERMINATE(current_kernel); + integrator_path_terminate(kg, state, current_kernel); } } } else { /* Nothing hit, continue with background kernel. */ - INTEGRATOR_PATH_NEXT(current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND); + integrator_path_next(kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND); } } @@ -263,7 +266,7 @@ ccl_device_forceinline void integrator_intersect_next_kernel( * * The logic here matches integrator_intersect_next_kernel, except that * volume shading and termination testing have already been done. */ -template +template ccl_device_forceinline void integrator_intersect_next_kernel_after_volume( KernelGlobals kg, IntegratorState state, @@ -273,7 +276,7 @@ ccl_device_forceinline void integrator_intersect_next_kernel_after_volume( if (isect->prim != PRIM_NONE) { /* Hit a surface, continue with light or surface kernel. */ if (isect->type & PRIMITIVE_LAMP) { - INTEGRATOR_PATH_NEXT(current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT); + integrator_path_next(kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT); return; } else { @@ -286,16 +289,16 @@ ccl_device_forceinline void integrator_intersect_next_kernel_after_volume( const bool use_raytrace_kernel = (flags & SD_HAS_RAYTRACE); if (use_caustics) { - INTEGRATOR_PATH_NEXT_SORTED( - current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE, shader); + integrator_path_next_sorted( + kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE, shader); } else if (use_raytrace_kernel) { - INTEGRATOR_PATH_NEXT_SORTED( - current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE, shader); + integrator_path_next_sorted( + kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE, shader); } else { - INTEGRATOR_PATH_NEXT_SORTED( - current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE, shader); + integrator_path_next_sorted( + kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE, shader); } #ifdef __SHADOW_CATCHER__ @@ -307,7 +310,7 @@ ccl_device_forceinline void integrator_intersect_next_kernel_after_volume( } else { /* Nothing hit, continue with background kernel. */ - INTEGRATOR_PATH_NEXT(current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND); + integrator_path_next(kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND); return; } } diff --git a/intern/cycles/kernel/integrator/intersect_shadow.h b/intern/cycles/kernel/integrator/intersect_shadow.h index 3e746998225..adcbfa19afe 100644 --- a/intern/cycles/kernel/integrator/intersect_shadow.h +++ b/intern/cycles/kernel/integrator/intersect_shadow.h @@ -162,7 +162,7 @@ ccl_device void integrator_intersect_shadow(KernelGlobals kg, IntegratorShadowSt if (opaque_hit) { /* Hit an opaque surface, shadow path ends here. */ - INTEGRATOR_SHADOW_PATH_TERMINATE(DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW); + integrator_shadow_path_terminate(kg, state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW); return; } else { @@ -171,7 +171,7 @@ ccl_device void integrator_intersect_shadow(KernelGlobals kg, IntegratorShadowSt * * TODO: could also write to render buffer directly if no transparent shadows? * Could save a kernel execution for the common case. */ - INTEGRATOR_SHADOW_PATH_NEXT(DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW, + integrator_shadow_path_next(kg, state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW, DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW); return; } diff --git a/intern/cycles/kernel/integrator/intersect_subsurface.h b/intern/cycles/kernel/integrator/intersect_subsurface.h index 0a2c4ad680d..f439d6905a0 100644 --- a/intern/cycles/kernel/integrator/intersect_subsurface.h +++ b/intern/cycles/kernel/integrator/intersect_subsurface.h @@ -17,7 +17,7 @@ ccl_device void integrator_intersect_subsurface(KernelGlobals kg, IntegratorStat } #endif - INTEGRATOR_PATH_TERMINATE(DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE); + integrator_path_terminate(kg, state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE); } CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/integrator/intersect_volume_stack.h b/intern/cycles/kernel/integrator/intersect_volume_stack.h index 49ef01dc870..68c7fdd5909 100644 --- a/intern/cycles/kernel/integrator/intersect_volume_stack.h +++ b/intern/cycles/kernel/integrator/intersect_volume_stack.h @@ -222,7 +222,7 @@ ccl_device void integrator_intersect_volume_stack(KernelGlobals kg, IntegratorSt } else { /* Volume stack init for camera rays, continue with intersection of camera ray. */ - INTEGRATOR_PATH_NEXT(DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK, + integrator_path_next(kg, state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK, DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST); } } diff --git a/intern/cycles/kernel/integrator/shade_background.h b/intern/cycles/kernel/integrator/shade_background.h index 4791a963ae6..47233634463 100644 --- a/intern/cycles/kernel/integrator/shade_background.h +++ b/intern/cycles/kernel/integrator/shade_background.h @@ -213,7 +213,7 @@ ccl_device void integrator_shade_background(KernelGlobals kg, } #endif - INTEGRATOR_PATH_TERMINATE(DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND); + integrator_path_terminate(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND); } CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/integrator/shade_light.h b/intern/cycles/kernel/integrator/shade_light.h index be926c78439..7b3e9e0ee7e 100644 --- a/intern/cycles/kernel/integrator/shade_light.h +++ b/intern/cycles/kernel/integrator/shade_light.h @@ -99,11 +99,11 @@ ccl_device void integrator_shade_light(KernelGlobals kg, INTEGRATOR_STATE_WRITE(state, path, transparent_bounce) = transparent_bounce; if (transparent_bounce >= kernel_data.integrator.transparent_max_bounce) { - INTEGRATOR_PATH_TERMINATE(DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT); + integrator_path_terminate(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT); return; } else { - INTEGRATOR_PATH_NEXT(DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT, + integrator_path_next(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT, DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST); return; } diff --git a/intern/cycles/kernel/integrator/shade_shadow.h b/intern/cycles/kernel/integrator/shade_shadow.h index 2b929b7b62e..afe77590e9a 100644 --- a/intern/cycles/kernel/integrator/shade_shadow.h +++ b/intern/cycles/kernel/integrator/shade_shadow.h @@ -158,20 +158,20 @@ ccl_device void integrator_shade_shadow(KernelGlobals kg, /* Evaluate transparent shadows. */ const bool opaque = integrate_transparent_shadow(kg, state, num_hits); if (opaque) { - INTEGRATOR_SHADOW_PATH_TERMINATE(DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW); + integrator_shadow_path_terminate(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW); return; } #endif if (shadow_intersections_has_remaining(num_hits)) { /* More intersections to find, continue shadow ray. */ - INTEGRATOR_SHADOW_PATH_NEXT(DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW, + integrator_shadow_path_next(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW); return; } else { kernel_accum_light(kg, state, render_buffer); - INTEGRATOR_SHADOW_PATH_TERMINATE(DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW); + integrator_shadow_path_terminate(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW); return; } } diff --git a/intern/cycles/kernel/integrator/shade_surface.h b/intern/cycles/kernel/integrator/shade_surface.h index 57b88b806a4..91e34968148 100644 --- a/intern/cycles/kernel/integrator/shade_surface.h +++ b/intern/cycles/kernel/integrator/shade_surface.h @@ -190,8 +190,8 @@ ccl_device_forceinline void integrate_surface_direct_light(KernelGlobals kg, const bool is_light = light_sample_is_light(&ls); /* Branch off shadow kernel. */ - INTEGRATOR_SHADOW_PATH_INIT( - shadow_state, state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW, shadow); + IntegratorShadowState shadow_state = integrator_shadow_path_init( + kg, state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW, false); /* Copy volume stack and enter/exit volume. */ integrator_state_copy_volume_stack_to_shadow(kg, shadow_state, state); @@ -442,7 +442,8 @@ ccl_device_forceinline void integrate_surface_ao(KernelGlobals kg, ray.dD = differential_zero_compact(); /* Branch off shadow kernel. */ - INTEGRATOR_SHADOW_PATH_INIT(shadow_state, state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW, ao); + IntegratorShadowState shadow_state = integrator_shadow_path_init( + kg, state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW, true); /* Copy volume stack and enter/exit volume. */ integrator_state_copy_volume_stack_to_shadow(kg, shadow_state, state); @@ -604,22 +605,23 @@ ccl_device bool integrate_surface(KernelGlobals kg, } template + DeviceKernel current_kernel = DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE> ccl_device_forceinline void integrator_shade_surface(KernelGlobals kg, IntegratorState state, ccl_global float *ccl_restrict render_buffer) { if (integrate_surface(kg, state, render_buffer)) { if (INTEGRATOR_STATE(state, path, flag) & PATH_RAY_SUBSURFACE) { - INTEGRATOR_PATH_NEXT(current_kernel, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE); + integrator_path_next( + kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE); } else { kernel_assert(INTEGRATOR_STATE(state, ray, t) != 0.0f); - INTEGRATOR_PATH_NEXT(current_kernel, DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST); + integrator_path_next(kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST); } } else { - INTEGRATOR_PATH_TERMINATE(current_kernel); + integrator_path_terminate(kg, state, current_kernel); } } diff --git a/intern/cycles/kernel/integrator/shade_volume.h b/intern/cycles/kernel/integrator/shade_volume.h index 6cf80f4ddc5..683c031f0d9 100644 --- a/intern/cycles/kernel/integrator/shade_volume.h +++ b/intern/cycles/kernel/integrator/shade_volume.h @@ -774,8 +774,8 @@ ccl_device_forceinline void integrate_volume_direct_light( const bool is_light = light_sample_is_light(ls); /* Branch off shadow kernel. */ - INTEGRATOR_SHADOW_PATH_INIT( - shadow_state, state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW, shadow); + IntegratorShadowState shadow_state = integrator_shadow_path_init( + kg, state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW, false); /* Write shadow ray and associated state to global memory. */ integrator_state_write_shadow_ray(kg, shadow_state, &ray); @@ -1032,13 +1032,15 @@ ccl_device void integrator_shade_volume(KernelGlobals kg, if (event == VOLUME_PATH_SCATTERED) { /* Queue intersect_closest kernel. */ - INTEGRATOR_PATH_NEXT(DEVICE_KERNEL_INTEGRATOR_SHADE_VOLUME, + integrator_path_next(kg, + state, + DEVICE_KERNEL_INTEGRATOR_SHADE_VOLUME, DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST); return; } else if (event == VOLUME_PATH_MISSED) { /* End path. */ - INTEGRATOR_PATH_TERMINATE(DEVICE_KERNEL_INTEGRATOR_SHADE_VOLUME); + integrator_path_terminate(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_VOLUME); return; } else { diff --git a/intern/cycles/kernel/integrator/shadow_catcher.h b/intern/cycles/kernel/integrator/shadow_catcher.h index 42d44580f80..ff63625aceb 100644 --- a/intern/cycles/kernel/integrator/shadow_catcher.h +++ b/intern/cycles/kernel/integrator/shadow_catcher.h @@ -50,7 +50,7 @@ ccl_device_inline bool kernel_shadow_catcher_is_path_split_bounce(KernelGlobals ccl_device_inline bool kernel_shadow_catcher_path_can_split(KernelGlobals kg, ConstIntegratorState state) { - if (INTEGRATOR_PATH_IS_TERMINATED) { + if (integrator_path_is_terminated(state)) { return false; } diff --git a/intern/cycles/kernel/integrator/state_flow.h b/intern/cycles/kernel/integrator/state_flow.h index d397ef385e7..1ae746022d0 100644 --- a/intern/cycles/kernel/integrator/state_flow.h +++ b/intern/cycles/kernel/integrator/state_flow.h @@ -10,62 +10,94 @@ CCL_NAMESPACE_BEGIN /* Control Flow * - * Utilities for control flow between kernels. The implementation may differ per device - * or even be handled on the host side. To abstract such differences, experiment with - * different implementations and for debugging, this is abstracted using macros. + * Utilities for control flow between kernels. The implementation is different between CPU and + * GPU devices. For the latter part of the logic is handled on the host side with wavefronts. * * There is a main path for regular path tracing camera for path tracing. Shadows for next * event estimation branch off from this into their own path, that may be computed in - * parallel while the main path continues. + * parallel while the main path continues. Additionally, shading kernels are sorted using + * a key for coherence. * * Each kernel on the main path must call one of these functions. These may not be called * multiple times from the same kernel. * - * INTEGRATOR_PATH_INIT(next_kernel) - * INTEGRATOR_PATH_NEXT(current_kernel, next_kernel) - * INTEGRATOR_PATH_TERMINATE(current_kernel) + * integrator_path_init(kg, state, next_kernel) + * integrator_path_next(kg, state, current_kernel, next_kernel) + * integrator_path_terminate(kg, state, current_kernel) * * For the shadow path similar functions are used, and again each shadow kernel must call * one of them, and only once. */ -#define INTEGRATOR_PATH_IS_TERMINATED (INTEGRATOR_STATE(state, path, queued_kernel) == 0) -#define INTEGRATOR_SHADOW_PATH_IS_TERMINATED \ - (INTEGRATOR_STATE(state, shadow_path, queued_kernel) == 0) +ccl_device_forceinline bool integrator_path_is_terminated(ConstIntegratorState state) +{ + return INTEGRATOR_STATE(state, path, queued_kernel) == 0; +} + +ccl_device_forceinline bool integrator_shadow_path_is_terminated(ConstIntegratorShadowState state) +{ + return INTEGRATOR_STATE(state, shadow_path, queued_kernel) == 0; +} #ifdef __KERNEL_GPU__ -# define INTEGRATOR_PATH_INIT(next_kernel) \ - atomic_fetch_and_add_uint32(&kernel_integrator_state.queue_counter->num_queued[next_kernel], \ - 1); \ - INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel; -# define INTEGRATOR_PATH_NEXT(current_kernel, next_kernel) \ - atomic_fetch_and_sub_uint32( \ - &kernel_integrator_state.queue_counter->num_queued[current_kernel], 1); \ - atomic_fetch_and_add_uint32(&kernel_integrator_state.queue_counter->num_queued[next_kernel], \ - 1); \ - INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel; -# define INTEGRATOR_PATH_TERMINATE(current_kernel) \ - atomic_fetch_and_sub_uint32( \ - &kernel_integrator_state.queue_counter->num_queued[current_kernel], 1); \ - INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = 0; - -# define INTEGRATOR_SHADOW_PATH_INIT(shadow_state, state, next_kernel, shadow_type) \ - IntegratorShadowState shadow_state = atomic_fetch_and_add_uint32( \ - &kernel_integrator_state.next_shadow_path_index[0], 1); \ - atomic_fetch_and_add_uint32(&kernel_integrator_state.queue_counter->num_queued[next_kernel], \ - 1); \ - INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, queued_kernel) = next_kernel; -# define INTEGRATOR_SHADOW_PATH_NEXT(current_kernel, next_kernel) \ - atomic_fetch_and_sub_uint32( \ - &kernel_integrator_state.queue_counter->num_queued[current_kernel], 1); \ - atomic_fetch_and_add_uint32(&kernel_integrator_state.queue_counter->num_queued[next_kernel], \ - 1); \ - INTEGRATOR_STATE_WRITE(state, shadow_path, queued_kernel) = next_kernel; -# define INTEGRATOR_SHADOW_PATH_TERMINATE(current_kernel) \ - atomic_fetch_and_sub_uint32( \ - &kernel_integrator_state.queue_counter->num_queued[current_kernel], 1); \ - INTEGRATOR_STATE_WRITE(state, shadow_path, queued_kernel) = 0; +ccl_device_forceinline void integrator_path_init(KernelGlobals kg, + IntegratorState state, + const DeviceKernel next_kernel) +{ + atomic_fetch_and_add_uint32(&kernel_integrator_state.queue_counter->num_queued[next_kernel], 1); + INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel; +} + +ccl_device_forceinline void integrator_path_next(KernelGlobals kg, + IntegratorState state, + const DeviceKernel current_kernel, + const DeviceKernel next_kernel) +{ + atomic_fetch_and_sub_uint32(&kernel_integrator_state.queue_counter->num_queued[current_kernel], + 1); + atomic_fetch_and_add_uint32(&kernel_integrator_state.queue_counter->num_queued[next_kernel], 1); + INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel; +} + +ccl_device_forceinline void integrator_path_terminate(KernelGlobals kg, + IntegratorState state, + const DeviceKernel current_kernel) +{ + atomic_fetch_and_sub_uint32(&kernel_integrator_state.queue_counter->num_queued[current_kernel], + 1); + INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = 0; +} + +ccl_device_forceinline IntegratorShadowState integrator_shadow_path_init( + KernelGlobals kg, IntegratorState state, const DeviceKernel next_kernel, const bool is_ao) +{ + IntegratorShadowState shadow_state = atomic_fetch_and_add_uint32( + &kernel_integrator_state.next_shadow_path_index[0], 1); + atomic_fetch_and_add_uint32(&kernel_integrator_state.queue_counter->num_queued[next_kernel], 1); + INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, queued_kernel) = next_kernel; + return shadow_state; +} + +ccl_device_forceinline void integrator_shadow_path_next(KernelGlobals kg, + IntegratorShadowState state, + const DeviceKernel current_kernel, + const DeviceKernel next_kernel) +{ + atomic_fetch_and_sub_uint32(&kernel_integrator_state.queue_counter->num_queued[current_kernel], + 1); + atomic_fetch_and_add_uint32(&kernel_integrator_state.queue_counter->num_queued[next_kernel], 1); + INTEGRATOR_STATE_WRITE(state, shadow_path, queued_kernel) = next_kernel; +} + +ccl_device_forceinline void integrator_shadow_path_terminate(KernelGlobals kg, + IntegratorShadowState state, + const DeviceKernel current_kernel) +{ + atomic_fetch_and_sub_uint32(&kernel_integrator_state.queue_counter->num_queued[current_kernel], + 1); + INTEGRATOR_STATE_WRITE(state, shadow_path, queued_kernel) = 0; +} # ifdef __KERNEL_SORT_PARTITIONING__ /* Sort first by truncated state index (for good locality), then by key (for good coherence). */ @@ -75,68 +107,103 @@ CCL_NAMESPACE_BEGIN # define INTEGRATOR_SORT_KEY(key, state) (key) # endif -# define INTEGRATOR_PATH_INIT_SORTED(next_kernel, key) \ - { \ - const int key_ = INTEGRATOR_SORT_KEY(key, state); \ - atomic_fetch_and_add_uint32( \ - &kernel_integrator_state.queue_counter->num_queued[next_kernel], 1); \ - INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel; \ - INTEGRATOR_STATE_WRITE(state, path, shader_sort_key) = key_; \ - atomic_fetch_and_add_uint32(&kernel_integrator_state.sort_key_counter[next_kernel][key_], \ - 1); \ - } -# define INTEGRATOR_PATH_NEXT_SORTED(current_kernel, next_kernel, key) \ - { \ - const int key_ = INTEGRATOR_SORT_KEY(key, state); \ - atomic_fetch_and_sub_uint32( \ - &kernel_integrator_state.queue_counter->num_queued[current_kernel], 1); \ - atomic_fetch_and_add_uint32( \ - &kernel_integrator_state.queue_counter->num_queued[next_kernel], 1); \ - INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel; \ - INTEGRATOR_STATE_WRITE(state, path, shader_sort_key) = key_; \ - atomic_fetch_and_add_uint32(&kernel_integrator_state.sort_key_counter[next_kernel][key_], \ - 1); \ - } +ccl_device_forceinline void integrator_path_init_sorted(KernelGlobals kg, + IntegratorState state, + const DeviceKernel next_kernel, + const uint32_t key) +{ + const int key_ = INTEGRATOR_SORT_KEY(key, state); + atomic_fetch_and_add_uint32(&kernel_integrator_state.queue_counter->num_queued[next_kernel], 1); + INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel; + INTEGRATOR_STATE_WRITE(state, path, shader_sort_key) = key_; + atomic_fetch_and_add_uint32(&kernel_integrator_state.sort_key_counter[next_kernel][key_], 1); +} + +ccl_device_forceinline void integrator_path_next_sorted(KernelGlobals kg, + IntegratorState state, + const DeviceKernel current_kernel, + const DeviceKernel next_kernel, + const uint32_t key) +{ + const int key_ = INTEGRATOR_SORT_KEY(key, state); + atomic_fetch_and_sub_uint32(&kernel_integrator_state.queue_counter->num_queued[current_kernel], + 1); + atomic_fetch_and_add_uint32(&kernel_integrator_state.queue_counter->num_queued[next_kernel], 1); + INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel; + INTEGRATOR_STATE_WRITE(state, path, shader_sort_key) = key_; + atomic_fetch_and_add_uint32(&kernel_integrator_state.sort_key_counter[next_kernel][key_], 1); +} #else -# define INTEGRATOR_PATH_INIT(next_kernel) \ - INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel; -# define INTEGRATOR_PATH_INIT_SORTED(next_kernel, key) \ - { \ - INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel; \ - (void)key; \ - } -# define INTEGRATOR_PATH_NEXT(current_kernel, next_kernel) \ - { \ - INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel; \ - (void)current_kernel; \ - } -# define INTEGRATOR_PATH_TERMINATE(current_kernel) \ - { \ - INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = 0; \ - (void)current_kernel; \ - } -# define INTEGRATOR_PATH_NEXT_SORTED(current_kernel, next_kernel, key) \ - { \ - INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel; \ - (void)key; \ - (void)current_kernel; \ - } - -# define INTEGRATOR_SHADOW_PATH_INIT(shadow_state, state, next_kernel, shadow_type) \ - IntegratorShadowState shadow_state = &state->shadow_type; \ - INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, queued_kernel) = next_kernel; -# define INTEGRATOR_SHADOW_PATH_NEXT(current_kernel, next_kernel) \ - { \ - INTEGRATOR_STATE_WRITE(state, shadow_path, queued_kernel) = next_kernel; \ - (void)current_kernel; \ - } -# define INTEGRATOR_SHADOW_PATH_TERMINATE(current_kernel) \ - { \ - INTEGRATOR_STATE_WRITE(state, shadow_path, queued_kernel) = 0; \ - (void)current_kernel; \ - } +ccl_device_forceinline void integrator_path_init(KernelGlobals kg, + IntegratorState state, + const DeviceKernel next_kernel) +{ + INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel; +} + +ccl_device_forceinline void integrator_path_init_sorted(KernelGlobals kg, + IntegratorState state, + const DeviceKernel next_kernel, + const uint32_t key) +{ + INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel; + (void)key; +} + +ccl_device_forceinline void integrator_path_next(KernelGlobals kg, + IntegratorState state, + const DeviceKernel current_kernel, + const DeviceKernel next_kernel) +{ + INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel; + (void)current_kernel; +} + +ccl_device_forceinline void integrator_path_terminate(KernelGlobals kg, + IntegratorState state, + const DeviceKernel current_kernel) +{ + INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = 0; + (void)current_kernel; +} + +ccl_device_forceinline void integrator_path_next_sorted(KernelGlobals kg, + IntegratorState state, + const DeviceKernel current_kernel, + const DeviceKernel next_kernel, + const uint32_t key) +{ + INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel; + (void)key; + (void)current_kernel; +} + +ccl_device_forceinline IntegratorShadowState integrator_shadow_path_init( + KernelGlobals kg, IntegratorState state, const DeviceKernel next_kernel, const bool is_ao) +{ + IntegratorShadowState shadow_state = (is_ao) ? &state->ao : &state->shadow; + INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, queued_kernel) = next_kernel; + return shadow_state; +} + +ccl_device_forceinline void integrator_shadow_path_next(KernelGlobals kg, + IntegratorShadowState state, + const DeviceKernel current_kernel, + const DeviceKernel next_kernel) +{ + INTEGRATOR_STATE_WRITE(state, shadow_path, queued_kernel) = next_kernel; + (void)current_kernel; +} + +ccl_device_forceinline void integrator_shadow_path_terminate(KernelGlobals kg, + IntegratorShadowState state, + const DeviceKernel current_kernel) +{ + INTEGRATOR_STATE_WRITE(state, shadow_path, queued_kernel) = 0; + (void)current_kernel; +} #endif diff --git a/intern/cycles/kernel/integrator/subsurface.h b/intern/cycles/kernel/integrator/subsurface.h index 1e6fcf4aff0..09e59919c86 100644 --- a/intern/cycles/kernel/integrator/subsurface.h +++ b/intern/cycles/kernel/integrator/subsurface.h @@ -177,17 +177,17 @@ ccl_device_inline bool subsurface_scatter(KernelGlobals kg, IntegratorState stat const bool use_raytrace_kernel = (shader_flags & SD_HAS_RAYTRACE); if (use_caustics) { - INTEGRATOR_PATH_NEXT_SORTED(DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE, + integrator_path_next_sorted(kg, state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE, shader); } else if (use_raytrace_kernel) { - INTEGRATOR_PATH_NEXT_SORTED(DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE, + integrator_path_next_sorted(kg, state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE, shader); } else { - INTEGRATOR_PATH_NEXT_SORTED(DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE, + integrator_path_next_sorted(kg, state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE, shader); } -- cgit v1.2.3 From 1b5db02a0207621a0325c0d2ee6f748b2520d2fe Mon Sep 17 00:00:00 2001 From: Olivier Maury Date: Thu, 14 Jul 2022 15:59:26 +0200 Subject: Fix Cycles MNEE wrong results with area light spread When the solve is successful, the light sample needs to be updated since the effective shading point is now on the last refractive interface. Spread was not taken into account, creating false caustics. Differential Revision: https://developer.blender.org/D15449 --- intern/cycles/kernel/integrator/mnee.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/intern/cycles/kernel/integrator/mnee.h b/intern/cycles/kernel/integrator/mnee.h index 67505b9b612..70b009d3b48 100644 --- a/intern/cycles/kernel/integrator/mnee.h +++ b/intern/cycles/kernel/integrator/mnee.h @@ -137,8 +137,14 @@ ccl_device_forceinline void mnee_update_light_sample(KernelGlobals kg, } } else if (ls->type == LIGHT_AREA) { + float invarea = fabsf(klight->area.invarea); ls->D = normalize_len(ls->P - P, &ls->t); - ls->pdf = fabsf(klight->area.invarea); + ls->pdf = invarea; + if (klight->area.tan_spread > 0.f) { + ls->eval_fac = 0.25f * invarea; + ls->eval_fac *= light_spread_attenuation( + ls->D, ls->Ng, klight->area.tan_spread, klight->area.normalize_spread); + } } ls->pdf *= kernel_data.integrator.pdf_lights; -- cgit v1.2.3 From 3b15467e97abf473d4d25c7382999115d3169a57 Mon Sep 17 00:00:00 2001 From: Antonio Vazquez Date: Thu, 14 Jul 2022 16:33:21 +0200 Subject: Fix T99702: Gpencil Flip strokes did not support multiframe edit This was a missing feature and this commit solves this. --- source/blender/editors/gpencil/gpencil_edit.c | 51 ++++++++++++++++----------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index 06f3c169fa9..71cf9b1fafd 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -3709,35 +3709,44 @@ static int gpencil_stroke_flip_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + bool changed = false; - /* read all selected strokes */ + /* Read all selected strokes. */ CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { - bGPDframe *gpf = gpl->actframe; - if (gpf == NULL) { - continue; - } + bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; - LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - if (gps->flag & GP_STROKE_SELECT) { - /* skip strokes that are invalid for current view */ - if (ED_gpencil_stroke_can_use(C, gps) == false) { - continue; - } - /* check if the color is editable */ - if (ED_gpencil_stroke_material_editable(ob, gpl, gps) == false) { + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { + if (gpf == NULL) { continue; } + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + if (gps->flag & GP_STROKE_SELECT) { + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } + /* check if the color is editable */ + if (ED_gpencil_stroke_material_editable(ob, gpl, gps) == false) { + continue; + } - if (is_curve_edit) { - BKE_report(op->reports, RPT_ERROR, "Not implemented!"); - } - else { - /* Flip stroke. */ - BKE_gpencil_stroke_flip(gps); + if (is_curve_edit) { + BKE_report(op->reports, RPT_ERROR, "Not implemented!"); + } + else { + /* Flip stroke. */ + BKE_gpencil_stroke_flip(gps); + changed = true; + } + } } - - changed = true; + } + /* If not multi-edit, exit loop. */ + if (!is_multiedit) { + break; } } } -- cgit v1.2.3 From b6de6da59afbf84b21df661d096f324360c763d7 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Thu, 14 Jul 2022 18:46:52 +0200 Subject: I18n: Fix regex for messages from `BKE_modifier_set_error`. Signature of this function changed at some point, regex to extract messages from it was no longer working. Reported/detected as part of D15418. --- release/scripts/modules/bl_i18n_utils/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release/scripts/modules/bl_i18n_utils/settings.py b/release/scripts/modules/bl_i18n_utils/settings.py index 7aeef80b0bd..9b38c512d31 100644 --- a/release/scripts/modules/bl_i18n_utils/settings.py +++ b/release/scripts/modules/bl_i18n_utils/settings.py @@ -248,7 +248,7 @@ PYGETTEXT_KEYWORDS = (() + tuple(("{}\\((?:[^\"',]+,){{3}}\\s*" + _msg_re + r"\s*\)").format(it) for it in ("BMO_error_raise",)) + - tuple(("{}\\((?:[^\"',]+,)\\s*" + _msg_re + r"\s*(?:\)|,)").format(it) + tuple(("{}\\((?:[^\"',]+,){{2}}\\s*" + _msg_re + r"\s*(?:\)|,)").format(it) for it in ("BKE_modifier_set_error",)) + # bUnitDef unit names. -- cgit v1.2.3 From 1ef686bd26cc3c89849f41770ce76d7b94f169db Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Thu, 14 Jul 2022 19:07:13 +0200 Subject: UI: Tweak layout of File Browser Preferences * Don't nest "Show Recent Locations" and "Show System Locations" under a "Defaults" heading. They are not just a default setting but completely hide panels from the UI. * Use own "Show Locations" heading instead, and remove redundant words from labels. * Move the options to the top of the panel, they are more general since they can't be toggled in a File Browser session, and thus have bigger impact. We may want to remove these options in a future major release, I don't think they are useful. Agreed on with Pablo Vazquez. --- release/scripts/startup/bl_ui/space_userpref.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py index cccbb63d27c..7a651e8c3c8 100644 --- a/release/scripts/startup/bl_ui/space_userpref.py +++ b/release/scripts/startup/bl_ui/space_userpref.py @@ -1485,11 +1485,13 @@ class USERPREF_PT_saveload_file_browser(SaveLoadPanel, CenterAlignMixIn, Panel): prefs = context.preferences paths = prefs.filepaths + col = layout.column(heading="Show Locations") + col.prop(paths, "show_recent_locations", text="Recent") + col.prop(paths, "show_system_bookmarks", text="System") + col = layout.column(heading="Defaults") col.prop(paths, "use_filter_files") col.prop(paths, "show_hidden_files_datablocks") - col.prop(paths, "show_recent_locations") - col.prop(paths, "show_system_bookmarks") # ----------------------------------------------------------------------------- -- cgit v1.2.3 From bdd0ac5bcebd6deb3d590ada9a3f25c6ddd58ea4 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Thu, 14 Jul 2022 19:21:56 +0200 Subject: Fix `on_drag_start` handler not getting ID when dragging from Outliner We would first invoke the dragging, and then set the drag data (like the ID or the dragged modifier), so the `wmDropBox.on_drag_start()` handler wouldn't be able to access this. This broke dragging some IDs from the Outliner, noticed in D15333. It's now possible to first create/request drag data, extend it, and then invoke the actual dragging. The normal function to start dragging returns `void` now instead of `wmDrag *`, so the drag data can't easily be modified after starting anymore. --- source/blender/editors/interface/interface_drag.cc | 4 +++- .../editors/space_outliner/outliner_dragdrop.cc | 4 +++- source/blender/windowmanager/WM_api.h | 18 ++++++++++++++++-- source/blender/windowmanager/intern/wm_dragdrop.c | 19 +++++++++++++++---- 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/source/blender/editors/interface/interface_drag.cc b/source/blender/editors/interface/interface_drag.cc index 4c68870b2c7..1db3db32411 100644 --- a/source/blender/editors/interface/interface_drag.cc +++ b/source/blender/editors/interface/interface_drag.cc @@ -122,7 +122,7 @@ bool ui_but_drag_is_draggable(const uiBut *but) void ui_but_drag_start(bContext *C, uiBut *but) { - wmDrag *drag = WM_event_start_drag(C, + wmDrag *drag = WM_drag_data_create(C, but->icon, but->dragtype, but->dragpoin, @@ -136,6 +136,8 @@ void ui_but_drag_start(bContext *C, uiBut *but) WM_event_drag_image(drag, but->imb, but->imb_scale); } + WM_event_start_prepared_drag(C, drag); + /* Special feature for assets: We add another drag item that supports multiple assets. It * gets the assets from context. */ if (ELEM(but->dragtype, WM_DRAG_ASSET, WM_DRAG_ID)) { diff --git a/source/blender/editors/space_outliner/outliner_dragdrop.cc b/source/blender/editors/space_outliner/outliner_dragdrop.cc index c72080be811..7435fa50a93 100644 --- a/source/blender/editors/space_outliner/outliner_dragdrop.cc +++ b/source/blender/editors/space_outliner/outliner_dragdrop.cc @@ -1455,7 +1455,7 @@ static int outliner_item_drag_drop_invoke(bContext *C, TSE_GPENCIL_EFFECT_BASE); const int wm_drag_type = use_datastack_drag ? WM_DRAG_DATASTACK : WM_DRAG_ID; - wmDrag *drag = WM_event_start_drag(C, data.icon, wm_drag_type, nullptr, 0.0, WM_DRAG_NOP); + wmDrag *drag = WM_drag_data_create(C, data.icon, wm_drag_type, nullptr, 0.0, WM_DRAG_NOP); if (use_datastack_drag) { TreeElement *te_bone = nullptr; @@ -1545,6 +1545,8 @@ static int outliner_item_drag_drop_invoke(bContext *C, WM_drag_add_local_ID(drag, data.drag_id, data.drag_parent); } + WM_event_start_prepared_drag(C, drag); + ED_outliner_select_sync_from_outliner(C, space_outliner); return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH); diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index f337dd9d89a..44c5b86857d 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -1214,10 +1214,24 @@ int WM_operator_flag_only_pass_through_on_press(int retval, const struct wmEvent /* Drag and drop. */ /** - * Note that the pointer should be valid allocated and not on stack. + * Start dragging immediately with the given data. + * Note that \a poin should be valid allocated and not on stack. */ -struct wmDrag *WM_event_start_drag( +void WM_event_start_drag( struct bContext *C, int icon, int type, void *poin, double value, unsigned int flags); +/** + * Create and fill the dragging data, but don't start dragging just yet (unlike + * #WM_event_start_drag()). Must be followed up by #WM_event_start_prepared_drag(), otherwise the + * returned pointer will leak memory. + * + * Note that \a poin should be valid allocated and not on stack. + */ +wmDrag *WM_drag_data_create( + struct bContext *C, int icon, int type, void *poin, double value, unsigned int flags); +/** + * Invoke dragging using the given \a drag data. + */ +void WM_event_start_prepared_drag(struct bContext *C, wmDrag *drag); void WM_event_drag_image(struct wmDrag *, struct ImBuf *, float scale); void WM_drag_free(struct wmDrag *drag); void WM_drag_data_free(int dragtype, void *poin); diff --git a/source/blender/windowmanager/intern/wm_dragdrop.c b/source/blender/windowmanager/intern/wm_dragdrop.c index 546ba795892..36bd69a9b25 100644 --- a/source/blender/windowmanager/intern/wm_dragdrop.c +++ b/source/blender/windowmanager/intern/wm_dragdrop.c @@ -175,16 +175,14 @@ static void wm_dropbox_invoke(bContext *C, wmDrag *drag) } } -wmDrag *WM_event_start_drag( +wmDrag *WM_drag_data_create( struct bContext *C, int icon, int type, void *poin, double value, unsigned int flags) { - wmWindowManager *wm = CTX_wm_manager(C); wmDrag *drag = MEM_callocN(sizeof(struct wmDrag), "new drag"); /* Keep track of future multi-touch drag too, add a mouse-pointer id or so. */ /* if multiple drags are added, they're drawn as list */ - BLI_addtail(&wm->drags, drag); drag->flags = flags; drag->icon = icon; drag->type = type; @@ -226,9 +224,22 @@ wmDrag *WM_event_start_drag( } drag->value = value; + return drag; +} + +void WM_event_start_prepared_drag(bContext *C, wmDrag *drag) +{ + wmWindowManager *wm = CTX_wm_manager(C); + + BLI_addtail(&wm->drags, drag); wm_dropbox_invoke(C, drag); +} - return drag; +void WM_event_start_drag( + struct bContext *C, int icon, int type, void *poin, double value, unsigned int flags) +{ + wmDrag *drag = WM_drag_data_create(C, icon, type, poin, value, flags); + WM_event_start_prepared_drag(C, drag); } void wm_drags_exit(wmWindowManager *wm, wmWindow *win) -- cgit v1.2.3 From 9fedcde750bb1f6fbbdc00f8086b12f1fd523231 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Thu, 14 Jul 2022 20:05:14 +0200 Subject: Modifiers: fix mesh to volume modifier on non-volume objects --- source/blender/modifiers/intern/MOD_mesh_to_volume.cc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/source/blender/modifiers/intern/MOD_mesh_to_volume.cc b/source/blender/modifiers/intern/MOD_mesh_to_volume.cc index 01c1875a760..39bd013609b 100644 --- a/source/blender/modifiers/intern/MOD_mesh_to_volume.cc +++ b/source/blender/modifiers/intern/MOD_mesh_to_volume.cc @@ -7,6 +7,7 @@ #include #include "BKE_geometry_set.hh" +#include "BKE_lib_id.h" #include "BKE_lib_query.h" #include "BKE_mesh_runtime.h" #include "BKE_mesh_wrapper.h" @@ -161,7 +162,13 @@ static Volume *mesh_to_volume(ModifierData *md, mesh_to_own_object_space_transform); /* Create a new volume. */ - Volume *volume = BKE_volume_new_for_eval(input_volume); + Volume *volume; + if (input_volume == nullptr) { + volume = static_cast(BKE_id_new_nomain(ID_VO, "Volume")); + } + else { + volume = BKE_volume_new_for_eval(input_volume); + } /* Convert mesh to grid and add to volume. */ geometry::volume_grid_add_from_mesh(volume, -- cgit v1.2.3 From b1329d7eaa52a11c73b75d19d20bd8f6d11ac535 Mon Sep 17 00:00:00 2001 From: Ray Molenkamp Date: Thu, 14 Jul 2022 12:18:35 -0600 Subject: Fix T99705: fix integer overflow in thumbnail extractor It was smart enough to check if the buffer had the right size but neglected to cast to a 64 bit value so it overflowed. Differential Revision: https://developer.blender.org/D15457 Reviewed By: brecht --- source/blender/blendthumb/src/blendthumb_extract.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/blender/blendthumb/src/blendthumb_extract.cc b/source/blender/blendthumb/src/blendthumb_extract.cc index de1f50dfdce..369da559fc8 100644 --- a/source/blender/blendthumb/src/blendthumb_extract.cc +++ b/source/blender/blendthumb/src/blendthumb_extract.cc @@ -134,7 +134,8 @@ static eThumbStatus blendthumb_extract_from_file_impl(FileReader *file, /* Verify that image dimensions and data size make sense. */ size_t data_size = block_size - 8; - const size_t expected_size = thumb->width * thumb->height * 4; + const uint64_t expected_size = static_cast(thumb->width) * + static_cast(thumb->height) * 4; if (thumb->width < 0 || thumb->height < 0 || data_size != expected_size) { return BT_INVALID_THUMB; } -- cgit v1.2.3 From 0e9367fc29bc42c80e10b7097facec2b029a7362 Mon Sep 17 00:00:00 2001 From: Chris Blackbourn Date: Fri, 15 Jul 2022 10:04:50 +1200 Subject: Cleanup: separate clipUVTransform into two different functions No functional changes. Prep for D15420 / T98061. --- .../blender/editors/transform/transform_convert.c | 126 --------------------- .../blender/editors/transform/transform_convert.h | 1 - .../editors/transform/transform_mode_resize.c | 99 +++++++++++++++- .../editors/transform/transform_mode_translate.c | 57 +++++++++- 4 files changed, 154 insertions(+), 129 deletions(-) diff --git a/source/blender/editors/transform/transform_convert.c b/source/blender/editors/transform/transform_convert.c index a23689166c4..bab700560fe 100644 --- a/source/blender/editors/transform/transform_convert.c +++ b/source/blender/editors/transform/transform_convert.c @@ -479,132 +479,6 @@ TransDataCurveHandleFlags *initTransDataCurveHandles(TransData *td, struct BezTr /** \name UV Coordinates * \{ */ -/** - * Find the correction for the scaling factor when "Constrain to Bounds" is active. - * \param numerator: How far the UV boundary (unit square) is from the origin of the scale. - * \param denominator: How far the AABB is from the origin of the scale. - * \param scale: Scale parameter to update. - */ -static void constrain_scale_to_boundary(const float numerator, - const float denominator, - float *scale) -{ - if (denominator == 0.0f) { - /* The origin of the scale is on the edge of the boundary. */ - if (numerator < 0.0f) { - /* Negative scale will wrap around and put us outside the boundary. */ - *scale = 0.0f; /* Hold at the boundary instead. */ - } - return; /* Nothing else we can do without more info. */ - } - - const float correction = numerator / denominator; - if (correction < 0.0f || !isfinite(correction)) { - /* TODO: Correction is negative or invalid, but we lack context to fix `*scale`. */ - return; - } - - if (denominator < 0.0f) { - /* Scale origin is outside boundary, only make scale bigger. */ - if (*scale < correction) { - *scale = correction; - } - return; - } - - /* Scale origin is inside boundary, the "regular" case, limit maximum scale. */ - if (*scale > correction) { - *scale = correction; - } -} - -bool clipUVTransform(TransInfo *t, float vec[2], const bool resize) -{ - bool clipx = true, clipy = true; - float min[2], max[2]; - - /* Check if the current image in UV editor is a tiled image or not. */ - const SpaceImage *sima = t->area->spacedata.first; - const Image *image = sima->image; - const bool is_tiled_image = image && (image->source == IMA_SRC_TILED); - /* Stores the coordinates of the closest UDIM tile. - * Also acts as an offset to the tile from the origin of UV space. */ - float base_offset[2] = {0.0f, 0.0f}; - - /* If tiled image then constrain to correct/closest UDIM tile, else 0-1 UV space. */ - if (is_tiled_image) { - int nearest_tile_index = BKE_image_find_nearest_tile(image, t->center_global); - if (nearest_tile_index != -1) { - nearest_tile_index -= 1001; - /* Getting coordinates of nearest tile from the tile index. */ - base_offset[0] = nearest_tile_index % 10; - base_offset[1] = nearest_tile_index / 10; - } - } - - min[0] = min[1] = FLT_MAX; - max[0] = max[1] = FLT_MIN; - - FOREACH_TRANS_DATA_CONTAINER (t, tc) { - - TransData *td; - int a; - - for (a = 0, td = tc->data; a < tc->data_len; a++, td++) { - minmax_v2v2_v2(min, max, td->loc); - } - } - - if (resize) { - /* Assume no change is required. */ - float scalex = 1.0f; - float scaley = 1.0f; - - /* Update U against the left border. */ - constrain_scale_to_boundary( - t->center_global[0] - base_offset[0], t->center_global[0] - min[0], &scalex); - /* Now the right border, negated, because `-1.0 / -1.0 = 1.0` */ - constrain_scale_to_boundary(base_offset[0] + t->aspect[0] - t->center_global[0], - max[0] - t->center_global[0], - &scalex); - - /* Do the same for the V co-ordinate, which is called `y`. */ - constrain_scale_to_boundary( - t->center_global[1] - base_offset[1], t->center_global[1] - min[1], &scaley); - constrain_scale_to_boundary(base_offset[1] + t->aspect[1] - t->center_global[1], - max[1] - t->center_global[1], - &scaley); - - clipx = (scalex != 1.0f); - clipy = (scaley != 1.0f); - vec[0] *= scalex; - vec[1] *= scaley; - } - else { - if (min[0] < base_offset[0]) { - vec[0] += base_offset[0] - min[0]; - } - else if (max[0] > base_offset[0] + t->aspect[0]) { - vec[0] -= max[0] - base_offset[0] - t->aspect[0]; - } - else { - clipx = 0; - } - - if (min[1] < base_offset[1]) { - vec[1] += base_offset[1] - min[1]; - } - else if (max[1] > base_offset[1] + t->aspect[1]) { - vec[1] -= max[1] - base_offset[1] - t->aspect[1]; - } - else { - clipy = 0; - } - } - - return (clipx || clipy); -} - void clipUVData(TransInfo *t) { FOREACH_TRANS_DATA_CONTAINER (t, tc) { diff --git a/source/blender/editors/transform/transform_convert.h b/source/blender/editors/transform/transform_convert.h index 037fbe26c77..f25a3db3f75 100644 --- a/source/blender/editors/transform/transform_convert.h +++ b/source/blender/editors/transform/transform_convert.h @@ -34,7 +34,6 @@ int special_transform_moving(TransInfo *t); void special_aftertrans_update(struct bContext *C, TransInfo *t); void sort_trans_data_dist(TransInfo *t); void createTransData(struct bContext *C, TransInfo *t); -bool clipUVTransform(TransInfo *t, float vec[2], bool resize); void clipUVData(TransInfo *t); void transform_convert_flush_handle2D(TransData *td, TransData2D *td2d, float y_fac); /** diff --git a/source/blender/editors/transform/transform_mode_resize.c b/source/blender/editors/transform/transform_mode_resize.c index 31d40486afc..bc45ec07eab 100644 --- a/source/blender/editors/transform/transform_mode_resize.c +++ b/source/blender/editors/transform/transform_mode_resize.c @@ -11,6 +11,7 @@ #include "BLI_task.h" #include "BKE_context.h" +#include "BKE_image.h" #include "BKE_unit.h" #include "ED_screen.h" @@ -84,6 +85,102 @@ static void ApplySnapResize(TransInfo *t, float vec[3]) } } +/** + * Find the correction for the scaling factor when "Constrain to Bounds" is active. + * \param numerator: How far the UV boundary (unit square) is from the origin of the scale. + * \param denominator: How far the AABB is from the origin of the scale. + * \param scale: Scale parameter to update. + */ +static void constrain_scale_to_boundary(const float numerator, + const float denominator, + float *scale) +{ + if (denominator == 0.0f) { + /* The origin of the scale is on the edge of the boundary. */ + if (numerator < 0.0f) { + /* Negative scale will wrap around and put us outside the boundary. */ + *scale = 0.0f; /* Hold at the boundary instead. */ + } + return; /* Nothing else we can do without more info. */ + } + + const float correction = numerator / denominator; + if (correction < 0.0f || !isfinite(correction)) { + /* TODO: Correction is negative or invalid, but we lack context to fix `*scale`. */ + return; + } + + if (denominator < 0.0f) { + /* Scale origin is outside boundary, only make scale bigger. */ + if (*scale < correction) { + *scale = correction; + } + return; + } + + /* Scale origin is inside boundary, the "regular" case, limit maximum scale. */ + if (*scale > correction) { + *scale = correction; + } +} + +static bool clip_uv_transform_resize(TransInfo *t, float vec[2]) +{ + /* Assume no change is required. */ + float scalex = 1.0f; + float scaley = 1.0f; + + /* Check if the current image in UV editor is a tiled image or not. */ + const SpaceImage *sima = t->area->spacedata.first; + const Image *image = sima->image; + const bool is_tiled_image = image && (image->source == IMA_SRC_TILED); + + /* Stores the coordinates of the closest UDIM tile. + * Also acts as an offset to the tile from the origin of UV space. */ + float base_offset[2] = {0.0f, 0.0f}; + + /* If tiled image then constrain to correct/closest UDIM tile, else 0-1 UV space. */ + if (is_tiled_image) { + int nearest_tile_index = BKE_image_find_nearest_tile(image, t->center_global); + if (nearest_tile_index != -1) { + nearest_tile_index -= 1001; + /* Getting coordinates of nearest tile from the tile index. */ + base_offset[0] = nearest_tile_index % 10; + base_offset[1] = nearest_tile_index / 10; + } + } + + float min[2], max[2]; + min[0] = min[1] = FLT_MAX; + max[0] = max[1] = -FLT_MAX; + + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + + TransData *td; + int a; + for (a = 0, td = tc->data; a < tc->data_len; a++, td++) { + minmax_v2v2_v2(min, max, td->loc); + } + } + + /* Update U against the left border. */ + constrain_scale_to_boundary( + t->center_global[0] - base_offset[0], t->center_global[0] - min[0], &scalex); + /* Now the right border, negated, because `-1.0 / -1.0 = 1.0` */ + constrain_scale_to_boundary( + base_offset[0] + t->aspect[0] - t->center_global[0], max[0] - t->center_global[0], &scalex); + + /* Do the same for the V co-ordinate, which is called `y`. */ + constrain_scale_to_boundary( + t->center_global[1] - base_offset[1], t->center_global[1] - min[1], &scaley); + constrain_scale_to_boundary( + base_offset[1] + t->aspect[1] - t->center_global[1], max[1] - t->center_global[1], &scaley); + + vec[0] *= scalex; + vec[1] *= scaley; + return (scalex != 1.0f) || (scaley != 1.0f); +} + static void applyResize(TransInfo *t, const int UNUSED(mval[2])) { float mat[3][3]; @@ -157,7 +254,7 @@ static void applyResize(TransInfo *t, const int UNUSED(mval[2])) } /* Evil hack - redo resize if clipping needed. */ - if (t->flag & T_CLIP_UV && clipUVTransform(t, t->values_final, 1)) { + if (t->flag & T_CLIP_UV && clip_uv_transform_resize(t, t->values_final)) { size_to_mat3(mat, t->values_final); if (t->con.mode & CON_APPLY) { diff --git a/source/blender/editors/transform/transform_mode_translate.c b/source/blender/editors/transform/transform_mode_translate.c index 65690f9069d..67bdeb3fed0 100644 --- a/source/blender/editors/transform/transform_mode_translate.c +++ b/source/blender/editors/transform/transform_mode_translate.c @@ -16,6 +16,7 @@ #include "BLI_task.h" #include "BKE_context.h" +#include "BKE_image.h" #include "BKE_report.h" #include "BKE_unit.h" @@ -434,6 +435,60 @@ static void applyTranslationValue(TransInfo *t, const float vec[3]) custom_data->prev.rotate_mode = rotate_mode; } +static bool clip_uv_transform_translation(TransInfo *t, float vec[2]) +{ + /* Check if the current image in UV editor is a tiled image or not. */ + const SpaceImage *sima = t->area->spacedata.first; + const Image *image = sima->image; + const bool is_tiled_image = image && (image->source == IMA_SRC_TILED); + + /* Stores the coordinates of the closest UDIM tile. + * Also acts as an offset to the tile from the origin of UV space. */ + float base_offset[2] = {0.0f, 0.0f}; + + /* If tiled image then constrain to correct/closest UDIM tile, else 0-1 UV space. */ + if (is_tiled_image) { + int nearest_tile_index = BKE_image_find_nearest_tile(image, t->center_global); + if (nearest_tile_index != -1) { + nearest_tile_index -= 1001; + /* Getting coordinates of nearest tile from the tile index. */ + base_offset[0] = nearest_tile_index % 10; + base_offset[1] = nearest_tile_index / 10; + } + } + + float min[2], max[2]; + min[0] = min[1] = FLT_MAX; + max[0] = max[1] = -FLT_MAX; + + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + for (TransData *td = tc->data; td < tc->data + tc->data_len; td++) { + minmax_v2v2_v2(min, max, td->loc); + } + } + + bool result = false; + if (min[0] < base_offset[0]) { + vec[0] += base_offset[0] - min[0]; + result = true; + } + else if (max[0] > base_offset[0] + t->aspect[0]) { + vec[0] -= max[0] - base_offset[0] - t->aspect[0]; + result = true; + } + + if (min[1] < base_offset[1]) { + vec[1] += base_offset[1] - min[1]; + result = true; + } + else if (max[1] > base_offset[1] + t->aspect[1]) { + vec[1] -= max[1] - base_offset[1] - t->aspect[1]; + result = true; + } + + return result; +} + static void applyTranslation(TransInfo *t, const int UNUSED(mval[2])) { char str[UI_MAX_DRAW_STR]; @@ -498,7 +553,7 @@ static void applyTranslation(TransInfo *t, const int UNUSED(mval[2])) applyTranslationValue(t, global_dir); /* evil hack - redo translation if clipping needed */ - if (t->flag & T_CLIP_UV && clipUVTransform(t, global_dir, 0)) { + if (t->flag & T_CLIP_UV && clip_uv_transform_translation(t, global_dir)) { applyTranslationValue(t, global_dir); /* In proportional edit it can happen that */ -- cgit v1.2.3 From 178868cf42594bf7eedfa4db93ba8b7f3bf017ce Mon Sep 17 00:00:00 2001 From: Chris Blackbourn Date: Thu, 14 Jul 2022 12:40:43 +1200 Subject: Fix T79304: improve uv island calculation when in edge selection mode Differential Revision: https://developer.blender.org/D15419 --- source/blender/editors/mesh/editmesh_utils.c | 144 +++++++++++++++++++++++++-- 1 file changed, 137 insertions(+), 7 deletions(-) diff --git a/source/blender/editors/mesh/editmesh_utils.c b/source/blender/editors/mesh/editmesh_utils.c index 60a666ee755..ac5530c8ea9 100644 --- a/source/blender/editors/mesh/editmesh_utils.c +++ b/source/blender/editors/mesh/editmesh_utils.c @@ -595,6 +595,132 @@ UvMapVert *BM_uv_vert_map_at_index(UvVertMap *vmap, uint v) #define INVALID_ISLAND ((unsigned int)-1) +static void bm_uv_assign_island(UvElementMap *element_map, + UvElement *element, + int nisland, + uint *map, + UvElement *islandbuf, + int islandbufsize) +{ + element->island = nisland; + map[element - element_map->buf] = islandbufsize; + + /* Copy *element to islandbuf[islandbufsize]. */ + islandbuf[islandbufsize].l = element->l; + islandbuf[islandbufsize].separate = element->separate; + islandbuf[islandbufsize].loop_of_poly_index = element->loop_of_poly_index; + islandbuf[islandbufsize].island = element->island; + islandbuf[islandbufsize].flag = element->flag; +} + +static int bm_uv_edge_select_build_islands(UvElementMap *element_map, + const Scene *scene, + UvElement *islandbuf, + uint *map, + bool uv_selected, + int cd_loop_uv_offset) +{ + int totuv = element_map->totalUVs; + + /* For each UvElement, locate the "separate" UvElement that precedes it in the linked list. */ + UvElement **head_table = MEM_mallocN(sizeof(*head_table) * totuv, "uv_island_head_table"); + for (int i = 0; i < totuv; i++) { + UvElement *head = element_map->buf + i; + if (head->separate) { + UvElement *element = head; + while (element) { + head_table[element - element_map->buf] = head; + element = element->next; + if (element && element->separate) { + break; + } + } + } + } + + /* Depth first search the graph, building islands as we go. */ + int nislands = 0; + int islandbufsize = 0; + int stack_upper_bound = totuv; + UvElement **stack_uv = MEM_mallocN(sizeof(*stack_uv) * stack_upper_bound, + "uv_island_element_stack"); + int stacksize_uv = 0; + for (int i = 0; i < totuv; i++) { + UvElement *element = element_map->buf + i; + if (element->island != INVALID_ISLAND) { + /* Unique UV (element and all it's children) are already part of an island. */ + continue; + } + + /* Create a new island, i.e. nislands++. */ + + BLI_assert(element->separate); /* Ensure we're the head of this unique UV. */ + + /* Seed the graph search. */ + stack_uv[stacksize_uv++] = element; + while (element) { + bm_uv_assign_island(element_map, element, nislands, map, islandbuf, islandbufsize++); + element = element->next; + if (element && element->separate) { + break; + } + } + + /* Traverse the graph. */ + while (stacksize_uv) { + BLI_assert(stacksize_uv < stack_upper_bound); + element = stack_uv[--stacksize_uv]; + while (element) { + + /* Scan forwards around the BMFace that contains element->l. */ + if (!uv_selected || uvedit_edge_select_test(scene, element->l, cd_loop_uv_offset)) { + UvElement *next = BM_uv_element_get(element_map, element->l->next->f, element->l->next); + if (next->island == INVALID_ISLAND) { + UvElement *tail = head_table[next - element_map->buf]; + stack_uv[stacksize_uv++] = tail; + while (tail) { + bm_uv_assign_island(element_map, tail, nislands, map, islandbuf, islandbufsize++); + tail = tail->next; + if (tail && tail->separate) { + break; + } + } + } + } + + /* Scan backwards around the BMFace that contains element->l. */ + if (!uv_selected || uvedit_edge_select_test(scene, element->l->prev, cd_loop_uv_offset)) { + UvElement *prev = BM_uv_element_get(element_map, element->l->prev->f, element->l->prev); + if (prev->island == INVALID_ISLAND) { + UvElement *tail = head_table[prev - element_map->buf]; + stack_uv[stacksize_uv++] = tail; + while (tail) { + bm_uv_assign_island(element_map, tail, nislands, map, islandbuf, islandbufsize++); + tail = tail->next; + if (tail && tail->separate) { + break; + } + } + } + } + + /* The same for all the UvElements in this unique UV. */ + element = element->next; + if (element && element->separate) { + break; + } + } + } + nislands++; + } + BLI_assert(islandbufsize == totuv); + + MEM_SAFE_FREE(stack_uv); + MEM_SAFE_FREE(head_table); + + return nislands; +} + UvElementMap *BM_uv_element_map_create(BMesh *bm, const Scene *scene, const bool uv_selected, @@ -783,6 +909,15 @@ UvElementMap *BM_uv_element_map_create(BMesh *bm, island_number = MEM_mallocN(sizeof(*island_number) * totfaces, "uv_island_number_face"); copy_vn_i(island_number, totfaces, INVALID_ISLAND); + const bool use_uv_edge_connectivity = scene->toolsettings->uv_flag & UV_SYNC_SELECTION ? + scene->toolsettings->selectmode & SCE_SELECT_EDGE : + scene->toolsettings->uv_selectmode & UV_SELECT_EDGE; + if (use_uv_edge_connectivity) { + nislands = bm_uv_edge_select_build_islands( + element_map, scene, islandbuf, map, uv_selected, cd_loop_uv_offset); + islandbufsize = totuv; + } + /* at this point, every UvElement in vert points to a UvElement sharing the same vertex. * Now we should sort uv's in islands. */ for (i = 0; i < totuv; i++) { @@ -810,13 +945,8 @@ UvElementMap *BM_uv_element_map_create(BMesh *bm, if (element->l->f == efa) { /* found the uv corresponding to our face and vertex. * Now fill it to the buffer */ - element->island = nislands; - map[element - element_map->buf] = islandbufsize; - islandbuf[islandbufsize].l = element->l; - islandbuf[islandbufsize].separate = element->separate; - islandbuf[islandbufsize].loop_of_poly_index = element->loop_of_poly_index; - islandbuf[islandbufsize].island = nislands; - islandbufsize++; + bm_uv_assign_island( + element_map, element, nislands, map, islandbuf, islandbufsize++); for (element = initelement; element; element = element->next) { if (element->separate && element != initelement) { -- cgit v1.2.3 From 675f6ef089ceeed2f03284e7e81e6af7130e46d7 Mon Sep 17 00:00:00 2001 From: Jesse Yurkovich Date: Thu, 14 Jul 2022 21:27:58 -0700 Subject: Cleanup: Use const pointers for ImageSaveOptions and ImageFormatData Use const pointers to ImageSaveOptions and ImageFormatData for API parameters where appropriate. Differential Revision: https://developer.blender.org/D15400 --- source/blender/blenkernel/BKE_image.h | 2 +- source/blender/blenkernel/BKE_image_save.h | 4 ++-- source/blender/blenkernel/BKE_writeffmpeg.h | 2 +- source/blender/blenkernel/intern/image.cc | 5 ++++- source/blender/blenkernel/intern/image_save.cc | 10 +++++----- source/blender/blenkernel/intern/writeffmpeg.c | 2 +- .../compositor/operations/COM_OutputFileMultiViewOperation.cc | 4 ++-- .../compositor/operations/COM_OutputFileMultiViewOperation.h | 4 ++-- .../blender/compositor/operations/COM_OutputFileOperation.cc | 2 +- source/blender/compositor/operations/COM_OutputFileOperation.h | 2 +- source/blender/editors/object/object_bake_api.c | 2 +- source/blender/editors/space_image/image_ops.c | 2 +- source/blender/nodes/NOD_composite.h | 2 +- .../nodes/composite/nodes/node_composite_output_file.cc | 2 +- 14 files changed, 24 insertions(+), 21 deletions(-) diff --git a/source/blender/blenkernel/BKE_image.h b/source/blender/blenkernel/BKE_image.h index 6ec1285af0c..4e622a5708f 100644 --- a/source/blender/blenkernel/BKE_image.h +++ b/source/blender/blenkernel/BKE_image.h @@ -97,7 +97,7 @@ int BKE_imbuf_write(struct ImBuf *ibuf, const char *name, const struct ImageForm */ int BKE_imbuf_write_as(struct ImBuf *ibuf, const char *name, - struct ImageFormatData *imf, + const struct ImageFormatData *imf, bool save_copy); /** diff --git a/source/blender/blenkernel/BKE_image_save.h b/source/blender/blenkernel/BKE_image_save.h index 673a7dffb82..e17136174eb 100644 --- a/source/blender/blenkernel/BKE_image_save.h +++ b/source/blender/blenkernel/BKE_image_save.h @@ -48,14 +48,14 @@ bool BKE_image_save_options_init(ImageSaveOptions *opts, struct ImageUser *iuser, const bool guess_path, const bool save_as_render); -void BKE_image_save_options_update(struct ImageSaveOptions *opts, struct Image *ima); +void BKE_image_save_options_update(struct ImageSaveOptions *opts, const struct Image *ima); void BKE_image_save_options_free(struct ImageSaveOptions *opts); bool BKE_image_save(struct ReportList *reports, struct Main *bmain, struct Image *ima, struct ImageUser *iuser, - struct ImageSaveOptions *opts); + const struct ImageSaveOptions *opts); /* Render saving. */ diff --git a/source/blender/blenkernel/BKE_writeffmpeg.h b/source/blender/blenkernel/BKE_writeffmpeg.h index 3f92d6fa117..736f7548bb4 100644 --- a/source/blender/blenkernel/BKE_writeffmpeg.h +++ b/source/blender/blenkernel/BKE_writeffmpeg.h @@ -68,7 +68,7 @@ void BKE_ffmpeg_filepath_get(char *string, const char *suffix); void BKE_ffmpeg_preset_set(struct RenderData *rd, int preset); -void BKE_ffmpeg_image_type_verify(struct RenderData *rd, struct ImageFormatData *imf); +void BKE_ffmpeg_image_type_verify(struct RenderData *rd, const struct ImageFormatData *imf); bool BKE_ffmpeg_alpha_channel_is_supported(const struct RenderData *rd); void *BKE_ffmpeg_context_create(void); diff --git a/source/blender/blenkernel/intern/image.cc b/source/blender/blenkernel/intern/image.cc index 92e98935e83..6ec1c52c61a 100644 --- a/source/blender/blenkernel/intern/image.cc +++ b/source/blender/blenkernel/intern/image.cc @@ -2475,7 +2475,10 @@ int BKE_imbuf_write(ImBuf *ibuf, const char *name, const ImageFormatData *imf) return ok; } -int BKE_imbuf_write_as(ImBuf *ibuf, const char *name, ImageFormatData *imf, const bool save_copy) +int BKE_imbuf_write_as(ImBuf *ibuf, + const char *name, + const ImageFormatData *imf, + const bool save_copy) { ImBuf ibuf_back = *ibuf; int ok; diff --git a/source/blender/blenkernel/intern/image_save.cc b/source/blender/blenkernel/intern/image_save.cc index 3f6f81845e2..cd86d3f7087 100644 --- a/source/blender/blenkernel/intern/image_save.cc +++ b/source/blender/blenkernel/intern/image_save.cc @@ -204,7 +204,7 @@ bool BKE_image_save_options_init(ImageSaveOptions *opts, return (ibuf != nullptr); } -void BKE_image_save_options_update(ImageSaveOptions *opts, Image *image) +void BKE_image_save_options_update(ImageSaveOptions *opts, const Image *image) { /* Auto update color space when changing save as render and file type. */ if (opts->save_as_render) { @@ -272,7 +272,7 @@ static void image_save_post(ReportList *reports, Image *ima, ImBuf *ibuf, int ok, - ImageSaveOptions *opts, + const ImageSaveOptions *opts, int save_copy, const char *filepath, bool *r_colorspace_changed) @@ -359,7 +359,7 @@ static void imbuf_save_post(ImBuf *ibuf, ImBuf *colormanaged_ibuf) static bool image_save_single(ReportList *reports, Image *ima, ImageUser *iuser, - ImageSaveOptions *opts, + const ImageSaveOptions *opts, bool *r_colorspace_changed) { void *lock; @@ -375,7 +375,7 @@ static bool image_save_single(ReportList *reports, ImBuf *colormanaged_ibuf = nullptr; const bool save_copy = opts->save_copy; const bool save_as_render = opts->save_as_render; - ImageFormatData *imf = &opts->im_format; + const ImageFormatData *imf = &opts->im_format; if (ima->type == IMA_TYPE_R_RESULT) { /* enforce user setting for RGB or RGBA, but skip BW */ @@ -620,7 +620,7 @@ static bool image_save_single(ReportList *reports, } bool BKE_image_save( - ReportList *reports, Main *bmain, Image *ima, ImageUser *iuser, ImageSaveOptions *opts) + ReportList *reports, Main *bmain, Image *ima, ImageUser *iuser, const ImageSaveOptions *opts) { /* For saving a tiled image we need an iuser, so use a local one if there isn't already one. */ ImageUser save_iuser; diff --git a/source/blender/blenkernel/intern/writeffmpeg.c b/source/blender/blenkernel/intern/writeffmpeg.c index 5e11cd0703a..883591e3e87 100644 --- a/source/blender/blenkernel/intern/writeffmpeg.c +++ b/source/blender/blenkernel/intern/writeffmpeg.c @@ -1485,7 +1485,7 @@ void BKE_ffmpeg_preset_set(RenderData *rd, int preset) } } -void BKE_ffmpeg_image_type_verify(RenderData *rd, ImageFormatData *imf) +void BKE_ffmpeg_image_type_verify(RenderData *rd, const ImageFormatData *imf) { int audio = 0; diff --git a/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.cc b/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.cc index 341541b4cdd..760ed94f882 100644 --- a/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.cc +++ b/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.cc @@ -21,7 +21,7 @@ OutputOpenExrSingleLayerMultiViewOperation::OutputOpenExrSingleLayerMultiViewOpe const RenderData *rd, const bNodeTree *tree, DataType datatype, - ImageFormatData *format, + const ImageFormatData *format, const char *path, const char *view_name, const bool save_as_render) @@ -243,7 +243,7 @@ OutputStereoOperation::OutputStereoOperation(const Scene *scene, const RenderData *rd, const bNodeTree *tree, DataType datatype, - ImageFormatData *format, + const ImageFormatData *format, const char *path, const char *name, const char *view_name, diff --git a/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.h b/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.h index 69f4011d340..e36999e5cf1 100644 --- a/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.h +++ b/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.h @@ -22,7 +22,7 @@ class OutputOpenExrSingleLayerMultiViewOperation : public OutputSingleLayerOpera const RenderData *rd, const bNodeTree *tree, DataType datatype, - ImageFormatData *format, + const ImageFormatData *format, const char *path, const char *view_name, bool save_as_render); @@ -57,7 +57,7 @@ class OutputStereoOperation : public OutputSingleLayerOperation { const RenderData *rd, const bNodeTree *tree, DataType datatype, - struct ImageFormatData *format, + const struct ImageFormatData *format, const char *path, const char *name, const char *view_name, diff --git a/source/blender/compositor/operations/COM_OutputFileOperation.cc b/source/blender/compositor/operations/COM_OutputFileOperation.cc index 49de275c256..1d22f3e8cd2 100644 --- a/source/blender/compositor/operations/COM_OutputFileOperation.cc +++ b/source/blender/compositor/operations/COM_OutputFileOperation.cc @@ -204,7 +204,7 @@ OutputSingleLayerOperation::OutputSingleLayerOperation(const Scene *scene, const RenderData *rd, const bNodeTree *tree, DataType datatype, - ImageFormatData *format, + const ImageFormatData *format, const char *path, const char *view_name, const bool save_as_render) diff --git a/source/blender/compositor/operations/COM_OutputFileOperation.h b/source/blender/compositor/operations/COM_OutputFileOperation.h index 875defe00e9..df1d68838d9 100644 --- a/source/blender/compositor/operations/COM_OutputFileOperation.h +++ b/source/blender/compositor/operations/COM_OutputFileOperation.h @@ -35,7 +35,7 @@ class OutputSingleLayerOperation : public MultiThreadedOperation { const RenderData *rd, const bNodeTree *tree, DataType datatype, - ImageFormatData *format, + const ImageFormatData *format, const char *path, const char *view_name, bool save_as_render); diff --git a/source/blender/editors/object/object_bake_api.c b/source/blender/editors/object/object_bake_api.c index 58daf753679..a664d93bb2e 100644 --- a/source/blender/editors/object/object_bake_api.c +++ b/source/blender/editors/object/object_bake_api.c @@ -324,7 +324,7 @@ static bool write_external_bake_pixels(const char *filepath, const int height, const int margin, const int margin_type, - ImageFormatData *im_format, + ImageFormatData const *im_format, const bool is_noncolor, Mesh const *mesh_eval, char const *uv_layer, diff --git a/source/blender/editors/space_image/image_ops.c b/source/blender/editors/space_image/image_ops.c index 49489b696ef..8b975ee6173 100644 --- a/source/blender/editors/space_image/image_ops.c +++ b/source/blender/editors/space_image/image_ops.c @@ -1830,7 +1830,7 @@ static void image_save_options_from_op(Main *bmain, ImageSaveOptions *opts, wmOp } static bool save_image_op( - Main *bmain, Image *ima, ImageUser *iuser, wmOperator *op, ImageSaveOptions *opts) + Main *bmain, Image *ima, ImageUser *iuser, wmOperator *op, const ImageSaveOptions *opts) { WM_cursor_wait(true); diff --git a/source/blender/nodes/NOD_composite.h b/source/blender/nodes/NOD_composite.h index 5d782674f16..58126c5722d 100644 --- a/source/blender/nodes/NOD_composite.h +++ b/source/blender/nodes/NOD_composite.h @@ -169,7 +169,7 @@ void ntreeCompositClearTags(struct bNodeTree *ntree); struct bNodeSocket *ntreeCompositOutputFileAddSocket(struct bNodeTree *ntree, struct bNode *node, const char *name, - struct ImageFormatData *im_format); + const struct ImageFormatData *im_format); int ntreeCompositOutputFileRemoveActiveSocket(struct bNodeTree *ntree, struct bNode *node); void ntreeCompositOutputFileSetPath(struct bNode *node, diff --git a/source/blender/nodes/composite/nodes/node_composite_output_file.cc b/source/blender/nodes/composite/nodes/node_composite_output_file.cc index f1621f83ac3..84235b085a4 100644 --- a/source/blender/nodes/composite/nodes/node_composite_output_file.cc +++ b/source/blender/nodes/composite/nodes/node_composite_output_file.cc @@ -114,7 +114,7 @@ void ntreeCompositOutputFileUniqueLayer(ListBase *list, bNodeSocket *ntreeCompositOutputFileAddSocket(bNodeTree *ntree, bNode *node, const char *name, - ImageFormatData *im_format) + const ImageFormatData *im_format) { NodeImageMultiFile *nimf = (NodeImageMultiFile *)node->storage; bNodeSocket *sock = nodeAddStaticSocket( -- cgit v1.2.3 From c8e8f107bf82fb56f49101e1098f4c697b16cfeb Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 15 Jul 2022 14:47:18 +1000 Subject: Fix T99711: Eternal loop reading blend file thumbnail Account for negative BHead length (already handled by blend file loading). --- source/blender/blendthumb/src/blendthumb_extract.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/source/blender/blendthumb/src/blendthumb_extract.cc b/source/blender/blendthumb/src/blendthumb_extract.cc index 369da559fc8..163197c8b67 100644 --- a/source/blender/blendthumb/src/blendthumb_extract.cc +++ b/source/blender/blendthumb/src/blendthumb_extract.cc @@ -121,6 +121,9 @@ static eThumbStatus blendthumb_extract_from_file_impl(FileReader *file, while (file_read(file, bhead_data, bhead_size)) { /* Parse type and size from `BHead`. */ const int32_t block_size = bytes_to_native_i32(&bhead_data[4], endian_switch); + if (UNLIKELY(block_size < 0)) { + return BT_INVALID_THUMB; + } /* We're looking for the thumbnail, so skip any other block. */ switch (*((int32_t *)bhead_data)) { -- cgit v1.2.3 From d14d5705807c8216f3cfb1de3af6f448a1401599 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 15 Jul 2022 14:52:32 +1000 Subject: blend_render_info: add check for negative BHead length (corrupt file) Without this check, corrupt files would raise a Python exception, now early exit with a useful error. --- release/scripts/modules/blend_render_info.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/release/scripts/modules/blend_render_info.py b/release/scripts/modules/blend_render_info.py index 37c5f6dd3ba..6b45a6f7e72 100755 --- a/release/scripts/modules/blend_render_info.py +++ b/release/scripts/modules/blend_render_info.py @@ -93,6 +93,11 @@ def _read_blend_rend_chunk_from_file(blendfile, filepath): break sizeof_data_left = struct.unpack('>i' if is_big_endian else ' Date: Fri, 15 Jul 2022 15:36:21 +1000 Subject: GHOST/Wayland: fix error setting the cursor scale Calculate a scale that's compatible with the cursor size. Needed so the cursor is always a multiple of scale. --- intern/ghost/intern/GHOST_SystemWayland.cpp | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 099792a4d06..4124cd059b3 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -3275,6 +3275,23 @@ static void cursor_buffer_hide(const input_t *input) } } +/** + * Needed to ensure the cursor size is always a multiple of scale. + */ +static int cursor_buffer_compatible_scale_from_image(const struct wl_cursor_image *wl_image, + int scale) +{ + const int32_t image_size_x = int32_t(wl_image->width); + const int32_t image_size_y = int32_t(wl_image->height); + while (scale > 1) { + if ((image_size_x % scale) == 0 && (image_size_y % scale) == 0) { + break; + } + scale -= 1; + } + return scale; +} + static void cursor_buffer_set_surface_impl(const input_t *input, wl_buffer *buffer, struct wl_surface *wl_surface, @@ -3301,7 +3318,8 @@ static void cursor_buffer_set(const input_t *input, wl_buffer *buffer) /* This is a requirement of WAYLAND, when this isn't the case, * it causes Blender's window to close intermittently. */ if (input->wl_pointer) { - const int scale = c->is_custom ? c->custom_scale : input->pointer.theme_scale; + const int scale = cursor_buffer_compatible_scale_from_image( + wl_image, c->is_custom ? c->custom_scale : input->pointer.theme_scale); const int32_t hotspot_x = int32_t(wl_image->hotspot_x) / scale; const int32_t hotspot_y = int32_t(wl_image->hotspot_y) / scale; cursor_buffer_set_surface_impl(input, buffer, c->wl_surface, scale); @@ -3314,7 +3332,8 @@ static void cursor_buffer_set(const input_t *input, wl_buffer *buffer) /* Set the cursor for all tablet tools as well. */ if (!input->tablet_tools.empty()) { - const int scale = c->is_custom ? c->custom_scale : input->tablet.theme_scale; + const int scale = cursor_buffer_compatible_scale_from_image( + wl_image, c->is_custom ? c->custom_scale : input->tablet.theme_scale); const int32_t hotspot_x = int32_t(wl_image->hotspot_x) / scale; const int32_t hotspot_y = int32_t(wl_image->hotspot_y) / scale; for (struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : input->tablet_tools) { -- cgit v1.2.3 From d8094f9212c1703e6230825780c06beb630f6d19 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 15 Jul 2022 15:42:24 +1000 Subject: GHOST/Wayland: partial support for updating the UI scale Partial support for changing the UI scale while Blender is open. The scale is set but issues with the window size not updating remain. --- intern/ghost/intern/GHOST_SystemWayland.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 4124cd059b3..d7520f1243f 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -2597,7 +2597,17 @@ static void output_handle_done(void *data, struct wl_output * /*wl_output*/) static void output_handle_scale(void *data, struct wl_output * /*wl_output*/, const int32_t factor) { + CLOG_INFO(LOG, 2, "scale"); static_cast(data)->scale = factor; + + if (window_manager) { + for (GHOST_IWindow *iwin : window_manager->getWindows()) { + GHOST_WindowWayland *win = static_cast(iwin); + win->outputs_changed_update_scale(); + /* TODO(@campbellbarton): support refreshing the UI when the DPI changes. + * There are glitches when resizing the monitor which would be nice to solve. */ + } + } } static const struct wl_output_listener output_listener = { -- cgit v1.2.3 From 8fd2b79ca190946fe95d915d19abbe9ddac895e9 Mon Sep 17 00:00:00 2001 From: Aras Pranckevicius Date: Fri, 15 Jul 2022 10:20:04 +0300 Subject: BLI_bitmap: ability to declare by-value, and function to find lowest unset bit In preparation for a larger change (D14162), some BLI_bitmap functionality that could be submitted separately: - Ability to declare a fixed size bitmap by-value, without extra memory allocation: BLI_BITMAP_DECLARE - Function to find the index of lowest unset bit: BLI_bitmap_find_first_unset - Test coverage of the above. Reviewed By: Campbell Barton, Bastien Montagne Differential Revision: https://developer.blender.org/D15454 --- source/blender/blenlib/BLI_bitmap.h | 11 ++++++ source/blender/blenlib/CMakeLists.txt | 1 + source/blender/blenlib/intern/bitmap.c | 20 +++++++++++ source/blender/blenlib/tests/BLI_bitmap_test.cc | 46 +++++++++++++++++++++++++ 4 files changed, 78 insertions(+) create mode 100644 source/blender/blenlib/tests/BLI_bitmap_test.cc diff --git a/source/blender/blenlib/BLI_bitmap.h b/source/blender/blenlib/BLI_bitmap.h index 19d8525311c..26dc6c7ffc9 100644 --- a/source/blender/blenlib/BLI_bitmap.h +++ b/source/blender/blenlib/BLI_bitmap.h @@ -53,6 +53,11 @@ typedef unsigned int BLI_bitmap; (CHECK_TYPE_INLINE(_mem, MemArena *), \ ((BLI_bitmap *)BLI_memarena_calloc(_mem, BLI_BITMAP_SIZE(_num)))) +/** + * Declares a bitmap as a variable. + */ +#define BLI_BITMAP_DECLARE(_name, _num) BLI_bitmap _name[_BITMAP_NUM_BLOCKS(_num)] = {} + /** * Get the value of a single bit at '_index'. */ @@ -137,6 +142,12 @@ void BLI_bitmap_and_all(BLI_bitmap *dst, const BLI_bitmap *src, size_t bits); */ void BLI_bitmap_or_all(BLI_bitmap *dst, const BLI_bitmap *src, size_t bits); +/** + * Find index of the lowest unset bit. + * Returns -1 if all the bits are set. + */ +int BLI_bitmap_find_first_unset(const BLI_bitmap *bitmap, size_t bits); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index 95b4987596e..d39a586206f 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -424,6 +424,7 @@ if(WITH_GTESTS) tests/BLI_array_store_test.cc tests/BLI_array_test.cc tests/BLI_array_utils_test.cc + tests/BLI_bitmap_test.cc tests/BLI_bounds_test.cc tests/BLI_color_test.cc tests/BLI_cpp_type_test.cc diff --git a/source/blender/blenlib/intern/bitmap.c b/source/blender/blenlib/intern/bitmap.c index 7fcbc31c066..2cc2fbc3e2f 100644 --- a/source/blender/blenlib/intern/bitmap.c +++ b/source/blender/blenlib/intern/bitmap.c @@ -11,6 +11,7 @@ #include #include "BLI_bitmap.h" +#include "BLI_math_bits.h" #include "BLI_utildefines.h" void BLI_bitmap_set_all(BLI_bitmap *bitmap, bool set, size_t bits) @@ -46,3 +47,22 @@ void BLI_bitmap_or_all(BLI_bitmap *dst, const BLI_bitmap *src, size_t bits) dst[i] |= src[i]; } } + +int BLI_bitmap_find_first_unset(const BLI_bitmap *bitmap, const size_t bits) +{ + const size_t blocks_num = _BITMAP_NUM_BLOCKS(bits); + int result = -1; + /* Skip over completely set blocks. */ + int index = 0; + while (index < blocks_num && bitmap[index] == ~0u) { + index++; + } + if (index < blocks_num) { + /* Found a partially used block: find the lowest unused bit. */ + const uint m = ~bitmap[index]; + BLI_assert(m != 0); + const uint bit_index = bitscan_forward_uint(m); + result = bit_index + (index << _BITMAP_POWER); + } + return result; +} diff --git a/source/blender/blenlib/tests/BLI_bitmap_test.cc b/source/blender/blenlib/tests/BLI_bitmap_test.cc new file mode 100644 index 00000000000..fb9e03e3136 --- /dev/null +++ b/source/blender/blenlib/tests/BLI_bitmap_test.cc @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +#include "BLI_bitmap.h" +#include "testing/testing.h" + +namespace blender::tests { + +TEST(bitmap, empty_is_all_unset) +{ + BLI_BITMAP_DECLARE(bitmap, 10); + for (int i = 0; i < 10; ++i) { + EXPECT_FALSE(BLI_BITMAP_TEST_BOOL(bitmap, i)); + } +} + +TEST(bitmap, find_first_unset_empty) +{ + BLI_BITMAP_DECLARE(bitmap, 10); + EXPECT_EQ(0, BLI_bitmap_find_first_unset(bitmap, 10)); +} + +TEST(bitmap, find_first_unset_full) +{ + BLI_BITMAP_DECLARE(bitmap, 10); + BLI_bitmap_flip_all(bitmap, 10); + EXPECT_EQ(-1, BLI_bitmap_find_first_unset(bitmap, 10)); +} + +TEST(bitmap, find_first_unset_middle) +{ + BLI_BITMAP_DECLARE(bitmap, 100); + BLI_bitmap_flip_all(bitmap, 100); + /* Turn some bits off */ + BLI_BITMAP_DISABLE(bitmap, 53); + BLI_BITMAP_DISABLE(bitmap, 81); + BLI_BITMAP_DISABLE(bitmap, 85); + BLI_BITMAP_DISABLE(bitmap, 86); + + /* Find lowest unset bit, and set it. */ + EXPECT_EQ(53, BLI_bitmap_find_first_unset(bitmap, 100)); + BLI_BITMAP_ENABLE(bitmap, 53); + /* Now should find the next lowest bit. */ + EXPECT_EQ(81, BLI_bitmap_find_first_unset(bitmap, 100)); +} + +} // namespace blender::tests -- cgit v1.2.3 From 63ea0f7581faeeb629b040b7a4dc661bc84c789c Mon Sep 17 00:00:00 2001 From: Aras Pranckevicius Date: Fri, 15 Jul 2022 10:21:27 +0300 Subject: BLI_bitmap: fix _BITMAP_NUM_BLOCKS to not over-count by one block For bit counts that were exact multiple of block size, the macro was computing one block too much. Reviewed By: Campbell Barton, Bastien Montagne Differential Revision: https://developer.blender.org/D15454 --- source/blender/blenlib/BLI_bitmap.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/blenlib/BLI_bitmap.h b/source/blender/blenlib/BLI_bitmap.h index 26dc6c7ffc9..973cc5c3d1e 100644 --- a/source/blender/blenlib/BLI_bitmap.h +++ b/source/blender/blenlib/BLI_bitmap.h @@ -27,7 +27,7 @@ typedef unsigned int BLI_bitmap; /** * Number of blocks needed to hold '_num' bits. */ -#define _BITMAP_NUM_BLOCKS(_num) (((_num) >> _BITMAP_POWER) + 1) +#define _BITMAP_NUM_BLOCKS(_num) (((_num) + _BITMAP_MASK) >> _BITMAP_POWER) /** * Size (in bytes) used to hold '_num' bits. -- cgit v1.2.3 From c2715dc41664781924f610d9c5c03362ccb29446 Mon Sep 17 00:00:00 2001 From: Ray Molenkamp Date: Fri, 15 Jul 2022 10:51:46 +0200 Subject: GPU: Remove USD dependency from shader_builder. Dependency was added as shader builder depended to blenkernel as an umbrella, in stead of adding the actual dependencies it required. --- CMakeLists.txt | 6 ------ source/blender/gpu/CMakeLists.txt | 18 ++++++++---------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b7ee7f1887..33064864be6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1340,12 +1340,6 @@ else() list(APPEND GL_DEFINITIONS -DWITH_GL_PROFILE_CORE) endif() -if (WITH_GPU_BUILDTIME_SHADER_BUILDER AND WITH_USD) - message(FATAL_ERROR - "Unable to compile WITH_GPU_BUILDTIME_SHADER_BUILDER and WITH_USD." - ) -endif() - #----------------------------------------------------------------------------- # Configure Metal. if (WITH_METAL_BACKEND) diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index 1c883f7fa41..0cb92e02515 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -570,11 +570,7 @@ if(WITH_GPU_BUILDTIME_SHADER_BUILDER) ) setup_platform_linker_flags(shader_builder) - - target_link_libraries(shader_builder PUBLIC - bf_blenkernel - buildinfoobj - ) + target_link_libraries(shader_builder PUBLIC buildinfoobj) else() if(WIN32) # We can re-use the manifest from tests.exe here since it's @@ -589,12 +585,14 @@ if(WITH_GPU_BUILDTIME_SHADER_BUILDER) ${MANIFEST} ) - target_link_libraries(shader_builder PUBLIC - bf_blenkernel - ${PLATFORM_LINKLIBS} - ) endif() - + target_link_libraries(shader_builder PUBLIC + bf_gpu + bf_intern_clog + bf_blenlib + bf_intern_ghost + ${PLATFORM_LINKLIBS} + ) target_include_directories(shader_builder PRIVATE ${INC} ${CMAKE_CURRENT_BINARY_DIR}) set(SRC_BAKED_CREATE_INFOS_FILE ${CMAKE_CURRENT_BINARY_DIR}/shader_baked.hh) -- cgit v1.2.3 From ca1daf4cdaaa9a806869586efe6a752b502c156c Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Wed, 13 Jul 2022 10:22:04 +0200 Subject: Fix an increasing bottleneck when key press operator is too slow The goal of this change is to fix an increasing bottleneck of the event queue handling when there is an operator bound to a key press event and is taking longer to finish than a key-repeat speed on the system. Practical example of when it happens is the marker tracking operator in a single-frame track mode. Quite often artists will hold down Alt-arrow to track a segment of footage which seems trivial to track. The issue arises when the Alt-arrow is released: prior to this change it is was possible that more frames will be tracked. It also seems that redraws are less smooth. It is a bit hard to make easily shareable computer-independent test case. Instead, a synthetic case can be reproduced by adding a 50 ms sleep in the `text_move_exec()`. In such synthetic case open a long text in the text editor and hold left/right arrow button to navigate the cursor. The observed behavior is that seemingly redraws happen less and less often and cursor travels longer and longer distances between redraws. The cursor will also keep moving after the buttons has been released. The proposed solution is to ignore sequential key-press events from being added to the event queue. This seems to be the least intrusive and the most safe approach: - If the operator is fast enough there will be no multiple press events in the queue in both prior and after of this change. - If the operator is slow enough, clicking the button multiple times (i.e. clicking arrow button 3 times in a heavy shot will change the scene frame by exactly 3 frames because no events are ignored in this case). - Only do it for key press events, keeping mouse and tabled behavior unchanged which is crucial for the paint mode. Note that this is a bit different from the key repeat tracking and filtering which is already implemented for keymaps as here we only want to avoid the event queue build-up and do want to ignore all repeat events. In other words: we do want to handle as many key presses as the operator performance allows it without clogging anything. A possible extension to this change could be a key press counter, so that instead of ignoring the event we merge it into the last event in the queue, incrementing some counter. This way if some operator really needs to know exact number of key repeats it can still access it. Differential Revision: https://developer.blender.org/D15444 --- .../windowmanager/intern/wm_event_system.cc | 51 +++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/source/blender/windowmanager/intern/wm_event_system.cc b/source/blender/windowmanager/intern/wm_event_system.cc index 757c3aae00c..79b303364d8 100644 --- a/source/blender/windowmanager/intern/wm_event_system.cc +++ b/source/blender/windowmanager/intern/wm_event_system.cc @@ -5133,6 +5133,53 @@ static void wm_event_state_update_and_click_set(wmEvent *event, wm_event_state_update_and_click_set_ex(event, event_state, is_keyboard, check_double_click); } +/* Returns true when the two events corresponds to a press of the same key with the same modifiers. + */ +static bool wm_event_is_same_key_press(const wmEvent &event_a, const wmEvent &event_b) +{ + if (event_a.val != KM_PRESS || event_b.val != KM_PRESS) { + return false; + } + + if (event_a.modifier != event_b.modifier || event_a.type != event_b.type) { + return false; + } + + return true; +} + +/** + * Returns true if the event is a key press event which is to be ignored and not added to the event + * queue. + * + * A key press event will be ignored if there is already matched key press in the queue. + * This avoids the event queue "clogging" in the situations when there is an operator bound to a + * key press event and the execution time of the operator is longer than the key repeat. + */ +static bool wm_event_is_ignorable_key_press(const wmWindow *win, const wmEvent &event) +{ + if (BLI_listbase_is_empty(&win->event_queue)) { + /* If the queue is empty never ignore the event. + * Empty queue at this point means that the events are handled fast enough, and there is no + * reason to ignore anything. */ + return false; + } + + if ((event.flag & WM_EVENT_IS_REPEAT) == 0) { + /* Only ignore repeat events from the keyboard, and allow accumulation of non-repeat events. + * + * The goal of this check is to allow events coming from a keyboard macro software, which can + * generate events quicker than the main loop handles them. In this case we want all events to + * be handled (unless the keyboard macro software tags them as repeat) because otherwise it + * will become impossible to get reliable results of automated events testing. */ + return false; + } + + const wmEvent &last_event = *reinterpret_cast(win->event_queue.last); + + return wm_event_is_same_key_press(last_event, event); +} + void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void *customdata) { if (UNLIKELY(G.f & G_FLAG_EVENT_SIMULATE)) { @@ -5439,7 +5486,9 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void G.is_break = true; } - wm_event_add(win, &event); + if (!wm_event_is_ignorable_key_press(win, event)) { + wm_event_add(win, &event); + } break; } -- cgit v1.2.3 From 862170c0b1ed1fb4f967aead1979568c86b141e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cle=CC=81ment=20Foucault?= Date: Fri, 15 Jul 2022 10:36:24 +0200 Subject: Cleanup: GPU: Replace NULL by nullptr from C++ files --- source/blender/gpu/intern/gpu_context_private.hh | 20 ++++++++++---------- source/blender/gpu/intern/gpu_immediate_private.hh | 6 +++--- source/blender/gpu/intern/gpu_shader_interface.hh | 14 +++++++------- .../blender/gpu/intern/gpu_storage_buffer_private.hh | 2 +- .../blender/gpu/intern/gpu_uniform_buffer_private.hh | 2 +- .../blender/gpu/intern/gpu_vertex_buffer_private.hh | 2 +- source/blender/gpu/opengl/gl_batch.hh | 4 ++-- source/blender/gpu/opengl/gl_debug.cc | 2 +- source/blender/gpu/opengl/gl_framebuffer.hh | 4 ++-- source/blender/gpu/opengl/gl_texture.hh | 2 +- 10 files changed, 29 insertions(+), 29 deletions(-) diff --git a/source/blender/gpu/intern/gpu_context_private.hh b/source/blender/gpu/intern/gpu_context_private.hh index 9cdf0075632..f823a92893c 100644 --- a/source/blender/gpu/intern/gpu_context_private.hh +++ b/source/blender/gpu/intern/gpu_context_private.hh @@ -28,11 +28,11 @@ namespace blender::gpu { class Context { public: /** State management */ - Shader *shader = NULL; - FrameBuffer *active_fb = NULL; - GPUMatrixState *matrix_state = NULL; - StateManager *state_manager = NULL; - Immediate *imm = NULL; + Shader *shader = nullptr; + FrameBuffer *active_fb = nullptr; + GPUMatrixState *matrix_state = nullptr; + StateManager *state_manager = nullptr; + Immediate *imm = nullptr; /** * All 4 window frame-buffers. @@ -41,10 +41,10 @@ class Context { * Front frame-buffers contains (in principle, but not always) the last frame color. * Default frame-buffer is back_left. */ - FrameBuffer *back_left = NULL; - FrameBuffer *front_left = NULL; - FrameBuffer *back_right = NULL; - FrameBuffer *front_right = NULL; + FrameBuffer *back_left = nullptr; + FrameBuffer *front_left = nullptr; + FrameBuffer *back_right = nullptr; + FrameBuffer *front_right = nullptr; DebugStack debug_stack; @@ -52,7 +52,7 @@ class Context { /** Thread on which this context is active. */ pthread_t thread_; bool is_active_; - /** Avoid including GHOST headers. Can be NULL for off-screen contexts. */ + /** Avoid including GHOST headers. Can be nullptr for off-screen contexts. */ void *ghost_window_; public: diff --git a/source/blender/gpu/intern/gpu_immediate_private.hh b/source/blender/gpu/intern/gpu_immediate_private.hh index 6c50fa01071..74ebbdc7ae3 100644 --- a/source/blender/gpu/intern/gpu_immediate_private.hh +++ b/source/blender/gpu/intern/gpu_immediate_private.hh @@ -19,7 +19,7 @@ namespace blender::gpu { class Immediate { public: /** Pointer to the mapped buffer data for the current vertex. */ - uchar *vertex_data = NULL; + uchar *vertex_data = nullptr; /** Current vertex index. */ uint vertex_idx = 0; /** Length of the buffer in vertices. */ @@ -32,12 +32,12 @@ class Immediate { /** Current draw call specification. */ GPUPrimType prim_type = GPU_PRIM_NONE; GPUVertFormat vertex_format = {}; - GPUShader *shader = NULL; + GPUShader *shader = nullptr; /** Enforce strict vertex count (disabled when using #immBeginAtMost). */ bool strict_vertex_len = true; /** Batch in construction when using #immBeginBatch. */ - GPUBatch *batch = NULL; + GPUBatch *batch = nullptr; /** Wide Line workaround. */ diff --git a/source/blender/gpu/intern/gpu_shader_interface.hh b/source/blender/gpu/intern/gpu_shader_interface.hh index ac78af38fcc..60344757b43 100644 --- a/source/blender/gpu/intern/gpu_shader_interface.hh +++ b/source/blender/gpu/intern/gpu_shader_interface.hh @@ -39,9 +39,9 @@ class ShaderInterface { /* TODO(fclem): should be protected. */ public: /** Flat array. In this order: Attributes, Ubos, Uniforms. */ - ShaderInput *inputs_ = NULL; + ShaderInput *inputs_ = nullptr; /** Buffer containing all inputs names separated by '\0'. */ - char *name_buffer_ = NULL; + char *name_buffer_ = nullptr; /** Input counts inside input array. */ uint attr_len_ = 0; uint ubo_len_ = 0; @@ -187,7 +187,7 @@ inline const char *ShaderInterface::builtin_uniform_name(GPUUniformBuiltin u) return "srgbTarget"; default: - return NULL; + return nullptr; } } @@ -208,7 +208,7 @@ inline const char *ShaderInterface::builtin_uniform_block_name(GPUUniformBlockBu case GPU_UNIFORM_BLOCK_DRW_INFOS: return "drw_infos"; default: - return NULL; + return nullptr; } } @@ -258,7 +258,7 @@ inline const ShaderInput *ShaderInterface::input_lookup(const ShaderInput *const return inputs + i; /* not found */ } } - return NULL; /* not found */ + return nullptr; /* not found */ } /* This is a bit dangerous since we could have a hash collision. @@ -268,7 +268,7 @@ inline const ShaderInput *ShaderInterface::input_lookup(const ShaderInput *const return inputs + i; } } - return NULL; /* not found */ + return nullptr; /* not found */ } inline const ShaderInput *ShaderInterface::input_lookup(const ShaderInput *const inputs, @@ -281,7 +281,7 @@ inline const ShaderInput *ShaderInterface::input_lookup(const ShaderInput *const return inputs + i; } } - return NULL; /* not found */ + return nullptr; /* not found */ } } // namespace blender::gpu diff --git a/source/blender/gpu/intern/gpu_storage_buffer_private.hh b/source/blender/gpu/intern/gpu_storage_buffer_private.hh index 091e6c2d386..9baec0c2a77 100644 --- a/source/blender/gpu/intern/gpu_storage_buffer_private.hh +++ b/source/blender/gpu/intern/gpu_storage_buffer_private.hh @@ -29,7 +29,7 @@ class StorageBuf { /** Data size in bytes. */ size_t size_in_bytes_; /** Continuous memory block to copy to GPU. This data is owned by the StorageBuf. */ - void *data_ = NULL; + void *data_ = nullptr; /** Debugging name */ char name_[DEBUG_NAME_LEN]; diff --git a/source/blender/gpu/intern/gpu_uniform_buffer_private.hh b/source/blender/gpu/intern/gpu_uniform_buffer_private.hh index 6e3285b6fef..e3d70634ce1 100644 --- a/source/blender/gpu/intern/gpu_uniform_buffer_private.hh +++ b/source/blender/gpu/intern/gpu_uniform_buffer_private.hh @@ -29,7 +29,7 @@ class UniformBuf { /** Data size in bytes. */ size_t size_in_bytes_; /** Continuous memory block to copy to GPU. This data is owned by the UniformBuf. */ - void *data_ = NULL; + void *data_ = nullptr; /** Debugging name */ char name_[DEBUG_NAME_LEN]; diff --git a/source/blender/gpu/intern/gpu_vertex_buffer_private.hh b/source/blender/gpu/intern/gpu_vertex_buffer_private.hh index 7a0b53cf958..a7920bacaec 100644 --- a/source/blender/gpu/intern/gpu_vertex_buffer_private.hh +++ b/source/blender/gpu/intern/gpu_vertex_buffer_private.hh @@ -29,7 +29,7 @@ class VertBuf { /** Status flag. */ GPUVertBufStatus flag = GPU_VERTBUF_INVALID; /** NULL indicates data in VRAM (unmapped) */ - uchar *data = NULL; + uchar *data = nullptr; protected: /** Usage hint for GL optimization. */ diff --git a/source/blender/gpu/opengl/gl_batch.hh b/source/blender/gpu/opengl/gl_batch.hh index a25e495b3b1..1a18572c683 100644 --- a/source/blender/gpu/opengl/gl_batch.hh +++ b/source/blender/gpu/opengl/gl_batch.hh @@ -35,9 +35,9 @@ class GLShaderInterface; class GLVaoCache { private: /** Context for which the vao_cache_ was generated. */ - GLContext *context_ = NULL; + GLContext *context_ = nullptr; /** Last interface this batch was drawn with. */ - GLShaderInterface *interface_ = NULL; + GLShaderInterface *interface_ = nullptr; /** Cached VAO for the last interface. */ GLuint vao_id_ = 0; /** Used when arb_base_instance is not supported. */ diff --git a/source/blender/gpu/opengl/gl_debug.cc b/source/blender/gpu/opengl/gl_debug.cc index f82138e0d65..79b28642a67 100644 --- a/source/blender/gpu/opengl/gl_debug.cc +++ b/source/blender/gpu/opengl/gl_debug.cc @@ -189,7 +189,7 @@ void check_gl_error(const char *info) case err: { \ char msg[256]; \ SNPRINTF(msg, "%s : %s", #err, info); \ - debug_callback(0, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, 0, msg, NULL); \ + debug_callback(0, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, 0, msg, nullptr); \ break; \ } diff --git a/source/blender/gpu/opengl/gl_framebuffer.hh b/source/blender/gpu/opengl/gl_framebuffer.hh index 224c7a92c0a..2dc0936d0fe 100644 --- a/source/blender/gpu/opengl/gl_framebuffer.hh +++ b/source/blender/gpu/opengl/gl_framebuffer.hh @@ -30,9 +30,9 @@ class GLFrameBuffer : public FrameBuffer { /** OpenGL handle. */ GLuint fbo_id_ = 0; /** Context the handle is from. Frame-buffers are not shared across contexts. */ - GLContext *context_ = NULL; + GLContext *context_ = nullptr; /** State Manager of the same contexts. */ - GLStateManager *state_manager_ = NULL; + GLStateManager *state_manager_ = nullptr; /** Copy of the GL state. Contains ONLY color attachments enums for slot binding. */ GLenum gl_attachments_[GPU_FB_MAX_COLOR_ATTACHMENT]; /** Internal frame-buffers are immutable. */ diff --git a/source/blender/gpu/opengl/gl_texture.hh b/source/blender/gpu/opengl/gl_texture.hh index e5b879f1f15..aeb9fc0e6b7 100644 --- a/source/blender/gpu/opengl/gl_texture.hh +++ b/source/blender/gpu/opengl/gl_texture.hh @@ -33,7 +33,7 @@ class GLTexture : public Texture { /** opengl identifier for texture. */ GLuint tex_id_ = 0; /** Legacy workaround for texture copy. Created when using framebuffer_get(). */ - struct GPUFrameBuffer *framebuffer_ = NULL; + struct GPUFrameBuffer *framebuffer_ = nullptr; /** True if this texture is bound to at least one texture unit. */ /* TODO(fclem): How do we ensure thread safety here? */ bool is_bound_ = false; -- cgit v1.2.3 From 98f688ac42fa6a59b618de39a2f0b85f1f675160 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cle=CC=81ment=20Foucault?= Date: Fri, 15 Jul 2022 11:03:42 +0200 Subject: GPU: Fix shader builder on hardware that does not have all features --- .../blender/gpu/intern/gpu_shader_create_info.cc | 5 ++++- .../blender/gpu/intern/gpu_shader_create_info.hh | 25 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/source/blender/gpu/intern/gpu_shader_create_info.cc b/source/blender/gpu/intern/gpu_shader_create_info.cc index 16ce4f7723e..bc0731862cb 100644 --- a/source/blender/gpu/intern/gpu_shader_create_info.cc +++ b/source/blender/gpu/intern/gpu_shader_create_info.cc @@ -333,8 +333,11 @@ bool gpu_shader_create_info_compile_all() int skipped = 0; int total = 0; for (ShaderCreateInfo *info : g_create_infos->values()) { + info->finalize(); if (info->do_static_compilation_) { - if (GPU_compute_shader_support() == false && info->compute_source_ != nullptr) { + if ((GPU_compute_shader_support() == false && info->compute_source_ != nullptr) || + (GPU_shader_image_load_store_support() == false && info->has_resource_image()) || + (GPU_shader_storage_buffer_objects_support() == false && info->has_resource_storage())) { skipped++; continue; } diff --git a/source/blender/gpu/intern/gpu_shader_create_info.hh b/source/blender/gpu/intern/gpu_shader_create_info.hh index 4927ef75a75..8e05412d0ee 100644 --- a/source/blender/gpu/intern/gpu_shader_create_info.hh +++ b/source/blender/gpu/intern/gpu_shader_create_info.hh @@ -872,6 +872,31 @@ struct ShaderCreateInfo { return stream; } + bool has_resource_type(Resource::BindType bind_type) const + { + for (auto &res : batch_resources_) { + if (res.bind_type == bind_type) { + return true; + } + } + for (auto &res : pass_resources_) { + if (res.bind_type == bind_type) { + return true; + } + } + return false; + } + + bool has_resource_image() const + { + return has_resource_type(Resource::BindType::IMAGE); + } + + bool has_resource_storage() const + { + return has_resource_type(Resource::BindType::STORAGE_BUFFER); + } + /** \} */ #undef TEST_EQUAL -- cgit v1.2.3 From f4d7ea2cf61ed30ac15adc7966a08f61846d6f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cle=CC=81ment=20Foucault?= Date: Fri, 15 Jul 2022 11:15:59 +0200 Subject: Fix T99606: Regression: TexCoordinate losing precision far away from origin Same root cause as T99128. The fix also needed to be done in another place. --- source/blender/draw/engines/eevee/shaders/surface_lib.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/draw/engines/eevee/shaders/surface_lib.glsl b/source/blender/draw/engines/eevee/shaders/surface_lib.glsl index 8e1bafe8d92..80c6b935187 100644 --- a/source/blender/draw/engines/eevee/shaders/surface_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/surface_lib.glsl @@ -97,7 +97,7 @@ GlobalData init_globals(void) GlobalData surf; # if defined(WORLD_BACKGROUND) || defined(PROBE_CAPTURE) - surf.P = -cameraVec(worldPosition); + surf.P = transform_direction(ViewMatrixInverse, viewCameraVec(viewPosition)); surf.N = surf.Ng = -surf.P; surf.ray_length = 0.0; # else -- cgit v1.2.3 From 8e1323f6330937a964c3776431bab02c2cfb6e2f Mon Sep 17 00:00:00 2001 From: Martijn Versteegh Date: Fri, 15 Jul 2022 11:22:10 +0200 Subject: Fix: Move DRW_shgroup_add_material_resources(grp, mat) to after the null-check for grp. Reviewed By: fclem Maniphest Tasks: T99646 Differential Revision: https://developer.blender.org/D15436 --- source/blender/draw/engines/eevee/eevee_volumes.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/blender/draw/engines/eevee/eevee_volumes.c b/source/blender/draw/engines/eevee/eevee_volumes.c index 8223df22ee9..533e71b9b32 100644 --- a/source/blender/draw/engines/eevee/eevee_volumes.c +++ b/source/blender/draw/engines/eevee/eevee_volumes.c @@ -316,12 +316,13 @@ void EEVEE_volumes_cache_object_add(EEVEE_ViewLayerData *sldata, DRWShadingGroup *grp = DRW_shgroup_create(sh, vedata->psl->volumetric_objects_ps); grp = DRW_shgroup_volume_create_sub(scene, ob, grp, mat); - DRW_shgroup_add_material_resources(grp, mat); if (grp == NULL) { return; } + DRW_shgroup_add_material_resources(grp, mat); + /* TODO(fclem): remove those "unnecessary" UBOs */ DRW_shgroup_uniform_block(grp, "planar_block", sldata->planar_ubo); DRW_shgroup_uniform_block(grp, "probe_block", sldata->probe_ubo); -- cgit v1.2.3 From e69c4482f16d51e5fda4da70abb94a23f8c5b3d9 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Fri, 15 Jul 2022 11:41:13 +0200 Subject: I18n: Add suport for labels from modifiers' subpanels. Was a bit oif a struggle since those functions take a first string which is not our label, but should work fine now. Reported/detected as part of D15418. --- .../modules/bl_i18n_utils/bl_extract_messages.py | 23 +++++++++++----------- release/scripts/modules/bl_i18n_utils/settings.py | 5 +++++ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/release/scripts/modules/bl_i18n_utils/bl_extract_messages.py b/release/scripts/modules/bl_i18n_utils/bl_extract_messages.py index 319fd3396a0..3edb5b445fe 100644 --- a/release/scripts/modules/bl_i18n_utils/bl_extract_messages.py +++ b/release/scripts/modules/bl_i18n_utils/bl_extract_messages.py @@ -803,20 +803,21 @@ def dump_src_messages(msgs, reports, settings): line += data[pos:m.start()].count('\n') msgsrc = rel_path + ":" + str(line) _msgid = d.get("msg_raw") - # First, try the "multi-contexts" stuff! - _msgctxts = tuple(d.get("ctxt_raw{}".format(i)) for i in range(settings.PYGETTEXT_MAX_MULTI_CTXT)) - if _msgctxts[0]: - for _msgctxt in _msgctxts: - if not _msgctxt: - break + if _msgid not in {'""', "''"}: + # First, try the "multi-contexts" stuff! + _msgctxts = tuple(d.get("ctxt_raw{}".format(i)) for i in range(settings.PYGETTEXT_MAX_MULTI_CTXT)) + if _msgctxts[0]: + for _msgctxt in _msgctxts: + if not _msgctxt: + break + msgctxt, msgid = process_entry(_msgctxt, _msgid) + process_msg(msgs, msgctxt, msgid, msgsrc, reports, check_ctxt_src, settings) + reports["src_messages"].append((msgctxt, msgid, msgsrc)) + else: + _msgctxt = d.get("ctxt_raw") msgctxt, msgid = process_entry(_msgctxt, _msgid) process_msg(msgs, msgctxt, msgid, msgsrc, reports, check_ctxt_src, settings) reports["src_messages"].append((msgctxt, msgid, msgsrc)) - else: - _msgctxt = d.get("ctxt_raw") - msgctxt, msgid = process_entry(_msgctxt, _msgid) - process_msg(msgs, msgctxt, msgid, msgsrc, reports, check_ctxt_src, settings) - reports["src_messages"].append((msgctxt, msgid, msgsrc)) pos = m.end() line += data[m.start():pos].count('\n') diff --git a/release/scripts/modules/bl_i18n_utils/settings.py b/release/scripts/modules/bl_i18n_utils/settings.py index 9b38c512d31..3b69f8a6bf7 100644 --- a/release/scripts/modules/bl_i18n_utils/settings.py +++ b/release/scripts/modules/bl_i18n_utils/settings.py @@ -251,6 +251,11 @@ PYGETTEXT_KEYWORDS = (() + tuple(("{}\\((?:[^\"',]+,){{2}}\\s*" + _msg_re + r"\s*(?:\)|,)").format(it) for it in ("BKE_modifier_set_error",)) + + # This one is a tad more risky, but in practice would not expect a name/uid string parameter + # (the second one in those functions) to ever have a comma in it, so think this is fine. + tuple(("{}\\((?:[^,]+,){{2}}\\s*" + _msg_re + r"\s*(?:\)|,)").format(it) + for it in ("modifier_subpanel_register", "gpencil_modifier_subpanel_register")) + + # bUnitDef unit names. # NOTE: regex is a bit more complex than it would need too. Since the actual # identifier (`B_UNIT_DEF_`) is at the end, if it's simpler/too general it -- cgit v1.2.3 From 4d7c9901800835122413faebd9ead267f1f32285 Mon Sep 17 00:00:00 2001 From: Chris Blackbourn Date: Fri, 15 Jul 2022 23:13:59 +1200 Subject: Fix T98061: uv resize with individual origins could break constrain to bounds Fix unreported: Resize with Constrain To Bounds will now limit one shared scale value for both U and V instead of calculating separate scale values for each. To fix T98061, the individual origins (transdata->center) is now used when that mode is active. See also: 0e9367fc29bc Differential Revision: https://developer.blender.org/D15420 --- .../editors/transform/transform_mode_resize.c | 63 ++++++++++++---------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/source/blender/editors/transform/transform_mode_resize.c b/source/blender/editors/transform/transform_mode_resize.c index bc45ec07eab..bbe1cfdf521 100644 --- a/source/blender/editors/transform/transform_mode_resize.c +++ b/source/blender/editors/transform/transform_mode_resize.c @@ -126,10 +126,6 @@ static void constrain_scale_to_boundary(const float numerator, static bool clip_uv_transform_resize(TransInfo *t, float vec[2]) { - /* Assume no change is required. */ - float scalex = 1.0f; - float scaley = 1.0f; - /* Check if the current image in UV editor is a tiled image or not. */ const SpaceImage *sima = t->area->spacedata.first; const Image *image = sima->image; @@ -150,35 +146,46 @@ static bool clip_uv_transform_resize(TransInfo *t, float vec[2]) } } - float min[2], max[2]; - min[0] = min[1] = FLT_MAX; - max[0] = max[1] = -FLT_MAX; + /* Assume no change is required. */ + float scale = 1.0f; + /* Are we scaling U and V together, or just one axis? */ + const bool adjust_u = !(t->con.mode & CON_AXIS1); + const bool adjust_v = !(t->con.mode & CON_AXIS0); + const bool use_local_center = transdata_check_local_center(t, t->around); FOREACH_TRANS_DATA_CONTAINER (t, tc) { + for (TransData *td = tc->data; td < tc->data + tc->data_len; td++) { + + /* Get scale origin. */ + const float *scale_origin = use_local_center ? td->center : t->center_global; + + /* Alias td->loc as min and max just in case we need to optimize later. */ + const float *min = td->loc; + const float *max = td->loc; + + if (adjust_u) { + /* Update U against the left border. */ + constrain_scale_to_boundary( + scale_origin[0] - base_offset[0], scale_origin[0] - min[0], &scale); - TransData *td; - int a; - for (a = 0, td = tc->data; a < tc->data_len; a++, td++) { - minmax_v2v2_v2(min, max, td->loc); + /* Now the right border, negated, because `-1.0 / -1.0 = 1.0` */ + constrain_scale_to_boundary( + base_offset[0] + t->aspect[0] - scale_origin[0], max[0] - scale_origin[0], &scale); + } + + /* Do the same for the V co-ordinate. */ + if (adjust_v) { + constrain_scale_to_boundary( + scale_origin[1] - base_offset[1], scale_origin[1] - min[1], &scale); + + constrain_scale_to_boundary( + base_offset[1] + t->aspect[1] - scale_origin[1], max[1] - scale_origin[1], &scale); + } } } - - /* Update U against the left border. */ - constrain_scale_to_boundary( - t->center_global[0] - base_offset[0], t->center_global[0] - min[0], &scalex); - /* Now the right border, negated, because `-1.0 / -1.0 = 1.0` */ - constrain_scale_to_boundary( - base_offset[0] + t->aspect[0] - t->center_global[0], max[0] - t->center_global[0], &scalex); - - /* Do the same for the V co-ordinate, which is called `y`. */ - constrain_scale_to_boundary( - t->center_global[1] - base_offset[1], t->center_global[1] - min[1], &scaley); - constrain_scale_to_boundary( - base_offset[1] + t->aspect[1] - t->center_global[1], max[1] - t->center_global[1], &scaley); - - vec[0] *= scalex; - vec[1] *= scaley; - return (scalex != 1.0f) || (scaley != 1.0f); + vec[0] *= scale; + vec[1] *= scale; + return scale != 1.0f; } static void applyResize(TransInfo *t, const int UNUSED(mval[2])) -- cgit v1.2.3 From b8ffd43bd28ff93c5420d8a50c2cde5a061be118 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 15 Jul 2022 13:08:51 +0200 Subject: Cleanup: make format --- intern/cycles/kernel/integrator/init_from_bake.h | 6 ++++-- intern/cycles/kernel/integrator/intersect_shadow.h | 4 +++- intern/cycles/kernel/integrator/intersect_volume_stack.h | 4 +++- intern/cycles/kernel/integrator/shade_light.h | 4 +++- intern/cycles/kernel/integrator/shade_shadow.h | 4 +++- intern/cycles/kernel/integrator/subsurface.h | 12 +++++++++--- 6 files changed, 25 insertions(+), 9 deletions(-) diff --git a/intern/cycles/kernel/integrator/init_from_bake.h b/intern/cycles/kernel/integrator/init_from_bake.h index 4d75dcea190..afd174de9e8 100644 --- a/intern/cycles/kernel/integrator/init_from_bake.h +++ b/intern/cycles/kernel/integrator/init_from_bake.h @@ -247,10 +247,12 @@ ccl_device bool integrator_init_from_bake(KernelGlobals kg, const bool use_raytrace_kernel = (shader_flags & SD_HAS_RAYTRACE); if (use_caustics) { - integrator_path_init_sorted(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE, shader_index); + integrator_path_init_sorted( + kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE, shader_index); } else if (use_raytrace_kernel) { - integrator_path_init_sorted(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE, shader_index); + integrator_path_init_sorted( + kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE, shader_index); } else { integrator_path_init_sorted(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE, shader_index); diff --git a/intern/cycles/kernel/integrator/intersect_shadow.h b/intern/cycles/kernel/integrator/intersect_shadow.h index adcbfa19afe..1b48b360858 100644 --- a/intern/cycles/kernel/integrator/intersect_shadow.h +++ b/intern/cycles/kernel/integrator/intersect_shadow.h @@ -171,7 +171,9 @@ ccl_device void integrator_intersect_shadow(KernelGlobals kg, IntegratorShadowSt * * TODO: could also write to render buffer directly if no transparent shadows? * Could save a kernel execution for the common case. */ - integrator_shadow_path_next(kg, state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW, + integrator_shadow_path_next(kg, + state, + DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW, DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW); return; } diff --git a/intern/cycles/kernel/integrator/intersect_volume_stack.h b/intern/cycles/kernel/integrator/intersect_volume_stack.h index 68c7fdd5909..78f0b4d62aa 100644 --- a/intern/cycles/kernel/integrator/intersect_volume_stack.h +++ b/intern/cycles/kernel/integrator/intersect_volume_stack.h @@ -222,7 +222,9 @@ ccl_device void integrator_intersect_volume_stack(KernelGlobals kg, IntegratorSt } else { /* Volume stack init for camera rays, continue with intersection of camera ray. */ - integrator_path_next(kg, state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK, + integrator_path_next(kg, + state, + DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK, DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST); } } diff --git a/intern/cycles/kernel/integrator/shade_light.h b/intern/cycles/kernel/integrator/shade_light.h index 7b3e9e0ee7e..1a5ac3637f6 100644 --- a/intern/cycles/kernel/integrator/shade_light.h +++ b/intern/cycles/kernel/integrator/shade_light.h @@ -103,7 +103,9 @@ ccl_device void integrator_shade_light(KernelGlobals kg, return; } else { - integrator_path_next(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT, + integrator_path_next(kg, + state, + DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT, DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST); return; } diff --git a/intern/cycles/kernel/integrator/shade_shadow.h b/intern/cycles/kernel/integrator/shade_shadow.h index afe77590e9a..830da0158cf 100644 --- a/intern/cycles/kernel/integrator/shade_shadow.h +++ b/intern/cycles/kernel/integrator/shade_shadow.h @@ -165,7 +165,9 @@ ccl_device void integrator_shade_shadow(KernelGlobals kg, if (shadow_intersections_has_remaining(num_hits)) { /* More intersections to find, continue shadow ray. */ - integrator_shadow_path_next(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW, + integrator_shadow_path_next(kg, + state, + DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW); return; } diff --git a/intern/cycles/kernel/integrator/subsurface.h b/intern/cycles/kernel/integrator/subsurface.h index 09e59919c86..ab26a2d93cc 100644 --- a/intern/cycles/kernel/integrator/subsurface.h +++ b/intern/cycles/kernel/integrator/subsurface.h @@ -177,17 +177,23 @@ ccl_device_inline bool subsurface_scatter(KernelGlobals kg, IntegratorState stat const bool use_raytrace_kernel = (shader_flags & SD_HAS_RAYTRACE); if (use_caustics) { - integrator_path_next_sorted(kg, state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE, + integrator_path_next_sorted(kg, + state, + DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE, shader); } else if (use_raytrace_kernel) { - integrator_path_next_sorted(kg, state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE, + integrator_path_next_sorted(kg, + state, + DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE, shader); } else { - integrator_path_next_sorted(kg, state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE, + integrator_path_next_sorted(kg, + state, + DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE, shader); } -- cgit v1.2.3 From 9ea1b88f0f01a617f452d5eaa44ecbebbaa95c4e Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Thu, 14 Jul 2022 20:16:34 +0200 Subject: Cleanup: add utlity function to compute render resolution Instead of duplicating logic many times. --- source/blender/blenkernel/BKE_scene.h | 4 ++++ source/blender/blenkernel/intern/image.cc | 8 +------- source/blender/blenkernel/intern/scene.cc | 14 ++++++++++++++ .../compositor/operations/COM_CompositorOperation.cc | 9 +++++---- .../blender/compositor/operations/COM_RenderLayersProg.cc | 4 ++-- .../blender/compositor/operations/COM_TextureOperation.cc | 5 +++-- .../blender/compositor/operations/COM_ViewerOperation.cc | 4 ++-- source/blender/editors/mask/mask_query.c | 3 +-- source/blender/editors/render/render_opengl.cc | 3 +-- source/blender/editors/render/render_view.cc | 8 ++++++-- source/blender/editors/space_image/image_edit.c | 9 ++------- source/blender/editors/space_image/image_ops.c | 1 + source/blender/editors/space_sequencer/sequencer_draw.c | 3 +-- source/blender/editors/space_sequencer/sequencer_view.c | 12 ++++++------ source/blender/editors/space_view3d/view3d_edit.c | 4 +++- source/blender/io/gpencil/intern/gpencil_io_base.cc | 3 +-- source/blender/io/gpencil/intern/gpencil_io_base.hh | 4 ++-- source/blender/render/intern/pipeline.c | 12 ++++-------- source/blender/sequencer/intern/proxy.c | 12 ++++-------- source/blender/sequencer/intern/render.c | 4 ++-- 20 files changed, 65 insertions(+), 61 deletions(-) diff --git a/source/blender/blenkernel/BKE_scene.h b/source/blender/blenkernel/BKE_scene.h index a6402a1e8a1..61fc883fe7f 100644 --- a/source/blender/blenkernel/BKE_scene.h +++ b/source/blender/blenkernel/BKE_scene.h @@ -251,6 +251,10 @@ bool BKE_scene_check_rigidbody_active(const struct Scene *scene); int BKE_scene_num_threads(const struct Scene *scene); int BKE_render_num_threads(const struct RenderData *r); +void BKE_render_resolution(const struct RenderData *r, + const bool use_crop, + int *r_width, + int *r_height); int BKE_render_preview_pixel_size(const struct RenderData *r); /**********************************/ diff --git a/source/blender/blenkernel/intern/image.cc b/source/blender/blenkernel/intern/image.cc index 6ec1c52c61a..f8b2d841028 100644 --- a/source/blender/blenkernel/intern/image.cc +++ b/source/blender/blenkernel/intern/image.cc @@ -5060,13 +5060,7 @@ void BKE_image_get_size(Image *image, ImageUser *iuser, int *r_width, int *r_hei } else if (image != nullptr && image->type == IMA_TYPE_R_RESULT && iuser != nullptr && iuser->scene != nullptr) { - Scene *scene = iuser->scene; - *r_width = (scene->r.xsch * scene->r.size) / 100; - *r_height = (scene->r.ysch * scene->r.size) / 100; - if ((scene->r.mode & R_BORDER) && (scene->r.mode & R_CROP)) { - *r_width *= BLI_rctf_size_x(&scene->r.border); - *r_height *= BLI_rctf_size_y(&scene->r.border); - } + BKE_render_resolution(&iuser->scene->r, true, r_width, r_height); } else { *r_width = IMG_SIZE_FALLBACK; diff --git a/source/blender/blenkernel/intern/scene.cc b/source/blender/blenkernel/intern/scene.cc index 39157bc9890..e2da27fc840 100644 --- a/source/blender/blenkernel/intern/scene.cc +++ b/source/blender/blenkernel/intern/scene.cc @@ -2952,6 +2952,20 @@ int BKE_scene_num_threads(const Scene *scene) return BKE_render_num_threads(&scene->r); } +void BKE_render_resolution(const struct RenderData *r, + const bool use_crop, + int *r_width, + int *r_height) +{ + *r_width = (r->xsch * r->size) / 100; + *r_height = (r->ysch * r->size) / 100; + + if (use_crop && (r->mode & R_BORDER) && (r->mode & R_CROP)) { + *r_width *= BLI_rctf_size_x(&r->border); + *r_height *= BLI_rctf_size_y(&r->border); + } +} + int BKE_render_preview_pixel_size(const RenderData *r) { if (r->preview_pixel_size == 0) { diff --git a/source/blender/compositor/operations/COM_CompositorOperation.cc b/source/blender/compositor/operations/COM_CompositorOperation.cc index e8b61ff229e..c0cc3fa1ba4 100644 --- a/source/blender/compositor/operations/COM_CompositorOperation.cc +++ b/source/blender/compositor/operations/COM_CompositorOperation.cc @@ -5,6 +5,7 @@ #include "BKE_global.h" #include "BKE_image.h" +#include "BKE_scene.h" #include "RE_pipeline.h" @@ -164,8 +165,8 @@ void CompositorOperation::execute_region(rcti *rect, unsigned int /*tile_number* * Full frame */ - int full_width = rd->xsch * rd->size / 100; - int full_height = rd->ysch * rd->size / 100; + int full_width, full_height; + BKE_render_resolution(rd, false, &full_width, &full_height); dx = rd->border.xmin * full_width - (full_width - this->get_width()) / 2.0f; dy = rd->border.ymin * full_height - (full_height - this->get_height()) / 2.0f; @@ -214,8 +215,8 @@ void CompositorOperation::update_memory_buffer_partial(MemoryBuffer *UNUSED(outp void CompositorOperation::determine_canvas(const rcti &UNUSED(preferred_area), rcti &r_area) { - int width = rd_->xsch * rd_->size / 100; - int height = rd_->ysch * rd_->size / 100; + int width, height; + BKE_render_resolution(rd_, false, &width, &height); /* Check actual render resolution with cropping it may differ with cropped border.rendering * Fix for T31777 Border Crop gives black (easy). */ diff --git a/source/blender/compositor/operations/COM_RenderLayersProg.cc b/source/blender/compositor/operations/COM_RenderLayersProg.cc index 522086658f8..40f2187b27b 100644 --- a/source/blender/compositor/operations/COM_RenderLayersProg.cc +++ b/source/blender/compositor/operations/COM_RenderLayersProg.cc @@ -109,8 +109,8 @@ void RenderLayersProg::execute_pixel_sampled(float output[4], /* see comment in execute_region describing coordinate mapping, * here it simply goes other way around */ - int full_width = rd->xsch * rd->size / 100; - int full_height = rd->ysch * rd->size / 100; + int full_width, full_height; + BKE_render_resolution(rd, false, &full_width, &full_height); dx = rd->border.xmin * full_width - (full_width - this->get_width()) / 2.0f; dy = rd->border.ymin * full_height - (full_height - this->get_height()) / 2.0f; diff --git a/source/blender/compositor/operations/COM_TextureOperation.cc b/source/blender/compositor/operations/COM_TextureOperation.cc index 8b27c3aded4..c1c8db2ae3f 100644 --- a/source/blender/compositor/operations/COM_TextureOperation.cc +++ b/source/blender/compositor/operations/COM_TextureOperation.cc @@ -6,6 +6,7 @@ #include "BKE_image.h" #include "BKE_node.h" +#include "BKE_scene.h" #include "NOD_texture.h" @@ -59,8 +60,8 @@ void TextureBaseOperation::determine_canvas(const rcti &preferred_area, rcti &r_ { r_area = preferred_area; if (BLI_rcti_is_empty(&preferred_area)) { - int width = rd_->xsch * rd_->size / 100; - int height = rd_->ysch * rd_->size / 100; + int width, height; + BKE_render_resolution(rd_, false, &width, &height); r_area.xmax = preferred_area.xmin + width; r_area.ymax = preferred_area.ymin + height; } diff --git a/source/blender/compositor/operations/COM_ViewerOperation.cc b/source/blender/compositor/operations/COM_ViewerOperation.cc index 58392b8a638..aeadf8f255d 100644 --- a/source/blender/compositor/operations/COM_ViewerOperation.cc +++ b/source/blender/compositor/operations/COM_ViewerOperation.cc @@ -104,8 +104,8 @@ void ViewerOperation::execute_region(rcti *rect, unsigned int /*tile_number*/) void ViewerOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - const int scene_render_width = rd_->xsch * rd_->size / 100; - const int scene_render_height = rd_->ysch * rd_->size / 100; + int scene_render_width, scene_render_height; + BKE_render_resolution(rd_, false, &scene_render_width, &scene_render_height); rcti local_preferred = preferred_area; local_preferred.xmax = local_preferred.xmin + scene_render_width; diff --git a/source/blender/editors/mask/mask_query.c b/source/blender/editors/mask/mask_query.c index 02e1524e23e..bb865e925d7 100644 --- a/source/blender/editors/mask/mask_query.c +++ b/source/blender/editors/mask/mask_query.c @@ -682,8 +682,7 @@ void ED_mask_get_size(ScrArea *area, int *width, int *height) } case SPACE_SEQ: { // Scene *scene = CTX_data_scene(C); - // *width = (scene->r.size * scene->r.xsch) / 100; - // *height = (scene->r.size * scene->r.ysch) / 100; + // BKE_render_resolution(&scene->r, false, width, height); break; } case SPACE_IMAGE: { diff --git a/source/blender/editors/render/render_opengl.cc b/source/blender/editors/render/render_opengl.cc index 7bd9812a178..77ad23f1e3f 100644 --- a/source/blender/editors/render/render_opengl.cc +++ b/source/blender/editors/render/render_opengl.cc @@ -758,8 +758,7 @@ static bool screen_opengl_render_init(bContext *C, wmOperator *op) WM_jobs_kill_all_except(wm, CTX_wm_screen(C)); /* create offscreen buffer */ - sizex = (scene->r.size * scene->r.xsch) / 100; - sizey = (scene->r.size * scene->r.ysch) / 100; + BKE_render_resolution(&scene->r, false, &sizex, &sizey); /* corrects render size with actual size, not every card supports non-power-of-two dimensions */ DRW_opengl_context_enable(); /* Off-screen creation needs to be done in DRW context. */ diff --git a/source/blender/editors/render/render_view.cc b/source/blender/editors/render/render_view.cc index a7ff2aad05a..9a16c910205 100644 --- a/source/blender/editors/render/render_view.cc +++ b/source/blender/editors/render/render_view.cc @@ -19,6 +19,7 @@ #include "BKE_image.h" #include "BKE_main.h" #include "BKE_report.h" +#include "BKE_scene.h" #include "BKE_screen.h" #include "BLT_translation.h" @@ -130,8 +131,11 @@ ScrArea *render_view_open(bContext *C, int mx, int my, ReportList *reports) } if (U.render_display_type == USER_RENDER_DISPLAY_WINDOW) { - int sizex = 30 * UI_DPI_FAC + (scene->r.xsch * scene->r.size) / 100; - int sizey = 60 * UI_DPI_FAC + (scene->r.ysch * scene->r.size) / 100; + int sizex, sizey; + BKE_render_resolution(&scene->r, false, &sizex, &sizey); + + sizex += 30 * UI_DPI_FAC; + sizey += 60 * UI_DPI_FAC; /* arbitrary... miniature image window views don't make much sense */ if (sizex < 320) { diff --git a/source/blender/editors/space_image/image_edit.c b/source/blender/editors/space_image/image_edit.c index 950acd77f6a..0de50474ab8 100644 --- a/source/blender/editors/space_image/image_edit.c +++ b/source/blender/editors/space_image/image_edit.c @@ -20,6 +20,7 @@ #include "BKE_image.h" #include "BKE_lib_id.h" #include "BKE_main.h" +#include "BKE_scene.h" #include "IMB_imbuf_types.h" @@ -212,13 +213,7 @@ void ED_space_image_get_size(SpaceImage *sima, int *r_width, int *r_height) } else if (sima->image && sima->image->type == IMA_TYPE_R_RESULT && scene) { /* not very important, just nice */ - *r_width = (scene->r.xsch * scene->r.size) / 100; - *r_height = (scene->r.ysch * scene->r.size) / 100; - - if ((scene->r.mode & R_BORDER) && (scene->r.mode & R_CROP)) { - *r_width *= BLI_rctf_size_x(&scene->r.border); - *r_height *= BLI_rctf_size_y(&scene->r.border); - } + BKE_render_resolution(&scene->r, true, r_width, r_height); } /* I know a bit weak... but preview uses not actual image size */ // XXX else if (image_preview_active(sima, r_width, r_height)); diff --git a/source/blender/editors/space_image/image_ops.c b/source/blender/editors/space_image/image_ops.c index 8b975ee6173..7a820de98b9 100644 --- a/source/blender/editors/space_image/image_ops.c +++ b/source/blender/editors/space_image/image_ops.c @@ -45,6 +45,7 @@ #include "BKE_main.h" #include "BKE_packedFile.h" #include "BKE_report.h" +#include "BKE_scene.h" #include "DEG_depsgraph.h" diff --git a/source/blender/editors/space_sequencer/sequencer_draw.c b/source/blender/editors/space_sequencer/sequencer_draw.c index 4b9ff1e170e..aa5681306a4 100644 --- a/source/blender/editors/space_sequencer/sequencer_draw.c +++ b/source/blender/editors/space_sequencer/sequencer_draw.c @@ -1762,8 +1762,7 @@ void sequencer_draw_maskedit(const bContext *C, Scene *scene, ARegion *region, S // ED_mask_get_size(C, &width, &height); //Scene *scene = CTX_data_scene(C); - width = (scene->r.size * scene->r.xsch) / 100; - height = (scene->r.size * scene->r.ysch) / 100; + BKE_render_resolution(&scene->r, false, &width, &height); ED_mask_draw_region(mask, region, diff --git a/source/blender/editors/space_sequencer/sequencer_view.c b/source/blender/editors/space_sequencer/sequencer_view.c index 4d32c00109a..78fa8c379d9 100644 --- a/source/blender/editors/space_sequencer/sequencer_view.c +++ b/source/blender/editors/space_sequencer/sequencer_view.c @@ -12,6 +12,7 @@ #include "DNA_scene_types.h" #include "BKE_context.h" +#include "BKE_scene.h" #include "WM_api.h" #include "WM_types.h" @@ -174,8 +175,7 @@ static int sequencer_view_all_preview_exec(bContext *C, wmOperator *UNUSED(op)) seq_reset_imageofs(sseq); - imgwidth = (scene->r.size * scene->r.xsch) / 100; - imgheight = (scene->r.size * scene->r.ysch) / 100; + BKE_render_resolution(&scene->r, false, &imgwidth, &imgheight); /* Apply aspect, doesn't need to be that accurate. */ imgwidth = (int)(imgwidth * (scene->r.xasp / scene->r.yasp)); @@ -227,11 +227,11 @@ static int sequencer_view_zoom_ratio_exec(bContext *C, wmOperator *op) float ratio = RNA_float_get(op->ptr, "ratio"); - float winx = (int)(rd->size * rd->xsch) / 100; - float winy = (int)(rd->size * rd->ysch) / 100; + int winx, winy; + BKE_render_resolution(rd, false, &winx, &winy); - float facx = BLI_rcti_size_x(&v2d->mask) / winx; - float facy = BLI_rcti_size_y(&v2d->mask) / winy; + float facx = BLI_rcti_size_x(&v2d->mask) / (float)winx; + float facy = BLI_rcti_size_y(&v2d->mask) / (float)winy; BLI_rctf_resize(&v2d->cur, ceilf(winx * facx / ratio + 0.5f), ceilf(winy * facy / ratio + 0.5f)); diff --git a/source/blender/editors/space_view3d/view3d_edit.c b/source/blender/editors/space_view3d/view3d_edit.c index 041663b4a00..d6ddd6d044e 100644 --- a/source/blender/editors/space_view3d/view3d_edit.c +++ b/source/blender/editors/space_view3d/view3d_edit.c @@ -413,7 +413,9 @@ static void view3d_set_1_to_1_viewborder(Scene *scene, { RegionView3D *rv3d = region->regiondata; float size[2]; - int im_width = (scene->r.size * scene->r.xsch) / 100; + + int im_width, im_height; + BKE_render_resolution(&scene->r, false, &im_width, &im_height); ED_view3d_calc_camera_border_size(scene, depsgraph, region, v3d, rv3d, size); diff --git a/source/blender/io/gpencil/intern/gpencil_io_base.cc b/source/blender/io/gpencil/intern/gpencil_io_base.cc index 05f1158c57d..6db3eccedbe 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_base.cc +++ b/source/blender/io/gpencil/intern/gpencil_io_base.cc @@ -91,8 +91,7 @@ void GpencilIO::prepare_camera_params(Scene *scene, const GpencilIOParams *ipara /* Camera rectangle. */ if ((rv3d_->persp == RV3D_CAMOB) || (force_camera_view)) { - render_x_ = (scene_->r.xsch * scene_->r.size) / 100; - render_y_ = (scene_->r.ysch * scene_->r.size) / 100; + BKE_render_resolution(&scene->r, false, &render_x_, &render_y_); ED_view3d_calc_camera_border(CTX_data_scene(params_.C), depsgraph_, diff --git a/source/blender/io/gpencil/intern/gpencil_io_base.hh b/source/blender/io/gpencil/intern/gpencil_io_base.hh index a89b723ed6c..4987ab34ffc 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_base.hh +++ b/source/blender/io/gpencil/intern/gpencil_io_base.hh @@ -58,8 +58,8 @@ class GpencilIO { struct Scene *scene_; struct RegionView3D *rv3d_; - int16_t winx_, winy_; - int16_t render_x_, render_y_; + int winx_, winy_; + int render_x_, render_y_; float camera_ratio_; rctf camera_rect_; diff --git a/source/blender/render/intern/pipeline.c b/source/blender/render/intern/pipeline.c index 154b689b4a5..1c42467bc3d 100644 --- a/source/blender/render/intern/pipeline.c +++ b/source/blender/render/intern/pipeline.c @@ -1036,8 +1036,7 @@ static void do_render_compositor_scene(Render *re, Scene *sce, int cfra) /* exception: scene uses own size (unfinished code) */ if (0) { - winx = (sce->r.size * sce->r.xsch) / 100; - winy = (sce->r.size * sce->r.ysch) / 100; + BKE_render_resolution(&sce->r, false, &winx, &winy); } /* initial setup */ @@ -1675,8 +1674,7 @@ static int render_init_from_main(Render *re, * r.border is the clipping rect */ /* calculate actual render result and display size */ - winx = (rd->size * rd->xsch) / 100; - winy = (rd->size * rd->ysch) / 100; + BKE_render_resolution(rd, false, &winx, &winy); /* We always render smaller part, inserting it in larger image is compositor business, * it uses 'disprect' for it. */ @@ -2409,8 +2407,7 @@ void RE_PreviewRender(Render *re, Main *bmain, Scene *sce) Object *camera; int winx, winy; - winx = (sce->r.size * sce->r.xsch) / 100; - winy = (sce->r.size * sce->r.ysch) / 100; + BKE_render_resolution(&sce->r, false, &winx, &winy); RE_InitState(re, NULL, &sce->r, &sce->view_layers, NULL, winx, winy, NULL); @@ -2439,8 +2436,7 @@ bool RE_ReadRenderResult(Scene *scene, Scene *scenode) rcti disprect; /* calculate actual render result and display size */ - winx = (scene->r.size * scene->r.xsch) / 100; - winy = (scene->r.size * scene->r.ysch) / 100; + BKE_render_resolution(&scene->r, false, &winx, &winy); /* only in movie case we render smaller part */ if (scene->r.mode & R_BORDER) { diff --git a/source/blender/sequencer/intern/proxy.c b/source/blender/sequencer/intern/proxy.c index 522c64f7791..374e18dd36a 100644 --- a/source/blender/sequencer/intern/proxy.c +++ b/source/blender/sequencer/intern/proxy.c @@ -511,15 +511,11 @@ void SEQ_proxy_rebuild(SeqIndexBuildContext *context, } /* fail safe code */ + int width, height; + BKE_render_resolution(&scene->r, false, &width, &height); - SEQ_render_new_render_data(bmain, - context->depsgraph, - context->scene, - roundf((scene->r.size * (float)scene->r.xsch) / 100.0f), - roundf((scene->r.size * (float)scene->r.ysch) / 100.0f), - 100, - false, - &render_context); + SEQ_render_new_render_data( + bmain, context->depsgraph, context->scene, width, height, 100, false, &render_context); render_context.skip_cache = true; render_context.is_proxy_render = true; diff --git a/source/blender/sequencer/intern/render.c b/source/blender/sequencer/intern/render.c index 15487a597d1..b7dc0e7035d 100644 --- a/source/blender/sequencer/intern/render.c +++ b/source/blender/sequencer/intern/render.c @@ -1447,8 +1447,8 @@ static ImBuf *seq_render_scene_strip(const SeqRenderData *context, if ((sequencer_view3d_fn && do_seq_gl && camera) && is_thread_main) { char err_out[256] = "unknown"; - const int width = (scene->r.xsch * scene->r.size) / 100; - const int height = (scene->r.ysch * scene->r.size) / 100; + int width, height; + BKE_render_resolution(&scene->r, false, &width, &height); const char *viewname = BKE_scene_multiview_render_view_name_get(&scene->r, context->view_id); unsigned int draw_flags = V3D_OFSDRAW_NONE; -- cgit v1.2.3 From 5ddbc14bb240a206ed38d5fd994db2a5ac2c4d3d Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Thu, 14 Jul 2022 20:18:56 +0200 Subject: Render: improve render border operator in image editor * Snap border to pixels just outside the drawn border, to more easily select specific pixels by drawing a border inside them. * Support cropped border renders. --- source/blender/editors/space_image/image_ops.c | 38 ++++++++++++++++++-------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/source/blender/editors/space_image/image_ops.c b/source/blender/editors/space_image/image_ops.c index 7a820de98b9..4036f859231 100644 --- a/source/blender/editors/space_image/image_ops.c +++ b/source/blender/editors/space_image/image_ops.c @@ -3733,38 +3733,52 @@ static int render_border_exec(bContext *C, wmOperator *op) ARegion *region = CTX_wm_region(C); Scene *scene = CTX_data_scene(C); Render *re = RE_GetSceneRender(scene); - RenderData *rd; - rctf border; + SpaceImage *sima = CTX_wm_space_image(C); if (re == NULL) { /* Shouldn't happen, but better be safe close to the release. */ return OPERATOR_CANCELLED; } - rd = RE_engine_get_render_data(re); - if ((rd->mode & (R_BORDER | R_CROP)) == (R_BORDER | R_CROP)) { - BKE_report(op->reports, RPT_INFO, "Can not set border from a cropped render"); - return OPERATOR_CANCELLED; - } + /* Get information about the previous render, or current scene if no render yet. */ + int width, height; + BKE_render_resolution(&scene->r, false, &width, &height); + const RenderData *rd = ED_space_image_has_buffer(sima) ? RE_engine_get_render_data(re) : + &scene->r; - /* get rectangle from operator */ + /* Get rectangle from the operator. */ + rctf border; WM_operator_properties_border_to_rctf(op, &border); UI_view2d_region_to_view_rctf(®ion->v2d, &border, &border); - /* actually set border */ + /* Adjust for cropping. */ + if ((rd->mode & (R_BORDER | R_CROP)) == (R_BORDER | R_CROP)) { + border.xmin = rd->border.xmin + border.xmin * (rd->border.xmax - rd->border.xmin); + border.xmax = rd->border.xmin + border.xmax * (rd->border.xmax - rd->border.xmin); + border.ymin = rd->border.ymin + border.ymin * (rd->border.ymax - rd->border.ymin); + border.ymax = rd->border.ymin + border.ymax * (rd->border.ymax - rd->border.ymin); + } + CLAMP(border.xmin, 0.0f, 1.0f); CLAMP(border.ymin, 0.0f, 1.0f); CLAMP(border.xmax, 0.0f, 1.0f); CLAMP(border.ymax, 0.0f, 1.0f); - scene->r.border = border; - /* drawing a border surrounding the entire camera view switches off border rendering - * or the border covers no pixels */ + /* Drawing a border surrounding the entire camera view switches off border rendering + * or the border covers no pixels. */ if ((border.xmin <= 0.0f && border.xmax >= 1.0f && border.ymin <= 0.0f && border.ymax >= 1.0f) || (border.xmin == border.xmax || border.ymin == border.ymax)) { scene->r.mode &= ~R_BORDER; } else { + /* Snap border to pixel boundaries, so drawing a border within a pixel selects that pixel. */ + border.xmin = floorf(border.xmin * width) / width; + border.xmax = ceilf(border.xmax * width) / width; + border.ymin = floorf(border.ymin * height) / height; + border.ymax = ceilf(border.ymax * height) / height; + + /* Set border. */ + scene->r.border = border; scene->r.mode |= R_BORDER; } -- cgit v1.2.3 From 2e70d5cb980ef2fda6d3bd7e89ed942f3f4b9c59 Mon Sep 17 00:00:00 2001 From: Damien Picard Date: Fri, 15 Jul 2022 13:30:57 +0200 Subject: Render: camera depth of field support for armature bone targets This is useful when using an armature as a camera rig, to avoid creating and targetting an empty object. Differential Revision: https://developer.blender.org/D7012 --- intern/cycles/blender/addon/ui.py | 2 ++ intern/cycles/blender/camera.cpp | 11 ++++++++++- release/scripts/startup/bl_ui/properties_data_camera.py | 2 ++ source/blender/blenkernel/intern/camera.c | 11 ++++++++++- .../depsgraph/intern/builder/deg_builder_relations.cc | 4 ++++ source/blender/editors/armature/armature_naming.c | 12 ++++++++++++ source/blender/makesdna/DNA_camera_types.h | 1 + source/blender/makesrna/intern/rna_camera.c | 6 ++++++ 8 files changed, 47 insertions(+), 2 deletions(-) diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index 3c898d4be73..0fead409866 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -952,6 +952,8 @@ class CYCLES_CAMERA_PT_dof(CyclesButtonsPanel, Panel): col = split.column() col.prop(dof, "focus_object", text="Focus Object") + if dof.focus_object and dof.focus_object.type == 'ARMATURE': + col.prop_search(dof, "focus_subtarget", dof.focus_object.data, "bones", text="Focus Bone") sub = col.row() sub.active = dof.focus_object is None diff --git a/intern/cycles/blender/camera.cpp b/intern/cycles/blender/camera.cpp index 2ab5f02a337..6926c833096 100644 --- a/intern/cycles/blender/camera.cpp +++ b/intern/cycles/blender/camera.cpp @@ -143,11 +143,20 @@ static float blender_camera_focal_distance(BL::RenderEngine &b_engine, if (!b_dof_object) return b_camera.dof().focus_distance(); + Transform dofmat = get_transform(b_dof_object.matrix_world()); + + string focus_subtarget = b_camera.dof().focus_subtarget(); + if (b_dof_object.pose() && !focus_subtarget.empty()) { + BL::PoseBone b_bone = b_dof_object.pose().bones[focus_subtarget]; + if (b_bone) { + dofmat = dofmat * get_transform(b_bone.matrix()); + } + } + /* for dof object, return distance along camera Z direction */ BL::Array b_ob_matrix; b_engine.camera_model_matrix(b_ob, bcam->use_spherical_stereo, b_ob_matrix); Transform obmat = transform_clear_scale(get_transform(b_ob_matrix)); - Transform dofmat = get_transform(b_dof_object.matrix_world()); float3 view_dir = normalize(transform_get_column(&obmat, 2)); float3 dof_dir = transform_get_column(&obmat, 3) - transform_get_column(&dofmat, 3); return fabsf(dot(view_dir, dof_dir)); diff --git a/release/scripts/startup/bl_ui/properties_data_camera.py b/release/scripts/startup/bl_ui/properties_data_camera.py index 8213a865990..a9a032ac4a3 100644 --- a/release/scripts/startup/bl_ui/properties_data_camera.py +++ b/release/scripts/startup/bl_ui/properties_data_camera.py @@ -218,6 +218,8 @@ class DATA_PT_camera_dof(CameraButtonsPanel, Panel): col = layout.column() col.prop(dof, "focus_object", text="Focus on Object") + if dof.focus_object and dof.focus_object.type == 'ARMATURE': + col.prop_search(dof, "focus_subtarget", dof.focus_object.data, "bones", text="Focus on Bone") sub = col.column() sub.active = (dof.focus_object is None) sub.prop(dof, "focus_distance", text="Focus Distance") diff --git a/source/blender/blenkernel/intern/camera.c b/source/blender/blenkernel/intern/camera.c index 7cfac0cb75a..2e6439f7e1a 100644 --- a/source/blender/blenkernel/intern/camera.c +++ b/source/blender/blenkernel/intern/camera.c @@ -26,6 +26,7 @@ #include "BLI_utildefines.h" #include "BKE_anim_data.h" +#include "BKE_action.h" #include "BKE_camera.h" #include "BKE_idtype.h" #include "BKE_layer.h" @@ -221,7 +222,15 @@ float BKE_camera_object_dof_distance(const Object *ob) if (cam->dof.focus_object) { float view_dir[3], dof_dir[3]; normalize_v3_v3(view_dir, ob->obmat[2]); - sub_v3_v3v3(dof_dir, ob->obmat[3], cam->dof.focus_object->obmat[3]); + bPoseChannel *pchan = BKE_pose_channel_find_name(cam->dof.focus_object->pose, cam->dof.focus_subtarget); + if (pchan) { + float posemat[4][4]; + mul_m4_m4m4(posemat, cam->dof.focus_object->obmat, pchan->pose_mat); + sub_v3_v3v3(dof_dir, ob->obmat[3], posemat[3]); + } + else { + sub_v3_v3v3(dof_dir, ob->obmat[3], cam->dof.focus_object->obmat[3]); + } return fabsf(dot_v3v3(view_dir, dof_dir)); } return cam->dof.focus_distance; diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc index 31d5308e825..92396a89cea 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc @@ -2459,6 +2459,10 @@ void DepsgraphRelationBuilder::build_camera(Camera *camera) ComponentKey camera_parameters_key(&camera->id, NodeType::PARAMETERS); ComponentKey dof_ob_key(&camera->dof.focus_object->id, NodeType::TRANSFORM); add_relation(dof_ob_key, camera_parameters_key, "Camera DOF"); + if (camera->dof.focus_subtarget[0]) { + OperationKey target_key(&camera->dof.focus_object->id, NodeType::BONE, camera->dof.focus_subtarget, OperationCode::BONE_DONE); + add_relation(target_key, camera_parameters_key, "Camera DOF subtarget"); + } } } diff --git a/source/blender/editors/armature/armature_naming.c b/source/blender/editors/armature/armature_naming.c index 1f02e24666d..8d5ae6f3654 100644 --- a/source/blender/editors/armature/armature_naming.c +++ b/source/blender/editors/armature/armature_naming.c @@ -13,6 +13,7 @@ #include "MEM_guardedalloc.h" #include "DNA_armature_types.h" +#include "DNA_camera_types.h" #include "DNA_constraint_types.h" #include "DNA_gpencil_modifier_types.h" #include "DNA_gpencil_types.h" @@ -281,6 +282,17 @@ void ED_armature_bone_rename(Main *bmain, } } + /* fix camera focus */ + if (ob->type == OB_CAMERA) { + Camera *cam = (Camera *)ob->data; + if (cam->dof.focus_object->data == arm){ + if (STREQ(cam->dof.focus_subtarget, oldname)) { + BLI_strncpy(cam->dof.focus_subtarget, newname, MAXBONENAME); + DEG_id_tag_update(&cam->id, ID_RECALC_COPY_ON_WRITE); + } + } + } + /* fix grease pencil modifiers and vertex groups */ if (ob->type == OB_GPENCIL) { diff --git a/source/blender/makesdna/DNA_camera_types.h b/source/blender/makesdna/DNA_camera_types.h index e0aec298cd0..10a6c936be1 100644 --- a/source/blender/makesdna/DNA_camera_types.h +++ b/source/blender/makesdna/DNA_camera_types.h @@ -54,6 +54,7 @@ typedef struct CameraBGImage { typedef struct CameraDOFSettings { /** Focal distance for depth of field. */ struct Object *focus_object; + char focus_subtarget[64]; float focus_distance; float aperture_fstop; float aperture_rotation; diff --git a/source/blender/makesrna/intern/rna_camera.c b/source/blender/makesrna/intern/rna_camera.c index 9628c6b2d65..99f8c263da6 100644 --- a/source/blender/makesrna/intern/rna_camera.c +++ b/source/blender/makesrna/intern/rna_camera.c @@ -519,6 +519,12 @@ static void rna_def_camera_dof_settings_data(BlenderRNA *brna) prop, "Focus Object", "Use this object to define the depth of field focal point"); RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_Camera_dependency_update"); + prop = RNA_def_property(srna, "focus_subtarget", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "focus_subtarget"); + RNA_def_property_ui_text( + prop, "Focus Bone", "Use this armature bone to define the depth of field focal point"); + RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_Camera_dependency_update"); + prop = RNA_def_property(srna, "focus_distance", PROP_FLOAT, PROP_DISTANCE); // RNA_def_property_pointer_sdna(prop, NULL, "focus_distance"); RNA_def_property_range(prop, 0.0f, FLT_MAX); -- cgit v1.2.3 From 79da7f2a8fbe37070c899cf8d1298694dbbef86e Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Tue, 12 Jul 2022 17:26:29 +0200 Subject: Cycles: refactor to move part of KernelData definition to template header To be used for specialization on Metal in a following commit, turning these members into compile time constants. Ref D14645 --- intern/cycles/device/cpu/device_impl.cpp | 2 +- intern/cycles/device/optix/device_impl.cpp | 2 +- intern/cycles/kernel/CMakeLists.txt | 1 + intern/cycles/kernel/bvh/bvh.h | 26 +-- intern/cycles/kernel/bvh/embree.h | 10 +- intern/cycles/kernel/camera/camera.h | 2 +- intern/cycles/kernel/data_template.h | 199 ++++++++++++++++++++++ intern/cycles/kernel/types.h | 256 ++++------------------------- intern/cycles/scene/film.cpp | 2 +- intern/cycles/scene/geometry.cpp | 2 +- 10 files changed, 257 insertions(+), 245 deletions(-) create mode 100644 intern/cycles/kernel/data_template.h diff --git a/intern/cycles/device/cpu/device_impl.cpp b/intern/cycles/device/cpu/device_impl.cpp index d4f0532aa5e..1e4b9baa0c0 100644 --- a/intern/cycles/device/cpu/device_impl.cpp +++ b/intern/cycles/device/cpu/device_impl.cpp @@ -197,7 +197,7 @@ void CPUDevice::const_copy_to(const char *name, void *host, size_t size) // Update scene handle (since it is different for each device on multi devices) KernelData *const data = (KernelData *)host; - data->bvh.scene = embree_scene; + data->device_bvh = embree_scene; } #endif kernel_const_copy(&kernel_globals, name, host, size); diff --git a/intern/cycles/device/optix/device_impl.cpp b/intern/cycles/device/optix/device_impl.cpp index e7dcc29a2da..11c0d1bf8a0 100644 --- a/intern/cycles/device/optix/device_impl.cpp +++ b/intern/cycles/device/optix/device_impl.cpp @@ -2047,7 +2047,7 @@ void OptiXDevice::const_copy_to(const char *name, void *host, size_t size) /* Update traversable handle (since it is different for each device on multi devices). */ KernelData *const data = (KernelData *)host; - *(OptixTraversableHandle *)&data->bvh.scene = tlas_handle; + *(OptixTraversableHandle *)&data->device_bvh = tlas_handle; update_launch_params(offsetof(KernelParamsOptiX, data), host, size); return; diff --git a/intern/cycles/kernel/CMakeLists.txt b/intern/cycles/kernel/CMakeLists.txt index 57a26edff50..4ff947e7136 100644 --- a/intern/cycles/kernel/CMakeLists.txt +++ b/intern/cycles/kernel/CMakeLists.txt @@ -282,6 +282,7 @@ set(SRC_KERNEL_UTIL_HEADERS set(SRC_KERNEL_TYPES_HEADERS data_arrays.h + data_template.h tables.h types.h ) diff --git a/intern/cycles/kernel/bvh/bvh.h b/intern/cycles/kernel/bvh/bvh.h index a1d0e307170..f375529a6f6 100644 --- a/intern/cycles/kernel/bvh/bvh.h +++ b/intern/cycles/kernel/bvh/bvh.h @@ -172,7 +172,7 @@ ccl_device_intersect bool scene_intersect(KernelGlobals kg, ray_flags |= OPTIX_RAY_FLAG_TERMINATE_ON_FIRST_HIT; } - optixTrace(scene_intersect_valid(ray) ? kernel_data.bvh.scene : 0, + optixTrace(scene_intersect_valid(ray) ? kernel_data.device_bvh : 0, ray->P, ray->D, 0.0f, @@ -295,14 +295,14 @@ ccl_device_intersect bool scene_intersect(KernelGlobals kg, } # ifdef __EMBREE__ - if (kernel_data.bvh.scene) { + if (kernel_data.device_bvh) { isect->t = ray->t; CCLIntersectContext ctx(kg, CCLIntersectContext::RAY_REGULAR); IntersectContext rtc_ctx(&ctx); RTCRayHit ray_hit; ctx.ray = ray; kernel_embree_setup_rayhit(*ray, ray_hit, visibility); - rtcIntersect1(kernel_data.bvh.scene, &rtc_ctx.context, &ray_hit); + rtcIntersect1(kernel_data.device_bvh, &rtc_ctx.context, &ray_hit); if (ray_hit.hit.geomID != RTC_INVALID_GEOMETRY_ID && ray_hit.hit.primID != RTC_INVALID_GEOMETRY_ID) { kernel_embree_convert_hit(kg, &ray_hit.ray, &ray_hit.hit, isect); @@ -357,7 +357,7 @@ ccl_device_intersect bool scene_intersect_local(KernelGlobals kg, if (local_isect) { local_isect->num_hits = 0; /* Initialize hit count to zero. */ } - optixTrace(scene_intersect_valid(ray) ? kernel_data.bvh.scene : 0, + optixTrace(scene_intersect_valid(ray) ? kernel_data.device_bvh : 0, ray->P, ray->D, 0.0f, @@ -451,7 +451,7 @@ ccl_device_intersect bool scene_intersect_local(KernelGlobals kg, } # ifdef __EMBREE__ - if (kernel_data.bvh.scene) { + if (kernel_data.device_bvh) { const bool has_bvh = !(kernel_data_fetch(object_flag, local_object) & SD_OBJECT_TRANSFORM_APPLIED); CCLIntersectContext ctx( @@ -470,7 +470,7 @@ ccl_device_intersect bool scene_intersect_local(KernelGlobals kg, /* If this object has its own BVH, use it. */ if (has_bvh) { - RTCGeometry geom = rtcGetGeometry(kernel_data.bvh.scene, local_object * 2); + RTCGeometry geom = rtcGetGeometry(kernel_data.device_bvh, local_object * 2); if (geom) { float3 P = ray->P; float3 dir = ray->D; @@ -496,7 +496,7 @@ ccl_device_intersect bool scene_intersect_local(KernelGlobals kg, } } else { - rtcOccluded1(kernel_data.bvh.scene, &rtc_ctx.context, &rtc_ray); + rtcOccluded1(kernel_data.device_bvh, &rtc_ctx.context, &rtc_ray); } /* rtcOccluded1 sets tfar to -inf if a hit was found. */ @@ -539,7 +539,7 @@ ccl_device_intersect bool scene_intersect_shadow_all(KernelGlobals kg, ray_mask = 0xFF; } - optixTrace(scene_intersect_valid(ray) ? kernel_data.bvh.scene : 0, + optixTrace(scene_intersect_valid(ray) ? kernel_data.device_bvh : 0, ray->P, ray->D, 0.0f, @@ -633,7 +633,7 @@ ccl_device_intersect bool scene_intersect_shadow_all(KernelGlobals kg, } # ifdef __EMBREE__ - if (kernel_data.bvh.scene) { + if (kernel_data.device_bvh) { CCLIntersectContext ctx(kg, CCLIntersectContext::RAY_SHADOW_ALL); Intersection *isect_array = (Intersection *)state->shadow_isect; ctx.isect_s = isect_array; @@ -642,7 +642,7 @@ ccl_device_intersect bool scene_intersect_shadow_all(KernelGlobals kg, IntersectContext rtc_ctx(&ctx); RTCRay rtc_ray; kernel_embree_setup_ray(*ray, rtc_ray, visibility); - rtcOccluded1(kernel_data.bvh.scene, &rtc_ctx.context, &rtc_ray); + rtcOccluded1(kernel_data.device_bvh, &rtc_ctx.context, &rtc_ray); *num_recorded_hits = ctx.num_recorded_hits; *throughput = ctx.throughput; @@ -698,7 +698,7 @@ ccl_device_intersect bool scene_intersect_volume(KernelGlobals kg, ray_mask = 0xFF; } - optixTrace(scene_intersect_valid(ray) ? kernel_data.bvh.scene : 0, + optixTrace(scene_intersect_valid(ray) ? kernel_data.device_bvh : 0, ray->P, ray->D, 0.0f, @@ -825,7 +825,7 @@ ccl_device_intersect uint scene_intersect_volume_all(KernelGlobals kg, } # ifdef __EMBREE__ - if (kernel_data.bvh.scene) { + if (kernel_data.device_bvh) { CCLIntersectContext ctx(kg, CCLIntersectContext::RAY_VOLUME_ALL); ctx.isect_s = isect; ctx.max_hits = max_hits; @@ -834,7 +834,7 @@ ccl_device_intersect uint scene_intersect_volume_all(KernelGlobals kg, IntersectContext rtc_ctx(&ctx); RTCRay rtc_ray; kernel_embree_setup_ray(*ray, rtc_ray, visibility); - rtcOccluded1(kernel_data.bvh.scene, &rtc_ctx.context, &rtc_ray); + rtcOccluded1(kernel_data.device_bvh, &rtc_ctx.context, &rtc_ray); return ctx.num_hits; } # endif /* __EMBREE__ */ diff --git a/intern/cycles/kernel/bvh/embree.h b/intern/cycles/kernel/bvh/embree.h index 1c6b9bc1e62..77eec2468f4 100644 --- a/intern/cycles/kernel/bvh/embree.h +++ b/intern/cycles/kernel/bvh/embree.h @@ -107,7 +107,7 @@ ccl_device_inline bool kernel_embree_is_self_intersection(const KernelGlobals kg const int oID = hit->instID[0] / 2; if ((ray->self.object == oID) || (ray->self.light_object == oID)) { RTCScene inst_scene = (RTCScene)rtcGetGeometryUserData( - rtcGetGeometry(kernel_data.bvh.scene, hit->instID[0])); + rtcGetGeometry(kernel_data.device_bvh, hit->instID[0])); const int pID = hit->primID + (intptr_t)rtcGetGeometryUserData(rtcGetGeometry(inst_scene, hit->geomID)); status = intersection_skip_self_shadow(ray->self, oID, pID); @@ -117,7 +117,7 @@ ccl_device_inline bool kernel_embree_is_self_intersection(const KernelGlobals kg const int oID = hit->geomID / 2; if ((ray->self.object == oID) || (ray->self.light_object == oID)) { const int pID = hit->primID + (intptr_t)rtcGetGeometryUserData( - rtcGetGeometry(kernel_data.bvh.scene, hit->geomID)); + rtcGetGeometry(kernel_data.device_bvh, hit->geomID)); status = intersection_skip_self_shadow(ray->self, oID, pID); } } @@ -133,14 +133,14 @@ ccl_device_inline void kernel_embree_convert_hit(KernelGlobals kg, isect->t = ray->tfar; if (hit->instID[0] != RTC_INVALID_GEOMETRY_ID) { RTCScene inst_scene = (RTCScene)rtcGetGeometryUserData( - rtcGetGeometry(kernel_data.bvh.scene, hit->instID[0])); + rtcGetGeometry(kernel_data.device_bvh, hit->instID[0])); isect->prim = hit->primID + (intptr_t)rtcGetGeometryUserData(rtcGetGeometry(inst_scene, hit->geomID)); isect->object = hit->instID[0] / 2; } else { isect->prim = hit->primID + (intptr_t)rtcGetGeometryUserData( - rtcGetGeometry(kernel_data.bvh.scene, hit->geomID)); + rtcGetGeometry(kernel_data.device_bvh, hit->geomID)); isect->object = hit->geomID / 2; } @@ -166,7 +166,7 @@ ccl_device_inline void kernel_embree_convert_sss_hit( isect->v = hit->u; isect->t = ray->tfar; RTCScene inst_scene = (RTCScene)rtcGetGeometryUserData( - rtcGetGeometry(kernel_data.bvh.scene, object * 2)); + rtcGetGeometry(kernel_data.device_bvh, object * 2)); isect->prim = hit->primID + (intptr_t)rtcGetGeometryUserData(rtcGetGeometry(inst_scene, hit->geomID)); isect->object = object; diff --git a/intern/cycles/kernel/camera/camera.h b/intern/cycles/kernel/camera/camera.h index 25960a94ddb..7e1b1c037e9 100644 --- a/intern/cycles/kernel/camera/camera.h +++ b/intern/cycles/kernel/camera/camera.h @@ -368,7 +368,7 @@ ccl_device_inline void camera_sample(KernelGlobals kg, ccl_private Ray *ray) { /* pixel filter */ - int filter_table_offset = kernel_data.film.filter_table_offset; + int filter_table_offset = kernel_data.tables.filter_table_offset; float raster_x = x + lookup_table_read(kg, filter_u, filter_table_offset, FILTER_TABLE_SIZE); float raster_y = y + lookup_table_read(kg, filter_v, filter_table_offset, FILTER_TABLE_SIZE); diff --git a/intern/cycles/kernel/data_template.h b/intern/cycles/kernel/data_template.h new file mode 100644 index 00000000000..22f945f1335 --- /dev/null +++ b/intern/cycles/kernel/data_template.h @@ -0,0 +1,199 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright 2011-2022 Blender Foundation */ + +#ifndef KERNEL_STRUCT_BEGIN +# define KERNEL_STRUCT_BEGIN(name, parent) +#endif +#ifndef KERNEL_STRUCT_END +# define KERNEL_STRUCT_END(name) +#endif +#ifndef KERNEL_STRUCT_MEMBER +# define KERNEL_STRUCT_MEMBER(parent, type, name) +#endif + +/* Background. */ + +KERNEL_STRUCT_BEGIN(KernelBackground, background) +/* xyz store direction, w the angle. float4 instead of float3 is used + * to ensure consistent padding/alignment across devices. */ +KERNEL_STRUCT_MEMBER(background, float4, sun) +/* Only shader index. */ +KERNEL_STRUCT_MEMBER(background, int, surface_shader) +KERNEL_STRUCT_MEMBER(background, int, volume_shader) +KERNEL_STRUCT_MEMBER(background, float, volume_step_size) +KERNEL_STRUCT_MEMBER(background, int, transparent) +KERNEL_STRUCT_MEMBER(background, float, transparent_roughness_squared_threshold) +/* Portal sampling. */ +KERNEL_STRUCT_MEMBER(background, float, portal_weight) +KERNEL_STRUCT_MEMBER(background, int, num_portals) +KERNEL_STRUCT_MEMBER(background, int, portal_offset) +/* Sun sampling. */ +KERNEL_STRUCT_MEMBER(background, float, sun_weight) +/* Importance map sampling. */ +KERNEL_STRUCT_MEMBER(background, float, map_weight) +KERNEL_STRUCT_MEMBER(background, int, map_res_x) +KERNEL_STRUCT_MEMBER(background, int, map_res_y) +/* Multiple importance sampling. */ +KERNEL_STRUCT_MEMBER(background, int, use_mis) +/* Lightgroup. */ +KERNEL_STRUCT_MEMBER(background, int, lightgroup) +/* Padding. */ +KERNEL_STRUCT_MEMBER(background, int, pad1) +KERNEL_STRUCT_MEMBER(background, int, pad2) +KERNEL_STRUCT_MEMBER(background, int, pad3) +KERNEL_STRUCT_END(KernelBackground) + +/* BVH: own BVH2 if no native device acceleration struct used. */ + +KERNEL_STRUCT_BEGIN(KernelBVH, bvh) +KERNEL_STRUCT_MEMBER(bvh, int, root) +KERNEL_STRUCT_MEMBER(bvh, int, have_motion) +KERNEL_STRUCT_MEMBER(bvh, int, have_curves) +KERNEL_STRUCT_MEMBER(bvh, int, bvh_layout) +KERNEL_STRUCT_MEMBER(bvh, int, use_bvh_steps) +KERNEL_STRUCT_MEMBER(bvh, int, curve_subdivisions) +KERNEL_STRUCT_MEMBER(bvh, int, pad1) +KERNEL_STRUCT_MEMBER(bvh, int, pad2) +KERNEL_STRUCT_END(KernelBVH) + +/* Film. */ + +KERNEL_STRUCT_BEGIN(KernelFilm, film) +/* XYZ to rendering color space transform. float4 instead of float3 to + * ensure consistent padding/alignment across devices. */ +KERNEL_STRUCT_MEMBER(film, float4, xyz_to_r) +KERNEL_STRUCT_MEMBER(film, float4, xyz_to_g) +KERNEL_STRUCT_MEMBER(film, float4, xyz_to_b) +KERNEL_STRUCT_MEMBER(film, float4, rgb_to_y) +/* Rec709 to rendering color space. */ +KERNEL_STRUCT_MEMBER(film, float4, rec709_to_r) +KERNEL_STRUCT_MEMBER(film, float4, rec709_to_g) +KERNEL_STRUCT_MEMBER(film, float4, rec709_to_b) +KERNEL_STRUCT_MEMBER(film, int, is_rec709) +/* Exposuse. */ +KERNEL_STRUCT_MEMBER(film, float, exposure) +/* Passed used. */ +KERNEL_STRUCT_MEMBER(film, int, pass_flag) +KERNEL_STRUCT_MEMBER(film, int, light_pass_flag) +/* Pass offsets. */ +KERNEL_STRUCT_MEMBER(film, int, pass_stride) +KERNEL_STRUCT_MEMBER(film, int, pass_combined) +KERNEL_STRUCT_MEMBER(film, int, pass_depth) +KERNEL_STRUCT_MEMBER(film, int, pass_position) +KERNEL_STRUCT_MEMBER(film, int, pass_normal) +KERNEL_STRUCT_MEMBER(film, int, pass_roughness) +KERNEL_STRUCT_MEMBER(film, int, pass_motion) +KERNEL_STRUCT_MEMBER(film, int, pass_motion_weight) +KERNEL_STRUCT_MEMBER(film, int, pass_uv) +KERNEL_STRUCT_MEMBER(film, int, pass_object_id) +KERNEL_STRUCT_MEMBER(film, int, pass_material_id) +KERNEL_STRUCT_MEMBER(film, int, pass_diffuse_color) +KERNEL_STRUCT_MEMBER(film, int, pass_glossy_color) +KERNEL_STRUCT_MEMBER(film, int, pass_transmission_color) +KERNEL_STRUCT_MEMBER(film, int, pass_diffuse_indirect) +KERNEL_STRUCT_MEMBER(film, int, pass_glossy_indirect) +KERNEL_STRUCT_MEMBER(film, int, pass_transmission_indirect) +KERNEL_STRUCT_MEMBER(film, int, pass_volume_indirect) +KERNEL_STRUCT_MEMBER(film, int, pass_diffuse_direct) +KERNEL_STRUCT_MEMBER(film, int, pass_glossy_direct) +KERNEL_STRUCT_MEMBER(film, int, pass_transmission_direct) +KERNEL_STRUCT_MEMBER(film, int, pass_volume_direct) +KERNEL_STRUCT_MEMBER(film, int, pass_emission) +KERNEL_STRUCT_MEMBER(film, int, pass_background) +KERNEL_STRUCT_MEMBER(film, int, pass_ao) +KERNEL_STRUCT_MEMBER(film, float, pass_alpha_threshold) +KERNEL_STRUCT_MEMBER(film, int, pass_shadow) +KERNEL_STRUCT_MEMBER(film, float, pass_shadow_scale) +KERNEL_STRUCT_MEMBER(film, int, pass_shadow_catcher) +KERNEL_STRUCT_MEMBER(film, int, pass_shadow_catcher_sample_count) +KERNEL_STRUCT_MEMBER(film, int, pass_shadow_catcher_matte) +/* Cryptomatte. */ +KERNEL_STRUCT_MEMBER(film, int, cryptomatte_passes) +KERNEL_STRUCT_MEMBER(film, int, cryptomatte_depth) +KERNEL_STRUCT_MEMBER(film, int, pass_cryptomatte) +/* Adaptive sampling. */ +KERNEL_STRUCT_MEMBER(film, int, pass_adaptive_aux_buffer) +KERNEL_STRUCT_MEMBER(film, int, pass_sample_count) +/* Mist. */ +KERNEL_STRUCT_MEMBER(film, int, pass_mist) +KERNEL_STRUCT_MEMBER(film, float, mist_start) +KERNEL_STRUCT_MEMBER(film, float, mist_inv_depth) +KERNEL_STRUCT_MEMBER(film, float, mist_falloff) +/* Denoising. */ +KERNEL_STRUCT_MEMBER(film, int, pass_denoising_normal) +KERNEL_STRUCT_MEMBER(film, int, pass_denoising_albedo) +KERNEL_STRUCT_MEMBER(film, int, pass_denoising_depth) +/* AOVs. */ +KERNEL_STRUCT_MEMBER(film, int, pass_aov_color) +KERNEL_STRUCT_MEMBER(film, int, pass_aov_value) +/* Light groups. */ +KERNEL_STRUCT_MEMBER(film, int, pass_lightgroup) +/* Baking. */ +KERNEL_STRUCT_MEMBER(film, int, pass_bake_primitive) +KERNEL_STRUCT_MEMBER(film, int, pass_bake_differential) +/* Shadow catcher. */ +KERNEL_STRUCT_MEMBER(film, int, use_approximate_shadow_catcher) +/* Padding. */ +KERNEL_STRUCT_MEMBER(film, int, pad1) +KERNEL_STRUCT_MEMBER(film, int, pad2) +KERNEL_STRUCT_END(KernelFilm) + +/* Integrator. */ + +KERNEL_STRUCT_BEGIN(KernelIntegrator, integrator) +/* Emission. */ +KERNEL_STRUCT_MEMBER(integrator, int, use_direct_light) +KERNEL_STRUCT_MEMBER(integrator, int, num_distribution) +KERNEL_STRUCT_MEMBER(integrator, int, num_all_lights) +KERNEL_STRUCT_MEMBER(integrator, float, pdf_triangles) +KERNEL_STRUCT_MEMBER(integrator, float, pdf_lights) +KERNEL_STRUCT_MEMBER(integrator, float, light_inv_rr_threshold) +/* Bounces. */ +KERNEL_STRUCT_MEMBER(integrator, int, min_bounce) +KERNEL_STRUCT_MEMBER(integrator, int, max_bounce) +KERNEL_STRUCT_MEMBER(integrator, int, max_diffuse_bounce) +KERNEL_STRUCT_MEMBER(integrator, int, max_glossy_bounce) +KERNEL_STRUCT_MEMBER(integrator, int, max_transmission_bounce) +KERNEL_STRUCT_MEMBER(integrator, int, max_volume_bounce) +/* AO bounces. */ +KERNEL_STRUCT_MEMBER(integrator, int, ao_bounces) +KERNEL_STRUCT_MEMBER(integrator, float, ao_bounces_distance) +KERNEL_STRUCT_MEMBER(integrator, float, ao_bounces_factor) +KERNEL_STRUCT_MEMBER(integrator, float, ao_additive_factor) +/* Transparency. */ +KERNEL_STRUCT_MEMBER(integrator, int, transparent_min_bounce) +KERNEL_STRUCT_MEMBER(integrator, int, transparent_max_bounce) +KERNEL_STRUCT_MEMBER(integrator, int, transparent_shadows) +/* Caustics. */ +KERNEL_STRUCT_MEMBER(integrator, int, caustics_reflective) +KERNEL_STRUCT_MEMBER(integrator, int, caustics_refractive) +KERNEL_STRUCT_MEMBER(integrator, float, filter_glossy) +/* Seed. */ +KERNEL_STRUCT_MEMBER(integrator, int, seed) +/* Clamp. */ +KERNEL_STRUCT_MEMBER(integrator, float, sample_clamp_direct) +KERNEL_STRUCT_MEMBER(integrator, float, sample_clamp_indirect) +/* MIS. */ +KERNEL_STRUCT_MEMBER(integrator, int, use_lamp_mis) +/* Caustics. */ +KERNEL_STRUCT_MEMBER(integrator, int, use_caustics) +/* Sampling pattern. */ +KERNEL_STRUCT_MEMBER(integrator, int, sampling_pattern) +KERNEL_STRUCT_MEMBER(integrator, float, scrambling_distance) +/* Volume render. */ +KERNEL_STRUCT_MEMBER(integrator, int, use_volumes) +KERNEL_STRUCT_MEMBER(integrator, int, volume_max_steps) +KERNEL_STRUCT_MEMBER(integrator, float, volume_step_rate) +/* Shadow catcher. */ +KERNEL_STRUCT_MEMBER(integrator, int, has_shadow_catcher) +/* Closure filter. */ +KERNEL_STRUCT_MEMBER(integrator, int, filter_closures) +/* MIS debugging. */ +KERNEL_STRUCT_MEMBER(integrator, int, direct_light_sampling_type) +/* Padding */ +KERNEL_STRUCT_MEMBER(integrator, int, pad1) +KERNEL_STRUCT_END(KernelIntegrator) + +#undef KERNEL_STRUCT_BEGIN +#undef KERNEL_STRUCT_MEMBER +#undef KERNEL_STRUCT_END diff --git a/intern/cycles/kernel/types.h b/intern/cycles/kernel/types.h index f2e61d25002..72cee6ae344 100644 --- a/intern/cycles/kernel/types.h +++ b/intern/cycles/kernel/types.h @@ -1072,94 +1072,6 @@ typedef struct KernelCamera { } KernelCamera; static_assert_align(KernelCamera, 16); -typedef struct KernelFilm { - float exposure; - int pass_flag; - - int light_pass_flag; - int pass_stride; - - int pass_combined; - int pass_depth; - int pass_position; - int pass_normal; - int pass_roughness; - int pass_motion; - - int pass_motion_weight; - int pass_uv; - int pass_object_id; - int pass_material_id; - - int pass_diffuse_color; - int pass_glossy_color; - int pass_transmission_color; - - int pass_diffuse_indirect; - int pass_glossy_indirect; - int pass_transmission_indirect; - int pass_volume_indirect; - - int pass_diffuse_direct; - int pass_glossy_direct; - int pass_transmission_direct; - int pass_volume_direct; - - int pass_emission; - int pass_background; - int pass_ao; - float pass_alpha_threshold; - - int pass_shadow; - float pass_shadow_scale; - - int pass_shadow_catcher; - int pass_shadow_catcher_sample_count; - int pass_shadow_catcher_matte; - - int filter_table_offset; - - int cryptomatte_passes; - int cryptomatte_depth; - int pass_cryptomatte; - - int pass_adaptive_aux_buffer; - int pass_sample_count; - - int pass_mist; - float mist_start; - float mist_inv_depth; - float mist_falloff; - - int pass_denoising_normal; - int pass_denoising_albedo; - int pass_denoising_depth; - - int pass_aov_color; - int pass_aov_value; - int pass_lightgroup; - - /* XYZ to rendering color space transform. float4 instead of float3 to - * ensure consistent padding/alignment across devices. */ - float4 xyz_to_r; - float4 xyz_to_g; - float4 xyz_to_b; - float4 rgb_to_y; - /* Rec709 to rendering color space. */ - float4 rec709_to_r; - float4 rec709_to_g; - float4 rec709_to_b; - int is_rec709; - - int pass_bake_primitive; - int pass_bake_differential; - - int use_approximate_shadow_catcher; - - int pad1; -} KernelFilm; -static_assert_align(KernelFilm, 16); - typedef struct KernelFilmConvert { int pass_offset; int pass_stride; @@ -1201,108 +1113,6 @@ typedef struct KernelFilmConvert { } KernelFilmConvert; static_assert_align(KernelFilmConvert, 16); -typedef struct KernelBackground { - /* only shader index */ - int surface_shader; - int volume_shader; - float volume_step_size; - int transparent; - float transparent_roughness_squared_threshold; - - /* portal sampling */ - float portal_weight; - int num_portals; - int portal_offset; - - /* sun sampling */ - float sun_weight; - /* xyz store direction, w the angle. float4 instead of float3 is used - * to ensure consistent padding/alignment across devices. */ - float4 sun; - - /* map sampling */ - float map_weight; - int map_res_x; - int map_res_y; - - int use_mis; - - int lightgroup; - - /* Padding */ - int pad1, pad2; -} KernelBackground; -static_assert_align(KernelBackground, 16); - -typedef struct KernelIntegrator { - /* emission */ - int use_direct_light; - int num_distribution; - int num_all_lights; - float pdf_triangles; - float pdf_lights; - float light_inv_rr_threshold; - - /* bounces */ - int min_bounce; - int max_bounce; - - int max_diffuse_bounce; - int max_glossy_bounce; - int max_transmission_bounce; - int max_volume_bounce; - - /* AO bounces */ - int ao_bounces; - float ao_bounces_distance; - float ao_bounces_factor; - float ao_additive_factor; - - /* transparent */ - int transparent_min_bounce; - int transparent_max_bounce; - int transparent_shadows; - - /* caustics */ - int caustics_reflective; - int caustics_refractive; - float filter_glossy; - - /* seed */ - int seed; - - /* clamp */ - float sample_clamp_direct; - float sample_clamp_indirect; - - /* mis */ - int use_lamp_mis; - - /* caustics */ - int use_caustics; - - /* sampler */ - int sampling_pattern; - - /* volume render */ - int use_volumes; - int volume_max_steps; - float volume_step_rate; - - int has_shadow_catcher; - float scrambling_distance; - - /* Closure filter. */ - int filter_closures; - - /* MIS debugging. */ - int direct_light_sampling_type; - - /* padding */ - int pad1; -} KernelIntegrator; -static_assert_align(KernelIntegrator, 16); - typedef enum KernelBVHLayout { BVH_LAYOUT_NONE = 0, @@ -1320,36 +1130,19 @@ typedef enum KernelBVHLayout { BVH_LAYOUT_ALL = BVH_LAYOUT_BVH2 | BVH_LAYOUT_EMBREE | BVH_LAYOUT_OPTIX | BVH_LAYOUT_METAL, } KernelBVHLayout; -typedef struct KernelBVH { - /* Own BVH */ - int root; - int have_motion; - int have_curves; - int bvh_layout; - int use_bvh_steps; - int curve_subdivisions; - - /* Custom BVH */ -#ifdef __KERNEL_OPTIX__ - OptixTraversableHandle scene; -#elif defined __METALRT__ - metalrt_as_type scene; -#else -# ifdef __EMBREE__ - RTCScene scene; -# ifndef __KERNEL_64_BIT__ - int pad2; -# endif -# else - int scene, pad2; -# endif -#endif -} KernelBVH; -static_assert_align(KernelBVH, 16); +/* Specialized struct that can become constants in dynamic compilation. */ +#define KERNEL_STRUCT_BEGIN(name, parent) struct name { +#define KERNEL_STRUCT_END(name) \ + } \ + ; \ + static_assert_align(name, 16); +#define KERNEL_STRUCT_MEMBER(parent, type, name) type name; +#include "kernel/data_template.h" typedef struct KernelTables { int beckmann_offset; - int pad1, pad2, pad3; + int filter_table_offset; + int pad1, pad2; } KernelTables; static_assert_align(KernelTables, 16); @@ -1362,18 +1155,37 @@ typedef struct KernelBake { static_assert_align(KernelBake, 16); typedef struct KernelData { + /* Features and limits. */ uint kernel_features; uint max_closures; uint max_shaders; uint volume_stack_size; + /* Always dynamic data mambers. */ KernelCamera cam; - KernelFilm film; - KernelBackground background; - KernelIntegrator integrator; - KernelBVH bvh; - KernelTables tables; KernelBake bake; + KernelTables tables; + + /* Potentially specialized data members. */ +#define KERNEL_STRUCT_BEGIN(name, parent) name parent; +#include "kernel/data_template.h" + + /* Device specific BVH. */ +#ifdef __KERNEL_OPTIX__ + OptixTraversableHandle device_bvh; +#elif defined __METALRT__ + metalrt_as_type device_bvh; +#else +# ifdef __EMBREE__ + RTCScene device_bvh; +# ifndef __KERNEL_64_BIT__ + int pad1; +# endif +# else + int device_bvh, pad1; +# endif +#endif + int pad2, pad3; } KernelData; static_assert_align(KernelData, 16); diff --git a/intern/cycles/scene/film.cpp b/intern/cycles/scene/film.cpp index 8239ee84b82..a6a8f90a449 100644 --- a/intern/cycles/scene/film.cpp +++ b/intern/cycles/scene/film.cpp @@ -394,7 +394,7 @@ void Film::device_update(Device *device, DeviceScene *dscene, Scene *scene) vector table = filter_table(filter_type, filter_width); scene->lookup_tables->remove_table(&filter_table_offset_); filter_table_offset_ = scene->lookup_tables->add_table(dscene, table); - kfilm->filter_table_offset = (int)filter_table_offset_; + dscene->data.tables.filter_table_offset = (int)filter_table_offset_; /* mist pass parameters */ kfilm->mist_start = mist_start; diff --git a/intern/cycles/scene/geometry.cpp b/intern/cycles/scene/geometry.cpp index bdc8839e277..67ff118692e 100644 --- a/intern/cycles/scene/geometry.cpp +++ b/intern/cycles/scene/geometry.cpp @@ -1362,7 +1362,7 @@ void GeometryManager::device_update_bvh(Device *device, dscene->data.bvh.use_bvh_steps = (scene->params.num_bvh_time_steps != 0); dscene->data.bvh.curve_subdivisions = scene->params.curve_subdivisions(); /* The scene handle is set in 'CPUDevice::const_copy_to' and 'OptiXDevice::const_copy_to' */ - dscene->data.bvh.scene = 0; + dscene->data.device_bvh = 0; } /* Set of flags used to help determining what data has been modified or needs reallocation, so we -- cgit v1.2.3 From 5653c5fcdd9f424dc05ddf73b18ba8294daf4788 Mon Sep 17 00:00:00 2001 From: Michael Jones Date: Tue, 12 Jul 2022 17:22:36 +0200 Subject: Cycles: keep track of SVM nodes used in kernels To be used for specialization in Metal, to automatically leave out unused nodes from the kernel. Ref D14645 --- intern/cycles/kernel/CMakeLists.txt | 1 + intern/cycles/kernel/data_template.h | 7 + intern/cycles/kernel/svm/node_types_template.h | 110 ++++ intern/cycles/kernel/svm/svm.h | 683 +++++++++++++------------ intern/cycles/kernel/svm/types.h | 101 +--- intern/cycles/scene/shader_nodes.cpp | 6 +- intern/cycles/scene/svm.cpp | 12 +- intern/cycles/scene/svm.h | 1 + 8 files changed, 477 insertions(+), 444 deletions(-) create mode 100644 intern/cycles/kernel/svm/node_types_template.h diff --git a/intern/cycles/kernel/CMakeLists.txt b/intern/cycles/kernel/CMakeLists.txt index 4ff947e7136..527cc4ec111 100644 --- a/intern/cycles/kernel/CMakeLists.txt +++ b/intern/cycles/kernel/CMakeLists.txt @@ -154,6 +154,7 @@ set(SRC_KERNEL_SVM_HEADERS svm/math_util.h svm/mix.h svm/musgrave.h + svm/node_types_template.h svm/noise.h svm/noisetex.h svm/normal.h diff --git a/intern/cycles/kernel/data_template.h b/intern/cycles/kernel/data_template.h index 22f945f1335..b06ac62a5d8 100644 --- a/intern/cycles/kernel/data_template.h +++ b/intern/cycles/kernel/data_template.h @@ -194,6 +194,13 @@ KERNEL_STRUCT_MEMBER(integrator, int, direct_light_sampling_type) KERNEL_STRUCT_MEMBER(integrator, int, pad1) KERNEL_STRUCT_END(KernelIntegrator) +/* SVM. For shader specialization. */ + +KERNEL_STRUCT_BEGIN(KernelSVMUsage, svm_usage) +#define SHADER_NODE_TYPE(type) KERNEL_STRUCT_MEMBER(svm_usage, int, type) +#include "kernel/svm/node_types_template.h" +KERNEL_STRUCT_END(KernelSVMUsage) + #undef KERNEL_STRUCT_BEGIN #undef KERNEL_STRUCT_MEMBER #undef KERNEL_STRUCT_END diff --git a/intern/cycles/kernel/svm/node_types_template.h b/intern/cycles/kernel/svm/node_types_template.h new file mode 100644 index 00000000000..39d279be4cb --- /dev/null +++ b/intern/cycles/kernel/svm/node_types_template.h @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright 2011-2022 Blender Foundation */ + +#ifndef SHADER_NODE_TYPE +# define SHADER_NODE_TYPE(name) +#endif + +/* NOTE: for best OpenCL performance, item definition in the enum must + * match the switch case order in `svm.h`. */ + +SHADER_NODE_TYPE(NODE_END) +SHADER_NODE_TYPE(NODE_SHADER_JUMP) +SHADER_NODE_TYPE(NODE_CLOSURE_BSDF) +SHADER_NODE_TYPE(NODE_CLOSURE_EMISSION) +SHADER_NODE_TYPE(NODE_CLOSURE_BACKGROUND) +SHADER_NODE_TYPE(NODE_CLOSURE_SET_WEIGHT) +SHADER_NODE_TYPE(NODE_CLOSURE_WEIGHT) +SHADER_NODE_TYPE(NODE_EMISSION_WEIGHT) +SHADER_NODE_TYPE(NODE_MIX_CLOSURE) +SHADER_NODE_TYPE(NODE_JUMP_IF_ZERO) +SHADER_NODE_TYPE(NODE_JUMP_IF_ONE) +SHADER_NODE_TYPE(NODE_GEOMETRY) +SHADER_NODE_TYPE(NODE_CONVERT) +SHADER_NODE_TYPE(NODE_TEX_COORD) +SHADER_NODE_TYPE(NODE_VALUE_F) +SHADER_NODE_TYPE(NODE_VALUE_V) +SHADER_NODE_TYPE(NODE_ATTR) +SHADER_NODE_TYPE(NODE_VERTEX_COLOR) +SHADER_NODE_TYPE(NODE_GEOMETRY_BUMP_DX) +SHADER_NODE_TYPE(NODE_GEOMETRY_BUMP_DY) +SHADER_NODE_TYPE(NODE_SET_DISPLACEMENT) +SHADER_NODE_TYPE(NODE_DISPLACEMENT) +SHADER_NODE_TYPE(NODE_VECTOR_DISPLACEMENT) +SHADER_NODE_TYPE(NODE_TEX_IMAGE) +SHADER_NODE_TYPE(NODE_TEX_IMAGE_BOX) +SHADER_NODE_TYPE(NODE_TEX_NOISE) +SHADER_NODE_TYPE(NODE_SET_BUMP) +SHADER_NODE_TYPE(NODE_ATTR_BUMP_DX) +SHADER_NODE_TYPE(NODE_ATTR_BUMP_DY) +SHADER_NODE_TYPE(NODE_VERTEX_COLOR_BUMP_DX) +SHADER_NODE_TYPE(NODE_VERTEX_COLOR_BUMP_DY) +SHADER_NODE_TYPE(NODE_TEX_COORD_BUMP_DX) +SHADER_NODE_TYPE(NODE_TEX_COORD_BUMP_DY) +SHADER_NODE_TYPE(NODE_CLOSURE_SET_NORMAL) +SHADER_NODE_TYPE(NODE_ENTER_BUMP_EVAL) +SHADER_NODE_TYPE(NODE_LEAVE_BUMP_EVAL) +SHADER_NODE_TYPE(NODE_HSV) +SHADER_NODE_TYPE(NODE_CLOSURE_HOLDOUT) +SHADER_NODE_TYPE(NODE_FRESNEL) +SHADER_NODE_TYPE(NODE_LAYER_WEIGHT) +SHADER_NODE_TYPE(NODE_CLOSURE_VOLUME) +SHADER_NODE_TYPE(NODE_PRINCIPLED_VOLUME) +SHADER_NODE_TYPE(NODE_MATH) +SHADER_NODE_TYPE(NODE_VECTOR_MATH) +SHADER_NODE_TYPE(NODE_RGB_RAMP) +SHADER_NODE_TYPE(NODE_GAMMA) +SHADER_NODE_TYPE(NODE_BRIGHTCONTRAST) +SHADER_NODE_TYPE(NODE_LIGHT_PATH) +SHADER_NODE_TYPE(NODE_OBJECT_INFO) +SHADER_NODE_TYPE(NODE_PARTICLE_INFO) +SHADER_NODE_TYPE(NODE_HAIR_INFO) +SHADER_NODE_TYPE(NODE_POINT_INFO) +SHADER_NODE_TYPE(NODE_TEXTURE_MAPPING) +SHADER_NODE_TYPE(NODE_MAPPING) +SHADER_NODE_TYPE(NODE_MIN_MAX) +SHADER_NODE_TYPE(NODE_CAMERA) +SHADER_NODE_TYPE(NODE_TEX_ENVIRONMENT) +SHADER_NODE_TYPE(NODE_TEX_SKY) +SHADER_NODE_TYPE(NODE_TEX_GRADIENT) +SHADER_NODE_TYPE(NODE_TEX_VORONOI) +SHADER_NODE_TYPE(NODE_TEX_MUSGRAVE) +SHADER_NODE_TYPE(NODE_TEX_WAVE) +SHADER_NODE_TYPE(NODE_TEX_MAGIC) +SHADER_NODE_TYPE(NODE_TEX_CHECKER) +SHADER_NODE_TYPE(NODE_TEX_BRICK) +SHADER_NODE_TYPE(NODE_TEX_WHITE_NOISE) +SHADER_NODE_TYPE(NODE_NORMAL) +SHADER_NODE_TYPE(NODE_LIGHT_FALLOFF) +SHADER_NODE_TYPE(NODE_IES) +SHADER_NODE_TYPE(NODE_CURVES) +SHADER_NODE_TYPE(NODE_TANGENT) +SHADER_NODE_TYPE(NODE_NORMAL_MAP) +SHADER_NODE_TYPE(NODE_INVERT) +SHADER_NODE_TYPE(NODE_MIX) +SHADER_NODE_TYPE(NODE_SEPARATE_COLOR) +SHADER_NODE_TYPE(NODE_COMBINE_COLOR) +SHADER_NODE_TYPE(NODE_SEPARATE_VECTOR) +SHADER_NODE_TYPE(NODE_COMBINE_VECTOR) +SHADER_NODE_TYPE(NODE_SEPARATE_HSV) +SHADER_NODE_TYPE(NODE_COMBINE_HSV) +SHADER_NODE_TYPE(NODE_VECTOR_ROTATE) +SHADER_NODE_TYPE(NODE_VECTOR_TRANSFORM) +SHADER_NODE_TYPE(NODE_WIREFRAME) +SHADER_NODE_TYPE(NODE_WAVELENGTH) +SHADER_NODE_TYPE(NODE_BLACKBODY) +SHADER_NODE_TYPE(NODE_MAP_RANGE) +SHADER_NODE_TYPE(NODE_VECTOR_MAP_RANGE) +SHADER_NODE_TYPE(NODE_CLAMP) +SHADER_NODE_TYPE(NODE_BEVEL) +SHADER_NODE_TYPE(NODE_AMBIENT_OCCLUSION) +SHADER_NODE_TYPE(NODE_TEX_VOXEL) +SHADER_NODE_TYPE(NODE_AOV_START) +SHADER_NODE_TYPE(NODE_AOV_COLOR) +SHADER_NODE_TYPE(NODE_AOV_VALUE) +SHADER_NODE_TYPE(NODE_FLOAT_CURVE) + +/* Padding for struct alignment. */ +SHADER_NODE_TYPE(NODE_PAD1) + +#undef SHADER_NODE_TYPE diff --git a/intern/cycles/kernel/svm/svm.h b/intern/cycles/kernel/svm/svm.h index 8fd41ec8531..9840cda3655 100644 --- a/intern/cycles/kernel/svm/svm.h +++ b/intern/cycles/kernel/svm/svm.h @@ -204,6 +204,8 @@ CCL_NAMESPACE_END CCL_NAMESPACE_BEGIN +#define SVM_CASE(node) case node: + /* Main Interpreter Loop */ template ccl_device void svm_eval_nodes(KernelGlobals kg, @@ -219,9 +221,10 @@ ccl_device void svm_eval_nodes(KernelGlobals kg, uint4 node = read_node(kg, &offset); switch (node.x) { - case NODE_END: - return; - case NODE_SHADER_JUMP: { + SVM_CASE(NODE_END) + return; + SVM_CASE(NODE_SHADER_JUMP) + { if (type == SHADER_TYPE_SURFACE) offset = node.y; else if (type == SHADER_TYPE_VOLUME) @@ -232,351 +235,349 @@ ccl_device void svm_eval_nodes(KernelGlobals kg, return; break; } - case NODE_CLOSURE_BSDF: - offset = svm_node_closure_bsdf( - kg, sd, stack, node, path_flag, offset); - break; - case NODE_CLOSURE_EMISSION: - IF_KERNEL_NODES_FEATURE(EMISSION) - { - svm_node_closure_emission(sd, stack, node); - } - break; - case NODE_CLOSURE_BACKGROUND: - IF_KERNEL_NODES_FEATURE(EMISSION) - { - svm_node_closure_background(sd, stack, node); - } - break; - case NODE_CLOSURE_SET_WEIGHT: - svm_node_closure_set_weight(sd, node.y, node.z, node.w); - break; - case NODE_CLOSURE_WEIGHT: - svm_node_closure_weight(sd, stack, node.y); - break; - case NODE_EMISSION_WEIGHT: - IF_KERNEL_NODES_FEATURE(EMISSION) - { - svm_node_emission_weight(kg, sd, stack, node); - } - break; - case NODE_MIX_CLOSURE: - svm_node_mix_closure(sd, stack, node); - break; - case NODE_JUMP_IF_ZERO: - if (stack_load_float(stack, node.z) <= 0.0f) - offset += node.y; - break; - case NODE_JUMP_IF_ONE: - if (stack_load_float(stack, node.z) >= 1.0f) - offset += node.y; - break; - case NODE_GEOMETRY: - svm_node_geometry(kg, sd, stack, node.y, node.z); - break; - case NODE_CONVERT: - svm_node_convert(kg, sd, stack, node.y, node.z, node.w); - break; - case NODE_TEX_COORD: - offset = svm_node_tex_coord(kg, sd, path_flag, stack, node, offset); - break; - case NODE_VALUE_F: - svm_node_value_f(kg, sd, stack, node.y, node.z); - break; - case NODE_VALUE_V: - offset = svm_node_value_v(kg, sd, stack, node.y, offset); - break; - case NODE_ATTR: - svm_node_attr(kg, sd, stack, node); - break; - case NODE_VERTEX_COLOR: - svm_node_vertex_color(kg, sd, stack, node.y, node.z, node.w); - break; - case NODE_GEOMETRY_BUMP_DX: - IF_KERNEL_NODES_FEATURE(BUMP) - { - svm_node_geometry_bump_dx(kg, sd, stack, node.y, node.z); - } - break; - case NODE_GEOMETRY_BUMP_DY: - IF_KERNEL_NODES_FEATURE(BUMP) - { - svm_node_geometry_bump_dy(kg, sd, stack, node.y, node.z); - } - break; - case NODE_SET_DISPLACEMENT: - svm_node_set_displacement(kg, sd, stack, node.y); - break; - case NODE_DISPLACEMENT: - svm_node_displacement(kg, sd, stack, node); - break; - case NODE_VECTOR_DISPLACEMENT: - offset = svm_node_vector_displacement(kg, sd, stack, node, offset); - break; - case NODE_TEX_IMAGE: - offset = svm_node_tex_image(kg, sd, stack, node, offset); - break; - case NODE_TEX_IMAGE_BOX: - svm_node_tex_image_box(kg, sd, stack, node); - break; - case NODE_TEX_NOISE: - offset = svm_node_tex_noise(kg, sd, stack, node.y, node.z, node.w, offset); - break; - case NODE_SET_BUMP: - svm_node_set_bump(kg, sd, stack, node); - break; - case NODE_ATTR_BUMP_DX: - IF_KERNEL_NODES_FEATURE(BUMP) - { - svm_node_attr_bump_dx(kg, sd, stack, node); - } - break; - case NODE_ATTR_BUMP_DY: - IF_KERNEL_NODES_FEATURE(BUMP) - { - svm_node_attr_bump_dy(kg, sd, stack, node); - } - break; - case NODE_VERTEX_COLOR_BUMP_DX: - IF_KERNEL_NODES_FEATURE(BUMP) - { - svm_node_vertex_color_bump_dx(kg, sd, stack, node.y, node.z, node.w); - } - break; - case NODE_VERTEX_COLOR_BUMP_DY: - IF_KERNEL_NODES_FEATURE(BUMP) - { - svm_node_vertex_color_bump_dy(kg, sd, stack, node.y, node.z, node.w); - } - break; - case NODE_TEX_COORD_BUMP_DX: - IF_KERNEL_NODES_FEATURE(BUMP) - { - offset = svm_node_tex_coord_bump_dx(kg, sd, path_flag, stack, node, offset); - } - break; - case NODE_TEX_COORD_BUMP_DY: - IF_KERNEL_NODES_FEATURE(BUMP) - { - offset = svm_node_tex_coord_bump_dy(kg, sd, path_flag, stack, node, offset); - } - break; - case NODE_CLOSURE_SET_NORMAL: - IF_KERNEL_NODES_FEATURE(BUMP) - { - svm_node_set_normal(kg, sd, stack, node.y, node.z); - } - break; - case NODE_ENTER_BUMP_EVAL: - IF_KERNEL_NODES_FEATURE(BUMP_STATE) - { - svm_node_enter_bump_eval(kg, sd, stack, node.y); - } - break; - case NODE_LEAVE_BUMP_EVAL: - IF_KERNEL_NODES_FEATURE(BUMP_STATE) - { - svm_node_leave_bump_eval(kg, sd, stack, node.y); - } - break; - case NODE_HSV: - svm_node_hsv(kg, sd, stack, node); - break; - - case NODE_CLOSURE_HOLDOUT: - svm_node_closure_holdout(sd, stack, node); - break; - case NODE_FRESNEL: - svm_node_fresnel(sd, stack, node.y, node.z, node.w); - break; - case NODE_LAYER_WEIGHT: - svm_node_layer_weight(sd, stack, node); - break; - case NODE_CLOSURE_VOLUME: - IF_KERNEL_NODES_FEATURE(VOLUME) - { - svm_node_closure_volume(kg, sd, stack, node); - } - break; - case NODE_PRINCIPLED_VOLUME: - IF_KERNEL_NODES_FEATURE(VOLUME) - { - offset = svm_node_principled_volume(kg, sd, stack, node, path_flag, offset); - } - break; - case NODE_MATH: - svm_node_math(kg, sd, stack, node.y, node.z, node.w); - break; - case NODE_VECTOR_MATH: - offset = svm_node_vector_math(kg, sd, stack, node.y, node.z, node.w, offset); - break; - case NODE_RGB_RAMP: - offset = svm_node_rgb_ramp(kg, sd, stack, node, offset); - break; - case NODE_GAMMA: - svm_node_gamma(sd, stack, node.y, node.z, node.w); - break; - case NODE_BRIGHTCONTRAST: - svm_node_brightness(sd, stack, node.y, node.z, node.w); - break; - case NODE_LIGHT_PATH: - svm_node_light_path(kg, state, sd, stack, node.y, node.z, path_flag); - break; - case NODE_OBJECT_INFO: - svm_node_object_info(kg, sd, stack, node.y, node.z); - break; - case NODE_PARTICLE_INFO: - svm_node_particle_info(kg, sd, stack, node.y, node.z); - break; + SVM_CASE(NODE_CLOSURE_BSDF) + offset = svm_node_closure_bsdf( + kg, sd, stack, node, path_flag, offset); + break; + SVM_CASE(NODE_CLOSURE_EMISSION) + IF_KERNEL_NODES_FEATURE(EMISSION) + { + svm_node_closure_emission(sd, stack, node); + } + break; + SVM_CASE(NODE_CLOSURE_BACKGROUND) + IF_KERNEL_NODES_FEATURE(EMISSION) + { + svm_node_closure_background(sd, stack, node); + } + break; + SVM_CASE(NODE_CLOSURE_SET_WEIGHT) + svm_node_closure_set_weight(sd, node.y, node.z, node.w); + break; + SVM_CASE(NODE_CLOSURE_WEIGHT) + svm_node_closure_weight(sd, stack, node.y); + break; + SVM_CASE(NODE_EMISSION_WEIGHT) + IF_KERNEL_NODES_FEATURE(EMISSION) + { + svm_node_emission_weight(kg, sd, stack, node); + } + break; + SVM_CASE(NODE_MIX_CLOSURE) + svm_node_mix_closure(sd, stack, node); + break; + SVM_CASE(NODE_JUMP_IF_ZERO) + if (stack_load_float(stack, node.z) <= 0.0f) + offset += node.y; + break; + SVM_CASE(NODE_JUMP_IF_ONE) + if (stack_load_float(stack, node.z) >= 1.0f) + offset += node.y; + break; + SVM_CASE(NODE_GEOMETRY) + svm_node_geometry(kg, sd, stack, node.y, node.z); + break; + SVM_CASE(NODE_CONVERT) + svm_node_convert(kg, sd, stack, node.y, node.z, node.w); + break; + SVM_CASE(NODE_TEX_COORD) + offset = svm_node_tex_coord(kg, sd, path_flag, stack, node, offset); + break; + SVM_CASE(NODE_VALUE_F) + svm_node_value_f(kg, sd, stack, node.y, node.z); + break; + SVM_CASE(NODE_VALUE_V) + offset = svm_node_value_v(kg, sd, stack, node.y, offset); + break; + SVM_CASE(NODE_ATTR) + svm_node_attr(kg, sd, stack, node); + break; + SVM_CASE(NODE_VERTEX_COLOR) + svm_node_vertex_color(kg, sd, stack, node.y, node.z, node.w); + break; + SVM_CASE(NODE_GEOMETRY_BUMP_DX) + IF_KERNEL_NODES_FEATURE(BUMP) + { + svm_node_geometry_bump_dx(kg, sd, stack, node.y, node.z); + } + break; + SVM_CASE(NODE_GEOMETRY_BUMP_DY) + IF_KERNEL_NODES_FEATURE(BUMP) + { + svm_node_geometry_bump_dy(kg, sd, stack, node.y, node.z); + } + break; + SVM_CASE(NODE_SET_DISPLACEMENT) + svm_node_set_displacement(kg, sd, stack, node.y); + break; + SVM_CASE(NODE_DISPLACEMENT) + svm_node_displacement(kg, sd, stack, node); + break; + SVM_CASE(NODE_VECTOR_DISPLACEMENT) + offset = svm_node_vector_displacement(kg, sd, stack, node, offset); + break; + SVM_CASE(NODE_TEX_IMAGE) + offset = svm_node_tex_image(kg, sd, stack, node, offset); + break; + SVM_CASE(NODE_TEX_IMAGE_BOX) + svm_node_tex_image_box(kg, sd, stack, node); + break; + SVM_CASE(NODE_TEX_NOISE) + offset = svm_node_tex_noise(kg, sd, stack, node.y, node.z, node.w, offset); + break; + SVM_CASE(NODE_SET_BUMP) + svm_node_set_bump(kg, sd, stack, node); + break; + SVM_CASE(NODE_ATTR_BUMP_DX) + IF_KERNEL_NODES_FEATURE(BUMP) + { + svm_node_attr_bump_dx(kg, sd, stack, node); + } + break; + SVM_CASE(NODE_ATTR_BUMP_DY) + IF_KERNEL_NODES_FEATURE(BUMP) + { + svm_node_attr_bump_dy(kg, sd, stack, node); + } + break; + SVM_CASE(NODE_VERTEX_COLOR_BUMP_DX) + IF_KERNEL_NODES_FEATURE(BUMP) + { + svm_node_vertex_color_bump_dx(kg, sd, stack, node.y, node.z, node.w); + } + break; + SVM_CASE(NODE_VERTEX_COLOR_BUMP_DY) + IF_KERNEL_NODES_FEATURE(BUMP) + { + svm_node_vertex_color_bump_dy(kg, sd, stack, node.y, node.z, node.w); + } + break; + SVM_CASE(NODE_TEX_COORD_BUMP_DX) + IF_KERNEL_NODES_FEATURE(BUMP) + { + offset = svm_node_tex_coord_bump_dx(kg, sd, path_flag, stack, node, offset); + } + break; + SVM_CASE(NODE_TEX_COORD_BUMP_DY) + IF_KERNEL_NODES_FEATURE(BUMP) + { + offset = svm_node_tex_coord_bump_dy(kg, sd, path_flag, stack, node, offset); + } + break; + SVM_CASE(NODE_CLOSURE_SET_NORMAL) + IF_KERNEL_NODES_FEATURE(BUMP) + { + svm_node_set_normal(kg, sd, stack, node.y, node.z); + } + break; + SVM_CASE(NODE_ENTER_BUMP_EVAL) + IF_KERNEL_NODES_FEATURE(BUMP_STATE) + { + svm_node_enter_bump_eval(kg, sd, stack, node.y); + } + break; + SVM_CASE(NODE_LEAVE_BUMP_EVAL) + IF_KERNEL_NODES_FEATURE(BUMP_STATE) + { + svm_node_leave_bump_eval(kg, sd, stack, node.y); + } + break; + SVM_CASE(NODE_HSV) + svm_node_hsv(kg, sd, stack, node); + break; + SVM_CASE(NODE_CLOSURE_HOLDOUT) + svm_node_closure_holdout(sd, stack, node); + break; + SVM_CASE(NODE_FRESNEL) + svm_node_fresnel(sd, stack, node.y, node.z, node.w); + break; + SVM_CASE(NODE_LAYER_WEIGHT) + svm_node_layer_weight(sd, stack, node); + break; + SVM_CASE(NODE_CLOSURE_VOLUME) + IF_KERNEL_NODES_FEATURE(VOLUME) + { + svm_node_closure_volume(kg, sd, stack, node); + } + break; + SVM_CASE(NODE_PRINCIPLED_VOLUME) + IF_KERNEL_NODES_FEATURE(VOLUME) + { + offset = svm_node_principled_volume(kg, sd, stack, node, path_flag, offset); + } + break; + SVM_CASE(NODE_MATH) + svm_node_math(kg, sd, stack, node.y, node.z, node.w); + break; + SVM_CASE(NODE_VECTOR_MATH) + offset = svm_node_vector_math(kg, sd, stack, node.y, node.z, node.w, offset); + break; + SVM_CASE(NODE_RGB_RAMP) + offset = svm_node_rgb_ramp(kg, sd, stack, node, offset); + break; + SVM_CASE(NODE_GAMMA) + svm_node_gamma(sd, stack, node.y, node.z, node.w); + break; + SVM_CASE(NODE_BRIGHTCONTRAST) + svm_node_brightness(sd, stack, node.y, node.z, node.w); + break; + SVM_CASE(NODE_LIGHT_PATH) + svm_node_light_path(kg, state, sd, stack, node.y, node.z, path_flag); + break; + SVM_CASE(NODE_OBJECT_INFO) + svm_node_object_info(kg, sd, stack, node.y, node.z); + break; + SVM_CASE(NODE_PARTICLE_INFO) + svm_node_particle_info(kg, sd, stack, node.y, node.z); + break; #if defined(__HAIR__) - case NODE_HAIR_INFO: - svm_node_hair_info(kg, sd, stack, node.y, node.z); - break; + SVM_CASE(NODE_HAIR_INFO) + svm_node_hair_info(kg, sd, stack, node.y, node.z); + break; #endif #if defined(__POINTCLOUD__) - case NODE_POINT_INFO: - svm_node_point_info(kg, sd, stack, node.y, node.z); - break; + SVM_CASE(NODE_POINT_INFO) + svm_node_point_info(kg, sd, stack, node.y, node.z); + break; #endif - case NODE_TEXTURE_MAPPING: - offset = svm_node_texture_mapping(kg, sd, stack, node.y, node.z, offset); - break; - case NODE_MAPPING: - svm_node_mapping(kg, sd, stack, node.y, node.z, node.w); - break; - case NODE_MIN_MAX: - offset = svm_node_min_max(kg, sd, stack, node.y, node.z, offset); - break; - case NODE_CAMERA: - svm_node_camera(kg, sd, stack, node.y, node.z, node.w); - break; - case NODE_TEX_ENVIRONMENT: - svm_node_tex_environment(kg, sd, stack, node); - break; - case NODE_TEX_SKY: - offset = svm_node_tex_sky(kg, sd, stack, node, offset); - break; - case NODE_TEX_GRADIENT: - svm_node_tex_gradient(sd, stack, node); - break; - case NODE_TEX_VORONOI: - offset = svm_node_tex_voronoi( - kg, sd, stack, node.y, node.z, node.w, offset); - break; - case NODE_TEX_MUSGRAVE: - offset = svm_node_tex_musgrave(kg, sd, stack, node.y, node.z, node.w, offset); - break; - case NODE_TEX_WAVE: - offset = svm_node_tex_wave(kg, sd, stack, node, offset); - break; - case NODE_TEX_MAGIC: - offset = svm_node_tex_magic(kg, sd, stack, node, offset); - break; - case NODE_TEX_CHECKER: - svm_node_tex_checker(kg, sd, stack, node); - break; - case NODE_TEX_BRICK: - offset = svm_node_tex_brick(kg, sd, stack, node, offset); - break; - case NODE_TEX_WHITE_NOISE: - svm_node_tex_white_noise(kg, sd, stack, node.y, node.z, node.w); - break; - case NODE_NORMAL: - offset = svm_node_normal(kg, sd, stack, node.y, node.z, node.w, offset); - break; - case NODE_LIGHT_FALLOFF: - svm_node_light_falloff(sd, stack, node); - break; - case NODE_IES: - svm_node_ies(kg, sd, stack, node); - break; - case NODE_RGB_CURVES: - case NODE_VECTOR_CURVES: - offset = svm_node_curves(kg, sd, stack, node, offset); - break; - case NODE_FLOAT_CURVE: - offset = svm_node_curve(kg, sd, stack, node, offset); - break; - case NODE_TANGENT: - svm_node_tangent(kg, sd, stack, node); - break; - case NODE_NORMAL_MAP: - svm_node_normal_map(kg, sd, stack, node); - break; - case NODE_INVERT: - svm_node_invert(sd, stack, node.y, node.z, node.w); - break; - case NODE_MIX: - offset = svm_node_mix(kg, sd, stack, node.y, node.z, node.w, offset); - break; - case NODE_SEPARATE_COLOR: - svm_node_separate_color(kg, sd, stack, node.y, node.z, node.w); - break; - case NODE_COMBINE_COLOR: - svm_node_combine_color(kg, sd, stack, node.y, node.z, node.w); - break; - case NODE_SEPARATE_VECTOR: - svm_node_separate_vector(sd, stack, node.y, node.z, node.w); - break; - case NODE_COMBINE_VECTOR: - svm_node_combine_vector(sd, stack, node.y, node.z, node.w); - break; - case NODE_SEPARATE_HSV: - offset = svm_node_separate_hsv(kg, sd, stack, node.y, node.z, node.w, offset); - break; - case NODE_COMBINE_HSV: - offset = svm_node_combine_hsv(kg, sd, stack, node.y, node.z, node.w, offset); - break; - case NODE_VECTOR_ROTATE: - svm_node_vector_rotate(sd, stack, node.y, node.z, node.w); - break; - case NODE_VECTOR_TRANSFORM: - svm_node_vector_transform(kg, sd, stack, node); - break; - case NODE_WIREFRAME: - svm_node_wireframe(kg, sd, stack, node); - break; - case NODE_WAVELENGTH: - svm_node_wavelength(kg, sd, stack, node.y, node.z); - break; - case NODE_BLACKBODY: - svm_node_blackbody(kg, sd, stack, node.y, node.z); - break; - case NODE_MAP_RANGE: - offset = svm_node_map_range(kg, sd, stack, node.y, node.z, node.w, offset); - break; - case NODE_VECTOR_MAP_RANGE: - offset = svm_node_vector_map_range(kg, sd, stack, node.y, node.z, node.w, offset); - break; - case NODE_CLAMP: - offset = svm_node_clamp(kg, sd, stack, node.y, node.z, node.w, offset); - break; + SVM_CASE(NODE_TEXTURE_MAPPING) + offset = svm_node_texture_mapping(kg, sd, stack, node.y, node.z, offset); + break; + SVM_CASE(NODE_MAPPING) + svm_node_mapping(kg, sd, stack, node.y, node.z, node.w); + break; + SVM_CASE(NODE_MIN_MAX) + offset = svm_node_min_max(kg, sd, stack, node.y, node.z, offset); + break; + SVM_CASE(NODE_CAMERA) + svm_node_camera(kg, sd, stack, node.y, node.z, node.w); + break; + SVM_CASE(NODE_TEX_ENVIRONMENT) + svm_node_tex_environment(kg, sd, stack, node); + break; + SVM_CASE(NODE_TEX_SKY) + offset = svm_node_tex_sky(kg, sd, stack, node, offset); + break; + SVM_CASE(NODE_TEX_GRADIENT) + svm_node_tex_gradient(sd, stack, node); + break; + SVM_CASE(NODE_TEX_VORONOI) + offset = svm_node_tex_voronoi( + kg, sd, stack, node.y, node.z, node.w, offset); + break; + SVM_CASE(NODE_TEX_MUSGRAVE) + offset = svm_node_tex_musgrave(kg, sd, stack, node.y, node.z, node.w, offset); + break; + SVM_CASE(NODE_TEX_WAVE) + offset = svm_node_tex_wave(kg, sd, stack, node, offset); + break; + SVM_CASE(NODE_TEX_MAGIC) + offset = svm_node_tex_magic(kg, sd, stack, node, offset); + break; + SVM_CASE(NODE_TEX_CHECKER) + svm_node_tex_checker(kg, sd, stack, node); + break; + SVM_CASE(NODE_TEX_BRICK) + offset = svm_node_tex_brick(kg, sd, stack, node, offset); + break; + SVM_CASE(NODE_TEX_WHITE_NOISE) + svm_node_tex_white_noise(kg, sd, stack, node.y, node.z, node.w); + break; + SVM_CASE(NODE_NORMAL) + offset = svm_node_normal(kg, sd, stack, node.y, node.z, node.w, offset); + break; + SVM_CASE(NODE_LIGHT_FALLOFF) + svm_node_light_falloff(sd, stack, node); + break; + SVM_CASE(NODE_IES) + svm_node_ies(kg, sd, stack, node); + break; + SVM_CASE(NODE_CURVES) + offset = svm_node_curves(kg, sd, stack, node, offset); + break; + SVM_CASE(NODE_FLOAT_CURVE) + offset = svm_node_curve(kg, sd, stack, node, offset); + break; + SVM_CASE(NODE_TANGENT) + svm_node_tangent(kg, sd, stack, node); + break; + SVM_CASE(NODE_NORMAL_MAP) + svm_node_normal_map(kg, sd, stack, node); + break; + SVM_CASE(NODE_INVERT) + svm_node_invert(sd, stack, node.y, node.z, node.w); + break; + SVM_CASE(NODE_MIX) + offset = svm_node_mix(kg, sd, stack, node.y, node.z, node.w, offset); + break; + SVM_CASE(NODE_SEPARATE_COLOR) + svm_node_separate_color(kg, sd, stack, node.y, node.z, node.w); + break; + SVM_CASE(NODE_COMBINE_COLOR) + svm_node_combine_color(kg, sd, stack, node.y, node.z, node.w); + break; + SVM_CASE(NODE_SEPARATE_VECTOR) + svm_node_separate_vector(sd, stack, node.y, node.z, node.w); + break; + SVM_CASE(NODE_COMBINE_VECTOR) + svm_node_combine_vector(sd, stack, node.y, node.z, node.w); + break; + SVM_CASE(NODE_SEPARATE_HSV) + offset = svm_node_separate_hsv(kg, sd, stack, node.y, node.z, node.w, offset); + break; + SVM_CASE(NODE_COMBINE_HSV) + offset = svm_node_combine_hsv(kg, sd, stack, node.y, node.z, node.w, offset); + break; + SVM_CASE(NODE_VECTOR_ROTATE) + svm_node_vector_rotate(sd, stack, node.y, node.z, node.w); + break; + SVM_CASE(NODE_VECTOR_TRANSFORM) + svm_node_vector_transform(kg, sd, stack, node); + break; + SVM_CASE(NODE_WIREFRAME) + svm_node_wireframe(kg, sd, stack, node); + break; + SVM_CASE(NODE_WAVELENGTH) + svm_node_wavelength(kg, sd, stack, node.y, node.z); + break; + SVM_CASE(NODE_BLACKBODY) + svm_node_blackbody(kg, sd, stack, node.y, node.z); + break; + SVM_CASE(NODE_MAP_RANGE) + offset = svm_node_map_range(kg, sd, stack, node.y, node.z, node.w, offset); + break; + SVM_CASE(NODE_VECTOR_MAP_RANGE) + offset = svm_node_vector_map_range(kg, sd, stack, node.y, node.z, node.w, offset); + break; + SVM_CASE(NODE_CLAMP) + offset = svm_node_clamp(kg, sd, stack, node.y, node.z, node.w, offset); + break; #ifdef __SHADER_RAYTRACE__ - case NODE_BEVEL: - svm_node_bevel(kg, state, sd, stack, node); - break; - case NODE_AMBIENT_OCCLUSION: - svm_node_ao(kg, state, sd, stack, node); - break; + SVM_CASE(NODE_BEVEL) + svm_node_bevel(kg, state, sd, stack, node); + break; + SVM_CASE(NODE_AMBIENT_OCCLUSION) + svm_node_ao(kg, state, sd, stack, node); + break; #endif - case NODE_TEX_VOXEL: - IF_KERNEL_NODES_FEATURE(VOLUME) - { - offset = svm_node_tex_voxel(kg, sd, stack, node, offset); - } - break; - case NODE_AOV_START: - if (!svm_node_aov_check(path_flag, render_buffer)) { - return; - } - break; - case NODE_AOV_COLOR: - svm_node_aov_color(kg, state, sd, stack, node, render_buffer); - break; - case NODE_AOV_VALUE: - svm_node_aov_value(kg, state, sd, stack, node, render_buffer); - break; + SVM_CASE(NODE_TEX_VOXEL) + IF_KERNEL_NODES_FEATURE(VOLUME) + { + offset = svm_node_tex_voxel(kg, sd, stack, node, offset); + } + break; + SVM_CASE(NODE_AOV_START) + if (!svm_node_aov_check(path_flag, render_buffer)) { + return; + } + break; + SVM_CASE(NODE_AOV_COLOR) + svm_node_aov_color(kg, state, sd, stack, node, render_buffer); + break; + SVM_CASE(NODE_AOV_VALUE) + svm_node_aov_value(kg, state, sd, stack, node, render_buffer); + break; default: kernel_assert(!"Unknown node type was passed to the SVM machine"); return; diff --git a/intern/cycles/kernel/svm/types.h b/intern/cycles/kernel/svm/types.h index 82109ec4c4f..12d0ec141e6 100644 --- a/intern/cycles/kernel/svm/types.h +++ b/intern/cycles/kernel/svm/types.h @@ -17,104 +17,9 @@ CCL_NAMESPACE_BEGIN /* Nodes */ typedef enum ShaderNodeType { - NODE_END = 0, - NODE_SHADER_JUMP, - NODE_CLOSURE_BSDF, - NODE_CLOSURE_EMISSION, - NODE_CLOSURE_BACKGROUND, - NODE_CLOSURE_SET_WEIGHT, - NODE_CLOSURE_WEIGHT, - NODE_EMISSION_WEIGHT, - NODE_MIX_CLOSURE, - NODE_JUMP_IF_ZERO, - NODE_JUMP_IF_ONE, - NODE_GEOMETRY, - NODE_CONVERT, - NODE_TEX_COORD, - NODE_VALUE_F, - NODE_VALUE_V, - NODE_ATTR, - NODE_VERTEX_COLOR, - NODE_GEOMETRY_BUMP_DX, - NODE_GEOMETRY_BUMP_DY, - NODE_SET_DISPLACEMENT, - NODE_DISPLACEMENT, - NODE_VECTOR_DISPLACEMENT, - NODE_TEX_IMAGE, - NODE_TEX_IMAGE_BOX, - NODE_TEX_NOISE, - NODE_SET_BUMP, - NODE_ATTR_BUMP_DX, - NODE_ATTR_BUMP_DY, - NODE_VERTEX_COLOR_BUMP_DX, - NODE_VERTEX_COLOR_BUMP_DY, - NODE_TEX_COORD_BUMP_DX, - NODE_TEX_COORD_BUMP_DY, - NODE_CLOSURE_SET_NORMAL, - NODE_ENTER_BUMP_EVAL, - NODE_LEAVE_BUMP_EVAL, - NODE_HSV, - NODE_CLOSURE_HOLDOUT, - NODE_FRESNEL, - NODE_LAYER_WEIGHT, - NODE_CLOSURE_VOLUME, - NODE_PRINCIPLED_VOLUME, - NODE_MATH, - NODE_VECTOR_MATH, - NODE_RGB_RAMP, - NODE_GAMMA, - NODE_BRIGHTCONTRAST, - NODE_LIGHT_PATH, - NODE_OBJECT_INFO, - NODE_PARTICLE_INFO, - NODE_HAIR_INFO, - NODE_POINT_INFO, - NODE_TEXTURE_MAPPING, - NODE_MAPPING, - NODE_MIN_MAX, - NODE_CAMERA, - NODE_TEX_ENVIRONMENT, - NODE_TEX_SKY, - NODE_TEX_GRADIENT, - NODE_TEX_VORONOI, - NODE_TEX_MUSGRAVE, - NODE_TEX_WAVE, - NODE_TEX_MAGIC, - NODE_TEX_CHECKER, - NODE_TEX_BRICK, - NODE_TEX_WHITE_NOISE, - NODE_NORMAL, - NODE_LIGHT_FALLOFF, - NODE_IES, - NODE_RGB_CURVES, - NODE_VECTOR_CURVES, - NODE_TANGENT, - NODE_NORMAL_MAP, - NODE_INVERT, - NODE_MIX, - NODE_SEPARATE_COLOR, - NODE_COMBINE_COLOR, - NODE_SEPARATE_VECTOR, - NODE_COMBINE_VECTOR, - NODE_SEPARATE_HSV, - NODE_COMBINE_HSV, - NODE_VECTOR_ROTATE, - NODE_VECTOR_TRANSFORM, - NODE_WIREFRAME, - NODE_WAVELENGTH, - NODE_BLACKBODY, - NODE_MAP_RANGE, - NODE_VECTOR_MAP_RANGE, - NODE_CLAMP, - NODE_BEVEL, - NODE_AMBIENT_OCCLUSION, - NODE_TEX_VOXEL, - NODE_AOV_START, - NODE_AOV_COLOR, - NODE_AOV_VALUE, - NODE_FLOAT_CURVE, - /* NOTE: for best OpenCL performance, item definition in the enum must - * match the switch case order in `svm.h`. */ +#define SHADER_NODE_TYPE(name) name, +#include "node_types_template.h" + NODE_NUM } ShaderNodeType; typedef enum NodeAttributeOutputType { diff --git a/intern/cycles/scene/shader_nodes.cpp b/intern/cycles/scene/shader_nodes.cpp index f93a1a5231a..bedb0fe2902 100644 --- a/intern/cycles/scene/shader_nodes.cpp +++ b/intern/cycles/scene/shader_nodes.cpp @@ -6671,7 +6671,7 @@ void CurvesNode::compile(SVMCompiler &compiler, ShaderInput *fac_in = input("Fac"); - compiler.add_node(type, + compiler.add_node(ShaderNodeType(type), compiler.encode_uchar4(compiler.stack_assign(fac_in), compiler.stack_assign(value_in), compiler.stack_assign(value_out), @@ -6736,7 +6736,7 @@ void RGBCurvesNode::constant_fold(const ConstantFolder &folder) void RGBCurvesNode::compile(SVMCompiler &compiler) { - CurvesNode::compile(compiler, NODE_RGB_CURVES, input("Color"), output("Color")); + CurvesNode::compile(compiler, NODE_CURVES, input("Color"), output("Color")); } void RGBCurvesNode::compile(OSLCompiler &compiler) @@ -6774,7 +6774,7 @@ void VectorCurvesNode::constant_fold(const ConstantFolder &folder) void VectorCurvesNode::compile(SVMCompiler &compiler) { - CurvesNode::compile(compiler, NODE_VECTOR_CURVES, input("Vector"), output("Vector")); + CurvesNode::compile(compiler, NODE_CURVES, input("Vector"), output("Vector")); } void VectorCurvesNode::compile(OSLCompiler &compiler) diff --git a/intern/cycles/scene/svm.cpp b/intern/cycles/scene/svm.cpp index 4bc5a1b9cc2..ede3f87e7e3 100644 --- a/intern/cycles/scene/svm.cpp +++ b/intern/cycles/scene/svm.cpp @@ -44,8 +44,6 @@ void SVMShaderManager::device_update_shader(Scene *scene, } assert(shader->graph); - svm_nodes->push_back_slow(make_int4(NODE_SHADER_JUMP, 0, 0, 0)); - SVMCompiler::Summary summary; SVMCompiler compiler(scene); compiler.background = (shader == scene->background->get_shader(scene)); @@ -170,6 +168,9 @@ SVMCompiler::SVMCompiler(Scene *scene) : scene(scene) background = false; mix_weight_offset = SVM_STACK_INVALID; compile_failed = false; + + /* This struct has one entry for every node, in order of ShaderNodeType definition. */ + svm_node_types_used = (std::atomic_int *)&scene->dscene.data.svm_usage; } int SVMCompiler::stack_size(SocketType::Type type) @@ -378,11 +379,13 @@ void SVMCompiler::add_node(int a, int b, int c, int d) void SVMCompiler::add_node(ShaderNodeType type, int a, int b, int c) { + svm_node_types_used[type] = true; current_svm_nodes.push_back_slow(make_int4(type, a, b, c)); } void SVMCompiler::add_node(ShaderNodeType type, const float3 &f) { + svm_node_types_used[type] = true; current_svm_nodes.push_back_slow( make_int4(type, __float_as_int(f.x), __float_as_int(f.y), __float_as_int(f.z))); } @@ -663,6 +666,7 @@ void SVMCompiler::generate_multi_closure(ShaderNode *root_node, /* Add instruction to skip closure and its dependencies if mix * weight is zero. */ + svm_node_types_used[NODE_JUMP_IF_ONE] = true; current_svm_nodes.push_back_slow(make_int4(NODE_JUMP_IF_ONE, 0, stack_assign(facin), 0)); int node_jump_skip_index = current_svm_nodes.size() - 1; @@ -678,6 +682,7 @@ void SVMCompiler::generate_multi_closure(ShaderNode *root_node, /* Add instruction to skip closure and its dependencies if mix * weight is zero. */ + svm_node_types_used[NODE_JUMP_IF_ZERO] = true; current_svm_nodes.push_back_slow(make_int4(NODE_JUMP_IF_ZERO, 0, stack_assign(facin), 0)); int node_jump_skip_index = current_svm_nodes.size() - 1; @@ -844,6 +849,9 @@ void SVMCompiler::compile_type(Shader *shader, ShaderGraph *graph, ShaderType ty void SVMCompiler::compile(Shader *shader, array &svm_nodes, int index, Summary *summary) { + svm_node_types_used[NODE_SHADER_JUMP] = true; + svm_nodes.push_back_slow(make_int4(NODE_SHADER_JUMP, 0, 0, 0)); + /* copy graph for shader with bump mapping */ ShaderNode *output = shader->graph->output(); int start_num_svm_nodes = svm_nodes.size(); diff --git a/intern/cycles/scene/svm.h b/intern/cycles/scene/svm.h index 19746616207..f72375e7f87 100644 --- a/intern/cycles/scene/svm.h +++ b/intern/cycles/scene/svm.h @@ -211,6 +211,7 @@ class SVMCompiler { /* compile */ void compile_type(Shader *shader, ShaderGraph *graph, ShaderType type); + std::atomic_int *svm_node_types_used; array current_svm_nodes; ShaderType current_type; Shader *current_shader; -- cgit v1.2.3 From da4ef05e4dfb700a61910e6d8e02183d7c272963 Mon Sep 17 00:00:00 2001 From: Michael Jones Date: Tue, 12 Jul 2022 15:32:46 +0200 Subject: Cycles: Apple Silicon optimization to specialize intersection kernels The Metal backend now compiles and caches a second set of kernels which are optimized for scene contents, enabled for Apple Silicon. The implementation supports doing this both for intersection and shading kernels. However this is currently only enabled for intersection kernels that are quick to compile, and already give a good speedup. Enabling this for shading kernels would be faster still, however this also causes a long wait times and would need a good user interface to control this. M1 Max samples per minute (macOS 13.0): PSO_GENERIC PSO_SPECIALIZED_INTERSECT PSO_SPECIALIZED_SHADE barbershop_interior 83.4 89.5 93.7 bmw27 1486.1 1671.0 1825.8 classroom 175.2 196.8 206.3 fishy_cat 674.2 704.3 719.3 junkshop 205.4 212.0 257.7 koro 310.1 336.1 342.8 monster 376.7 418.6 424.1 pabellon 273.5 325.4 339.8 sponza 830.6 929.6 1142.4 victor 86.7 96.4 96.3 wdas_cloud 111.8 112.7 183.1 Code contributed by Jason Fielder, Morteza Mostajabodaveh and Michael Jones Differential Revision: https://developer.blender.org/D14645 --- intern/cycles/device/device.h | 6 + intern/cycles/device/metal/device_impl.h | 11 +- intern/cycles/device/metal/device_impl.mm | 193 +++++++++++++----- intern/cycles/device/metal/kernel.h | 30 ++- intern/cycles/device/metal/kernel.mm | 221 ++++++++++++++++----- intern/cycles/kernel/CMakeLists.txt | 1 + .../kernel/device/metal/function_constants.h | 14 ++ intern/cycles/kernel/device/metal/kernel.metal | 1 + intern/cycles/kernel/svm/svm.h | 9 +- intern/cycles/kernel/types.h | 8 +- intern/cycles/scene/scene.cpp | 2 + intern/cycles/util/string.cpp | 18 ++ intern/cycles/util/string.h | 2 + 13 files changed, 394 insertions(+), 122 deletions(-) create mode 100644 intern/cycles/kernel/device/metal/function_constants.h diff --git a/intern/cycles/device/device.h b/intern/cycles/device/device.h index 340be85e853..e7916ec3a52 100644 --- a/intern/cycles/device/device.h +++ b/intern/cycles/device/device.h @@ -29,6 +29,7 @@ class DeviceQueue; class Progress; class CPUKernels; class CPUKernelThreadGlobals; +class Scene; /* Device Types */ @@ -186,6 +187,11 @@ class Device { return 0; } + /* Called after kernel texture setup, and prior to integrator state setup. */ + virtual void optimize_for_scene(Scene *scene) + { + } + virtual bool is_resident(device_ptr /*key*/, Device *sub_device) { /* Memory is always resident if this is not a multi device, regardless of whether the pointer diff --git a/intern/cycles/device/metal/device_impl.h b/intern/cycles/device/metal/device_impl.h index 4aea8d697a5..99e60d3a788 100644 --- a/intern/cycles/device/metal/device_impl.h +++ b/intern/cycles/device/metal/device_impl.h @@ -75,7 +75,8 @@ class MetalDevice : public Device { std::vector> texture_slot_map; bool use_metalrt = false; - bool use_function_specialisation = false; + MetalPipelineType kernel_specialization_level = PSO_GENERIC; + std::atomic_bool async_compile_and_load = false; virtual BVHLayoutMask get_bvh_layout_mask() const override; @@ -91,9 +92,7 @@ class MetalDevice : public Device { bool use_adaptive_compilation(); - string get_source(const uint kernel_features); - - string compile_kernel(const uint kernel_features, const char *name); + void make_source(MetalPipelineType pso_type, const uint kernel_features); virtual bool load_kernels(const uint kernel_features) override; @@ -111,7 +110,9 @@ class MetalDevice : public Device { virtual void build_bvh(BVH *bvh, Progress &progress, bool refit) override; - id compile(string const &source); + virtual void optimize_for_scene(Scene *scene) override; + + bool compile_and_load(MetalPipelineType pso_type); /* ------------------------------------------------------------------ */ /* low-level memory management */ diff --git a/intern/cycles/device/metal/device_impl.mm b/intern/cycles/device/metal/device_impl.mm index ba9317e3204..d8bb3b867cd 100644 --- a/intern/cycles/device/metal/device_impl.mm +++ b/intern/cycles/device/metal/device_impl.mm @@ -6,6 +6,8 @@ # include "device/metal/device_impl.h" # include "device/metal/device.h" +# include "scene/scene.h" + # include "util/debug.h" # include "util/md5.h" # include "util/path.h" @@ -78,6 +80,10 @@ MetalDevice::MetalDevice(const DeviceInfo &info, Stats &stats, Profiler &profile case METAL_GPU_APPLE: { max_threads_per_threadgroup = 512; use_metalrt = info.use_metalrt; + + /* Specialize the intersection kernels on Apple GPUs by default as these can be built very + * quickly. */ + kernel_specialization_level = PSO_SPECIALIZED_INTERSECT; break; } } @@ -90,6 +96,13 @@ MetalDevice::MetalDevice(const DeviceInfo &info, Stats &stats, Profiler &profile capture_enabled = true; } + if (auto envstr = getenv("CYCLES_METAL_SPECIALIZATION_LEVEL")) { + kernel_specialization_level = (MetalPipelineType)atoi(envstr); + } + metal_printf("kernel_specialization_level = %s\n", + kernel_type_as_string( + (MetalPipelineType)min((int)kernel_specialization_level, (int)PSO_NUM - 1))); + MTLArgumentDescriptor *arg_desc_params = [[MTLArgumentDescriptor alloc] init]; arg_desc_params.dataType = MTLDataTypePointer; arg_desc_params.access = MTLArgumentAccessReadOnly; @@ -209,12 +222,11 @@ bool MetalDevice::use_adaptive_compilation() return DebugFlags().metal.adaptive_compile; } -string MetalDevice::get_source(const uint kernel_features) +void MetalDevice::make_source(MetalPipelineType pso_type, const uint kernel_features) { - string build_options; - + string global_defines; if (use_adaptive_compilation()) { - build_options += " -D__KERNEL_FEATURES__=" + to_string(kernel_features); + global_defines += "#define __KERNEL_FEATURES__ " + to_string(kernel_features) + "\n"; } if (MetalInfo::optimal_sort_partition_elements(mtlDevice) > 0) { @@ -222,52 +234,78 @@ string MetalDevice::get_source(const uint kernel_features) } if (use_metalrt) { - build_options += "-D__METALRT__ "; + global_defines += "#define __METALRT__\n"; if (motion_blur) { - build_options += "-D__METALRT_MOTION__ "; + global_defines += "#define __METALRT_MOTION__\n"; } } # ifdef WITH_CYCLES_DEBUG - build_options += "-D__KERNEL_DEBUG__ "; + global_defines += "#define __KERNEL_DEBUG__\n"; # endif switch (device_vendor) { default: break; case METAL_GPU_INTEL: - build_options += "-D__KERNEL_METAL_INTEL__ "; + global_defines += "#define __KERNEL_METAL_INTEL__\n"; break; case METAL_GPU_AMD: - build_options += "-D__KERNEL_METAL_AMD__ "; + global_defines += "#define __KERNEL_METAL_AMD__\n"; break; case METAL_GPU_APPLE: - build_options += "-D__KERNEL_METAL_APPLE__ "; + global_defines += "#define __KERNEL_METAL_APPLE__\n"; break; } - /* reformat -D defines list into compilable form */ - vector components; - string_replace(build_options, "-D", ""); - string_split(components, build_options, " "); + string &source = this->source[pso_type]; + source = "\n#include \"kernel/device/metal/kernel.metal\"\n"; + source = path_source_replace_includes(source, path_get("source")); - string globalDefines; - for (const string &component : components) { - vector assignments; - string_split(assignments, component, "="); - if (assignments.size() == 2) - globalDefines += string_printf( - "#define %s %s\n", assignments[0].c_str(), assignments[1].c_str()); - else - globalDefines += string_printf("#define %s\n", assignments[0].c_str()); + /* Perform any required specialization on the source. + * With Metal function constants we can generate a single variant of the kernel source which can + * be repeatedly respecialized. + */ + string baked_constants; + + /* Replace specific KernelData "dot" dereferences with a Metal function_constant identifier of + * the same character length. Build a string of all active constant values which is then hashed + * in order to identify the PSO. + */ + if (pso_type != PSO_GENERIC) { + const double starttime = time_dt(); + +# define KERNEL_STRUCT_BEGIN(name, parent) \ + string_replace_same_length(source, "kernel_data." #parent ".", "kernel_data_" #parent "_"); + + /* Add constants to md5 so that 'get_best_pipeline' is able to return a suitable match. */ +# define KERNEL_STRUCT_MEMBER(parent, _type, name) \ + baked_constants += string(#parent "." #name "=") + \ + to_string(_type(launch_params.data.parent.name)) + "\n"; + +# include "kernel/data_template.h" + + /* Opt in to all of available specializations. This can be made more granular for the + * PSO_SPECIALIZED_INTERSECT case in order to minimize the number of specialization requests, + * but the overhead should be negligible as these are very quick to (re)build and aren't + * serialized to disk via MTLBinaryArchives. + */ + global_defines += "#define __KERNEL_USE_DATA_CONSTANTS__\n"; + + metal_printf("KernelData patching took %.1f ms\n", (time_dt() - starttime) * 1000.0); } - string source = globalDefines + "\n#include \"kernel/device/metal/kernel.metal\"\n"; - source = path_source_replace_includes(source, path_get("source")); - - metal_printf("Global defines:\n%s\n", globalDefines.c_str()); + source = global_defines + source; + metal_printf("================\n%s================\n\%s================\n", + global_defines.c_str(), + baked_constants.c_str()); - return source; + /* Generate an MD5 from the source and include any baked constants. This is used when caching + * PSOs. */ + MD5Hash md5; + md5.append(baked_constants); + md5.append(source); + source_md5[pso_type] = md5.get_hex(); } bool MetalDevice::load_kernels(const uint _kernel_features) @@ -283,28 +321,22 @@ bool MetalDevice::load_kernels(const uint _kernel_features) * active, but may still need to be rendered without motion blur if that isn't active as well. */ motion_blur = kernel_features & KERNEL_FEATURE_OBJECT_MOTION; - source[PSO_GENERIC] = get_source(kernel_features); - - const double starttime = time_dt(); - - mtlLibrary[PSO_GENERIC] = compile(source[PSO_GENERIC]); - - metal_printf("Front-end compilation finished in %.1f seconds (generic)\n", - time_dt() - starttime); - - MD5Hash md5; - md5.append(source[PSO_GENERIC]); - source_md5[PSO_GENERIC] = md5.get_hex(); - - bool result = MetalDeviceKernels::load(this, false); + bool result = compile_and_load(PSO_GENERIC); reserve_local_memory(kernel_features); - return result; } -id MetalDevice::compile(string const &source) +bool MetalDevice::compile_and_load(MetalPipelineType pso_type) { + make_source(pso_type, kernel_features); + + if (!MetalDeviceKernels::should_load_kernels(this, pso_type)) { + /* We already have a full set of matching pipelines which are cached or queued. */ + metal_printf("%s kernels already requested\n", kernel_type_as_string(pso_type)); + return true; + } + MTLCompileOptions *options = [[MTLCompileOptions alloc] init]; options.fastMathEnabled = YES; @@ -312,19 +344,30 @@ id MetalDevice::compile(string const &source) options.languageVersion = MTLLanguageVersion2_4; } + if (getenv("CYCLES_METAL_PROFILING") || getenv("CYCLES_METAL_DEBUG")) { + path_write_text(path_cache_get(string_printf("%s.metal", kernel_type_as_string(pso_type))), + source[pso_type]); + } + + const double starttime = time_dt(); + NSError *error = NULL; - id mtlLibrary = [mtlDevice newLibraryWithSource:@(source.c_str()) - options:options - error:&error]; + mtlLibrary[pso_type] = [mtlDevice newLibraryWithSource:@(source[pso_type].c_str()) + options:options + error:&error]; - if (!mtlLibrary) { + if (!mtlLibrary[pso_type]) { NSString *err = [error localizedDescription]; set_error(string_printf("Failed to compile library:\n%s", [err UTF8String])); } + metal_printf("Front-end compilation finished in %.1f seconds (%s)\n", + time_dt() - starttime, + kernel_type_as_string(pso_type)); + [options release]; - return mtlLibrary; + return MetalDeviceKernels::load(this, pso_type); } void MetalDevice::reserve_local_memory(const uint kernel_features) @@ -631,6 +674,58 @@ device_ptr MetalDevice::mem_alloc_sub_ptr(device_memory &mem, size_t offset, siz return 0; } +void MetalDevice::optimize_for_scene(Scene *scene) +{ + MetalPipelineType specialization_level = kernel_specialization_level; + + if (specialization_level < PSO_SPECIALIZED_INTERSECT) { + return; + } + + /* PSO_SPECIALIZED_INTERSECT kernels are fast to specialize, so we always load them + * synchronously. */ + compile_and_load(PSO_SPECIALIZED_INTERSECT); + + if (specialization_level < PSO_SPECIALIZED_SHADE) { + return; + } + if (!scene->params.background) { + /* Don't load PSO_SPECIALIZED_SHADE kernels during viewport rendering as they are slower to + * build. */ + return; + } + + /* PSO_SPECIALIZED_SHADE kernels are slower to specialize, so we load them asynchronously, and + * only if there isn't an existing load in flight. + */ + auto specialize_shade_fn = ^() { + compile_and_load(PSO_SPECIALIZED_SHADE); + async_compile_and_load = false; + }; + + bool async_specialize_shade = true; + + /* Block if a per-kernel profiling is enabled (ensure steady rendering rate). */ + if (getenv("CYCLES_METAL_PROFILING") != nullptr) { + async_specialize_shade = false; + } + + if (async_specialize_shade) { + if (!async_compile_and_load) { + async_compile_and_load = true; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), + specialize_shade_fn); + } + else { + metal_printf( + "Async PSO_SPECIALIZED_SHADE load request already in progress - dropping request\n"); + } + } + else { + specialize_shade_fn(); + } +} + void MetalDevice::const_copy_to(const char *name, void *host, size_t size) { if (strcmp(name, "data") == 0) { diff --git a/intern/cycles/device/metal/kernel.h b/intern/cycles/device/metal/kernel.h index 69b2a686ecc..11393f8b7e1 100644 --- a/intern/cycles/device/metal/kernel.h +++ b/intern/cycles/device/metal/kernel.h @@ -31,7 +31,7 @@ enum { enum { METALRT_TABLE_DEFAULT, METALRT_TABLE_SHADOW, METALRT_TABLE_LOCAL, METALRT_TABLE_NUM }; /* Pipeline State Object types */ -enum { +enum MetalPipelineType { /* A kernel that can be used with all scenes, supporting all features. * It is slow to compile, but only needs to be compiled once and is then * cached for future render sessions. This allows a render to get underway @@ -39,28 +39,33 @@ enum { */ PSO_GENERIC, - /* A kernel that is relatively quick to compile, but is specialized for the - * scene being rendered. It only contains the functionality and even baked in - * constants for values that means it needs to be recompiled whenever a - * dependent setting is changed. The render performance of this kernel is - * significantly faster though, and justifies the extra compile time. + /* A intersection kernel that is very quick to specialize and results in faster intersection + * kernel performance. It uses Metal function constants to replace several KernelData variables + * with fixed constants. + */ + PSO_SPECIALIZED_INTERSECT, + + /* A shading kernel that is slow to specialize, but results in faster shading kernel performance + * rendered. It uses Metal function constants to replace several KernelData variables with fixed + * constants and short-circuit all unused SVM node case handlers. */ - /* METAL_WIP: This isn't used and will require more changes to enable. */ - PSO_SPECIALISED, + PSO_SPECIALIZED_SHADE, PSO_NUM }; -const char *kernel_type_as_string(int kernel_type); +const char *kernel_type_as_string(MetalPipelineType pso_type); struct MetalKernelPipeline { void compile(); id mtlLibrary = nil; - bool scene_specialized; + MetalPipelineType pso_type; string source_md5; + size_t usage_count = 0; + KernelData kernel_data_; bool use_metalrt; bool metalrt_hair; bool metalrt_hair_thick; @@ -75,6 +80,8 @@ struct MetalKernelPipeline { id pipeline = nil; int num_threads_per_block = 0; + bool should_use_binary_archive() const; + string error_str; API_AVAILABLE(macos(11.0)) @@ -85,7 +92,8 @@ struct MetalKernelPipeline { /* Cache of Metal kernels for each DeviceKernel. */ namespace MetalDeviceKernels { -bool load(MetalDevice *device, bool scene_specialized); +bool should_load_kernels(MetalDevice *device, MetalPipelineType pso_type); +bool load(MetalDevice *device, MetalPipelineType pso_type); const MetalKernelPipeline *get_best_pipeline(const MetalDevice *device, DeviceKernel kernel); } /* namespace MetalDeviceKernels */ diff --git a/intern/cycles/device/metal/kernel.mm b/intern/cycles/device/metal/kernel.mm index fec4cd80466..385cb412b06 100644 --- a/intern/cycles/device/metal/kernel.mm +++ b/intern/cycles/device/metal/kernel.mm @@ -5,6 +5,7 @@ # include "device/metal/kernel.h" # include "device/metal/device_impl.h" +# include "kernel/device/metal/function_constants.h" # include "util/md5.h" # include "util/path.h" # include "util/tbb.h" @@ -16,13 +17,15 @@ CCL_NAMESPACE_BEGIN /* limit to 2 MTLCompiler instances */ int max_mtlcompiler_threads = 2; -const char *kernel_type_as_string(int kernel_type) +const char *kernel_type_as_string(MetalPipelineType pso_type) { - switch (kernel_type) { + switch (pso_type) { case PSO_GENERIC: return "PSO_GENERIC"; - case PSO_SPECIALISED: - return "PSO_SPECIALISED"; + case PSO_SPECIALIZED_INTERSECT: + return "PSO_SPECIALIZED_INTERSECT"; + case PSO_SPECIALIZED_SHADE: + return "PSO_SPECIALIZED_SHADE"; default: assert(0); } @@ -50,7 +53,11 @@ struct ShaderCache { /* Non-blocking request for a kernel, optionally specialized to the scene being rendered by * device. */ - void load_kernel(DeviceKernel kernel, MetalDevice *device, bool scene_specialized); + void load_kernel(DeviceKernel kernel, MetalDevice *device, MetalPipelineType pso_type); + + bool should_load_kernel(DeviceKernel device_kernel, + MetalDevice *device, + MetalPipelineType pso_type); void wait_for_all(); @@ -139,31 +146,34 @@ void ShaderCache::compile_thread_func(int thread_index) } } -void ShaderCache::load_kernel(DeviceKernel device_kernel, - MetalDevice *device, - bool scene_specialized) +bool ShaderCache::should_load_kernel(DeviceKernel device_kernel, + MetalDevice *device, + MetalPipelineType pso_type) { - { - /* create compiler threads on first run */ - thread_scoped_lock lock(cache_mutex); - if (compile_threads.empty()) { - running = true; - for (int i = 0; i < max_mtlcompiler_threads; i++) { - compile_threads.push_back(std::thread([&] { compile_thread_func(i); })); - } - } + if (device_kernel == DEVICE_KERNEL_INTEGRATOR_MEGAKERNEL) { + /* Skip megakernel. */ + return false; } - if (device_kernel == DEVICE_KERNEL_INTEGRATOR_MEGAKERNEL) { - /* skip megakernel */ - return; + if (device_kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE) { + if ((device->kernel_features & KERNEL_FEATURE_NODE_RAYTRACE) == 0) { + /* Skip shade_surface_raytrace kernel if the scene doesn't require it. */ + return false; + } } - if (scene_specialized) { + if (pso_type != PSO_GENERIC) { /* Only specialize kernels where it can make an impact. */ if (device_kernel < DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST || device_kernel > DEVICE_KERNEL_INTEGRATOR_MEGAKERNEL) { - return; + return false; + } + + /* Only specialize shading / intersection kernels as requested. */ + bool is_shade_kernel = (device_kernel >= DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND); + bool is_shade_pso = (pso_type == PSO_SPECIALIZED_SHADE); + if (is_shade_pso != is_shade_kernel) { + return false; } } @@ -171,35 +181,45 @@ void ShaderCache::load_kernel(DeviceKernel device_kernel, /* check whether the kernel has already been requested / cached */ thread_scoped_lock lock(cache_mutex); for (auto &pipeline : pipelines[device_kernel]) { - if (scene_specialized) { - if (pipeline->source_md5 == device->source_md5[PSO_SPECIALISED]) { - /* we already requested a pipeline that is specialized for this kernel data */ - metal_printf("Specialized kernel already requested (%s)\n", - device_kernel_as_string(device_kernel)); - return; - } + if (pipeline->source_md5 == device->source_md5[pso_type]) { + return false; } - else { - if (pipeline->source_md5 == device->source_md5[PSO_GENERIC]) { - /* we already requested a generic pipeline for this kernel */ - metal_printf("Generic kernel already requested (%s)\n", - device_kernel_as_string(device_kernel)); - return; - } + } + } + + return true; +} + +void ShaderCache::load_kernel(DeviceKernel device_kernel, + MetalDevice *device, + MetalPipelineType pso_type) +{ + { + /* create compiler threads on first run */ + thread_scoped_lock lock(cache_mutex); + if (compile_threads.empty()) { + running = true; + for (int i = 0; i < max_mtlcompiler_threads; i++) { + compile_threads.push_back(std::thread([&] { compile_thread_func(i); })); } } } + if (!should_load_kernel(device_kernel, device, pso_type)) { + return; + } + incomplete_requests++; PipelineRequest request; request.pipeline = new MetalKernelPipeline; - request.pipeline->scene_specialized = scene_specialized; + memcpy(&request.pipeline->kernel_data_, + &device->launch_params.data, + sizeof(request.pipeline->kernel_data_)); + request.pipeline->pso_type = pso_type; request.pipeline->mtlDevice = mtlDevice; - request.pipeline->source_md5 = - device->source_md5[scene_specialized ? PSO_SPECIALISED : PSO_GENERIC]; - request.pipeline->mtlLibrary = - device->mtlLibrary[scene_specialized ? PSO_SPECIALISED : PSO_GENERIC]; + request.pipeline->source_md5 = device->source_md5[pso_type]; + request.pipeline->mtlLibrary = device->mtlLibrary[pso_type]; request.pipeline->device_kernel = device_kernel; request.pipeline->threads_per_threadgroup = device->max_threads_per_threadgroup; @@ -214,7 +234,24 @@ void ShaderCache::load_kernel(DeviceKernel device_kernel, { thread_scoped_lock lock(cache_mutex); - pipelines[device_kernel].push_back(unique_ptr(request.pipeline)); + auto &collection = pipelines[device_kernel]; + + /* Cache up to 3 kernel variants with the same pso_type, purging oldest first. */ + int max_entries_of_same_pso_type = 3; + for (int i = (int)collection.size() - 1; i >= 0; i--) { + if (collection[i]->pso_type == pso_type) { + max_entries_of_same_pso_type -= 1; + if (max_entries_of_same_pso_type == 0) { + metal_printf("Purging oldest %s:%s kernel from ShaderCache\n", + kernel_type_as_string(pso_type), + device_kernel_as_string(device_kernel)); + collection.erase(collection.begin() + i); + break; + } + } + } + + collection.push_back(unique_ptr(request.pipeline)); request_queue.push_back(request); } cond_var.notify_one(); @@ -248,8 +285,9 @@ MetalKernelPipeline *ShaderCache::get_best_pipeline(DeviceKernel kernel, const M continue; } - if (pipeline->scene_specialized) { - if (pipeline->source_md5 == device->source_md5[PSO_SPECIALISED]) { + if (pipeline->pso_type != PSO_GENERIC) { + if (pipeline->source_md5 == device->source_md5[PSO_SPECIALIZED_INTERSECT] || + pipeline->source_md5 == device->source_md5[PSO_SPECIALIZED_SHADE]) { best_pipeline = pipeline.get(); } } @@ -258,13 +296,65 @@ MetalKernelPipeline *ShaderCache::get_best_pipeline(DeviceKernel kernel, const M } } + if (best_pipeline->usage_count == 0 && best_pipeline->pso_type != PSO_GENERIC) { + metal_printf("Swapping in %s version of %s\n", + kernel_type_as_string(best_pipeline->pso_type), + device_kernel_as_string(kernel)); + } + best_pipeline->usage_count += 1; + return best_pipeline; } -void MetalKernelPipeline::compile() +bool MetalKernelPipeline::should_use_binary_archive() const { - int pso_type = scene_specialized ? PSO_SPECIALISED : PSO_GENERIC; + if (auto str = getenv("CYCLES_METAL_DISABLE_BINARY_ARCHIVES")) { + if (atoi(str) != 0) { + /* Don't archive if we have opted out by env var. */ + return false; + } + } + + if (pso_type == PSO_GENERIC) { + /* Archive the generic kernels. */ + return true; + } + + if (device_kernel >= DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND && + device_kernel <= DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW) { + /* Archive all shade kernels - they take a long time to compile. */ + return true; + } + + /* The remaining kernels are all fast to compile. They may get cached by the system shader cache, + * but will be quick to regenerate if not. */ + return false; +} + +static MTLFunctionConstantValues *GetConstantValues(KernelData const *data = nullptr) +{ + MTLFunctionConstantValues *constant_values = [MTLFunctionConstantValues new]; + + MTLDataType MTLDataType_int = MTLDataTypeInt; + MTLDataType MTLDataType_float = MTLDataTypeFloat; + MTLDataType MTLDataType_float4 = MTLDataTypeFloat4; + KernelData zero_data = {0}; + if (!data) { + data = &zero_data; + } +# define KERNEL_STRUCT_MEMBER(parent, _type, name) \ + [constant_values setConstantValue:&data->parent.name \ + type:MTLDataType_##_type \ + atIndex:KernelData_##parent##_##name]; + +# include "kernel/data_template.h" + + return constant_values; +} + +void MetalKernelPipeline::compile() +{ const std::string function_name = std::string("cycles_metal_") + device_kernel_as_string(device_kernel); @@ -281,6 +371,17 @@ void MetalKernelPipeline::compile() if (@available(macOS 11.0, *)) { MTLFunctionDescriptor *func_desc = [MTLIntersectionFunctionDescriptor functionDescriptor]; func_desc.name = entryPoint; + + if (pso_type == PSO_SPECIALIZED_SHADE) { + func_desc.constantValues = GetConstantValues(&kernel_data_); + } + else if (pso_type == PSO_SPECIALIZED_INTERSECT) { + func_desc.constantValues = GetConstantValues(&kernel_data_); + } + else { + func_desc.constantValues = GetConstantValues(); + } + function = [mtlLibrary newFunctionWithDescriptor:func_desc error:&error]; } @@ -427,10 +528,7 @@ void MetalKernelPipeline::compile() MTLPipelineOption pipelineOptions = MTLPipelineOptionNone; - bool use_binary_archive = true; - if (auto str = getenv("CYCLES_METAL_DISABLE_BINARY_ARCHIVES")) { - use_binary_archive = (atoi(str) == 0); - } + bool use_binary_archive = should_use_binary_archive(); id archive = nil; string metalbin_path; @@ -608,19 +706,32 @@ void MetalKernelPipeline::compile() } } -bool MetalDeviceKernels::load(MetalDevice *device, bool scene_specialized) +bool MetalDeviceKernels::load(MetalDevice *device, MetalPipelineType pso_type) { + const double starttime = time_dt(); auto shader_cache = get_shader_cache(device->mtlDevice); for (int i = 0; i < DEVICE_KERNEL_NUM; i++) { - shader_cache->load_kernel((DeviceKernel)i, device, scene_specialized); + shader_cache->load_kernel((DeviceKernel)i, device, pso_type); } - if (!scene_specialized || getenv("CYCLES_METAL_PROFILING")) { - shader_cache->wait_for_all(); - } + shader_cache->wait_for_all(); + metal_printf("Back-end compilation finished in %.1f seconds (%s)\n", + time_dt() - starttime, + kernel_type_as_string(pso_type)); return true; } +bool MetalDeviceKernels::should_load_kernels(MetalDevice *device, MetalPipelineType pso_type) +{ + auto shader_cache = get_shader_cache(device->mtlDevice); + for (int i = 0; i < DEVICE_KERNEL_NUM; i++) { + if (shader_cache->should_load_kernel((DeviceKernel)i, device, pso_type)) { + return true; + } + } + return false; +} + const MetalKernelPipeline *MetalDeviceKernels::get_best_pipeline(const MetalDevice *device, DeviceKernel kernel) { diff --git a/intern/cycles/kernel/CMakeLists.txt b/intern/cycles/kernel/CMakeLists.txt index 527cc4ec111..21a78722c0d 100644 --- a/intern/cycles/kernel/CMakeLists.txt +++ b/intern/cycles/kernel/CMakeLists.txt @@ -79,6 +79,7 @@ set(SRC_KERNEL_DEVICE_METAL_HEADERS device/metal/compat.h device/metal/context_begin.h device/metal/context_end.h + device/metal/function_constants.h device/metal/globals.h ) diff --git a/intern/cycles/kernel/device/metal/function_constants.h b/intern/cycles/kernel/device/metal/function_constants.h new file mode 100644 index 00000000000..f4001735672 --- /dev/null +++ b/intern/cycles/kernel/device/metal/function_constants.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright 2021-2022 Blender Foundation */ + +enum { +#define KERNEL_STRUCT_MEMBER(parent, type, name) KernelData_##parent##_##name, +#include "kernel/data_template.h" +}; + +#ifdef __KERNEL_METAL__ +# define KERNEL_STRUCT_MEMBER(parent, type, name) \ + constant type kernel_data_##parent##_##name \ + [[function_constant(KernelData_##parent##_##name)]]; +# include "kernel/data_template.h" +#endif diff --git a/intern/cycles/kernel/device/metal/kernel.metal b/intern/cycles/kernel/device/metal/kernel.metal index 3c31dc3354c..74b4b079a32 100644 --- a/intern/cycles/kernel/device/metal/kernel.metal +++ b/intern/cycles/kernel/device/metal/kernel.metal @@ -5,6 +5,7 @@ #include "kernel/device/metal/compat.h" #include "kernel/device/metal/globals.h" +#include "kernel/device/metal/function_constants.h" #include "kernel/device/gpu/kernel.h" /* MetalRT intersection handlers */ diff --git a/intern/cycles/kernel/svm/svm.h b/intern/cycles/kernel/svm/svm.h index 9840cda3655..9d6d3e9222c 100644 --- a/intern/cycles/kernel/svm/svm.h +++ b/intern/cycles/kernel/svm/svm.h @@ -204,7 +204,14 @@ CCL_NAMESPACE_END CCL_NAMESPACE_BEGIN -#define SVM_CASE(node) case node: +#ifdef __KERNEL_USE_DATA_CONSTANTS__ +# define SVM_CASE(node) \ + case node: \ + if (!kernel_data_svm_usage_##node) \ + break; +#else +# define SVM_CASE(node) case node: +#endif /* Main Interpreter Loop */ template diff --git a/intern/cycles/kernel/types.h b/intern/cycles/kernel/types.h index 72cee6ae344..62ac75e5e4d 100644 --- a/intern/cycles/kernel/types.h +++ b/intern/cycles/kernel/types.h @@ -1136,7 +1136,13 @@ typedef enum KernelBVHLayout { } \ ; \ static_assert_align(name, 16); -#define KERNEL_STRUCT_MEMBER(parent, type, name) type name; + +#ifdef __KERNEL_USE_DATA_CONSTANTS__ +# define KERNEL_STRUCT_MEMBER(parent, type, name) type __unused_##name; +#else +# define KERNEL_STRUCT_MEMBER(parent, type, name) type name; +#endif + #include "kernel/data_template.h" typedef struct KernelTables { diff --git a/intern/cycles/scene/scene.cpp b/intern/cycles/scene/scene.cpp index eedb2a4fa3a..18cd665ac74 100644 --- a/intern/cycles/scene/scene.cpp +++ b/intern/cycles/scene/scene.cpp @@ -369,6 +369,8 @@ void Scene::device_update(Device *device_, Progress &progress) device->const_copy_to("data", &dscene.data, sizeof(dscene.data)); } + device->optimize_for_scene(this); + if (print_stats) { size_t mem_used = util_guarded_get_mem_used(); size_t mem_peak = util_guarded_get_mem_peak(); diff --git a/intern/cycles/util/string.cpp b/intern/cycles/util/string.cpp index 66ff866ee10..0c318cea44a 100644 --- a/intern/cycles/util/string.cpp +++ b/intern/cycles/util/string.cpp @@ -136,6 +136,19 @@ void string_replace(string &haystack, const string &needle, const string &other) } } +void string_replace_same_length(string &haystack, const string &needle, const string &other) +{ + assert(needle.size() == other.size()); + size_t pos = 0; + while (pos != string::npos) { + pos = haystack.find(needle, pos); + if (pos != string::npos) { + memcpy(haystack.data() + pos, other.data(), other.size()); + pos += other.size(); + } + } +} + string string_remove_trademark(const string &s) { string result = s; @@ -164,6 +177,11 @@ string to_string(const char *str) return string(str); } +string to_string(const float4 &v) +{ + return string_printf("%f,%f,%f,%f", v.x, v.y, v.z, v.w); +} + string string_to_lower(const string &s) { string r = s; diff --git a/intern/cycles/util/string.h b/intern/cycles/util/string.h index a74feee1750..ecbe9e106c6 100644 --- a/intern/cycles/util/string.h +++ b/intern/cycles/util/string.h @@ -38,12 +38,14 @@ void string_split(vector &tokens, const string &separators = "\t ", bool skip_empty_tokens = true); void string_replace(string &haystack, const string &needle, const string &other); +void string_replace_same_length(string &haystack, const string &needle, const string &other); bool string_startswith(string_view s, string_view start); bool string_endswith(string_view s, string_view end); string string_strip(const string &s); string string_remove_trademark(const string &s); string string_from_bool(const bool var); string to_string(const char *str); +string to_string(const float4 &v); string string_to_lower(const string &s); /* Wide char strings are only used on Windows to deal with non-ASCII -- cgit v1.2.3 From 523bbf7065547a67e7c23f67f546a5ed6433f809 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Thu, 14 Jul 2022 16:42:43 +0200 Subject: Cycles: generalize shader sorting / locality heuristic to all GPU devices This was added for Metal, but also gives good results with CUDA and OptiX. Also enable it for future Apple GPUs instead of only M1 and M2, since this has been shown to help across multiple GPUs so the better bet seems to enable rather than disable it. Also moves some of the logic outside of the Metal device code, and always enables the code in the kernel since other devices don't do dynamic compile. Time per sample with OptiX + RTX A6000: new old barbershop_interior 0.0730s 0.0727s bmw27 0.0047s 0.0053s classroom 0.0428s 0.0464s fishy_cat 0.0102s 0.0108s junkshop 0.0366s 0.0395s koro 0.0567s 0.0578s monster 0.0206s 0.0223s pabellon 0.0158s 0.0174s sponza 0.0088s 0.0100s spring 0.1267s 0.1280s victor 0.0524s 0.0531s wdas_cloud 0.0817s 0.0816s Ref D15331, T87836 --- intern/cycles/device/metal/device_impl.mm | 4 -- intern/cycles/device/metal/queue.h | 2 +- intern/cycles/device/metal/queue.mm | 16 +------ intern/cycles/device/metal/util.mm | 5 +-- intern/cycles/device/queue.h | 9 ++-- intern/cycles/integrator/path_trace_work_gpu.cpp | 53 ++++++++++++++++-------- intern/cycles/kernel/integrator/state_flow.h | 8 +--- 7 files changed, 45 insertions(+), 52 deletions(-) diff --git a/intern/cycles/device/metal/device_impl.mm b/intern/cycles/device/metal/device_impl.mm index d8bb3b867cd..d1250b83d22 100644 --- a/intern/cycles/device/metal/device_impl.mm +++ b/intern/cycles/device/metal/device_impl.mm @@ -229,10 +229,6 @@ void MetalDevice::make_source(MetalPipelineType pso_type, const uint kernel_feat global_defines += "#define __KERNEL_FEATURES__ " + to_string(kernel_features) + "\n"; } - if (MetalInfo::optimal_sort_partition_elements(mtlDevice) > 0) { - build_options += " -D__KERNEL_SORT_PARTITIONING__ "; - } - if (use_metalrt) { global_defines += "#define __METALRT__\n"; if (motion_blur) { diff --git a/intern/cycles/device/metal/queue.h b/intern/cycles/device/metal/queue.h index 836289172f7..fc32740f3e1 100644 --- a/intern/cycles/device/metal/queue.h +++ b/intern/cycles/device/metal/queue.h @@ -24,7 +24,7 @@ class MetalDeviceQueue : public DeviceQueue { virtual int num_concurrent_states(const size_t) const override; virtual int num_concurrent_busy_states() const override; - virtual int num_sort_partitions(const size_t) const override; + virtual int num_sort_partition_elements() const override; virtual void init_execution() override; diff --git a/intern/cycles/device/metal/queue.mm b/intern/cycles/device/metal/queue.mm index 6a9cc552098..5ac63a16c61 100644 --- a/intern/cycles/device/metal/queue.mm +++ b/intern/cycles/device/metal/queue.mm @@ -293,21 +293,9 @@ int MetalDeviceQueue::num_concurrent_busy_states() const return result; } -int MetalDeviceQueue::num_sort_partitions(const size_t state_size) const +int MetalDeviceQueue::num_sort_partition_elements() const { - /* Sort partitioning becomes less effective when more shaders are in the wavefront. In lieu of a - * more sophisticated heuristic we simply disable sort partitioning if the shader count is high. - */ - if (metal_device_->launch_params.data.max_shaders >= 300) { - return 1; - } - - const int optimal_partition_elements = MetalInfo::optimal_sort_partition_elements( - metal_device_->mtlDevice); - if (optimal_partition_elements) { - return num_concurrent_states(state_size) / optimal_partition_elements; - } - return 1; + return MetalInfo::optimal_sort_partition_elements(metal_device_->mtlDevice); } void MetalDeviceQueue::init_execution() diff --git a/intern/cycles/device/metal/util.mm b/intern/cycles/device/metal/util.mm index c336dc310c8..65c67c400fe 100644 --- a/intern/cycles/device/metal/util.mm +++ b/intern/cycles/device/metal/util.mm @@ -82,10 +82,7 @@ int MetalInfo::optimal_sort_partition_elements(id device) * sorting each partition by material. Partitioning into chunks of 65536 elements results in an * overall render time speedup of up to 15%. */ if (get_device_vendor(device) == METAL_GPU_APPLE) { - AppleGPUArchitecture arch = get_apple_gpu_architecture(device); - if (arch == APPLE_M1 || arch == APPLE_M2) { - return 65536; - } + return 65536; } return 0; } diff --git a/intern/cycles/device/queue.h b/intern/cycles/device/queue.h index 20308e4a106..808431af401 100644 --- a/intern/cycles/device/queue.h +++ b/intern/cycles/device/queue.h @@ -105,12 +105,11 @@ class DeviceQueue { * value. */ virtual int num_concurrent_busy_states() const = 0; - /* Number of partitions within which active indices are sorted by material ID. - * Using more partitions lets us trade off material coherence for better integrator state fetch - * locality. */ - virtual int num_sort_partitions(const size_t /*state_size*/) const + /* Number of elements in a partition of sorted shaders, that improves memory locality of + * integrator state fetch at the cost of decreased coherence for shader kernel execution. */ + virtual int num_sort_partition_elements() const { - return 1; + return 65536; } /* Initialize execution of kernels on this queue. diff --git a/intern/cycles/integrator/path_trace_work_gpu.cpp b/intern/cycles/integrator/path_trace_work_gpu.cpp index d51e8a28bb4..fa313f6460a 100644 --- a/intern/cycles/integrator/path_trace_work_gpu.cpp +++ b/intern/cycles/integrator/path_trace_work_gpu.cpp @@ -181,28 +181,45 @@ void PathTraceWorkGPU::alloc_integrator_queue() void PathTraceWorkGPU::alloc_integrator_sorting() { + /* Compute sort partitions, to balance between memory locality and coherence. + * Sort partitioning becomes less effective when more shaders are in the wavefront. In lieu of a + * more sophisticated heuristic we simply disable sort partitioning if the shader count is high. + */ + num_sort_partitions_ = 1; + if (device_scene_->data.max_shaders < 300) { + const int num_elements = queue_->num_sort_partition_elements(); + if (num_elements) { + num_sort_partitions_ = max(max_num_paths_ / num_elements, 1); + } + } + + integrator_state_gpu_.sort_partition_divisor = (int)divide_up(max_num_paths_, + num_sort_partitions_); + /* Allocate arrays for shader sorting. */ - num_sort_partitions_ = queue_->num_sort_partitions(estimate_single_state_size()); const int sort_buckets = device_scene_->data.max_shaders * num_sort_partitions_; if (integrator_shader_sort_counter_.size() < sort_buckets) { integrator_shader_sort_counter_.alloc(sort_buckets); integrator_shader_sort_counter_.zero_to_device(); + integrator_state_gpu_.sort_key_counter[DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE] = + (int *)integrator_shader_sort_counter_.device_pointer; - integrator_shader_raytrace_sort_counter_.alloc(sort_buckets); - integrator_shader_raytrace_sort_counter_.zero_to_device(); + if (device_scene_->data.kernel_features & KERNEL_FEATURE_NODE_RAYTRACE) { + integrator_shader_raytrace_sort_counter_.alloc(sort_buckets); + integrator_shader_raytrace_sort_counter_.zero_to_device(); + integrator_state_gpu_.sort_key_counter[DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE] = + (int *)integrator_shader_raytrace_sort_counter_.device_pointer; + } - integrator_shader_mnee_sort_counter_.alloc(sort_buckets); - integrator_shader_mnee_sort_counter_.zero_to_device(); + if (device_scene_->data.kernel_features & KERNEL_FEATURE_MNEE) { + integrator_shader_mnee_sort_counter_.alloc(sort_buckets); + integrator_shader_mnee_sort_counter_.zero_to_device(); + integrator_state_gpu_.sort_key_counter[DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE] = + (int *)integrator_shader_mnee_sort_counter_.device_pointer; + } integrator_shader_sort_prefix_sum_.alloc(sort_buckets); integrator_shader_sort_prefix_sum_.zero_to_device(); - - integrator_state_gpu_.sort_key_counter[DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE] = - (int *)integrator_shader_sort_counter_.device_pointer; - integrator_state_gpu_.sort_key_counter[DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE] = - (int *)integrator_shader_raytrace_sort_counter_.device_pointer; - integrator_state_gpu_.sort_key_counter[DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE] = - (int *)integrator_shader_mnee_sort_counter_.device_pointer; } } @@ -238,10 +255,6 @@ void PathTraceWorkGPU::init_execution() { queue_->init_execution(); - /* Setup sort partitioning divisor for better cache utilization. */ - integrator_state_gpu_.sort_partition_divisor = (int)divide_up(max_num_paths_, - num_sort_partitions_); - /* Copy to device side struct in constant memory. */ device_->const_copy_to( "integrator_state", &integrator_state_gpu_, sizeof(integrator_state_gpu_)); @@ -338,8 +351,12 @@ void PathTraceWorkGPU::enqueue_reset() queue_->enqueue(DEVICE_KERNEL_INTEGRATOR_RESET, max_num_paths_, args); queue_->zero_to_device(integrator_queue_counter_); queue_->zero_to_device(integrator_shader_sort_counter_); - queue_->zero_to_device(integrator_shader_raytrace_sort_counter_); - queue_->zero_to_device(integrator_shader_mnee_sort_counter_); + if (device_scene_->data.kernel_features & KERNEL_FEATURE_NODE_RAYTRACE) { + queue_->zero_to_device(integrator_shader_raytrace_sort_counter_); + } + if (device_scene_->data.kernel_features & KERNEL_FEATURE_MNEE) { + queue_->zero_to_device(integrator_shader_mnee_sort_counter_); + } /* Tiles enqueue need to know number of active paths, which is based on this counter. Zero the * counter on the host side because `zero_to_device()` is not doing it. */ diff --git a/intern/cycles/kernel/integrator/state_flow.h b/intern/cycles/kernel/integrator/state_flow.h index 1ae746022d0..4b03c665e17 100644 --- a/intern/cycles/kernel/integrator/state_flow.h +++ b/intern/cycles/kernel/integrator/state_flow.h @@ -99,13 +99,9 @@ ccl_device_forceinline void integrator_shadow_path_terminate(KernelGlobals kg, INTEGRATOR_STATE_WRITE(state, shadow_path, queued_kernel) = 0; } -# ifdef __KERNEL_SORT_PARTITIONING__ /* Sort first by truncated state index (for good locality), then by key (for good coherence). */ -# define INTEGRATOR_SORT_KEY(key, state) \ - (key + kernel_data.max_shaders * (state / kernel_integrator_state.sort_partition_divisor)) -# else -# define INTEGRATOR_SORT_KEY(key, state) (key) -# endif +# define INTEGRATOR_SORT_KEY(key, state) \ + (key + kernel_data.max_shaders * (state / kernel_integrator_state.sort_partition_divisor)) ccl_device_forceinline void integrator_path_init_sorted(KernelGlobals kg, IntegratorState state, -- cgit v1.2.3 From 914617f8fd01f8e84daf4ae0b8ea750a1bfbdc42 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Fri, 15 Jul 2022 12:57:37 +0200 Subject: Fix (unreported) LibOverride: invalid behaviors when creating (partial) overrides. The outliner would tagg all existing local IDs (for remap from linked reference data to newly created overrides) when creating a new override. This would become critical issue in case there is several existing copies of the same override hierarchy (leading to several hierarchies using the same override). Further more, BKE override creation code would not systematically properly remapp linked usages to new overrides one whithin the affected override hierarchy, leading to potential undesired remaining usages of linked data. --- source/blender/blenkernel/intern/lib_override.cc | 18 +++++++++++++++--- .../blender/editors/space_outliner/outliner_tools.cc | 2 +- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/source/blender/blenkernel/intern/lib_override.cc b/source/blender/blenkernel/intern/lib_override.cc index aa3210b64ad..0c5f59be768 100644 --- a/source/blender/blenkernel/intern/lib_override.cc +++ b/source/blender/blenkernel/intern/lib_override.cc @@ -1149,14 +1149,26 @@ static bool lib_override_library_create_do(Main *bmain, BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false); lib_override_hierarchy_dependencies_recursive_tag(&data); + /* In case the operation is on an already partially overridden hierarchy, all existing overrides + * in that hierarchy need to be tagged for remapping from linked reference ID usages to newly + * created overrides ones. */ + if (id_hierarchy_root_reference->lib != id_root_reference->lib) { + BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_reference)); + BLI_assert(id_hierarchy_root_reference->override_library->reference->lib == + id_root_reference->lib); + + BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false); + data.hierarchy_root_id = id_hierarchy_root_reference; + data.id_root = id_hierarchy_root_reference; + data.is_override = true; + lib_override_overrides_group_tag(&data); + } + BKE_main_relations_free(bmain); lib_override_group_tag_data_clear(&data); bool success = false; if (id_hierarchy_root_reference->lib != id_root_reference->lib) { - BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_reference)); - BLI_assert(id_hierarchy_root_reference->override_library->reference->lib == - id_root_reference->lib); success = BKE_lib_override_library_create_from_tag(bmain, owner_library, id_root_reference, diff --git a/source/blender/editors/space_outliner/outliner_tools.cc b/source/blender/editors/space_outliner/outliner_tools.cc index 7b7182eb106..9d52103a266 100644 --- a/source/blender/editors/space_outliner/outliner_tools.cc +++ b/source/blender/editors/space_outliner/outliner_tools.cc @@ -875,7 +875,7 @@ static void id_override_library_create_fn(bContext *C, /* For now, remap all local usages of linked ID to local override one here. */ ID *id_iter; FOREACH_MAIN_ID_BEGIN (bmain, id_iter) { - if (ID_IS_LINKED(id_iter)) { + if (ID_IS_LINKED(id_iter) || ID_IS_OVERRIDE_LIBRARY(id_iter)) { id_iter->tag &= ~LIB_TAG_DOIT; } else { -- cgit v1.2.3 From 180db0f752c88d3bbd47774a0f7c9a31de5a3864 Mon Sep 17 00:00:00 2001 From: Damien Picard Date: Fri, 15 Jul 2022 14:12:34 +0200 Subject: UI: make many modifier strings translatable This includes: - new modifier names It mostly uses `N_` because the strings are actually translated elsewhere. The goal is simply to export them to .po files. Most of the new translations were reported in T43295#1105335. Reviewed By: mont29 Differential Revision: https://developer.blender.org/D15418 --- source/blender/modifiers/intern/MOD_armature.c | 2 +- source/blender/modifiers/intern/MOD_array.c | 2 +- source/blender/modifiers/intern/MOD_bevel.c | 2 +- source/blender/modifiers/intern/MOD_boolean.cc | 2 +- source/blender/modifiers/intern/MOD_build.c | 4 +++- source/blender/modifiers/intern/MOD_cast.c | 2 +- source/blender/modifiers/intern/MOD_cloth.c | 2 +- source/blender/modifiers/intern/MOD_collision.c | 2 +- source/blender/modifiers/intern/MOD_correctivesmooth.c | 2 +- source/blender/modifiers/intern/MOD_curve.c | 2 +- source/blender/modifiers/intern/MOD_datatransfer.c | 2 +- source/blender/modifiers/intern/MOD_decimate.c | 2 +- source/blender/modifiers/intern/MOD_displace.c | 2 +- source/blender/modifiers/intern/MOD_dynamicpaint.c | 2 +- source/blender/modifiers/intern/MOD_edgesplit.c | 2 +- source/blender/modifiers/intern/MOD_explode.c | 2 +- source/blender/modifiers/intern/MOD_fluid.c | 2 +- source/blender/modifiers/intern/MOD_hook.c | 2 +- source/blender/modifiers/intern/MOD_laplaciandeform.c | 2 +- source/blender/modifiers/intern/MOD_laplaciansmooth.c | 2 +- source/blender/modifiers/intern/MOD_lattice.c | 2 +- source/blender/modifiers/intern/MOD_mask.cc | 2 +- source/blender/modifiers/intern/MOD_mesh_to_volume.cc | 4 +++- source/blender/modifiers/intern/MOD_meshcache.c | 2 +- source/blender/modifiers/intern/MOD_meshdeform.c | 2 +- source/blender/modifiers/intern/MOD_meshsequencecache.cc | 2 +- source/blender/modifiers/intern/MOD_mirror.c | 2 +- source/blender/modifiers/intern/MOD_multires.c | 2 +- source/blender/modifiers/intern/MOD_nodes.cc | 2 +- source/blender/modifiers/intern/MOD_normal_edit.c | 2 +- source/blender/modifiers/intern/MOD_ocean.c | 2 +- source/blender/modifiers/intern/MOD_particleinstance.c | 2 +- source/blender/modifiers/intern/MOD_particlesystem.cc | 2 +- source/blender/modifiers/intern/MOD_remesh.c | 2 +- source/blender/modifiers/intern/MOD_screw.c | 2 +- source/blender/modifiers/intern/MOD_shapekey.c | 4 +++- source/blender/modifiers/intern/MOD_shrinkwrap.c | 2 +- source/blender/modifiers/intern/MOD_simpledeform.c | 2 +- source/blender/modifiers/intern/MOD_skin.c | 2 +- source/blender/modifiers/intern/MOD_smooth.c | 2 +- source/blender/modifiers/intern/MOD_softbody.c | 2 +- source/blender/modifiers/intern/MOD_solidify.c | 2 +- source/blender/modifiers/intern/MOD_subsurf.c | 2 +- source/blender/modifiers/intern/MOD_surface.c | 2 +- source/blender/modifiers/intern/MOD_surfacedeform.c | 2 +- source/blender/modifiers/intern/MOD_triangulate.c | 2 +- source/blender/modifiers/intern/MOD_uvproject.c | 2 +- source/blender/modifiers/intern/MOD_uvwarp.c | 2 +- source/blender/modifiers/intern/MOD_volume_displace.cc | 4 +++- source/blender/modifiers/intern/MOD_volume_to_mesh.cc | 4 +++- source/blender/modifiers/intern/MOD_warp.c | 2 +- source/blender/modifiers/intern/MOD_wave.c | 12 ++++++------ source/blender/modifiers/intern/MOD_weighted_normal.c | 2 +- source/blender/modifiers/intern/MOD_weightvgedit.c | 2 +- source/blender/modifiers/intern/MOD_weightvgmix.c | 2 +- source/blender/modifiers/intern/MOD_weightvgproximity.c | 2 +- source/blender/modifiers/intern/MOD_weld.cc | 2 +- source/blender/modifiers/intern/MOD_wireframe.c | 2 +- 58 files changed, 73 insertions(+), 63 deletions(-) diff --git a/source/blender/modifiers/intern/MOD_armature.c b/source/blender/modifiers/intern/MOD_armature.c index f6e8a26f0e1..15ad361a262 100644 --- a/source/blender/modifiers/intern/MOD_armature.c +++ b/source/blender/modifiers/intern/MOD_armature.c @@ -266,7 +266,7 @@ static void blendRead(BlendDataReader *UNUSED(reader), ModifierData *md) } ModifierTypeInfo modifierType_Armature = { - /* name */ "Armature", + /* name */ N_("Armature"), /* structName */ "ArmatureModifierData", /* structSize */ sizeof(ArmatureModifierData), /* srna */ &RNA_ArmatureModifier, diff --git a/source/blender/modifiers/intern/MOD_array.c b/source/blender/modifiers/intern/MOD_array.c index 77915702ee4..a7361f6d0b6 100644 --- a/source/blender/modifiers/intern/MOD_array.c +++ b/source/blender/modifiers/intern/MOD_array.c @@ -1002,7 +1002,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Array = { - /* name */ "Array", + /* name */ N_("Array"), /* structName */ "ArrayModifierData", /* structSize */ sizeof(ArrayModifierData), /* srna */ &RNA_ArrayModifier, diff --git a/source/blender/modifiers/intern/MOD_bevel.c b/source/blender/modifiers/intern/MOD_bevel.c index c634873cfe4..94f2090e081 100644 --- a/source/blender/modifiers/intern/MOD_bevel.c +++ b/source/blender/modifiers/intern/MOD_bevel.c @@ -417,7 +417,7 @@ static void blendRead(BlendDataReader *reader, ModifierData *md) } ModifierTypeInfo modifierType_Bevel = { - /* name */ "Bevel", + /* name */ N_("Bevel"), /* structName */ "BevelModifierData", /* structSize */ sizeof(BevelModifierData), /* srna */ &RNA_BevelModifier, diff --git a/source/blender/modifiers/intern/MOD_boolean.cc b/source/blender/modifiers/intern/MOD_boolean.cc index 09676d0e9ee..c9dc14b3b20 100644 --- a/source/blender/modifiers/intern/MOD_boolean.cc +++ b/source/blender/modifiers/intern/MOD_boolean.cc @@ -639,7 +639,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Boolean = { - /* name */ "Boolean", + /* name */ N_("Boolean"), /* structName */ "BooleanModifierData", /* structSize */ sizeof(BooleanModifierData), /* srna */ &RNA_BooleanModifier, diff --git a/source/blender/modifiers/intern/MOD_build.c b/source/blender/modifiers/intern/MOD_build.c index 687ff04cedf..a9b6af967be 100644 --- a/source/blender/modifiers/intern/MOD_build.c +++ b/source/blender/modifiers/intern/MOD_build.c @@ -13,6 +13,8 @@ #include "BLI_math_vector.h" #include "BLI_rand.h" +#include "BLT_translation.h" + #include "DNA_defaults.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" @@ -311,7 +313,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Build = { - /* name */ "Build", + /* name */ N_("Build"), /* structName */ "BuildModifierData", /* structSize */ sizeof(BuildModifierData), /* srna */ &RNA_BuildModifier, diff --git a/source/blender/modifiers/intern/MOD_cast.c b/source/blender/modifiers/intern/MOD_cast.c index 3916551bc90..9aaf7fead36 100644 --- a/source/blender/modifiers/intern/MOD_cast.c +++ b/source/blender/modifiers/intern/MOD_cast.c @@ -559,7 +559,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Cast = { - /* name */ "Cast", + /* name */ N_("Cast"), /* structName */ "CastModifierData", /* structSize */ sizeof(CastModifierData), /* srna */ &RNA_CastModifier, diff --git a/source/blender/modifiers/intern/MOD_cloth.c b/source/blender/modifiers/intern/MOD_cloth.c index 185b05b4cf9..cc0bd87d614 100644 --- a/source/blender/modifiers/intern/MOD_cloth.c +++ b/source/blender/modifiers/intern/MOD_cloth.c @@ -275,7 +275,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Cloth = { - /* name */ "Cloth", + /* name */ N_("Cloth"), /* structName */ "ClothModifierData", /* structSize */ sizeof(ClothModifierData), /* srna */ &RNA_ClothModifier, diff --git a/source/blender/modifiers/intern/MOD_collision.c b/source/blender/modifiers/intern/MOD_collision.c index f9cd3d5937d..74cb4ac700a 100644 --- a/source/blender/modifiers/intern/MOD_collision.c +++ b/source/blender/modifiers/intern/MOD_collision.c @@ -284,7 +284,7 @@ static void blendRead(BlendDataReader *UNUSED(reader), ModifierData *md) } ModifierTypeInfo modifierType_Collision = { - /* name */ "Collision", + /* name */ N_("Collision"), /* structName */ "CollisionModifierData", /* structSize */ sizeof(CollisionModifierData), /* srna */ &RNA_CollisionModifier, diff --git a/source/blender/modifiers/intern/MOD_correctivesmooth.c b/source/blender/modifiers/intern/MOD_correctivesmooth.c index 3698f4403a1..2beb1be6749 100644 --- a/source/blender/modifiers/intern/MOD_correctivesmooth.c +++ b/source/blender/modifiers/intern/MOD_correctivesmooth.c @@ -835,7 +835,7 @@ static void blendRead(BlendDataReader *reader, ModifierData *md) } ModifierTypeInfo modifierType_CorrectiveSmooth = { - /* name */ "CorrectiveSmooth", + /* name */ N_("CorrectiveSmooth"), /* structName */ "CorrectiveSmoothModifierData", /* structSize */ sizeof(CorrectiveSmoothModifierData), /* srna */ &RNA_CorrectiveSmoothModifier, diff --git a/source/blender/modifiers/intern/MOD_curve.c b/source/blender/modifiers/intern/MOD_curve.c index a82b999f4dc..48a59f4d949 100644 --- a/source/blender/modifiers/intern/MOD_curve.c +++ b/source/blender/modifiers/intern/MOD_curve.c @@ -203,7 +203,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Curve = { - /* name */ "Curve", + /* name */ N_("Curve"), /* structName */ "CurveModifierData", /* structSize */ sizeof(CurveModifierData), /* srna */ &RNA_CurveModifier, diff --git a/source/blender/modifiers/intern/MOD_datatransfer.c b/source/blender/modifiers/intern/MOD_datatransfer.c index a3b088799cc..e9f1cf47e38 100644 --- a/source/blender/modifiers/intern/MOD_datatransfer.c +++ b/source/blender/modifiers/intern/MOD_datatransfer.c @@ -474,7 +474,7 @@ static void panelRegister(ARegionType *region_type) #undef DT_TYPES_AFFECT_MESH ModifierTypeInfo modifierType_DataTransfer = { - /* name */ "DataTransfer", + /* name */ N_("DataTransfer"), /* structName */ "DataTransferModifierData", /* structSize */ sizeof(DataTransferModifierData), /* srna */ &RNA_DataTransferModifier, diff --git a/source/blender/modifiers/intern/MOD_decimate.c b/source/blender/modifiers/intern/MOD_decimate.c index 70f028d6907..3df4fbcbea8 100644 --- a/source/blender/modifiers/intern/MOD_decimate.c +++ b/source/blender/modifiers/intern/MOD_decimate.c @@ -271,7 +271,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Decimate = { - /* name */ "Decimate", + /* name */ N_("Decimate"), /* structName */ "DecimateModifierData", /* structSize */ sizeof(DecimateModifierData), /* srna */ &RNA_DecimateModifier, diff --git a/source/blender/modifiers/intern/MOD_displace.c b/source/blender/modifiers/intern/MOD_displace.c index 1e2224e3a65..5289fc42e21 100644 --- a/source/blender/modifiers/intern/MOD_displace.c +++ b/source/blender/modifiers/intern/MOD_displace.c @@ -473,7 +473,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Displace = { - /* name */ "Displace", + /* name */ N_("Displace"), /* structName */ "DisplaceModifierData", /* structSize */ sizeof(DisplaceModifierData), /* srna */ &RNA_DisplaceModifier, diff --git a/source/blender/modifiers/intern/MOD_dynamicpaint.c b/source/blender/modifiers/intern/MOD_dynamicpaint.c index 6b0578c77f1..4afb81c04a9 100644 --- a/source/blender/modifiers/intern/MOD_dynamicpaint.c +++ b/source/blender/modifiers/intern/MOD_dynamicpaint.c @@ -189,7 +189,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_DynamicPaint = { - /* name */ "Dynamic Paint", + /* name */ N_("Dynamic Paint"), /* structName */ "DynamicPaintModifierData", /* structSize */ sizeof(DynamicPaintModifierData), /* srna */ &RNA_DynamicPaintModifier, diff --git a/source/blender/modifiers/intern/MOD_edgesplit.c b/source/blender/modifiers/intern/MOD_edgesplit.c index 49ddcb9a61d..b381ff32aa2 100644 --- a/source/blender/modifiers/intern/MOD_edgesplit.c +++ b/source/blender/modifiers/intern/MOD_edgesplit.c @@ -154,7 +154,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_EdgeSplit = { - /* name */ "EdgeSplit", + /* name */ N_("EdgeSplit"), /* structName */ "EdgeSplitModifierData", /* structSize */ sizeof(EdgeSplitModifierData), /* srna */ &RNA_EdgeSplitModifier, diff --git a/source/blender/modifiers/intern/MOD_explode.c b/source/blender/modifiers/intern/MOD_explode.c index f88e930e127..ff0616fd288 100644 --- a/source/blender/modifiers/intern/MOD_explode.c +++ b/source/blender/modifiers/intern/MOD_explode.c @@ -1224,7 +1224,7 @@ static void blendRead(BlendDataReader *UNUSED(reader), ModifierData *md) } ModifierTypeInfo modifierType_Explode = { - /* name */ "Explode", + /* name */ N_("Explode"), /* structName */ "ExplodeModifierData", /* structSize */ sizeof(ExplodeModifierData), /* srna */ &RNA_ExplodeModifier, diff --git a/source/blender/modifiers/intern/MOD_fluid.c b/source/blender/modifiers/intern/MOD_fluid.c index 562f0df510d..a3e9cd083d2 100644 --- a/source/blender/modifiers/intern/MOD_fluid.c +++ b/source/blender/modifiers/intern/MOD_fluid.c @@ -241,7 +241,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Fluid = { - /* name */ "Fluid", + /* name */ N_("Fluid"), /* structName */ "FluidModifierData", /* structSize */ sizeof(FluidModifierData), /* srna */ &RNA_FluidModifier, diff --git a/source/blender/modifiers/intern/MOD_hook.c b/source/blender/modifiers/intern/MOD_hook.c index 3649ece12e1..3c4e6b0d90f 100644 --- a/source/blender/modifiers/intern/MOD_hook.c +++ b/source/blender/modifiers/intern/MOD_hook.c @@ -545,7 +545,7 @@ static void blendRead(BlendDataReader *reader, ModifierData *md) } ModifierTypeInfo modifierType_Hook = { - /* name */ "Hook", + /* name */ N_("Hook"), /* structName */ "HookModifierData", /* structSize */ sizeof(HookModifierData), /* srna */ &RNA_HookModifier, diff --git a/source/blender/modifiers/intern/MOD_laplaciandeform.c b/source/blender/modifiers/intern/MOD_laplaciandeform.c index a22f4b35e0d..e29098eb218 100644 --- a/source/blender/modifiers/intern/MOD_laplaciandeform.c +++ b/source/blender/modifiers/intern/MOD_laplaciandeform.c @@ -875,7 +875,7 @@ static void blendRead(BlendDataReader *reader, ModifierData *md) } ModifierTypeInfo modifierType_LaplacianDeform = { - /* name */ "LaplacianDeform", + /* name */ N_("LaplacianDeform"), /* structName */ "LaplacianDeformModifierData", /* structSize */ sizeof(LaplacianDeformModifierData), /* srna */ &RNA_LaplacianDeformModifier, diff --git a/source/blender/modifiers/intern/MOD_laplaciansmooth.c b/source/blender/modifiers/intern/MOD_laplaciansmooth.c index 95283b1cd20..2cce0c14e4c 100644 --- a/source/blender/modifiers/intern/MOD_laplaciansmooth.c +++ b/source/blender/modifiers/intern/MOD_laplaciansmooth.c @@ -608,7 +608,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_LaplacianSmooth = { - /* name */ "LaplacianSmooth", + /* name */ N_("LaplacianSmooth"), /* structName */ "LaplacianSmoothModifierData", /* structSize */ sizeof(LaplacianSmoothModifierData), /* srna */ &RNA_LaplacianSmoothModifier, diff --git a/source/blender/modifiers/intern/MOD_lattice.c b/source/blender/modifiers/intern/MOD_lattice.c index 832372304a0..0e1994eed36 100644 --- a/source/blender/modifiers/intern/MOD_lattice.c +++ b/source/blender/modifiers/intern/MOD_lattice.c @@ -160,7 +160,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Lattice = { - /* name */ "Lattice", + /* name */ N_("Lattice"), /* structName */ "LatticeModifierData", /* structSize */ sizeof(LatticeModifierData), /* srna */ &RNA_LatticeModifier, diff --git a/source/blender/modifiers/intern/MOD_mask.cc b/source/blender/modifiers/intern/MOD_mask.cc index 0813901fc49..fac3ea36537 100644 --- a/source/blender/modifiers/intern/MOD_mask.cc +++ b/source/blender/modifiers/intern/MOD_mask.cc @@ -804,7 +804,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Mask = { - /* name */ "Mask", + /* name */ N_("Mask"), /* structName */ "MaskModifierData", /* structSize */ sizeof(MaskModifierData), /* srna */ &RNA_MaskModifier, diff --git a/source/blender/modifiers/intern/MOD_mesh_to_volume.cc b/source/blender/modifiers/intern/MOD_mesh_to_volume.cc index 39bd013609b..7d4affa2dce 100644 --- a/source/blender/modifiers/intern/MOD_mesh_to_volume.cc +++ b/source/blender/modifiers/intern/MOD_mesh_to_volume.cc @@ -15,6 +15,8 @@ #include "BKE_object.h" #include "BKE_volume.h" +#include "BLT_translation.h" + #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "DNA_object_types.h" @@ -202,7 +204,7 @@ static void modifyGeometrySet(ModifierData *md, } ModifierTypeInfo modifierType_MeshToVolume = { - /* name */ "Mesh to Volume", + /* name */ N_("Mesh to Volume"), /* structName */ "MeshToVolumeModifierData", /* structSize */ sizeof(MeshToVolumeModifierData), /* srna */ &RNA_MeshToVolumeModifier, diff --git a/source/blender/modifiers/intern/MOD_meshcache.c b/source/blender/modifiers/intern/MOD_meshcache.c index 6f065797b43..8dfdd07ace9 100644 --- a/source/blender/modifiers/intern/MOD_meshcache.c +++ b/source/blender/modifiers/intern/MOD_meshcache.c @@ -416,7 +416,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_MeshCache = { - /* name */ "MeshCache", + /* name */ N_("MeshCache"), /* structName */ "MeshCacheModifierData", /* structSize */ sizeof(MeshCacheModifierData), /* srna */ &RNA_MeshCacheModifier, diff --git a/source/blender/modifiers/intern/MOD_meshdeform.c b/source/blender/modifiers/intern/MOD_meshdeform.c index 5dac31ac1df..40aa0f84f92 100644 --- a/source/blender/modifiers/intern/MOD_meshdeform.c +++ b/source/blender/modifiers/intern/MOD_meshdeform.c @@ -650,7 +650,7 @@ static void blendRead(BlendDataReader *reader, ModifierData *md) } ModifierTypeInfo modifierType_MeshDeform = { - /* name */ "MeshDeform", + /* name */ N_("MeshDeform"), /* structName */ "MeshDeformModifierData", /* structSize */ sizeof(MeshDeformModifierData), /* srna */ &RNA_MeshDeformModifier, diff --git a/source/blender/modifiers/intern/MOD_meshsequencecache.cc b/source/blender/modifiers/intern/MOD_meshsequencecache.cc index 273050eafd8..1c35160d3ef 100644 --- a/source/blender/modifiers/intern/MOD_meshsequencecache.cc +++ b/source/blender/modifiers/intern/MOD_meshsequencecache.cc @@ -392,7 +392,7 @@ static void blendRead(BlendDataReader *UNUSED(reader), ModifierData *md) } ModifierTypeInfo modifierType_MeshSequenceCache = { - /* name */ "MeshSequenceCache", + /* name */ N_("MeshSequenceCache"), /* structName */ "MeshSeqCacheModifierData", /* structSize */ sizeof(MeshSeqCacheModifierData), /* srna */ &RNA_MeshSequenceCacheModifier, diff --git a/source/blender/modifiers/intern/MOD_mirror.c b/source/blender/modifiers/intern/MOD_mirror.c index b6ba8c9e0f9..5f095a72dca 100644 --- a/source/blender/modifiers/intern/MOD_mirror.c +++ b/source/blender/modifiers/intern/MOD_mirror.c @@ -204,7 +204,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Mirror = { - /* name */ "Mirror", + /* name */ N_("Mirror"), /* structName */ "MirrorModifierData", /* structSize */ sizeof(MirrorModifierData), /* srna */ &RNA_MirrorModifier, diff --git a/source/blender/modifiers/intern/MOD_multires.c b/source/blender/modifiers/intern/MOD_multires.c index a4c5ddac5c9..cdad834f9b4 100644 --- a/source/blender/modifiers/intern/MOD_multires.c +++ b/source/blender/modifiers/intern/MOD_multires.c @@ -487,7 +487,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Multires = { - /* name */ "Multires", + /* name */ N_("Multires"), /* structName */ "MultiresModifierData", /* structSize */ sizeof(MultiresModifierData), /* srna */ &RNA_MultiresModifier, diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 885d2f901ec..01e4d5ff6b3 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -1825,7 +1825,7 @@ static void requiredDataMask(Object *UNUSED(ob), } ModifierTypeInfo modifierType_Nodes = { - /* name */ "GeometryNodes", + /* name */ N_("GeometryNodes"), /* structName */ "NodesModifierData", /* structSize */ sizeof(NodesModifierData), /* srna */ &RNA_NodesModifier, diff --git a/source/blender/modifiers/intern/MOD_normal_edit.c b/source/blender/modifiers/intern/MOD_normal_edit.c index c215ac601a1..09bc9546325 100644 --- a/source/blender/modifiers/intern/MOD_normal_edit.c +++ b/source/blender/modifiers/intern/MOD_normal_edit.c @@ -756,7 +756,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_NormalEdit = { - /* name */ "NormalEdit", + /* name */ N_("NormalEdit"), /* structName */ "NormalEditModifierData", /* structSize */ sizeof(NormalEditModifierData), /* srna */ &RNA_NormalEditModifier, diff --git a/source/blender/modifiers/intern/MOD_ocean.c b/source/blender/modifiers/intern/MOD_ocean.c index 75708851030..ea9049200cc 100644 --- a/source/blender/modifiers/intern/MOD_ocean.c +++ b/source/blender/modifiers/intern/MOD_ocean.c @@ -701,7 +701,7 @@ static void blendRead(BlendDataReader *UNUSED(reader), ModifierData *md) } ModifierTypeInfo modifierType_Ocean = { - /* name */ "Ocean", + /* name */ N_("Ocean"), /* structName */ "OceanModifierData", /* structSize */ sizeof(OceanModifierData), /* srna */ &RNA_OceanModifier, diff --git a/source/blender/modifiers/intern/MOD_particleinstance.c b/source/blender/modifiers/intern/MOD_particleinstance.c index 2a8d2454670..5018b2d1030 100644 --- a/source/blender/modifiers/intern/MOD_particleinstance.c +++ b/source/blender/modifiers/intern/MOD_particleinstance.c @@ -644,7 +644,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_ParticleInstance = { - /* name */ "ParticleInstance", + /* name */ N_("ParticleInstance"), /* structName */ "ParticleInstanceModifierData", /* structSize */ sizeof(ParticleInstanceModifierData), /* srna */ &RNA_ParticleInstanceModifier, diff --git a/source/blender/modifiers/intern/MOD_particlesystem.cc b/source/blender/modifiers/intern/MOD_particlesystem.cc index ccbc8f1d835..7f7465947f9 100644 --- a/source/blender/modifiers/intern/MOD_particlesystem.cc +++ b/source/blender/modifiers/intern/MOD_particlesystem.cc @@ -301,7 +301,7 @@ static void blendRead(BlendDataReader *reader, ModifierData *md) } ModifierTypeInfo modifierType_ParticleSystem = { - /* name */ "ParticleSystem", + /* name */ N_("ParticleSystem"), /* structName */ "ParticleSystemModifierData", /* structSize */ sizeof(ParticleSystemModifierData), /* srna */ &RNA_ParticleSystemModifier, diff --git a/source/blender/modifiers/intern/MOD_remesh.c b/source/blender/modifiers/intern/MOD_remesh.c index 288faee247f..f21d536fadf 100644 --- a/source/blender/modifiers/intern/MOD_remesh.c +++ b/source/blender/modifiers/intern/MOD_remesh.c @@ -268,7 +268,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Remesh = { - /* name */ "Remesh", + /* name */ N_("Remesh"), /* structName */ "RemeshModifierData", /* structSize */ sizeof(RemeshModifierData), /* srna */ &RNA_RemeshModifier, diff --git a/source/blender/modifiers/intern/MOD_screw.c b/source/blender/modifiers/intern/MOD_screw.c index 0e22f59c2fb..9588b9acd3b 100644 --- a/source/blender/modifiers/intern/MOD_screw.c +++ b/source/blender/modifiers/intern/MOD_screw.c @@ -1235,7 +1235,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Screw = { - /* name */ "Screw", + /* name */ N_("Screw"), /* structName */ "ScrewModifierData", /* structSize */ sizeof(ScrewModifierData), /* srna */ &RNA_ScrewModifier, diff --git a/source/blender/modifiers/intern/MOD_shapekey.c b/source/blender/modifiers/intern/MOD_shapekey.c index 6953e89dfc2..cc214c1147a 100644 --- a/source/blender/modifiers/intern/MOD_shapekey.c +++ b/source/blender/modifiers/intern/MOD_shapekey.c @@ -9,6 +9,8 @@ #include "BLI_math.h" +#include "BLT_translation.h" + #include "DNA_key_types.h" #include "DNA_mesh_types.h" #include "DNA_object_types.h" @@ -108,7 +110,7 @@ static void deformMatricesEM(ModifierData *UNUSED(md), } ModifierTypeInfo modifierType_ShapeKey = { - /* name */ "ShapeKey", + /* name */ N_("ShapeKey"), /* structName */ "ShapeKeyModifierData", /* structSize */ sizeof(ShapeKeyModifierData), /* srna */ &RNA_Modifier, diff --git a/source/blender/modifiers/intern/MOD_shrinkwrap.c b/source/blender/modifiers/intern/MOD_shrinkwrap.c index 488df3d6f4c..be12dc6639b 100644 --- a/source/blender/modifiers/intern/MOD_shrinkwrap.c +++ b/source/blender/modifiers/intern/MOD_shrinkwrap.c @@ -260,7 +260,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Shrinkwrap = { - /* name */ "Shrinkwrap", + /* name */ N_("Shrinkwrap"), /* structName */ "ShrinkwrapModifierData", /* structSize */ sizeof(ShrinkwrapModifierData), /* srna */ &RNA_ShrinkwrapModifier, diff --git a/source/blender/modifiers/intern/MOD_simpledeform.c b/source/blender/modifiers/intern/MOD_simpledeform.c index e3c7f1c423b..9f1d0cd36c4 100644 --- a/source/blender/modifiers/intern/MOD_simpledeform.c +++ b/source/blender/modifiers/intern/MOD_simpledeform.c @@ -564,7 +564,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_SimpleDeform = { - /* name */ "SimpleDeform", + /* name */ N_("SimpleDeform"), /* structName */ "SimpleDeformModifierData", /* structSize */ sizeof(SimpleDeformModifierData), /* srna */ &RNA_SimpleDeformModifier, diff --git a/source/blender/modifiers/intern/MOD_skin.c b/source/blender/modifiers/intern/MOD_skin.c index 5f238209015..84795cdb2d9 100644 --- a/source/blender/modifiers/intern/MOD_skin.c +++ b/source/blender/modifiers/intern/MOD_skin.c @@ -2069,7 +2069,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Skin = { - /* name */ "Skin", + /* name */ N_("Skin"), /* structName */ "SkinModifierData", /* structSize */ sizeof(SkinModifierData), /* srna */ &RNA_SkinModifier, diff --git a/source/blender/modifiers/intern/MOD_smooth.c b/source/blender/modifiers/intern/MOD_smooth.c index 5439083d9a7..c868c47cb90 100644 --- a/source/blender/modifiers/intern/MOD_smooth.c +++ b/source/blender/modifiers/intern/MOD_smooth.c @@ -253,7 +253,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Smooth = { - /* name */ "Smooth", + /* name */ N_("Smooth"), /* structName */ "SmoothModifierData", /* structSize */ sizeof(SmoothModifierData), /* srna */ &RNA_SmoothModifier, diff --git a/source/blender/modifiers/intern/MOD_softbody.c b/source/blender/modifiers/intern/MOD_softbody.c index d8379cc870a..a49f2609641 100644 --- a/source/blender/modifiers/intern/MOD_softbody.c +++ b/source/blender/modifiers/intern/MOD_softbody.c @@ -86,7 +86,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Softbody = { - /* name */ "Softbody", + /* name */ N_("Softbody"), /* structName */ "SoftbodyModifierData", /* structSize */ sizeof(SoftbodyModifierData), /* srna */ &RNA_SoftBodyModifier, diff --git a/source/blender/modifiers/intern/MOD_solidify.c b/source/blender/modifiers/intern/MOD_solidify.c index 1f0aee7d689..3e2d590c928 100644 --- a/source/blender/modifiers/intern/MOD_solidify.c +++ b/source/blender/modifiers/intern/MOD_solidify.c @@ -241,7 +241,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Solidify = { - /* name */ "Solidify", + /* name */ N_("Solidify"), /* structName */ "SolidifyModifierData", /* structSize */ sizeof(SolidifyModifierData), /* srna */ &RNA_SolidifyModifier, diff --git a/source/blender/modifiers/intern/MOD_subsurf.c b/source/blender/modifiers/intern/MOD_subsurf.c index 23f447f2469..8faf2bdbea2 100644 --- a/source/blender/modifiers/intern/MOD_subsurf.c +++ b/source/blender/modifiers/intern/MOD_subsurf.c @@ -472,7 +472,7 @@ static void blendRead(BlendDataReader *UNUSED(reader), ModifierData *md) } ModifierTypeInfo modifierType_Subsurf = { - /* name */ "Subdivision", + /* name */ N_("Subdivision"), /* structName */ "SubsurfModifierData", /* structSize */ sizeof(SubsurfModifierData), /* srna */ &RNA_SubsurfModifier, diff --git a/source/blender/modifiers/intern/MOD_surface.c b/source/blender/modifiers/intern/MOD_surface.c index af1de77d8c9..8cfe3b35949 100644 --- a/source/blender/modifiers/intern/MOD_surface.c +++ b/source/blender/modifiers/intern/MOD_surface.c @@ -211,7 +211,7 @@ static void blendRead(BlendDataReader *UNUSED(reader), ModifierData *md) } ModifierTypeInfo modifierType_Surface = { - /* name */ "Surface", + /* name */ N_("Surface"), /* structName */ "SurfaceModifierData", /* structSize */ sizeof(SurfaceModifierData), /* srna */ &RNA_SurfaceModifier, diff --git a/source/blender/modifiers/intern/MOD_surfacedeform.c b/source/blender/modifiers/intern/MOD_surfacedeform.c index 20cc9b2392f..d63ef12285b 100644 --- a/source/blender/modifiers/intern/MOD_surfacedeform.c +++ b/source/blender/modifiers/intern/MOD_surfacedeform.c @@ -1741,7 +1741,7 @@ static void blendRead(BlendDataReader *reader, ModifierData *md) } ModifierTypeInfo modifierType_SurfaceDeform = { - /* name */ "SurfaceDeform", + /* name */ N_("SurfaceDeform"), /* structName */ "SurfaceDeformModifierData", /* structSize */ sizeof(SurfaceDeformModifierData), /* srna */ &RNA_SurfaceDeformModifier, diff --git a/source/blender/modifiers/intern/MOD_triangulate.c b/source/blender/modifiers/intern/MOD_triangulate.c index f1e8ef5bf38..d4faf682cdc 100644 --- a/source/blender/modifiers/intern/MOD_triangulate.c +++ b/source/blender/modifiers/intern/MOD_triangulate.c @@ -139,7 +139,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Triangulate = { - /* name */ "Triangulate", + /* name */ N_("Triangulate"), /* structName */ "TriangulateModifierData", /* structSize */ sizeof(TriangulateModifierData), /* srna */ &RNA_TriangulateModifier, diff --git a/source/blender/modifiers/intern/MOD_uvproject.c b/source/blender/modifiers/intern/MOD_uvproject.c index d4d7ecef283..0474d3e47e6 100644 --- a/source/blender/modifiers/intern/MOD_uvproject.c +++ b/source/blender/modifiers/intern/MOD_uvproject.c @@ -349,7 +349,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_UVProject = { - /* name */ "UVProject", + /* name */ N_("UVProject"), /* structName */ "UVProjectModifierData", /* structSize */ sizeof(UVProjectModifierData), /* srna */ &RNA_UVProjectModifier, diff --git a/source/blender/modifiers/intern/MOD_uvwarp.c b/source/blender/modifiers/intern/MOD_uvwarp.c index a15efdaa381..c33b25c38e3 100644 --- a/source/blender/modifiers/intern/MOD_uvwarp.c +++ b/source/blender/modifiers/intern/MOD_uvwarp.c @@ -308,7 +308,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_UVWarp = { - /* name */ "UVWarp", + /* name */ N_("UVWarp"), /* structName */ "UVWarpModifierData", /* structSize */ sizeof(UVWarpModifierData), /* srna */ &RNA_UVWarpModifier, diff --git a/source/blender/modifiers/intern/MOD_volume_displace.cc b/source/blender/modifiers/intern/MOD_volume_displace.cc index 059cfdbdd4e..d9b94d79348 100644 --- a/source/blender/modifiers/intern/MOD_volume_displace.cc +++ b/source/blender/modifiers/intern/MOD_volume_displace.cc @@ -12,6 +12,8 @@ #include "BKE_texture.h" #include "BKE_volume.h" +#include "BLT_translation.h" + #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "DNA_object_types.h" @@ -307,7 +309,7 @@ static void modifyGeometrySet(ModifierData *md, } ModifierTypeInfo modifierType_VolumeDisplace = { - /* name */ "Volume Displace", + /* name */ N_("Volume Displace"), /* structName */ "VolumeDisplaceModifierData", /* structSize */ sizeof(VolumeDisplaceModifierData), /* srna */ &RNA_VolumeDisplaceModifier, diff --git a/source/blender/modifiers/intern/MOD_volume_to_mesh.cc b/source/blender/modifiers/intern/MOD_volume_to_mesh.cc index d33687e4d92..3292f73137a 100644 --- a/source/blender/modifiers/intern/MOD_volume_to_mesh.cc +++ b/source/blender/modifiers/intern/MOD_volume_to_mesh.cc @@ -12,6 +12,8 @@ #include "BKE_volume.h" #include "BKE_volume_to_mesh.hh" +#include "BLT_translation.h" + #include "MOD_modifiertypes.h" #include "MOD_ui_common.h" @@ -193,7 +195,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * } ModifierTypeInfo modifierType_VolumeToMesh = { - /* name */ "Volume to Mesh", + /* name */ N_("Volume to Mesh"), /* structName */ "VolumeToMeshModifierData", /* structSize */ sizeof(VolumeToMeshModifierData), /* srna */ &RNA_VolumeToMeshModifier, diff --git a/source/blender/modifiers/intern/MOD_warp.c b/source/blender/modifiers/intern/MOD_warp.c index 9693cf0c0f2..afdc230a877 100644 --- a/source/blender/modifiers/intern/MOD_warp.c +++ b/source/blender/modifiers/intern/MOD_warp.c @@ -510,7 +510,7 @@ static void blendRead(BlendDataReader *reader, ModifierData *md) } ModifierTypeInfo modifierType_Warp = { - /* name */ "Warp", + /* name */ N_("Warp"), /* structName */ "WarpModifierData", /* structSize */ sizeof(WarpModifierData), /* srna */ &RNA_WarpModifier, diff --git a/source/blender/modifiers/intern/MOD_wave.c b/source/blender/modifiers/intern/MOD_wave.c index 4073f028db5..b92e3a0fa9d 100644 --- a/source/blender/modifiers/intern/MOD_wave.c +++ b/source/blender/modifiers/intern/MOD_wave.c @@ -372,7 +372,7 @@ static void panel_draw(const bContext *UNUSED(C), Panel *panel) uiItemR(sub, ptr, "use_normal_z", UI_ITEM_R_TOGGLE, "Z", ICON_NONE); col = uiLayoutColumn(layout, false); - uiItemR(col, ptr, "falloff_radius", 0, "Falloff", ICON_NONE); + uiItemR(col, ptr, "falloff_radius", 0, IFACE_("Falloff"), ICON_NONE); uiItemR(col, ptr, "height", UI_ITEM_R_SLIDER, NULL, ICON_NONE); uiItemR(col, ptr, "width", UI_ITEM_R_SLIDER, NULL, ICON_NONE); uiItemR(col, ptr, "narrowness", UI_ITEM_R_SLIDER, NULL, ICON_NONE); @@ -394,7 +394,7 @@ static void position_panel_draw(const bContext *UNUSED(C), Panel *panel) uiItemR(layout, ptr, "start_position_object", 0, IFACE_("Object"), ICON_NONE); col = uiLayoutColumn(layout, true); - uiItemR(col, ptr, "start_position_x", 0, "Start Position X", ICON_NONE); + uiItemR(col, ptr, "start_position_x", 0, IFACE_("Start Position X"), ICON_NONE); uiItemR(col, ptr, "start_position_y", 0, "Y", ICON_NONE); } @@ -408,9 +408,9 @@ static void time_panel_draw(const bContext *UNUSED(C), Panel *panel) uiLayoutSetPropSep(layout, true); col = uiLayoutColumn(layout, false); - uiItemR(col, ptr, "time_offset", 0, "Offset", ICON_NONE); - uiItemR(col, ptr, "lifetime", 0, "Life", ICON_NONE); - uiItemR(col, ptr, "damping_time", 0, "Damping", ICON_NONE); + uiItemR(col, ptr, "time_offset", 0, IFACE_("Offset"), ICON_NONE); + uiItemR(col, ptr, "lifetime", 0, IFACE_("Life"), ICON_NONE); + uiItemR(col, ptr, "damping_time", 0, IFACE_("Damping"), ICON_NONE); uiItemR(col, ptr, "speed", UI_ITEM_R_SLIDER, NULL, ICON_NONE); } @@ -462,7 +462,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Wave = { - /* name */ "Wave", + /* name */ N_("Wave"), /* structName */ "WaveModifierData", /* structSize */ sizeof(WaveModifierData), /* srna */ &RNA_WaveModifier, diff --git a/source/blender/modifiers/intern/MOD_weighted_normal.c b/source/blender/modifiers/intern/MOD_weighted_normal.c index d436acb8ad5..af992c00097 100644 --- a/source/blender/modifiers/intern/MOD_weighted_normal.c +++ b/source/blender/modifiers/intern/MOD_weighted_normal.c @@ -726,7 +726,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_WeightedNormal = { - /* name */ "WeightedNormal", + /* name */ N_("WeightedNormal"), /* structName */ "WeightedNormalModifierData", /* structSize */ sizeof(WeightedNormalModifierData), /* srna */ &RNA_WeightedNormalModifier, diff --git a/source/blender/modifiers/intern/MOD_weightvgedit.c b/source/blender/modifiers/intern/MOD_weightvgedit.c index e1b43157adb..f6e0cd9303d 100644 --- a/source/blender/modifiers/intern/MOD_weightvgedit.c +++ b/source/blender/modifiers/intern/MOD_weightvgedit.c @@ -397,7 +397,7 @@ static void blendRead(BlendDataReader *reader, ModifierData *md) } ModifierTypeInfo modifierType_WeightVGEdit = { - /* name */ "VertexWeightEdit", + /* name */ N_("VertexWeightEdit"), /* structName */ "WeightVGEditModifierData", /* structSize */ sizeof(WeightVGEditModifierData), /* srna */ &RNA_VertexWeightEditModifier, diff --git a/source/blender/modifiers/intern/MOD_weightvgmix.c b/source/blender/modifiers/intern/MOD_weightvgmix.c index b827d41e80a..49088d42a5e 100644 --- a/source/blender/modifiers/intern/MOD_weightvgmix.c +++ b/source/blender/modifiers/intern/MOD_weightvgmix.c @@ -496,7 +496,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_WeightVGMix = { - /* name */ "VertexWeightMix", + /* name */ N_("VertexWeightMix"), /* structName */ "WeightVGMixModifierData", /* structSize */ sizeof(WeightVGMixModifierData), /* srna */ &RNA_VertexWeightMixModifier, diff --git a/source/blender/modifiers/intern/MOD_weightvgproximity.c b/source/blender/modifiers/intern/MOD_weightvgproximity.c index 1bea5b93c97..d798bf88bbc 100644 --- a/source/blender/modifiers/intern/MOD_weightvgproximity.c +++ b/source/blender/modifiers/intern/MOD_weightvgproximity.c @@ -737,7 +737,7 @@ static void blendRead(BlendDataReader *reader, ModifierData *md) } ModifierTypeInfo modifierType_WeightVGProximity = { - /* name */ "VertexWeightProximity", + /* name */ N_("VertexWeightProximity"), /* structName */ "WeightVGProximityModifierData", /* structSize */ sizeof(WeightVGProximityModifierData), /* srna */ &RNA_VertexWeightProximityModifier, diff --git a/source/blender/modifiers/intern/MOD_weld.cc b/source/blender/modifiers/intern/MOD_weld.cc index 93d4b56176c..19b0bf62fea 100644 --- a/source/blender/modifiers/intern/MOD_weld.cc +++ b/source/blender/modifiers/intern/MOD_weld.cc @@ -185,7 +185,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Weld = { - /* name */ "Weld", + /* name */ N_("Weld"), /* structName */ "WeldModifierData", /* structSize */ sizeof(WeldModifierData), /* srna */ &RNA_WeldModifier, diff --git a/source/blender/modifiers/intern/MOD_wireframe.c b/source/blender/modifiers/intern/MOD_wireframe.c index b657ea87244..5799da5d156 100644 --- a/source/blender/modifiers/intern/MOD_wireframe.c +++ b/source/blender/modifiers/intern/MOD_wireframe.c @@ -165,7 +165,7 @@ static void panelRegister(ARegionType *region_type) } ModifierTypeInfo modifierType_Wireframe = { - /* name */ "Wireframe", + /* name */ N_("Wireframe"), /* structName */ "WireframeModifierData", /* structSize */ sizeof(WireframeModifierData), /* srna */ &RNA_WireframeModifier, -- cgit v1.2.3 From 00dc7477022acdd969e4d709a235c0be819efa6c Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 15 Jul 2022 22:14:04 +1000 Subject: Fix T99706: Crash rendering with headless builds When rendering with headless builds, show an error instead of crashing. Previously GPU_backend_init was called indirectly from DRW_opengl_context_create, a new function is now called from the window manager (GPU_backend_init_once), so it's possible to check if the GPU has a back-end. This also disables the `bgl` Python module when building WITH_HEADLESS. Reviewed By: fclem Ref D15463 --- CMakeLists.txt | 4 ++++ source/blender/gpu/CMakeLists.txt | 2 +- source/blender/gpu/GPU_context.h | 1 + source/blender/gpu/intern/gpu_context.cc | 17 +++++++++++++---- source/blender/python/generic/CMakeLists.txt | 10 ++++++++-- source/blender/python/intern/bpy_interface.c | 2 ++ source/blender/render/intern/engine.c | 12 ++++++++++++ source/blender/windowmanager/intern/wm_init_exit.c | 4 ++++ 8 files changed, 45 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 33064864be6..c998919622e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -574,6 +574,10 @@ mark_as_advanced( WITH_GPU_BUILDTIME_SHADER_BUILDER ) +if(WITH_HEADLESS) + set(WITH_OPENGL OFF) +endif() + # Metal if (APPLE) diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index 0cb92e02515..774f2a0f312 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -5,7 +5,7 @@ # to more easily highlight code-paths in other libraries that need to be refactored, # bf_gpu is allowed to have opengl regardless of this option. -if(NOT WITH_OPENGL AND NOT WITH_METAL_BACKEND) +if(NOT WITH_OPENGL AND NOT WITH_METAL_BACKEND AND NOT WITH_HEADLESS) add_definitions(-DWITH_OPENGL) endif() diff --git a/source/blender/gpu/GPU_context.h b/source/blender/gpu/GPU_context.h index 1fcd94c48fc..b04a4422baa 100644 --- a/source/blender/gpu/GPU_context.h +++ b/source/blender/gpu/GPU_context.h @@ -17,6 +17,7 @@ extern "C" { #endif +bool GPU_backend_init_once(void); void GPU_backend_init(eGPUBackendType backend); void GPU_backend_exit(void); bool GPU_backend_supported(eGPUBackendType type); diff --git a/source/blender/gpu/intern/gpu_context.cc b/source/blender/gpu/intern/gpu_context.cc index 4a0a9ecc7f6..d3b208dc6f6 100644 --- a/source/blender/gpu/intern/gpu_context.cc +++ b/source/blender/gpu/intern/gpu_context.cc @@ -85,10 +85,7 @@ Context *Context::get() GPUContext *GPU_context_create(void *ghost_window) { - if (GPUBackend::get() == nullptr) { - /* TODO: move where it make sense. */ - GPU_backend_init(GPU_BACKEND_OPENGL); - } + GPU_backend_init_once(); Context *ctx = GPUBackend::get()->context_alloc(ghost_window); @@ -214,6 +211,18 @@ bool GPU_backend_supported(eGPUBackendType type) } } +bool GPU_backend_init_once() +{ + if (GPUBackend::get() == nullptr) { + if (!GPU_backend_supported(GPU_BACKEND_OPENGL)) { + return false; + } + /* TODO: move where it make sense. */ + GPU_backend_init(GPU_BACKEND_OPENGL); + } + return true; +} + void GPU_backend_init(eGPUBackendType backend_type) { BLI_assert(g_backend == nullptr); diff --git a/source/blender/python/generic/CMakeLists.txt b/source/blender/python/generic/CMakeLists.txt index 69bcfdfae4e..dfca528e758 100644 --- a/source/blender/python/generic/CMakeLists.txt +++ b/source/blender/python/generic/CMakeLists.txt @@ -17,7 +17,6 @@ set(INC_SYS ) set(SRC - bgl.c bl_math_py_api.c blf_py_api.c bpy_threads.c @@ -27,7 +26,6 @@ set(SRC py_capi_rna.c py_capi_utils.c - bgl.h bl_math_py_api.h blf_py_api.h idprop_py_api.h @@ -40,6 +38,14 @@ set(SRC python_utildefines.h ) +if(WITH_OPENGL) + list(APPEND SRC + bgl.c + + bgl.h + ) +endif() + set(LIB ${GLEW_LIBRARY} ${PYTHON_LINKFLAGS} diff --git a/source/blender/python/intern/bpy_interface.c b/source/blender/python/intern/bpy_interface.c index 939fa475344..08dd5fe9cfc 100644 --- a/source/blender/python/intern/bpy_interface.c +++ b/source/blender/python/intern/bpy_interface.c @@ -259,7 +259,9 @@ static struct _inittab bpy_internal_modules[] = { {"mathutils.kdtree", PyInit_mathutils_kdtree}, #endif {"_bpy_path", BPyInit__bpy_path}, +#ifdef WITH_OPENGL {"bgl", BPyInit_bgl}, +#endif {"blf", BPyInit_blf}, {"bl_math", BPyInit_bl_math}, {"imbuf", BPyInit_imbuf}, diff --git a/source/blender/render/intern/engine.c b/source/blender/render/intern/engine.c index 8a4b4c2a70d..113af393706 100644 --- a/source/blender/render/intern/engine.c +++ b/source/blender/render/intern/engine.c @@ -46,6 +46,8 @@ #include "DRW_engine.h" +#include "GPU_context.h" + #include "pipeline.h" #include "render_result.h" #include "render_types.h" @@ -950,6 +952,16 @@ bool RE_engine_render(Render *re, bool do_all) re->draw_lock(re->dlh, true); } + if ((type->flag & RE_USE_GPU_CONTEXT) && (GPU_backend_get_type() == GPU_BACKEND_NONE)) { + /* Clear UI drawing locks. */ + if (re->draw_lock) { + re->draw_lock(re->dlh, false); + } + BKE_report(re->reports, RPT_ERROR, "Can not initialize the GPU"); + G.is_break = true; + return true; + } + /* update animation here so any render layer animation is applied before * creating the render result */ if ((re->r.scemode & (R_NO_FRAME_UPDATE | R_BUTS_PREVIEW)) == 0) { diff --git a/source/blender/windowmanager/intern/wm_init_exit.c b/source/blender/windowmanager/intern/wm_init_exit.c index 252cfc6e143..b9bb1d88819 100644 --- a/source/blender/windowmanager/intern/wm_init_exit.c +++ b/source/blender/windowmanager/intern/wm_init_exit.c @@ -169,6 +169,10 @@ void WM_init_opengl(void) wm_ghost_init(NULL); } + if (!GPU_backend_init_once()) { + return; + } + /* Needs to be first to have an OpenGL context bound. */ DRW_opengl_context_create(); -- cgit v1.2.3 From 011d3c75a74532920bb90fb1d45ddf2dc836ba8c Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 15 Jul 2022 15:05:56 +0200 Subject: Cleanup: compiler warning --- intern/cycles/device/device.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intern/cycles/device/device.h b/intern/cycles/device/device.h index e7916ec3a52..cdb13ca0a97 100644 --- a/intern/cycles/device/device.h +++ b/intern/cycles/device/device.h @@ -188,7 +188,7 @@ class Device { } /* Called after kernel texture setup, and prior to integrator state setup. */ - virtual void optimize_for_scene(Scene *scene) + virtual void optimize_for_scene(Scene * /*scene*/) { } -- cgit v1.2.3 From 82f65d8971ea2def50ab6cd6031793207cc45168 Mon Sep 17 00:00:00 2001 From: Richard Antalik Date: Fri, 15 Jul 2022 15:46:50 +0200 Subject: Cleanup: VSE waveform drawing No functional changes. --- .../editors/space_sequencer/sequencer_draw.c | 522 ++++++++++----------- 1 file changed, 240 insertions(+), 282 deletions(-) diff --git a/source/blender/editors/space_sequencer/sequencer_draw.c b/source/blender/editors/space_sequencer/sequencer_draw.c index aa5681306a4..b1e13850bf2 100644 --- a/source/blender/editors/space_sequencer/sequencer_draw.c +++ b/source/blender/editors/space_sequencer/sequencer_draw.c @@ -241,329 +241,296 @@ typedef struct WaveVizData { float pos[2]; float rms_pos; bool clip; - bool end; + bool draw_line; /* Draw triangle otherwise. */ + bool final_sample; /* There are no more samples. */ } WaveVizData; -static int get_section_len(WaveVizData *start, WaveVizData *end) +static bool seq_draw_waveforms_poll(const bContext *C, SpaceSeq *sseq, Sequence *seq) { - int len = 0; - while (start != end) { - len++; - if (start->end) { - return len; - } - start++; - } - return len; -} + const bool strip_is_valid = seq->type == SEQ_TYPE_SOUND_RAM && seq->sound != NULL; + const bool overlays_enabled = (sseq->flag & SEQ_SHOW_OVERLAY) != 0; + const bool ovelay_option = ((sseq->timeline_overlay.flag & SEQ_TIMELINE_ALL_WAVEFORMS) != 0 || + (seq->flag & SEQ_AUDIO_DRAW_WAVEFORM)); -static void draw_waveform(WaveVizData *iter, WaveVizData *end, GPUPrimType prim_type, bool use_rms) -{ - int strip_len = get_section_len(iter, end); - if (strip_len > 1) { - GPU_blend(GPU_BLEND_ALPHA); - GPUVertFormat *format = immVertexFormat(); - uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + if ((sseq->timeline_overlay.flag & SEQ_TIMELINE_NO_WAVEFORMS) != 0) { + return false; + } - immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); - immBegin(prim_type, strip_len); + if (strip_is_valid && overlays_enabled && ovelay_option) { + return true; + } - while (iter != end) { - if (iter->clip) { - immAttr4f(col, 1.0f, 0.0f, 0.0f, 0.5f); - } - else if (use_rms) { - immAttr4f(col, 1.0f, 1.0f, 1.0f, 0.8f); - } - else { - immAttr4f(col, 1.0f, 1.0f, 1.0f, 0.5f); - } + return false; +} - if (use_rms) { - immVertex2f(pos, iter->pos[0], iter->rms_pos); - } - else { - immVertex2f(pos, iter->pos[0], iter->pos[1]); - } +static void waveform_job_start_if_needed(const bContext *C, Sequence *seq) +{ + bSound *sound = seq->sound; - if (iter->end) { - /* End of line. */ - iter++; - strip_len = get_section_len(iter, end); - if (strip_len != 0) { - immEnd(); - immUnbindProgram(); - immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); - immBegin(prim_type, strip_len); - } - } - else { - iter++; - } + BLI_spin_lock(sound->spinlock); + if (!sound->waveform) { + /* Load the waveform data if it hasn't been loaded and cached already. */ + if (!(sound->tags & SOUND_TAGS_WAVEFORM_LOADING)) { + /* Prevent sounds from reloading. */ + sound->tags |= SOUND_TAGS_WAVEFORM_LOADING; + BLI_spin_unlock(sound->spinlock); + sequencer_preview_add_sound(C, seq); + } + else { + BLI_spin_unlock(sound->spinlock); } - immEnd(); - immUnbindProgram(); - - GPU_blend(GPU_BLEND_NONE); } + BLI_spin_unlock(sound->spinlock); } -static float clamp_frame_coord_to_pixel(float frame_coord, - float pixel_frac, - float frames_per_pixel) +static size_t get_vertex_count(WaveVizData *waveform_data) { - float cur_pixel = (frame_coord / frames_per_pixel); - float new_pixel = (int)(frame_coord / frames_per_pixel) + pixel_frac; - if (cur_pixel > new_pixel) { - new_pixel += 1.0f; + bool draw_line = waveform_data->draw_line; + size_t length = 0; + + while (waveform_data->draw_line == draw_line && !waveform_data->final_sample) { + waveform_data++; + length++; } - return new_pixel * frames_per_pixel; + + return length; } -/** - * \param x1, x2, y1, y2: The starting and end X value to draw the wave, same for y1 and y2. - * \param frames_per_pixel: The amount of pixels a whole frame takes up (x-axis direction). - */ -static void draw_seq_waveform_overlay(View2D *v2d, - const bContext *C, - SpaceSeq *sseq, - Scene *scene, - Sequence *seq, - float x1, - float y1, - float x2, - float y2, - float frames_per_pixel) +static size_t draw_waveform_segment(WaveVizData *waveform_data, bool use_rms) { - if (seq->sound && ((sseq->timeline_overlay.flag & SEQ_TIMELINE_ALL_WAVEFORMS) || - (seq->flag & SEQ_AUDIO_DRAW_WAVEFORM))) { - /* Make sure that the start drawing position is aligned to the pixels on the screen to avoid - * flickering when moving around the strip. - * To do this we figure out the fractional offset in pixel space by checking where the - * window starts. - * We then append this pixel offset to our strip start coordinate to ensure we are aligned to - * the screen pixel grid. */ - float pixel_frac = v2d->cur.xmin / frames_per_pixel - floor(v2d->cur.xmin / frames_per_pixel); - float x1_adj = clamp_frame_coord_to_pixel(x1, pixel_frac, frames_per_pixel); - - /* Offset x1 and x2 values, to match view min/max, if strip is out of bounds. */ - float x1_offset = max_ff(v2d->cur.xmin, x1_adj); - float x2_offset = min_ff(v2d->cur.xmax, x2); - - /* Calculate how long the strip that is in view is in pixels. */ - int pix_strip_len = round((x2_offset - x1_offset) / frames_per_pixel); - - if (pix_strip_len < 2) { - return; - } + size_t vertices_done = 0; + size_t vertex_count = get_vertex_count(waveform_data); - bSound *sound = seq->sound; + /* Not enough data to draw. */ + if (vertex_count <= 2) { + return vertex_count; + } - BLI_spin_lock(sound->spinlock); - if (!sound->waveform) { - /* Load the waveform data if it hasn't been loaded and cached already. */ - if (!(sound->tags & SOUND_TAGS_WAVEFORM_LOADING)) { - /* Prevent sounds from reloading. */ - sound->tags |= SOUND_TAGS_WAVEFORM_LOADING; - BLI_spin_unlock(sound->spinlock); - sequencer_preview_add_sound(C, seq); - } - else { - BLI_spin_unlock(sound->spinlock); - } - return; /* Nothing to draw. */ - } - BLI_spin_unlock(sound->spinlock); + GPU_blend(GPU_BLEND_ALPHA); + GPUVertFormat *format = immVertexFormat(); + GPUPrimType prim_type = waveform_data->draw_line ? GPU_PRIM_LINE_STRIP : GPU_PRIM_TRI_STRIP; + uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); + immBegin(prim_type, vertex_count); - SoundWaveform *waveform = sound->waveform; + while (vertices_done < vertex_count && !waveform_data->final_sample) { + /* Color. */ + if (waveform_data->clip) { + immAttr4f(col, 1.0f, 0.0f, 0.0f, 0.5f); + } + else if (use_rms) { + immAttr4f(col, 1.0f, 1.0f, 1.0f, 0.8f); + } + else { + immAttr4f(col, 1.0f, 1.0f, 1.0f, 0.5f); + } - /* Waveform could not be built. */ - if (waveform->length == 0) { - return; + /* Vertices. */ + if (use_rms) { + immVertex2f(pos, waveform_data->pos[0], waveform_data->rms_pos); + } + else { + immVertex2f(pos, waveform_data->pos[0], waveform_data->pos[1]); } - /* F-Curve lookup is quite expensive, so do this after precondition. */ - FCurve *fcu = id_data_find_fcurve(&scene->id, seq, &RNA_Sequence, "volume", 0, NULL); + vertices_done++; + waveform_data++; + } - WaveVizData *tri_strip_arr = MEM_callocN(sizeof(*tri_strip_arr) * pix_strip_len * 2, - "tri_strip"); - WaveVizData *line_strip_arr = MEM_callocN(sizeof(*line_strip_arr) * pix_strip_len, - "line_strip"); + immEnd(); + immUnbindProgram(); - WaveVizData *tri_strip_iter = tri_strip_arr; - WaveVizData *line_strip_iter = line_strip_arr; + GPU_blend(GPU_BLEND_NONE); - /* The y coordinate for the middle of the strip. */ - float y_mid = (y1 + y2) / 2.0f; - /* The length from the middle of the strip to the top/bottom. */ - float y_scale = (y2 - y1) / 2.0f; - float volume = seq->volume; + return vertices_done; +} - /* Value to keep track if the previous item to be drawn was a line strip. */ - int8_t was_line_strip = -1; /* -1 == no previous value. */ +static void draw_waveform(WaveVizData *waveform_data, size_t wave_data_len) +{ + size_t items_done = 0; + while (items_done < wave_data_len) { + if (!waveform_data[items_done].draw_line) { /* Draw RMS. */ + draw_waveform_segment(&waveform_data[items_done], true); + } + items_done += draw_waveform_segment(&waveform_data[items_done], false); + } +} - float samples_per_frame = SOUND_WAVE_SAMPLES_PER_SECOND / FPS; +static float align_frame_with_pixel(const View2D *v2d, float frame_coord, float frames_per_pixel) +{ + return round_fl_to_int(frame_coord / frames_per_pixel) * frames_per_pixel; +} - /* How many samples do we have for each pixel? */ - float samples_per_pix = samples_per_frame * frames_per_pixel; +static void write_waveform_data(WaveVizData *waveform_data, + const vec2f pos, + const float rms, + const bool is_clipping, + const bool draw_line) +{ + waveform_data->pos[0] = pos.x; + waveform_data->pos[1] = pos.y; + waveform_data->clip = is_clipping; + waveform_data->rms_pos = rms; + waveform_data->draw_line = draw_line; +} - float strip_start_offset = seq->startofs + seq->anim_startofs; - float start_sample = 0; +static size_t waveform_append_sample(WaveVizData *waveform_data, + vec2f pos, + const float value_min, + const float value_max, + const float y_mid, + const float y_scale, + const float rms, + const bool is_clipping, + const bool is_line_strip) +{ + size_t data_written = 0; + pos.y = y_mid + value_min * y_scale; + float rms_value = y_mid + max_ff(-rms, value_min) * y_scale; + write_waveform_data(&waveform_data[0], pos, rms_value, is_clipping, is_line_strip); + data_written++; + + /* Use `value_max` as second vertex for triangle drawing. */ + if (!is_line_strip) { + pos.y = y_mid + value_max * y_scale; + rms_value = y_mid + min_ff(rms, value_max) * y_scale; + write_waveform_data(&waveform_data[1], pos, rms_value, is_clipping, is_line_strip); + data_written++; + } + return data_written; +} - if (strip_start_offset != 0) { - /* If start offset is not zero, we need to make sure that we pick the same start sample as if - * we simply scrolled the start of the strip off-screen. Otherwise we will get flickering - * when changing start offset as the pixel alignment will not be the same for the drawn - * samples. */ - strip_start_offset = clamp_frame_coord_to_pixel( - x1 - strip_start_offset, pixel_frac, frames_per_pixel); - start_sample = fabsf(strip_start_offset - x1_adj) * samples_per_frame; - } +/** + * \param x1, x2, y1, y2: The starting and end X value to draw the wave, same for y1 and y2. + * \param frames_per_pixel: The amount of pixels a whole frame takes up (x-axis direction). + */ +static void draw_seq_waveform_overlay( + const bContext *C, ARegion *region, Sequence *seq, float x1, float y1, float x2, float y2) +{ + const View2D *v2d = ®ion->v2d; + Scene *scene = CTX_data_scene(C); - start_sample += seq->sound->offset_time * SOUND_WAVE_SAMPLES_PER_SECOND; - /* If we scrolled the start off-screen, then the start sample should be at the first visible - * sample. */ - start_sample += (x1_offset - x1_adj) * samples_per_frame; + const float frames_per_pixel = BLI_rctf_size_x(®ion->v2d.cur) / region->winx; + const float samples_per_frame = SOUND_WAVE_SAMPLES_PER_SECOND / FPS; + float samples_per_pixel = samples_per_frame * frames_per_pixel; - for (int i = 0; i < pix_strip_len; i++) { - float sample_offset = start_sample + i * samples_per_pix; - int p = sample_offset; + /* Align strip start with nearest pixel to prevent waveform flickering. */ + const float x1_aligned = align_frame_with_pixel(v2d, x1, frames_per_pixel); + /* Offset x1 and x2 values, to match view min/max, if strip is out of bounds. */ + const float frame_start = max_ff(v2d->cur.xmin, x1_aligned); + const float frame_end = min_ff(v2d->cur.xmax, x2); + const int pixels_to_draw = round_fl_to_int((frame_end - frame_start) / frames_per_pixel); - if (p < 0) { - continue; - } + if (pixels_to_draw < 2) { + return; /* Not much to draw, exit before running job. */ + } - if (p >= waveform->length) { - break; - } + waveform_job_start_if_needed(C, seq); - float value_min = waveform->data[p * 3]; - float value_max = waveform->data[p * 3 + 1]; - float rms = waveform->data[p * 3 + 2]; - - if (p + 1 < waveform->length) { - /* Use simple linear interpolation. */ - float f = sample_offset - p; - value_min = (1.0f - f) * value_min + f * waveform->data[p * 3 + 3]; - value_max = (1.0f - f) * value_max + f * waveform->data[p * 3 + 4]; - rms = (1.0f - f) * rms + f * waveform->data[p * 3 + 5]; - if (samples_per_pix > 1.0f) { - /* We need to sum up the values we skip over until the next step. */ - float next_pos = sample_offset + samples_per_pix; - int end_idx = next_pos; - - for (int j = p + 1; (j < waveform->length) && (j < end_idx); j++) { - value_min = min_ff(value_min, waveform->data[j * 3]); - value_max = max_ff(value_max, waveform->data[j * 3 + 1]); - rms = max_ff(rms, waveform->data[j * 3 + 2]); - } - } - } + SoundWaveform *waveform = seq->sound->waveform; + if (waveform == NULL || waveform->length == 0) { + return; /* Waveform was not built. */ + } - if (fcu && !BKE_fcurve_is_empty(fcu)) { - float evaltime = x1_offset + (i * frames_per_pixel); - volume = evaluate_fcurve(fcu, evaltime); - CLAMP_MIN(volume, 0.0f); - } + /* F-Curve lookup is quite expensive, so do this after precondition. */ + FCurve *fcu = id_data_find_fcurve(&scene->id, seq, &RNA_Sequence, "volume", 0, NULL); + WaveVizData *waveform_data = MEM_callocN(sizeof(WaveVizData) * pixels_to_draw * 3, __func__); + size_t wave_data_len = 0; - value_min *= volume; - value_max *= volume; - rms *= volume; + /* Offset must be also aligned, otherwise waveform flickers when moving left handle. */ + const float strip_offset = align_frame_with_pixel( + v2d, seq->startofs + seq->anim_startofs, frames_per_pixel); + float start_sample = strip_offset * samples_per_frame; + start_sample += seq->sound->offset_time * SOUND_WAVE_SAMPLES_PER_SECOND; + /* Add off-screen part of strip to offset. */ + start_sample += (frame_start - x1_aligned) * samples_per_frame; - bool clipping = false; + for (int i = 0; i < pixels_to_draw; i++) { + float sample = start_sample + i * samples_per_pixel; + int sample_index = round_fl_to_int(sample); - if (value_max > 1 || value_min < -1) { - clipping = true; + if (sample_index < 0) { + continue; + } - CLAMP_MAX(value_max, 1.0f); - CLAMP_MIN(value_min, -1.0f); - } + if (sample_index >= waveform->length) { + break; + } - bool is_line_strip = (value_max - value_min < 0.05f); - - if (!ELEM(was_line_strip, -1, is_line_strip)) { - /* If the previously added strip type isn't the same as the current one, - * add transition areas so they transition smoothly between each other. */ - if (is_line_strip) { - /* This will be a line strip, end the tri strip. */ - tri_strip_iter->pos[0] = x1_offset + i * frames_per_pixel; - tri_strip_iter->pos[1] = y_mid + value_min * y_scale; - tri_strip_iter->clip = clipping; - tri_strip_iter->rms_pos = tri_strip_iter->pos[1]; - tri_strip_iter->end = true; - - /* End of section. */ - tri_strip_iter++; - - /* Check if we are at the end. - * If so, skip one point line. */ - if (i + 1 == pix_strip_len) { - continue; - } - } - else { - /* This will be a tri strip. */ - line_strip_iter--; - tri_strip_iter->pos[0] = line_strip_iter->pos[0]; - tri_strip_iter->pos[1] = line_strip_iter->pos[1]; - tri_strip_iter->clip = line_strip_iter->clip; - tri_strip_iter->rms_pos = line_strip_iter->pos[1]; - tri_strip_iter++; - - /* Check if line had only one point. */ - line_strip_iter--; - if (line_strip_iter < line_strip_arr || line_strip_iter->end) { - /* Only one point, skip it. */ - line_strip_iter++; - } - else { - /* End of section. */ - line_strip_iter++; - line_strip_iter->end = true; - line_strip_iter++; - } + float value_min = waveform->data[sample_index * 3]; + float value_max = waveform->data[sample_index * 3 + 1]; + float rms = waveform->data[sample_index * 3 + 2]; + + if (sample_index + 1 < waveform->length) { + /* Use simple linear interpolation. */ + float f = sample - sample_index; + value_min = (1.0f - f) * value_min + f * waveform->data[sample_index * 3 + 3]; + value_max = (1.0f - f) * value_max + f * waveform->data[sample_index * 3 + 4]; + rms = (1.0f - f) * rms + f * waveform->data[sample_index * 3 + 5]; + if (samples_per_pixel > 1.0f) { + /* We need to sum up the values we skip over until the next step. */ + float next_pos = sample + samples_per_pixel; + int end_idx = next_pos; + + for (int j = sample_index + 1; (j < waveform->length) && (j < end_idx); j++) { + value_min = min_ff(value_min, waveform->data[j * 3]); + value_max = max_ff(value_max, waveform->data[j * 3 + 1]); + rms = max_ff(rms, waveform->data[j * 3 + 2]); } } + } - was_line_strip = is_line_strip; - - if (is_line_strip) { - line_strip_iter->pos[0] = x1_offset + i * frames_per_pixel; - line_strip_iter->pos[1] = y_mid + value_min * y_scale; - line_strip_iter->clip = clipping; - line_strip_iter++; - } - else { - tri_strip_iter->pos[0] = x1_offset + i * frames_per_pixel; - tri_strip_iter->pos[1] = y_mid + value_min * y_scale; - tri_strip_iter->clip = clipping; - tri_strip_iter->rms_pos = y_mid + max_ff(-rms, value_min) * y_scale; - tri_strip_iter++; - - tri_strip_iter->pos[0] = x1_offset + i * frames_per_pixel; - tri_strip_iter->pos[1] = y_mid + value_max * y_scale; - tri_strip_iter->clip = clipping; - tri_strip_iter->rms_pos = y_mid + min_ff(rms, value_max) * y_scale; - tri_strip_iter++; - } + float volume = seq->volume; + if (fcu && !BKE_fcurve_is_empty(fcu)) { + float evaltime = frame_start + (i * frames_per_pixel); + volume = evaluate_fcurve(fcu, evaltime); + CLAMP_MIN(volume, 0.0f); } - WaveVizData *tri_strip_end = tri_strip_iter; - WaveVizData *line_strip_end = line_strip_iter; + value_min *= volume; + value_max *= volume; + rms *= volume; + + bool is_clipping = false; + + if (value_max > 1 || value_min < -1) { + is_clipping = true; - tri_strip_iter = tri_strip_arr; - line_strip_iter = line_strip_arr; + CLAMP_MAX(value_max, 1.0f); + CLAMP_MIN(value_min, -1.0f); + } - draw_waveform(line_strip_iter, line_strip_end, GPU_PRIM_LINE_STRIP, false); - draw_waveform(tri_strip_iter, tri_strip_end, GPU_PRIM_TRI_STRIP, false); - draw_waveform(tri_strip_iter, tri_strip_end, GPU_PRIM_TRI_STRIP, true); + bool is_line_strip = (value_max - value_min < 0.05f); + /* The y coordinate for the middle of the strip. */ + float y_mid = (y1 + y2) / 2.0f; + /* The length from the middle of the strip to the top/bottom. */ + float y_scale = (y2 - y1) / 2.0f; - MEM_freeN(tri_strip_arr); - MEM_freeN(line_strip_arr); + vec2f pos = {frame_start + i * frames_per_pixel, y_mid + value_min * y_scale}; + WaveVizData *new_data = &waveform_data[wave_data_len]; + wave_data_len += waveform_append_sample( + new_data, pos, value_min, value_max, y_mid, y_scale, rms, is_clipping, is_line_strip); } + + /* Terminate array, so `get_segment_length()` can know when to stop. */ + waveform_data[wave_data_len].final_sample = true; + draw_waveform(waveform_data, wave_data_len); + MEM_freeN(waveform_data); } +/* +static size_t *waveform_append(WaveVizData *waveform_data, + vec2f pos, + const float value_min, + const float value_max, + const float y_mid, + const float y_scale, + const float rms, + const bool is_clipping, + const bool is_line_strip) +*/ + static void drawmeta_contents(Scene *scene, Sequence *seqm, float x1, @@ -1430,18 +1397,9 @@ static void draw_seq_strip(const bContext *C, } /* Draw sound strip waveform. */ - if ((seq->type == SEQ_TYPE_SOUND_RAM) && ((sseq->flag & SEQ_SHOW_OVERLAY)) && - (sseq->timeline_overlay.flag & SEQ_TIMELINE_NO_WAVEFORMS) == 0) { - draw_seq_waveform_overlay(v2d, - C, - sseq, - scene, - seq, - x1, - y_threshold ? y1 + 0.05f : y1, - x2, - y_threshold ? text_margin_y : y2, - BLI_rctf_size_x(®ion->v2d.cur) / region->winx); + if (seq_draw_waveforms_poll(C, sseq, seq)) { + draw_seq_waveform_overlay( + C, region, seq, x1, y_threshold ? y1 + 0.05f : y1, x2, y_threshold ? text_margin_y : y2); } /* Draw locked state. */ if (SEQ_transform_is_locked(channels, seq)) { -- cgit v1.2.3 From 5e1229f25387a90fe626b4b2ac34f2eb5c7dc23a Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Fri, 15 Jul 2022 15:54:14 +0200 Subject: Fix overly noisy surface deform warning message An increased number of vertices is not a stopper for the surface deform modifier anymore. It might still be useful to expose the message in the UI, but printing error message to the console on every modifier evaluation makes real errors to become almost invisible. Differential Revision: https://developer.blender.org/D15468 --- source/blender/blenkernel/BKE_modifier.h | 12 ++++++++ source/blender/blenkernel/intern/modifier.c | 34 ++++++++++++++++++++++ .../blender/modifiers/intern/MOD_surfacedeform.c | 10 +++---- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/source/blender/blenkernel/BKE_modifier.h b/source/blender/blenkernel/BKE_modifier.h index 866b0353d07..46d609f9aa3 100644 --- a/source/blender/blenkernel/BKE_modifier.h +++ b/source/blender/blenkernel/BKE_modifier.h @@ -446,10 +446,22 @@ bool BKE_modifier_is_enabled(const struct Scene *scene, */ bool BKE_modifier_is_nonlocal_in_liboverride(const struct Object *ob, const struct ModifierData *md); + +/* Set modifier execution error. + * The message will be shown in the interface and will be logged as an error to the console. */ void BKE_modifier_set_error(const struct Object *ob, struct ModifierData *md, const char *format, ...) ATTR_PRINTF_FORMAT(3, 4); + +/* Set modifier execution warning, which does not prevent the modifier from being applied but which + * might need an attention. The message will only be shown in the interface, but will not appear in + * the logs. */ +void BKE_modifier_set_warning(const struct Object *ob, + struct ModifierData *md, + const char *format, + ...) ATTR_PRINTF_FORMAT(3, 4); + bool BKE_modifier_is_preview(struct ModifierData *md); void BKE_modifiers_foreach_ID_link(struct Object *ob, IDWalkFunc walk, void *userData); diff --git a/source/blender/blenkernel/intern/modifier.c b/source/blender/blenkernel/intern/modifier.c index 6348d83362e..831ea084961 100644 --- a/source/blender/blenkernel/intern/modifier.c +++ b/source/blender/blenkernel/intern/modifier.c @@ -456,6 +456,40 @@ void BKE_modifier_set_error(const Object *ob, ModifierData *md, const char *_for CLOG_ERROR(&LOG, "Object: \"%s\", Modifier: \"%s\", %s", ob->id.name + 2, md->name, md->error); } +void BKE_modifier_set_warning(const struct Object *ob, + struct ModifierData *md, + const char *_format, + ...) +{ + char buffer[512]; + va_list ap; + const char *format = TIP_(_format); + + va_start(ap, _format); + vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + buffer[sizeof(buffer) - 1] = '\0'; + + /* Store the warning in the same field as the error. + * It is not expected to have both error and warning and having a single place to store the + * message simplifies interface code. */ + + if (md->error) { + MEM_freeN(md->error); + } + + md->error = BLI_strdup(buffer); + +#ifndef NDEBUG + if ((md->mode & eModifierMode_Virtual) == 0) { + /* Ensure correct object is passed in. */ + BLI_assert(BKE_modifier_get_original(ob, md) != NULL); + } +#endif + + UNUSED_VARS_NDEBUG(ob); +} + int BKE_modifiers_get_cage_index(const Scene *scene, Object *ob, int *r_lastPossibleCageIndex, diff --git a/source/blender/modifiers/intern/MOD_surfacedeform.c b/source/blender/modifiers/intern/MOD_surfacedeform.c index d63ef12285b..edc6819a26c 100644 --- a/source/blender/modifiers/intern/MOD_surfacedeform.c +++ b/source/blender/modifiers/intern/MOD_surfacedeform.c @@ -1521,11 +1521,11 @@ static void surfacedeformModifier_do(ModifierData *md, * added after the original ones. This covers typical case when target was at the subdivision * level 0 and then subdivision was increased (i.e. for the render purposes). */ - BKE_modifier_set_error(ob, - md, - "Target vertices changed from %u to %u, continuing anyway", - smd->target_verts_num, - target_verts_num); + BKE_modifier_set_warning(ob, + md, + "Target vertices changed from %u to %u, continuing anyway", + smd->target_verts_num, + target_verts_num); /* In theory we only need the `smd->verts_num` vertices in the `targetCos` for evaluation, but * it is not currently possible to request a subset of coordinates: the API expects that the -- cgit v1.2.3 From 1cf465bbc3312ae8eac3e1ae573b716e0fad92cf Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 15 Jul 2022 12:44:35 +0200 Subject: Fix GPU backend deleting resources without an active context This causes an assert with libepoxy, but was wrong already regardless. Refactor logic to work as follows: * GPU_exit() deletes backend resources * Destroy UI GPU resources with the context active * Call GPU_backend_exit() after deleting the context Ref D15291 Differential Revision: https://developer.blender.org/D15465 --- source/blender/gpu/GPU_context.h | 5 +++- source/blender/gpu/intern/gpu_backend.hh | 1 + source/blender/gpu/intern/gpu_context.cc | 33 +++++++++++++--------- source/blender/gpu/intern/gpu_init_exit.c | 2 ++ source/blender/gpu/intern/gpu_private.h | 4 +++ source/blender/gpu/metal/mtl_backend.hh | 5 ++++ source/blender/gpu/opengl/gl_backend.hh | 8 ++++-- source/blender/gpu/tests/gpu_testing.cc | 3 +- source/blender/windowmanager/intern/wm_init_exit.c | 26 +++++++++-------- 9 files changed, 58 insertions(+), 29 deletions(-) diff --git a/source/blender/gpu/GPU_context.h b/source/blender/gpu/GPU_context.h index b04a4422baa..c81296093a1 100644 --- a/source/blender/gpu/GPU_context.h +++ b/source/blender/gpu/GPU_context.h @@ -17,8 +17,11 @@ extern "C" { #endif +/* GPU backends abstract the differences between different APIs. These must be + * initialized before creating contexts, and deleted after the last context is + * discarded. GPU_context_create automatically initializes a backend if none + * exists yet. */ bool GPU_backend_init_once(void); -void GPU_backend_init(eGPUBackendType backend); void GPU_backend_exit(void); bool GPU_backend_supported(eGPUBackendType type); diff --git a/source/blender/gpu/intern/gpu_backend.hh b/source/blender/gpu/intern/gpu_backend.hh index 6e07e6c3229..d2890efee72 100644 --- a/source/blender/gpu/intern/gpu_backend.hh +++ b/source/blender/gpu/intern/gpu_backend.hh @@ -30,6 +30,7 @@ class VertBuf; class GPUBackend { public: virtual ~GPUBackend() = default; + virtual void delete_resources() = 0; static GPUBackend *get(); diff --git a/source/blender/gpu/intern/gpu_context.cc b/source/blender/gpu/intern/gpu_context.cc index d3b208dc6f6..9b0670da8cb 100644 --- a/source/blender/gpu/intern/gpu_context.cc +++ b/source/blender/gpu/intern/gpu_context.cc @@ -27,6 +27,7 @@ #include "gpu_batch_private.hh" #include "gpu_context_private.hh" #include "gpu_matrix_private.h" +#include "gpu_private.h" #ifdef WITH_OPENGL_BACKEND # include "gl_backend.hh" @@ -213,20 +214,17 @@ bool GPU_backend_supported(eGPUBackendType type) bool GPU_backend_init_once() { - if (GPUBackend::get() == nullptr) { - if (!GPU_backend_supported(GPU_BACKEND_OPENGL)) { - return false; - } - /* TODO: move where it make sense. */ - GPU_backend_init(GPU_BACKEND_OPENGL); + if (GPUBackend::get() != nullptr) { + return true; } - return true; -} -void GPU_backend_init(eGPUBackendType backend_type) -{ - BLI_assert(g_backend == nullptr); - BLI_assert(GPU_backend_supported(backend_type)); + const eGPUBackendType backend_type = GPU_BACKEND_OPENGL; + if (!GPU_backend_supported(backend_type)) { + return false; + } + + static std::mutex backend_init_mutex; + std::scoped_lock lock(backend_init_mutex); switch (backend_type) { #ifdef WITH_OPENGL_BACKEND @@ -243,12 +241,19 @@ void GPU_backend_init(eGPUBackendType backend_type) BLI_assert(0); break; } + + return true; +} + +void gpu_backend_delete_resources() +{ + BLI_assert(backend); + g_backend->delete_resources(); } void GPU_backend_exit() { - /* TODO: assert no resource left. Currently UI textures are still not freed in their context - * correctly. */ + /* TODO: assert no resource left. */ delete g_backend; g_backend = nullptr; } diff --git a/source/blender/gpu/intern/gpu_init_exit.c b/source/blender/gpu/intern/gpu_init_exit.c index 062614fb5cb..34b355eefaf 100644 --- a/source/blender/gpu/intern/gpu_init_exit.c +++ b/source/blender/gpu/intern/gpu_init_exit.c @@ -55,6 +55,8 @@ void GPU_exit(void) gpu_shader_dependency_exit(); gpu_shader_create_info_exit(); + gpu_backend_delete_resources(); + initialized = false; } diff --git a/source/blender/gpu/intern/gpu_private.h b/source/blender/gpu/intern/gpu_private.h index a8ee5187d98..0e293302086 100644 --- a/source/blender/gpu/intern/gpu_private.h +++ b/source/blender/gpu/intern/gpu_private.h @@ -10,6 +10,10 @@ extern "C" { #endif +/* gpu_backend.cc */ + +void gpu_backend_delete_resources(void); + /* gpu_pbvh.c */ void gpu_pbvh_init(void); diff --git a/source/blender/gpu/metal/mtl_backend.hh b/source/blender/gpu/metal/mtl_backend.hh index 7228a5f7596..3e09408e43e 100644 --- a/source/blender/gpu/metal/mtl_backend.hh +++ b/source/blender/gpu/metal/mtl_backend.hh @@ -40,6 +40,11 @@ class MTLBackend : public GPUBackend { MTLBackend::platform_exit(); } + void delete_resources() + { + /* Delete any resources with context active. */ + } + static bool metal_is_supported(); static MTLBackend *get() { diff --git a/source/blender/gpu/opengl/gl_backend.hh b/source/blender/gpu/opengl/gl_backend.hh index 29249111294..e425b87afe8 100644 --- a/source/blender/gpu/opengl/gl_backend.hh +++ b/source/blender/gpu/opengl/gl_backend.hh @@ -42,11 +42,15 @@ class GLBackend : public GPUBackend { } ~GLBackend() { - GLTexture::samplers_free(); - GLBackend::platform_exit(); } + void delete_resources() override + { + /* Delete any resources with context active. */ + GLTexture::samplers_free(); + } + static GLBackend *get() { return static_cast(GPUBackend::get()); diff --git a/source/blender/gpu/tests/gpu_testing.cc b/source/blender/gpu/tests/gpu_testing.cc index 4e93e062b50..5a2ad893360 100644 --- a/source/blender/gpu/tests/gpu_testing.cc +++ b/source/blender/gpu/tests/gpu_testing.cc @@ -17,6 +17,7 @@ void GPUTest::SetUp() GHOST_GLSettings glSettings = {0}; CLG_init(); ghost_system = GHOST_CreateSystem(); + GPU_backend_init_once(); ghost_context = GHOST_CreateOpenGLContext(ghost_system, glSettings); GHOST_ActivateOpenGLContext(ghost_context); context = GPU_context_create(nullptr); @@ -26,9 +27,9 @@ void GPUTest::SetUp() void GPUTest::TearDown() { GPU_exit(); - GPU_backend_exit(); GPU_context_discard(context); GHOST_DisposeOpenGLContext(ghost_system, ghost_context); + GPU_backend_exit(); GHOST_DisposeSystem(ghost_system); CLG_exit(); } diff --git a/source/blender/windowmanager/intern/wm_init_exit.c b/source/blender/windowmanager/intern/wm_init_exit.c index b9bb1d88819..7324abfd096 100644 --- a/source/blender/windowmanager/intern/wm_init_exit.c +++ b/source/blender/windowmanager/intern/wm_init_exit.c @@ -576,14 +576,6 @@ void WM_exit_ex(bContext *C, const bool do_python) BLF_exit(); - if (opengl_is_init) { - DRW_opengl_context_enable_ex(false); - GPU_pass_cache_free(); - GPU_exit(); - DRW_opengl_context_disable_ex(false); - DRW_opengl_context_destroy(); - } - BLT_lang_free(); ANIM_keyingset_infos_exit(); @@ -608,13 +600,25 @@ void WM_exit_ex(bContext *C, const bool do_python) ED_file_exit(); /* for fsmenu */ - UI_exit(); + /* Delete GPU resources and context. The UI also uses GPU resources and so + * is also deleted with the context active. */ + if (opengl_is_init) { + DRW_opengl_context_enable_ex(false); + UI_exit(); + GPU_pass_cache_free(); + GPU_exit(); + DRW_opengl_context_disable_ex(false); + DRW_opengl_context_destroy(); + } + else { + UI_exit(); + } + GPU_backend_exit(); + BKE_blender_userdef_data_free(&U, false); RNA_exit(); /* should be after BPY_python_end so struct python slots are cleared */ - GPU_backend_exit(); - wm_ghost_exit(); CTX_free(C); -- cgit v1.2.3 From c505f19efe3f476e1164b455817ad275fa913454 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Fri, 15 Jul 2022 16:52:01 +0200 Subject: Fix compiler error in debug builds after 1cf465bbc331 --- source/blender/gpu/intern/gpu_context.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/gpu/intern/gpu_context.cc b/source/blender/gpu/intern/gpu_context.cc index 9b0670da8cb..5ae020e45a4 100644 --- a/source/blender/gpu/intern/gpu_context.cc +++ b/source/blender/gpu/intern/gpu_context.cc @@ -247,7 +247,7 @@ bool GPU_backend_init_once() void gpu_backend_delete_resources() { - BLI_assert(backend); + BLI_assert(g_backend); g_backend->delete_resources(); } -- cgit v1.2.3 From 03aeef64d5c2bd1eb1aa790bddbce728857778f3 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 15 Jul 2022 16:47:07 +0200 Subject: Cleanup: compiler warnings --- source/blender/editors/space_sequencer/sequencer_draw.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/blender/editors/space_sequencer/sequencer_draw.c b/source/blender/editors/space_sequencer/sequencer_draw.c index b1e13850bf2..eb2e4ef05e5 100644 --- a/source/blender/editors/space_sequencer/sequencer_draw.c +++ b/source/blender/editors/space_sequencer/sequencer_draw.c @@ -245,7 +245,7 @@ typedef struct WaveVizData { bool final_sample; /* There are no more samples. */ } WaveVizData; -static bool seq_draw_waveforms_poll(const bContext *C, SpaceSeq *sseq, Sequence *seq) +static bool seq_draw_waveforms_poll(const bContext *UNUSED(C), SpaceSeq *sseq, Sequence *seq) { const bool strip_is_valid = seq->type == SEQ_TYPE_SOUND_RAM && seq->sound != NULL; const bool overlays_enabled = (sseq->flag & SEQ_SHOW_OVERLAY) != 0; @@ -357,7 +357,7 @@ static void draw_waveform(WaveVizData *waveform_data, size_t wave_data_len) } } -static float align_frame_with_pixel(const View2D *v2d, float frame_coord, float frames_per_pixel) +static float align_frame_with_pixel(float frame_coord, float frames_per_pixel) { return round_fl_to_int(frame_coord / frames_per_pixel) * frames_per_pixel; } @@ -416,7 +416,7 @@ static void draw_seq_waveform_overlay( float samples_per_pixel = samples_per_frame * frames_per_pixel; /* Align strip start with nearest pixel to prevent waveform flickering. */ - const float x1_aligned = align_frame_with_pixel(v2d, x1, frames_per_pixel); + const float x1_aligned = align_frame_with_pixel(x1, frames_per_pixel); /* Offset x1 and x2 values, to match view min/max, if strip is out of bounds. */ const float frame_start = max_ff(v2d->cur.xmin, x1_aligned); const float frame_end = min_ff(v2d->cur.xmax, x2); @@ -439,8 +439,8 @@ static void draw_seq_waveform_overlay( size_t wave_data_len = 0; /* Offset must be also aligned, otherwise waveform flickers when moving left handle. */ - const float strip_offset = align_frame_with_pixel( - v2d, seq->startofs + seq->anim_startofs, frames_per_pixel); + const float strip_offset = align_frame_with_pixel(seq->startofs + seq->anim_startofs, + frames_per_pixel); float start_sample = strip_offset * samples_per_frame; start_sample += seq->sound->offset_time * SOUND_WAVE_SAMPLES_PER_SECOND; /* Add off-screen part of strip to offset. */ -- cgit v1.2.3 From bb376da6dfdd2476fc3738ce1fc89dac27825cef Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 15 Jul 2022 18:00:45 +0200 Subject: Fix Cycles MetalRT error after recent specialization changes --- intern/cycles/kernel/device/gpu/kernel.h | 2 +- intern/cycles/kernel/device/metal/function_constants.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/intern/cycles/kernel/device/gpu/kernel.h b/intern/cycles/kernel/device/gpu/kernel.h index b9a44ccad02..e1ab802aa80 100644 --- a/intern/cycles/kernel/device/gpu/kernel.h +++ b/intern/cycles/kernel/device/gpu/kernel.h @@ -246,7 +246,7 @@ ccl_gpu_kernel(GPU_KERNEL_BLOCK_NUM_THREADS, GPU_KERNEL_MAX_REGISTERS) ccl_gpu_kernel_postfix #if defined(__KERNEL_METAL_APPLE__) && defined(__METALRT__) -constant int __dummy_constant [[function_constant(0)]]; +constant int __dummy_constant [[function_constant(Kernel_DummyConstant)]]; #endif ccl_gpu_kernel(GPU_KERNEL_BLOCK_NUM_THREADS, GPU_KERNEL_MAX_REGISTERS) diff --git a/intern/cycles/kernel/device/metal/function_constants.h b/intern/cycles/kernel/device/metal/function_constants.h index f4001735672..3adf390c7f6 100644 --- a/intern/cycles/kernel/device/metal/function_constants.h +++ b/intern/cycles/kernel/device/metal/function_constants.h @@ -2,6 +2,7 @@ * Copyright 2021-2022 Blender Foundation */ enum { + Kernel_DummyConstant, #define KERNEL_STRUCT_MEMBER(parent, type, name) KernelData_##parent##_##name, #include "kernel/data_template.h" }; -- cgit v1.2.3 From 5152c7c152e52d563cbd3ba3c792de3af0c2c14f Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 13 Jul 2022 16:54:53 +0200 Subject: Cycles: refactor rays to have start and end distance, fix precision issues For transparency, volume and light intersection rays, adjust these distances rather than the ray start position. This way we increment the start distance by the smallest possible float increment to avoid self intersections, and be sure it works as the distance compared to be will be exactly the same as before, due to the ray start position and direction remaining the same. Fix T98764, T96537, hair ray tracing precision issues. Differential Revision: https://developer.blender.org/D15455 --- intern/cycles/kernel/bvh/bvh.h | 36 +++++------ intern/cycles/kernel/bvh/embree.h | 4 +- intern/cycles/kernel/bvh/local.h | 13 +++- intern/cycles/kernel/bvh/nodes.h | 32 +++++----- intern/cycles/kernel/bvh/shadow_all.h | 47 +++++++++++--- intern/cycles/kernel/bvh/traversal.h | 30 ++++++--- intern/cycles/kernel/bvh/util.h | 13 ++++ intern/cycles/kernel/bvh/volume.h | 17 +++-- intern/cycles/kernel/bvh/volume_all.h | 31 ++++++--- intern/cycles/kernel/camera/camera.h | 20 +++--- intern/cycles/kernel/device/metal/kernel.metal | 30 ++++++--- intern/cycles/kernel/device/optix/kernel.cu | 26 +++++--- intern/cycles/kernel/geom/curve_intersect.h | 36 ++++++----- .../cycles/kernel/geom/motion_triangle_intersect.h | 6 +- intern/cycles/kernel/geom/point_intersect.h | 15 +++-- intern/cycles/kernel/geom/shader_data.h | 3 +- intern/cycles/kernel/geom/triangle_intersect.h | 6 +- intern/cycles/kernel/integrator/init_from_bake.h | 6 +- intern/cycles/kernel/integrator/init_from_camera.h | 2 +- .../cycles/kernel/integrator/intersect_closest.h | 6 +- .../kernel/integrator/intersect_volume_stack.h | 13 ++-- intern/cycles/kernel/integrator/mnee.h | 22 +++---- intern/cycles/kernel/integrator/path_state.h | 1 - intern/cycles/kernel/integrator/shade_background.h | 3 +- intern/cycles/kernel/integrator/shade_light.h | 15 +---- intern/cycles/kernel/integrator/shade_shadow.h | 15 ++--- intern/cycles/kernel/integrator/shade_surface.h | 47 ++++++-------- intern/cycles/kernel/integrator/shade_volume.h | 73 ++++++++++++---------- .../kernel/integrator/shadow_state_template.h | 3 +- intern/cycles/kernel/integrator/state_template.h | 10 +-- intern/cycles/kernel/integrator/state_util.h | 12 ++-- intern/cycles/kernel/integrator/subsurface.h | 5 +- intern/cycles/kernel/integrator/subsurface_disk.h | 6 +- .../kernel/integrator/subsurface_random_walk.h | 17 ++--- intern/cycles/kernel/light/light.h | 34 ++++++---- intern/cycles/kernel/light/sample.h | 7 ++- intern/cycles/kernel/osl/services.cpp | 9 ++- intern/cycles/kernel/svm/ao.h | 3 +- intern/cycles/kernel/svm/bevel.h | 3 +- intern/cycles/kernel/svm/tex_coord.h | 4 +- intern/cycles/kernel/types.h | 3 +- intern/cycles/util/math_intersect.h | 37 ++++++----- 42 files changed, 427 insertions(+), 294 deletions(-) diff --git a/intern/cycles/kernel/bvh/bvh.h b/intern/cycles/kernel/bvh/bvh.h index f375529a6f6..9972de86c47 100644 --- a/intern/cycles/kernel/bvh/bvh.h +++ b/intern/cycles/kernel/bvh/bvh.h @@ -175,8 +175,8 @@ ccl_device_intersect bool scene_intersect(KernelGlobals kg, optixTrace(scene_intersect_valid(ray) ? kernel_data.device_bvh : 0, ray->P, ray->D, - 0.0f, - ray->t, + ray->tmin, + ray->tmax, ray->time, ray_mask, ray_flags, @@ -203,28 +203,28 @@ ccl_device_intersect bool scene_intersect(KernelGlobals kg, #elif defined(__METALRT__) if (!scene_intersect_valid(ray)) { - isect->t = ray->t; + isect->t = ray->tmax; isect->type = PRIMITIVE_NONE; return false; } # if defined(__KERNEL_DEBUG__) if (is_null_instance_acceleration_structure(metal_ancillaries->accel_struct)) { - isect->t = ray->t; + isect->t = ray->tmax; isect->type = PRIMITIVE_NONE; kernel_assert(!"Invalid metal_ancillaries->accel_struct pointer"); return false; } if (is_null_intersection_function_table(metal_ancillaries->ift_default)) { - isect->t = ray->t; + isect->t = ray->tmax; isect->type = PRIMITIVE_NONE; kernel_assert(!"Invalid ift_default"); return false; } # endif - metal::raytracing::ray r(ray->P, ray->D, 0.0f, ray->t); + metal::raytracing::ray r(ray->P, ray->D, ray->tmin, ray->tmax); metalrt_intersector_type metalrt_intersect; if (!kernel_data.bvh.have_curves) { @@ -263,7 +263,7 @@ ccl_device_intersect bool scene_intersect(KernelGlobals kg, # endif if (intersection.type == intersection_type::none) { - isect->t = ray->t; + isect->t = ray->tmax; isect->type = PRIMITIVE_NONE; return false; @@ -296,7 +296,7 @@ ccl_device_intersect bool scene_intersect(KernelGlobals kg, # ifdef __EMBREE__ if (kernel_data.device_bvh) { - isect->t = ray->t; + isect->t = ray->tmax; CCLIntersectContext ctx(kg, CCLIntersectContext::RAY_REGULAR); IntersectContext rtc_ctx(&ctx); RTCRayHit ray_hit; @@ -360,8 +360,8 @@ ccl_device_intersect bool scene_intersect_local(KernelGlobals kg, optixTrace(scene_intersect_valid(ray) ? kernel_data.device_bvh : 0, ray->P, ray->D, - 0.0f, - ray->t, + ray->tmin, + ray->tmax, ray->time, 0xFF, /* Need to always call into __anyhit__kernel_optix_local_hit. */ @@ -405,7 +405,7 @@ ccl_device_intersect bool scene_intersect_local(KernelGlobals kg, } # endif - metal::raytracing::ray r(ray->P, ray->D, 0.0f, ray->t); + metal::raytracing::ray r(ray->P, ray->D, ray->tmin, ray->tmax); metalrt_intersector_type metalrt_intersect; metalrt_intersect.force_opacity(metal::raytracing::forced_opacity::non_opaque); @@ -476,7 +476,7 @@ ccl_device_intersect bool scene_intersect_local(KernelGlobals kg, float3 dir = ray->D; float3 idir = ray->D; Transform ob_itfm; - rtc_ray.tfar = ray->t * + rtc_ray.tfar = ray->tmax * bvh_instance_motion_push(kg, local_object, ray, &P, &dir, &idir, &ob_itfm); /* bvh_instance_motion_push() returns the inverse transform but * it's not needed here. */ @@ -542,8 +542,8 @@ ccl_device_intersect bool scene_intersect_shadow_all(KernelGlobals kg, optixTrace(scene_intersect_valid(ray) ? kernel_data.device_bvh : 0, ray->P, ray->D, - 0.0f, - ray->t, + ray->tmin, + ray->tmax, ray->time, ray_mask, /* Need to always call into __anyhit__kernel_optix_shadow_all_hit. */ @@ -582,7 +582,7 @@ ccl_device_intersect bool scene_intersect_shadow_all(KernelGlobals kg, } # endif - metal::raytracing::ray r(ray->P, ray->D, 0.0f, ray->t); + metal::raytracing::ray r(ray->P, ray->D, ray->tmin, ray->tmax); metalrt_intersector_type metalrt_intersect; metalrt_intersect.force_opacity(metal::raytracing::forced_opacity::non_opaque); @@ -701,8 +701,8 @@ ccl_device_intersect bool scene_intersect_volume(KernelGlobals kg, optixTrace(scene_intersect_valid(ray) ? kernel_data.device_bvh : 0, ray->P, ray->D, - 0.0f, - ray->t, + ray->tmin, + ray->tmax, ray->time, ray_mask, /* Need to always call into __anyhit__kernel_optix_volume_test. */ @@ -744,7 +744,7 @@ ccl_device_intersect bool scene_intersect_volume(KernelGlobals kg, } # endif - metal::raytracing::ray r(ray->P, ray->D, 0.0f, ray->t); + metal::raytracing::ray r(ray->P, ray->D, ray->tmin, ray->tmax); metalrt_intersector_type metalrt_intersect; metalrt_intersect.force_opacity(metal::raytracing::forced_opacity::non_opaque); diff --git a/intern/cycles/kernel/bvh/embree.h b/intern/cycles/kernel/bvh/embree.h index 77eec2468f4..fecbccac2f8 100644 --- a/intern/cycles/kernel/bvh/embree.h +++ b/intern/cycles/kernel/bvh/embree.h @@ -83,8 +83,8 @@ ccl_device_inline void kernel_embree_setup_ray(const Ray &ray, rtc_ray.dir_x = ray.D.x; rtc_ray.dir_y = ray.D.y; rtc_ray.dir_z = ray.D.z; - rtc_ray.tnear = 0.0f; - rtc_ray.tfar = ray.t; + rtc_ray.tnear = ray.tmin; + rtc_ray.tfar = ray.tmax; rtc_ray.time = ray.time; rtc_ray.mask = visibility; } diff --git a/intern/cycles/kernel/bvh/local.h b/intern/cycles/kernel/bvh/local.h index 3b6b30ea93d..017a241ef4a 100644 --- a/intern/cycles/kernel/bvh/local.h +++ b/intern/cycles/kernel/bvh/local.h @@ -47,8 +47,9 @@ ccl_device_inline float3 P = ray->P; float3 dir = bvh_clamp_direction(ray->D); float3 idir = bvh_inverse_direction(dir); + float tmin = ray->tmin; int object = OBJECT_NONE; - float isect_t = ray->t; + float isect_t = ray->tmax; if (local_isect != NULL) { local_isect->num_hits = 0; @@ -59,10 +60,13 @@ ccl_device_inline if (!(object_flag & SD_OBJECT_TRANSFORM_APPLIED)) { #if BVH_FEATURE(BVH_MOTION) Transform ob_itfm; - isect_t *= bvh_instance_motion_push(kg, local_object, ray, &P, &dir, &idir, &ob_itfm); + const float t_world_to_instance = bvh_instance_motion_push( + kg, local_object, ray, &P, &dir, &idir, &ob_itfm); #else - isect_t *= bvh_instance_push(kg, local_object, ray, &P, &dir, &idir); + const float t_world_to_instance = bvh_instance_push(kg, local_object, ray, &P, &dir, &idir); #endif + isect_t *= t_world_to_instance; + tmin *= t_world_to_instance; object = local_object; } @@ -81,6 +85,7 @@ ccl_device_inline dir, #endif idir, + tmin, isect_t, node_addr, PATH_RAY_ALL_VISIBILITY, @@ -155,6 +160,7 @@ ccl_device_inline local_object, prim, prim_addr, + tmin, isect_t, lcg_state, max_hits)) { @@ -191,6 +197,7 @@ ccl_device_inline local_object, prim, prim_addr, + tmin, isect_t, lcg_state, max_hits)) { diff --git a/intern/cycles/kernel/bvh/nodes.h b/intern/cycles/kernel/bvh/nodes.h index c19dea9223b..e02841fad16 100644 --- a/intern/cycles/kernel/bvh/nodes.h +++ b/intern/cycles/kernel/bvh/nodes.h @@ -18,7 +18,8 @@ ccl_device_forceinline Transform bvh_unaligned_node_fetch_space(KernelGlobals kg ccl_device_forceinline int bvh_aligned_node_intersect(KernelGlobals kg, const float3 P, const float3 idir, - const float t, + const float tmin, + const float tmax, const int node_addr, const uint visibility, float dist[2]) @@ -39,8 +40,8 @@ ccl_device_forceinline int bvh_aligned_node_intersect(KernelGlobals kg, float c0hiy = (node1.z - P.y) * idir.y; float c0loz = (node2.x - P.z) * idir.z; float c0hiz = (node2.z - P.z) * idir.z; - float c0min = max4(0.0f, min(c0lox, c0hix), min(c0loy, c0hiy), min(c0loz, c0hiz)); - float c0max = min4(t, max(c0lox, c0hix), max(c0loy, c0hiy), max(c0loz, c0hiz)); + float c0min = max4(tmin, min(c0lox, c0hix), min(c0loy, c0hiy), min(c0loz, c0hiz)); + float c0max = min4(tmax, max(c0lox, c0hix), max(c0loy, c0hiy), max(c0loz, c0hiz)); float c1lox = (node0.y - P.x) * idir.x; float c1hix = (node0.w - P.x) * idir.x; @@ -48,8 +49,8 @@ ccl_device_forceinline int bvh_aligned_node_intersect(KernelGlobals kg, float c1hiy = (node1.w - P.y) * idir.y; float c1loz = (node2.y - P.z) * idir.z; float c1hiz = (node2.w - P.z) * idir.z; - float c1min = max4(0.0f, min(c1lox, c1hix), min(c1loy, c1hiy), min(c1loz, c1hiz)); - float c1max = min4(t, max(c1lox, c1hix), max(c1loy, c1hiy), max(c1loz, c1hiz)); + float c1min = max4(tmin, min(c1lox, c1hix), min(c1loy, c1hiy), min(c1loz, c1hiz)); + float c1max = min4(tmax, max(c1lox, c1hix), max(c1loy, c1hiy), max(c1loz, c1hiz)); dist[0] = c0min; dist[1] = c1min; @@ -66,7 +67,8 @@ ccl_device_forceinline int bvh_aligned_node_intersect(KernelGlobals kg, ccl_device_forceinline bool bvh_unaligned_node_intersect_child(KernelGlobals kg, const float3 P, const float3 dir, - const float t, + const float tmin, + const float tmax, int node_addr, int child, float dist[2]) @@ -83,8 +85,8 @@ ccl_device_forceinline bool bvh_unaligned_node_intersect_child(KernelGlobals kg, const float far_x = max(lower_xyz.x, upper_xyz.x); const float far_y = max(lower_xyz.y, upper_xyz.y); const float far_z = max(lower_xyz.z, upper_xyz.z); - const float tnear = max4(0.0f, near_x, near_y, near_z); - const float tfar = min4(t, far_x, far_y, far_z); + const float tnear = max4(tmin, near_x, near_y, near_z); + const float tfar = min4(tmax, far_x, far_y, far_z); *dist = tnear; return tnear <= tfar; } @@ -93,7 +95,8 @@ ccl_device_forceinline int bvh_unaligned_node_intersect(KernelGlobals kg, const float3 P, const float3 dir, const float3 idir, - const float t, + const float tmin, + const float tmax, const int node_addr, const uint visibility, float dist[2]) @@ -102,7 +105,7 @@ ccl_device_forceinline int bvh_unaligned_node_intersect(KernelGlobals kg, #ifdef __VISIBILITY_FLAG__ float4 cnodes = kernel_data_fetch(bvh_nodes, node_addr + 0); #endif - if (bvh_unaligned_node_intersect_child(kg, P, dir, t, node_addr, 0, &dist[0])) { + if (bvh_unaligned_node_intersect_child(kg, P, dir, tmin, tmax, node_addr, 0, &dist[0])) { #ifdef __VISIBILITY_FLAG__ if ((__float_as_uint(cnodes.x) & visibility)) #endif @@ -110,7 +113,7 @@ ccl_device_forceinline int bvh_unaligned_node_intersect(KernelGlobals kg, mask |= 1; } } - if (bvh_unaligned_node_intersect_child(kg, P, dir, t, node_addr, 1, &dist[1])) { + if (bvh_unaligned_node_intersect_child(kg, P, dir, tmin, tmax, node_addr, 1, &dist[1])) { #ifdef __VISIBILITY_FLAG__ if ((__float_as_uint(cnodes.y) & visibility)) #endif @@ -125,16 +128,17 @@ ccl_device_forceinline int bvh_node_intersect(KernelGlobals kg, const float3 P, const float3 dir, const float3 idir, - const float t, + const float tmin, + const float tmax, const int node_addr, const uint visibility, float dist[2]) { float4 node = kernel_data_fetch(bvh_nodes, node_addr); if (__float_as_uint(node.x) & PATH_RAY_NODE_UNALIGNED) { - return bvh_unaligned_node_intersect(kg, P, dir, idir, t, node_addr, visibility, dist); + return bvh_unaligned_node_intersect(kg, P, dir, idir, tmin, tmax, node_addr, visibility, dist); } else { - return bvh_aligned_node_intersect(kg, P, idir, t, node_addr, visibility, dist); + return bvh_aligned_node_intersect(kg, P, idir, tmin, tmax, node_addr, visibility, dist); } } diff --git a/intern/cycles/kernel/bvh/shadow_all.h b/intern/cycles/kernel/bvh/shadow_all.h index e86fe867eac..db3c91569aa 100644 --- a/intern/cycles/kernel/bvh/shadow_all.h +++ b/intern/cycles/kernel/bvh/shadow_all.h @@ -49,6 +49,7 @@ ccl_device_inline float3 P = ray->P; float3 dir = bvh_clamp_direction(ray->D); float3 idir = bvh_inverse_direction(dir); + float tmin = ray->tmin; int object = OBJECT_NONE; uint num_hits = 0; @@ -59,12 +60,12 @@ ccl_device_inline /* Max distance in world space. May be dynamically reduced when max number of * recorded hits is exceeded and we no longer need to find hits beyond the max * distance found. */ - float t_max_world = ray->t; + float t_max_world = ray->tmax; /* Current maximum distance to the intersection. * Is calculated as a ray length, transformed to an object space when entering * instance node. */ - float t_max_current = ray->t; + float t_max_current = ray->tmax; /* Conversion from world to local space for the current instance if any, 1.0 * otherwise. */ @@ -88,6 +89,7 @@ ccl_device_inline dir, #endif idir, + tmin, t_max_current, node_addr, visibility, @@ -156,8 +158,16 @@ ccl_device_inline switch (type & PRIMITIVE_ALL) { case PRIMITIVE_TRIANGLE: { - hit = triangle_intersect( - kg, &isect, P, dir, t_max_current, visibility, prim_object, prim, prim_addr); + hit = triangle_intersect(kg, + &isect, + P, + dir, + tmin, + t_max_current, + visibility, + prim_object, + prim, + prim_addr); break; } #if BVH_FEATURE(BVH_MOTION) @@ -166,6 +176,7 @@ ccl_device_inline &isect, P, dir, + tmin, t_max_current, ray->time, visibility, @@ -189,8 +200,16 @@ ccl_device_inline } const int curve_type = kernel_data_fetch(prim_type, prim_addr); - hit = curve_intersect( - kg, &isect, P, dir, t_max_current, prim_object, prim, ray->time, curve_type); + hit = curve_intersect(kg, + &isect, + P, + dir, + tmin, + t_max_current, + prim_object, + prim, + ray->time, + curve_type); break; } @@ -207,8 +226,16 @@ ccl_device_inline } const int point_type = kernel_data_fetch(prim_type, prim_addr); - hit = point_intersect( - kg, &isect, P, dir, t_max_current, prim_object, prim, ray->time, point_type); + hit = point_intersect(kg, + &isect, + P, + dir, + tmin, + t_max_current, + prim_object, + prim, + ray->time, + point_type); break; } #endif /* BVH_FEATURE(BVH_POINTCLOUD) */ @@ -302,6 +329,7 @@ ccl_device_inline /* Convert intersection to object space. */ t_max_current *= t_world_to_instance; + tmin *= t_world_to_instance; ++stack_ptr; kernel_assert(stack_ptr < BVH_STACK_SIZE); @@ -323,7 +351,8 @@ ccl_device_inline #endif /* Restore world space ray length. */ - t_max_current = ray->t; + tmin = ray->tmin; + t_max_current = ray->tmax; object = OBJECT_NONE; t_world_to_instance = 1.0f; diff --git a/intern/cycles/kernel/bvh/traversal.h b/intern/cycles/kernel/bvh/traversal.h index 784fbf4fd11..0ff38bf02de 100644 --- a/intern/cycles/kernel/bvh/traversal.h +++ b/intern/cycles/kernel/bvh/traversal.h @@ -43,13 +43,14 @@ ccl_device_noinline bool BVH_FUNCTION_FULL_NAME(BVH)(KernelGlobals kg, float3 P = ray->P; float3 dir = bvh_clamp_direction(ray->D); float3 idir = bvh_inverse_direction(dir); + float tmin = ray->tmin; int object = OBJECT_NONE; #if BVH_FEATURE(BVH_MOTION) Transform ob_itfm; #endif - isect->t = ray->t; + isect->t = ray->tmax; isect->u = 0.0f; isect->v = 0.0f; isect->prim = PRIM_NONE; @@ -71,6 +72,7 @@ ccl_device_noinline bool BVH_FUNCTION_FULL_NAME(BVH)(KernelGlobals kg, dir, #endif idir, + tmin, isect->t, node_addr, visibility, @@ -133,8 +135,16 @@ ccl_device_noinline bool BVH_FUNCTION_FULL_NAME(BVH)(KernelGlobals kg, switch (type & PRIMITIVE_ALL) { case PRIMITIVE_TRIANGLE: { - if (triangle_intersect( - kg, isect, P, dir, isect->t, visibility, prim_object, prim, prim_addr)) { + if (triangle_intersect(kg, + isect, + P, + dir, + tmin, + isect->t, + visibility, + prim_object, + prim, + prim_addr)) { /* shadow ray early termination */ if (visibility & PATH_RAY_SHADOW_OPAQUE) return true; @@ -147,6 +157,7 @@ ccl_device_noinline bool BVH_FUNCTION_FULL_NAME(BVH)(KernelGlobals kg, isect, P, dir, + tmin, isect->t, ray->time, visibility, @@ -174,7 +185,7 @@ ccl_device_noinline bool BVH_FUNCTION_FULL_NAME(BVH)(KernelGlobals kg, const int curve_type = kernel_data_fetch(prim_type, prim_addr); const bool hit = curve_intersect( - kg, isect, P, dir, isect->t, prim_object, prim, ray->time, curve_type); + kg, isect, P, dir, tmin, isect->t, prim_object, prim, ray->time, curve_type); if (hit) { /* shadow ray early termination */ if (visibility & PATH_RAY_SHADOW_OPAQUE) @@ -195,7 +206,7 @@ ccl_device_noinline bool BVH_FUNCTION_FULL_NAME(BVH)(KernelGlobals kg, const int point_type = kernel_data_fetch(prim_type, prim_addr); const bool hit = point_intersect( - kg, isect, P, dir, isect->t, prim_object, prim, ray->time, point_type); + kg, isect, P, dir, tmin, isect->t, prim_object, prim, ray->time, point_type); if (hit) { /* shadow ray early termination */ if (visibility & PATH_RAY_SHADOW_OPAQUE) @@ -212,11 +223,15 @@ ccl_device_noinline bool BVH_FUNCTION_FULL_NAME(BVH)(KernelGlobals kg, object = kernel_data_fetch(prim_object, -prim_addr - 1); #if BVH_FEATURE(BVH_MOTION) - isect->t *= bvh_instance_motion_push(kg, object, ray, &P, &dir, &idir, &ob_itfm); + const float t_world_to_instance = bvh_instance_motion_push( + kg, object, ray, &P, &dir, &idir, &ob_itfm); #else - isect->t *= bvh_instance_push(kg, object, ray, &P, &dir, &idir); + const float t_world_to_instance = bvh_instance_push(kg, object, ray, &P, &dir, &idir); #endif + isect->t *= t_world_to_instance; + tmin *= t_world_to_instance; + ++stack_ptr; kernel_assert(stack_ptr < BVH_STACK_SIZE); traversal_stack[stack_ptr] = ENTRYPOINT_SENTINEL; @@ -235,6 +250,7 @@ ccl_device_noinline bool BVH_FUNCTION_FULL_NAME(BVH)(KernelGlobals kg, #else isect->t = bvh_instance_pop(kg, object, ray, &P, &dir, &idir, isect->t); #endif + tmin = ray->tmin; object = OBJECT_NONE; node_addr = traversal_stack[stack_ptr]; diff --git a/intern/cycles/kernel/bvh/util.h b/intern/cycles/kernel/bvh/util.h index 572e023db25..1795ae4c790 100644 --- a/intern/cycles/kernel/bvh/util.h +++ b/intern/cycles/kernel/bvh/util.h @@ -5,6 +5,19 @@ CCL_NAMESPACE_BEGIN +/* Offset intersection distance by the smallest possible amount, to skip + * intersections at this distance. This works in cases where the ray start + * position is unchanged and only tmin is updated, since for self + * intersection we'll be comparing against the exact same distances. */ +ccl_device_forceinline float intersection_t_offset(const float t) +{ + /* This is a simplified version of nextafterf(t, FLT_MAX), only dealing with + * non-negative and finite t. */ + kernel_assert(t >= 0.0f && isfinite_safe(t)); + const uint32_t bits = (t == 0.0f) ? 1 : __float_as_uint(t) + 1; + return __uint_as_float(bits); +} + #if defined(__KERNEL_CPU__) ccl_device int intersections_compare(const void *a, const void *b) { diff --git a/intern/cycles/kernel/bvh/volume.h b/intern/cycles/kernel/bvh/volume.h index 9715712a8f2..bd4e508ecac 100644 --- a/intern/cycles/kernel/bvh/volume.h +++ b/intern/cycles/kernel/bvh/volume.h @@ -46,13 +46,14 @@ ccl_device_inline float3 P = ray->P; float3 dir = bvh_clamp_direction(ray->D); float3 idir = bvh_inverse_direction(dir); + float tmin = ray->tmin; int object = OBJECT_NONE; #if BVH_FEATURE(BVH_MOTION) Transform ob_itfm; #endif - isect->t = ray->t; + isect->t = ray->tmax; isect->u = 0.0f; isect->v = 0.0f; isect->prim = PRIM_NONE; @@ -73,6 +74,7 @@ ccl_device_inline dir, #endif idir, + tmin, isect->t, node_addr, visibility, @@ -140,7 +142,7 @@ ccl_device_inline continue; } triangle_intersect( - kg, isect, P, dir, isect->t, visibility, prim_object, prim, prim_addr); + kg, isect, P, dir, tmin, isect->t, visibility, prim_object, prim, prim_addr); } break; } @@ -165,6 +167,7 @@ ccl_device_inline isect, P, dir, + tmin, isect->t, ray->time, visibility, @@ -186,11 +189,15 @@ ccl_device_inline int object_flag = kernel_data_fetch(object_flag, object); if (object_flag & SD_OBJECT_HAS_VOLUME) { #if BVH_FEATURE(BVH_MOTION) - isect->t *= bvh_instance_motion_push(kg, object, ray, &P, &dir, &idir, &ob_itfm); + const float t_world_to_instance = bvh_instance_motion_push( + kg, object, ray, &P, &dir, &idir, &ob_itfm); #else - isect->t *= bvh_instance_push(kg, object, ray, &P, &dir, &idir); + const float t_world_to_instance = bvh_instance_push(kg, object, ray, &P, &dir, &idir); #endif + isect->t *= t_world_to_instance; + tmin *= t_world_to_instance; + ++stack_ptr; kernel_assert(stack_ptr < BVH_STACK_SIZE); traversal_stack[stack_ptr] = ENTRYPOINT_SENTINEL; @@ -217,6 +224,8 @@ ccl_device_inline isect->t = bvh_instance_pop(kg, object, ray, &P, &dir, &idir, isect->t); #endif + tmin = ray->tmin; + object = OBJECT_NONE; node_addr = traversal_stack[stack_ptr]; --stack_ptr; diff --git a/intern/cycles/kernel/bvh/volume_all.h b/intern/cycles/kernel/bvh/volume_all.h index d06ea8fe557..c6eeb07a14d 100644 --- a/intern/cycles/kernel/bvh/volume_all.h +++ b/intern/cycles/kernel/bvh/volume_all.h @@ -44,12 +44,12 @@ ccl_device_inline int node_addr = kernel_data.bvh.root; /* ray parameters in registers */ - const float tmax = ray->t; float3 P = ray->P; float3 dir = bvh_clamp_direction(ray->D); float3 idir = bvh_inverse_direction(dir); + float tmin = ray->tmin; int object = OBJECT_NONE; - float isect_t = tmax; + float isect_t = ray->tmax; #if BVH_FEATURE(BVH_MOTION) Transform ob_itfm; @@ -58,7 +58,7 @@ ccl_device_inline int num_hits_in_instance = 0; uint num_hits = 0; - isect_array->t = tmax; + isect_array->t = ray->tmax; /* traversal loop */ do { @@ -75,6 +75,7 @@ ccl_device_inline dir, #endif idir, + tmin, isect_t, node_addr, visibility, @@ -141,8 +142,16 @@ ccl_device_inline if ((object_flag & SD_OBJECT_HAS_VOLUME) == 0) { continue; } - hit = triangle_intersect( - kg, isect_array, P, dir, isect_t, visibility, prim_object, prim, prim_addr); + hit = triangle_intersect(kg, + isect_array, + P, + dir, + tmin, + isect_t, + visibility, + prim_object, + prim, + prim_addr); if (hit) { /* Move on to next entry in intersections array. */ isect_array++; @@ -189,6 +198,7 @@ ccl_device_inline isect_array, P, dir, + tmin, isect_t, ray->time, visibility, @@ -232,11 +242,15 @@ ccl_device_inline int object_flag = kernel_data_fetch(object_flag, object); if (object_flag & SD_OBJECT_HAS_VOLUME) { #if BVH_FEATURE(BVH_MOTION) - isect_t *= bvh_instance_motion_push(kg, object, ray, &P, &dir, &idir, &ob_itfm); + const float t_world_to_instance = bvh_instance_motion_push( + kg, object, ray, &P, &dir, &idir, &ob_itfm); #else - isect_t *= bvh_instance_push(kg, object, ray, &P, &dir, &idir); + const float t_world_to_instance = bvh_instance_push(kg, object, ray, &P, &dir, &idir); #endif + isect_t *= t_world_to_instance; + tmin *= t_world_to_instance; + num_hits_in_instance = 0; isect_array->t = isect_t; @@ -280,7 +294,8 @@ ccl_device_inline #endif } - isect_t = tmax; + tmin = ray->tmin; + isect_t = ray->tmax; isect_array->t = isect_t; object = OBJECT_NONE; diff --git a/intern/cycles/kernel/camera/camera.h b/intern/cycles/kernel/camera/camera.h index 7e1b1c037e9..926ccf7b86f 100644 --- a/intern/cycles/kernel/camera/camera.h +++ b/intern/cycles/kernel/camera/camera.h @@ -165,9 +165,11 @@ ccl_device void camera_sample_perspective(KernelGlobals kg, float nearclip = kernel_data.cam.nearclip * z_inv; ray->P += nearclip * ray->D; ray->dP += nearclip * ray->dD; - ray->t = kernel_data.cam.cliplength * z_inv; + ray->tmin = 0.0f; + ray->tmax = kernel_data.cam.cliplength * z_inv; #else - ray->t = FLT_MAX; + ray->tmin = 0.0f; + ray->tmax = FLT_MAX; #endif } @@ -231,9 +233,11 @@ ccl_device void camera_sample_orthographic(KernelGlobals kg, #ifdef __CAMERA_CLIPPING__ /* clipping */ - ray->t = kernel_data.cam.cliplength; + ray->tmin = 0.0f; + ray->tmax = kernel_data.cam.cliplength; #else - ray->t = FLT_MAX; + ray->tmin = 0.0f; + ray->tmax = FLT_MAX; #endif } @@ -258,7 +262,7 @@ ccl_device_inline void camera_sample_panorama(ccl_constant KernelCamera *cam, /* indicates ray should not receive any light, outside of the lens */ if (is_zero(D)) { - ray->t = 0.0f; + ray->tmax = 0.0f; return; } @@ -349,9 +353,11 @@ ccl_device_inline void camera_sample_panorama(ccl_constant KernelCamera *cam, float nearclip = cam->nearclip; ray->P += nearclip * ray->D; ray->dP += nearclip * ray->dD; - ray->t = cam->cliplength; + ray->tmin = 0.0f; + ray->tmax = cam->cliplength; #else - ray->t = FLT_MAX; + ray->tmin = 0.0f; + ray->tmax = FLT_MAX; #endif } diff --git a/intern/cycles/kernel/device/metal/kernel.metal b/intern/cycles/kernel/device/metal/kernel.metal index 74b4b079a32..764c26dbe8f 100644 --- a/intern/cycles/kernel/device/metal/kernel.metal +++ b/intern/cycles/kernel/device/metal/kernel.metal @@ -410,6 +410,7 @@ void metalrt_intersection_curve(constant KernelParamsMetal &launch_params_metal, const float3 ray_origin, const float3 ray_direction, float time, + const float ray_tmin, const float ray_tmax, thread BoundingBoxIntersectionResult &result) { @@ -434,7 +435,7 @@ void metalrt_intersection_curve(constant KernelParamsMetal &launch_params_metal, isect.t *= len; MetalKernelContext context(launch_params_metal); - if (context.curve_intersect(NULL, &isect, P, dir, isect.t, object, prim, time, type)) { + if (context.curve_intersect(NULL, &isect, P, dir, ray_tmin, isect.t, object, prim, time, type)) { result = metalrt_visibility_test( launch_params_metal, payload, object, prim, isect.u); if (result.accept) { @@ -456,6 +457,7 @@ void metalrt_intersection_curve_shadow(constant KernelParamsMetal &launch_params const float3 ray_origin, const float3 ray_direction, float time, + const float ray_tmin, const float ray_tmax, thread BoundingBoxIntersectionResult &result) { @@ -475,7 +477,7 @@ void metalrt_intersection_curve_shadow(constant KernelParamsMetal &launch_params isect.t *= len; MetalKernelContext context(launch_params_metal); - if (context.curve_intersect(NULL, &isect, P, dir, isect.t, object, prim, time, type)) { + if (context.curve_intersect(NULL, &isect, P, dir, ray_tmin, isect.t, object, prim, time, type)) { result.continue_search = metalrt_shadow_all_hit( launch_params_metal, payload, object, prim, float2(isect.u, isect.v), ray_tmax); result.accept = !result.continue_search; @@ -494,6 +496,7 @@ __intersection__curve_ribbon(constant KernelParamsMetal &launch_params_metal [[b const uint primitive_id [[primitive_id]], const float3 ray_origin [[origin]], const float3 ray_direction [[direction]], + const float ray_tmin [[min_distance]], const float ray_tmax [[max_distance]]) { uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); @@ -511,7 +514,7 @@ __intersection__curve_ribbon(constant KernelParamsMetal &launch_params_metal [[b # else 0.0f, # endif - ray_tmax, result); + ray_tmin, ray_tmax, result); } return result; @@ -525,6 +528,7 @@ __intersection__curve_ribbon_shadow(constant KernelParamsMetal &launch_params_me const uint primitive_id [[primitive_id]], const float3 ray_origin [[origin]], const float3 ray_direction [[direction]], + const float ray_tmin [[min_distance]], const float ray_tmax [[max_distance]]) { uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); @@ -542,7 +546,7 @@ __intersection__curve_ribbon_shadow(constant KernelParamsMetal &launch_params_me # else 0.0f, # endif - ray_tmax, result); + ray_tmin, ray_tmax, result); } return result; @@ -556,6 +560,7 @@ __intersection__curve_all(constant KernelParamsMetal &launch_params_metal [[buff const uint primitive_id [[primitive_id]], const float3 ray_origin [[origin]], const float3 ray_direction [[direction]], + const float ray_tmin [[min_distance]], const float ray_tmax [[max_distance]]) { uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); @@ -571,7 +576,7 @@ __intersection__curve_all(constant KernelParamsMetal &launch_params_metal [[buff # else 0.0f, # endif - ray_tmax, result); + ray_tmin, ray_tmax, result); return result; } @@ -584,6 +589,7 @@ __intersection__curve_all_shadow(constant KernelParamsMetal &launch_params_metal const uint primitive_id [[primitive_id]], const float3 ray_origin [[origin]], const float3 ray_direction [[direction]], + const float ray_tmin [[min_distance]], const float ray_tmax [[max_distance]]) { uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); @@ -600,7 +606,7 @@ __intersection__curve_all_shadow(constant KernelParamsMetal &launch_params_metal # else 0.0f, # endif - ray_tmax, result); + ray_tmin, ray_tmax, result); return result; } @@ -616,6 +622,7 @@ void metalrt_intersection_point(constant KernelParamsMetal &launch_params_metal, const float3 ray_origin, const float3 ray_direction, float time, + const float ray_tmin, const float ray_tmax, thread BoundingBoxIntersectionResult &result) { @@ -640,7 +647,7 @@ void metalrt_intersection_point(constant KernelParamsMetal &launch_params_metal, isect.t *= len; MetalKernelContext context(launch_params_metal); - if (context.point_intersect(NULL, &isect, P, dir, isect.t, object, prim, time, type)) { + if (context.point_intersect(NULL, &isect, P, dir, ray_tmin, isect.t, object, prim, time, type)) { result = metalrt_visibility_test( launch_params_metal, payload, object, prim, isect.u); if (result.accept) { @@ -662,6 +669,7 @@ void metalrt_intersection_point_shadow(constant KernelParamsMetal &launch_params const float3 ray_origin, const float3 ray_direction, float time, + const float ray_tmin, const float ray_tmax, thread BoundingBoxIntersectionResult &result) { @@ -681,7 +689,7 @@ void metalrt_intersection_point_shadow(constant KernelParamsMetal &launch_params isect.t *= len; MetalKernelContext context(launch_params_metal); - if (context.point_intersect(NULL, &isect, P, dir, isect.t, object, prim, time, type)) { + if (context.point_intersect(NULL, &isect, P, dir, ray_tmin, isect.t, object, prim, time, type)) { result.continue_search = metalrt_shadow_all_hit( launch_params_metal, payload, object, prim, float2(isect.u, isect.v), ray_tmax); result.accept = !result.continue_search; @@ -700,6 +708,7 @@ __intersection__point(constant KernelParamsMetal &launch_params_metal [[buffer(1 const uint primitive_id [[primitive_id]], const float3 ray_origin [[origin]], const float3 ray_direction [[direction]], + const float ray_tmin [[min_distance]], const float ray_tmax [[max_distance]]) { const uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); @@ -716,7 +725,7 @@ __intersection__point(constant KernelParamsMetal &launch_params_metal [[buffer(1 # else 0.0f, # endif - ray_tmax, result); + ray_tmin, ray_tmax, result); return result; } @@ -729,6 +738,7 @@ __intersection__point_shadow(constant KernelParamsMetal &launch_params_metal [[b const uint primitive_id [[primitive_id]], const float3 ray_origin [[origin]], const float3 ray_direction [[direction]], + const float ray_tmin [[min_distance]], const float ray_tmax [[max_distance]]) { const uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); @@ -745,7 +755,7 @@ __intersection__point_shadow(constant KernelParamsMetal &launch_params_metal [[b # else 0.0f, # endif - ray_tmax, result); + ray_tmin, ray_tmax, result); return result; } diff --git a/intern/cycles/kernel/device/optix/kernel.cu b/intern/cycles/kernel/device/optix/kernel.cu index 949bf41d171..510f7cca5d6 100644 --- a/intern/cycles/kernel/device/optix/kernel.cu +++ b/intern/cycles/kernel/device/optix/kernel.cu @@ -51,32 +51,36 @@ ccl_device_forceinline int get_object_id() extern "C" __global__ void __raygen__kernel_optix_integrator_intersect_closest() { const int global_index = optixGetLaunchIndex().x; - const int path_index = (kernel_params.path_index_array) ? kernel_params.path_index_array[global_index] : - global_index; + const int path_index = (kernel_params.path_index_array) ? + kernel_params.path_index_array[global_index] : + global_index; integrator_intersect_closest(nullptr, path_index, kernel_params.render_buffer); } extern "C" __global__ void __raygen__kernel_optix_integrator_intersect_shadow() { const int global_index = optixGetLaunchIndex().x; - const int path_index = (kernel_params.path_index_array) ? kernel_params.path_index_array[global_index] : - global_index; + const int path_index = (kernel_params.path_index_array) ? + kernel_params.path_index_array[global_index] : + global_index; integrator_intersect_shadow(nullptr, path_index); } extern "C" __global__ void __raygen__kernel_optix_integrator_intersect_subsurface() { const int global_index = optixGetLaunchIndex().x; - const int path_index = (kernel_params.path_index_array) ? kernel_params.path_index_array[global_index] : - global_index; + const int path_index = (kernel_params.path_index_array) ? + kernel_params.path_index_array[global_index] : + global_index; integrator_intersect_subsurface(nullptr, path_index); } extern "C" __global__ void __raygen__kernel_optix_integrator_intersect_volume_stack() { const int global_index = optixGetLaunchIndex().x; - const int path_index = (kernel_params.path_index_array) ? kernel_params.path_index_array[global_index] : - global_index; + const int path_index = (kernel_params.path_index_array) ? + kernel_params.path_index_array[global_index] : + global_index; integrator_intersect_volume_stack(nullptr, path_index); } @@ -408,6 +412,7 @@ ccl_device_inline void optix_intersection_curve(const int prim, const int type) float3 P = optixGetObjectRayOrigin(); float3 dir = optixGetObjectRayDirection(); + float tmin = optixGetRayTmin(); /* The direction is not normalized by default, but the curve intersection routine expects that */ float len; @@ -425,7 +430,7 @@ ccl_device_inline void optix_intersection_curve(const int prim, const int type) if (isect.t != FLT_MAX) isect.t *= len; - if (curve_intersect(NULL, &isect, P, dir, isect.t, object, prim, time, type)) { + if (curve_intersect(NULL, &isect, P, dir, tmin, isect.t, object, prim, time, type)) { static_assert(PRIMITIVE_ALL < 128, "Values >= 128 are reserved for OptiX internal use"); optixReportIntersection(isect.t / len, type & PRIMITIVE_ALL, @@ -462,6 +467,7 @@ extern "C" __global__ void __intersection__point() float3 P = optixGetObjectRayOrigin(); float3 dir = optixGetObjectRayDirection(); + float tmin = optixGetRayTmin(); /* The direction is not normalized by default, the point intersection routine expects that. */ float len; @@ -480,7 +486,7 @@ extern "C" __global__ void __intersection__point() isect.t *= len; } - if (point_intersect(NULL, &isect, P, dir, isect.t, object, prim, time, type)) { + if (point_intersect(NULL, &isect, P, dir, tmin, isect.t, object, prim, time, type)) { static_assert(PRIMITIVE_ALL < 128, "Values >= 128 are reserved for OptiX internal use"); optixReportIntersection(isect.t / len, type & PRIMITIVE_ALL); } diff --git a/intern/cycles/kernel/geom/curve_intersect.h b/intern/cycles/kernel/geom/curve_intersect.h index 001bec01749..9770105dd81 100644 --- a/intern/cycles/kernel/geom/curve_intersect.h +++ b/intern/cycles/kernel/geom/curve_intersect.h @@ -156,7 +156,8 @@ ccl_device_inline float2 half_plane_intersect(const float3 P, const float3 N, co } ccl_device bool curve_intersect_iterative(const float3 ray_dir, - ccl_private float *ray_tfar, + const float ray_tmin, + ccl_private float *ray_tmax, const float dt, const float4 curve[4], float u, @@ -220,7 +221,7 @@ ccl_device bool curve_intersect_iterative(const float3 ray_dir, if (fabsf(f) < f_err && fabsf(g) < g_err) { t += dt; - if (!(0.0f <= t && t <= *ray_tfar)) { + if (!(t >= ray_tmin && t <= *ray_tmax)) { return false; /* Rejects NaNs */ } if (!(u >= 0.0f && u <= 1.0f)) { @@ -237,7 +238,7 @@ ccl_device bool curve_intersect_iterative(const float3 ray_dir, } /* Record intersection. */ - *ray_tfar = t; + *ray_tmax = t; isect->t = t; isect->u = u; isect->v = 0.0f; @@ -250,7 +251,8 @@ ccl_device bool curve_intersect_iterative(const float3 ray_dir, ccl_device bool curve_intersect_recursive(const float3 ray_orig, const float3 ray_dir, - float ray_tfar, + const float ray_tmin, + float ray_tmax, float4 curve[4], ccl_private Intersection *isect) { @@ -331,7 +333,7 @@ ccl_device bool curve_intersect_recursive(const float3 ray_orig, } /* Intersect with cap-planes. */ - float2 tp = make_float2(-dt, ray_tfar - dt); + float2 tp = make_float2(ray_tmin - dt, ray_tmax - dt); tp = make_float2(max(tp.x, tc_outer.x), min(tp.y, tc_outer.y)); const float2 h0 = half_plane_intersect( float4_to_float3(P0), float4_to_float3(dP0du), ray_dir); @@ -394,19 +396,20 @@ ccl_device bool curve_intersect_recursive(const float3 ray_orig, CURVE_NUM_BEZIER_SUBDIVISIONS; if (depth >= termDepth) { found |= curve_intersect_iterative( - ray_dir, &ray_tfar, dt, curve, u_outer0, tp0.x, use_backfacing, isect); + ray_dir, ray_tmin, &ray_tmax, dt, curve, u_outer0, tp0.x, use_backfacing, isect); } else { recurse = true; } } - if (valid1 && (tp1.x + dt <= ray_tfar)) { + const float t1 = tp1.x + dt; + if (valid1 && (t1 >= ray_tmin && t1 <= ray_tmax)) { const int termDepth = unstable1 ? CURVE_NUM_BEZIER_SUBDIVISIONS_UNSTABLE : CURVE_NUM_BEZIER_SUBDIVISIONS; if (depth >= termDepth) { found |= curve_intersect_iterative( - ray_dir, &ray_tfar, dt, curve, u_outer1, tp1.y, use_backfacing, isect); + ray_dir, ray_tmin, &ray_tmax, dt, curve, u_outer1, tp1.y, use_backfacing, isect); } else { recurse = true; @@ -456,7 +459,8 @@ ccl_device_inline bool cylinder_culling_test(const float2 p1, const float2 p2, c * v0,v1,v3 and v2,v3,v1. The edge v1,v2 decides which of the two * triangles gets intersected. */ -ccl_device_inline bool ribbon_intersect_quad(const float ray_tfar, +ccl_device_inline bool ribbon_intersect_quad(const float ray_tmin, + const float ray_tmax, const float3 quad_v0, const float3 quad_v1, const float3 quad_v2, @@ -497,7 +501,7 @@ ccl_device_inline bool ribbon_intersect_quad(const float ray_tfar, /* Perform depth test? */ const float t = rcpDen * dot(v0, Ng); - if (!(0.0f <= t && t <= ray_tfar)) { + if (!(t >= ray_tmin && t <= ray_tmax)) { return false; } @@ -534,7 +538,8 @@ ccl_device_inline float4 ribbon_to_ray_space(const float3 ray_space[3], ccl_device_inline bool ribbon_intersect(const float3 ray_org, const float3 ray_dir, - float ray_tfar, + const float ray_tmin, + float ray_tmax, const int N, float4 curve[4], ccl_private Intersection *isect) @@ -582,7 +587,7 @@ ccl_device_inline bool ribbon_intersect(const float3 ray_org, /* Intersect quad. */ float vu, vv, vt; - bool valid0 = ribbon_intersect_quad(ray_tfar, lp0, lp1, up1, up0, &vu, &vv, &vt); + bool valid0 = ribbon_intersect_quad(ray_tmin, ray_tmax, lp0, lp1, up1, up0, &vu, &vv, &vt); if (valid0) { /* ignore self intersections */ @@ -596,7 +601,7 @@ ccl_device_inline bool ribbon_intersect(const float3 ray_org, vv = 2.0f * vv - 1.0f; /* Record intersection. */ - ray_tfar = vt; + ray_tmax = vt; isect->t = vt; isect->u = u + vu * step_size; isect->v = vv; @@ -616,6 +621,7 @@ ccl_device_forceinline bool curve_intersect(KernelGlobals kg, ccl_private Intersection *isect, const float3 P, const float3 dir, + const float tmin, const float tmax, int object, int prim, @@ -645,7 +651,7 @@ ccl_device_forceinline bool curve_intersect(KernelGlobals kg, if (type & PRIMITIVE_CURVE_RIBBON) { /* todo: adaptive number of subdivisions could help performance here. */ const int subdivisions = kernel_data.bvh.curve_subdivisions; - if (ribbon_intersect(P, dir, tmax, subdivisions, curve, isect)) { + if (ribbon_intersect(P, dir, tmin, tmax, subdivisions, curve, isect)) { isect->prim = prim; isect->object = object; isect->type = type; @@ -655,7 +661,7 @@ ccl_device_forceinline bool curve_intersect(KernelGlobals kg, return false; } else { - if (curve_intersect_recursive(P, dir, tmax, curve, isect)) { + if (curve_intersect_recursive(P, dir, tmin, tmax, curve, isect)) { isect->prim = prim; isect->object = object; isect->type = type; diff --git a/intern/cycles/kernel/geom/motion_triangle_intersect.h b/intern/cycles/kernel/geom/motion_triangle_intersect.h index 6eea5096567..b59c5c43c20 100644 --- a/intern/cycles/kernel/geom/motion_triangle_intersect.h +++ b/intern/cycles/kernel/geom/motion_triangle_intersect.h @@ -46,6 +46,7 @@ ccl_device_inline bool motion_triangle_intersect(KernelGlobals kg, ccl_private Intersection *isect, float3 P, float3 dir, + float tmin, float tmax, float time, uint visibility, @@ -58,7 +59,7 @@ ccl_device_inline bool motion_triangle_intersect(KernelGlobals kg, motion_triangle_vertices(kg, object, prim, time, verts); /* Ray-triangle intersection, unoptimized. */ float t, u, v; - if (ray_triangle_intersect(P, dir, tmax, verts[0], verts[1], verts[2], &u, &v, &t)) { + if (ray_triangle_intersect(P, dir, tmin, tmax, verts[0], verts[1], verts[2], &u, &v, &t)) { #ifdef __VISIBILITY_FLAG__ /* Visibility flag test. we do it here under the assumption * that most triangles are culled by node flags. @@ -92,6 +93,7 @@ ccl_device_inline bool motion_triangle_intersect_local(KernelGlobals kg, int object, int prim, int prim_addr, + float tmin, float tmax, ccl_private uint *lcg_state, int max_hits) @@ -101,7 +103,7 @@ ccl_device_inline bool motion_triangle_intersect_local(KernelGlobals kg, motion_triangle_vertices(kg, object, prim, time, verts); /* Ray-triangle intersection, unoptimized. */ float t, u, v; - if (!ray_triangle_intersect(P, dir, tmax, verts[0], verts[1], verts[2], &u, &v, &t)) { + if (!ray_triangle_intersect(P, dir, tmin, tmax, verts[0], verts[1], verts[2], &u, &v, &t)) { return false; } diff --git a/intern/cycles/kernel/geom/point_intersect.h b/intern/cycles/kernel/geom/point_intersect.h index dfd9d9a015b..ee5a564947b 100644 --- a/intern/cycles/kernel/geom/point_intersect.h +++ b/intern/cycles/kernel/geom/point_intersect.h @@ -9,8 +9,12 @@ CCL_NAMESPACE_BEGIN #ifdef __POINTCLOUD__ -ccl_device_forceinline bool point_intersect_test( - const float4 point, const float3 P, const float3 dir, const float tmax, ccl_private float *t) +ccl_device_forceinline bool point_intersect_test(const float4 point, + const float3 P, + const float3 dir, + const float tmin, + const float tmax, + ccl_private float *t) { const float3 center = float4_to_float3(point); const float radius = point.w; @@ -28,12 +32,12 @@ ccl_device_forceinline bool point_intersect_test( const float td = sqrt((r2 - l2) * rd2); const float t_front = projC0 - td; - const bool valid_front = (0.0f <= t_front) & (t_front <= tmax); + const bool valid_front = (tmin <= t_front) & (t_front <= tmax); /* Always back-face culling for now. */ # if 0 const float t_back = projC0 + td; - const bool valid_back = (0.0f <= t_back) & (t_back <= tmax); + const bool valid_back = (tmin <= t_back) & (t_back <= tmax); /* check if there is a first hit */ const bool valid_first = valid_front | valid_back; @@ -56,6 +60,7 @@ ccl_device_forceinline bool point_intersect(KernelGlobals kg, ccl_private Intersection *isect, const float3 P, const float3 dir, + const float tmin, const float tmax, const int object, const int prim, @@ -65,7 +70,7 @@ ccl_device_forceinline bool point_intersect(KernelGlobals kg, const float4 point = (type & PRIMITIVE_MOTION) ? motion_point(kg, object, prim, time) : kernel_data_fetch(points, prim); - if (!point_intersect_test(point, P, dir, tmax, &isect->t)) { + if (!point_intersect_test(point, P, dir, tmin, tmax, &isect->t)) { return false; } diff --git a/intern/cycles/kernel/geom/shader_data.h b/intern/cycles/kernel/geom/shader_data.h index e5dbeac5e66..99b9289cb4a 100644 --- a/intern/cycles/kernel/geom/shader_data.h +++ b/intern/cycles/kernel/geom/shader_data.h @@ -407,7 +407,7 @@ ccl_device_inline void shader_setup_from_volume(KernelGlobals kg, { /* vectors */ - sd->P = ray->P; + sd->P = ray->P + ray->D * ray->tmin; sd->N = -ray->D; sd->Ng = -ray->D; sd->I = -ray->D; @@ -441,7 +441,6 @@ ccl_device_inline void shader_setup_from_volume(KernelGlobals kg, /* for NDC coordinates */ sd->ray_P = ray->P; - sd->ray_dP = ray->dP; } #endif /* __VOLUME__ */ diff --git a/intern/cycles/kernel/geom/triangle_intersect.h b/intern/cycles/kernel/geom/triangle_intersect.h index 0c76de9ccc7..f968e537cfa 100644 --- a/intern/cycles/kernel/geom/triangle_intersect.h +++ b/intern/cycles/kernel/geom/triangle_intersect.h @@ -17,6 +17,7 @@ ccl_device_inline bool triangle_intersect(KernelGlobals kg, ccl_private Intersection *isect, float3 P, float3 dir, + float tmin, float tmax, uint visibility, int object, @@ -28,7 +29,7 @@ ccl_device_inline bool triangle_intersect(KernelGlobals kg, tri_b = kernel_data_fetch(tri_verts, tri_vindex + 1), tri_c = kernel_data_fetch(tri_verts, tri_vindex + 2); float t, u, v; - if (ray_triangle_intersect(P, dir, tmax, tri_a, tri_b, tri_c, &u, &v, &t)) { + if (ray_triangle_intersect(P, dir, tmin, tmax, tri_a, tri_b, tri_c, &u, &v, &t)) { #ifdef __VISIBILITY_FLAG__ /* Visibility flag test. we do it here under the assumption * that most triangles are culled by node flags. @@ -62,6 +63,7 @@ ccl_device_inline bool triangle_intersect_local(KernelGlobals kg, int object, int prim, int prim_addr, + float tmin, float tmax, ccl_private uint *lcg_state, int max_hits) @@ -71,7 +73,7 @@ ccl_device_inline bool triangle_intersect_local(KernelGlobals kg, tri_b = kernel_data_fetch(tri_verts, tri_vindex + 1), tri_c = kernel_data_fetch(tri_verts, tri_vindex + 2); float t, u, v; - if (!ray_triangle_intersect(P, dir, tmax, tri_a, tri_b, tri_c, &u, &v, &t)) { + if (!ray_triangle_intersect(P, dir, tmin, tmax, tri_a, tri_b, tri_c, &u, &v, &t)) { return false; } diff --git a/intern/cycles/kernel/integrator/init_from_bake.h b/intern/cycles/kernel/integrator/init_from_bake.h index afd174de9e8..bf3f41b52b9 100644 --- a/intern/cycles/kernel/integrator/init_from_bake.h +++ b/intern/cycles/kernel/integrator/init_from_bake.h @@ -174,7 +174,8 @@ ccl_device bool integrator_init_from_bake(KernelGlobals kg, Ray ray ccl_optional_struct_init; ray.P = zero_float3(); ray.D = normalize(P); - ray.t = FLT_MAX; + ray.tmin = 0.0f; + ray.tmax = FLT_MAX; ray.time = 0.5f; ray.dP = differential_zero_compact(); ray.dD = differential_zero_compact(); @@ -210,7 +211,8 @@ ccl_device bool integrator_init_from_bake(KernelGlobals kg, Ray ray ccl_optional_struct_init; ray.P = P + N; ray.D = -N; - ray.t = FLT_MAX; + ray.tmin = 0.0f; + ray.tmax = FLT_MAX; ray.time = 0.5f; /* Setup differentials. */ diff --git a/intern/cycles/kernel/integrator/init_from_camera.h b/intern/cycles/kernel/integrator/init_from_camera.h index 73db13be697..e89ab3991c7 100644 --- a/intern/cycles/kernel/integrator/init_from_camera.h +++ b/intern/cycles/kernel/integrator/init_from_camera.h @@ -86,7 +86,7 @@ ccl_device bool integrator_init_from_camera(KernelGlobals kg, /* Generate camera ray. */ Ray ray; integrate_camera_sample(kg, sample, x, y, rng_hash, &ray); - if (ray.t == 0.0f) { + if (ray.tmax == 0.0f) { return true; } diff --git a/intern/cycles/kernel/integrator/intersect_closest.h b/intern/cycles/kernel/integrator/intersect_closest.h index 923dab9591a..60299f2cb2f 100644 --- a/intern/cycles/kernel/integrator/intersect_closest.h +++ b/intern/cycles/kernel/integrator/intersect_closest.h @@ -324,7 +324,7 @@ ccl_device void integrator_intersect_closest(KernelGlobals kg, /* Read ray from integrator state into local memory. */ Ray ray ccl_optional_struct_init; integrator_state_read_ray(kg, state, &ray); - kernel_assert(ray.t != 0.0f); + kernel_assert(ray.tmax != 0.0f); const uint visibility = path_state_ray_visibility(state); const int last_isect_prim = INTEGRATOR_STATE(state, isect, prim); @@ -332,12 +332,12 @@ ccl_device void integrator_intersect_closest(KernelGlobals kg, /* Trick to use short AO rays to approximate indirect light at the end of the path. */ if (path_state_ao_bounce(kg, state)) { - ray.t = kernel_data.integrator.ao_bounces_distance; + ray.tmax = kernel_data.integrator.ao_bounces_distance; if (last_isect_object != OBJECT_NONE) { const float object_ao_distance = kernel_data_fetch(objects, last_isect_object).ao_distance; if (object_ao_distance != 0.0f) { - ray.t = object_ao_distance; + ray.tmax = object_ao_distance; } } } diff --git a/intern/cycles/kernel/integrator/intersect_volume_stack.h b/intern/cycles/kernel/integrator/intersect_volume_stack.h index 78f0b4d62aa..9ba4a0a3964 100644 --- a/intern/cycles/kernel/integrator/intersect_volume_stack.h +++ b/intern/cycles/kernel/integrator/intersect_volume_stack.h @@ -24,7 +24,8 @@ ccl_device void integrator_volume_stack_update_for_subsurface(KernelGlobals kg, Ray volume_ray ccl_optional_struct_init; volume_ray.P = from_P; - volume_ray.D = normalize_len(to_P - from_P, &volume_ray.t); + volume_ray.D = normalize_len(to_P - from_P, &volume_ray.tmax); + volume_ray.tmin = 0.0f; volume_ray.self.object = INTEGRATOR_STATE(state, isect, object); volume_ray.self.prim = INTEGRATOR_STATE(state, isect, prim); volume_ray.self.light_object = OBJECT_NONE; @@ -58,12 +59,9 @@ ccl_device void integrator_volume_stack_update_for_subsurface(KernelGlobals kg, volume_stack_enter_exit(kg, state, stack_sd); /* Move ray forward. */ - volume_ray.P = stack_sd->P; + volume_ray.tmin = intersection_t_offset(isect.t); volume_ray.self.object = isect.object; volume_ray.self.prim = isect.prim; - if (volume_ray.t != FLT_MAX) { - volume_ray.D = normalize_len(to_P - volume_ray.P, &volume_ray.t); - } ++step; } #endif @@ -82,7 +80,8 @@ ccl_device void integrator_volume_stack_init(KernelGlobals kg, IntegratorState s /* Trace ray in random direction. Any direction works, Z up is a guess to get the * fewest hits. */ volume_ray.D = make_float3(0.0f, 0.0f, 1.0f); - volume_ray.t = FLT_MAX; + volume_ray.tmin = 0.0f; + volume_ray.tmax = FLT_MAX; volume_ray.self.object = OBJECT_NONE; volume_ray.self.prim = PRIM_NONE; volume_ray.self.light_object = OBJECT_NONE; @@ -199,7 +198,7 @@ ccl_device void integrator_volume_stack_init(KernelGlobals kg, IntegratorState s } /* Move ray forward. */ - volume_ray.P = stack_sd->P; + volume_ray.tmin = intersection_t_offset(isect.t); volume_ray.self.object = isect.object; volume_ray.self.prim = isect.prim; ++step; diff --git a/intern/cycles/kernel/integrator/mnee.h b/intern/cycles/kernel/integrator/mnee.h index 70b009d3b48..7a6f866b1a0 100644 --- a/intern/cycles/kernel/integrator/mnee.h +++ b/intern/cycles/kernel/integrator/mnee.h @@ -442,6 +442,7 @@ ccl_device_forceinline bool mnee_newton_solver(KernelGlobals kg, projection_ray.self.light_prim = PRIM_NONE; projection_ray.dP = differential_make_compact(sd->dP); projection_ray.dD = differential_zero_compact(); + projection_ray.tmin = 0.0f; projection_ray.time = sd->time; Intersection projection_isect; @@ -505,8 +506,8 @@ ccl_device_forceinline bool mnee_newton_solver(KernelGlobals kg, projection_ray.self.prim = pv.prim; projection_ray.P = pv.p; } - projection_ray.D = normalize_len(tentative_p - projection_ray.P, &projection_ray.t); - projection_ray.t *= MNEE_PROJECTION_DISTANCE_MULTIPLIER; + projection_ray.D = normalize_len(tentative_p - projection_ray.P, &projection_ray.tmax); + projection_ray.tmax *= MNEE_PROJECTION_DISTANCE_MULTIPLIER; bool projection_success = false; for (int isect_count = 0; isect_count < MNEE_MAX_INTERSECTION_COUNT; isect_count++) { @@ -525,8 +526,7 @@ ccl_device_forceinline bool mnee_newton_solver(KernelGlobals kg, projection_ray.self.object = projection_isect.object; projection_ray.self.prim = projection_isect.prim; - projection_ray.P += projection_isect.t * projection_ray.D; - projection_ray.t -= projection_isect.t; + projection_ray.tmin = intersection_t_offset(projection_isect.t); } if (!projection_success) { reduce_stepsize = true; @@ -858,6 +858,7 @@ ccl_device_forceinline bool mnee_path_contribution(KernelGlobals kg, Ray probe_ray; probe_ray.self.light_object = ls->object; probe_ray.self.light_prim = ls->prim; + probe_ray.tmin = 0.0f; probe_ray.dP = differential_make_compact(sd->dP); probe_ray.dD = differential_zero_compact(); probe_ray.time = sd->time; @@ -873,13 +874,13 @@ ccl_device_forceinline bool mnee_path_contribution(KernelGlobals kg, ccl_private const ManifoldVertex &v = vertices[vi]; /* Check visibility. */ - probe_ray.D = normalize_len(v.p - probe_ray.P, &probe_ray.t); + probe_ray.D = normalize_len(v.p - probe_ray.P, &probe_ray.tmax); if (scene_intersect(kg, &probe_ray, PATH_RAY_TRANSMIT, &probe_isect)) { int hit_object = (probe_isect.object == OBJECT_NONE) ? kernel_data_fetch(prim_object, probe_isect.prim) : probe_isect.object; /* Test whether the ray hit the appropriate object at its intended location. */ - if (hit_object != v.object || fabsf(probe_ray.t - probe_isect.t) > MNEE_MIN_DISTANCE) + if (hit_object != v.object || fabsf(probe_ray.tmax - probe_isect.t) > MNEE_MIN_DISTANCE) return false; } probe_ray.self.object = v.object; @@ -958,15 +959,16 @@ ccl_device_forceinline int kernel_path_mnee_sample(KernelGlobals kg, probe_ray.self.light_object = ls->object; probe_ray.self.light_prim = ls->prim; probe_ray.P = sd->P; + probe_ray.tmin = 0.0f; if (ls->t == FLT_MAX) { /* Distant / env light. */ probe_ray.D = ls->D; - probe_ray.t = ls->t; + probe_ray.tmax = ls->t; } else { /* Other lights, avoid self-intersection. */ probe_ray.D = ls->P - probe_ray.P; - probe_ray.D = normalize_len(probe_ray.D, &probe_ray.t); + probe_ray.D = normalize_len(probe_ray.D, &probe_ray.tmax); } probe_ray.dP = differential_make_compact(sd->dP); probe_ray.dD = differential_zero_compact(); @@ -1048,9 +1050,7 @@ ccl_device_forceinline int kernel_path_mnee_sample(KernelGlobals kg, probe_ray.self.object = probe_isect.object; probe_ray.self.prim = probe_isect.prim; - probe_ray.P += probe_isect.t * probe_ray.D; - if (ls->t != FLT_MAX) - probe_ray.t -= probe_isect.t; + probe_ray.tmin = intersection_t_offset(probe_isect.t); }; /* Mark the manifold walk invalid to keep mollification on by default. */ diff --git a/intern/cycles/kernel/integrator/path_state.h b/intern/cycles/kernel/integrator/path_state.h index 1a085506a70..912c380cdb6 100644 --- a/intern/cycles/kernel/integrator/path_state.h +++ b/intern/cycles/kernel/integrator/path_state.h @@ -52,7 +52,6 @@ ccl_device_inline void path_state_init_integrator(KernelGlobals kg, INTEGRATOR_STATE_WRITE(state, path, flag) = PATH_RAY_CAMERA | PATH_RAY_MIS_SKIP | PATH_RAY_TRANSPARENT_BACKGROUND; INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = 0.0f; - INTEGRATOR_STATE_WRITE(state, path, mis_ray_t) = 0.0f; INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = FLT_MAX; INTEGRATOR_STATE_WRITE(state, path, continuation_probability) = 1.0f; INTEGRATOR_STATE_WRITE(state, path, throughput) = make_float3(1.0f, 1.0f, 1.0f); diff --git a/intern/cycles/kernel/integrator/shade_background.h b/intern/cycles/kernel/integrator/shade_background.h index 47233634463..a7edfffd175 100644 --- a/intern/cycles/kernel/integrator/shade_background.h +++ b/intern/cycles/kernel/integrator/shade_background.h @@ -62,11 +62,10 @@ ccl_device float3 integrator_eval_background_shader(KernelGlobals kg, const float3 ray_P = INTEGRATOR_STATE(state, ray, P); const float3 ray_D = INTEGRATOR_STATE(state, ray, D); const float mis_ray_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf); - const float mis_ray_t = INTEGRATOR_STATE(state, path, mis_ray_t); /* multiple importance sampling, get background light pdf for ray * direction, and compute weight with respect to BSDF pdf */ - const float pdf = background_light_pdf(kg, ray_P - ray_D * mis_ray_t, ray_D); + const float pdf = background_light_pdf(kg, ray_P, ray_D); const float mis_weight = light_sample_mis_weight_forward(kg, mis_ray_pdf, pdf); L *= mis_weight; } diff --git a/intern/cycles/kernel/integrator/shade_light.h b/intern/cycles/kernel/integrator/shade_light.h index 1a5ac3637f6..910e3383f51 100644 --- a/intern/cycles/kernel/integrator/shade_light.h +++ b/intern/cycles/kernel/integrator/shade_light.h @@ -22,19 +22,8 @@ ccl_device_inline void integrate_light(KernelGlobals kg, const float3 ray_D = INTEGRATOR_STATE(state, ray, D); const float ray_time = INTEGRATOR_STATE(state, ray, time); - /* Advance ray beyond light. */ - /* TODO: can we make this more numerically robust to avoid reintersecting the - * same light in some cases? Ray should not intersect surface anymore as the - * object and prim ids will prevent self intersection. */ - const float3 new_ray_P = ray_P + ray_D * isect.t; - INTEGRATOR_STATE_WRITE(state, ray, P) = new_ray_P; - INTEGRATOR_STATE_WRITE(state, ray, t) -= isect.t; - - /* Set position to where the BSDF was sampled, for correct MIS PDF. */ - const float mis_ray_t = INTEGRATOR_STATE(state, path, mis_ray_t); - ray_P -= ray_D * mis_ray_t; - isect.t += mis_ray_t; - INTEGRATOR_STATE_WRITE(state, path, mis_ray_t) = isect.t; + /* Advance ray to new start distance. */ + INTEGRATOR_STATE_WRITE(state, ray, tmin) = intersection_t_offset(isect.t); LightSample ls ccl_optional_struct_init; const bool use_light_sample = light_sample_from_intersection(kg, &isect, ray_P, ray_D, &ls); diff --git a/intern/cycles/kernel/integrator/shade_shadow.h b/intern/cycles/kernel/integrator/shade_shadow.h index 830da0158cf..4b002a47bee 100644 --- a/intern/cycles/kernel/integrator/shade_shadow.h +++ b/intern/cycles/kernel/integrator/shade_shadow.h @@ -75,13 +75,9 @@ ccl_device_inline void integrate_transparent_volume_shadow(KernelGlobals kg, ray.self.light_object = OBJECT_NONE; ray.self.light_prim = PRIM_NONE; /* Modify ray position and length to match current segment. */ - const float start_t = (hit == 0) ? 0.0f : - INTEGRATOR_STATE_ARRAY(state, shadow_isect, hit - 1, t); - const float end_t = (hit < num_recorded_hits) ? - INTEGRATOR_STATE_ARRAY(state, shadow_isect, hit, t) : - ray.t; - ray.P += start_t * ray.D; - ray.t = end_t - start_t; + ray.tmin = (hit == 0) ? ray.tmin : INTEGRATOR_STATE_ARRAY(state, shadow_isect, hit - 1, t); + ray.tmax = (hit < num_recorded_hits) ? INTEGRATOR_STATE_ARRAY(state, shadow_isect, hit, t) : + ray.tmax; shader_setup_from_volume(kg, shadow_sd, &ray); @@ -137,10 +133,7 @@ ccl_device_inline bool integrate_transparent_shadow(KernelGlobals kg, /* There are more hits that we could not recorded due to memory usage, * adjust ray to intersect again from the last hit. */ const float last_hit_t = INTEGRATOR_STATE_ARRAY(state, shadow_isect, num_recorded_hits - 1, t); - const float3 ray_P = INTEGRATOR_STATE(state, shadow_ray, P); - const float3 ray_D = INTEGRATOR_STATE(state, shadow_ray, D); - INTEGRATOR_STATE_WRITE(state, shadow_ray, P) = ray_P + last_hit_t * ray_D; - INTEGRATOR_STATE_WRITE(state, shadow_ray, t) -= last_hit_t; + INTEGRATOR_STATE_WRITE(state, shadow_ray, tmin) = intersection_t_offset(last_hit_t); } return false; diff --git a/intern/cycles/kernel/integrator/shade_surface.h b/intern/cycles/kernel/integrator/shade_surface.h index 91e34968148..1514b3956ad 100644 --- a/intern/cycles/kernel/integrator/shade_surface.h +++ b/intern/cycles/kernel/integrator/shade_surface.h @@ -77,7 +77,7 @@ ccl_device_forceinline void integrate_surface_emission(KernelGlobals kg, # endif { const float bsdf_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf); - const float t = sd->ray_length + INTEGRATOR_STATE(state, path, mis_ray_t); + const float t = sd->ray_length; /* Multiple importance sampling, get triangle light pdf, * and compute weight with respect to BSDF pdf. */ @@ -323,16 +323,21 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce( return LABEL_NONE; } - /* Setup ray. Note that clipping works through transparent bounces. */ - INTEGRATOR_STATE_WRITE(state, ray, P) = sd->P; - INTEGRATOR_STATE_WRITE(state, ray, D) = normalize(bsdf_omega_in); - INTEGRATOR_STATE_WRITE(state, ray, t) = (label & LABEL_TRANSPARENT) ? - INTEGRATOR_STATE(state, ray, t) - sd->ray_length : - FLT_MAX; + if (label & LABEL_TRANSPARENT) { + /* Only need to modify start distance for transparent. */ + INTEGRATOR_STATE_WRITE(state, ray, tmin) = intersection_t_offset(sd->ray_length); + } + else { + /* Setup ray with changed origin and direction. */ + INTEGRATOR_STATE_WRITE(state, ray, P) = sd->P; + INTEGRATOR_STATE_WRITE(state, ray, D) = normalize(bsdf_omega_in); + INTEGRATOR_STATE_WRITE(state, ray, tmin) = 0.0f; + INTEGRATOR_STATE_WRITE(state, ray, tmax) = FLT_MAX; #ifdef __RAY_DIFFERENTIALS__ - INTEGRATOR_STATE_WRITE(state, ray, dP) = differential_make_compact(sd->dP); - INTEGRATOR_STATE_WRITE(state, ray, dD) = differential_make_compact(bsdf_domega_in); + INTEGRATOR_STATE_WRITE(state, ray, dP) = differential_make_compact(sd->dP); + INTEGRATOR_STATE_WRITE(state, ray, dD) = differential_make_compact(bsdf_domega_in); #endif + } /* Update throughput. */ float3 throughput = INTEGRATOR_STATE(state, path, throughput); @@ -349,12 +354,8 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce( } /* Update path state */ - if (label & LABEL_TRANSPARENT) { - INTEGRATOR_STATE_WRITE(state, path, mis_ray_t) += sd->ray_length; - } - else { + if (!(label & LABEL_TRANSPARENT)) { INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = bsdf_pdf; - INTEGRATOR_STATE_WRITE(state, path, mis_ray_t) = 0.0f; INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = fminf( bsdf_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf)); } @@ -371,17 +372,8 @@ ccl_device_forceinline int integrate_surface_volume_only_bounce(IntegratorState return LABEL_NONE; } - /* Setup ray position, direction stays unchanged. */ - INTEGRATOR_STATE_WRITE(state, ray, P) = sd->P; - - /* Clipping works through transparent. */ - INTEGRATOR_STATE_WRITE(state, ray, t) -= sd->ray_length; - -# ifdef __RAY_DIFFERENTIALS__ - INTEGRATOR_STATE_WRITE(state, ray, dP) = differential_make_compact(sd->dP); -# endif - - INTEGRATOR_STATE_WRITE(state, path, mis_ray_t) += sd->ray_length; + /* Only modify start distance. */ + INTEGRATOR_STATE_WRITE(state, ray, tmin) = intersection_t_offset(sd->ray_length); return LABEL_TRANSMIT | LABEL_TRANSPARENT; } @@ -432,7 +424,8 @@ ccl_device_forceinline void integrate_surface_ao(KernelGlobals kg, Ray ray ccl_optional_struct_init; ray.P = shadow_ray_offset(kg, sd, ao_D, &skip_self); ray.D = ao_D; - ray.t = kernel_data.integrator.ao_bounces_distance; + ray.tmin = 0.0f; + ray.tmax = kernel_data.integrator.ao_bounces_distance; ray.time = sd->time; ray.self.object = (skip_self) ? sd->object : OBJECT_NONE; ray.self.prim = (skip_self) ? sd->prim : PRIM_NONE; @@ -616,7 +609,7 @@ ccl_device_forceinline void integrator_shade_surface(KernelGlobals kg, kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE); } else { - kernel_assert(INTEGRATOR_STATE(state, ray, t) != 0.0f); + kernel_assert(INTEGRATOR_STATE(state, ray, tmax) != 0.0f); integrator_path_next(kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST); } } diff --git a/intern/cycles/kernel/integrator/shade_volume.h b/intern/cycles/kernel/integrator/shade_volume.h index 683c031f0d9..4aab097a7d8 100644 --- a/intern/cycles/kernel/integrator/shade_volume.h +++ b/intern/cycles/kernel/integrator/shade_volume.h @@ -114,7 +114,8 @@ ccl_device_inline bool volume_shader_sample(KernelGlobals kg, ccl_device_forceinline void volume_step_init(KernelGlobals kg, ccl_private const RNGState *rng_state, const float object_step_size, - float t, + const float tmin, + const float tmax, ccl_private float *step_size, ccl_private float *step_shade_offset, ccl_private float *steps_offset, @@ -122,7 +123,7 @@ ccl_device_forceinline void volume_step_init(KernelGlobals kg, { if (object_step_size == FLT_MAX) { /* Homogeneous volume. */ - *step_size = t; + *step_size = tmax - tmin; *step_shade_offset = 0.0f; *steps_offset = 1.0f; *max_steps = 1; @@ -130,6 +131,7 @@ ccl_device_forceinline void volume_step_init(KernelGlobals kg, else { /* Heterogeneous volume. */ *max_steps = kernel_data.integrator.volume_max_steps; + const float t = tmax - tmin; float step = min(object_step_size, t); /* compute exact steps in advance for malloc */ @@ -165,7 +167,7 @@ ccl_device void volume_shadow_homogeneous(KernelGlobals kg, IntegratorState stat float3 sigma_t = zero_float3(); if (shadow_volume_shader_sample(kg, state, sd, &sigma_t)) { - *throughput *= volume_color_transmittance(sigma_t, ray->t); + *throughput *= volume_color_transmittance(sigma_t, ray->tmax - ray->tmin); } } # endif @@ -194,7 +196,8 @@ ccl_device void volume_shadow_heterogeneous(KernelGlobals kg, volume_step_init(kg, &rng_state, object_step_size, - ray->t, + ray->tmin, + ray->tmax, &step_size, &step_shade_offset, &unused, @@ -202,13 +205,13 @@ ccl_device void volume_shadow_heterogeneous(KernelGlobals kg, const float steps_offset = 1.0f; /* compute extinction at the start */ - float t = 0.0f; + float t = ray->tmin; float3 sum = zero_float3(); for (int i = 0; i < max_steps; i++) { /* advance to new position */ - float new_t = min(ray->t, (i + steps_offset) * step_size); + float new_t = min(ray->tmax, ray->tmin + (i + steps_offset) * step_size); float dt = new_t - t; float3 new_P = ray->P + ray->D * (t + dt * step_shade_offset); @@ -233,7 +236,7 @@ ccl_device void volume_shadow_heterogeneous(KernelGlobals kg, /* stop if at the end of the volume */ t = new_t; - if (t == ray->t) { + if (t == ray->tmax) { /* Update throughput in case we haven't done it above */ tp = *throughput * exp(sum); break; @@ -257,15 +260,16 @@ ccl_device float volume_equiangular_sample(ccl_private const Ray *ccl_restrict r const float xi, ccl_private float *pdf) { - const float t = ray->t; + const float tmin = ray->tmin; + const float tmax = ray->tmax; const float delta = dot((light_P - ray->P), ray->D); const float D = safe_sqrtf(len_squared(light_P - ray->P) - delta * delta); if (UNLIKELY(D == 0.0f)) { *pdf = 0.0f; return 0.0f; } - const float theta_a = -atan2f(delta, D); - const float theta_b = atan2f(t - delta, D); + const float theta_a = atan2f(tmin - delta, D); + const float theta_b = atan2f(tmax - delta, D); const float t_ = D * tanf((xi * theta_b) + (1 - xi) * theta_a); if (UNLIKELY(theta_b == theta_a)) { *pdf = 0.0f; @@ -273,7 +277,7 @@ ccl_device float volume_equiangular_sample(ccl_private const Ray *ccl_restrict r } *pdf = D / ((theta_b - theta_a) * (D * D + t_ * t_)); - return min(t, delta + t_); /* min is only for float precision errors */ + return clamp(delta + t_, tmin, tmax); /* clamp is only for float precision errors */ } ccl_device float volume_equiangular_pdf(ccl_private const Ray *ccl_restrict ray, @@ -286,11 +290,12 @@ ccl_device float volume_equiangular_pdf(ccl_private const Ray *ccl_restrict ray, return 0.0f; } - const float t = ray->t; + const float tmin = ray->tmin; + const float tmax = ray->tmax; const float t_ = sample_t - delta; - const float theta_a = -atan2f(delta, D); - const float theta_b = atan2f(t - delta, D); + const float theta_a = atan2f(tmin - delta, D); + const float theta_b = atan2f(tmax - delta, D); if (UNLIKELY(theta_b == theta_a)) { return 0.0f; } @@ -310,11 +315,12 @@ ccl_device float volume_equiangular_cdf(ccl_private const Ray *ccl_restrict ray, return 0.0f; } - const float t = ray->t; + const float tmin = ray->tmin; + const float tmax = ray->tmax; const float t_ = sample_t - delta; - const float theta_a = -atan2f(delta, D); - const float theta_b = atan2f(t - delta, D); + const float theta_a = atan2f(tmin - delta, D); + const float theta_b = atan2f(tmax - delta, D); if (UNLIKELY(theta_b == theta_a)) { return 0.0f; } @@ -390,8 +396,8 @@ ccl_device float3 volume_emission_integrate(ccl_private VolumeShaderCoefficients typedef struct VolumeIntegrateState { /* Volume segment extents. */ - float start_t; - float end_t; + float tmin; + float tmax; /* If volume is absorption-only up to this point, and no probabilistic * scattering or termination has been used yet. */ @@ -426,9 +432,9 @@ ccl_device_forceinline void volume_integrate_step_scattering( /* Equiangular sampling for direct lighting. */ if (vstate.direct_sample_method == VOLUME_SAMPLE_EQUIANGULAR && !result.direct_scatter) { - if (result.direct_t >= vstate.start_t && result.direct_t <= vstate.end_t && + if (result.direct_t >= vstate.tmin && result.direct_t <= vstate.tmax && vstate.equiangular_pdf > VOLUME_SAMPLE_PDF_CUTOFF) { - const float new_dt = result.direct_t - vstate.start_t; + const float new_dt = result.direct_t - vstate.tmin; const float3 new_transmittance = volume_color_transmittance(coeff.sigma_t, new_dt); result.direct_scatter = true; @@ -458,7 +464,7 @@ ccl_device_forceinline void volume_integrate_step_scattering( /* compute sampling distance */ const float sample_sigma_t = volume_channel_get(coeff.sigma_t, channel); const float new_dt = -logf(1.0f - vstate.rscatter) / sample_sigma_t; - const float new_t = vstate.start_t + new_dt; + const float new_t = vstate.tmin + new_dt; /* transmittance and pdf */ const float3 new_transmittance = volume_color_transmittance(coeff.sigma_t, new_dt); @@ -528,7 +534,8 @@ ccl_device_forceinline void volume_integrate_heterogeneous( volume_step_init(kg, rng_state, object_step_size, - ray->t, + ray->tmin, + ray->tmax, &step_size, &step_shade_offset, &steps_offset, @@ -536,8 +543,8 @@ ccl_device_forceinline void volume_integrate_heterogeneous( /* Initialize volume integration state. */ VolumeIntegrateState vstate ccl_optional_struct_init; - vstate.start_t = 0.0f; - vstate.end_t = 0.0f; + vstate.tmin = ray->tmin; + vstate.tmax = ray->tmin; vstate.absorption_only = true; vstate.rscatter = path_state_rng_1D(kg, rng_state, PRNG_SCATTER_DISTANCE); vstate.rphase = path_state_rng_1D(kg, rng_state, PRNG_PHASE_CHANNEL); @@ -578,8 +585,8 @@ ccl_device_forceinline void volume_integrate_heterogeneous( for (int i = 0; i < max_steps; i++) { /* Advance to new position */ - vstate.end_t = min(ray->t, (i + steps_offset) * step_size); - const float shade_t = vstate.start_t + (vstate.end_t - vstate.start_t) * step_shade_offset; + vstate.tmax = min(ray->tmax, ray->tmin + (i + steps_offset) * step_size); + const float shade_t = vstate.tmin + (vstate.tmax - vstate.tmin) * step_shade_offset; sd->P = ray->P + ray->D * shade_t; /* compute segment */ @@ -588,7 +595,7 @@ ccl_device_forceinline void volume_integrate_heterogeneous( const int closure_flag = sd->flag; /* Evaluate transmittance over segment. */ - const float dt = (vstate.end_t - vstate.start_t); + const float dt = (vstate.tmax - vstate.tmin); const float3 transmittance = (closure_flag & SD_EXTINCTION) ? volume_color_transmittance(coeff.sigma_t, dt) : one_float3(); @@ -645,8 +652,8 @@ ccl_device_forceinline void volume_integrate_heterogeneous( } /* Stop if at the end of the volume. */ - vstate.start_t = vstate.end_t; - if (vstate.start_t == ray->t) { + vstate.tmin = vstate.tmax; + if (vstate.tmin == ray->tmax) { break; } } @@ -880,7 +887,8 @@ ccl_device_forceinline bool integrate_volume_phase_scatter( /* Setup ray. */ INTEGRATOR_STATE_WRITE(state, ray, P) = sd->P; INTEGRATOR_STATE_WRITE(state, ray, D) = normalize(phase_omega_in); - INTEGRATOR_STATE_WRITE(state, ray, t) = FLT_MAX; + INTEGRATOR_STATE_WRITE(state, ray, tmin) = 0.0f; + INTEGRATOR_STATE_WRITE(state, ray, tmax) = FLT_MAX; # ifdef __RAY_DIFFERENTIALS__ INTEGRATOR_STATE_WRITE(state, ray, dP) = differential_make_compact(sd->dP); INTEGRATOR_STATE_WRITE(state, ray, dD) = differential_make_compact(phase_domega_in); @@ -901,7 +909,6 @@ ccl_device_forceinline bool integrate_volume_phase_scatter( /* Update path state */ INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = phase_pdf; - INTEGRATOR_STATE_WRITE(state, path, mis_ray_t) = 0.0f; INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = fminf( phase_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf)); @@ -1021,7 +1028,7 @@ ccl_device void integrator_shade_volume(KernelGlobals kg, integrator_state_read_isect(kg, state, &isect); /* Set ray length to current segment. */ - ray.t = (isect.prim != PRIM_NONE) ? isect.t : FLT_MAX; + ray.tmax = (isect.prim != PRIM_NONE) ? isect.t : FLT_MAX; /* Clean volume stack for background rays. */ if (isect.prim == PRIM_NONE) { diff --git a/intern/cycles/kernel/integrator/shadow_state_template.h b/intern/cycles/kernel/integrator/shadow_state_template.h index eaee65ada40..c340467606d 100644 --- a/intern/cycles/kernel/integrator/shadow_state_template.h +++ b/intern/cycles/kernel/integrator/shadow_state_template.h @@ -47,7 +47,8 @@ KERNEL_STRUCT_END(shadow_path) KERNEL_STRUCT_BEGIN(shadow_ray) KERNEL_STRUCT_MEMBER(shadow_ray, packed_float3, P, KERNEL_FEATURE_PATH_TRACING) KERNEL_STRUCT_MEMBER(shadow_ray, packed_float3, D, KERNEL_FEATURE_PATH_TRACING) -KERNEL_STRUCT_MEMBER(shadow_ray, float, t, KERNEL_FEATURE_PATH_TRACING) +KERNEL_STRUCT_MEMBER(shadow_ray, float, tmin, KERNEL_FEATURE_PATH_TRACING) +KERNEL_STRUCT_MEMBER(shadow_ray, float, tmax, KERNEL_FEATURE_PATH_TRACING) KERNEL_STRUCT_MEMBER(shadow_ray, float, time, KERNEL_FEATURE_PATH_TRACING) KERNEL_STRUCT_MEMBER(shadow_ray, float, dP, KERNEL_FEATURE_PATH_TRACING) KERNEL_STRUCT_MEMBER(shadow_ray, int, object, KERNEL_FEATURE_PATH_TRACING) diff --git a/intern/cycles/kernel/integrator/state_template.h b/intern/cycles/kernel/integrator/state_template.h index e7e6db037b0..5c2af131945 100644 --- a/intern/cycles/kernel/integrator/state_template.h +++ b/intern/cycles/kernel/integrator/state_template.h @@ -37,11 +37,10 @@ KERNEL_STRUCT_MEMBER(path, uint32_t, flag, KERNEL_FEATURE_PATH_TRACING) /* enum PathRayMNEE */ KERNEL_STRUCT_MEMBER(path, uint8_t, mnee, KERNEL_FEATURE_PATH_TRACING) /* Multiple importance sampling - * The PDF of BSDF sampling at the last scatter point, and distance to the - * last scatter point minus the last ray segment. This distance lets us - * compute the complete distance through transparent surfaces and volumes. */ + * The PDF of BSDF sampling at the last scatter point, which is at ray distance + * zero and distance. Note that transparency and volume attenuation increase + * the ray tmin but keep P unmodified so that this works. */ KERNEL_STRUCT_MEMBER(path, float, mis_ray_pdf, KERNEL_FEATURE_PATH_TRACING) -KERNEL_STRUCT_MEMBER(path, float, mis_ray_t, KERNEL_FEATURE_PATH_TRACING) /* Filter glossy. */ KERNEL_STRUCT_MEMBER(path, float, min_ray_pdf, KERNEL_FEATURE_PATH_TRACING) /* Continuation probability for path termination. */ @@ -63,7 +62,8 @@ KERNEL_STRUCT_END(path) KERNEL_STRUCT_BEGIN(ray) KERNEL_STRUCT_MEMBER(ray, packed_float3, P, KERNEL_FEATURE_PATH_TRACING) KERNEL_STRUCT_MEMBER(ray, packed_float3, D, KERNEL_FEATURE_PATH_TRACING) -KERNEL_STRUCT_MEMBER(ray, float, t, KERNEL_FEATURE_PATH_TRACING) +KERNEL_STRUCT_MEMBER(ray, float, tmin, KERNEL_FEATURE_PATH_TRACING) +KERNEL_STRUCT_MEMBER(ray, float, tmax, KERNEL_FEATURE_PATH_TRACING) KERNEL_STRUCT_MEMBER(ray, float, time, KERNEL_FEATURE_PATH_TRACING) KERNEL_STRUCT_MEMBER(ray, float, dP, KERNEL_FEATURE_PATH_TRACING) KERNEL_STRUCT_MEMBER(ray, float, dD, KERNEL_FEATURE_PATH_TRACING) diff --git a/intern/cycles/kernel/integrator/state_util.h b/intern/cycles/kernel/integrator/state_util.h index 280db2d1aac..8dd58ad6bcd 100644 --- a/intern/cycles/kernel/integrator/state_util.h +++ b/intern/cycles/kernel/integrator/state_util.h @@ -17,7 +17,8 @@ ccl_device_forceinline void integrator_state_write_ray(KernelGlobals kg, { INTEGRATOR_STATE_WRITE(state, ray, P) = ray->P; INTEGRATOR_STATE_WRITE(state, ray, D) = ray->D; - INTEGRATOR_STATE_WRITE(state, ray, t) = ray->t; + INTEGRATOR_STATE_WRITE(state, ray, tmin) = ray->tmin; + INTEGRATOR_STATE_WRITE(state, ray, tmax) = ray->tmax; INTEGRATOR_STATE_WRITE(state, ray, time) = ray->time; INTEGRATOR_STATE_WRITE(state, ray, dP) = ray->dP; INTEGRATOR_STATE_WRITE(state, ray, dD) = ray->dD; @@ -29,7 +30,8 @@ ccl_device_forceinline void integrator_state_read_ray(KernelGlobals kg, { ray->P = INTEGRATOR_STATE(state, ray, P); ray->D = INTEGRATOR_STATE(state, ray, D); - ray->t = INTEGRATOR_STATE(state, ray, t); + ray->tmin = INTEGRATOR_STATE(state, ray, tmin); + ray->tmax = INTEGRATOR_STATE(state, ray, tmax); ray->time = INTEGRATOR_STATE(state, ray, time); ray->dP = INTEGRATOR_STATE(state, ray, dP); ray->dD = INTEGRATOR_STATE(state, ray, dD); @@ -42,7 +44,8 @@ ccl_device_forceinline void integrator_state_write_shadow_ray( { INTEGRATOR_STATE_WRITE(state, shadow_ray, P) = ray->P; INTEGRATOR_STATE_WRITE(state, shadow_ray, D) = ray->D; - INTEGRATOR_STATE_WRITE(state, shadow_ray, t) = ray->t; + INTEGRATOR_STATE_WRITE(state, shadow_ray, tmin) = ray->tmin; + INTEGRATOR_STATE_WRITE(state, shadow_ray, tmax) = ray->tmax; INTEGRATOR_STATE_WRITE(state, shadow_ray, time) = ray->time; INTEGRATOR_STATE_WRITE(state, shadow_ray, dP) = ray->dP; } @@ -53,7 +56,8 @@ ccl_device_forceinline void integrator_state_read_shadow_ray(KernelGlobals kg, { ray->P = INTEGRATOR_STATE(state, shadow_ray, P); ray->D = INTEGRATOR_STATE(state, shadow_ray, D); - ray->t = INTEGRATOR_STATE(state, shadow_ray, t); + ray->tmin = INTEGRATOR_STATE(state, shadow_ray, tmin); + ray->tmax = INTEGRATOR_STATE(state, shadow_ray, tmax); ray->time = INTEGRATOR_STATE(state, shadow_ray, time); ray->dP = INTEGRATOR_STATE(state, shadow_ray, dP); ray->dD = differential_zero_compact(); diff --git a/intern/cycles/kernel/integrator/subsurface.h b/intern/cycles/kernel/integrator/subsurface.h index ab26a2d93cc..2f96f215d8a 100644 --- a/intern/cycles/kernel/integrator/subsurface.h +++ b/intern/cycles/kernel/integrator/subsurface.h @@ -38,7 +38,8 @@ ccl_device int subsurface_bounce(KernelGlobals kg, /* Setup ray into surface. */ INTEGRATOR_STATE_WRITE(state, ray, P) = sd->P; INTEGRATOR_STATE_WRITE(state, ray, D) = bssrdf->N; - INTEGRATOR_STATE_WRITE(state, ray, t) = FLT_MAX; + INTEGRATOR_STATE_WRITE(state, ray, tmin) = 0.0f; + INTEGRATOR_STATE_WRITE(state, ray, tmax) = FLT_MAX; INTEGRATOR_STATE_WRITE(state, ray, dP) = differential_make_compact(sd->dP); INTEGRATOR_STATE_WRITE(state, ray, dD) = differential_zero_compact(); @@ -160,7 +161,7 @@ ccl_device_inline bool subsurface_scatter(KernelGlobals kg, IntegratorState stat /* Pretend ray is coming from the outside towards the exit point. This ensures * correct front/back facing normals. * TODO: find a more elegant solution? */ - ray.P += ray.D * ray.t * 2.0f; + ray.P += ray.D * ray.tmax * 2.0f; ray.D = -ray.D; integrator_state_write_isect(kg, state, &ss_isect.hits[0]); diff --git a/intern/cycles/kernel/integrator/subsurface_disk.h b/intern/cycles/kernel/integrator/subsurface_disk.h index ae857c50493..2836934f6dd 100644 --- a/intern/cycles/kernel/integrator/subsurface_disk.h +++ b/intern/cycles/kernel/integrator/subsurface_disk.h @@ -82,7 +82,8 @@ ccl_device_inline bool subsurface_disk(KernelGlobals kg, /* Create ray. */ ray.P = P + disk_N * disk_height + disk_P; ray.D = -disk_N; - ray.t = 2.0f * disk_height; + ray.tmin = 0.0f; + ray.tmax = 2.0f * disk_height; ray.dP = ray_dP; ray.dD = differential_zero_compact(); ray.time = time; @@ -188,7 +189,8 @@ ccl_device_inline bool subsurface_disk(KernelGlobals kg, ray.P = ray.P + ray.D * ss_isect.hits[hit].t; ray.D = ss_isect.Ng[hit]; - ray.t = 1.0f; + ray.tmin = 0.0f; + ray.tmax = 1.0f; return true; } diff --git a/intern/cycles/kernel/integrator/subsurface_random_walk.h b/intern/cycles/kernel/integrator/subsurface_random_walk.h index 8094bf7159e..c1691030817 100644 --- a/intern/cycles/kernel/integrator/subsurface_random_walk.h +++ b/intern/cycles/kernel/integrator/subsurface_random_walk.h @@ -195,7 +195,8 @@ ccl_device_inline bool subsurface_random_walk(KernelGlobals kg, /* Setup ray. */ ray.P = P; ray.D = D; - ray.t = FLT_MAX; + ray.tmin = 0.0f; + ray.tmax = FLT_MAX; ray.time = time; ray.dP = ray_dP; ray.dD = differential_zero_compact(); @@ -370,10 +371,10 @@ ccl_device_inline bool subsurface_random_walk(KernelGlobals kg, * chance of connecting to it. * TODO: Maybe use less than 10 times the mean free path? */ if (bounce == 0) { - ray.t = max(t, 10.0f / (reduce_min(sigma_t))); + ray.tmax = max(t, 10.0f / (reduce_min(sigma_t))); } else { - ray.t = t; + ray.tmax = t; /* After the first bounce the object can intersect the same surface again */ ray.self.object = OBJECT_NONE; ray.self.prim = PRIM_NONE; @@ -384,12 +385,12 @@ ccl_device_inline bool subsurface_random_walk(KernelGlobals kg, if (hit) { #ifdef __KERNEL_GPU_RAYTRACING__ /* t is always in world space with OptiX and MetalRT. */ - ray.t = ss_isect.hits[0].t; + ray.tmax = ss_isect.hits[0].t; #else /* Compute world space distance to surface hit. */ float3 D = transform_direction(&ob_itfm, ray.D); D = normalize(D) * ss_isect.hits[0].t; - ray.t = len(transform_direction(&ob_tfm, D)); + ray.tmax = len(transform_direction(&ob_tfm, D)); #endif } @@ -397,16 +398,16 @@ ccl_device_inline bool subsurface_random_walk(KernelGlobals kg, /* Check if we hit the opposite side. */ if (hit) { have_opposite_interface = true; - opposite_distance = dot(ray.P + ray.t * ray.D - P, -N); + opposite_distance = dot(ray.P + ray.tmax * ray.D - P, -N); } /* Apart from the opposite side check, we were supposed to only trace up to distance t, * so check if there would have been a hit in that case. */ - hit = ray.t < t; + hit = ray.tmax < t; } /* Use the distance to the exit point for the throughput update if we found one. */ if (hit) { - t = ray.t; + t = ray.tmax; } /* Advance to new scatter location. */ diff --git a/intern/cycles/kernel/light/light.h b/intern/cycles/kernel/light/light.h index 1e7a333d013..b939489bb18 100644 --- a/intern/cycles/kernel/light/light.h +++ b/intern/cycles/kernel/light/light.h @@ -270,31 +270,26 @@ ccl_device bool lights_intersect(KernelGlobals kg, if (type == LIGHT_SPOT) { /* Spot/Disk light. */ - const float mis_ray_t = INTEGRATOR_STATE(state, path, mis_ray_t); - const float3 ray_P = ray->P - ray->D * mis_ray_t; - const float3 lightP = make_float3(klight->co[0], klight->co[1], klight->co[2]); const float radius = klight->spot.radius; if (radius == 0.0f) { continue; } /* disk oriented normal */ - const float3 lightN = normalize(ray_P - lightP); + const float3 lightN = normalize(ray->P - lightP); /* One sided. */ if (dot(ray->D, lightN) >= 0.0f) { continue; } float3 P; - if (!ray_disk_intersect(ray->P, ray->D, ray->t, lightP, lightN, radius, &P, &t)) { + if (!ray_disk_intersect( + ray->P, ray->D, ray->tmin, ray->tmax, lightP, lightN, radius, &P, &t)) { continue; } } else if (type == LIGHT_POINT) { /* Sphere light (aka, aligned disk light). */ - const float mis_ray_t = INTEGRATOR_STATE(state, path, mis_ray_t); - const float3 ray_P = ray->P - ray->D * mis_ray_t; - const float3 lightP = make_float3(klight->co[0], klight->co[1], klight->co[2]); const float radius = klight->spot.radius; if (radius == 0.0f) { @@ -302,9 +297,10 @@ ccl_device bool lights_intersect(KernelGlobals kg, } /* disk oriented normal */ - const float3 lightN = normalize(ray_P - lightP); + const float3 lightN = normalize(ray->P - lightP); float3 P; - if (!ray_disk_intersect(ray->P, ray->D, ray->t, lightP, lightN, radius, &P, &t)) { + if (!ray_disk_intersect( + ray->P, ray->D, ray->tmin, ray->tmax, lightP, lightN, radius, &P, &t)) { continue; } } @@ -330,8 +326,19 @@ ccl_device bool lights_intersect(KernelGlobals kg, const float3 light_P = make_float3(klight->co[0], klight->co[1], klight->co[2]); float3 P; - if (!ray_quad_intersect( - ray->P, ray->D, 0.0f, ray->t, light_P, axisu, axisv, Ng, &P, &t, &u, &v, is_round)) { + if (!ray_quad_intersect(ray->P, + ray->D, + ray->tmin, + ray->tmax, + light_P, + axisu, + axisv, + Ng, + &P, + &t, + &u, + &v, + is_round)) { continue; } } @@ -775,7 +782,8 @@ ccl_device_forceinline void triangle_light_sample(KernelGlobals kg, ls->D = z * B + safe_sqrtf(1.0f - z * z) * safe_normalize(C_ - dot(C_, B) * B); /* calculate intersection with the planar triangle */ - if (!ray_triangle_intersect(P, ls->D, FLT_MAX, V[0], V[1], V[2], &ls->u, &ls->v, &ls->t)) { + if (!ray_triangle_intersect( + P, ls->D, 0.0f, FLT_MAX, V[0], V[1], V[2], &ls->u, &ls->v, &ls->t)) { ls->pdf = 0.0f; return; } diff --git a/intern/cycles/kernel/light/sample.h b/intern/cycles/kernel/light/sample.h index 5cf7dce683a..210bb1b35c2 100644 --- a/intern/cycles/kernel/light/sample.h +++ b/intern/cycles/kernel/light/sample.h @@ -227,23 +227,24 @@ ccl_device_inline void shadow_ray_setup(ccl_private const ShaderData *ccl_restri if (ls->shader & SHADER_CAST_SHADOW) { /* setup ray */ ray->P = P; + ray->tmin = 0.0f; if (ls->t == FLT_MAX) { /* distant light */ ray->D = ls->D; - ray->t = ls->t; + ray->tmax = ls->t; } else { /* other lights, avoid self-intersection */ ray->D = ls->P - P; - ray->D = normalize_len(ray->D, &ray->t); + ray->D = normalize_len(ray->D, &ray->tmax); } } else { /* signal to not cast shadow ray */ ray->P = zero_float3(); ray->D = zero_float3(); - ray->t = 0.0f; + ray->tmax = 0.0f; } ray->dP = differential_make_compact(sd->dP); diff --git a/intern/cycles/kernel/osl/services.cpp b/intern/cycles/kernel/osl/services.cpp index 78c23b858c4..6b7981b7f3a 100644 --- a/intern/cycles/kernel/osl/services.cpp +++ b/intern/cycles/kernel/osl/services.cpp @@ -1094,10 +1094,8 @@ bool OSLRenderServices::get_background_attribute(const KernelGlobalsCPU *kg, ndc[0] = camera_world_to_ndc(kg, sd, sd->ray_P); if (derivatives) { - ndc[1] = camera_world_to_ndc(kg, sd, sd->ray_P + make_float3(sd->ray_dP, 0.0f, 0.0f)) - - ndc[0]; - ndc[2] = camera_world_to_ndc(kg, sd, sd->ray_P + make_float3(0.0f, sd->ray_dP, 0.0f)) - - ndc[0]; + ndc[1] = zero_float3(); + ndc[2] = zero_float3(); } } else { @@ -1671,7 +1669,8 @@ bool OSLRenderServices::trace(TraceOpt &options, ray.P = TO_FLOAT3(P); ray.D = TO_FLOAT3(R); - ray.t = (options.maxdist == 1.0e30f) ? FLT_MAX : options.maxdist - options.mindist; + ray.tmin = 0.0f; + ray.tmax = (options.maxdist == 1.0e30f) ? FLT_MAX : options.maxdist - options.mindist; ray.time = sd->time; ray.self.object = OBJECT_NONE; ray.self.prim = PRIM_NONE; diff --git a/intern/cycles/kernel/svm/ao.h b/intern/cycles/kernel/svm/ao.h index e66c535824c..c57c68d6230 100644 --- a/intern/cycles/kernel/svm/ao.h +++ b/intern/cycles/kernel/svm/ao.h @@ -59,7 +59,8 @@ ccl_device float svm_ao( Ray ray; ray.P = sd->P; ray.D = D.x * T + D.y * B + D.z * N; - ray.t = max_dist; + ray.tmin = 0.0f; + ray.tmax = max_dist; ray.time = sd->time; ray.self.object = sd->object; ray.self.prim = sd->prim; diff --git a/intern/cycles/kernel/svm/bevel.h b/intern/cycles/kernel/svm/bevel.h index 790437d8e82..4617a056a52 100644 --- a/intern/cycles/kernel/svm/bevel.h +++ b/intern/cycles/kernel/svm/bevel.h @@ -179,7 +179,8 @@ ccl_device float3 svm_bevel( Ray ray ccl_optional_struct_init; ray.P = sd->P + disk_N * disk_height + disk_P; ray.D = -disk_N; - ray.t = 2.0f * disk_height; + ray.tmin = 0.0f; + ray.tmax = 2.0f * disk_height; ray.dP = differential_zero_compact(); ray.dD = differential_zero_compact(); ray.time = sd->time; diff --git a/intern/cycles/kernel/svm/tex_coord.h b/intern/cycles/kernel/svm/tex_coord.h index d9138796c45..2a0130e11d4 100644 --- a/intern/cycles/kernel/svm/tex_coord.h +++ b/intern/cycles/kernel/svm/tex_coord.h @@ -138,7 +138,7 @@ ccl_device_noinline int svm_node_tex_coord_bump_dx(KernelGlobals kg, case NODE_TEXCO_WINDOW: { if ((path_flag & PATH_RAY_CAMERA) && sd->object == OBJECT_NONE && kernel_data.cam.type == CAMERA_ORTHOGRAPHIC) - data = camera_world_to_ndc(kg, sd, sd->ray_P + make_float3(sd->ray_dP, 0.0f, 0.0f)); + data = camera_world_to_ndc(kg, sd, sd->ray_P); else data = camera_world_to_ndc(kg, sd, sd->P + sd->dP.dx); data.z = 0.0f; @@ -223,7 +223,7 @@ ccl_device_noinline int svm_node_tex_coord_bump_dy(KernelGlobals kg, case NODE_TEXCO_WINDOW: { if ((path_flag & PATH_RAY_CAMERA) && sd->object == OBJECT_NONE && kernel_data.cam.type == CAMERA_ORTHOGRAPHIC) - data = camera_world_to_ndc(kg, sd, sd->ray_P + make_float3(0.0f, sd->ray_dP, 0.0f)); + data = camera_world_to_ndc(kg, sd, sd->ray_P); else data = camera_world_to_ndc(kg, sd, sd->P + sd->dP.dy); data.z = 0.0f; diff --git a/intern/cycles/kernel/types.h b/intern/cycles/kernel/types.h index 62ac75e5e4d..05320deed19 100644 --- a/intern/cycles/kernel/types.h +++ b/intern/cycles/kernel/types.h @@ -535,7 +535,8 @@ typedef struct RaySelfPrimitives { typedef struct Ray { float3 P; /* origin */ float3 D; /* direction */ - float t; /* length of the ray */ + float tmin; /* start distance */ + float tmax; /* end distance */ float time; /* time (for motion blur) */ RaySelfPrimitives self; diff --git a/intern/cycles/util/math_intersect.h b/intern/cycles/util/math_intersect.h index b0de0b25a45..c5b1cd51030 100644 --- a/intern/cycles/util/math_intersect.h +++ b/intern/cycles/util/math_intersect.h @@ -10,7 +10,8 @@ CCL_NAMESPACE_BEGIN ccl_device bool ray_sphere_intersect(float3 ray_P, float3 ray_D, - float ray_t, + float ray_tmin, + float ray_tmax, float3 sphere_P, float sphere_radius, ccl_private float3 *isect_P, @@ -33,7 +34,7 @@ ccl_device bool ray_sphere_intersect(float3 ray_P, return false; } const float t = tp - sqrtf(radiussq - dsq); /* pythagoras */ - if (t < ray_t) { + if (t > ray_tmin && t < ray_tmax) { *isect_t = t; *isect_P = ray_P + ray_D * t; return true; @@ -44,7 +45,8 @@ ccl_device bool ray_sphere_intersect(float3 ray_P, ccl_device bool ray_aligned_disk_intersect(float3 ray_P, float3 ray_D, - float ray_t, + float ray_tmin, + float ray_tmax, float3 disk_P, float disk_radius, ccl_private float3 *isect_P, @@ -59,7 +61,7 @@ ccl_device bool ray_aligned_disk_intersect(float3 ray_P, } /* Compute t to intersection point. */ const float t = -disk_t / div; - if (t < 0.0f || t > ray_t) { + if (!(t > ray_tmin && t < ray_tmax)) { return false; } /* Test if within radius. */ @@ -74,7 +76,8 @@ ccl_device bool ray_aligned_disk_intersect(float3 ray_P, ccl_device bool ray_disk_intersect(float3 ray_P, float3 ray_D, - float ray_t, + float ray_tmin, + float ray_tmax, float3 disk_P, float3 disk_N, float disk_radius, @@ -92,7 +95,8 @@ ccl_device bool ray_disk_intersect(float3 ray_P, } float3 P = ray_P + t * ray_D; float3 T = P - disk_P; - if (dot(T, T) < sqr(disk_radius) /*&& t > 0.f*/ && t <= ray_t) { + + if (dot(T, T) < sqr(disk_radius) && (t > ray_tmin && t < ray_tmax)) { *isect_P = ray_P + t * ray_D; *isect_t = t; return true; @@ -103,7 +107,8 @@ ccl_device bool ray_disk_intersect(float3 ray_P, ccl_device_forceinline bool ray_triangle_intersect(float3 ray_P, float3 ray_dir, - float ray_t, + float ray_tmin, + float ray_tmax, const float3 tri_a, const float3 tri_b, const float3 tri_c, @@ -149,16 +154,14 @@ ccl_device_forceinline bool ray_triangle_intersect(float3 ray_P, /* Perform depth test. */ const float T = dot3(v0, Ng); - const int sign_den = (__float_as_int(den) & 0x80000000); - const float sign_T = xor_signmask(T, sign_den); - if ((sign_T < 0.0f) || (sign_T > ray_t * xor_signmask(den, sign_den))) { + const float t = T / den; + if (!(t >= ray_tmin && t <= ray_tmax)) { return false; } - const float inv_den = 1.0f / den; - *isect_u = U * inv_den; - *isect_v = V * inv_den; - *isect_t = T * inv_den; + *isect_u = U / den; + *isect_v = V / den; + *isect_t = t; return true; #undef dot3 @@ -171,8 +174,8 @@ ccl_device_forceinline bool ray_triangle_intersect(float3 ray_P, */ ccl_device bool ray_quad_intersect(float3 ray_P, float3 ray_D, - float ray_mint, - float ray_maxt, + float ray_tmin, + float ray_tmax, float3 quad_P, float3 quad_u, float3 quad_v, @@ -185,7 +188,7 @@ ccl_device bool ray_quad_intersect(float3 ray_P, { /* Perform intersection test. */ float t = -(dot(ray_P, quad_n) - dot(quad_P, quad_n)) / dot(ray_D, quad_n); - if (t < ray_mint || t > ray_maxt) { + if (!(t > ray_tmin && t < ray_tmax)) { return false; } const float3 hit = ray_P + t * ray_D; -- cgit v1.2.3 From 92a99c14965905e73f049bc1f92b597a903977fc Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 15 Jul 2022 18:58:35 +0200 Subject: Fix Eevee backround render crash after recent changes from D15463 Backend initialization needs to be delayed until after the OpenGL context is created. This worked fine in foreground mode because the OpenGL context already exists for the window at the point GPU_backend_init_once was called, but not for background mode. Create the backend just in time in GPU_context_create as before, and automatically free it when the last context id discarded. But check if any GPU backend is supported before creating the OpenGL context. Ref D15463, D15465 --- source/blender/gpu/GPU_context.h | 12 ++--- source/blender/gpu/intern/gpu_context.cc | 51 ++++++++++++++-------- source/blender/gpu/tests/gpu_testing.cc | 2 - source/blender/windowmanager/intern/wm_init_exit.c | 3 +- 4 files changed, 37 insertions(+), 31 deletions(-) diff --git a/source/blender/gpu/GPU_context.h b/source/blender/gpu/GPU_context.h index c81296093a1..9d92ea2cad9 100644 --- a/source/blender/gpu/GPU_context.h +++ b/source/blender/gpu/GPU_context.h @@ -17,14 +17,10 @@ extern "C" { #endif -/* GPU backends abstract the differences between different APIs. These must be - * initialized before creating contexts, and deleted after the last context is - * discarded. GPU_context_create automatically initializes a backend if none - * exists yet. */ -bool GPU_backend_init_once(void); -void GPU_backend_exit(void); -bool GPU_backend_supported(eGPUBackendType type); - +/* GPU backends abstract the differences between different APIs. GPU_context_create + * automatically initializes the backend, and GPU_context_discard frees it when there + * are no more contexts. */ +bool GPU_backend_supported(void); eGPUBackendType GPU_backend_get_type(void); /** Opaque type hiding blender::gpu::Context. */ diff --git a/source/blender/gpu/intern/gpu_context.cc b/source/blender/gpu/intern/gpu_context.cc index 5ae020e45a4..20d9208a199 100644 --- a/source/blender/gpu/intern/gpu_context.cc +++ b/source/blender/gpu/intern/gpu_context.cc @@ -44,6 +44,12 @@ using namespace blender::gpu; static thread_local Context *active_ctx = nullptr; +static std::mutex backend_users_mutex; +static int num_backend_users = 0; + +static void gpu_backend_create(); +static void gpu_backend_discard(); + /* -------------------------------------------------------------------- */ /** \name gpu::Context methods * \{ */ @@ -86,7 +92,14 @@ Context *Context::get() GPUContext *GPU_context_create(void *ghost_window) { - GPU_backend_init_once(); + { + std::scoped_lock lock(backend_users_mutex); + if (num_backend_users == 0) { + /* Automatically create backend when first context is created. */ + gpu_backend_create(); + } + num_backend_users++; + } Context *ctx = GPUBackend::get()->context_alloc(ghost_window); @@ -99,6 +112,16 @@ void GPU_context_discard(GPUContext *ctx_) Context *ctx = unwrap(ctx_); delete ctx; active_ctx = nullptr; + + { + std::scoped_lock lock(backend_users_mutex); + num_backend_users--; + BLI_assert(num_backend_users >= 0); + if (num_backend_users == 0) { + /* Discard backend when last context is discarded. */ + gpu_backend_discard(); + } + } } void GPU_context_active_set(GPUContext *ctx_) @@ -189,11 +212,12 @@ void GPU_render_step() /** \name Backend selection * \{ */ +static const eGPUBackendType g_backend_type = GPU_BACKEND_OPENGL; static GPUBackend *g_backend = nullptr; -bool GPU_backend_supported(eGPUBackendType type) +bool GPU_backend_supported(void) { - switch (type) { + switch (g_backend_type) { case GPU_BACKEND_OPENGL: #ifdef WITH_OPENGL_BACKEND return true; @@ -212,21 +236,12 @@ bool GPU_backend_supported(eGPUBackendType type) } } -bool GPU_backend_init_once() +static void gpu_backend_create() { - if (GPUBackend::get() != nullptr) { - return true; - } - - const eGPUBackendType backend_type = GPU_BACKEND_OPENGL; - if (!GPU_backend_supported(backend_type)) { - return false; - } + BLI_assert(g_backend == nullptr); + BLI_assert(GPU_backend_supported(g_backend_type)); - static std::mutex backend_init_mutex; - std::scoped_lock lock(backend_init_mutex); - - switch (backend_type) { + switch (g_backend_type) { #ifdef WITH_OPENGL_BACKEND case GPU_BACKEND_OPENGL: g_backend = new GLBackend; @@ -241,8 +256,6 @@ bool GPU_backend_init_once() BLI_assert(0); break; } - - return true; } void gpu_backend_delete_resources() @@ -251,7 +264,7 @@ void gpu_backend_delete_resources() g_backend->delete_resources(); } -void GPU_backend_exit() +void gpu_backend_discard() { /* TODO: assert no resource left. */ delete g_backend; diff --git a/source/blender/gpu/tests/gpu_testing.cc b/source/blender/gpu/tests/gpu_testing.cc index 5a2ad893360..224a9afcf59 100644 --- a/source/blender/gpu/tests/gpu_testing.cc +++ b/source/blender/gpu/tests/gpu_testing.cc @@ -17,7 +17,6 @@ void GPUTest::SetUp() GHOST_GLSettings glSettings = {0}; CLG_init(); ghost_system = GHOST_CreateSystem(); - GPU_backend_init_once(); ghost_context = GHOST_CreateOpenGLContext(ghost_system, glSettings); GHOST_ActivateOpenGLContext(ghost_context); context = GPU_context_create(nullptr); @@ -29,7 +28,6 @@ void GPUTest::TearDown() GPU_exit(); GPU_context_discard(context); GHOST_DisposeOpenGLContext(ghost_system, ghost_context); - GPU_backend_exit(); GHOST_DisposeSystem(ghost_system); CLG_exit(); } diff --git a/source/blender/windowmanager/intern/wm_init_exit.c b/source/blender/windowmanager/intern/wm_init_exit.c index 7324abfd096..7f5ec77e16d 100644 --- a/source/blender/windowmanager/intern/wm_init_exit.c +++ b/source/blender/windowmanager/intern/wm_init_exit.c @@ -169,7 +169,7 @@ void WM_init_opengl(void) wm_ghost_init(NULL); } - if (!GPU_backend_init_once()) { + if (!GPU_backend_supported()) { return; } @@ -613,7 +613,6 @@ void WM_exit_ex(bContext *C, const bool do_python) else { UI_exit(); } - GPU_backend_exit(); BKE_blender_userdef_data_free(&U, false); -- cgit v1.2.3 From b9854372832ac43978f6d999891944dbb7f2e752 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 15 Jul 2022 20:10:42 +0200 Subject: Fix workbench background render broken after recent changes from D15463 For Eevee the light baking can initialize OpenGL earlier, but for workbench we can't assume the backend exists here already. --- source/blender/render/intern/engine.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/render/intern/engine.c b/source/blender/render/intern/engine.c index 113af393706..266e66092b8 100644 --- a/source/blender/render/intern/engine.c +++ b/source/blender/render/intern/engine.c @@ -952,7 +952,7 @@ bool RE_engine_render(Render *re, bool do_all) re->draw_lock(re->dlh, true); } - if ((type->flag & RE_USE_GPU_CONTEXT) && (GPU_backend_get_type() == GPU_BACKEND_NONE)) { + if ((type->flag & RE_USE_GPU_CONTEXT) && !GPU_backend_supported()) { /* Clear UI drawing locks. */ if (re->draw_lock) { re->draw_lock(re->dlh, false); -- cgit v1.2.3 From bf49e6040caac1e975982375731c348a1fcb11c1 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sat, 16 Jul 2022 16:12:48 +1000 Subject: Fix error in assertion after 92a99c14965905e73f049bc1f92b597a903977fc --- source/blender/gpu/intern/gpu_context.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/gpu/intern/gpu_context.cc b/source/blender/gpu/intern/gpu_context.cc index 20d9208a199..e29b0d5801d 100644 --- a/source/blender/gpu/intern/gpu_context.cc +++ b/source/blender/gpu/intern/gpu_context.cc @@ -239,7 +239,7 @@ bool GPU_backend_supported(void) static void gpu_backend_create() { BLI_assert(g_backend == nullptr); - BLI_assert(GPU_backend_supported(g_backend_type)); + BLI_assert(GPU_backend_supported()); switch (g_backend_type) { #ifdef WITH_OPENGL_BACKEND -- cgit v1.2.3 From f76b537d486029fc8ad5bf3bf9b122bc4b3c9d89 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sat, 16 Jul 2022 16:32:36 +1000 Subject: Fix T99744: NULL pointer free with corrupt zSTD reading --- source/blender/blenlib/intern/filereader_zstd.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/blender/blenlib/intern/filereader_zstd.c b/source/blender/blenlib/intern/filereader_zstd.c index 5f114f24fb0..aeb000e9754 100644 --- a/source/blender/blenlib/intern/filereader_zstd.c +++ b/source/blender/blenlib/intern/filereader_zstd.c @@ -281,7 +281,10 @@ static void zstd_close(FileReader *reader) if (zstd->reader.seek) { MEM_freeN(zstd->seek.uncompressed_ofs); MEM_freeN(zstd->seek.compressed_ofs); - MEM_freeN(zstd->seek.cached_content); + /* When an error has occurred this may be NULL, see: T99744. */ + if (zstd->seek.cached_content) { + MEM_freeN(zstd->seek.cached_content); + } } else { MEM_freeN((void *)zstd->in_buf.src); -- cgit v1.2.3 From 49babc7caa82883fa891640406da89b68ae8d8e5 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sat, 16 Jul 2022 17:13:25 +1000 Subject: Cleanup: early exit MEM_lockfree_freeN when called with NULL pointer Simplify logic for freeing a NULL pointer. While no null-pointer de-reference was performed, this wasn't as so obvious as the pointer was passed to MEM_lockfree_allocN_len before checking for NULL. NOTE: T99744 claimed the a NULL pointer free was a vulnerability, while I can't see evidence for this - exiting early makes it clearer the memory isn't accessed. *Details* - Add MEMHEAD_LEN macro, avoids redundant NULL check. - Use "UNLIKELY(..)" hint's for error cases (freeing NULL pointer and checking if `leak_detector_has_run`). --- intern/guardedalloc/intern/mallocn_lockfree_impl.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/intern/guardedalloc/intern/mallocn_lockfree_impl.c b/intern/guardedalloc/intern/mallocn_lockfree_impl.c index 73912ad07b1..300e2000a14 100644 --- a/intern/guardedalloc/intern/mallocn_lockfree_impl.c +++ b/intern/guardedalloc/intern/mallocn_lockfree_impl.c @@ -44,6 +44,7 @@ enum { #define PTR_FROM_MEMHEAD(memhead) (memhead + 1) #define MEMHEAD_ALIGNED_FROM_PTR(ptr) (((MemHeadAligned *)ptr) - 1) #define MEMHEAD_IS_ALIGNED(memhead) ((memhead)->len & (size_t)MEMHEAD_ALIGN_FLAG) +#define MEMHEAD_LEN(memhead) ((memhead)->len & ~((size_t)(MEMHEAD_ALIGN_FLAG))) /* Uncomment this to have proper peak counter. */ #define USE_ATOMIC_MAX @@ -78,8 +79,8 @@ print_error(const char *str, ...) size_t MEM_lockfree_allocN_len(const void *vmemh) { - if (vmemh) { - return MEMHEAD_FROM_PTR(vmemh)->len & ~((size_t)(MEMHEAD_ALIGN_FLAG)); + if (LIKELY(vmemh)) { + return MEMHEAD_LEN(MEMHEAD_FROM_PTR(vmemh)); } return 0; @@ -87,14 +88,11 @@ size_t MEM_lockfree_allocN_len(const void *vmemh) void MEM_lockfree_freeN(void *vmemh) { - if (leak_detector_has_run) { + if (UNLIKELY(leak_detector_has_run)) { print_error("%s\n", free_after_leak_detection_message); } - MemHead *memh = MEMHEAD_FROM_PTR(vmemh); - size_t len = MEM_lockfree_allocN_len(vmemh); - - if (vmemh == NULL) { + if (UNLIKELY(vmemh == NULL)) { print_error("Attempt to free NULL pointer\n"); #ifdef WITH_ASSERT_ABORT abort(); @@ -102,6 +100,9 @@ void MEM_lockfree_freeN(void *vmemh) return; } + MemHead *memh = MEMHEAD_FROM_PTR(vmemh); + size_t len = MEMHEAD_LEN(memh); + atomic_sub_and_fetch_u(&totblock, 1); atomic_sub_and_fetch_z(&mem_in_use, len); -- cgit v1.2.3 From 0a8d21e0c96e2f9c7f6f990d756f5abe011ed805 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sat, 16 Jul 2022 17:28:09 +1000 Subject: PyAPI: re-enable the "bgl" module for headless builds Instead of removing the `bgl` module, set all it's functions to stubs so importing `bgl` or any of it's members doesn't raise an error. This avoids problems for scripts that import bgl but don't call it's functions when running in background mode. --- source/blender/python/generic/CMakeLists.txt | 10 +---- source/blender/python/generic/bgl.c | 66 ++++++++++++++++++++-------- source/blender/python/intern/bpy_interface.c | 2 - 3 files changed, 50 insertions(+), 28 deletions(-) diff --git a/source/blender/python/generic/CMakeLists.txt b/source/blender/python/generic/CMakeLists.txt index dfca528e758..69bcfdfae4e 100644 --- a/source/blender/python/generic/CMakeLists.txt +++ b/source/blender/python/generic/CMakeLists.txt @@ -17,6 +17,7 @@ set(INC_SYS ) set(SRC + bgl.c bl_math_py_api.c blf_py_api.c bpy_threads.c @@ -26,6 +27,7 @@ set(SRC py_capi_rna.c py_capi_utils.c + bgl.h bl_math_py_api.h blf_py_api.h idprop_py_api.h @@ -38,14 +40,6 @@ set(SRC python_utildefines.h ) -if(WITH_OPENGL) - list(APPEND SRC - bgl.c - - bgl.h - ) -endif() - set(LIB ${GLEW_LIBRARY} ${PYTHON_LINKFLAGS} diff --git a/source/blender/python/generic/bgl.c b/source/blender/python/generic/bgl.c index 92ed0c51b1f..f5c1f060e80 100644 --- a/source/blender/python/generic/bgl.c +++ b/source/blender/python/generic/bgl.c @@ -1082,18 +1082,34 @@ static PyObject *Buffer_repr(Buffer *self) /** \name OpenGL API Wrapping * \{ */ -#define BGL_Wrap(funcname, ret, arg_list) \ - static PyObject *Method_##funcname(PyObject *UNUSED(self), PyObject *args) \ - { \ - arg_def arg_list; \ - ret_def_##ret; \ - if (!PyArg_ParseTuple(args, arg_str arg_list, arg_ref arg_list)) { \ +#ifdef WITH_OPENGL +# define BGL_Wrap(funcname, ret, arg_list) \ + static PyObject *Method_##funcname(PyObject *UNUSED(self), PyObject *args) \ + { \ + arg_def arg_list; \ + ret_def_##ret; \ + if (!PyArg_ParseTuple(args, arg_str arg_list, arg_ref arg_list)) { \ + return NULL; \ + } \ + GPU_bgl_start(); \ + ret_set_##ret gl##funcname(arg_var arg_list); \ + ret_ret_##ret; \ + } +#else + +static void bgl_no_opengl_error(void) +{ + PyErr_SetString(PyExc_RuntimeError, "Built without OpenGL support"); +} + +# define BGL_Wrap(funcname, ret, arg_list) \ + static PyObject *Method_##funcname(PyObject *UNUSED(self), PyObject *args) \ + { \ + (void)args; \ + bgl_no_opengl_error(); \ return NULL; \ - } \ - GPU_bgl_start(); \ - ret_set_##ret gl##funcname(arg_var arg_list); \ - ret_ret_##ret; \ - } + } +#endif /* GL_VERSION_1_0 */ BGL_Wrap(BlendFunc, void, (GLenum, GLenum)); @@ -1421,12 +1437,22 @@ static void py_module_dict_add_method(PyObject *submodule, #ifdef __GNUC__ # pragma GCC diagnostic ignored "-Waddress" #endif -#define PY_MOD_ADD_METHOD(func) \ - { \ - static PyMethodDef method_def = {"gl" #func, Method_##func, METH_VARARGS}; \ - py_module_dict_add_method(submodule, dict, &method_def, (gl##func != NULL)); \ - } \ - ((void)0) + +#ifdef WITH_OPENGL +# define PY_MOD_ADD_METHOD(func) \ + { \ + static PyMethodDef method_def = {"gl" #func, Method_##func, METH_VARARGS}; \ + py_module_dict_add_method(submodule, dict, &method_def, (gl##func != NULL)); \ + } \ + ((void)0) +#else +# define PY_MOD_ADD_METHOD(func) \ + { \ + static PyMethodDef method_def = {"gl" #func, Method_##func, METH_VARARGS}; \ + py_module_dict_add_method(submodule, dict, &method_def, false); \ + } \ + ((void)0) +#endif static void init_bgl_version_1_0_methods(PyObject *submodule, PyObject *dict) { @@ -2620,9 +2646,13 @@ static PyObject *Method_ShaderSource(PyObject *UNUSED(self), PyObject *args) return NULL; } +#ifdef WITH_OPENGL glShaderSource(shader, 1, (const char **)&source, NULL); - Py_RETURN_NONE; +#else + bgl_no_opengl_error(); + return NULL; +#endif } /** \} */ diff --git a/source/blender/python/intern/bpy_interface.c b/source/blender/python/intern/bpy_interface.c index 08dd5fe9cfc..939fa475344 100644 --- a/source/blender/python/intern/bpy_interface.c +++ b/source/blender/python/intern/bpy_interface.c @@ -259,9 +259,7 @@ static struct _inittab bpy_internal_modules[] = { {"mathutils.kdtree", PyInit_mathutils_kdtree}, #endif {"_bpy_path", BPyInit__bpy_path}, -#ifdef WITH_OPENGL {"bgl", BPyInit_bgl}, -#endif {"blf", BPyInit_blf}, {"bl_math", BPyInit_bl_math}, {"imbuf", BPyInit_imbuf}, -- cgit v1.2.3 From 1e4c557d82b005578dd2b361701241b66ccee42f Mon Sep 17 00:00:00 2001 From: Colin Basnett Date: Sat, 16 Jul 2022 20:57:28 +0200 Subject: Fix T99039: bpy.ops.sound.mixdown returns indecipherable error Fix for {T99039}. The problem was that `AUD_mixdown` and `AUD_mixdown_per_channel` were returning pointers to freed memory. Two key changes are made: 1. The return value of those functions now simply return a bool as to whether the operation succeeded, instead of an optional error string pointer. 2. The error string buffer is now passed into the function to be filled in case an error occurs. In this way, the onus of memory ownership is unamibiguously on the caller. Differential Revision: https://developer.blender.org/D15260 --- extern/audaspace/bindings/C/AUD_Special.cpp | 20 ++++++++++++++------ extern/audaspace/bindings/C/AUD_Special.h | 16 ++++++++++------ source/blender/editors/sound/sound_ops.c | 15 ++++++++++----- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/extern/audaspace/bindings/C/AUD_Special.cpp b/extern/audaspace/bindings/C/AUD_Special.cpp index 1ce25dcd41c..686187bc70c 100644 --- a/extern/audaspace/bindings/C/AUD_Special.cpp +++ b/extern/audaspace/bindings/C/AUD_Special.cpp @@ -270,7 +270,7 @@ AUD_API int AUD_readSound(AUD_Sound* sound, float* buffer, int length, int sampl return length; } -AUD_API const char* AUD_mixdown(AUD_Sound* sound, unsigned int start, unsigned int length, unsigned int buffersize, const char* filename, AUD_DeviceSpecs specs, AUD_Container format, AUD_Codec codec, unsigned int bitrate, void(*callback)(float, void*), void* data) +AUD_API int AUD_mixdown(AUD_Sound* sound, unsigned int start, unsigned int length, unsigned int buffersize, const char* filename, AUD_DeviceSpecs specs, AUD_Container format, AUD_Codec codec, unsigned int bitrate, void(*callback)(float, void*), void* data, char* error, size_t errorsize) { try { @@ -282,15 +282,19 @@ AUD_API const char* AUD_mixdown(AUD_Sound* sound, unsigned int start, unsigned i std::shared_ptr writer = FileWriter::createWriter(filename, convCToDSpec(specs), static_cast(format), static_cast(codec), bitrate); FileWriter::writeReader(reader, writer, length, buffersize, callback, data); - return nullptr; + return 1; } catch(Exception& e) { - return e.getMessage().c_str(); + if(error && errorsize) { + std::strncpy(error, e.getMessage().c_str(), errorsize); + error[errorsize - 1] = '\0'; + } + return 0; } } -AUD_API const char* AUD_mixdown_per_channel(AUD_Sound* sound, unsigned int start, unsigned int length, unsigned int buffersize, const char* filename, AUD_DeviceSpecs specs, AUD_Container format, AUD_Codec codec, unsigned int bitrate, void(*callback)(float, void*), void* data) +AUD_API int AUD_mixdown_per_channel(AUD_Sound* sound, unsigned int start, unsigned int length, unsigned int buffersize, const char* filename, AUD_DeviceSpecs specs, AUD_Container format, AUD_Codec codec, unsigned int bitrate, void(*callback)(float, void*), void* data, char* error, size_t errorsize) { try { @@ -328,11 +332,15 @@ AUD_API const char* AUD_mixdown_per_channel(AUD_Sound* sound, unsigned int start reader->seek(start); FileWriter::writeReader(reader, writers, length, buffersize, callback, data); - return nullptr; + return 1; } catch(Exception& e) { - return e.getMessage().c_str(); + if(error && errorsize) { + std::strncpy(error, e.getMessage().c_str(), errorsize); + error[errorsize - 1] = '\0'; + } + return 0; } } diff --git a/extern/audaspace/bindings/C/AUD_Special.h b/extern/audaspace/bindings/C/AUD_Special.h index 2f5d13c6fd9..1d181d33f87 100644 --- a/extern/audaspace/bindings/C/AUD_Special.h +++ b/extern/audaspace/bindings/C/AUD_Special.h @@ -70,13 +70,15 @@ extern AUD_API int AUD_readSound(AUD_Sound* sound, float* buffer, int length, in * \param bitrate The bitrate for encoding. * \param callback A callback function that is called periodically during mixdown, reporting progress if length > 0. Can be NULL. * \param data Pass through parameter that is passed to the callback. - * \return An error message or NULL in case of success. + * \param error String buffer to copy the error message to in case of failure. + * \param errorsize The size of the error buffer. + * \return Whether or not the operation succeeded. */ -extern AUD_API const char* AUD_mixdown(AUD_Sound* sound, unsigned int start, unsigned int length, +extern AUD_API int AUD_mixdown(AUD_Sound* sound, unsigned int start, unsigned int length, unsigned int buffersize, const char* filename, AUD_DeviceSpecs specs, AUD_Container format, AUD_Codec codec, unsigned int bitrate, - void(*callback)(float, void*), void* data); + void(*callback)(float, void*), void* data, char* error, size_t errorsize); /** * Mixes a sound down into multiple files. @@ -91,13 +93,15 @@ extern AUD_API const char* AUD_mixdown(AUD_Sound* sound, unsigned int start, uns * \param bitrate The bitrate for encoding. * \param callback A callback function that is called periodically during mixdown, reporting progress if length > 0. Can be NULL. * \param data Pass through parameter that is passed to the callback. - * \return An error message or NULL in case of success. + * \param error String buffer to copy the error message to in case of failure. + * \param errorsize The size of the error buffer. + * \return Whether or not the operation succeeded. */ -extern AUD_API const char* AUD_mixdown_per_channel(AUD_Sound* sound, unsigned int start, unsigned int length, +extern AUD_API int AUD_mixdown_per_channel(AUD_Sound* sound, unsigned int start, unsigned int length, unsigned int buffersize, const char* filename, AUD_DeviceSpecs specs, AUD_Container format, AUD_Codec codec, unsigned int bitrate, - void(*callback)(float, void*), void* data); + void(*callback)(float, void*), void* data, char* error, size_t errorsize); /** * Opens a read device and prepares it for mixdown of the sound scene. diff --git a/source/blender/editors/sound/sound_ops.c b/source/blender/editors/sound/sound_ops.c index 2a8a2be8b65..08b795db0c3 100644 --- a/source/blender/editors/sound/sound_ops.c +++ b/source/blender/editors/sound/sound_ops.c @@ -340,7 +340,8 @@ static int sound_mixdown_exec(bContext *C, wmOperator *op) AUD_DeviceSpecs specs; AUD_Container container; AUD_Codec codec; - const char *result; + int result; + char error_message[1024] = {'\0'}; sound_bake_animation_exec(C, op); @@ -372,7 +373,9 @@ static int sound_mixdown_exec(bContext *C, wmOperator *op) codec, bitrate, NULL, - NULL); + NULL, + error_message, + sizeof(error_message)); } else { result = AUD_mixdown(scene_eval->sound_scene, @@ -385,13 +388,15 @@ static int sound_mixdown_exec(bContext *C, wmOperator *op) codec, bitrate, NULL, - NULL); + NULL, + error_message, + sizeof(error_message)); } BKE_sound_reset_scene_specs(scene_eval); - if (result) { - BKE_report(op->reports, RPT_ERROR, result); + if (!result) { + BKE_report(op->reports, RPT_ERROR, error_message); return OPERATOR_CANCELLED; } #else /* WITH_AUDASPACE */ -- cgit v1.2.3 From d136a996caa9da3024bb90c543764b895201a4e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20M=C3=BCller?= Date: Sat, 16 Jul 2022 22:20:08 +0200 Subject: Audaspace: minor formatting fix for last commit. --- extern/audaspace/bindings/C/AUD_Special.cpp | 14 ++++++++------ release/datafiles/locale | 2 +- release/scripts/addons | 2 +- source/tools | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/extern/audaspace/bindings/C/AUD_Special.cpp b/extern/audaspace/bindings/C/AUD_Special.cpp index 686187bc70c..a5ecb7a6dc0 100644 --- a/extern/audaspace/bindings/C/AUD_Special.cpp +++ b/extern/audaspace/bindings/C/AUD_Special.cpp @@ -282,15 +282,16 @@ AUD_API int AUD_mixdown(AUD_Sound* sound, unsigned int start, unsigned int lengt std::shared_ptr writer = FileWriter::createWriter(filename, convCToDSpec(specs), static_cast(format), static_cast(codec), bitrate); FileWriter::writeReader(reader, writer, length, buffersize, callback, data); - return 1; + return true; } catch(Exception& e) { - if(error && errorsize) { + if(error && errorsize) + { std::strncpy(error, e.getMessage().c_str(), errorsize); error[errorsize - 1] = '\0'; } - return 0; + return false; } } @@ -332,15 +333,16 @@ AUD_API int AUD_mixdown_per_channel(AUD_Sound* sound, unsigned int start, unsign reader->seek(start); FileWriter::writeReader(reader, writers, length, buffersize, callback, data); - return 1; + return true; } catch(Exception& e) { - if(error && errorsize) { + if(error && errorsize) + { std::strncpy(error, e.getMessage().c_str(), errorsize); error[errorsize - 1] = '\0'; } - return 0; + return false; } } diff --git a/release/datafiles/locale b/release/datafiles/locale index a2eb5078914..9a85b137951 160000 --- a/release/datafiles/locale +++ b/release/datafiles/locale @@ -1 +1 @@ -Subproject commit a2eb507891449a0b67582be9561840075513661d +Subproject commit 9a85b13795157560b319235c63f5a13b0107ba41 diff --git a/release/scripts/addons b/release/scripts/addons index 7a8502871c3..bdf75cb276d 160000 --- a/release/scripts/addons +++ b/release/scripts/addons @@ -1 +1 @@ -Subproject commit 7a8502871c34db0343cc7de52d6b49b15a84238a +Subproject commit bdf75cb276dfd3b5266c909de4c099c00c68a659 diff --git a/source/tools b/source/tools index da8bdd7244c..01b4c0e4a17 160000 --- a/source/tools +++ b/source/tools @@ -1 +1 @@ -Subproject commit da8bdd7244c7b6c2eadf4c949ff391d0cc430275 +Subproject commit 01b4c0e4a172819414229445c314be34527bf412 -- cgit v1.2.3 From 9a1488790532b6a64dea66cbe696ac35c70e6793 Mon Sep 17 00:00:00 2001 From: Joseph Eagar Date: Sat, 16 Jul 2022 16:45:41 -0700 Subject: Sculpt: Fix scene spacing mode (phase 1) The scene spacing code was failing to check if a raycast failed, which can happen when sculpting the edges of objects in negative mode. Note I removed what I suspect was a hack put in to fix this, spacing was clamped to 0.001 scene units. Scene spacing mode is actually quite broken, so it will be fixed in a series of phases. --- release/datafiles/locale | 2 +- release/scripts/addons | 2 +- source/blender/editors/sculpt_paint/paint_stroke.c | 17 ++++++++++++++--- source/tools | 2 +- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/release/datafiles/locale b/release/datafiles/locale index 9a85b137951..a2eb5078914 160000 --- a/release/datafiles/locale +++ b/release/datafiles/locale @@ -1 +1 @@ -Subproject commit 9a85b13795157560b319235c63f5a13b0107ba41 +Subproject commit a2eb507891449a0b67582be9561840075513661d diff --git a/release/scripts/addons b/release/scripts/addons index bdf75cb276d..7a8502871c3 160000 --- a/release/scripts/addons +++ b/release/scripts/addons @@ -1 +1 @@ -Subproject commit bdf75cb276dfd3b5266c909de4c099c00c68a659 +Subproject commit 7a8502871c34db0343cc7de52d6b49b15a84238a diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c index 1ee26935dc9..a4c942796d8 100644 --- a/source/blender/editors/sculpt_paint/paint_stroke.c +++ b/source/blender/editors/sculpt_paint/paint_stroke.c @@ -79,6 +79,8 @@ typedef struct PaintStroke { float last_mouse_position[2]; float last_world_space_position[3]; + float last_scene_spacing_delta[3]; + bool stroke_over_mesh; /* space distance covered so far */ float stroke_distance; @@ -550,8 +552,15 @@ static void paint_brush_stroke_add_step( stroke->last_pressure = pressure; if (paint_stroke_use_scene_spacing(brush, mode)) { - SCULPT_stroke_get_location(C, stroke->last_world_space_position, stroke->last_mouse_position); - mul_m4_v3(stroke->vc.obact->obmat, stroke->last_world_space_position); + float world_space_position[3]; + + if (SCULPT_stroke_get_location(C, world_space_position, stroke->last_mouse_position)) { + copy_v3_v3(stroke->last_world_space_position, world_space_position); + mul_m4_v3(stroke->vc.obact->obmat, stroke->last_world_space_position); + } + else { + add_v3_v3(stroke->last_world_space_position, stroke->last_scene_spacing_delta); + } } if (paint_stroke_use_jitter(mode, brush, stroke->stroke_mode == BRUSH_STROKE_INVERT)) { @@ -698,7 +707,7 @@ static float paint_space_stroke_spacing(bContext *C, spacing *= stroke->zoom_2d; if (paint_stroke_use_scene_spacing(brush, mode)) { - return max_ff(0.001f, size_clamp * spacing / 50.0f); + return size_clamp * spacing / 50.0f; } return max_ff(stroke->zoom_2d, size_clamp * spacing / 50.0f); } @@ -838,6 +847,8 @@ static int paint_space_stroke(bContext *C, stroke->last_world_space_position, final_world_space_position); ED_view3d_project_v2(region, final_world_space_position, mouse); + + mul_v3_v3fl(stroke->last_scene_spacing_delta, d_world_space_position, spacing); } else { mouse[0] = stroke->last_mouse_position[0] + dmouse[0] * spacing; diff --git a/source/tools b/source/tools index 01b4c0e4a17..da8bdd7244c 160000 --- a/source/tools +++ b/source/tools @@ -1 +1 @@ -Subproject commit 01b4c0e4a172819414229445c314be34527bf412 +Subproject commit da8bdd7244c7b6c2eadf4c949ff391d0cc430275 -- cgit v1.2.3 From cd1e4ae4483056157f238f56f19367ad98ae3f3d Mon Sep 17 00:00:00 2001 From: Joseph Eagar Date: Sat, 16 Jul 2022 17:27:25 -0700 Subject: Fix T99644: Anchored brush mode fails for negative brushes The stroke code now supports raycasting the original mesh. This fixes anchored mode not working for negative brushes, which might move the mesh out of the initial mouse cursor position. --- .../editors/sculpt_paint/curves_sculpt_ops.cc | 5 +++- source/blender/editors/sculpt_paint/paint_intern.h | 5 +++- source/blender/editors/sculpt_paint/paint_stroke.c | 28 +++++++++++++++------- source/blender/editors/sculpt_paint/sculpt.c | 9 ++++--- .../blender/editors/sculpt_paint/sculpt_intern.h | 5 +++- .../editors/sculpt_paint/sculpt_transform.c | 2 +- 6 files changed, 39 insertions(+), 15 deletions(-) diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc index 5c73c7a37d3..47e0fe3a61a 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc @@ -147,7 +147,10 @@ struct SculptCurvesBrushStrokeData { PaintStroke *stroke; }; -static bool stroke_get_location(bContext *C, float out[3], const float mouse[2]) +static bool stroke_get_location(bContext *C, + float out[3], + const float mouse[2], + bool UNUSED(force_original)) { out[0] = mouse[0]; out[1] = mouse[1]; diff --git a/source/blender/editors/sculpt_paint/paint_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h index 02c3b5be8b9..99c25953d50 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.h +++ b/source/blender/editors/sculpt_paint/paint_intern.h @@ -46,7 +46,10 @@ typedef struct CoNo { /* paint_stroke.c */ -typedef bool (*StrokeGetLocation)(struct bContext *C, float location[3], const float mouse[2]); +typedef bool (*StrokeGetLocation)(struct bContext *C, + float location[3], + const float mouse[2], + bool force_original); typedef bool (*StrokeTestStart)(struct bContext *C, struct wmOperator *op, const float mouse[2]); typedef void (*StrokeUpdateStep)(struct bContext *C, struct wmOperator *op, diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c index a4c942796d8..33a307376bb 100644 --- a/source/blender/editors/sculpt_paint/paint_stroke.c +++ b/source/blender/editors/sculpt_paint/paint_stroke.c @@ -122,6 +122,8 @@ typedef struct PaintStroke { StrokeUpdateStep update_step; StrokeRedraw redraw; StrokeDone done; + + bool original; /* Raycast original mesh at start of stroke */ } PaintStroke; /*** Cursors ***/ @@ -245,6 +247,11 @@ static bool paint_stroke_use_scene_spacing(Brush *brush, ePaintMode mode) return false; } +static bool paint_tool_raycast_original(Brush *brush, ePaintMode mode) +{ + return brush->flag & BRUSH_ANCHORED; +} + static bool paint_tool_require_inbetween_mouse_events(Brush *brush, ePaintMode mode) { if (brush->flag & BRUSH_ANCHORED) { @@ -394,7 +401,7 @@ static bool paint_brush_update(bContext *C, halfway[1] = dy * 0.5f + stroke->initial_mouse[1]; if (stroke->get_location) { - if (stroke->get_location(C, r_location, halfway)) { + if (stroke->get_location(C, r_location, halfway, stroke->original)) { hit = true; location_sampled = true; location_success = true; @@ -468,7 +475,7 @@ static bool paint_brush_update(bContext *C, if (!location_sampled) { if (stroke->get_location) { - if (stroke->get_location(C, r_location, mouse)) { + if (stroke->get_location(C, r_location, mouse, stroke->original)) { location_success = true; *r_location_is_set = true; } @@ -554,7 +561,8 @@ static void paint_brush_stroke_add_step( if (paint_stroke_use_scene_spacing(brush, mode)) { float world_space_position[3]; - if (SCULPT_stroke_get_location(C, world_space_position, stroke->last_mouse_position)) { + if (SCULPT_stroke_get_location( + C, world_space_position, stroke->last_mouse_position, stroke->original)) { copy_v3_v3(stroke->last_world_space_position, world_space_position); mul_m4_v3(stroke->vc.obact->obmat, stroke->last_world_space_position); } @@ -816,7 +824,7 @@ static int paint_space_stroke(bContext *C, if (use_scene_spacing) { float world_space_position[3]; - bool hit = SCULPT_stroke_get_location(C, world_space_position, final_mouse); + bool hit = SCULPT_stroke_get_location(C, world_space_position, final_mouse, stroke->original); mul_m4_v3(stroke->vc.obact->obmat, world_space_position); if (hit && stroke->stroke_over_mesh) { sub_v3_v3v3(d_world_space_position, world_space_position, stroke->last_world_space_position); @@ -907,6 +915,8 @@ PaintStroke *paint_stroke_new(bContext *C, stroke->ups = ups; stroke->stroke_mode = RNA_enum_get(op->ptr, "mode"); + stroke->original = paint_tool_raycast_original(br, BKE_paintmode_get_active_from_context(C)); + get_imapaint_zoom(C, &zoomx, &zoomy); stroke->zoom_2d = max_ff(zoomx, zoomy); @@ -1202,8 +1212,10 @@ static void paint_line_strokes_spacing(bContext *C, copy_v2_v2(stroke->last_mouse_position, old_pos); if (use_scene_spacing) { - bool hit_old = SCULPT_stroke_get_location(C, world_space_position_old, old_pos); - bool hit_new = SCULPT_stroke_get_location(C, world_space_position_new, new_pos); + bool hit_old = SCULPT_stroke_get_location( + C, world_space_position_old, old_pos, stroke->original); + bool hit_new = SCULPT_stroke_get_location( + C, world_space_position_new, new_pos, stroke->original); mul_m4_v3(stroke->vc.obact->obmat, world_space_position_old); mul_m4_v3(stroke->vc.obact->obmat, world_space_position_new); if (hit_old && hit_new && stroke->stroke_over_mesh) { @@ -1347,7 +1359,7 @@ static bool paint_stroke_curve_end(bContext *C, wmOperator *op, PaintStroke *str if (paint_stroke_use_scene_spacing(br, BKE_paintmode_get_active_from_context(C))) { stroke->stroke_over_mesh = SCULPT_stroke_get_location( - C, stroke->last_world_space_position, data + 2 * j); + C, stroke->last_world_space_position, data + 2 * j, stroke->original); mul_m4_v3(stroke->vc.obact->obmat, stroke->last_world_space_position); } @@ -1479,7 +1491,7 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event, PaintS copy_v2_v2(stroke->last_mouse_position, sample_average.mouse); if (paint_stroke_use_scene_spacing(br, mode)) { stroke->stroke_over_mesh = SCULPT_stroke_get_location( - C, stroke->last_world_space_position, sample_average.mouse); + C, stroke->last_world_space_position, sample_average.mouse, stroke->original); mul_m4_v3(stroke->vc.obact->obmat, stroke->last_world_space_position); } stroke->stroke_started = stroke->test_start(C, op, sample_average.mouse); diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index ae65ca8178b..2951706ebd8 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -4952,7 +4952,10 @@ bool SCULPT_cursor_geometry_info_update(bContext *C, return true; } -bool SCULPT_stroke_get_location(bContext *C, float out[3], const float mval[2]) +bool SCULPT_stroke_get_location(bContext *C, + float out[3], + const float mval[2], + bool force_original) { Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); Object *ob; @@ -4968,7 +4971,7 @@ bool SCULPT_stroke_get_location(bContext *C, float out[3], const float mval[2]) ss = ob->sculpt; cache = ss->cache; - original = (cache) ? cache->original : false; + original = force_original || ((cache) ? cache->original : false); const Brush *brush = BKE_paint_brush(BKE_paint_get_active_from_context(C)); @@ -5284,7 +5287,7 @@ void SCULPT_flush_update_done(const bContext *C, Object *ob, SculptUpdateType up static bool over_mesh(bContext *C, struct wmOperator *UNUSED(op), const float mval[2]) { float co_dummy[3]; - return SCULPT_stroke_get_location(C, co_dummy, mval); + return SCULPT_stroke_get_location(C, co_dummy, mval, false); } bool SCULPT_handles_colors_report(SculptSession *ss, ReportList *reports) diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index 8485c7fcbf9..83526006d7d 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h @@ -845,7 +845,10 @@ void SCULPT_tag_update_overlays(bContext *C); * (This allows us to ignore the GL depth buffer) * Returns 0 if the ray doesn't hit the mesh, non-zero otherwise. */ -bool SCULPT_stroke_get_location(struct bContext *C, float out[3], const float mouse[2]); +bool SCULPT_stroke_get_location(struct bContext *C, + float out[3], + const float mouse[2], + bool force_original); /** * Gets the normal, location and active vertex location of the geometry under the cursor. This also * updates the active vertex and cursor related data of the SculptSession using the mouse position diff --git a/source/blender/editors/sculpt_paint/sculpt_transform.c b/source/blender/editors/sculpt_paint/sculpt_transform.c index 365000ab163..8856e3bf3db 100644 --- a/source/blender/editors/sculpt_paint/sculpt_transform.c +++ b/source/blender/editors/sculpt_paint/sculpt_transform.c @@ -426,7 +426,7 @@ static int sculpt_set_pivot_position_exec(bContext *C, wmOperator *op) RNA_float_get(op->ptr, "mouse_x"), RNA_float_get(op->ptr, "mouse_y"), }; - if (SCULPT_stroke_get_location(C, stroke_location, mval)) { + if (SCULPT_stroke_get_location(C, stroke_location, mval, false)) { copy_v3_v3(ss->pivot_pos, stroke_location); } } -- cgit v1.2.3 From 3c016fbfd092e0ea2bbdc62b2967a6b1f7da00d5 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 18 Jul 2022 19:55:51 +1000 Subject: Fix error indenting new-lines in generated RST API docs New-lines in RNA type descriptions caused invalid RST indentation. This resolve the error noted by @nutti in D15481. --- doc/python_api/sphinx_doc_gen.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/python_api/sphinx_doc_gen.py b/doc/python_api/sphinx_doc_gen.py index 17ffdb8e244..f5e0369cede 100644 --- a/doc/python_api/sphinx_doc_gen.py +++ b/doc/python_api/sphinx_doc_gen.py @@ -1529,7 +1529,8 @@ def pyrna2sphinx(basepath): else: fw(".. class:: %s\n\n" % struct_id) - fw(" %s\n\n" % struct.description) + write_indented_lines(" ", fw, struct.description, False) + fw("\n") # Properties sorted in alphabetical order. sorted_struct_properties = struct.properties[:] -- cgit v1.2.3 From c7f788b877cc60a8784c41b5cd5ba85044e1d04f Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 15 Jul 2022 19:32:09 +0200 Subject: Subdiv: remove unused GPU device choice, fix crash with libepoxy on init openSubdiv_init() would detect available evaluators before any OpenGL context exists, causing a crash with libepoxy. This test however is redundant as we already check the requirements on the Blender side through the GPU API. To simplify things, completely remove the device detection in the opensubdiv module and reduce the evaluators to just CPU and GPU. The plan here is to move to the GPU module abstraction over OpenGL/Metal/Vulkan and so all these different backends no longer make sense. This also removes the user preference for OpenSubdiv compute device, which was not used for the new GPU subdivision implementation. Ref D15291 Differential Revision: https://developer.blender.org/D15470 --- intern/opensubdiv/CMakeLists.txt | 12 ---- intern/opensubdiv/internal/base/opensubdiv_capi.cc | 40 ----------- .../internal/device/device_context_cuda.cc | 39 ----------- .../internal/device/device_context_cuda.h | 38 ---------- .../internal/device/device_context_glsl_compute.cc | 40 ----------- .../internal/device/device_context_glsl_compute.h | 38 ---------- .../device_context_glsl_transform_feedback.cc | 40 ----------- .../device_context_glsl_transform_feedback.h | 38 ---------- .../internal/device/device_context_opencl.cc | 39 ----------- .../internal/device/device_context_opencl.h | 38 ---------- .../internal/device/device_context_openmp.cc | 42 ----------- .../internal/device/device_context_openmp.h | 38 ---------- .../internal/evaluator/evaluator_cache_impl.cc | 2 +- .../internal/evaluator/evaluator_impl.cc | 9 +-- intern/opensubdiv/opensubdiv_capi.h | 3 - intern/opensubdiv/opensubdiv_capi_type.h | 10 +-- release/datafiles/userdef/userdef_default.c | 1 - release/scripts/startup/bl_ui/space_userpref.py | 6 -- source/blender/blenkernel/BKE_subdiv_eval.h | 2 +- source/blender/blenkernel/intern/subdiv_eval.c | 4 +- source/blender/blenkernel/intern/subdiv_modifier.c | 5 -- .../draw/intern/draw_cache_impl_subdivision.cc | 4 +- source/blender/makesdna/DNA_userdef_types.h | 3 +- source/blender/makesrna/intern/rna_userdef.c | 81 ---------------------- 24 files changed, 11 insertions(+), 561 deletions(-) delete mode 100644 intern/opensubdiv/internal/device/device_context_cuda.cc delete mode 100644 intern/opensubdiv/internal/device/device_context_cuda.h delete mode 100644 intern/opensubdiv/internal/device/device_context_glsl_compute.cc delete mode 100644 intern/opensubdiv/internal/device/device_context_glsl_compute.h delete mode 100644 intern/opensubdiv/internal/device/device_context_glsl_transform_feedback.cc delete mode 100644 intern/opensubdiv/internal/device/device_context_glsl_transform_feedback.h delete mode 100644 intern/opensubdiv/internal/device/device_context_opencl.cc delete mode 100644 intern/opensubdiv/internal/device/device_context_opencl.h delete mode 100644 intern/opensubdiv/internal/device/device_context_openmp.cc delete mode 100644 intern/opensubdiv/internal/device/device_context_openmp.h diff --git a/intern/opensubdiv/CMakeLists.txt b/intern/opensubdiv/CMakeLists.txt index bb3aa16a9fe..14cc6a70cd5 100644 --- a/intern/opensubdiv/CMakeLists.txt +++ b/intern/opensubdiv/CMakeLists.txt @@ -42,18 +42,6 @@ if(WITH_OPENSUBDIV) internal/base/util.cc internal/base/util.h - # Device. - internal/device/device_context_cuda.cc - internal/device/device_context_cuda.h - internal/device/device_context_glsl_compute.cc - internal/device/device_context_glsl_compute.h - internal/device/device_context_glsl_transform_feedback.cc - internal/device/device_context_glsl_transform_feedback.h - internal/device/device_context_opencl.cc - internal/device/device_context_opencl.h - internal/device/device_context_openmp.cc - internal/device/device_context_openmp.h - # Evaluator. internal/evaluator/eval_output.cc internal/evaluator/eval_output.h diff --git a/intern/opensubdiv/internal/base/opensubdiv_capi.cc b/intern/opensubdiv/internal/base/opensubdiv_capi.cc index 85f8120c76b..40d820836b9 100644 --- a/intern/opensubdiv/internal/base/opensubdiv_capi.cc +++ b/intern/opensubdiv/internal/base/opensubdiv_capi.cc @@ -21,55 +21,15 @@ #endif #include "internal/base/util.h" -#include "internal/device/device_context_cuda.h" -#include "internal/device/device_context_glsl_compute.h" -#include "internal/device/device_context_glsl_transform_feedback.h" -#include "internal/device/device_context_opencl.h" -#include "internal/device/device_context_openmp.h" - -using blender::opensubdiv::CUDADeviceContext; -using blender::opensubdiv::GLSLComputeDeviceContext; -using blender::opensubdiv::GLSLTransformFeedbackDeviceContext; -using blender::opensubdiv::OpenCLDeviceContext; -using blender::opensubdiv::OpenMPDeviceContext; void openSubdiv_init() { - // Ensure all OpenGL strings are cached. - openSubdiv_getAvailableEvaluators(); } void openSubdiv_cleanup() { } -int openSubdiv_getAvailableEvaluators() -{ - int flags = OPENSUBDIV_EVALUATOR_CPU; - - if (OpenMPDeviceContext::isSupported()) { - flags |= OPENSUBDIV_EVALUATOR_OPENMP; - } - - if (OpenCLDeviceContext::isSupported()) { - flags |= OPENSUBDIV_EVALUATOR_OPENCL; - } - - if (CUDADeviceContext::isSupported()) { - flags |= OPENSUBDIV_EVALUATOR_CUDA; - } - - if (GLSLTransformFeedbackDeviceContext::isSupported()) { - flags |= OPENSUBDIV_EVALUATOR_GLSL_TRANSFORM_FEEDBACK; - } - - if (GLSLComputeDeviceContext::isSupported()) { - flags |= OPENSUBDIV_EVALUATOR_GLSL_COMPUTE; - } - - return flags; -} - int openSubdiv_getVersionHex() { #if defined(OPENSUBDIV_VERSION_NUMBER) diff --git a/intern/opensubdiv/internal/device/device_context_cuda.cc b/intern/opensubdiv/internal/device/device_context_cuda.cc deleted file mode 100644 index cd4336265a5..00000000000 --- a/intern/opensubdiv/internal/device/device_context_cuda.cc +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2020 Blender Foundation. All rights reserved. -// -// 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. -// -// Author: Sergey Sharybin - -#include "internal/device/device_context_cuda.h" - -namespace blender { -namespace opensubdiv { - -bool CUDADeviceContext::isSupported() -{ - // TODO(sergey): Add CUDA device support, using CUDA-RT API. - return false; -} - -CUDADeviceContext::CUDADeviceContext() -{ -} - -CUDADeviceContext::~CUDADeviceContext() -{ -} - -} // namespace opensubdiv -} // namespace blender diff --git a/intern/opensubdiv/internal/device/device_context_cuda.h b/intern/opensubdiv/internal/device/device_context_cuda.h deleted file mode 100644 index d1bfb15fbcb..00000000000 --- a/intern/opensubdiv/internal/device/device_context_cuda.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2020 Blender Foundation. All rights reserved. -// -// 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. -// -// Author: Sergey Sharybin - -#ifndef OPENSUBDIV_DEVICE_CONTEXT_CUDA_H_ -#define OPENSUBDIV_DEVICE_CONTEXT_CUDA_H_ - -namespace blender { -namespace opensubdiv { - -class CUDADeviceContext { - public: - // Stateless check to see whether CUDA functionality is available on this - // platform. - static bool isSupported(); - - CUDADeviceContext(); - ~CUDADeviceContext(); -}; - -} // namespace opensubdiv -} // namespace blender - -#endif // _OPENSUBDIV_DEVICE_CONTEXT_CUDA_H_ diff --git a/intern/opensubdiv/internal/device/device_context_glsl_compute.cc b/intern/opensubdiv/internal/device/device_context_glsl_compute.cc deleted file mode 100644 index 7b416976099..00000000000 --- a/intern/opensubdiv/internal/device/device_context_glsl_compute.cc +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2020 Blender Foundation. All rights reserved. -// -// 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. -// -// Author: Sergey Sharybin - -#include "internal/device/device_context_glsl_compute.h" - -#include - -namespace blender { -namespace opensubdiv { - -bool GLSLComputeDeviceContext::isSupported() -{ - return GLEW_VERSION_4_3 || GLEW_ARB_compute_shader; -} - -GLSLComputeDeviceContext::GLSLComputeDeviceContext() -{ -} - -GLSLComputeDeviceContext::~GLSLComputeDeviceContext() -{ -} - -} // namespace opensubdiv -} // namespace blender diff --git a/intern/opensubdiv/internal/device/device_context_glsl_compute.h b/intern/opensubdiv/internal/device/device_context_glsl_compute.h deleted file mode 100644 index f64c7d1954b..00000000000 --- a/intern/opensubdiv/internal/device/device_context_glsl_compute.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2020 Blender Foundation. All rights reserved. -// -// 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. -// -// Author: Sergey Sharybin - -#ifndef OPENSUBDIV_DEVICE_CONTEXT_GLSL_COMPUTE_H_ -#define OPENSUBDIV_DEVICE_CONTEXT_GLSL_COMPUTE_H_ - -namespace blender { -namespace opensubdiv { - -class GLSLComputeDeviceContext { - public: - // Stateless check to see whether GLSL compute functionality is - // available on this platform. - static bool isSupported(); - - GLSLComputeDeviceContext(); - ~GLSLComputeDeviceContext(); -}; - -} // namespace opensubdiv -} // namespace blender - -#endif // _OPENSUBDIV_DEVICE_CONTEXT_GLSL_COMPUTE_H_ diff --git a/intern/opensubdiv/internal/device/device_context_glsl_transform_feedback.cc b/intern/opensubdiv/internal/device/device_context_glsl_transform_feedback.cc deleted file mode 100644 index ef897608b6e..00000000000 --- a/intern/opensubdiv/internal/device/device_context_glsl_transform_feedback.cc +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2020 Blender Foundation. All rights reserved. -// -// 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. -// -// Author: Sergey Sharybin - -#include "internal/device/device_context_glsl_transform_feedback.h" - -#include - -namespace blender { -namespace opensubdiv { - -bool GLSLTransformFeedbackDeviceContext::isSupported() -{ - return GLEW_VERSION_4_1; -} - -GLSLTransformFeedbackDeviceContext::GLSLTransformFeedbackDeviceContext() -{ -} - -GLSLTransformFeedbackDeviceContext::~GLSLTransformFeedbackDeviceContext() -{ -} - -} // namespace opensubdiv -} // namespace blender diff --git a/intern/opensubdiv/internal/device/device_context_glsl_transform_feedback.h b/intern/opensubdiv/internal/device/device_context_glsl_transform_feedback.h deleted file mode 100644 index 7bbbba1380f..00000000000 --- a/intern/opensubdiv/internal/device/device_context_glsl_transform_feedback.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2020 Blender Foundation. All rights reserved. -// -// 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. -// -// Author: Sergey Sharybin - -#ifndef OPENSUBDIV_DEVICE_CONTEXT_GLSL_TRANSFORM_FEEDBACK_H_ -#define OPENSUBDIV_DEVICE_CONTEXT_GLSL_TRANSFORM_FEEDBACK_H_ - -namespace blender { -namespace opensubdiv { - -class GLSLTransformFeedbackDeviceContext { - public: - // Stateless check to see whether GLSL transform feedback functionality is - // available on this platform. - static bool isSupported(); - - GLSLTransformFeedbackDeviceContext(); - ~GLSLTransformFeedbackDeviceContext(); -}; - -} // namespace opensubdiv -} // namespace blender - -#endif // _OPENSUBDIV_DEVICE_CONTEXT_GLSL_TRANSFORM_FEEDBACK_H_ diff --git a/intern/opensubdiv/internal/device/device_context_opencl.cc b/intern/opensubdiv/internal/device/device_context_opencl.cc deleted file mode 100644 index 1670ea3c9d0..00000000000 --- a/intern/opensubdiv/internal/device/device_context_opencl.cc +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2020 Blender Foundation. All rights reserved. -// -// 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. -// -// Author: Sergey Sharybin - -#include "internal/device/device_context_opencl.h" - -namespace blender { -namespace opensubdiv { - -bool OpenCLDeviceContext::isSupported() -{ - // TODO(sergey): Add support of OpenCL devices. - return false; -} - -OpenCLDeviceContext::OpenCLDeviceContext() -{ -} - -OpenCLDeviceContext::~OpenCLDeviceContext() -{ -} - -} // namespace opensubdiv -} // namespace blender diff --git a/intern/opensubdiv/internal/device/device_context_opencl.h b/intern/opensubdiv/internal/device/device_context_opencl.h deleted file mode 100644 index 57ec6ed3bb6..00000000000 --- a/intern/opensubdiv/internal/device/device_context_opencl.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2020 Blender Foundation. All rights reserved. -// -// 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. -// -// Author: Sergey Sharybin - -#ifndef OPENSUBDIV_DEVICE_CONTEXT_OPENCL_H_ -#define OPENSUBDIV_DEVICE_CONTEXT_OPENCL_H_ - -namespace blender { -namespace opensubdiv { - -class OpenCLDeviceContext { - public: - // Stateless check to see whether OpenCL functionality is available on this - // platform. - static bool isSupported(); - - OpenCLDeviceContext(); - ~OpenCLDeviceContext(); -}; - -} // namespace opensubdiv -} // namespace blender - -#endif // _OPENSUBDIV_DEVICE_CONTEXT_OPENCL_H_ diff --git a/intern/opensubdiv/internal/device/device_context_openmp.cc b/intern/opensubdiv/internal/device/device_context_openmp.cc deleted file mode 100644 index e01312fefaf..00000000000 --- a/intern/opensubdiv/internal/device/device_context_openmp.cc +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2020 Blender Foundation. All rights reserved. -// -// 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. -// -// Author: Sergey Sharybin - -#include "internal/device/device_context_openmp.h" - -namespace blender { -namespace opensubdiv { - -bool OpenMPDeviceContext::isSupported() -{ -#ifdef OPENSUBDIV_HAS_OPENMP - return true; -#else - return false; -#endif -} - -OpenMPDeviceContext::OpenMPDeviceContext() -{ -} - -OpenMPDeviceContext::~OpenMPDeviceContext() -{ -} - -} // namespace opensubdiv -} // namespace blender diff --git a/intern/opensubdiv/internal/device/device_context_openmp.h b/intern/opensubdiv/internal/device/device_context_openmp.h deleted file mode 100644 index 2bebbdf40bc..00000000000 --- a/intern/opensubdiv/internal/device/device_context_openmp.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2020 Blender Foundation. All rights reserved. -// -// 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. -// -// Author: Sergey Sharybin - -#ifndef OPENSUBDIV_DEVICE_CONTEXT_OPENMP_H_ -#define OPENSUBDIV_DEVICE_CONTEXT_OPENMP_H_ - -namespace blender { -namespace opensubdiv { - -class OpenMPDeviceContext { - public: - // Stateless check to see whether OpenMP functionality is available on this - // platform. - static bool isSupported(); - - OpenMPDeviceContext(); - ~OpenMPDeviceContext(); -}; - -} // namespace opensubdiv -} // namespace blender - -#endif // _OPENSUBDIV_DEVICE_CONTEXT_OPENMP_H_ diff --git a/intern/opensubdiv/internal/evaluator/evaluator_cache_impl.cc b/intern/opensubdiv/internal/evaluator/evaluator_cache_impl.cc index 2fffcefa460..efac0926fef 100644 --- a/intern/opensubdiv/internal/evaluator/evaluator_cache_impl.cc +++ b/intern/opensubdiv/internal/evaluator/evaluator_cache_impl.cc @@ -30,7 +30,7 @@ OpenSubdiv_EvaluatorCacheImpl::~OpenSubdiv_EvaluatorCacheImpl() OpenSubdiv_EvaluatorCacheImpl *openSubdiv_createEvaluatorCacheInternal( eOpenSubdivEvaluator evaluator_type) { - if (evaluator_type != eOpenSubdivEvaluator::OPENSUBDIV_EVALUATOR_GLSL_COMPUTE) { + if (evaluator_type != eOpenSubdivEvaluator::OPENSUBDIV_EVALUATOR_GPU) { return nullptr; } OpenSubdiv_EvaluatorCacheImpl *evaluator_cache; diff --git a/intern/opensubdiv/internal/evaluator/evaluator_impl.cc b/intern/opensubdiv/internal/evaluator/evaluator_impl.cc index 49a59c44be8..262b418169d 100644 --- a/intern/opensubdiv/internal/evaluator/evaluator_impl.cc +++ b/intern/opensubdiv/internal/evaluator/evaluator_impl.cc @@ -442,11 +442,6 @@ OpenSubdiv_EvaluatorImpl *openSubdiv_createEvaluatorInternal( eOpenSubdivEvaluator evaluator_type, OpenSubdiv_EvaluatorCacheImpl *evaluator_cache_descr) { - // Only CPU and GLCompute are implemented at the moment. - if (evaluator_type != OPENSUBDIV_EVALUATOR_CPU && - evaluator_type != OPENSUBDIV_EVALUATOR_GLSL_COMPUTE) { - return NULL; - } using blender::opensubdiv::vector; TopologyRefiner *refiner = topology_refiner->impl->topology_refiner; if (refiner == NULL) { @@ -551,8 +546,8 @@ OpenSubdiv_EvaluatorImpl *openSubdiv_createEvaluatorInternal( // Create OpenSubdiv's CPU side evaluator. blender::opensubdiv::EvalOutputAPI::EvalOutput *eval_output = nullptr; - const bool use_gl_evaluator = evaluator_type == OPENSUBDIV_EVALUATOR_GLSL_COMPUTE; - if (use_gl_evaluator) { + const bool use_gpu_evaluator = evaluator_type == OPENSUBDIV_EVALUATOR_GPU; + if (use_gpu_evaluator) { blender::opensubdiv::GpuEvalOutput::EvaluatorCache *evaluator_cache = nullptr; if (evaluator_cache_descr) { evaluator_cache = static_cast( diff --git a/intern/opensubdiv/opensubdiv_capi.h b/intern/opensubdiv/opensubdiv_capi.h index a26ea36b863..1fa75a9a8c2 100644 --- a/intern/opensubdiv/opensubdiv_capi.h +++ b/intern/opensubdiv/opensubdiv_capi.h @@ -31,9 +31,6 @@ extern "C" { void openSubdiv_init(void); void openSubdiv_cleanup(void); -// Bitmask of eOpenSubdivEvaluator. -int openSubdiv_getAvailableEvaluators(void); - int openSubdiv_getVersionHex(void); #ifdef __cplusplus diff --git a/intern/opensubdiv/opensubdiv_capi_type.h b/intern/opensubdiv/opensubdiv_capi_type.h index e78842036be..585d9bc49df 100644 --- a/intern/opensubdiv/opensubdiv_capi_type.h +++ b/intern/opensubdiv/opensubdiv_capi_type.h @@ -23,15 +23,9 @@ extern "C" { #endif -// Keep this a bitmask so it's possible to pass available -// evaluators to Blender. typedef enum eOpenSubdivEvaluator { - OPENSUBDIV_EVALUATOR_CPU = (1 << 0), - OPENSUBDIV_EVALUATOR_OPENMP = (1 << 1), - OPENSUBDIV_EVALUATOR_OPENCL = (1 << 2), - OPENSUBDIV_EVALUATOR_CUDA = (1 << 3), - OPENSUBDIV_EVALUATOR_GLSL_TRANSFORM_FEEDBACK = (1 << 4), - OPENSUBDIV_EVALUATOR_GLSL_COMPUTE = (1 << 5), + OPENSUBDIV_EVALUATOR_CPU = 0, + OPENSUBDIV_EVALUATOR_GPU = 1, } eOpenSubdivEvaluator; typedef enum OpenSubdiv_SchemeType { diff --git a/release/datafiles/userdef/userdef_default.c b/release/datafiles/userdef/userdef_default.c index fba3c7882c6..b4a1ab6d5f8 100644 --- a/release/datafiles/userdef/userdef_default.c +++ b/release/datafiles/userdef/userdef_default.c @@ -174,7 +174,6 @@ const UserDef U_default = { .pie_menu_confirm = 0, .pie_menu_radius = 100, .pie_menu_threshold = 12, - .opensubdiv_compute_type = 0, .factor_display_type = USER_FACTOR_AS_FACTOR, .render_display_type = USER_RENDER_DISPLAY_WINDOW, .filebrowser_display_type = USER_TEMP_SPACE_DISPLAY_WINDOW, diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py index 7a651e8c3c8..52b2fb7f3da 100644 --- a/release/scripts/startup/bl_ui/space_userpref.py +++ b/release/scripts/startup/bl_ui/space_userpref.py @@ -589,12 +589,6 @@ class USERPREF_PT_system_cycles_devices(SystemPanel, CenterAlignMixIn, Panel): addon.preferences.draw_impl(col, context) del addon - # NOTE: Disabled for until GPU side of OpenSubdiv is brought back. - # system = prefs.system - # if hasattr(system, "opensubdiv_compute_type"): - # col.label(text="OpenSubdiv compute:") - # col.row().prop(system, "opensubdiv_compute_type", text="") - class USERPREF_PT_system_os_settings(SystemPanel, CenterAlignMixIn, Panel): bl_label = "Operating System Settings" diff --git a/source/blender/blenkernel/BKE_subdiv_eval.h b/source/blender/blenkernel/BKE_subdiv_eval.h index cb0f2ac7e32..e4a7f813a8b 100644 --- a/source/blender/blenkernel/BKE_subdiv_eval.h +++ b/source/blender/blenkernel/BKE_subdiv_eval.h @@ -20,7 +20,7 @@ struct Subdiv; typedef enum eSubdivEvaluatorType { SUBDIV_EVALUATOR_TYPE_CPU, - SUBDIV_EVALUATOR_TYPE_GLSL_COMPUTE, + SUBDIV_EVALUATOR_TYPE_GPU, } eSubdivEvaluatorType; /* Returns true if evaluator is ready for use. */ diff --git a/source/blender/blenkernel/intern/subdiv_eval.c b/source/blender/blenkernel/intern/subdiv_eval.c index fda833ffd27..841b47818f1 100644 --- a/source/blender/blenkernel/intern/subdiv_eval.c +++ b/source/blender/blenkernel/intern/subdiv_eval.c @@ -34,8 +34,8 @@ static eOpenSubdivEvaluator opensubdiv_evalutor_from_subdiv_evaluator_type( case SUBDIV_EVALUATOR_TYPE_CPU: { return OPENSUBDIV_EVALUATOR_CPU; } - case SUBDIV_EVALUATOR_TYPE_GLSL_COMPUTE: { - return OPENSUBDIV_EVALUATOR_GLSL_COMPUTE; + case SUBDIV_EVALUATOR_TYPE_GPU: { + return OPENSUBDIV_EVALUATOR_GPU; } } BLI_assert_msg(0, "Unknown evaluator type"); diff --git a/source/blender/blenkernel/intern/subdiv_modifier.c b/source/blender/blenkernel/intern/subdiv_modifier.c index 57af0192d59..2271fd90bda 100644 --- a/source/blender/blenkernel/intern/subdiv_modifier.c +++ b/source/blender/blenkernel/intern/subdiv_modifier.c @@ -98,11 +98,6 @@ static bool is_subdivision_evaluation_possible_on_gpu(void) return false; } - const int available_evaluators = openSubdiv_getAvailableEvaluators(); - if ((available_evaluators & OPENSUBDIV_EVALUATOR_GLSL_COMPUTE) == 0) { - return false; - } - return true; } diff --git a/source/blender/draw/intern/draw_cache_impl_subdivision.cc b/source/blender/draw/intern/draw_cache_impl_subdivision.cc index 29ab814cfa4..cde2b59ea23 100644 --- a/source/blender/draw/intern/draw_cache_impl_subdivision.cc +++ b/source/blender/draw/intern/draw_cache_impl_subdivision.cc @@ -2045,7 +2045,7 @@ static bool draw_subdiv_create_requested_buffers(Object *ob, draw_subdiv_invalidate_evaluator_for_orco(subdiv, mesh_eval); if (!BKE_subdiv_eval_begin_from_mesh( - subdiv, mesh_eval, nullptr, SUBDIV_EVALUATOR_TYPE_GLSL_COMPUTE, evaluator_cache)) { + subdiv, mesh_eval, nullptr, SUBDIV_EVALUATOR_TYPE_GPU, evaluator_cache)) { /* This could happen in two situations: * - OpenSubdiv is disabled. * - Something totally bad happened, and OpenSubdiv rejected our @@ -2220,7 +2220,7 @@ void DRW_create_subdivision(Object *ob, const bool use_hide) { if (g_evaluator_cache == nullptr) { - g_evaluator_cache = openSubdiv_createEvaluatorCache(OPENSUBDIV_EVALUATOR_GLSL_COMPUTE); + g_evaluator_cache = openSubdiv_createEvaluatorCache(OPENSUBDIV_EVALUATOR_GPU); } #undef TIME_SUBDIV diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 2ceef4f623e..74fb1c3ac96 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -913,8 +913,7 @@ typedef struct UserDef { /** Pie menu distance from center before a direction is set. */ short pie_menu_threshold; - short opensubdiv_compute_type; - short _pad6; + short _pad6[2]; char factor_display_type; diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index 25eacdaab30..cfc72791123 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -38,23 +38,6 @@ #include "BLT_lang.h" -#ifdef WITH_OPENSUBDIV -static const EnumPropertyItem opensubdiv_compute_type_items[] = { - {USER_OPENSUBDIV_COMPUTE_NONE, "NONE", 0, "None", ""}, - {USER_OPENSUBDIV_COMPUTE_CPU, "CPU", 0, "CPU", ""}, - {USER_OPENSUBDIV_COMPUTE_OPENMP, "OPENMP", 0, "OpenMP", ""}, - {USER_OPENSUBDIV_COMPUTE_OPENCL, "OPENCL", 0, "OpenCL", ""}, - {USER_OPENSUBDIV_COMPUTE_CUDA, "CUDA", 0, "CUDA", ""}, - {USER_OPENSUBDIV_COMPUTE_GLSL_TRANSFORM_FEEDBACK, - "GLSL_TRANSFORM_FEEDBACK", - 0, - "GLSL Transform Feedback", - ""}, - {USER_OPENSUBDIV_COMPUTE_GLSL_COMPUTE, "GLSL_COMPUTE", 0, "GLSL Compute", ""}, - {0, NULL, 0, NULL, NULL}, -}; -#endif - const EnumPropertyItem rna_enum_preference_section_items[] = { {USER_SECTION_INTERFACE, "INTERFACE", 0, "Interface", ""}, {USER_SECTION_THEME, "THEMES", 0, "Themes", ""}, @@ -189,10 +172,6 @@ static const EnumPropertyItem rna_enum_userdef_viewport_aa_items[] = { # include "UI_interface.h" -# ifdef WITH_OPENSUBDIV -# include "opensubdiv_capi.h" -# endif - # ifdef WITH_SDL_DYNLOAD # include "sdlew.h" # endif @@ -728,55 +707,6 @@ static PointerRNA rna_Theme_space_list_generic_get(PointerRNA *ptr) return rna_pointer_inherit_refine(ptr, &RNA_ThemeSpaceListGeneric, ptr->data); } -# ifdef WITH_OPENSUBDIV -static const EnumPropertyItem *rna_userdef_opensubdiv_compute_type_itemf(bContext *UNUSED(C), - PointerRNA *UNUSED(ptr), - PropertyRNA *UNUSED(prop), - bool *r_free) -{ - EnumPropertyItem *item = NULL; - int totitem = 0; - int evaluators = openSubdiv_getAvailableEvaluators(); - - RNA_enum_items_add_value( - &item, &totitem, opensubdiv_compute_type_items, USER_OPENSUBDIV_COMPUTE_NONE); - -# define APPEND_COMPUTE(compute) \ - if (evaluators & OPENSUBDIV_EVALUATOR_##compute) { \ - RNA_enum_items_add_value( \ - &item, &totitem, opensubdiv_compute_type_items, USER_OPENSUBDIV_COMPUTE_##compute); \ - } \ - ((void)0) - - APPEND_COMPUTE(CPU); - APPEND_COMPUTE(OPENMP); - APPEND_COMPUTE(OPENCL); - APPEND_COMPUTE(CUDA); - APPEND_COMPUTE(GLSL_TRANSFORM_FEEDBACK); - APPEND_COMPUTE(GLSL_COMPUTE); - -# undef APPEND_COMPUTE - - RNA_enum_item_end(&item, &totitem); - *r_free = true; - - return item; -} - -static void rna_userdef_opensubdiv_update(Main *bmain, - Scene *UNUSED(scene), - PointerRNA *UNUSED(ptr)) -{ - Object *object; - - for (object = bmain->objects.first; object; object = object->id.next) { - DEG_id_tag_update(&object->id, ID_RECALC_TRANSFORM); - } - USERDEF_TAG_DIRTY; -} - -# endif - static const EnumPropertyItem *rna_userdef_audio_device_itemf(bContext *UNUSED(C), PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), @@ -5699,17 +5629,6 @@ static void rna_def_userdef_system(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Audio Channels", "Audio channel count"); RNA_def_property_update(prop, 0, "rna_UserDef_audio_update"); -# ifdef WITH_OPENSUBDIV - prop = RNA_def_property(srna, "opensubdiv_compute_type", PROP_ENUM, PROP_NONE); - RNA_def_property_flag(prop, PROP_ENUM_NO_CONTEXT); - RNA_def_property_enum_sdna(prop, NULL, "opensubdiv_compute_type"); - RNA_def_property_enum_items(prop, opensubdiv_compute_type_items); - RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_userdef_opensubdiv_compute_type_itemf"); - RNA_def_property_ui_text( - prop, "OpenSubdiv Compute Type", "Type of computer back-end used with OpenSubdiv"); - RNA_def_property_update(prop, NC_SPACE | ND_SPACE_PROPERTIES, "rna_userdef_opensubdiv_update"); -# endif - # ifdef WITH_CYCLES prop = RNA_def_property(srna, "legacy_compute_device_type", PROP_INT, PROP_NONE); RNA_def_property_int_sdna(prop, NULL, "compute_device_type"); -- cgit v1.2.3 From 47f3b5375648505f435f21417012ffcd78ddd923 Mon Sep 17 00:00:00 2001 From: Jun Mizutani Date: Mon, 18 Jul 2022 14:47:26 +0200 Subject: Fix T99742: crash when generating rigify rigs, after recent changes Differential Revision: https://developer.blender.org/D15473 --- source/blender/editors/armature/armature_naming.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/editors/armature/armature_naming.c b/source/blender/editors/armature/armature_naming.c index 8d5ae6f3654..4f329dbe449 100644 --- a/source/blender/editors/armature/armature_naming.c +++ b/source/blender/editors/armature/armature_naming.c @@ -285,7 +285,7 @@ void ED_armature_bone_rename(Main *bmain, /* fix camera focus */ if (ob->type == OB_CAMERA) { Camera *cam = (Camera *)ob->data; - if (cam->dof.focus_object->data == arm){ + if ((cam->dof.focus_object != NULL) && (cam->dof.focus_object->data == arm)) { if (STREQ(cam->dof.focus_subtarget, oldname)) { BLI_strncpy(cam->dof.focus_subtarget, newname, MAXBONENAME); DEG_id_tag_update(&cam->id, ID_RECALC_COPY_ON_WRITE); -- cgit v1.2.3 From dec8854bf31df3cedfa0efb5169160ba4a54d202 Mon Sep 17 00:00:00 2001 From: Damien Picard Date: Mon, 18 Jul 2022 15:09:37 +0200 Subject: I18n: translate add node operator tooltips The tooltips from the Add Node menu were extracted, but not translated. Reviewed By: mont29 Differential Revision: https://developer.blender.org/D15467 --- release/scripts/startup/bl_operators/node.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/release/scripts/startup/bl_operators/node.py b/release/scripts/startup/bl_operators/node.py index bad12ff4621..df4ca9ef170 100644 --- a/release/scripts/startup/bl_operators/node.py +++ b/release/scripts/startup/bl_operators/node.py @@ -14,6 +14,8 @@ from bpy.props import ( StringProperty, ) +from bpy.app.translations import pgettext_tip as tip_ + class NodeSetting(PropertyGroup): value: StringProperty( @@ -134,7 +136,7 @@ class NodeAddOperator: nodetype = properties["type"] bl_rna = bpy.types.Node.bl_rna_get_subclass(nodetype) if bl_rna is not None: - return bl_rna.description + return tip_(bl_rna.description) else: return "" -- cgit v1.2.3 From 3407ed5f9b489aaa81a730b0c0245dc6f3707abb Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Mon, 18 Jul 2022 14:55:21 +0200 Subject: Cleanup: change internal Cycles compact BVH default to match UI --- intern/cycles/bvh/params.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intern/cycles/bvh/params.h b/intern/cycles/bvh/params.h index 41d851ee687..648350d03b0 100644 --- a/intern/cycles/bvh/params.h +++ b/intern/cycles/bvh/params.h @@ -129,7 +129,7 @@ class BVHParams { top_level = false; bvh_layout = BVH_LAYOUT_BVH2; - use_compact_structure = true; + use_compact_structure = false; use_unaligned_nodes = false; num_motion_curve_steps = 0; -- cgit v1.2.3 From 757041560f6dca8eecfa743dc563394623e3ad13 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Mon, 18 Jul 2022 14:38:43 +0200 Subject: Build: update Embree to 3.13.4, enable Neon2x on Arm * Allows Apple Silicon machines to use 8-wide BVH, which the release notes mention give an 8% performance boost. * An update to this version is also required for OpenPGL. This patch includes contributions from Jason Fielder and Sebastian Herholz. Ref D15286, T98555 Differential Revision: https://developer.blender.org/D15482 --- build_files/build_environment/cmake/embree.cmake | 23 +------- build_files/build_environment/cmake/versions.cmake | 4 +- build_files/build_environment/install_deps.sh | 2 +- build_files/build_environment/patches/embree.diff | 65 ++++++++++++---------- 4 files changed, 41 insertions(+), 53 deletions(-) diff --git a/build_files/build_environment/cmake/embree.cmake b/build_files/build_environment/cmake/embree.cmake index 2eafc729111..8c689cf000b 100644 --- a/build_files/build_environment/cmake/embree.cmake +++ b/build_files/build_environment/cmake/embree.cmake @@ -10,18 +10,12 @@ set(EMBREE_EXTRA_ARGS -DEMBREE_RAY_MASK=ON -DEMBREE_FILTER_FUNCTION=ON -DEMBREE_BACKFACE_CULLING=OFF - -DEMBREE_MAX_ISA=AVX2 -DEMBREE_TASKING_SYSTEM=TBB -DEMBREE_TBB_ROOT=${LIBDIR}/tbb -DTBB_ROOT=${LIBDIR}/tbb - -DTBB_STATIC_LIB=${TBB_STATIC_LIBRARY} ) -if(BLENDER_PLATFORM_ARM) - set(EMBREE_EXTRA_ARGS - ${EMBREE_EXTRA_ARGS} - -DEMBREE_MAX_ISA=NEON) -else() +if (NOT BLENDER_PLATFORM_ARM) set(EMBREE_EXTRA_ARGS ${EMBREE_EXTRA_ARGS} -DEMBREE_MAX_ISA=AVX2) @@ -30,23 +24,10 @@ endif() if(TBB_STATIC_LIBRARY) set(EMBREE_EXTRA_ARGS ${EMBREE_EXTRA_ARGS} - -DEMBREE_TBB_LIBRARY_NAME=tbb_static - -DEMBREE_TBBMALLOC_LIBRARY_NAME=tbbmalloc_static + -DEMBREE_TBB_COMPONENT=tbb_static ) endif() -if(WIN32) - set(EMBREE_BUILD_DIR ${BUILD_MODE}/) - if(BUILD_MODE STREQUAL Debug) - list(APPEND EMBREE_EXTRA_ARGS - -DEMBREE_TBBMALLOC_LIBRARY_NAME=tbbmalloc_debug - -DEMBREE_TBB_LIBRARY_NAME=tbb_debug - ) - endif() -else() - set(EMBREE_BUILD_DIR) -endif() - ExternalProject_Add(external_embree URL file://${PACKAGE_DIR}/${EMBREE_FILE} DOWNLOAD_DIR ${DOWNLOAD_DIR} diff --git a/build_files/build_environment/cmake/versions.cmake b/build_files/build_environment/cmake/versions.cmake index 1a4ad291581..42c82b68654 100644 --- a/build_files/build_environment/cmake/versions.cmake +++ b/build_files/build_environment/cmake/versions.cmake @@ -410,9 +410,9 @@ set(SQLITE_HASH fb558c49ee21a837713c4f1e7e413309aabdd9c7) set(SQLITE_HASH_TYPE SHA1) set(SQLITE_FILE sqlite-src-3240000.zip) -set(EMBREE_VERSION 3.13.3) +set(EMBREE_VERSION 3.13.4) set(EMBREE_URI https://github.com/embree/embree/archive/v${EMBREE_VERSION}.zip) -set(EMBREE_HASH f62766ba54e48a2f327c3a22596e7133) +set(EMBREE_HASH 52d0be294d6c88ba7a6c9e046796e7be) set(EMBREE_HASH_TYPE MD5) set(EMBREE_FILE embree-v${EMBREE_VERSION}.zip) diff --git a/build_files/build_environment/install_deps.sh b/build_files/build_environment/install_deps.sh index cfba4f12c73..2441b9ad89b 100755 --- a/build_files/build_environment/install_deps.sh +++ b/build_files/build_environment/install_deps.sh @@ -567,7 +567,7 @@ OPENCOLLADA_FORCE_BUILD=false OPENCOLLADA_FORCE_REBUILD=false OPENCOLLADA_SKIP=false -EMBREE_VERSION="3.13.3" +EMBREE_VERSION="3.13.4" EMBREE_VERSION_SHORT="3.13" EMBREE_VERSION_MIN="3.13" EMBREE_VERSION_MEX="4.0" diff --git a/build_files/build_environment/patches/embree.diff b/build_files/build_environment/patches/embree.diff index e83d754a465..e448fe5ee2e 100644 --- a/build_files/build_environment/patches/embree.diff +++ b/build_files/build_environment/patches/embree.diff @@ -1,30 +1,37 @@ -diff -Naur orig/common/sys/platform.h external_embree/common/sys/platform.h ---- orig/common/sys/platform.h 2020-05-13 23:08:53 -0600 -+++ external_embree/common/sys/platform.h 2020-06-13 17:40:26 -0600 -@@ -84,8 +84,8 @@ - //////////////////////////////////////////////////////////////////////////////// +diff -Naur org/kernels/rtcore_config.h.in embree-3.13.4/kernels/rtcore_config.h.in +--- org/kernels/rtcore_config.h.in 2022-06-14 22:13:52 -0600 ++++ embree-3.13.4/kernels/rtcore_config.h.in 2022-06-24 15:20:12 -0600 +@@ -14,6 +14,7 @@ + #cmakedefine01 EMBREE_MIN_WIDTH + #define RTC_MIN_WIDTH EMBREE_MIN_WIDTH + ++#cmakedefine EMBREE_STATIC_LIB + #cmakedefine EMBREE_API_NAMESPACE + + #if defined(EMBREE_API_NAMESPACE) +diff --git a/kernels/CMakeLists.txt b/kernels/CMakeLists.txt +index 7c2f43d..106b1d5 100644 +--- a/kernels/CMakeLists.txt ++++ b/kernels/CMakeLists.txt +@@ -201,6 +201,12 @@ embree_files(EMBREE_LIBRARY_FILES_AVX512 ${AVX512}) + #message("AVX2: ${EMBREE_LIBRARY_FILES_AVX2}") + #message("AVX512: ${EMBREE_LIBRARY_FILES_AVX512}") - #ifdef __WIN32__ --#define dll_export __declspec(dllexport) --#define dll_import __declspec(dllimport) -+#define dll_export -+#define dll_import - #else - #define dll_export __attribute__ ((visibility ("default"))) - #define dll_import -diff --git orig/common/tasking/CMakeLists.txt external_embree/common/tasking/CMakeLists.txt ---- orig/common/tasking/CMakeLists.txt -+++ external_embree/common/tasking/CMakeLists.txt -@@ -27,7 +27,11 @@ - else() - # If not found try getting older TBB via module (FindTBB.cmake) - unset(TBB_DIR CACHE) -- find_package(TBB 4.1 REQUIRED tbb) -+ if (TBB_STATIC_LIB) -+ find_package(TBB 4.1 REQUIRED tbb_static) -+ else() -+ find_package(TBB 4.1 REQUIRED tbb) -+ endif() - if (TBB_FOUND) - TARGET_LINK_LIBRARIES(tasking PUBLIC TBB) - TARGET_INCLUDE_DIRECTORIES(tasking PUBLIC "${TBB_INCLUDE_DIRS}") ++# Bundle Neon2x into the main static library. ++IF(EMBREE_ISA_NEON2X AND EMBREE_STATIC_LIB) ++ LIST(APPEND EMBREE_LIBRARY_FILES ${EMBREE_LIBRARY_FILES_AVX2}) ++ LIST(REMOVE_DUPLICATES EMBREE_LIBRARY_FILES) ++ENDIF() ++ + # replaces all .cpp files with a dummy file that includes that .cpp file + # this is to work around an ICC name mangling issue related to lambda functions under windows + MACRO (CreateISADummyFiles list isa) +@@ -277,7 +283,7 @@ IF (EMBREE_ISA_AVX AND EMBREE_LIBRARY_FILES_AVX) + ENDIF() + ENDIF() + +-IF (EMBREE_ISA_AVX2 AND EMBREE_LIBRARY_FILES_AVX2) ++IF (EMBREE_ISA_AVX2 AND EMBREE_LIBRARY_FILES_AVX2 AND NOT (EMBREE_ISA_NEON2X AND EMBREE_STATIC_LIB)) + DISABLE_STACK_PROTECTOR_FOR_INTERSECTORS(${EMBREE_LIBRARY_FILES_AVX2}) + ADD_LIBRARY(embree_avx2 STATIC ${EMBREE_LIBRARY_FILES_AVX2}) + TARGET_LINK_LIBRARIES(embree_avx2 PRIVATE tasking) -- cgit v1.2.3 From 1f8567ac68b667ba9b001fd2f33c4c1f24ccafa0 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Mon, 18 Jul 2022 15:47:21 +0200 Subject: Fix T99750: crash with file output node, after image colorspace saving changes --- source/blender/imbuf/intern/divers.c | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/source/blender/imbuf/intern/divers.c b/source/blender/imbuf/intern/divers.c index 588c92d748d..13c8f0887b3 100644 --- a/source/blender/imbuf/intern/divers.c +++ b/source/blender/imbuf/intern/divers.c @@ -695,9 +695,6 @@ void IMB_buffer_byte_from_byte(uchar *rect_to, void IMB_rect_from_float(ImBuf *ibuf) { - float *buffer; - const char *from_colorspace; - /* verify we have a float buffer */ if (ibuf->rect_float == NULL) { return; @@ -710,24 +707,21 @@ void IMB_rect_from_float(ImBuf *ibuf) } } - if (ibuf->float_colorspace == NULL) { - from_colorspace = IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_SCENE_LINEAR); - } - else { - from_colorspace = ibuf->float_colorspace->name; - } + const char *from_colorspace = (ibuf->float_colorspace == NULL) ? + IMB_colormanagement_role_colorspace_name_get( + COLOR_ROLE_SCENE_LINEAR) : + ibuf->float_colorspace->name; + const char *to_colorspace = (ibuf->rect_colorspace == NULL) ? + IMB_colormanagement_role_colorspace_name_get( + COLOR_ROLE_DEFAULT_BYTE) : + ibuf->rect_colorspace->name; - buffer = MEM_dupallocN(ibuf->rect_float); + float *buffer = MEM_dupallocN(ibuf->rect_float); /* first make float buffer in byte space */ const bool predivide = IMB_alpha_affects_rgb(ibuf); - IMB_colormanagement_transform(buffer, - ibuf->x, - ibuf->y, - ibuf->channels, - from_colorspace, - ibuf->rect_colorspace->name, - predivide); + IMB_colormanagement_transform( + buffer, ibuf->x, ibuf->y, ibuf->channels, from_colorspace, to_colorspace, predivide); /* convert from float's premul alpha to byte's straight alpha */ if (IMB_alpha_affects_rgb(ibuf)) { -- cgit v1.2.3 From 935b7a6f65d2c90e54b59a50fd1c488f02da0e8e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 24 Jun 2022 17:08:54 +0300 Subject: Context: implement indexing for list properties in path_resolve. The real RNA path_resolve method supports indexing lists, but the python version on the Context object does not. This patch adds the missing feature for completeness. Differential Revision: https://developer.blender.org/D15413 --- release/scripts/modules/bpy_types.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/release/scripts/modules/bpy_types.py b/release/scripts/modules/bpy_types.py index aaa2ae59a0d..df0631ec26d 100644 --- a/release/scripts/modules/bpy_types.py +++ b/release/scripts/modules/bpy_types.py @@ -56,6 +56,21 @@ class Context(StructRNA): if value is None: return value + # If the attribute is a list property, apply subscripting. + if isinstance(value, list) and path_rest.startswith("["): + index_str, div, index_tail = path_rest[1:].partition("]") + if not div: + raise ValueError("Path index is not terminated: %s%s" % (attr, path_rest)) + try: + index = int(index_str) + except ValueError: + raise ValueError("Path index is invalid: %s[%s]" % (attr, index_str)) + if 0 <= index < len(value): + path_rest = index_tail + value = value[index] + else: + raise IndexError("Path index out of range: %s[%s]" % (attr, index_str)) + # Resolve the rest of the path if necessary. if path_rest: path_resolve_fn = getattr(value, "path_resolve", None) -- cgit v1.2.3 From cd21022b78fe48c16ab11d04682bb92271873a9c Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 10 Jul 2022 12:28:44 +0300 Subject: Context: implement an active_action property that returns a single action. Although e.g. in the dopesheet there is no specific concept of active action, displaying panels requires singling out one action reference. It is more efficient and clearer to implement this natively in the context rather than using selected_visible_actions[0]. - In the Action Editor the action is taken from the header. - In the Dope Sheet the first selected action is chosen, because there is no concept of an active channel or keyframe. - In the Graph Editor the action associated with the active curve is used, which should also be associated with the active vertex. This case may be different from selected_visible_actions[0]. Differential Revision: https://developer.blender.org/D15412 --- release/scripts/startup/bl_ui/space_dopesheet.py | 4 +- source/blender/editors/screen/screen_context.c | 54 +++++++++++++++++------- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/release/scripts/startup/bl_ui/space_dopesheet.py b/release/scripts/startup/bl_ui/space_dopesheet.py index a1c086db745..4d1514c2777 100644 --- a/release/scripts/startup/bl_ui/space_dopesheet.py +++ b/release/scripts/startup/bl_ui/space_dopesheet.py @@ -567,10 +567,10 @@ class DOPESHEET_PT_action(DopesheetActionPanelBase, Panel): @classmethod def poll(cls, context): - return bool(context.selected_visible_actions) + return bool(context.active_action) def draw(self, context): - action = context.selected_visible_actions[0] + action = context.active_action self.draw_generic_panel(context, self.layout, action) diff --git a/source/blender/editors/screen/screen_context.c b/source/blender/editors/screen/screen_context.c index 239113c28a4..0d6b6ee1d78 100644 --- a/source/blender/editors/screen/screen_context.c +++ b/source/blender/editors/screen/screen_context.c @@ -100,6 +100,7 @@ const char *screen_context_dir[] = { "active_gpencil_frame", "active_annotation_layer", "active_operator", + "active_action", "selected_visible_actions", "selected_editable_actions", "visible_fcurves", @@ -969,6 +970,7 @@ static eContextResult screen_ctx_active_operator(const bContext *C, bContextData } static eContextResult screen_ctx_sel_actions_impl(const bContext *C, bContextDataResult *result, + bool active_only, bool editable) { bAnimContext ac; @@ -978,11 +980,17 @@ static eContextResult screen_ctx_sel_actions_impl(const bContext *C, SpaceAction *saction = (SpaceAction *)ac.sl; if (ELEM(saction->mode, SACTCONT_ACTION, SACTCONT_SHAPEKEY)) { - if (saction->action && !(editable && ID_IS_LINKED(saction->action))) { - CTX_data_id_list_add(result, &saction->action->id); + if (active_only) { + CTX_data_id_pointer_set(result, (ID *)saction->action); + } + else { + if (saction->action && !(editable && ID_IS_LINKED(saction->action))) { + CTX_data_id_list_add(result, &saction->action->id); + } + + CTX_data_type_set(result, CTX_DATA_TYPE_COLLECTION); } - CTX_data_type_set(result, CTX_DATA_TYPE_COLLECTION); return CTX_RESULT_OK; } } @@ -995,7 +1003,8 @@ static eContextResult screen_ctx_sel_actions_impl(const bContext *C, switch (ac.spacetype) { case SPACE_GRAPH: - filter |= ANIMFILTER_FCURVESONLY | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_SEL; + filter |= ANIMFILTER_FCURVESONLY | ANIMFILTER_CURVE_VISIBLE | + (active_only ? ANIMFILTER_ACTIVE : ANIMFILTER_SEL); break; case SPACE_ACTION: @@ -1006,7 +1015,7 @@ static eContextResult screen_ctx_sel_actions_impl(const bContext *C, ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); - GSet *seen_set = BLI_gset_ptr_new("seen actions"); + GSet *seen_set = active_only ? NULL : BLI_gset_ptr_new("seen actions"); LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) { /* In dopesheet check selection status of individual items, skipping @@ -1019,36 +1028,48 @@ static eContextResult screen_ctx_sel_actions_impl(const bContext *C, bAction *action = ANIM_channel_action_get(ale); if (action) { - if (editable && ID_IS_LINKED(action)) { - continue; + if (active_only) { + CTX_data_id_pointer_set(result, (ID *)action); + break; } + else { + if (editable && ID_IS_LINKED(action)) { + continue; + } - /* Add the action to the output list if not already added. */ - if (BLI_gset_add(seen_set, action)) { - CTX_data_id_list_add(result, &action->id); + /* Add the action to the output list if not already added. */ + if (BLI_gset_add(seen_set, action)) { + CTX_data_id_list_add(result, &action->id); + } } } } - BLI_gset_free(seen_set, NULL); - ANIM_animdata_freelist(&anim_data); - CTX_data_type_set(result, CTX_DATA_TYPE_COLLECTION); + if (!active_only) { + BLI_gset_free(seen_set, NULL); + + CTX_data_type_set(result, CTX_DATA_TYPE_COLLECTION); + } + return CTX_RESULT_OK; } return CTX_RESULT_NO_DATA; } - +static eContextResult screen_ctx_active_action(const bContext *C, bContextDataResult *result) +{ + return screen_ctx_sel_actions_impl(C, result, true, false); +} static eContextResult screen_ctx_selected_visible_actions(const bContext *C, bContextDataResult *result) { - return screen_ctx_sel_actions_impl(C, result, false); + return screen_ctx_sel_actions_impl(C, result, false, false); } static eContextResult screen_ctx_selected_editable_actions(const bContext *C, bContextDataResult *result) { - return screen_ctx_sel_actions_impl(C, result, true); + return screen_ctx_sel_actions_impl(C, result, false, true); } static eContextResult screen_ctx_sel_edit_fcurves_(const bContext *C, bContextDataResult *result, @@ -1262,6 +1283,7 @@ static void ensure_ed_screen_context_functions(void) register_context_function("editable_gpencil_layers", screen_ctx_editable_gpencil_layers); register_context_function("editable_gpencil_strokes", screen_ctx_editable_gpencil_strokes); register_context_function("active_operator", screen_ctx_active_operator); + register_context_function("active_action", screen_ctx_active_action); register_context_function("selected_visible_actions", screen_ctx_selected_visible_actions); register_context_function("selected_editable_actions", screen_ctx_selected_editable_actions); register_context_function("editable_fcurves", screen_ctx_editable_fcurves); -- cgit v1.2.3 From 8358cc79639c9ee797c5de9f5496032016e945d2 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Mon, 18 Jul 2022 17:31:19 +0200 Subject: Fix wrong alpha for scene linear byte images during texture painting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make the update logic consistent with the case where the initial texture is created. Also fixes a wrong assert. Thanks Clément for spotting this. --- source/blender/blenkernel/intern/image_gpu.cc | 6 +++--- source/blender/imbuf/intern/colormanagement.c | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/source/blender/blenkernel/intern/image_gpu.cc b/source/blender/blenkernel/intern/image_gpu.cc index 6edb9e1b24c..6506b40b603 100644 --- a/source/blender/blenkernel/intern/image_gpu.cc +++ b/source/blender/blenkernel/intern/image_gpu.cc @@ -737,11 +737,11 @@ static void gpu_texture_update_from_ibuf( } else { /* Byte image is in original colorspace from the file, and may need conversion. */ - if (IMB_colormanagement_space_is_data(ibuf->rect_colorspace) || - IMB_colormanagement_space_is_scene_linear(ibuf->rect_colorspace)) { + if (IMB_colormanagement_space_is_data(ibuf->rect_colorspace)) { /* Non-color data, just store buffer as is. */ } - else if (IMB_colormanagement_space_is_srgb(ibuf->rect_colorspace)) { + else if (IMB_colormanagement_space_is_srgb(ibuf->rect_colorspace) || + IMB_colormanagement_space_is_scene_linear(ibuf->rect_colorspace)) { /* sRGB or scene linear, store as byte texture that the GPU can decode directly. */ rect = (uchar *)MEM_mallocN(sizeof(uchar[4]) * w * h, __func__); if (rect == nullptr) { diff --git a/source/blender/imbuf/intern/colormanagement.c b/source/blender/imbuf/intern/colormanagement.c index b95ec1cd92b..b62bdd5521d 100644 --- a/source/blender/imbuf/intern/colormanagement.c +++ b/source/blender/imbuf/intern/colormanagement.c @@ -2213,10 +2213,11 @@ void IMB_colormanagement_imbuf_to_byte_texture(unsigned char *out_buffer, const struct ImBuf *ibuf, const bool store_premultiplied) { - /* Byte buffer storage, only for sRGB and data texture since other + /* Byte buffer storage, only for sRGB, scene linear and data texture since other * color space conversions can't be done on the GPU. */ BLI_assert(ibuf->rect && ibuf->rect_float == NULL); BLI_assert(IMB_colormanagement_space_is_srgb(ibuf->rect_colorspace) || + IMB_colormanagement_space_is_scene_linear(ibuf->rect_colorspace) || IMB_colormanagement_space_is_data(ibuf->rect_colorspace)); const unsigned char *in_buffer = (unsigned char *)ibuf->rect; -- cgit v1.2.3 From d175eb6c30b0eb9630669522c643debf6a183efd Mon Sep 17 00:00:00 2001 From: Henrik Dick Date: Mon, 18 Jul 2022 23:33:30 +0200 Subject: Fix Text Editor highlight of assert and async Due to the ordering of the checks, assert and async were not highlighted in the editor, even though they were in the list of keywords. Differential Revision: http://developer.blender.org/D15483 --- source/blender/editors/space_text/text_format_py.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/editors/space_text/text_format_py.c b/source/blender/editors/space_text/text_format_py.c index aa361f07932..28f536ffa98 100644 --- a/source/blender/editors/space_text/text_format_py.c +++ b/source/blender/editors/space_text/text_format_py.c @@ -59,9 +59,9 @@ static int txtfmt_py_find_builtinfunc(const char *string) /* clang-format off */ if (STR_LITERAL_STARTSWITH(string, "and", len)) { i = len; - } else if (STR_LITERAL_STARTSWITH(string, "as", len)) { i = len; } else if (STR_LITERAL_STARTSWITH(string, "assert", len)) { i = len; } else if (STR_LITERAL_STARTSWITH(string, "async", len)) { i = len; + } else if (STR_LITERAL_STARTSWITH(string, "as", len)) { i = len; } else if (STR_LITERAL_STARTSWITH(string, "await", len)) { i = len; } else if (STR_LITERAL_STARTSWITH(string, "break", len)) { i = len; } else if (STR_LITERAL_STARTSWITH(string, "case", len)) { i = len; -- cgit v1.2.3 From 135e530356d09378f153c4cb483a77b3375cc4f2 Mon Sep 17 00:00:00 2001 From: Chris Blackbourn Date: Tue, 19 Jul 2022 10:24:29 +1200 Subject: Fix T99781: uv minimize stretch now unflips flipped faces Add a small gradient to flipped faces proportional to length of edges. Differential Revision: https://developer.blender.org/D15475 --- source/blender/geometry/intern/uv_parametrizer.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/blender/geometry/intern/uv_parametrizer.c b/source/blender/geometry/intern/uv_parametrizer.c index 7ef17d4e9d0..38924c718c3 100644 --- a/source/blender/geometry/intern/uv_parametrizer.c +++ b/source/blender/geometry/intern/uv_parametrizer.c @@ -3307,8 +3307,10 @@ static float p_face_stretch(PFace *f) area = p_face_uv_area_signed(f); - if (area <= 0.0f) { /* flipped face -> infinite stretch */ - return 1e10f; + if (area <= 0.0f) { + /* When a face is flipped, provide a large penalty. + * Add on a slight gradient to unflip the face, see also: T99781. */ + return 1e8f * (1.0f + p_edge_uv_length(e1) + p_edge_uv_length(e2) + p_edge_uv_length(e3)); } w = 1.0f / (2.0f * area); -- cgit v1.2.3 From 37922eab90a9147cb0282cf648e4b6e1e5f05d66 Mon Sep 17 00:00:00 2001 From: Chris Blackbourn Date: Tue, 19 Jul 2022 13:25:46 +1200 Subject: Fix T99794: regression from uv unwrap selected Restore only_selected_faces flag inadvertently changed by c0e453233132 Differential Revision: https://developer.blender.org/D15480 --- source/blender/editors/uvedit/uvedit_unwrap_ops.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/blender/editors/uvedit/uvedit_unwrap_ops.c b/source/blender/editors/uvedit/uvedit_unwrap_ops.c index 7ec80a05f6d..2c7ad012dd2 100644 --- a/source/blender/editors/uvedit/uvedit_unwrap_ops.c +++ b/source/blender/editors/uvedit/uvedit_unwrap_ops.c @@ -1915,7 +1915,7 @@ static int unwrap_exec(bContext *C, wmOperator *op) UnwrapOptions options = { .topology_from_uvs = false, - .only_selected_faces = false, + .only_selected_faces = true, .only_selected_uvs = false, .fill_holes = RNA_boolean_get(op->ptr, "fill_holes"), .correct_aspect = RNA_boolean_get(op->ptr, "correct_aspect"), @@ -1926,7 +1926,6 @@ static int unwrap_exec(bContext *C, wmOperator *op) if (CTX_wm_space_image(C)) { /* Inside the UV Editor, only unwrap selected UVs. */ options.only_selected_uvs = true; - options.only_selected_faces = true; options.pin_unselected = true; } -- cgit v1.2.3 From 7ebd1f4b7960100ff9f4a1bfcf3e798fbec9dfbd Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 19 Jul 2022 13:32:23 +1000 Subject: Cleanup: quiet compiler warnings --- intern/opensubdiv/stub/opensubdiv_stub.cc | 5 ----- source/blender/editors/sculpt_paint/paint_stroke.c | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/intern/opensubdiv/stub/opensubdiv_stub.cc b/intern/opensubdiv/stub/opensubdiv_stub.cc index 24bdcbc79ff..5eaa2df9a27 100644 --- a/intern/opensubdiv/stub/opensubdiv_stub.cc +++ b/intern/opensubdiv/stub/opensubdiv_stub.cc @@ -28,11 +28,6 @@ void openSubdiv_cleanup() { } -int openSubdiv_getAvailableEvaluators() -{ - return 0; -} - int openSubdiv_getVersionHex() { return 0; diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c index 33a307376bb..88e7a786a47 100644 --- a/source/blender/editors/sculpt_paint/paint_stroke.c +++ b/source/blender/editors/sculpt_paint/paint_stroke.c @@ -247,7 +247,7 @@ static bool paint_stroke_use_scene_spacing(Brush *brush, ePaintMode mode) return false; } -static bool paint_tool_raycast_original(Brush *brush, ePaintMode mode) +static bool paint_tool_raycast_original(Brush *brush, ePaintMode UNUSED(mode)) { return brush->flag & BRUSH_ANCHORED; } -- cgit v1.2.3 From bbf87c4f750992f47f514733e3a7d33573f5c860 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 19 Jul 2022 13:33:02 +1000 Subject: Fix T99737: Dropping files fails with Wayland Drop events ignored the cursor coordinates, under the assumption that cursor motion events would also be sent to update the cursor location. This depended on the behavior of the compositor, it failed for Sway but worked for Gnome-shell & River. Resolve by making use of the drop events cursor coordinates. --- source/blender/windowmanager/intern/wm_window.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c index 70640eda605..d2182f759e5 100644 --- a/source/blender/windowmanager/intern/wm_window.c +++ b/source/blender/windowmanager/intern/wm_window.c @@ -1362,6 +1362,11 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_pt event.type = MOUSEMOVE; event.val = KM_NOTHING; copy_v2_v2_int(event.prev_xy, event.xy); + + wm_cursor_position_from_ghost_screen_coords(win, &ddd->x, &ddd->y); + event.xy[0] = ddd->x; + event.xy[1] = ddd->y; + event.flag = 0; /* No context change! C->wm->windrawable is drawable, or for area queues. */ -- cgit v1.2.3 From 533a5a6a8c60686dd7d627cf7815915d7a6b467b Mon Sep 17 00:00:00 2001 From: Jesse Yurkovich Date: Mon, 18 Jul 2022 20:37:11 -0700 Subject: Fix T99785: Make Principled Hair IOR input behave like other IOR sliders Was accidental regression in rBed9b21098dd27bf9364397357f89b4c2648f40c2 Remove the input slider's PROP_FACTOR subtype in favor of the default to align with other IOR sliders. This provides much better control when dragging the value with the mouse. Differential Revision: https://developer.blender.org/D15477 --- source/blender/nodes/shader/nodes/node_shader_bsdf_hair_principled.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/blender/nodes/shader/nodes/node_shader_bsdf_hair_principled.cc b/source/blender/nodes/shader/nodes/node_shader_bsdf_hair_principled.cc index 6495dcfffba..a0579372a15 100644 --- a/source/blender/nodes/shader/nodes/node_shader_bsdf_hair_principled.cc +++ b/source/blender/nodes/shader/nodes/node_shader_bsdf_hair_principled.cc @@ -42,8 +42,7 @@ static void node_declare(NodeDeclarationBuilder &b) .min(0.0f) .max(1.0f) .subtype(PROP_FACTOR); - b.add_input(N_("IOR")).default_value(1.55f).min(0.0f).max(1000.0f).subtype( - PROP_FACTOR); + b.add_input(N_("IOR")).default_value(1.55f).min(0.0f).max(1000.0f); b.add_input(N_("Offset")) .default_value(2.0f * ((float)M_PI) / 180.0f) .min(-M_PI_2) -- cgit v1.2.3 From e00a027c1e01396de9fa141965e57a0e6c1dc1cd Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Tue, 19 Jul 2022 11:18:30 +0200 Subject: Depsgraph: Use single task pool during evaluation Not sure why multiple pools were created: the pool should be able to handle two sets of tasks. Perhaps non-measurable improvement in terms of performance but this change simplifies code a bit. --- source/blender/depsgraph/intern/eval/deg_eval.cc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/source/blender/depsgraph/intern/eval/deg_eval.cc b/source/blender/depsgraph/intern/eval/deg_eval.cc index 2d9d40aede6..64334e95c4c 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval.cc +++ b/source/blender/depsgraph/intern/eval/deg_eval.cc @@ -361,27 +361,29 @@ void deg_evaluate_on_refresh(Depsgraph *graph) graph->is_evaluating = true; depsgraph_ensure_view_layer(graph); + /* Set up evaluation state. */ DepsgraphEvalState state; state.graph = graph; state.do_stats = graph->debug.do_time_debug(); state.need_single_thread_pass = false; + /* Prepare all nodes for evaluation. */ initialize_execution(&state, graph); + TaskPool *task_pool = deg_evaluate_task_pool_create(&state); + /* Do actual evaluation now. */ /* First, process all Copy-On-Write nodes. */ state.stage = EvaluationStage::COPY_ON_WRITE; - TaskPool *task_pool = deg_evaluate_task_pool_create(&state); schedule_graph(&state, schedule_node_to_pool, task_pool); BLI_task_pool_work_and_wait(task_pool); - BLI_task_pool_free(task_pool); /* After that, process all other nodes. */ state.stage = EvaluationStage::THREADED_EVALUATION; - task_pool = deg_evaluate_task_pool_create(&state); schedule_graph(&state, schedule_node_to_pool, task_pool); BLI_task_pool_work_and_wait(task_pool); + BLI_task_pool_free(task_pool); if (state.need_single_thread_pass) { @@ -395,6 +397,7 @@ void deg_evaluate_on_refresh(Depsgraph *graph) if (state.do_stats) { deg_eval_stats_aggregate(graph); } + /* Clear any uncleared tags - just in case. */ deg_graph_clear_tags(graph); graph->is_evaluating = false; -- cgit v1.2.3 From ff98b5eaa805d411f967921eca7bea24f24ea4dc Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Tue, 19 Jul 2022 11:27:50 +0200 Subject: Depsgraph: Clarify comment in the component node --- .../blender/depsgraph/intern/node/deg_node_component.h | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/source/blender/depsgraph/intern/node/deg_node_component.h b/source/blender/depsgraph/intern/node/deg_node_component.h index 6958866af3b..b31881a96c0 100644 --- a/source/blender/depsgraph/intern/node/deg_node_component.h +++ b/source/blender/depsgraph/intern/node/deg_node_component.h @@ -56,12 +56,14 @@ struct ComponentNode : public Node { virtual string identifier() const override; - /* Find an existing operation, if requested operation does not exist - * nullptr will be returned. */ + /* Find an existing operation, if requested operation does not exist nullptr will be returned. + * See #add_operation for the meaning and examples of #name and #name_tag. + */ OperationNode *find_operation(OperationIDKey key) const; OperationNode *find_operation(OperationCode opcode, const char *name, int name_tag) const; - /* Find an existing operation, will throw an assert() if it does not exist. */ + /* Find an existing operation, will throw an assert() if it does not exist. + * See #add_operation for the meaning and examples of #name and #name_tag. */ OperationNode *get_operation(OperationIDKey key) const; OperationNode *get_operation(OperationCode opcode, const char *name, int name_tag) const; @@ -71,12 +73,19 @@ struct ComponentNode : public Node { /** * Create a new node for representing an operation and add this to graph + * * \warning If an existing node is found, it will be modified. This helps * when node may have been partially created earlier (e.g. parent ref before * parent item is added) * * \param opcode: The operation to perform. - * \param name: Identifier for operation - used to find/locate it again. + * \param name: An optional identifier for operation. It will be used to tell operation nodes + * with the same code apart. For example, parameter operation code will have name + * set to the corresponding custom property name + * \param name_tag: An optional integer tag for the name. Is an additional way to tell operations + * apart. For example, RNA path to an array property will have the same opcode + * of PARAMETERS, name corresponding to the property name, and name tag + * corresponding to the array index within the property. */ OperationNode *add_operation(const DepsEvalOperationCb &op, OperationCode opcode, -- cgit v1.2.3 From d3c063188e00624040bc6a25441da456e3f4cbf6 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Tue, 19 Jul 2022 11:31:44 +0200 Subject: Depsgraph: Make name and name tag optional in component node Matches the builder API, making some code less verbose. --- .../blender/depsgraph/intern/builder/deg_builder_nodes.cc | 6 ++---- source/blender/depsgraph/intern/node/deg_node_component.h | 14 +++++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc index 657bc3eb25c..ead0bdab3b2 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc @@ -174,15 +174,13 @@ IDNode *DepsgraphNodeBuilder::add_id_node(ID *id) ComponentNode *comp_cow = id_node->add_component(NodeType::COPY_ON_WRITE); OperationNode *op_cow = comp_cow->add_operation( [id_node](::Depsgraph *depsgraph) { deg_evaluate_copy_on_write(depsgraph, id_node); }, - OperationCode::COPY_ON_WRITE, - "", - -1); + OperationCode::COPY_ON_WRITE); graph_->operations.append(op_cow); } ComponentNode *visibility_component = id_node->add_component(NodeType::VISIBILITY); OperationNode *visibility_operation = visibility_component->add_operation( - nullptr, OperationCode::OPERATION, "", -1); + nullptr, OperationCode::OPERATION); /* Pin the node so that it and its relations are preserved by the unused nodes/relations * deletion. This is mainly to make it easier to debug visibility. */ visibility_operation->flag |= OperationFlag::DEPSOP_FLAG_PINNED; diff --git a/source/blender/depsgraph/intern/node/deg_node_component.h b/source/blender/depsgraph/intern/node/deg_node_component.h index b31881a96c0..ee6c56b1171 100644 --- a/source/blender/depsgraph/intern/node/deg_node_component.h +++ b/source/blender/depsgraph/intern/node/deg_node_component.h @@ -60,16 +60,20 @@ struct ComponentNode : public Node { * See #add_operation for the meaning and examples of #name and #name_tag. */ OperationNode *find_operation(OperationIDKey key) const; - OperationNode *find_operation(OperationCode opcode, const char *name, int name_tag) const; + OperationNode *find_operation(OperationCode opcode, + const char *name = "", + int name_tag = -1) const; /* Find an existing operation, will throw an assert() if it does not exist. * See #add_operation for the meaning and examples of #name and #name_tag. */ OperationNode *get_operation(OperationIDKey key) const; - OperationNode *get_operation(OperationCode opcode, const char *name, int name_tag) const; + OperationNode *get_operation(OperationCode opcode, + const char *name = "", + int name_tag = -1) const; /* Check operation exists and return it. */ bool has_operation(OperationIDKey key) const; - bool has_operation(OperationCode opcode, const char *name, int name_tag) const; + bool has_operation(OperationCode opcode, const char *name = "", int name_tag = -1) const; /** * Create a new node for representing an operation and add this to graph @@ -89,8 +93,8 @@ struct ComponentNode : public Node { */ OperationNode *add_operation(const DepsEvalOperationCb &op, OperationCode opcode, - const char *name, - int name_tag); + const char *name = "", + int name_tag = -1); /* Entry/exit operations management. * -- cgit v1.2.3 From 9b2b61a07b3fdd8eb0fecf10ec56f1b027fc255f Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Tue, 19 Jul 2022 11:40:18 +0200 Subject: Depsgraph: Cleanup, use nested namespace definition --- source/blender/depsgraph/intern/builder/deg_builder.h | 6 ++---- source/blender/depsgraph/intern/builder/deg_builder_cache.h | 6 ++---- source/blender/depsgraph/intern/builder/deg_builder_cycle.h | 6 ++---- source/blender/depsgraph/intern/builder/deg_builder_map.h | 6 ++---- source/blender/depsgraph/intern/builder/deg_builder_nodes.h | 6 ++---- source/blender/depsgraph/intern/builder/deg_builder_pchanmap.h | 6 ++---- source/blender/depsgraph/intern/builder/deg_builder_relations.h | 6 ++---- .../depsgraph/intern/builder/deg_builder_relations_drivers.h | 6 ++---- .../blender/depsgraph/intern/builder/deg_builder_relations_impl.h | 6 ++---- source/blender/depsgraph/intern/builder/deg_builder_remove_noop.h | 6 ++---- source/blender/depsgraph/intern/builder/deg_builder_rna.h | 6 ++---- source/blender/depsgraph/intern/builder/deg_builder_transitive.h | 6 ++---- source/blender/depsgraph/intern/builder/pipeline.h | 6 ++---- source/blender/depsgraph/intern/builder/pipeline_all_objects.h | 6 ++---- source/blender/depsgraph/intern/builder/pipeline_compositor.h | 6 ++---- source/blender/depsgraph/intern/builder/pipeline_from_ids.h | 6 ++---- source/blender/depsgraph/intern/builder/pipeline_render.h | 6 ++---- source/blender/depsgraph/intern/builder/pipeline_view_layer.h | 6 ++---- 18 files changed, 36 insertions(+), 72 deletions(-) diff --git a/source/blender/depsgraph/intern/builder/deg_builder.h b/source/blender/depsgraph/intern/builder/deg_builder.h index 8fbe255ed9d..6f2bde3a2ff 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder.h +++ b/source/blender/depsgraph/intern/builder/deg_builder.h @@ -13,8 +13,7 @@ struct Main; struct Object; struct bPoseChannel; -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; class DepsgraphBuilderCache; @@ -43,5 +42,4 @@ bool deg_check_id_in_depsgraph(const Depsgraph *graph, ID *id_orig); bool deg_check_base_in_depsgraph(const Depsgraph *graph, Base *base); void deg_graph_build_finalize(Main *bmain, Depsgraph *graph); -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/builder/deg_builder_cache.h b/source/blender/depsgraph/intern/builder/deg_builder_cache.h index 2d2bdeaf825..5568967f163 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_cache.h +++ b/source/blender/depsgraph/intern/builder/deg_builder_cache.h @@ -17,8 +17,7 @@ struct ID; struct PointerRNA; struct PropertyRNA; -namespace blender { -namespace deg { +namespace blender::deg { class DepsgraphBuilderCache; @@ -101,5 +100,4 @@ class DepsgraphBuilderCache { MEM_CXX_CLASS_ALLOC_FUNCS("DepsgraphBuilderCache"); }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/builder/deg_builder_cycle.h b/source/blender/depsgraph/intern/builder/deg_builder_cycle.h index 8e94e0ae21f..83d25f8c23f 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_cycle.h +++ b/source/blender/depsgraph/intern/builder/deg_builder_cycle.h @@ -7,13 +7,11 @@ #pragma once -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; /* Detect and solve dependency cycles. */ void deg_graph_detect_cycles(Depsgraph *graph); -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/builder/deg_builder_map.h b/source/blender/depsgraph/intern/builder/deg_builder_map.h index 50ebadeb382..2b2b4d089e9 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_map.h +++ b/source/blender/depsgraph/intern/builder/deg_builder_map.h @@ -11,8 +11,7 @@ struct ID; -namespace blender { -namespace deg { +namespace blender::deg { class BuilderMap { public: @@ -60,5 +59,4 @@ class BuilderMap { Map id_tags_; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.h b/source/blender/depsgraph/intern/builder/deg_builder_nodes.h index be9983edf85..18e28311132 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.h +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.h @@ -50,8 +50,7 @@ struct bNodeTree; struct bPoseChannel; struct bSound; -namespace blender { -namespace deg { +namespace blender::deg { struct ComponentNode; struct Depsgraph; @@ -306,5 +305,4 @@ class DepsgraphNodeBuilder : public DepsgraphBuilder { BuilderMap built_map_; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/builder/deg_builder_pchanmap.h b/source/blender/depsgraph/intern/builder/deg_builder_pchanmap.h index 2f98c8b419c..afb3a3c22bd 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_pchanmap.h +++ b/source/blender/depsgraph/intern/builder/deg_builder_pchanmap.h @@ -9,8 +9,7 @@ #include "intern/depsgraph_type.h" -namespace blender { -namespace deg { +namespace blender::deg { struct RootPChanMap { /** Debug contents of map. */ @@ -30,5 +29,4 @@ struct RootPChanMap { Map> map_; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.h b/source/blender/depsgraph/intern/builder/deg_builder_relations.h index 64bdd2334d8..0cb0b60dfb0 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.h +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.h @@ -70,8 +70,7 @@ struct bSound; struct PropertyRNA; -namespace blender { -namespace deg { +namespace blender::deg { struct ComponentNode; struct DepsNodeHandle; @@ -381,7 +380,6 @@ struct DepsNodeHandle { const char *default_name; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg #include "intern/builder/deg_builder_relations_impl.h" diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations_drivers.h b/source/blender/depsgraph/intern/builder/deg_builder_relations_drivers.h index e517dd6a927..9860b17fd56 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations_drivers.h +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations_drivers.h @@ -15,8 +15,7 @@ struct FCurve; -namespace blender { -namespace deg { +namespace blender::deg { /* Helper class for determining which relations are needed between driver evaluation nodes. */ class DriverDescriptor { @@ -59,5 +58,4 @@ class DriverDescriptor { bool resolve_rna(); }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations_impl.h b/source/blender/depsgraph/intern/builder/deg_builder_relations_impl.h index aba4a011e72..76066ce97a7 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations_impl.h +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations_impl.h @@ -15,8 +15,7 @@ #include "DNA_object_types.h" #include "DNA_rigidbody_types.h" -namespace blender { -namespace deg { +namespace blender::deg { template OperationNode *DepsgraphRelationBuilder::find_operation_node(const KeyType &key) @@ -209,5 +208,4 @@ bool DepsgraphRelationBuilder::is_same_nodetree_node_dependency(const KeyFrom &k return true; } -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/builder/deg_builder_remove_noop.h b/source/blender/depsgraph/intern/builder/deg_builder_remove_noop.h index 922d2d7dc05..ad10eb7cd10 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_remove_noop.h +++ b/source/blender/depsgraph/intern/builder/deg_builder_remove_noop.h @@ -7,13 +7,11 @@ #pragma once -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; /* Remove all no-op nodes that have zero outgoing relations. */ void deg_graph_remove_unused_noops(Depsgraph *graph); -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/builder/deg_builder_rna.h b/source/blender/depsgraph/intern/builder/deg_builder_rna.h index 4f482d4352d..9baa956bd80 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_rna.h +++ b/source/blender/depsgraph/intern/builder/deg_builder_rna.h @@ -14,8 +14,7 @@ struct ID; struct PointerRNA; struct PropertyRNA; -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; struct Node; @@ -94,5 +93,4 @@ class RNANodeQuery { bool rna_prop_affects_parameters_node(const PointerRNA *ptr, const PropertyRNA *prop); -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/builder/deg_builder_transitive.h b/source/blender/depsgraph/intern/builder/deg_builder_transitive.h index 8b208610203..63016431eec 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_transitive.h +++ b/source/blender/depsgraph/intern/builder/deg_builder_transitive.h @@ -7,13 +7,11 @@ #pragma once -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; /* Performs a transitive reduction to remove redundant relations. */ void deg_graph_transitive_reduction(Depsgraph *graph); -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/builder/pipeline.h b/source/blender/depsgraph/intern/builder/pipeline.h index b106e73b97c..7568aa78106 100644 --- a/source/blender/depsgraph/intern/builder/pipeline.h +++ b/source/blender/depsgraph/intern/builder/pipeline.h @@ -16,8 +16,7 @@ struct Main; struct Scene; struct ViewLayer; -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; class DepsgraphNodeBuilder; @@ -57,5 +56,4 @@ class AbstractBuilderPipeline { virtual void build_relations(DepsgraphRelationBuilder &relation_builder) = 0; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/builder/pipeline_all_objects.h b/source/blender/depsgraph/intern/builder/pipeline_all_objects.h index 75d9605dec7..fde5e7e2163 100644 --- a/source/blender/depsgraph/intern/builder/pipeline_all_objects.h +++ b/source/blender/depsgraph/intern/builder/pipeline_all_objects.h @@ -9,8 +9,7 @@ #include "pipeline_view_layer.h" -namespace blender { -namespace deg { +namespace blender::deg { /* Builds a dependency graph that contains all objects in the view layer. * This is contrary to the regular ViewLayerBuilderPipeline, which is limited to visible objects @@ -24,5 +23,4 @@ class AllObjectsBuilderPipeline : public ViewLayerBuilderPipeline { virtual unique_ptr construct_relation_builder() override; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/builder/pipeline_compositor.h b/source/blender/depsgraph/intern/builder/pipeline_compositor.h index 3325741c94a..304f2d4ec9a 100644 --- a/source/blender/depsgraph/intern/builder/pipeline_compositor.h +++ b/source/blender/depsgraph/intern/builder/pipeline_compositor.h @@ -11,8 +11,7 @@ struct bNodeTree; -namespace blender { -namespace deg { +namespace blender::deg { class CompositorBuilderPipeline : public AbstractBuilderPipeline { public: @@ -26,5 +25,4 @@ class CompositorBuilderPipeline : public AbstractBuilderPipeline { bNodeTree *nodetree_; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/builder/pipeline_from_ids.h b/source/blender/depsgraph/intern/builder/pipeline_from_ids.h index a2c75c048cb..c277d44aaad 100644 --- a/source/blender/depsgraph/intern/builder/pipeline_from_ids.h +++ b/source/blender/depsgraph/intern/builder/pipeline_from_ids.h @@ -9,8 +9,7 @@ #include "pipeline.h" -namespace blender { -namespace deg { +namespace blender::deg { /* Optimized builders for dependency graph built from a given set of IDs. * @@ -37,5 +36,4 @@ class FromIDsBuilderPipeline : public AbstractBuilderPipeline { Span ids_; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/builder/pipeline_render.h b/source/blender/depsgraph/intern/builder/pipeline_render.h index cb704f84c59..7eb65168ea6 100644 --- a/source/blender/depsgraph/intern/builder/pipeline_render.h +++ b/source/blender/depsgraph/intern/builder/pipeline_render.h @@ -9,8 +9,7 @@ #include "pipeline.h" -namespace blender { -namespace deg { +namespace blender::deg { class RenderBuilderPipeline : public AbstractBuilderPipeline { public: @@ -21,5 +20,4 @@ class RenderBuilderPipeline : public AbstractBuilderPipeline { virtual void build_relations(DepsgraphRelationBuilder &relation_builder) override; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/builder/pipeline_view_layer.h b/source/blender/depsgraph/intern/builder/pipeline_view_layer.h index 18b9ce5d5ff..ffd6fa07776 100644 --- a/source/blender/depsgraph/intern/builder/pipeline_view_layer.h +++ b/source/blender/depsgraph/intern/builder/pipeline_view_layer.h @@ -9,8 +9,7 @@ #include "pipeline.h" -namespace blender { -namespace deg { +namespace blender::deg { class ViewLayerBuilderPipeline : public AbstractBuilderPipeline { public: @@ -21,5 +20,4 @@ class ViewLayerBuilderPipeline : public AbstractBuilderPipeline { virtual void build_relations(DepsgraphRelationBuilder &relation_builder) override; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg -- cgit v1.2.3 From 2280a71f900eb7907c243f76930a7e9779a90d7d Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Tue, 19 Jul 2022 12:41:47 +0200 Subject: Depsgraph: Refactor evaluation into smaller reusable functions Should be no functional changes. --- source/blender/depsgraph/intern/eval/deg_eval.cc | 52 +++++++++++++++++------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/source/blender/depsgraph/intern/eval/deg_eval.cc b/source/blender/depsgraph/intern/eval/deg_eval.cc index 64334e95c4c..9da9586c1cf 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval.cc +++ b/source/blender/depsgraph/intern/eval/deg_eval.cc @@ -302,8 +302,28 @@ void schedule_node_to_queue(OperationNode *node, BLI_gsqueue_push(evaluation_queue, &node); } -void evaluate_graph_single_threaded(DepsgraphEvalState *state) +/* Evaluate given stage of the dependency graph evaluation using multiple threads. + * + * NOTE: Will assign the `state->stage` to the given stage. */ +void evaluate_graph_threaded_stage(DepsgraphEvalState *state, + TaskPool *task_pool, + const EvaluationStage stage) { + state->stage = stage; + + schedule_graph(state, schedule_node_to_pool, task_pool); + BLI_task_pool_work_and_wait(task_pool); +} + +/* Evaluate remaining operations of the dependency graph in a single threaded manner. */ +void evaluate_graph_single_threaded_if_needed(DepsgraphEvalState *state) +{ + if (!state->need_single_thread_pass) { + return; + } + + state->stage = EvaluationStage::SINGLE_THREADED_WORKAROUND; + GSQueue *evaluation_queue = BLI_gsqueue_new(sizeof(OperationNode *)); schedule_graph(state, schedule_node_to_queue, evaluation_queue); @@ -371,25 +391,27 @@ void deg_evaluate_on_refresh(Depsgraph *graph) /* Prepare all nodes for evaluation. */ initialize_execution(&state, graph); - TaskPool *task_pool = deg_evaluate_task_pool_create(&state); + /* Evaluation happens in several incremental steps: + * + * - Start with the copy-on-write operations which never form dependency cycles. This will ensure + * that if a dependency graph has a cycle evaluation functions will always "see" valid expanded + * datablock. It might not be evaluated yet, but at least the datablock will be valid. + * + * - Multi-threaded evaluation of all possible nodes. + * Certain operations (and their subtrees) could be ignored. For example, meta-balls are not + * safe from threading point of view, so the threaded evaluation will stop at the metaball + * operation node. + * + * - Single-threaded pass of all remaining operations. */ - /* Do actual evaluation now. */ - /* First, process all Copy-On-Write nodes. */ - state.stage = EvaluationStage::COPY_ON_WRITE; - schedule_graph(&state, schedule_node_to_pool, task_pool); - BLI_task_pool_work_and_wait(task_pool); + TaskPool *task_pool = deg_evaluate_task_pool_create(&state); - /* After that, process all other nodes. */ - state.stage = EvaluationStage::THREADED_EVALUATION; - schedule_graph(&state, schedule_node_to_pool, task_pool); - BLI_task_pool_work_and_wait(task_pool); + evaluate_graph_threaded_stage(&state, task_pool, EvaluationStage::COPY_ON_WRITE); + evaluate_graph_threaded_stage(&state, task_pool, EvaluationStage::THREADED_EVALUATION); BLI_task_pool_free(task_pool); - if (state.need_single_thread_pass) { - state.stage = EvaluationStage::SINGLE_THREADED_WORKAROUND; - evaluate_graph_single_threaded(&state); - } + evaluate_graph_single_threaded_if_needed(&state); /* Finalize statistics gathering. This is because we only gather single * operation timing here, without aggregating anything to avoid any extra -- cgit v1.2.3 From 99faebfca6d4f7953d970dcdbdf901f78a8cf4c3 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Tue, 19 Jul 2022 12:51:24 +0200 Subject: Depsgraph: Cleanup, don't mic static function and anonymous namespace --- source/blender/depsgraph/intern/eval/deg_eval.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/blender/depsgraph/intern/eval/deg_eval.cc b/source/blender/depsgraph/intern/eval/deg_eval.cc index 9da9586c1cf..9ac1f5275ac 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval.cc +++ b/source/blender/depsgraph/intern/eval/deg_eval.cc @@ -354,9 +354,7 @@ void depsgraph_ensure_view_layer(Depsgraph *graph) deg_update_copy_on_write_datablock(graph, scene_id_node); } -} // namespace - -static TaskPool *deg_evaluate_task_pool_create(DepsgraphEvalState *state) +TaskPool *deg_evaluate_task_pool_create(DepsgraphEvalState *state) { if (G.debug & G_DEBUG_DEPSGRAPH_NO_THREADS) { return BLI_task_pool_create_no_threads(state); @@ -365,6 +363,8 @@ static TaskPool *deg_evaluate_task_pool_create(DepsgraphEvalState *state) return BLI_task_pool_create_suspended(state, TASK_PRIORITY_HIGH); } +} // namespace + void deg_evaluate_on_refresh(Depsgraph *graph) { /* Nothing to update, early out. */ -- cgit v1.2.3 From 6d2100f7de1002623172ede914fa754d714f71d8 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Tue, 19 Jul 2022 13:00:19 +0200 Subject: Depsgraph: Introduce operation code for visibility operation No functional changes, just makes code more semantically clear. --- source/blender/depsgraph/intern/builder/deg_builder_nodes.cc | 2 +- source/blender/depsgraph/intern/node/deg_node_operation.cc | 2 ++ source/blender/depsgraph/intern/node/deg_node_operation.h | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc index ead0bdab3b2..473dda2290c 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc @@ -180,7 +180,7 @@ IDNode *DepsgraphNodeBuilder::add_id_node(ID *id) ComponentNode *visibility_component = id_node->add_component(NodeType::VISIBILITY); OperationNode *visibility_operation = visibility_component->add_operation( - nullptr, OperationCode::OPERATION); + nullptr, OperationCode::VISIBILITY); /* Pin the node so that it and its relations are preserved by the unused nodes/relations * deletion. This is mainly to make it easier to debug visibility. */ visibility_operation->flag |= OperationFlag::DEPSOP_FLAG_PINNED; diff --git a/source/blender/depsgraph/intern/node/deg_node_operation.cc b/source/blender/depsgraph/intern/node/deg_node_operation.cc index c29aeefd9b2..3029379d141 100644 --- a/source/blender/depsgraph/intern/node/deg_node_operation.cc +++ b/source/blender/depsgraph/intern/node/deg_node_operation.cc @@ -32,6 +32,8 @@ const char *operationCodeAsString(OperationCode opcode) return "PARAMETERS_EVAL"; case OperationCode::PARAMETERS_EXIT: return "PARAMETERS_EXIT"; + case OperationCode::VISIBILITY: + return "VISIBILITY"; /* Animation, Drivers, etc. */ case OperationCode::ANIMATION_ENTRY: return "ANIMATION_ENTRY"; diff --git a/source/blender/depsgraph/intern/node/deg_node_operation.h b/source/blender/depsgraph/intern/node/deg_node_operation.h index d4916be1113..aaa508ab3e8 100644 --- a/source/blender/depsgraph/intern/node/deg_node_operation.h +++ b/source/blender/depsgraph/intern/node/deg_node_operation.h @@ -34,6 +34,7 @@ enum class OperationCode { PARAMETERS_ENTRY, PARAMETERS_EVAL, PARAMETERS_EXIT, + VISIBILITY, /* Animation, Drivers, etc. --------------------------------------------- */ /* NLA + Action */ -- cgit v1.2.3 From 73f8a7ca0a30de398b0309d15cc459cfeff495ce Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Tue, 19 Jul 2022 14:25:02 +0200 Subject: Depsgraph: Cleanup, comments wrapping and spacing between lines Should make it easier to read. No functional changes expected. --- .../intern/builder/deg_builder_relations.cc | 24 ++++++++++++++++++++-- .../depsgraph/intern/node/deg_node_operation.h | 9 +++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc index 92396a89cea..4fe8a626af5 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc @@ -692,7 +692,7 @@ void DepsgraphRelationBuilder::build_object(Object *object) const BuilderStack::ScopedEntry stack_entry = stack_.trace(object->id); - /* Object Transforms */ + /* Object Transforms. */ OperationCode base_op = (object->parent) ? OperationCode::TRANSFORM_PARENT : OperationCode::TRANSFORM_LOCAL; OperationKey base_op_key(&object->id, NodeType::TRANSFORM, base_op); @@ -705,9 +705,12 @@ void DepsgraphRelationBuilder::build_object(Object *object) OperationKey final_transform_key( &object->id, NodeType::TRANSFORM, OperationCode::TRANSFORM_FINAL); OperationKey ob_eval_key(&object->id, NodeType::TRANSFORM, OperationCode::TRANSFORM_EVAL); + add_relation(init_transform_key, local_transform_key, "Transform Init"); + /* Various flags, flushing from bases/collections. */ build_object_layer_component_relations(object); + /* Parenting. */ if (object->parent != nullptr) { /* Make sure parent object's relations are built. */ @@ -717,30 +720,35 @@ void DepsgraphRelationBuilder::build_object(Object *object) /* Local -> parent. */ add_relation(local_transform_key, parent_transform_key, "ObLocal -> ObParent"); } + /* Modifiers. */ if (object->modifiers.first != nullptr) { BuilderWalkUserData data; data.builder = this; BKE_modifiers_foreach_ID_link(object, modifier_walk, &data); } + /* Grease Pencil Modifiers. */ if (object->greasepencil_modifiers.first != nullptr) { BuilderWalkUserData data; data.builder = this; BKE_gpencil_modifiers_foreach_ID_link(object, modifier_walk, &data); } + /* Shader FX. */ if (object->shader_fx.first != nullptr) { BuilderWalkUserData data; data.builder = this; BKE_shaderfx_foreach_ID_link(object, modifier_walk, &data); } + /* Constraints. */ if (object->constraints.first != nullptr) { BuilderWalkUserData data; data.builder = this; BKE_constraints_id_loop(&object->constraints, constraint_walk, &data); } + /* Object constraints. */ OperationKey object_transform_simulation_init_key( &object->id, NodeType::TRANSFORM, OperationCode::TRANSFORM_SIMULATION_INIT); @@ -767,30 +775,39 @@ void DepsgraphRelationBuilder::build_object(Object *object) final_transform_key, "Simulation -> Final Transform"); } + build_idproperties(object->id.properties); + /* Animation data */ build_animdata(&object->id); + /* Object data. */ build_object_data(object); + /* Particle systems. */ if (object->particlesystem.first != nullptr) { build_particle_systems(object); } + /* Force field Texture. */ if ((object->pd != nullptr) && (object->pd->forcefield == PFIELD_TEXTURE) && (object->pd->tex != nullptr)) { build_texture(object->pd->tex); } + /* Object dupligroup. */ if (object->instance_collection != nullptr) { build_collection(nullptr, object, object->instance_collection); } + /* Point caches. */ build_object_pointcache(object); + /* Synchronization back to original object. */ OperationKey synchronize_key( &object->id, NodeType::SYNCHRONIZATION, OperationCode::SYNCHRONIZE_TO_ORIGINAL); add_relation(final_transform_key, synchronize_key, "Synchronize to Original"); + /* Parameters. */ build_parameters(&object->id); } @@ -2460,7 +2477,10 @@ void DepsgraphRelationBuilder::build_camera(Camera *camera) ComponentKey dof_ob_key(&camera->dof.focus_object->id, NodeType::TRANSFORM); add_relation(dof_ob_key, camera_parameters_key, "Camera DOF"); if (camera->dof.focus_subtarget[0]) { - OperationKey target_key(&camera->dof.focus_object->id, NodeType::BONE, camera->dof.focus_subtarget, OperationCode::BONE_DONE); + OperationKey target_key(&camera->dof.focus_object->id, + NodeType::BONE, + camera->dof.focus_subtarget, + OperationCode::BONE_DONE); add_relation(target_key, camera_parameters_key, "Camera DOF subtarget"); } } diff --git a/source/blender/depsgraph/intern/node/deg_node_operation.h b/source/blender/depsgraph/intern/node/deg_node_operation.h index aaa508ab3e8..4808e004dc2 100644 --- a/source/blender/depsgraph/intern/node/deg_node_operation.h +++ b/source/blender/depsgraph/intern/node/deg_node_operation.h @@ -203,13 +203,16 @@ const char *operationCodeAsString(OperationCode opcode); enum OperationFlag { /* Node needs to be updated. */ DEPSOP_FLAG_NEEDS_UPDATE = (1 << 0), + /* Node was directly modified, causing need for update. */ DEPSOP_FLAG_DIRECTLY_MODIFIED = (1 << 1), + /* Node was updated due to user input. */ DEPSOP_FLAG_USER_MODIFIED = (1 << 2), - /* Node may not be removed, even when it has no evaluation callback and no - * outgoing relations. This is for NO-OP nodes that are purely used to indicate a - * relation between components/IDs, and not for connecting to an operation. */ + + /* Node may not be removed, even when it has no evaluation callback and no outgoing relations. + * This is for NO-OP nodes that are purely used to indicate a relation between components/IDs, + * and not for connecting to an operation. */ DEPSOP_FLAG_PINNED = (1 << 3), /* Set of flags which gets flushed along the relations. */ -- cgit v1.2.3 From 835203fde8d61014c727bfc9c8aa3d32862f2592 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Tue, 19 Jul 2022 14:35:18 +0200 Subject: Depsgraph: Localize synchronization component visibility handling Move it from generic visibility handling to the synchronization component node implementation. Should be no functional changes. --- .../blender/depsgraph/intern/builder/deg_builder.cc | 13 ------------- .../depsgraph/intern/node/deg_node_component.h | 20 +++++++++++++++++++- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/source/blender/depsgraph/intern/builder/deg_builder.cc b/source/blender/depsgraph/intern/builder/deg_builder.cc index a3cd821e82f..888e0649065 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder.cc @@ -138,19 +138,6 @@ void deg_graph_build_flush_visibility(Depsgraph *graph) for (IDNode *id_node : graph->id_nodes) { for (ComponentNode *comp_node : id_node->components.values()) { comp_node->affects_directly_visible |= id_node->is_directly_visible; - - /* Enforce "visibility" of the synchronization component. - * - * This component is never connected to other ID nodes, and hence can not be handled in the - * same way as other components needed for evaluation. It is only needed for proper - * evaluation of the ID node it belongs to. - * - * The design is such that the synchronization is supposed to happen whenever any part of the - * ID changed/evaluated. Here we mark the component as "visible" so that genetic recalc flag - * flushing and scheduling will handle the component in a generic manner. */ - if (comp_node->type == NodeType::SYNCHRONIZATION) { - comp_node->affects_directly_visible = true; - } } } diff --git a/source/blender/depsgraph/intern/node/deg_node_component.h b/source/blender/depsgraph/intern/node/deg_node_component.h index ee6c56b1171..fd6af43f40e 100644 --- a/source/blender/depsgraph/intern/node/deg_node_component.h +++ b/source/blender/depsgraph/intern/node/deg_node_component.h @@ -195,7 +195,6 @@ DEG_COMPONENT_NODE_DECLARE_GENERIC(ShadingParameters); DEG_COMPONENT_NODE_DECLARE_GENERIC(Transform); DEG_COMPONENT_NODE_DECLARE_NO_COW_TAG_ON_UPDATE(ObjectFromLayer); DEG_COMPONENT_NODE_DECLARE_GENERIC(Dupli); -DEG_COMPONENT_NODE_DECLARE_GENERIC(Synchronization); DEG_COMPONENT_NODE_DECLARE_GENERIC(Audio); DEG_COMPONENT_NODE_DECLARE_GENERIC(Armature); DEG_COMPONENT_NODE_DECLARE_GENERIC(GenericDatablock); @@ -203,6 +202,25 @@ DEG_COMPONENT_NODE_DECLARE_NO_COW(Visibility); DEG_COMPONENT_NODE_DECLARE_GENERIC(Simulation); DEG_COMPONENT_NODE_DECLARE_GENERIC(NTreeOutput); +/* Synchronization Component. */ +struct SynchronizationComponentNode : public ComponentNode { + SynchronizationComponentNode() + { + /* Enforce "visibility" of the synchronization component. + * + * This component is never connected to other ID nodes, and hence can not be handled in the + * same way as other components needed for evaluation. It is only needed for proper + * evaluation of the ID node it belongs to. + * + * The design is such that the synchronization is supposed to happen whenever any part of the + * ID changed/evaluated. Here we mark the component as "visible" so that genetic recalc flag + * flushing and scheduling will handle the component in a generic manner. */ + affects_directly_visible = true; + } + + DEG_COMPONENT_NODE_DECLARE; +}; + /* Bone Component */ struct BoneComponentNode : public ComponentNode { /** Initialize 'bone component' node - from pointer data given. */ -- cgit v1.2.3 From e8465f941cefe2cd408a5e9db97e9a9b3eef1d25 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Tue, 19 Jul 2022 14:44:07 +0200 Subject: Depsgraph: Cleanup, use nested namespace definition --- source/blender/depsgraph/intern/debug/deg_debug.h | 6 ++---- source/blender/depsgraph/intern/debug/deg_time_average.h | 6 ++---- source/blender/depsgraph/intern/depsgraph.h | 6 ++---- source/blender/depsgraph/intern/depsgraph_physics.h | 6 ++---- source/blender/depsgraph/intern/depsgraph_registry.h | 6 ++---- source/blender/depsgraph/intern/depsgraph_relation.h | 6 ++---- source/blender/depsgraph/intern/depsgraph_tag.h | 6 ++---- source/blender/depsgraph/intern/depsgraph_type.h | 6 ++---- source/blender/depsgraph/intern/depsgraph_update.h | 6 ++---- source/blender/depsgraph/intern/eval/deg_eval.h | 6 ++---- source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.h | 6 ++---- source/blender/depsgraph/intern/eval/deg_eval_flush.h | 6 ++---- source/blender/depsgraph/intern/eval/deg_eval_runtime_backup.h | 6 ++---- .../depsgraph/intern/eval/deg_eval_runtime_backup_animation.h | 6 ++---- .../depsgraph/intern/eval/deg_eval_runtime_backup_gpencil.h | 6 ++---- .../depsgraph/intern/eval/deg_eval_runtime_backup_modifier.h | 6 ++---- .../depsgraph/intern/eval/deg_eval_runtime_backup_movieclip.h | 6 ++---- .../blender/depsgraph/intern/eval/deg_eval_runtime_backup_object.h | 6 ++---- .../blender/depsgraph/intern/eval/deg_eval_runtime_backup_pose.h | 6 ++---- .../blender/depsgraph/intern/eval/deg_eval_runtime_backup_scene.h | 6 ++---- .../depsgraph/intern/eval/deg_eval_runtime_backup_sequence.h | 6 ++---- .../depsgraph/intern/eval/deg_eval_runtime_backup_sequencer.h | 6 ++---- .../blender/depsgraph/intern/eval/deg_eval_runtime_backup_sound.h | 6 ++---- .../blender/depsgraph/intern/eval/deg_eval_runtime_backup_volume.h | 6 ++---- source/blender/depsgraph/intern/eval/deg_eval_stats.h | 6 ++---- source/blender/depsgraph/intern/node/deg_node.h | 6 ++---- source/blender/depsgraph/intern/node/deg_node_component.h | 6 ++---- source/blender/depsgraph/intern/node/deg_node_factory.h | 7 ++----- source/blender/depsgraph/intern/node/deg_node_factory_impl.h | 6 ++---- source/blender/depsgraph/intern/node/deg_node_id.h | 6 ++---- source/blender/depsgraph/intern/node/deg_node_operation.h | 6 ++---- source/blender/depsgraph/intern/node/deg_node_time.h | 6 ++---- 32 files changed, 64 insertions(+), 129 deletions(-) diff --git a/source/blender/depsgraph/intern/debug/deg_debug.h b/source/blender/depsgraph/intern/debug/deg_debug.h index ee6c5b046b7..8f18bd70d36 100644 --- a/source/blender/depsgraph/intern/debug/deg_debug.h +++ b/source/blender/depsgraph/intern/debug/deg_debug.h @@ -14,8 +14,7 @@ #include "DEG_depsgraph_debug.h" -namespace blender { -namespace deg { +namespace blender::deg { class DepsgraphDebug { public: @@ -74,5 +73,4 @@ bool terminal_do_color(void); string color_for_pointer(const void *pointer); string color_end(void); -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/debug/deg_time_average.h b/source/blender/depsgraph/intern/debug/deg_time_average.h index cc47f03b221..d4eb05a9ddb 100644 --- a/source/blender/depsgraph/intern/debug/deg_time_average.h +++ b/source/blender/depsgraph/intern/debug/deg_time_average.h @@ -7,8 +7,7 @@ #pragma once -namespace blender { -namespace deg { +namespace blender::deg { /* Utility class which takes care of calculating average of time series, such as FPS counters. */ template class AveragedTimeSampler { @@ -52,5 +51,4 @@ template class AveragedTimeSampler { int next_sample_index_; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/depsgraph.h b/source/blender/depsgraph/intern/depsgraph.h index ca4ce058904..fc92580f5f5 100644 --- a/source/blender/depsgraph/intern/depsgraph.h +++ b/source/blender/depsgraph/intern/depsgraph.h @@ -31,8 +31,7 @@ struct ID; struct Scene; struct ViewLayer; -namespace blender { -namespace deg { +namespace blender::deg { struct IDNode; struct Node; @@ -162,5 +161,4 @@ struct Depsgraph { MEM_CXX_CLASS_ALLOC_FUNCS("Depsgraph"); }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/depsgraph_physics.h b/source/blender/depsgraph/intern/depsgraph_physics.h index a8d37f76b09..2105eb62ce1 100644 --- a/source/blender/depsgraph/intern/depsgraph_physics.h +++ b/source/blender/depsgraph/intern/depsgraph_physics.h @@ -10,8 +10,7 @@ struct Collection; struct ListBase; -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; @@ -21,5 +20,4 @@ ListBase *build_collision_relations(Depsgraph *graph, unsigned int modifier_type); void clear_physics_relations(Depsgraph *graph); -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/depsgraph_registry.h b/source/blender/depsgraph/intern/depsgraph_registry.h index 2746f17590a..4ab63dcc77d 100644 --- a/source/blender/depsgraph/intern/depsgraph_registry.h +++ b/source/blender/depsgraph/intern/depsgraph_registry.h @@ -11,8 +11,7 @@ struct Main; -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; @@ -20,5 +19,4 @@ void register_graph(Depsgraph *depsgraph); void unregister_graph(Depsgraph *depsgraph); Span get_all_registered_graphs(Main *bmain); -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/depsgraph_relation.h b/source/blender/depsgraph/intern/depsgraph_relation.h index 4d51ad85878..1bacb9abfa6 100644 --- a/source/blender/depsgraph/intern/depsgraph_relation.h +++ b/source/blender/depsgraph/intern/depsgraph_relation.h @@ -9,8 +9,7 @@ #include "MEM_guardedalloc.h" -namespace blender { -namespace deg { +namespace blender::deg { struct Node; @@ -49,5 +48,4 @@ struct Relation { MEM_CXX_CLASS_ALLOC_FUNCS("Relation"); }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/depsgraph_tag.h b/source/blender/depsgraph/intern/depsgraph_tag.h index a0518420bfb..b722aab5719 100644 --- a/source/blender/depsgraph/intern/depsgraph_tag.h +++ b/source/blender/depsgraph/intern/depsgraph_tag.h @@ -10,8 +10,7 @@ struct ID; struct Main; -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; @@ -29,5 +28,4 @@ void graph_id_tag_update( * Will do nothing if the graph is not tagged for visibility update. */ void graph_tag_ids_for_visible_update(Depsgraph *graph); -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/depsgraph_type.h b/source/blender/depsgraph/intern/depsgraph_type.h index 9cf14a831bc..9e21d124f83 100644 --- a/source/blender/depsgraph/intern/depsgraph_type.h +++ b/source/blender/depsgraph/intern/depsgraph_type.h @@ -34,8 +34,7 @@ struct Depsgraph; struct CustomData_MeshMasks; -namespace blender { -namespace deg { +namespace blender::deg { /* Commonly used types. */ using std::deque; @@ -153,5 +152,4 @@ struct DEGCustomDataMeshMasks { } }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/depsgraph_update.h b/source/blender/depsgraph/intern/depsgraph_update.h index 18f8e6acab6..c5a72157bd2 100644 --- a/source/blender/depsgraph/intern/depsgraph_update.h +++ b/source/blender/depsgraph/intern/depsgraph_update.h @@ -10,12 +10,10 @@ struct DEGEditorUpdateContext; struct ID; -namespace blender { -namespace deg { +namespace blender::deg { void deg_editors_id_update(const DEGEditorUpdateContext *update_ctx, struct ID *id); void deg_editors_scene_update(const DEGEditorUpdateContext *update_ctx, bool updated); -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/eval/deg_eval.h b/source/blender/depsgraph/intern/eval/deg_eval.h index ba86e1a349d..6937231d81a 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval.h +++ b/source/blender/depsgraph/intern/eval/deg_eval.h @@ -9,8 +9,7 @@ #pragma once -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; @@ -23,5 +22,4 @@ struct Depsgraph; */ void deg_evaluate_on_refresh(Depsgraph *graph); -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.h b/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.h index 29322d58218..cbf450aa3f1 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.h +++ b/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.h @@ -27,8 +27,7 @@ struct ID; struct Depsgraph; -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; class DepsgraphNodeBuilder; @@ -77,5 +76,4 @@ bool deg_copy_on_write_is_expanded(const struct ID *id_cow); bool deg_copy_on_write_is_needed(const ID *id_orig); bool deg_copy_on_write_is_needed(const ID_Type id_type); -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/eval/deg_eval_flush.h b/source/blender/depsgraph/intern/eval/deg_eval_flush.h index 6eb232e76e5..614ca66faed 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_flush.h +++ b/source/blender/depsgraph/intern/eval/deg_eval_flush.h @@ -9,8 +9,7 @@ #pragma once -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; @@ -24,5 +23,4 @@ void deg_graph_flush_updates(struct Depsgraph *graph); */ void deg_graph_clear_tags(struct Depsgraph *graph); -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup.h b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup.h index deb21715a28..3d9b308c5ad 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup.h +++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup.h @@ -17,8 +17,7 @@ #include "intern/eval/deg_eval_runtime_backup_sound.h" #include "intern/eval/deg_eval_runtime_backup_volume.h" -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; @@ -59,5 +58,4 @@ class RuntimeBackup { GPencilBackup gpencil_backup; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_animation.h b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_animation.h index 92847b330e8..807cc91242e 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_animation.h +++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_animation.h @@ -11,8 +11,7 @@ #include "intern/depsgraph_type.h" -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; @@ -46,5 +45,4 @@ class AnimationBackup { Vector values_backup; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_gpencil.h b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_gpencil.h index 68eff01fd60..95c0ca3a2fe 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_gpencil.h +++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_gpencil.h @@ -9,8 +9,7 @@ struct bGPdata; -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; @@ -25,5 +24,4 @@ class GPencilBackup { const Depsgraph *depsgraph; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_modifier.h b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_modifier.h index faec8f7c065..ee51204b24c 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_modifier.h +++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_modifier.h @@ -11,8 +11,7 @@ struct ModifierData; -namespace blender { -namespace deg { +namespace blender::deg { class ModifierDataBackup { public: @@ -22,5 +21,4 @@ class ModifierDataBackup { void *runtime; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_movieclip.h b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_movieclip.h index 0e826e8f72a..aa13914d8c8 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_movieclip.h +++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_movieclip.h @@ -11,8 +11,7 @@ struct MovieClip; struct MovieClipCache; struct anim; -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; @@ -30,5 +29,4 @@ class MovieClipBackup { struct MovieClipCache *cache; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_object.h b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_object.h index 3b138feec0b..c9cc167d927 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_object.h +++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_object.h @@ -17,8 +17,7 @@ struct Object; -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; @@ -46,5 +45,4 @@ class ObjectRuntimeBackup { Map pose_channel_runtime_data; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_pose.h b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_pose.h index 8fd5de44001..2f6a3dd4371 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_pose.h +++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_pose.h @@ -11,8 +11,6 @@ #include "DNA_action_types.h" -namespace blender { -namespace deg { +namespace blender::deg { -} -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_scene.h b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_scene.h index 17683966a22..155cb42a96d 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_scene.h +++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_scene.h @@ -11,8 +11,7 @@ struct Scene; -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; @@ -40,5 +39,4 @@ class SceneBackup { SequencerBackup sequencer_backup; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sequence.h b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sequence.h index 95fbd2a3e4e..f97b6b200e9 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sequence.h +++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sequence.h @@ -11,8 +11,7 @@ struct Sequence; -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; @@ -32,5 +31,4 @@ class SequenceBackup { ListBase anims; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sequencer.h b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sequencer.h index 19e1a63ab4c..38fb8e81cc3 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sequencer.h +++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sequencer.h @@ -16,8 +16,7 @@ struct Scene; -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; @@ -34,5 +33,4 @@ class SequencerBackup { Map sequences_backup; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sound.h b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sound.h index c4267be1421..9e1d15251e8 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sound.h +++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sound.h @@ -9,8 +9,7 @@ struct bSound; -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; @@ -29,5 +28,4 @@ class SoundBackup { void *playback_handle; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_volume.h b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_volume.h index bc263cc58f8..60cba64f120 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_volume.h +++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_volume.h @@ -10,8 +10,7 @@ struct Volume; struct VolumeGridVector; -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; @@ -27,5 +26,4 @@ class VolumeBackup { char filepath[1024]; /* FILE_MAX */ }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/eval/deg_eval_stats.h b/source/blender/depsgraph/intern/eval/deg_eval_stats.h index a1e3cdaca76..c24c5f07135 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_stats.h +++ b/source/blender/depsgraph/intern/eval/deg_eval_stats.h @@ -7,13 +7,11 @@ #pragma once -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; /* Aggregate operation timings to overall component and ID nodes timing. */ void deg_eval_stats_aggregate(Depsgraph *graph); -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/node/deg_node.h b/source/blender/depsgraph/intern/node/deg_node.h index 509867ad47a..5b33528df33 100644 --- a/source/blender/depsgraph/intern/node/deg_node.h +++ b/source/blender/depsgraph/intern/node/deg_node.h @@ -18,8 +18,7 @@ struct ID; struct Scene; -namespace blender { -namespace deg { +namespace blender::deg { struct Depsgraph; struct OperationNode; @@ -217,5 +216,4 @@ struct Node { void deg_register_base_depsnodes(); -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/node/deg_node_component.h b/source/blender/depsgraph/intern/node/deg_node_component.h index fd6af43f40e..375a26ab49b 100644 --- a/source/blender/depsgraph/intern/node/deg_node_component.h +++ b/source/blender/depsgraph/intern/node/deg_node_component.h @@ -22,8 +22,7 @@ struct ID; struct bPoseChannel; -namespace blender { -namespace deg { +namespace blender::deg { struct BoneComponentNode; struct Depsgraph; @@ -249,5 +248,4 @@ struct ParametersComponentNode : public ComponentNode { void deg_register_component_depsnodes(); -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/node/deg_node_factory.h b/source/blender/depsgraph/intern/node/deg_node_factory.h index 6caf99ac9ba..ec5029e8352 100644 --- a/source/blender/depsgraph/intern/node/deg_node_factory.h +++ b/source/blender/depsgraph/intern/node/deg_node_factory.h @@ -14,9 +14,7 @@ struct ID; -namespace blender { -namespace deg { - +namespace blender::deg { struct DepsNodeFactory { virtual NodeType type() const = 0; virtual const char *type_name() const = 0; @@ -41,7 +39,6 @@ void register_node_typeinfo(DepsNodeFactory *factory); /* Get typeinfo for specified type */ DepsNodeFactory *type_get_factory(NodeType type); -} // namespace deg -} // namespace blender +} // namespace blender::deg #include "intern/node/deg_node_factory_impl.h" diff --git a/source/blender/depsgraph/intern/node/deg_node_factory_impl.h b/source/blender/depsgraph/intern/node/deg_node_factory_impl.h index 76a91860cc1..d9d0a1c1e3e 100644 --- a/source/blender/depsgraph/intern/node/deg_node_factory_impl.h +++ b/source/blender/depsgraph/intern/node/deg_node_factory_impl.h @@ -11,8 +11,7 @@ struct ID; -namespace blender { -namespace deg { +namespace blender::deg { template NodeType DepsNodeFactoryImpl::type() const { @@ -48,5 +47,4 @@ Node *DepsNodeFactoryImpl::create_node(const ID *id, return node; } -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/node/deg_node_id.h b/source/blender/depsgraph/intern/node/deg_node_id.h index a8b6294b482..406ca828049 100644 --- a/source/blender/depsgraph/intern/node/deg_node_id.h +++ b/source/blender/depsgraph/intern/node/deg_node_id.h @@ -12,8 +12,7 @@ #include "DNA_ID.h" #include "intern/node/deg_node.h" -namespace blender { -namespace deg { +namespace blender::deg { struct ComponentNode; @@ -117,5 +116,4 @@ struct IDNode : public Node { DEG_DEPSNODE_DECLARE; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/node/deg_node_operation.h b/source/blender/depsgraph/intern/node/deg_node_operation.h index 4808e004dc2..fd772fbce9d 100644 --- a/source/blender/depsgraph/intern/node/deg_node_operation.h +++ b/source/blender/depsgraph/intern/node/deg_node_operation.h @@ -13,8 +13,7 @@ struct Depsgraph; -namespace blender { -namespace deg { +namespace blender::deg { struct ComponentNode; @@ -272,5 +271,4 @@ struct OperationNode : public Node { void deg_register_operation_depsnodes(); -} // namespace deg -} // namespace blender +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/node/deg_node_time.h b/source/blender/depsgraph/intern/node/deg_node_time.h index fcfe9a0fa80..3946b63384d 100644 --- a/source/blender/depsgraph/intern/node/deg_node_time.h +++ b/source/blender/depsgraph/intern/node/deg_node_time.h @@ -9,8 +9,7 @@ #include "intern/node/deg_node.h" -namespace blender { -namespace deg { +namespace blender::deg { /* Time Source Node. */ struct TimeSourceNode : public Node { @@ -25,5 +24,4 @@ struct TimeSourceNode : public Node { DEG_DEPSNODE_DECLARE; }; -} // namespace deg -} // namespace blender +} // namespace blender::deg -- cgit v1.2.3 From 44f1495b570037f06c698b0fb0a7cb21fdf0ac97 Mon Sep 17 00:00:00 2001 From: Ethan-Hall Date: Tue, 19 Jul 2022 15:20:01 +0200 Subject: EEVEE: use mipmaps of compressed textures (DDS) Currently Blender generates mipmaps that override the existing ones. This patch disables generating new mipmaps for compressed textures. Reviewed By: fclem Differential Revision: https://developer.blender.org/D14459 --- source/blender/gpu/opengl/gl_texture.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/blender/gpu/opengl/gl_texture.cc b/source/blender/gpu/opengl/gl_texture.cc index 055c8d104e2..cfb3184c4a5 100644 --- a/source/blender/gpu/opengl/gl_texture.cc +++ b/source/blender/gpu/opengl/gl_texture.cc @@ -310,6 +310,12 @@ void GLTexture::update_sub( */ void GLTexture::generate_mipmap() { + /* Allow users to provide mipmaps stored in compressed textures. + * Skip generating mipmaps to avoid overriding the existing ones. */ + if (format_flag_ & GPU_FORMAT_COMPRESSED) { + return; + } + /* Some drivers have bugs when using #glGenerateMipmap with depth textures (see T56789). * In this case we just create a complete texture with mipmaps manually without * down-sampling. You must initialize the texture levels using other methods like -- cgit v1.2.3 From bc6b612d8b673a6dcfce110466cd8e924103fb1d Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Tue, 19 Jul 2022 15:23:44 +0200 Subject: Depsgraph: Make variable naming more clear Disambiguate from nodes visibility flags. --- source/blender/depsgraph/intern/depsgraph.cc | 4 ++-- source/blender/depsgraph/intern/depsgraph.h | 4 ++-- source/blender/depsgraph/intern/depsgraph_tag.cc | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/source/blender/depsgraph/intern/depsgraph.cc b/source/blender/depsgraph/intern/depsgraph.cc index 4514084059a..787b4210cff 100644 --- a/source/blender/depsgraph/intern/depsgraph.cc +++ b/source/blender/depsgraph/intern/depsgraph.cc @@ -46,8 +46,8 @@ namespace blender::deg { Depsgraph::Depsgraph(Main *bmain, Scene *scene, ViewLayer *view_layer, eEvaluationMode mode) : time_source(nullptr), need_update(true), - need_visibility_update(true), - need_visibility_time_update(false), + need_tag_id_on_graph_visibility_update(true), + need_tag_id_on_graph_visibility_time_update(false), bmain(bmain), scene(scene), view_layer(view_layer), diff --git a/source/blender/depsgraph/intern/depsgraph.h b/source/blender/depsgraph/intern/depsgraph.h index fc92580f5f5..fc24e98acb7 100644 --- a/source/blender/depsgraph/intern/depsgraph.h +++ b/source/blender/depsgraph/intern/depsgraph.h @@ -93,8 +93,8 @@ struct Depsgraph { /* Indicated whether IDs in this graph are to be tagged as if they first appear visible, with * an optional tag for their animation (time) update. */ - bool need_visibility_update; - bool need_visibility_time_update; + bool need_tag_id_on_graph_visibility_update; + bool need_tag_id_on_graph_visibility_time_update; /* Indicates which ID types were updated. */ char id_type_updated[INDEX_ID_MAX]; diff --git a/source/blender/depsgraph/intern/depsgraph_tag.cc b/source/blender/depsgraph/intern/depsgraph_tag.cc index b50081458ad..9cd5980d8fe 100644 --- a/source/blender/depsgraph/intern/depsgraph_tag.cc +++ b/source/blender/depsgraph/intern/depsgraph_tag.cc @@ -494,19 +494,19 @@ void deg_graph_node_tag_zero(Main *bmain, void graph_tag_on_visible_update(Depsgraph *graph, const bool do_time) { - graph->need_visibility_update = true; - graph->need_visibility_time_update |= do_time; + graph->need_tag_id_on_graph_visibility_update = true; + graph->need_tag_id_on_graph_visibility_time_update |= do_time; } } /* namespace */ void graph_tag_ids_for_visible_update(Depsgraph *graph) { - if (!graph->need_visibility_update) { + if (!graph->need_tag_id_on_graph_visibility_update) { return; } - const bool do_time = graph->need_visibility_time_update; + const bool do_time = graph->need_tag_id_on_graph_visibility_time_update; Main *bmain = graph->bmain; /* NOTE: It is possible to have this function called with `do_time=false` first and later (prior @@ -561,8 +561,8 @@ void graph_tag_ids_for_visible_update(Depsgraph *graph) id_node->previously_visible_components_mask = id_node->visible_components_mask; } - graph->need_visibility_update = false; - graph->need_visibility_time_update = false; + graph->need_tag_id_on_graph_visibility_update = false; + graph->need_tag_id_on_graph_visibility_time_update = false; } NodeType geometry_tag_to_component(const ID *id) -- cgit v1.2.3 From 95fd7c3679566e87ceff7a6df8b24a8da33534ab Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Tue, 19 Jul 2022 15:27:20 +0200 Subject: Depsgraph: Cleanup, Make variable less ambiguous and more clear --- source/blender/depsgraph/intern/builder/pipeline.cc | 2 +- source/blender/depsgraph/intern/depsgraph.cc | 2 +- source/blender/depsgraph/intern/depsgraph.h | 2 +- source/blender/depsgraph/intern/depsgraph_build.cc | 4 ++-- source/blender/depsgraph/intern/depsgraph_query.cc | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/source/blender/depsgraph/intern/builder/pipeline.cc b/source/blender/depsgraph/intern/builder/pipeline.cc index 540b8e173f1..815a06d03d1 100644 --- a/source/blender/depsgraph/intern/builder/pipeline.cc +++ b/source/blender/depsgraph/intern/builder/pipeline.cc @@ -90,7 +90,7 @@ void AbstractBuilderPipeline::build_step_finalize() } #endif /* Relations are up to date. */ - deg_graph_->need_update = false; + deg_graph_->need_update_relations = false; } unique_ptr AbstractBuilderPipeline::construct_node_builder() diff --git a/source/blender/depsgraph/intern/depsgraph.cc b/source/blender/depsgraph/intern/depsgraph.cc index 787b4210cff..d460a68747d 100644 --- a/source/blender/depsgraph/intern/depsgraph.cc +++ b/source/blender/depsgraph/intern/depsgraph.cc @@ -45,7 +45,7 @@ namespace blender::deg { Depsgraph::Depsgraph(Main *bmain, Scene *scene, ViewLayer *view_layer, eEvaluationMode mode) : time_source(nullptr), - need_update(true), + need_update_relations(true), need_tag_id_on_graph_visibility_update(true), need_tag_id_on_graph_visibility_time_update(false), bmain(bmain), diff --git a/source/blender/depsgraph/intern/depsgraph.h b/source/blender/depsgraph/intern/depsgraph.h index fc24e98acb7..33d97e4b8b2 100644 --- a/source/blender/depsgraph/intern/depsgraph.h +++ b/source/blender/depsgraph/intern/depsgraph.h @@ -89,7 +89,7 @@ struct Depsgraph { TimeSourceNode *time_source; /* Indicates whether relations needs to be updated. */ - bool need_update; + bool need_update_relations; /* Indicated whether IDs in this graph are to be tagged as if they first appear visible, with * an optional tag for their animation (time) update. */ diff --git a/source/blender/depsgraph/intern/depsgraph_build.cc b/source/blender/depsgraph/intern/depsgraph_build.cc index c64b7bc1eb7..a207c13d646 100644 --- a/source/blender/depsgraph/intern/depsgraph_build.cc +++ b/source/blender/depsgraph/intern/depsgraph_build.cc @@ -270,7 +270,7 @@ void DEG_graph_tag_relations_update(Depsgraph *graph) { DEG_DEBUG_PRINTF(graph, TAG, "%s: Tagging relations for update.\n", __func__); deg::Depsgraph *deg_graph = reinterpret_cast(graph); - deg_graph->need_update = true; + deg_graph->need_update_relations = true; /* NOTE: When relations are updated, it's quite possible that * we've got new bases in the scene. This means, we need to * re-create flat array of bases in view layer. @@ -286,7 +286,7 @@ void DEG_graph_tag_relations_update(Depsgraph *graph) void DEG_graph_relations_update(Depsgraph *graph) { deg::Depsgraph *deg_graph = (deg::Depsgraph *)graph; - if (!deg_graph->need_update) { + if (!deg_graph->need_update_relations) { /* Graph is up to date, nothing to do. */ return; } diff --git a/source/blender/depsgraph/intern/depsgraph_query.cc b/source/blender/depsgraph/intern/depsgraph_query.cc index 6ffc711a475..9a047c70d01 100644 --- a/source/blender/depsgraph/intern/depsgraph_query.cc +++ b/source/blender/depsgraph/intern/depsgraph_query.cc @@ -328,7 +328,7 @@ bool DEG_is_fully_evaluated(const struct Depsgraph *depsgraph) { const deg::Depsgraph *deg_graph = (const deg::Depsgraph *)depsgraph; /* Check whether relations are up to date. */ - if (deg_graph->need_update) { + if (deg_graph->need_update_relations) { return false; } /* Check whether IDs are up to date. */ -- cgit v1.2.3 From 2232855b50e90ef49d07df8ee4e3e0d0efb2cc4c Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Tue, 19 Jul 2022 15:49:29 +0200 Subject: Curves: align surface and curves object in Empty Hair operator Without this, symmetry does not work by default when the surface object was not at the same location as the 3d cursor. --- source/blender/editors/object/object_add.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc index 35d23edfbf0..a8e11afba65 100644 --- a/source/blender/editors/object/object_add.cc +++ b/source/blender/editors/object/object_add.cc @@ -2071,16 +2071,16 @@ static int object_curves_empty_hair_add_exec(bContext *C, wmOperator *op) Scene *scene = CTX_data_scene(C); ushort local_view_bits; - blender::float3 loc, rot; if (!ED_object_add_generic_get_opts( - C, op, 'Z', loc, rot, nullptr, nullptr, &local_view_bits, nullptr)) { + C, op, 'Z', nullptr, nullptr, nullptr, nullptr, &local_view_bits, nullptr)) { return OPERATOR_CANCELLED; } Object *surface_ob = CTX_data_active_object(C); BLI_assert(surface_ob != nullptr); - Object *curves_ob = ED_object_add_type(C, OB_CURVES, nullptr, loc, rot, false, local_view_bits); + Object *curves_ob = ED_object_add_type(C, OB_CURVES, nullptr, nullptr, nullptr, false, local_view_bits); + BKE_object_apply_mat4(curves_ob, surface_ob->obmat, false, false); /* Set surface object. */ Curves *curves_id = static_cast(curves_ob->data); -- cgit v1.2.3 From 4812eda3c5d14c672e91ef11182e1a875c070b10 Mon Sep 17 00:00:00 2001 From: Colin Basnett Date: Tue, 19 Jul 2022 15:57:31 +0200 Subject: Animation RNA: Add `clear()` method to FCurveKeyframePoints Add `FCurveKeyframePoints.clear()` method to delete all keyframe points from an FCurve. Reviewed By: sybren Differential Revision: https://developer.blender.org/D15283 --- source/blender/makesrna/intern/rna_fcurve.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/source/blender/makesrna/intern/rna_fcurve.c b/source/blender/makesrna/intern/rna_fcurve.c index 461536ffb8a..727d329781d 100644 --- a/source/blender/makesrna/intern/rna_fcurve.c +++ b/source/blender/makesrna/intern/rna_fcurve.c @@ -1031,6 +1031,13 @@ static void rna_FKeyframe_points_remove( rna_tag_animation_update(bmain, id); } +static void rna_FKeyframe_points_clear(ID *id, FCurve *fcu, Main *bmain) +{ + BKE_fcurve_delete_keys_all(fcu); + + rna_tag_animation_update(bmain, id); +} + static FCM_EnvelopeData *rna_FModifierEnvelope_points_add( ID *id, FModifier *fmod, Main *bmain, ReportList *reports, float frame) { @@ -2310,6 +2317,10 @@ static void rna_def_fcurve_keyframe_points(BlenderRNA *brna, PropertyRNA *cprop) /* optional */ RNA_def_boolean( func, "fast", 0, "Fast", "Fast keyframe removal to avoid recalculating the curve each time"); + + func = RNA_def_function(srna, "clear", "rna_FKeyframe_points_clear"); + RNA_def_function_ui_description(func, "Remove all keyframes from an F-Curve"); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN); } static void rna_def_fcurve(BlenderRNA *brna) -- cgit v1.2.3 From 2f834bfc14824c224f99ab7d9a9e561fa86aef6b Mon Sep 17 00:00:00 2001 From: Colin Basnett Date: Tue, 19 Jul 2022 16:06:00 +0200 Subject: Fix T97559: Undoing of NLA strip duplication requires two undo steps Fix the issue where undoing a "duplicate NLA strip" operation would require two undo steps. The cause of this was that the operator was not using the operator macro system to combine both the duplication and the translate operators into one. Instead, the old code was simply manually invoking invoking the translate operator after the duplicate operator had completed. This patch requires the default keymap to be modified to include the two new macro operators, `NLA_OT_duplicate_move` and `NLA_OT_duplicate_linked_move` in favour of the old keymap that simply called `NLA_OT_duplicate` and passed along a `linked` argument. `duplicate_move` and `duplicate_move_linked` are two different enough operations to justify having their own operators from user's point-of-view, especially since we cannot yet have different tool-tips based on an operator's settings. Reviewed By: sybren, mont29 Differential Revision: https://developer.blender.org/D15086 --- .../keyconfig/keymap_data/blender_default.py | 6 ++---- source/blender/editors/include/ED_anim_api.h | 2 ++ source/blender/editors/space_api/spacetypes.c | 1 + source/blender/editors/space_nla/nla_edit.c | 8 +------- source/blender/editors/space_nla/nla_ops.c | 24 ++++++++++++++++++++++ 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index e1e88a0e48d..d08efc6909b 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -2578,10 +2578,8 @@ def km_nla_editor(params): ("nla.soundclip_add", {"type": 'K', "value": 'PRESS', "shift": True}, None), ("nla.meta_add", {"type": 'G', "value": 'PRESS', "ctrl": True}, None), ("nla.meta_remove", {"type": 'G', "value": 'PRESS', "ctrl": True, "alt": True}, None), - ("nla.duplicate", {"type": 'D', "value": 'PRESS', "shift": True}, - {"properties": [("linked", False)]}), - ("nla.duplicate", {"type": 'D', "value": 'PRESS', "alt": True}, - {"properties": [("linked", True)]}), + ("nla.duplicate_move", {"type": 'D', "value": 'PRESS', "shift": True}, None), + ("nla.duplicate_linked_move", {"type": 'D', "value": 'PRESS', "alt": True}, None), ("nla.make_single_user", {"type": 'U', "value": 'PRESS'}, None), ("nla.delete", {"type": 'X', "value": 'PRESS'}, None), ("nla.delete", {"type": 'DEL', "value": 'PRESS'}, None), diff --git a/source/blender/editors/include/ED_anim_api.h b/source/blender/editors/include/ED_anim_api.h index ac3b4133007..cc3c68abc55 100644 --- a/source/blender/editors/include/ED_anim_api.h +++ b/source/blender/editors/include/ED_anim_api.h @@ -1046,6 +1046,8 @@ void ED_keymap_anim(struct wmKeyConfig *keyconf); void ED_operatormacros_graph(void); /* space_action */ void ED_operatormacros_action(void); +/* space_nla*/ +void ED_operatormacros_nla(void); /** \} */ diff --git a/source/blender/editors/space_api/spacetypes.c b/source/blender/editors/space_api/spacetypes.c index d53fe2efb03..3d964a95bc0 100644 --- a/source/blender/editors/space_api/spacetypes.c +++ b/source/blender/editors/space_api/spacetypes.c @@ -163,6 +163,7 @@ void ED_spacemacros_init(void) ED_operatormacros_sequencer(); ED_operatormacros_paint(); ED_operatormacros_gpencil(); + ED_operatormacros_nla(); /* Register dropboxes (can use macros). */ ED_dropboxes_ui(); diff --git a/source/blender/editors/space_nla/nla_edit.c b/source/blender/editors/space_nla/nla_edit.c index d1a667c6e4e..801d032a861 100644 --- a/source/blender/editors/space_nla/nla_edit.c +++ b/source/blender/editors/space_nla/nla_edit.c @@ -1216,13 +1216,10 @@ static int nlaedit_duplicate_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } -static int nlaedit_duplicate_invoke(bContext *C, wmOperator *op, const wmEvent *event) +static int nlaedit_duplicate_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { nlaedit_duplicate_exec(C, op); - RNA_enum_set(op->ptr, "mode", TFM_TRANSLATION); - WM_operator_name_call(C, "TRANSFORM_OT_transform", WM_OP_INVOKE_REGION_WIN, op->ptr, event); - return OPERATOR_FINISHED; } @@ -1248,9 +1245,6 @@ void NLA_OT_duplicate(wmOperatorType *ot) false, "Linked", "When duplicating strips, assign new copies of the actions they use"); - - /* to give to transform */ - RNA_def_enum(ot->srna, "mode", rna_enum_transform_mode_types, TFM_TRANSLATION, "Mode", ""); } /** \} */ diff --git a/source/blender/editors/space_nla/nla_ops.c b/source/blender/editors/space_nla/nla_ops.c index 902e7a176a3..3ae73282230 100644 --- a/source/blender/editors/space_nla/nla_ops.c +++ b/source/blender/editors/space_nla/nla_ops.c @@ -16,6 +16,8 @@ #include "ED_anim_api.h" #include "ED_screen.h" +#include "RNA_access.h" + #include "WM_api.h" #include "WM_types.h" @@ -138,6 +140,28 @@ void nla_operatortypes(void) WM_operatortype_append(NLA_OT_fmodifier_paste); } +void ED_operatormacros_nla() +{ + wmOperatorType *ot; + wmOperatorTypeMacro *otmacro; + + ot = WM_operatortype_append_macro("NLA_OT_duplicate_move", + "Duplicate", + "Duplicate selected strips and their Actions and move them", + OPTYPE_UNDO | OPTYPE_REGISTER); + otmacro = WM_operatortype_macro_define(ot, "NLA_OT_duplicate"); + RNA_boolean_set(otmacro->ptr, "linked", false); + WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate"); + + ot = WM_operatortype_append_macro("NLA_OT_duplicate_linked_move", + "Duplicate Linked", + "Duplicate selected strips and move them", + OPTYPE_UNDO | OPTYPE_REGISTER); + otmacro = WM_operatortype_macro_define(ot, "NLA_OT_duplicate"); + RNA_boolean_set(otmacro->ptr, "linked", true); + WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate"); +} + /* ************************** registration - keymaps **********************************/ void nla_keymap(wmKeyConfig *keyconf) -- cgit v1.2.3 From 348ec37f52452614cb26baa8be40a161e1446b15 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Mon, 18 Jul 2022 16:51:57 +0200 Subject: UI: Add AbstractViewItem base class No user visible changes expected. Similar to rBc355be6faeac, but for view items now instead of the view. Not much of the item code is ported to use it yet, it's actually a bit tricky for the most part. But just introducing the base class already allows me to start unifying the view item buttons (`uiButTreeRow` and `uiButGridTile`). This would be a nice improvement. --- source/blender/editors/include/UI_abstract_view.hh | 27 ++++++++++++++++++++++ source/blender/editors/include/UI_grid_view.hh | 11 +-------- source/blender/editors/include/UI_tree_view.hh | 11 +++------ source/blender/editors/interface/CMakeLists.txt | 1 + .../editors/interface/abstract_view_item.cc | 22 ++++++++++++++++++ source/blender/editors/interface/grid_view.cc | 5 ---- source/blender/editors/interface/tree_view.cc | 10 ++++---- 7 files changed, 60 insertions(+), 27 deletions(-) create mode 100644 source/blender/editors/interface/abstract_view_item.cc diff --git a/source/blender/editors/include/UI_abstract_view.hh b/source/blender/editors/include/UI_abstract_view.hh index 82f81f1702b..fdb7069e7c4 100644 --- a/source/blender/editors/include/UI_abstract_view.hh +++ b/source/blender/editors/include/UI_abstract_view.hh @@ -15,12 +15,17 @@ #include #include +#include "DNA_defs.h" + #include "BLI_span.hh" struct wmNotifier; +struct uiBlock; namespace blender::ui { +class AbstractViewItem; + class AbstractView { bool is_reconstructed_ = false; /** @@ -66,4 +71,26 @@ class AbstractView { bool is_reconstructed() const; }; +class AbstractViewItem { + protected: + bool is_active_ = false; + + public: + virtual ~AbstractViewItem() = default; + + protected: + AbstractViewItem() = default; + + /** + * Copy persistent state (e.g. active, selection, etc.) from a matching item of + * the last redraw to this item. If sub-classes introduce more advanced state they should + * override this and make it update their state accordingly. + * + * \note Always call the base class implementation when overriding this! + */ + virtual void update_from_old(const AbstractViewItem &old); + + void set_view(AbstractView &view); +}; + } // namespace blender::ui diff --git a/source/blender/editors/include/UI_grid_view.hh b/source/blender/editors/include/UI_grid_view.hh index cabc49e411c..74ce16ccd85 100644 --- a/source/blender/editors/include/UI_grid_view.hh +++ b/source/blender/editors/include/UI_grid_view.hh @@ -32,14 +32,12 @@ class AbstractGridView; /** \name Grid-View Item Type * \{ */ -class AbstractGridViewItem { +class AbstractGridViewItem : public AbstractViewItem { friend class AbstractGridView; friend class GridViewLayoutBuilder; const AbstractGridView *view_; - bool is_active_ = false; - protected: /** Reference to a string that uniquely identifies this item in the view. */ StringRef identifier_{}; @@ -77,13 +75,6 @@ class AbstractGridViewItem { */ virtual std::optional should_be_active() const; - /** - * Copy persistent state (e.g. active, selection, etc.) from a matching item of - * the last redraw to this item. If sub-classes introduce more advanced state they should - * override this and make it update their state accordingly. - */ - virtual void update_from_old(const AbstractGridViewItem &old); - /** * Activates this item, deactivates other items, and calls the * #AbstractGridViewItem::on_activate() function. diff --git a/source/blender/editors/include/UI_tree_view.hh b/source/blender/editors/include/UI_tree_view.hh index 9527df590b7..fa427d96dd8 100644 --- a/source/blender/editors/include/UI_tree_view.hh +++ b/source/blender/editors/include/UI_tree_view.hh @@ -153,7 +153,7 @@ class AbstractTreeView : public AbstractView, public TreeViewItemContainer { * It also stores state information that needs to be persistent over redraws, like the collapsed * state. */ -class AbstractTreeViewItem : public TreeViewItemContainer { +class AbstractTreeViewItem : public AbstractViewItem, public TreeViewItemContainer { friend class AbstractTreeView; friend class TreeViewLayoutBuilder; /* Higher-level API. */ @@ -161,7 +161,6 @@ class AbstractTreeViewItem : public TreeViewItemContainer { private: bool is_open_ = false; - bool is_active_ = false; bool is_renaming_ = false; protected: @@ -222,12 +221,8 @@ class AbstractTreeViewItem : public TreeViewItemContainer { */ virtual bool supports_collapsing() const; - /** - * Copy persistent state (e.g. is-collapsed flag, selection, etc.) from a matching item of - * the last redraw to this item. If sub-classes introduce more advanced state they should - * override this and make it update their state accordingly. - */ - virtual void update_from_old(const AbstractTreeViewItem &old); + /** See #AbstractViewItem::update_from_old(). */ + virtual void update_from_old(const AbstractViewItem &old) override; /** * Compare this item to \a other to check if they represent the same data. diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index 1bdec57ac59..c6c9f1f80c8 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -26,6 +26,7 @@ set(INC set(SRC abstract_view.cc + abstract_view_item.cc grid_view.cc interface.cc interface_align.c diff --git a/source/blender/editors/interface/abstract_view_item.cc b/source/blender/editors/interface/abstract_view_item.cc new file mode 100644 index 00000000000..fc71dbe8b95 --- /dev/null +++ b/source/blender/editors/interface/abstract_view_item.cc @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + */ + +#include "UI_abstract_view.hh" + +namespace blender::ui { + +/* ---------------------------------------------------------------------- */ +/** \name View Reconstruction + * \{ */ + +void AbstractViewItem::update_from_old(const AbstractViewItem &old) +{ + is_active_ = old.is_active_; +} + +/** \} */ + +} // namespace blender::ui diff --git a/source/blender/editors/interface/grid_view.cc b/source/blender/editors/interface/grid_view.cc index 19a2326fba1..362642b0846 100644 --- a/source/blender/editors/interface/grid_view.cc +++ b/source/blender/editors/interface/grid_view.cc @@ -158,11 +158,6 @@ void AbstractGridViewItem::change_state_delayed() } } -void AbstractGridViewItem::update_from_old(const AbstractGridViewItem &old) -{ - is_active_ = old.is_active_; -} - void AbstractGridViewItem::activate() { BLI_assert_msg(get_view().is_reconstructed(), diff --git a/source/blender/editors/interface/tree_view.cc b/source/blender/editors/interface/tree_view.cc index d29cf367e59..ec1140e8efb 100644 --- a/source/blender/editors/interface/tree_view.cc +++ b/source/blender/editors/interface/tree_view.cc @@ -339,11 +339,13 @@ void AbstractTreeViewItem::build_context_menu(bContext & /*C*/, uiLayout & /*col /* No context menu by default. */ } -void AbstractTreeViewItem::update_from_old(const AbstractTreeViewItem &old) +void AbstractTreeViewItem::update_from_old(const AbstractViewItem &old) { - is_open_ = old.is_open_; - is_active_ = old.is_active_; - is_renaming_ = old.is_renaming_; + AbstractViewItem::update_from_old(old); + + const AbstractTreeViewItem &old_tree_item = dynamic_cast(old); + is_open_ = old_tree_item.is_open_; + is_renaming_ = old_tree_item.is_renaming_; } bool AbstractTreeViewItem::matches(const AbstractTreeViewItem &other) const -- cgit v1.2.3 From 5bee991132ea8aa7fea827df5b5153f3211a3431 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Tue, 19 Jul 2022 16:14:42 +0200 Subject: UI: Port view item features to base class, merge view item button types No user visible changes expected. Merges the tree row and grid tile button types, which were mostly doing the same things. The idea is that there is a button type for highlighting, as well as supporting general view item features (e.g. renaming, drag/drop, etc.). So instead there is a view item button type now. Also ports view item features like renaming, custom context menus, drag controllers and drop controllers to `ui::AbstractViewItem` (the new base class for all view items). This should be quite an improvement because: - Merges code that was duplicated over view items. - Mentioned features (renaming, drag & drop, ...) are much easier to implement in new view types now. Most of it comes "for free". - Further features will immediately become availalbe to all views (e.g. selection). - Simplifies APIs, there don't have to be functions for individual view item types anymore. - View item classes are split and thus less overwhelming visually. - View item buttons now share all code (drawing, handling, etc.) - We're soon running out of available button types, this commit merges two into one. I was hoping I could do this in multiple smaller commits, but things were quite intertwined so that would've taken quite some effort. --- source/blender/editors/include/UI_abstract_view.hh | 194 ++++++++++- source/blender/editors/include/UI_grid_view.hh | 22 +- source/blender/editors/include/UI_interface.h | 73 ++--- source/blender/editors/include/UI_tree_view.hh | 140 +------- source/blender/editors/interface/abstract_view.cc | 7 + .../editors/interface/abstract_view_item.cc | 351 ++++++++++++++++++++ source/blender/editors/interface/grid_view.cc | 45 +-- source/blender/editors/interface/interface.cc | 72 +---- .../editors/interface/interface_context_menu.c | 10 +- .../editors/interface/interface_dropboxes.cc | 32 +- .../blender/editors/interface/interface_handlers.c | 88 ++--- .../blender/editors/interface/interface_intern.h | 25 +- source/blender/editors/interface/interface_ops.c | 64 ++-- .../blender/editors/interface/interface_query.cc | 32 +- source/blender/editors/interface/interface_view.cc | 43 ++- .../blender/editors/interface/interface_widgets.c | 57 +--- source/blender/editors/interface/tree_view.cc | 354 ++------------------- .../editors/space_file/asset_catalog_tree_view.cc | 58 ++-- .../space_spreadsheet/spreadsheet_dataset_draw.cc | 2 +- 19 files changed, 807 insertions(+), 862 deletions(-) diff --git a/source/blender/editors/include/UI_abstract_view.hh b/source/blender/editors/include/UI_abstract_view.hh index fdb7069e7c4..dfddace8899 100644 --- a/source/blender/editors/include/UI_abstract_view.hh +++ b/source/blender/editors/include/UI_abstract_view.hh @@ -3,11 +3,16 @@ /** \file * \ingroup editorui * - * Base for all views (UIs to display data sets), supporting common features. + * Base class for all views (UIs to display data sets) and view items, supporting common features. * https://wiki.blender.org/wiki/Source/Interface/Views * * One of the most important responsibilities of the base class is managing reconstruction, - * enabling state that is persistent over reconstructions/redraws. + * enabling state that is persistent over reconstructions/redraws. Other features: + * - Renaming + * - Custom context menus + * - Notifier listening + * - Drag controllers (dragging view items) + * - Drop controllers (dropping onto/into view items) */ #pragma once @@ -18,15 +23,25 @@ #include "DNA_defs.h" #include "BLI_span.hh" +#include "BLI_string_ref.hh" -struct wmNotifier; +struct bContext; struct uiBlock; +struct uiBut; +struct uiLayout; +struct uiViewItemHandle; +struct wmDrag; +struct wmNotifier; namespace blender::ui { class AbstractViewItem; +class AbstractViewItemDropController; +class AbstractViewItemDragController; class AbstractView { + friend class AbstractViewItem; + bool is_reconstructed_ = false; /** * Only one item can be renamed at a time. So rather than giving each item an own rename buffer @@ -43,6 +58,12 @@ class AbstractView { /** Listen to a notifier, returning true if a redraw is needed. */ virtual bool listen(const wmNotifier &) const; + /** + * Makes \a item valid for display in this view. Behavior is undefined for items not registered + * with this. + */ + void register_item(AbstractViewItem &item); + /** Only one item can be renamed at a time. */ bool is_renaming() const; /** \return If renaming was started successfully. */ @@ -63,7 +84,6 @@ class AbstractView { * After this, reconstruction is complete (see #is_reconstructed()). */ void update_from_old(uiBlock &new_block); - /** * Check if the view is fully (re-)constructed. That means, both the build function and * #update_from_old() have finished. @@ -72,15 +92,85 @@ class AbstractView { }; class AbstractViewItem { + friend class AbstractView; + friend class ViewItemAPIWrapper; + protected: + /** + * The view this item is a part of, and was registered for using #AbstractView::register_item(). + * If this wasn't done, the behavior of items is undefined. + */ + AbstractView *view_ = nullptr; bool is_active_ = false; + bool is_renaming_ = false; public: virtual ~AbstractViewItem() = default; + virtual void build_context_menu(bContext &C, uiLayout &column) const; + + /** + * Queries if the view item supports renaming in principle. Renaming may still fail, e.g. if + * another item is already being renamed. + */ + virtual bool supports_renaming() const; + /** + * Try renaming the item, or the data it represents. Can assume + * #AbstractViewItem::supports_renaming() returned true. Sub-classes that override this should + * usually call this, unless they have a custom #AbstractViewItem.matches() implementation. + * + * \return True if the renaming was successful. + */ + virtual bool rename(StringRefNull new_name); + /** + * Get the string that should be used for renaming, typically the item's label. This string will + * not be modified, but if the renaming is canceled, the value will be reset to this. + */ + virtual StringRef get_rename_string() const; + + /** + * If an item wants to support being dragged, it has to return a drag controller here. + * That is an object implementing #AbstractViewItemDragController. + */ + virtual std::unique_ptr create_drag_controller() const; + /** + * If an item wants to support dropping data into it, it has to return a drop controller here. + * That is an object implementing #AbstractViewItemDropController. + * + * \note This drop controller may be requested for each event. The view doesn't keep a drop + * controller around currently. So it can not contain persistent state. + */ + virtual std::unique_ptr create_drop_controller() const; + + /** Get the view this item is registered for using #AbstractView::register_item(). */ + AbstractView &get_view() const; + + /** + * Requires the view to have completed reconstruction, see #is_reconstructed(). Otherwise we + * can't be sure about the item state. + */ + bool is_active() const; + + bool is_renaming() const; + void begin_renaming(); + void end_renaming(); + void rename_apply(); + + template + static ToType *from_item_handle(uiViewItemHandle *handle); + protected: AbstractViewItem() = default; + /** + * Compare this item's identity to \a other to check if they represent the same data. + * Implementations can assume that the types match already (caller must check). + * + * Used to recognize an item from a previous redraw, to be able to keep its state (e.g. active, + * renaming, etc.). + */ + virtual bool matches(const AbstractViewItem &other) const = 0; + /** * Copy persistent state (e.g. active, selection, etc.) from a matching item of * the last redraw to this item. If sub-classes introduce more advanced state they should @@ -90,7 +180,101 @@ class AbstractViewItem { */ virtual void update_from_old(const AbstractViewItem &old); - void set_view(AbstractView &view); + /** + * Add a text button for renaming the item to \a block. This must be used for the built-in + * renaming to work. This button is meant to appear temporarily. It is removed when renaming is + * done. + */ + void add_rename_button(uiBlock &block); }; +template ToType *AbstractViewItem::from_item_handle(uiViewItemHandle *handle) +{ + static_assert(std::is_base_of::value, + "Type must derive from and implement the AbstractViewItem interface"); + + return dynamic_cast(reinterpret_cast(handle)); +} + +/* ---------------------------------------------------------------------- */ +/** \name Drag 'n Drop + * \{ */ + +/** + * Class to enable dragging a view item. An item can return a drop controller for itself by + * implementing #AbstractViewItem::create_drag_controller(). + */ +class AbstractViewItemDragController { + protected: + AbstractView &view_; + + public: + AbstractViewItemDragController(AbstractView &view); + virtual ~AbstractViewItemDragController() = default; + + virtual int get_drag_type() const = 0; + virtual void *create_drag_data() const = 0; + virtual void on_drag_start(); + + /** Request the view the item is registered for as type #ViewType. Throws a `std::bad_cast` + * exception if the view is not of the requested type. */ + template inline ViewType &get_view() const; +}; + +/** + * Class to define the behavior when dropping something onto/into a view item, plus the behavior + * when dragging over this item. An item can return a drop controller for itself via a custom + * implementation of #AbstractViewItem::create_drop_controller(). + */ +class AbstractViewItemDropController { + protected: + AbstractView &view_; + + public: + AbstractViewItemDropController(AbstractView &view); + virtual ~AbstractViewItemDropController() = default; + + /** + * Check if the data dragged with \a drag can be dropped on the item this controller is for. + * \param r_disabled_hint: Return a static string to display to the user, explaining why dropping + * isn't possible on this item. Shouldn't be done too aggressively, e.g. + * don't set this if the drag-type can't be dropped here; only if it can + * but there's another reason it can't be dropped. + * Can assume this is a non-null pointer. + */ + virtual bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const = 0; + /** + * Custom text to display when dragging over a view item. Should explain what happens when + * dropping the data onto this item. Will only be used if #AbstractViewItem::can_drop() + * returns true, so the implementing override doesn't have to check that again. + * The returned value must be a translated string. + */ + virtual std::string drop_tooltip(const wmDrag &drag) const = 0; + /** + * Execute the logic to apply a drop of the data dragged with \a drag onto/into the item this + * controller is for. + */ + virtual bool on_drop(struct bContext *C, const wmDrag &drag) = 0; + + /** Request the view the item is registered for as type #ViewType. Throws a `std::bad_cast` + * exception if the view is not of the requested type. */ + template inline ViewType &get_view() const; +}; + +template ViewType &AbstractViewItemDragController::get_view() const +{ + static_assert(std::is_base_of::value, + "Type must derive from and implement the ui::AbstractView interface"); + return dynamic_cast(view_); +} + +template ViewType &AbstractViewItemDropController::get_view() const +{ + static_assert(std::is_base_of::value, + "Type must derive from and implement the ui::AbstractView interface"); + return dynamic_cast(view_); +} + +/** \} */ + } // namespace blender::ui diff --git a/source/blender/editors/include/UI_grid_view.hh b/source/blender/editors/include/UI_grid_view.hh index 74ce16ccd85..805198f38ef 100644 --- a/source/blender/editors/include/UI_grid_view.hh +++ b/source/blender/editors/include/UI_grid_view.hh @@ -19,7 +19,7 @@ struct bContext; struct PreviewImage; struct uiBlock; -struct uiButGridTile; +struct uiButViewItem; struct uiLayout; struct View2D; struct wmNotifier; @@ -41,32 +41,22 @@ class AbstractGridViewItem : public AbstractViewItem { protected: /** Reference to a string that uniquely identifies this item in the view. */ StringRef identifier_{}; - /** Every visible item gets a button of type #UI_BTYPE_GRID_TILE during the layout building. */ - uiButGridTile *grid_tile_but_ = nullptr; + /** Every visible item gets a button of type #UI_BTYPE_VIEW_ITEM during the layout building. */ + uiButViewItem *view_item_but_ = nullptr; public: virtual ~AbstractGridViewItem() = default; virtual void build_grid_tile(uiLayout &layout) const = 0; - /** - * Compare this item's identifier to \a other to check if they represent the same data. - * Used to recognize an item from a previous redraw, to be able to keep its state (e.g. active, - * renaming, etc.). - */ - bool matches(const AbstractGridViewItem &other) const; - const AbstractGridView &get_view() const; - /** - * Requires the tree to have completed reconstruction, see #is_reconstructed(). Otherwise we - * can't be sure about the item state. - */ - bool is_active() const; - protected: AbstractGridViewItem(StringRef identifier); + /** See AbstractViewItem::matches(). */ + virtual bool matches(const AbstractViewItem &other) const override; + /** Called when the item's state changes from inactive to active. */ virtual void on_activate(); /** diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index f39c62d8e2b..99a1d038cca 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -74,10 +74,8 @@ typedef struct uiLayout uiLayout; typedef struct uiPopupBlockHandle uiPopupBlockHandle; /* C handle for C++ #ui::AbstractView type. */ typedef struct uiViewHandle uiViewHandle; -/* C handle for C++ #ui::AbstractTreeViewItem type. */ -typedef struct uiTreeViewItemHandle uiTreeViewItemHandle; -/* C handle for C++ #ui::AbstractGridViewItem type. */ -typedef struct uiGridViewItemHandle uiGridViewItemHandle; +/* C handle for C++ #ui::AbstractViewItem type. */ +typedef struct uiViewItemHandle uiViewItemHandle; /* Defines */ @@ -391,10 +389,8 @@ typedef enum { /** Resize handle (resize uilist). */ UI_BTYPE_GRIP = 57 << 9, UI_BTYPE_DECORATOR = 58 << 9, - /* An item in a tree view. Parent items may be collapsible. */ - UI_BTYPE_TREEROW = 59 << 9, - /* An item in a grid view. */ - UI_BTYPE_GRID_TILE = 60 << 9, + /* An item a view (see #ui::AbstractViewItem). */ + UI_BTYPE_VIEW_ITEM = 59 << 9, } eButType; #define BUTTYPE (63 << 9) @@ -1685,8 +1681,6 @@ int UI_search_items_find_index(uiSearchItems *items, const char *name); */ void UI_but_hint_drawstr_set(uiBut *but, const char *string); -void UI_but_treerow_indentation_set(uiBut *but, int indentation); - void UI_but_node_link_set(uiBut *but, struct bNodeSocket *socket, const float draw_color[4]); void UI_but_number_step_size_set(uiBut *but, float step_size); @@ -3201,45 +3195,44 @@ void UI_interface_tag_script_reload(void); void UI_block_views_listen(const uiBlock *block, const struct wmRegionListenerParams *listener_params); -bool UI_grid_view_item_is_active(const uiGridViewItemHandle *item_handle); -bool UI_tree_view_item_is_active(const uiTreeViewItemHandle *item); -bool UI_grid_view_item_matches(const uiGridViewItemHandle *a, const uiGridViewItemHandle *b); -bool UI_tree_view_item_matches(const uiTreeViewItemHandle *a, const uiTreeViewItemHandle *b); +bool UI_view_item_is_active(const uiViewItemHandle *item_handle); +bool UI_view_item_matches(const uiViewItemHandle *a_handle, const uiViewItemHandle *b_handle); /** - * Attempt to start dragging the tree-item \a item_. This will not work if the tree item doesn't - * support dragging, i.e. it won't create a drag-controller upon request. - * \return True if dragging started successfully, otherwise false. + * Can \a item_handle be renamed right now? Note that this isn't just a mere wrapper around + * #AbstractViewItem::supports_renaming(). This also checks if there is another item being renamed, + * and returns false if so. */ -bool UI_tree_view_item_drag_start(struct bContext *C, uiTreeViewItemHandle *item_); -bool UI_tree_view_item_can_drop(const uiTreeViewItemHandle *item_, - const struct wmDrag *drag, - const char **r_disabled_hint); -char *UI_tree_view_item_drop_tooltip(const uiTreeViewItemHandle *item, const struct wmDrag *drag); +bool UI_view_item_can_rename(const uiViewItemHandle *item_handle); +void UI_view_item_begin_rename(uiViewItemHandle *item_handle); + +void UI_view_item_context_menu_build(struct bContext *C, + const uiViewItemHandle *item_handle, + uiLayout *column); + /** - * Let a tree-view item handle a drop event. - * \return True if the drop was handled by the tree-view item. + * Attempt to start dragging \a item_. This will not work if the view item doesn't + * support dragging, i.e. if it won't create a drag-controller upon request. + * \return True if dragging started successfully, otherwise false. */ -bool UI_tree_view_item_drop_handle(struct bContext *C, - const uiTreeViewItemHandle *item_, - const struct ListBase *drags); +bool UI_view_item_drag_start(struct bContext *C, const uiViewItemHandle *item_); +bool UI_view_item_can_drop(const uiViewItemHandle *item_, + const struct wmDrag *drag, + const char **r_disabled_hint); +char *UI_view_item_drop_tooltip(const uiViewItemHandle *item, const struct wmDrag *drag); /** - * Can \a item_handle be renamed right now? Not that this isn't just a mere wrapper around - * #AbstractTreeViewItem::can_rename(). This also checks if there is another item being renamed, - * and returns false if so. + * Let a view item handle a drop event. + * \return True if the drop was handled by the view item. */ -bool UI_tree_view_item_can_rename(const uiTreeViewItemHandle *item_handle); -void UI_tree_view_item_begin_rename(uiTreeViewItemHandle *item_handle); - -void UI_tree_view_item_context_menu_build(struct bContext *C, - const uiTreeViewItemHandle *item, - uiLayout *column); +bool UI_view_item_drop_handle(struct bContext *C, + const uiViewItemHandle *item_, + const struct ListBase *drags); /** - * \param xy: Coordinate to find a tree-row item at, in window space. + * \param xy: Coordinate to find a view item at, in window space. */ -uiTreeViewItemHandle *UI_block_tree_view_find_item_at(const struct ARegion *region, - const int xy[2]) ATTR_NONNULL(1, 2); -uiTreeViewItemHandle *UI_block_tree_view_find_active_item(const struct ARegion *region); +uiViewItemHandle *UI_block_view_find_item_at(const struct ARegion *region, const int xy[2]) + ATTR_NONNULL(); +uiViewItemHandle *UI_block_view_find_active_item(const struct ARegion *region); #ifdef __cplusplus } diff --git a/source/blender/editors/include/UI_tree_view.hh b/source/blender/editors/include/UI_tree_view.hh index fa427d96dd8..872a6085060 100644 --- a/source/blender/editors/include/UI_tree_view.hh +++ b/source/blender/editors/include/UI_tree_view.hh @@ -9,7 +9,6 @@ #pragma once -#include #include #include #include @@ -25,18 +24,13 @@ struct bContext; struct uiBlock; struct uiBut; -struct uiButTreeRow; +struct uiButViewItem; struct uiLayout; -struct wmDrag; -struct wmEvent; -struct wmNotifier; namespace blender::ui { class AbstractTreeView; class AbstractTreeViewItem; -class AbstractTreeViewItemDropController; -class AbstractTreeViewItemDragController; /* ---------------------------------------------------------------------- */ /** \name Tree-View Item Container @@ -161,19 +155,17 @@ class AbstractTreeViewItem : public AbstractViewItem, public TreeViewItemContain private: bool is_open_ = false; - bool is_renaming_ = false; protected: /** This label is used as the default way to identifying an item within its parent. */ std::string label_{}; - /** Every visible item gets a button of type #UI_BTYPE_TREEROW during the layout building. */ - uiButTreeRow *tree_row_but_ = nullptr; + /** Every visible item gets a button of type #UI_BTYPE_VIEW_ITEM during the layout building. */ + uiButViewItem *view_item_but_ = nullptr; public: virtual ~AbstractTreeViewItem() = default; virtual void build_row(uiLayout &row) = 0; - virtual void build_context_menu(bContext &C, uiLayout &column) const; AbstractTreeView &get_tree_view() const; @@ -185,11 +177,6 @@ class AbstractTreeViewItem : public AbstractViewItem, public TreeViewItemContain * can't be sure about the item state. */ bool is_collapsed() const; - /** - * Requires the tree to have completed reconstruction, see #is_reconstructed(). Otherwise we - * can't be sure about the item state. - */ - bool is_active() const; protected: /** @@ -202,25 +189,19 @@ class AbstractTreeViewItem : public AbstractViewItem, public TreeViewItemContain */ virtual std::optional should_be_active() const; - /** - * Queries if the tree-view item supports renaming in principle. Renaming may still fail, e.g. if - * another item is already being renamed. - */ - virtual bool supports_renaming() const; - /** - * Try renaming the item, or the data it represents. Can assume - * #AbstractTreeViewItem::supports_renaming() returned true. Sub-classes that override this - * should usually call this, unless they have a custom #AbstractTreeViewItem.matches(). - * - * \return True if the renaming was successful. - */ - virtual bool rename(StringRefNull new_name); + /** See AbstractViewItem::get_rename_string(). */ + virtual StringRef get_rename_string() const override; + /** See AbstractViewItem::rename(). */ + virtual bool rename(StringRefNull new_name) override; /** * Return whether the item can be collapsed. Used to disable collapsing for items with children. */ virtual bool supports_collapsing() const; + /** See #AbstractViewItem::matches(). */ + virtual bool matches(const AbstractViewItem &other) const override; + /** See #AbstractViewItem::update_from_old(). */ virtual void update_from_old(const AbstractViewItem &old) override; @@ -230,22 +211,11 @@ class AbstractTreeViewItem : public AbstractViewItem, public TreeViewItemContain * open/closed, active, etc.). Items are only matched if their parents also match. * By default this just matches the item's label (if the parents match!). If that isn't * good enough for a sub-class, that can override it. - */ - virtual bool matches(const AbstractTreeViewItem &other) const; - - /** - * If an item wants to support being dragged, it has to return a drag controller here. - * That is an object implementing #AbstractTreeViewItemDragController. - */ - virtual std::unique_ptr create_drag_controller() const; - /** - * If an item wants to support dropping data into it, it has to return a drop controller here. - * That is an object implementing #AbstractTreeViewItemDropController. * - * \note This drop controller may be requested for each event. The tree-view doesn't keep a drop - * controller around currently. So it can not contain persistent state. + * TODO #matches_single() is a rather temporary name, used to indicate that this only compares + * the item itself, not the parents. Item matching is expected to change quite a bit anyway. */ - virtual std::unique_ptr create_drop_controller() const; + virtual bool matches_single(const AbstractTreeViewItem &other) const; /** * Activates this item, deactivates other items, calls the #AbstractTreeViewItem::on_activate() @@ -263,98 +233,30 @@ class AbstractTreeViewItem : public AbstractViewItem, public TreeViewItemContain */ bool is_hovered() const; bool is_collapsible() const; - bool is_renaming() const; void ensure_parents_uncollapsed(); - uiButTreeRow *tree_row_button(); + uiButViewItem *view_item_button(); private: - static void rename_button_fn(bContext *, void *, char *); - static AbstractTreeViewItem *find_tree_item_from_rename_button(const uiBut &but); static void tree_row_click_fn(struct bContext *, void *, void *); static void collapse_chevron_click_fn(bContext *, void *but_arg1, void *); static bool is_collapse_chevron_but(const uiBut *but); /** See #AbstractTreeView::change_state_delayed() */ void change_state_delayed(); - void end_renaming(); void add_treerow_button(uiBlock &block); void add_indent(uiLayout &row) const; void add_collapse_chevron(uiBlock &block) const; void add_rename_button(uiLayout &row); - bool matches_including_parents(const AbstractTreeViewItem &other) const; bool has_active_child() const; int count_parents() const; }; /** \} */ -/* ---------------------------------------------------------------------- */ -/** \name Drag 'n Drop - * \{ */ - -/** - * Class to enable dragging a tree-item. An item can return a drop controller for itself via a - * custom implementation of #AbstractTreeViewItem::create_drag_controller(). - */ -class AbstractTreeViewItemDragController { - protected: - AbstractTreeView &tree_view_; - - public: - AbstractTreeViewItemDragController(AbstractTreeView &tree_view); - virtual ~AbstractTreeViewItemDragController() = default; - - virtual int get_drag_type() const = 0; - virtual void *create_drag_data() const = 0; - virtual void on_drag_start(); - - template inline TreeViewType &tree_view() const; -}; - -/** - * Class to customize the drop behavior of a tree-item, plus the behavior when dragging over this - * item. An item can return a drop controller for itself via a custom implementation of - * #AbstractTreeViewItem::create_drop_controller(). - */ -class AbstractTreeViewItemDropController { - protected: - AbstractTreeView &tree_view_; - - public: - AbstractTreeViewItemDropController(AbstractTreeView &tree_view); - virtual ~AbstractTreeViewItemDropController() = default; - - /** - * Check if the data dragged with \a drag can be dropped on the item this controller is for. - * \param r_disabled_hint: Return a static string to display to the user, explaining why dropping - * isn't possible on this item. Shouldn't be done too aggressively, e.g. - * don't set this if the drag-type can't be dropped here; only if it can - * but there's another reason it can't be dropped. - * Can assume this is a non-null pointer. - */ - virtual bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const = 0; - /** - * Custom text to display when dragging over a tree item. Should explain what happens when - * dropping the data onto this item. Will only be used if #AbstractTreeViewItem::can_drop() - * returns true, so the implementing override doesn't have to check that again. - * The returned value must be a translated string. - */ - virtual std::string drop_tooltip(const wmDrag &drag) const = 0; - /** - * Execute the logic to apply a drop of the data dragged with \a drag onto/into the item this - * controller is for. - */ - virtual bool on_drop(struct bContext *C, const wmDrag &drag) = 0; - - template inline TreeViewType &tree_view() const; -}; - -/** \} */ - /* ---------------------------------------------------------------------- */ /** \name Predefined Tree-View Item Types * @@ -426,18 +328,4 @@ inline ItemT &TreeViewItemContainer::add_tree_item(Args &&...args) add_tree_item(std::make_unique(std::forward(args)...))); } -template TreeViewType &AbstractTreeViewItemDragController::tree_view() const -{ - static_assert(std::is_base_of::value, - "Type must derive from and implement the AbstractTreeView interface"); - return static_cast(tree_view_); -} - -template TreeViewType &AbstractTreeViewItemDropController::tree_view() const -{ - static_assert(std::is_base_of::value, - "Type must derive from and implement the AbstractTreeView interface"); - return static_cast(tree_view_); -} - } // namespace blender::ui diff --git a/source/blender/editors/interface/abstract_view.cc b/source/blender/editors/interface/abstract_view.cc index dea9600fde4..077c76a08f1 100644 --- a/source/blender/editors/interface/abstract_view.cc +++ b/source/blender/editors/interface/abstract_view.cc @@ -10,6 +10,13 @@ namespace blender::ui { +void AbstractView::register_item(AbstractViewItem &item) +{ + /* Actually modifies the item, not the view. But for the public API it "feels" a bit nicer to + * have the view base class register the items, rather than setting the view on the item. */ + item.view_ = this; +} + /* ---------------------------------------------------------------------- */ /** \name View Reconstruction * \{ */ diff --git a/source/blender/editors/interface/abstract_view_item.cc b/source/blender/editors/interface/abstract_view_item.cc index fc71dbe8b95..f73183d07e9 100644 --- a/source/blender/editors/interface/abstract_view_item.cc +++ b/source/blender/editors/interface/abstract_view_item.cc @@ -4,6 +4,16 @@ * \ingroup edinterface */ +#include "BKE_context.h" + +#include "BLI_listbase.h" +#include "BLI_string.h" + +#include "WM_api.h" + +#include "UI_interface.h" +#include "interface_intern.h" + #include "UI_abstract_view.hh" namespace blender::ui { @@ -15,8 +25,349 @@ namespace blender::ui { void AbstractViewItem::update_from_old(const AbstractViewItem &old) { is_active_ = old.is_active_; + is_renaming_ = old.is_renaming_; +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Renaming + * \{ */ + +bool AbstractViewItem::supports_renaming() const +{ + /* No renaming by default. */ + return false; +} +bool AbstractViewItem::rename(StringRefNull /*new_name*/) +{ + /* No renaming by default. */ + return false; +} + +StringRef AbstractViewItem::get_rename_string() const +{ + /* No rename string by default. */ + return {}; +} + +bool AbstractViewItem::is_renaming() const +{ + return is_renaming_; +} + +void AbstractViewItem::begin_renaming() +{ + AbstractView &view = get_view(); + if (view.is_renaming() || !supports_renaming()) { + return; + } + + if (view.begin_renaming()) { + is_renaming_ = true; + } + + StringRef initial_str = get_rename_string(); + std::copy(std::begin(initial_str), std::end(initial_str), std::begin(view.get_rename_buffer())); +} + +void AbstractViewItem::rename_apply() +{ + const AbstractView &view = get_view(); + rename(view.get_rename_buffer().data()); + end_renaming(); +} + +void AbstractViewItem::end_renaming() +{ + if (!is_renaming()) { + return; + } + + is_renaming_ = false; + + AbstractView &view = get_view(); + view.end_renaming(); +} + +static AbstractViewItem *find_item_from_rename_button(const uiBut &rename_but) +{ + /* A minimal sanity check, can't do much more here. */ + BLI_assert(rename_but.type == UI_BTYPE_TEXT && rename_but.poin); + + LISTBASE_FOREACH (uiBut *, but, &rename_but.block->buttons) { + if (but->type != UI_BTYPE_VIEW_ITEM) { + continue; + } + + uiButViewItem *view_item_but = (uiButViewItem *)but; + AbstractViewItem *item = reinterpret_cast(view_item_but->view_item); + const AbstractView &view = item->get_view(); + + if (item->is_renaming() && (view.get_rename_buffer().data() == rename_but.poin)) { + return item; + } + } + + return nullptr; +} + +static void rename_button_fn(bContext *UNUSED(C), void *arg, char *UNUSED(origstr)) +{ + const uiBut *rename_but = static_cast(arg); + AbstractViewItem *item = find_item_from_rename_button(*rename_but); + BLI_assert(item); + item->rename_apply(); +} + +void AbstractViewItem::add_rename_button(uiBlock &block) +{ + AbstractView &view = get_view(); + uiBut *rename_but = uiDefBut(&block, + UI_BTYPE_TEXT, + 1, + "", + 0, + 0, + UI_UNIT_X * 10, + UI_UNIT_Y, + view.get_rename_buffer().data(), + 1.0f, + view.get_rename_buffer().size(), + 0, + 0, + ""); + + /* Gotta be careful with what's passed to the `arg1` here. Any view data will be freed once the + * callback is executed. */ + UI_but_func_rename_set(rename_but, rename_button_fn, rename_but); + UI_but_flag_disable(rename_but, UI_BUT_UNDO); + + const bContext *evil_C = reinterpret_cast(block.evil_C); + ARegion *region = CTX_wm_region(evil_C); + /* Returns false if the button was removed. */ + if (UI_but_active_only(evil_C, region, &block, rename_but) == false) { + end_renaming(); + } +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Context Menu + * \{ */ + +void AbstractViewItem::build_context_menu(bContext & /*C*/, uiLayout & /*column*/) const +{ + /* No context menu by default. */ +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Drag 'n Drop + * \{ */ + +std::unique_ptr AbstractViewItem::create_drag_controller() const +{ + /* There's no drag controller (and hence no drag support) by default. */ + return nullptr; +} + +std::unique_ptr AbstractViewItem::create_drop_controller() const +{ + /* There's no drop controller (and hence no drop support) by default. */ + return nullptr; +} + +AbstractViewItemDragController::AbstractViewItemDragController(AbstractView &view) : view_(view) +{ +} + +void AbstractViewItemDragController::on_drag_start() +{ + /* Do nothing by default. */ +} + +AbstractViewItemDropController::AbstractViewItemDropController(AbstractView &view) : view_(view) +{ +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name General Getters & Setters + * \{ */ + +AbstractView &AbstractViewItem::get_view() const +{ + if (UNLIKELY(!view_)) { + throw std::runtime_error( + "Invalid state, item must be registered through AbstractView::register_item()"); + } + return *view_; +} + +bool AbstractViewItem::is_active() const +{ + BLI_assert_msg(get_view().is_reconstructed(), + "State can't be queried until reconstruction is completed"); + return is_active_; } /** \} */ } // namespace blender::ui + +/* ---------------------------------------------------------------------- */ +/** \name C-API + * \{ */ + +namespace blender::ui { + +/** + * Helper class to provide a higher level public (C-)API. Has access to private/protected view item + * members and ensures some invariants that way. + */ +class ViewItemAPIWrapper { + public: + static bool matches(const AbstractViewItem &a, const AbstractViewItem &b) + { + if (typeid(a) != typeid(b)) { + return false; + } + /* TODO should match the view as well. */ + return a.matches(b); + } + + static bool can_rename(const AbstractViewItem &item) + { + const AbstractView &view = item.get_view(); + return !view.is_renaming() && item.supports_renaming(); + } + + static bool drag_start(bContext &C, const AbstractViewItem &item) + { + const std::unique_ptr drag_controller = + item.create_drag_controller(); + if (!drag_controller) { + return false; + } + + WM_event_start_drag(&C, + ICON_NONE, + drag_controller->get_drag_type(), + drag_controller->create_drag_data(), + 0, + WM_DRAG_FREE_DATA); + drag_controller->on_drag_start(); + + return true; + } + + static bool can_drop(const AbstractViewItem &item, + const wmDrag &drag, + const char **r_disabled_hint) + { + const std::unique_ptr drop_controller = + item.create_drop_controller(); + if (!drop_controller) { + return false; + } + + return drop_controller->can_drop(drag, r_disabled_hint); + } + + static std::string drop_tooltip(const AbstractViewItem &item, const wmDrag &drag) + { + const std::unique_ptr drop_controller = + item.create_drop_controller(); + if (!drop_controller) { + return {}; + } + + return drop_controller->drop_tooltip(drag); + } + + static bool drop_handle(bContext &C, const AbstractViewItem &item, const ListBase &drags) + { + std::unique_ptr drop_controller = + item.create_drop_controller(); + + const char *disabled_hint_dummy = nullptr; + LISTBASE_FOREACH (const wmDrag *, drag, &drags) { + if (drop_controller->can_drop(*drag, &disabled_hint_dummy)) { + return drop_controller->on_drop(&C, *drag); + } + } + + return false; + } +}; + +} // namespace blender::ui + +using namespace blender::ui; + +bool UI_view_item_is_active(const uiViewItemHandle *item_handle) +{ + const AbstractViewItem &item = reinterpret_cast(*item_handle); + return item.is_active(); +} + +bool UI_view_item_matches(const uiViewItemHandle *a_handle, const uiViewItemHandle *b_handle) +{ + const AbstractViewItem &a = reinterpret_cast(*a_handle); + const AbstractViewItem &b = reinterpret_cast(*b_handle); + return ViewItemAPIWrapper::matches(a, b); +} + +bool UI_view_item_can_rename(const uiViewItemHandle *item_handle) +{ + const AbstractViewItem &item = reinterpret_cast(*item_handle); + return ViewItemAPIWrapper::can_rename(item); +} + +void UI_view_item_begin_rename(uiViewItemHandle *item_handle) +{ + AbstractViewItem &item = reinterpret_cast(*item_handle); + item.begin_renaming(); +} + +void UI_view_item_context_menu_build(bContext *C, + const uiViewItemHandle *item_handle, + uiLayout *column) +{ + const AbstractViewItem &item = reinterpret_cast(*item_handle); + item.build_context_menu(*C, *column); +} + +bool UI_view_item_drag_start(bContext *C, const uiViewItemHandle *item_) +{ + const AbstractViewItem &item = reinterpret_cast(*item_); + return ViewItemAPIWrapper::drag_start(*C, item); +} + +bool UI_view_item_can_drop(const uiViewItemHandle *item_, + const wmDrag *drag, + const char **r_disabled_hint) +{ + const AbstractViewItem &item = reinterpret_cast(*item_); + return ViewItemAPIWrapper::can_drop(item, *drag, r_disabled_hint); +} + +char *UI_view_item_drop_tooltip(const uiViewItemHandle *item_, const wmDrag *drag) +{ + const AbstractViewItem &item = reinterpret_cast(*item_); + + const std::string tooltip = ViewItemAPIWrapper::drop_tooltip(item, *drag); + return tooltip.empty() ? nullptr : BLI_strdup(tooltip.c_str()); +} + +bool UI_view_item_drop_handle(bContext *C, const uiViewItemHandle *item_, const ListBase *drags) +{ + const AbstractViewItem &item = reinterpret_cast(*item_); + return ViewItemAPIWrapper::drop_handle(*C, item, *drags); +} + +/** \} */ diff --git a/source/blender/editors/interface/grid_view.cc b/source/blender/editors/interface/grid_view.cc index 362642b0846..37fbb33f83b 100644 --- a/source/blender/editors/interface/grid_view.cc +++ b/source/blender/editors/interface/grid_view.cc @@ -95,18 +95,19 @@ AbstractGridViewItem::AbstractGridViewItem(StringRef identifier) : identifier_(i { } -bool AbstractGridViewItem::matches(const AbstractGridViewItem &other) const +bool AbstractGridViewItem::matches(const AbstractViewItem &other) const { - return identifier_ == other.identifier_; + const AbstractGridViewItem &other_grid_item = dynamic_cast(other); + return identifier_ == other_grid_item.identifier_; } void AbstractGridViewItem::grid_tile_click_fn(struct bContext * /*C*/, void *but_arg1, void * /*arg2*/) { - uiButGridTile *grid_tile_but = (uiButGridTile *)but_arg1; + uiButViewItem *view_item_but = (uiButViewItem *)but_arg1; AbstractGridViewItem &grid_item = reinterpret_cast( - *grid_tile_but->view_item); + *view_item_but->view_item); grid_item.activate(); } @@ -114,8 +115,8 @@ void AbstractGridViewItem::grid_tile_click_fn(struct bContext * /*C*/, void AbstractGridViewItem::add_grid_tile_button(uiBlock &block) { const GridViewStyle &style = get_view().get_style(); - grid_tile_but_ = (uiButGridTile *)uiDefBut(&block, - UI_BTYPE_GRID_TILE, + view_item_but_ = (uiButViewItem *)uiDefBut(&block, + UI_BTYPE_VIEW_ITEM, 0, "", 0, @@ -129,15 +130,8 @@ void AbstractGridViewItem::add_grid_tile_button(uiBlock &block) 0, ""); - grid_tile_but_->view_item = reinterpret_cast(this); - UI_but_func_set(&grid_tile_but_->but, grid_tile_click_fn, grid_tile_but_, nullptr); -} - -bool AbstractGridViewItem::is_active() const -{ - BLI_assert_msg(get_view().is_reconstructed(), - "State can't be queried until reconstruction is completed"); - return is_active_; + view_item_but_->view_item = reinterpret_cast(this); + UI_but_func_set(&view_item_but_->but, grid_tile_click_fn, view_item_but_, nullptr); } void AbstractGridViewItem::on_activate() @@ -468,24 +462,3 @@ std::optional PreviewGridItem::should_be_active() const } } // namespace blender::ui - -using namespace blender::ui; - -/* ---------------------------------------------------------------------- */ -/* C-API */ - -using namespace blender::ui; - -bool UI_grid_view_item_is_active(const uiGridViewItemHandle *item_handle) -{ - const AbstractGridViewItem &item = reinterpret_cast(*item_handle); - return item.is_active(); -} - -bool UI_grid_view_item_matches(const uiGridViewItemHandle *a_handle, - const uiGridViewItemHandle *b_handle) -{ - const AbstractGridViewItem &a = reinterpret_cast(*a_handle); - const AbstractGridViewItem &b = reinterpret_cast(*b_handle); - return a.matches(b); -} diff --git a/source/blender/editors/interface/interface.cc b/source/blender/editors/interface/interface.cc index c0df193de87..2f9e69137ed 100644 --- a/source/blender/editors/interface/interface.cc +++ b/source/blender/editors/interface/interface.cc @@ -769,20 +769,11 @@ static bool ui_but_equals_old(const uiBut *but, const uiBut *oldbut) return false; } - if ((but->type == UI_BTYPE_TREEROW) && (oldbut->type == UI_BTYPE_TREEROW)) { - uiButTreeRow *but_treerow = (uiButTreeRow *)but; - uiButTreeRow *oldbut_treerow = (uiButTreeRow *)oldbut; - if (!but_treerow->tree_item || !oldbut_treerow->tree_item || - !UI_tree_view_item_matches(but_treerow->tree_item, oldbut_treerow->tree_item)) { - return false; - } - } - - if ((but->type == UI_BTYPE_GRID_TILE) && (oldbut->type == UI_BTYPE_GRID_TILE)) { - uiButGridTile *but_gridtile = (uiButGridTile *)but; - uiButGridTile *oldbut_gridtile = (uiButGridTile *)oldbut; - if (!but_gridtile->view_item || !oldbut_gridtile->view_item || - !UI_grid_view_item_matches(but_gridtile->view_item, oldbut_gridtile->view_item)) { + if ((but->type == UI_BTYPE_VIEW_ITEM) && (oldbut->type == UI_BTYPE_VIEW_ITEM)) { + uiButViewItem *but_item = (uiButViewItem *)but; + uiButViewItem *oldbut_item = (uiButViewItem *)oldbut; + if (!but_item->view_item || !oldbut_item->view_item || + !UI_view_item_matches(but_item->view_item, oldbut_item->view_item)) { return false; } } @@ -907,16 +898,10 @@ static void ui_but_update_old_active_from_new(uiBut *oldbut, uiBut *but) progress_oldbut->progress = progress_but->progress; break; } - case UI_BTYPE_TREEROW: { - uiButTreeRow *treerow_oldbut = (uiButTreeRow *)oldbut; - uiButTreeRow *treerow_newbut = (uiButTreeRow *)but; - SWAP(uiTreeViewItemHandle *, treerow_newbut->tree_item, treerow_oldbut->tree_item); - break; - } - case UI_BTYPE_GRID_TILE: { - uiButGridTile *gridtile_oldbut = (uiButGridTile *)oldbut; - uiButGridTile *gridtile_newbut = (uiButGridTile *)but; - SWAP(uiGridViewItemHandle *, gridtile_newbut->view_item, gridtile_oldbut->view_item); + case UI_BTYPE_VIEW_ITEM: { + uiButViewItem *view_item_oldbut = (uiButViewItem *)oldbut; + uiButViewItem *view_item_newbut = (uiButViewItem *)but; + SWAP(uiViewItemHandle *, view_item_newbut->view_item, view_item_oldbut->view_item); break; } default: @@ -1013,7 +998,7 @@ static bool ui_but_update_from_old_block(const bContext *C, /* Stupid special case: The active button may be inside (as in, overlapped on top) a view-item * button which we also want to keep highlighted then. */ - if (ui_but_is_view_item(but)) { + if (but->type == UI_BTYPE_VIEW_ITEM) { flag_copy |= UI_ACTIVE; } @@ -2245,21 +2230,12 @@ int ui_but_is_pushed_ex(uiBut *but, double *value) } } break; - case UI_BTYPE_TREEROW: { - uiButTreeRow *tree_row_but = (uiButTreeRow *)but; - - is_push = -1; - if (tree_row_but->tree_item) { - is_push = UI_tree_view_item_is_active(tree_row_but->tree_item); - } - break; - } - case UI_BTYPE_GRID_TILE: { - uiButGridTile *grid_tile_but = (uiButGridTile *)but; + case UI_BTYPE_VIEW_ITEM: { + const uiButViewItem *view_item_but = (const uiButViewItem *)but; is_push = -1; - if (grid_tile_but->view_item) { - is_push = UI_grid_view_item_is_active(grid_tile_but->view_item); + if (view_item_but->view_item) { + is_push = UI_view_item_is_active(view_item_but->view_item); } break; } @@ -4011,17 +3987,13 @@ static void ui_but_alloc_info(const eButType type, alloc_size = sizeof(uiButCurveProfile); alloc_str = "uiButCurveProfile"; break; - case UI_BTYPE_TREEROW: - alloc_size = sizeof(uiButTreeRow); - alloc_str = "uiButTreeRow"; - break; case UI_BTYPE_HOTKEY_EVENT: alloc_size = sizeof(uiButHotkeyEvent); alloc_str = "uiButHotkeyEvent"; break; - case UI_BTYPE_GRID_TILE: - alloc_size = sizeof(uiButGridTile); - alloc_str = "uiButGridTile"; + case UI_BTYPE_VIEW_ITEM: + alloc_size = sizeof(uiButViewItem); + alloc_str = "uiButViewItem"; break; default: alloc_size = sizeof(uiBut); @@ -4214,7 +4186,6 @@ static uiBut *ui_def_but(uiBlock *block, UI_BTYPE_BLOCK, UI_BTYPE_BUT_MENU, UI_BTYPE_SEARCH_MENU, - UI_BTYPE_TREEROW, UI_BTYPE_POPOVER)) { but->drawflag |= (UI_BUT_TEXT_LEFT | UI_BUT_ICON_LEFT); } @@ -6469,15 +6440,6 @@ uiBut *uiDefSearchButO_ptr(uiBlock *block, return but; } -void UI_but_treerow_indentation_set(uiBut *but, int indentation) -{ - uiButTreeRow *but_row = (uiButTreeRow *)but; - BLI_assert(but->type == UI_BTYPE_TREEROW); - - but_row->indentation = indentation; - BLI_assert(indentation >= 0); -} - void UI_but_hint_drawstr_set(uiBut *but, const char *string) { ui_but_add_shortcut(but, string, false); diff --git a/source/blender/editors/interface/interface_context_menu.c b/source/blender/editors/interface/interface_context_menu.c index e58298cdaee..518fe65ee09 100644 --- a/source/blender/editors/interface/interface_context_menu.c +++ b/source/blender/editors/interface/interface_context_menu.c @@ -927,11 +927,11 @@ bool ui_popup_context_menu_for_button(bContext *C, uiBut *but, const wmEvent *ev { const ARegion *region = CTX_wm_menu(C) ? CTX_wm_menu(C) : CTX_wm_region(C); - uiButTreeRow *treerow_but = (uiButTreeRow *)ui_tree_row_find_mouse_over(region, event->xy); - if (treerow_but) { - BLI_assert(treerow_but->but.type == UI_BTYPE_TREEROW); - UI_tree_view_item_context_menu_build( - C, treerow_but->tree_item, uiLayoutColumn(layout, false)); + uiButViewItem *view_item_but = (uiButViewItem *)ui_view_item_find_mouse_over(region, + event->xy); + if (view_item_but) { + BLI_assert(view_item_but->but.type == UI_BTYPE_VIEW_ITEM); + UI_view_item_context_menu_build(C, view_item_but->view_item, uiLayoutColumn(layout, false)); uiItemS(layout); } } diff --git a/source/blender/editors/interface/interface_dropboxes.cc b/source/blender/editors/interface/interface_dropboxes.cc index 9d3c1372b15..df488fb9127 100644 --- a/source/blender/editors/interface/interface_dropboxes.cc +++ b/source/blender/editors/interface/interface_dropboxes.cc @@ -22,15 +22,14 @@ #include "UI_interface.h" /* -------------------------------------------------------------------- */ -/** \name Tree View Drag/Drop Callbacks +/** \name View Drag/Drop Callbacks * \{ */ -static bool ui_tree_view_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event) +static bool ui_view_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event) { const ARegion *region = CTX_wm_region(C); - const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at(region, - event->xy); - if (!hovered_tree_item) { + const uiViewItemHandle *hovered_item = UI_block_view_find_item_at(region, event->xy); + if (!hovered_item) { return false; } @@ -39,21 +38,21 @@ static bool ui_tree_view_drop_poll(bContext *C, wmDrag *drag, const wmEvent *eve } drag->drop_state.free_disabled_info = false; - return UI_tree_view_item_can_drop(hovered_tree_item, drag, &drag->drop_state.disabled_info); + return UI_view_item_can_drop(hovered_item, drag, &drag->drop_state.disabled_info); } -static char *ui_tree_view_drop_tooltip(bContext *C, - wmDrag *drag, - const int xy[2], - wmDropBox *UNUSED(drop)) +static char *ui_view_drop_tooltip(bContext *C, + wmDrag *drag, + const int xy[2], + wmDropBox *UNUSED(drop)) { const ARegion *region = CTX_wm_region(C); - const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at(region, xy); - if (!hovered_tree_item) { + const uiViewItemHandle *hovered_item = UI_block_view_find_item_at(region, xy); + if (!hovered_item) { return nullptr; } - return UI_tree_view_item_drop_tooltip(hovered_tree_item, drag); + return UI_view_item_drop_tooltip(hovered_item, drag); } /** \} */ @@ -140,12 +139,7 @@ void ED_dropboxes_ui() { ListBase *lb = WM_dropboxmap_find("User Interface", SPACE_EMPTY, 0); - WM_dropbox_add(lb, - "UI_OT_tree_view_drop", - ui_tree_view_drop_poll, - nullptr, - nullptr, - ui_tree_view_drop_tooltip); + WM_dropbox_add(lb, "UI_OT_view_drop", ui_view_drop_poll, nullptr, nullptr, ui_view_drop_tooltip); WM_dropbox_add(lb, "UI_OT_drop_name", ui_drop_name_poll, diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 2496136883d..0a50522a141 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -1151,7 +1151,10 @@ static void ui_apply_but_ROW(bContext *C, uiBlock *block, uiBut *but, uiHandleBu data->applied = true; } -static void ui_apply_but_TREEROW(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data) +static void ui_apply_but_VIEW_ITEM(bContext *C, + uiBlock *block, + uiBut *but, + uiHandleButtonData *data) { if (data->apply_through_extra_icon) { /* Don't apply this, it would cause unintended tree-row toggling when clicking on extra icons. @@ -2128,10 +2131,10 @@ static bool ui_but_drag_init(bContext *C, return false; } } - else if (but->type == UI_BTYPE_TREEROW) { - uiButTreeRow *tree_row_but = (uiButTreeRow *)but; - if (tree_row_but->tree_item) { - UI_tree_view_item_drag_start(C, tree_row_but->tree_item); + else if (but->type == UI_BTYPE_VIEW_ITEM) { + const uiButViewItem *view_item_but = (uiButViewItem *)but; + if (view_item_but->view_item) { + UI_view_item_drag_start(C, view_item_but->view_item); } } else { @@ -2289,11 +2292,8 @@ static void ui_apply_but( case UI_BTYPE_ROW: ui_apply_but_ROW(C, block, but, data); break; - case UI_BTYPE_GRID_TILE: - ui_apply_but_ROW(C, block, but, data); - break; - case UI_BTYPE_TREEROW: - ui_apply_but_TREEROW(C, block, but, data); + case UI_BTYPE_VIEW_ITEM: + ui_apply_but_VIEW_ITEM(C, block, but, data); break; case UI_BTYPE_LISTROW: ui_apply_but_LISTROW(C, block, but, data); @@ -4764,53 +4764,13 @@ static int ui_do_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data, cons return WM_UI_HANDLER_CONTINUE; } -static int ui_do_but_TREEROW(bContext *C, - uiBut *but, - uiHandleButtonData *data, - const wmEvent *event) -{ - uiButTreeRow *tree_row_but = (uiButTreeRow *)but; - BLI_assert(tree_row_but->but.type == UI_BTYPE_TREEROW); - - if (data->state == BUTTON_STATE_HIGHLIGHT) { - if (event->type == LEFTMOUSE) { - switch (event->val) { - case KM_PRESS: - /* Extra icons have priority, don't mess with them. */ - if (ui_but_extra_operator_icon_mouse_over_get(but, data->region, event)) { - return WM_UI_HANDLER_BREAK; - } - button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); - data->dragstartx = event->xy[0]; - data->dragstarty = event->xy[1]; - return WM_UI_HANDLER_CONTINUE; - - case KM_CLICK: - button_activate_state(C, but, BUTTON_STATE_EXIT); - return WM_UI_HANDLER_BREAK; - - case KM_DBL_CLICK: - data->cancel = true; - UI_tree_view_item_begin_rename(tree_row_but->tree_item); - ED_region_tag_redraw(CTX_wm_region(C)); - return WM_UI_HANDLER_BREAK; - } - } - } - else if (data->state == BUTTON_STATE_WAIT_DRAG) { - /* Let "default" button handling take care of the drag logic. */ - return ui_do_but_EXIT(C, but, data, event); - } - - return WM_UI_HANDLER_CONTINUE; -} - -static int ui_do_but_GRIDTILE(bContext *C, - uiBut *but, - uiHandleButtonData *data, - const wmEvent *event) +static int ui_do_but_VIEW_ITEM(bContext *C, + uiBut *but, + uiHandleButtonData *data, + const wmEvent *event) { - BLI_assert(but->type == UI_BTYPE_GRID_TILE); + uiButViewItem *view_item_but = (uiButViewItem *)but; + BLI_assert(view_item_but->but.type == UI_BTYPE_VIEW_ITEM); if (data->state == BUTTON_STATE_HIGHLIGHT) { if (event->type == LEFTMOUSE) { @@ -4831,8 +4791,7 @@ static int ui_do_but_GRIDTILE(bContext *C, case KM_DBL_CLICK: data->cancel = true; - // uiButGridTile *grid_tile_but = (uiButGridTile *)but; - // UI_tree_view_item_begin_rename(grid_tile_but->tree_item); + UI_view_item_begin_rename(view_item_but->view_item); ED_region_tag_redraw(CTX_wm_region(C)); return WM_UI_HANDLER_BREAK; } @@ -7980,7 +7939,7 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent * * to spawn the context menu should also activate the item. This makes it clear which item * will be operated on. * Apply the button immediately, so context menu polls get the right active item. */ - if (ELEM(but->type, UI_BTYPE_TREEROW)) { + if (ELEM(but->type, UI_BTYPE_VIEW_ITEM)) { ui_apply_but(C, but->block, but, but->active, true); } @@ -8045,11 +8004,8 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent * case UI_BTYPE_ROW: retval = ui_do_but_TOG(C, but, data, event); break; - case UI_BTYPE_GRID_TILE: - retval = ui_do_but_GRIDTILE(C, but, data, event); - break; - case UI_BTYPE_TREEROW: - retval = ui_do_but_TREEROW(C, but, data, event); + case UI_BTYPE_VIEW_ITEM: + retval = ui_do_but_VIEW_ITEM(C, but, data, event); break; case UI_BTYPE_SCROLL: retval = ui_do_but_SCROLL(C, block, but, data, event); @@ -9725,7 +9681,7 @@ static int ui_handle_view_items_hover(const wmEvent *event, const ARegion *regio } LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (ui_but_is_view_item(but)) { + if (but->type == UI_BTYPE_VIEW_ITEM) { but->flag &= ~UI_ACTIVE; has_view_item = true; } @@ -9752,7 +9708,7 @@ static int ui_handle_view_item_event(bContext *C, ARegion *region, uiBut *view_but) { - BLI_assert(ui_but_is_view_item(view_but)); + BLI_assert(view_but->type == UI_BTYPE_VIEW_ITEM); if (event->type == LEFTMOUSE) { /* Will free active button if there already is one. */ ui_handle_button_activate(C, region, view_but, BUTTON_ACTIVATE_OVER); diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index 5e0382f73a9..03b9d03a6e3 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -343,20 +343,12 @@ typedef struct uiButProgressbar { float progress; } uiButProgressbar; -/** Derived struct for #UI_BTYPE_TREEROW. */ -typedef struct uiButTreeRow { +typedef struct uiButViewItem { uiBut but; - uiTreeViewItemHandle *tree_item; - int indentation; -} uiButTreeRow; - -/** Derived struct for #UI_BTYPE_GRID_TILE. */ -typedef struct uiButGridTile { - uiBut but; - - uiGridViewItemHandle *view_item; -} uiButGridTile; + /* C-Handle to the view item this button was created for. */ + uiViewItemHandle *view_item; +} uiButViewItem; /** Derived struct for #UI_BTYPE_HSVCUBE. */ typedef struct uiButHSVCube { @@ -1372,7 +1364,6 @@ void ui_but_anim_decorate_update_from_flag(uiButDecorator *but); bool ui_but_is_editable(const uiBut *but) ATTR_WARN_UNUSED_RESULT; bool ui_but_is_editable_as_text(const uiBut *but) ATTR_WARN_UNUSED_RESULT; bool ui_but_is_toggle(const uiBut *but) ATTR_WARN_UNUSED_RESULT; -bool ui_but_is_view_item(const uiBut *but) ATTR_WARN_UNUSED_RESULT; /** * Can we mouse over the button or is it hidden/disabled/layout. * \note ctrl is kind of a hack currently, @@ -1406,9 +1397,7 @@ uiBut *ui_list_row_find_from_index(const struct ARegion *region, uiBut *listbox) ATTR_WARN_UNUSED_RESULT; uiBut *ui_view_item_find_mouse_over(const struct ARegion *region, const int xy[2]) ATTR_NONNULL(1, 2); -uiBut *ui_tree_row_find_mouse_over(const struct ARegion *region, const int xy[2]) - ATTR_NONNULL(1, 2); -uiBut *ui_tree_row_find_active(const struct ARegion *region); +uiBut *ui_view_item_find_active(const struct ARegion *region); typedef bool (*uiButFindPollFn)(const uiBut *but, const void *customdata); /** @@ -1546,8 +1535,8 @@ void ui_block_free_views(struct uiBlock *block); uiViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block, const uiViewHandle *new_view); -uiButTreeRow *ui_block_view_find_treerow_in_old_block(const uiBlock *new_block, - const uiTreeViewItemHandle *new_item_handle); +uiButViewItem *ui_block_view_find_matching_view_item_but_in_old_block( + const uiBlock *new_block, const uiViewItemHandle *new_item_handle); /* interface_templates.c */ diff --git a/source/blender/editors/interface/interface_ops.c b/source/blender/editors/interface/interface_ops.c index aafb56119ae..7a51ed23677 100644 --- a/source/blender/editors/interface/interface_ops.c +++ b/source/blender/editors/interface/interface_ops.c @@ -2054,40 +2054,39 @@ static void UI_OT_list_start_filter(wmOperatorType *ot) /** \name UI Tree-View Drop Operator * \{ */ -static bool ui_tree_view_drop_poll(bContext *C) +static bool ui_view_drop_poll(bContext *C) { const wmWindow *win = CTX_wm_window(C); const ARegion *region = CTX_wm_region(C); - const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at( - region, win->eventstate->xy); + const uiViewItemHandle *hovered_item = UI_block_view_find_item_at(region, win->eventstate->xy); - return hovered_tree_item != NULL; + return hovered_item != NULL; } -static int ui_tree_view_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) +static int ui_view_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) { if (event->custom != EVT_DATA_DRAGDROP) { return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; } const ARegion *region = CTX_wm_region(C); - uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at(region, event->xy); + uiViewItemHandle *hovered_item = UI_block_view_find_item_at(region, event->xy); - if (!UI_tree_view_item_drop_handle(C, hovered_tree_item, event->customdata)) { + if (!UI_view_item_drop_handle(C, hovered_item, event->customdata)) { return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; } return OPERATOR_FINISHED; } -static void UI_OT_tree_view_drop(wmOperatorType *ot) +static void UI_OT_view_drop(wmOperatorType *ot) { - ot->name = "Tree View drop"; - ot->idname = "UI_OT_tree_view_drop"; - ot->description = "Drag and drop items onto a tree item"; + ot->name = "View drop"; + ot->idname = "UI_OT_view_drop"; + ot->description = "Drag and drop items onto a data-set item"; - ot->invoke = ui_tree_view_drop_invoke; - ot->poll = ui_tree_view_drop_poll; + ot->invoke = ui_view_drop_invoke; + ot->poll = ui_view_drop_poll; ot->flag = OPTYPE_INTERNAL; } @@ -2095,43 +2094,42 @@ static void UI_OT_tree_view_drop(wmOperatorType *ot) /** \} */ /* -------------------------------------------------------------------- */ -/** \name UI Tree-View Item Rename Operator +/** \name UI View Item Rename Operator * - * General purpose renaming operator for tree-views. Thanks to this, to add a rename button to - * context menus for example, tree-view API users don't have to implement their own renaming - * operators with the same logic as they already have for their #ui::AbstractTreeViewItem::rename() - * override. + * General purpose renaming operator for views. Thanks to this, to add a rename button to context + * menus for example, view API users don't have to implement their own renaming operators with the + * same logic as they already have for their #ui::AbstractViewItem::rename() override. * * \{ */ -static bool ui_tree_view_item_rename_poll(bContext *C) +static bool ui_view_item_rename_poll(bContext *C) { const ARegion *region = CTX_wm_region(C); - const uiTreeViewItemHandle *active_item = UI_block_tree_view_find_active_item(region); - return active_item != NULL && UI_tree_view_item_can_rename(active_item); + const uiViewItemHandle *active_item = UI_block_view_find_active_item(region); + return active_item != NULL && UI_view_item_can_rename(active_item); } -static int ui_tree_view_item_rename_exec(bContext *C, wmOperator *UNUSED(op)) +static int ui_view_item_rename_exec(bContext *C, wmOperator *UNUSED(op)) { ARegion *region = CTX_wm_region(C); - uiTreeViewItemHandle *active_item = UI_block_tree_view_find_active_item(region); + uiViewItemHandle *active_item = UI_block_view_find_active_item(region); - UI_tree_view_item_begin_rename(active_item); + UI_view_item_begin_rename(active_item); ED_region_tag_redraw(region); return OPERATOR_FINISHED; } -static void UI_OT_tree_view_item_rename(wmOperatorType *ot) +static void UI_OT_view_item_rename(wmOperatorType *ot) { - ot->name = "Rename Tree-View Item"; - ot->idname = "UI_OT_tree_view_item_rename"; - ot->description = "Rename the active item in the tree"; + ot->name = "Rename View Item"; + ot->idname = "UI_OT_view_item_rename"; + ot->description = "Rename the active item in the data-set view"; - ot->exec = ui_tree_view_item_rename_exec; - ot->poll = ui_tree_view_item_rename_poll; + ot->exec = ui_view_item_rename_exec; + ot->poll = ui_view_item_rename_poll; /* Could get a custom tooltip via the `get_description()` callback and another overridable - * function of the tree-view. */ + * function of the view. */ ot->flag = OPTYPE_INTERNAL; } @@ -2235,8 +2233,8 @@ void ED_operatortypes_ui(void) WM_operatortype_append(UI_OT_list_start_filter); - WM_operatortype_append(UI_OT_tree_view_drop); - WM_operatortype_append(UI_OT_tree_view_item_rename); + WM_operatortype_append(UI_OT_view_drop); + WM_operatortype_append(UI_OT_view_item_rename); /* external */ WM_operatortype_append(UI_OT_eyedropper_color); diff --git a/source/blender/editors/interface/interface_query.cc b/source/blender/editors/interface/interface_query.cc index 71cf60985df..f084f3e06cb 100644 --- a/source/blender/editors/interface/interface_query.cc +++ b/source/blender/editors/interface/interface_query.cc @@ -54,13 +54,7 @@ bool ui_but_is_toggle(const uiBut *but) UI_BTYPE_TOGGLE_N, UI_BTYPE_CHECKBOX, UI_BTYPE_CHECKBOX_N, - UI_BTYPE_ROW, - UI_BTYPE_TREEROW); -} - -bool ui_but_is_view_item(const uiBut *but) -{ - return ELEM(but->type, UI_BTYPE_TREEROW, UI_BTYPE_GRID_TILE); + UI_BTYPE_ROW); } bool ui_but_is_interactive_ex(const uiBut *but, const bool labeledit, const bool for_tooltip) @@ -462,14 +456,9 @@ uiBut *ui_list_row_find_from_index(const ARegion *region, const int index, uiBut return ui_but_find(region, ui_but_is_listrow_at_index, &data); } -static bool ui_but_is_treerow(const uiBut *but, const void *UNUSED(customdata)) -{ - return but->type == UI_BTYPE_TREEROW; -} - static bool ui_but_is_view_item_fn(const uiBut *but, const void *UNUSED(customdata)) { - return ui_but_is_view_item(but); + return but->type == UI_BTYPE_VIEW_ITEM; } uiBut *ui_view_item_find_mouse_over(const ARegion *region, const int xy[2]) @@ -477,24 +466,19 @@ uiBut *ui_view_item_find_mouse_over(const ARegion *region, const int xy[2]) return ui_but_find_mouse_over_ex(region, xy, false, false, ui_but_is_view_item_fn, nullptr); } -uiBut *ui_tree_row_find_mouse_over(const ARegion *region, const int xy[2]) -{ - return ui_but_find_mouse_over_ex(region, xy, false, false, ui_but_is_treerow, nullptr); -} - -static bool ui_but_is_active_treerow(const uiBut *but, const void *customdata) +static bool ui_but_is_active_view_item(const uiBut *but, const void *UNUSED(customdata)) { - if (!ui_but_is_treerow(but, customdata)) { + if (but->type != UI_BTYPE_VIEW_ITEM) { return false; } - const uiButTreeRow *treerow_but = (const uiButTreeRow *)but; - return UI_tree_view_item_is_active(treerow_but->tree_item); + const uiButViewItem *view_item_but = (const uiButViewItem *)but; + return UI_view_item_is_active(view_item_but->view_item); } -uiBut *ui_tree_row_find_active(const ARegion *region) +uiBut *ui_view_item_find_active(const ARegion *region) { - return ui_but_find(region, ui_but_is_active_treerow, nullptr); + return ui_but_find(region, ui_but_is_active_view_item, nullptr); } /** \} */ diff --git a/source/blender/editors/interface/interface_view.cc b/source/blender/editors/interface/interface_view.cc index 70728565263..b35f6d2c969 100644 --- a/source/blender/editors/interface/interface_view.cc +++ b/source/blender/editors/interface/interface_view.cc @@ -86,24 +86,24 @@ void UI_block_views_listen(const uiBlock *block, const wmRegionListenerParams *l } } -uiTreeViewItemHandle *UI_block_tree_view_find_item_at(const ARegion *region, const int xy[2]) +uiViewItemHandle *UI_block_view_find_item_at(const ARegion *region, const int xy[2]) { - uiButTreeRow *tree_row_but = (uiButTreeRow *)ui_tree_row_find_mouse_over(region, xy); - if (!tree_row_but) { + uiButViewItem *item_but = (uiButViewItem *)ui_view_item_find_mouse_over(region, xy); + if (!item_but) { return nullptr; } - return tree_row_but->tree_item; + return item_but->view_item; } -uiTreeViewItemHandle *UI_block_tree_view_find_active_item(const ARegion *region) +uiViewItemHandle *UI_block_view_find_active_item(const ARegion *region) { - uiButTreeRow *tree_row_but = (uiButTreeRow *)ui_tree_row_find_active(region); - if (!tree_row_but) { + uiButViewItem *item_but = (uiButViewItem *)ui_view_item_find_active(region); + if (!item_but) { return nullptr; } - return tree_row_but->tree_item; + return item_but->view_item; } static StringRef ui_block_view_find_idname(const uiBlock &block, const AbstractView &view) @@ -151,39 +151,38 @@ uiViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block, return reinterpret_cast(old_view); } -uiButTreeRow *ui_block_view_find_treerow_in_old_block(const uiBlock *new_block, - const uiTreeViewItemHandle *new_item_handle) +uiButViewItem *ui_block_view_find_matching_view_item_but_in_old_block( + const uiBlock *new_block, const uiViewItemHandle *new_item_handle) { uiBlock *old_block = new_block->oldblock; if (!old_block) { return nullptr; } - const AbstractTreeViewItem &new_item = *reinterpret_cast( - new_item_handle); + const AbstractViewItem &new_item = *reinterpret_cast(new_item_handle); const AbstractView *old_view = ui_block_view_find_matching_in_old_block_impl( - *new_block, new_item.get_tree_view()); + *new_block, new_item.get_view()); if (!old_view) { return nullptr; } LISTBASE_FOREACH (uiBut *, old_but, &old_block->buttons) { - if (old_but->type != UI_BTYPE_TREEROW) { + if (old_but->type != UI_BTYPE_VIEW_ITEM) { continue; } - uiButTreeRow *old_treerow_but = (uiButTreeRow *)old_but; - if (!old_treerow_but->tree_item) { + uiButViewItem *old_item_but = (uiButViewItem *)old_but; + if (!old_item_but->view_item) { continue; } - AbstractTreeViewItem &old_item = *reinterpret_cast( - old_treerow_but->tree_item); - /* Check if the row is from the expected tree-view. */ - if (&old_item.get_tree_view() != old_view) { + AbstractViewItem &old_item = *reinterpret_cast(old_item_but->view_item); + /* Check if the item is from the expected view. */ + if (&old_item.get_view() != old_view) { continue; } - if (UI_tree_view_item_matches(new_item_handle, old_treerow_but->tree_item)) { - return old_treerow_but; + if (UI_view_item_matches(reinterpret_cast(&new_item), + reinterpret_cast(&old_item))) { + return old_item_but; } } diff --git a/source/blender/editors/interface/interface_widgets.c b/source/blender/editors/interface/interface_widgets.c index e2df2d77817..855e72788d2 100644 --- a/source/blender/editors/interface/interface_widgets.c +++ b/source/blender/editors/interface/interface_widgets.c @@ -104,8 +104,7 @@ typedef enum { UI_WTYPE_LISTITEM, UI_WTYPE_PROGRESSBAR, UI_WTYPE_NODESOCKET, - UI_WTYPE_TREEROW, - UI_WTYPE_GRID_TILE, + UI_WTYPE_VIEW_ITEM, } uiWidgetTypeEnum; /** @@ -3672,12 +3671,11 @@ static void widget_progressbar(uiBut *but, widgetbase_draw(&wtb_bar, wcol); } -static void widget_treerow_exec(uiWidgetColors *wcol, - rcti *rect, - const uiWidgetStateInfo *state, - int UNUSED(roundboxalign), - int indentation, - const float zoom) +static void widget_view_item(uiWidgetColors *wcol, + rcti *rect, + const uiWidgetStateInfo *state, + int UNUSED(roundboxalign), + const float zoom) { uiWidgetBase wtb; widget_init(&wtb); @@ -3690,31 +3688,6 @@ static void widget_treerow_exec(uiWidgetColors *wcol, if ((state->but_flag & UI_ACTIVE) || (state->but_flag & UI_SELECT)) { widgetbase_draw(&wtb, wcol); } - - BLI_rcti_resize(rect, BLI_rcti_size_x(rect) - UI_UNIT_X * indentation, BLI_rcti_size_y(rect)); - BLI_rcti_translate(rect, 0.5f * UI_UNIT_X * indentation, 0); -} - -static void widget_treerow(uiBut *but, - uiWidgetColors *wcol, - rcti *rect, - const uiWidgetStateInfo *state, - int roundboxalign, - const float zoom) -{ - uiButTreeRow *tree_row = (uiButTreeRow *)but; - BLI_assert(but->type == UI_BTYPE_TREEROW); - widget_treerow_exec(wcol, rect, state, roundboxalign, tree_row->indentation, zoom); -} - -static void widget_gridtile(uiWidgetColors *wcol, - rcti *rect, - const uiWidgetStateInfo *state, - int roundboxalign, - const float zoom) -{ - /* TODO Reuse tree-row drawing. */ - widget_treerow_exec(wcol, rect, state, roundboxalign, 0, zoom); } static void widget_nodesocket(uiBut *but, @@ -4608,14 +4581,9 @@ static uiWidgetType *widget_type(uiWidgetTypeEnum type) wt.custom = widget_progressbar; break; - case UI_WTYPE_TREEROW: - wt.wcol_theme = &btheme->tui.wcol_view_item; - wt.custom = widget_treerow; - break; - - case UI_WTYPE_GRID_TILE: + case UI_WTYPE_VIEW_ITEM: wt.wcol_theme = &btheme->tui.wcol_view_item; - wt.draw = widget_gridtile; + wt.draw = widget_view_item; break; case UI_WTYPE_NODESOCKET: @@ -4949,13 +4917,8 @@ void ui_draw_but(const bContext *C, struct ARegion *region, uiStyle *style, uiBu fstyle = &style->widgetlabel; break; - case UI_BTYPE_TREEROW: - wt = widget_type(UI_WTYPE_TREEROW); - fstyle = &style->widgetlabel; - break; - - case UI_BTYPE_GRID_TILE: - wt = widget_type(UI_WTYPE_GRID_TILE); + case UI_BTYPE_VIEW_ITEM: + wt = widget_type(UI_WTYPE_VIEW_ITEM); fstyle = &style->widgetlabel; break; diff --git a/source/blender/editors/interface/tree_view.cc b/source/blender/editors/interface/tree_view.cc index ec1140e8efb..c224226ba17 100644 --- a/source/blender/editors/interface/tree_view.cc +++ b/source/blender/editors/interface/tree_view.cc @@ -37,9 +37,11 @@ AbstractTreeViewItem &TreeViewItemContainer::add_tree_item( if (root_ == nullptr) { root_ = this; } - + AbstractTreeView &tree_view = static_cast(*root_); AbstractTreeViewItem &added_item = *children_.last(); added_item.root_ = root_; + tree_view.register_item(added_item); + if (root_ != this) { /* Any item that isn't the root can be assumed to the a #AbstractTreeViewItem. Not entirely * nice to static_cast this, but well... */ @@ -95,7 +97,7 @@ AbstractTreeViewItem *AbstractTreeView::find_matching_child( const AbstractTreeViewItem &lookup_item, const TreeViewOrItem &items) { for (const auto &iter_item : items.children_) { - if (lookup_item.matches(*iter_item)) { + if (lookup_item.matches_single(*iter_item)) { /* We have a matching item! */ return iter_item.get(); } @@ -118,9 +120,8 @@ void AbstractTreeViewItem::tree_row_click_fn(struct bContext * /*C*/, void *but_arg1, void * /*arg2*/) { - uiButTreeRow *tree_row_but = (uiButTreeRow *)but_arg1; - AbstractTreeViewItem &tree_item = reinterpret_cast( - *tree_row_but->tree_item); + uiButViewItem *item_but = (uiButViewItem *)but_arg1; + AbstractTreeViewItem &tree_item = reinterpret_cast(*item_but->view_item); tree_item.activate(); /* Not only activate the item, also show its children. Maybe this should be optional, or @@ -131,11 +132,11 @@ void AbstractTreeViewItem::tree_row_click_fn(struct bContext * /*C*/, void AbstractTreeViewItem::add_treerow_button(uiBlock &block) { /* For some reason a width > (UI_UNIT_X * 2) make the layout system use all available width. */ - tree_row_but_ = (uiButTreeRow *)uiDefBut( - &block, UI_BTYPE_TREEROW, 0, "", 0, 0, UI_UNIT_X * 10, UI_UNIT_Y, nullptr, 0, 0, 0, 0, ""); + view_item_but_ = (uiButViewItem *)uiDefBut( + &block, UI_BTYPE_VIEW_ITEM, 0, "", 0, 0, UI_UNIT_X * 10, UI_UNIT_Y, nullptr, 0, 0, 0, 0, ""); - tree_row_but_->tree_item = reinterpret_cast(this); - UI_but_func_set(&tree_row_but_->but, tree_row_click_fn, tree_row_but_, nullptr); + view_item_but_->view_item = reinterpret_cast(this); + UI_but_func_set(&view_item_but_->but, tree_row_click_fn, view_item_but_, nullptr); } void AbstractTreeViewItem::add_indent(uiLayout &row) const @@ -167,10 +168,9 @@ void AbstractTreeViewItem::collapse_chevron_click_fn(struct bContext *C, const wmWindow *win = CTX_wm_window(C); const ARegion *region = CTX_wm_region(C); - uiTreeViewItemHandle *hovered_item_handle = UI_block_tree_view_find_item_at(region, - win->eventstate->xy); - AbstractTreeViewItem *hovered_item = reinterpret_cast( - hovered_item_handle); + uiViewItemHandle *hovered_item_handle = UI_block_view_find_item_at(region, win->eventstate->xy); + + AbstractTreeViewItem *hovered_item = from_item_handle(hovered_item_handle); BLI_assert(hovered_item != nullptr); hovered_item->toggle_collapsed(); @@ -204,40 +204,6 @@ void AbstractTreeViewItem::add_collapse_chevron(uiBlock &block) const BLI_assert(is_collapse_chevron_but(but)); } -AbstractTreeViewItem *AbstractTreeViewItem::find_tree_item_from_rename_button( - const uiBut &rename_but) -{ - /* A minimal sanity check, can't do much more here. */ - BLI_assert(rename_but.type == UI_BTYPE_TEXT && rename_but.poin); - - LISTBASE_FOREACH (uiBut *, but, &rename_but.block->buttons) { - if (but->type != UI_BTYPE_TREEROW) { - continue; - } - - uiButTreeRow *tree_row_but = (uiButTreeRow *)but; - AbstractTreeViewItem *item = reinterpret_cast(tree_row_but->tree_item); - const AbstractTreeView &tree_view = item->get_tree_view(); - - if (item->is_renaming() && (tree_view.get_rename_buffer().data() == rename_but.poin)) { - return item; - } - } - - return nullptr; -} - -void AbstractTreeViewItem::rename_button_fn(bContext *UNUSED(C), void *arg, char *UNUSED(origstr)) -{ - const uiBut *rename_but = static_cast(arg); - AbstractTreeViewItem *item = find_tree_item_from_rename_button(*rename_but); - BLI_assert(item); - - const AbstractTreeView &tree_view = item->get_tree_view(); - item->rename(tree_view.get_rename_buffer().data()); - item->end_renaming(); -} - void AbstractTreeViewItem::add_rename_button(uiLayout &row) { uiBlock *block = uiLayoutGetBlock(&row); @@ -247,33 +213,7 @@ void AbstractTreeViewItem::add_rename_button(uiLayout &row) /* Enable emboss for the text button. */ UI_block_emboss_set(block, UI_EMBOSS); - AbstractTreeView &tree_view = get_tree_view(); - uiBut *rename_but = uiDefBut(block, - UI_BTYPE_TEXT, - 1, - "", - 0, - 0, - UI_UNIT_X * 10, - UI_UNIT_Y, - tree_view.get_rename_buffer().data(), - 1.0f, - tree_view.get_rename_buffer().size(), - 0, - 0, - ""); - - /* Gotta be careful with what's passed to the `arg1` here. Any tree data will be freed once the - * callback is executed. */ - UI_but_func_rename_set(rename_but, AbstractTreeViewItem::rename_button_fn, rename_but); - UI_but_flag_disable(rename_but, UI_BUT_UNDO); - - const bContext *evil_C = static_cast(block->evil_C); - ARegion *region = CTX_wm_region(evil_C); - /* Returns false if the button was removed. */ - if (UI_but_active_only(evil_C, region, block, rename_but) == false) { - end_renaming(); - } + AbstractViewItem::add_rename_button(*block); UI_block_emboss_set(block, previous_emboss); UI_block_layout_set_current(block, &row); @@ -306,82 +246,35 @@ bool AbstractTreeViewItem::supports_collapsing() const return true; } -std::unique_ptr AbstractTreeViewItem::create_drag_controller() - const -{ - /* There's no drag controller (and hence no drag support) by default. */ - return nullptr; -} - -std::unique_ptr AbstractTreeViewItem::create_drop_controller() - const -{ - /* There's no drop controller (and hence no drop support) by default. */ - return nullptr; -} - -bool AbstractTreeViewItem::supports_renaming() const +StringRef AbstractTreeViewItem::get_rename_string() const { - /* No renaming by default. */ - return false; + return label_; } bool AbstractTreeViewItem::rename(StringRefNull new_name) { - /* It is important to update the label after renaming, so #AbstractTreeViewItem::matches() + /* It is important to update the label after renaming, so #AbstractTreeViewItem::matches_single() * recognizes the item. (It only compares labels by default.) */ label_ = new_name; return true; } -void AbstractTreeViewItem::build_context_menu(bContext & /*C*/, uiLayout & /*column*/) const -{ - /* No context menu by default. */ -} - void AbstractTreeViewItem::update_from_old(const AbstractViewItem &old) { AbstractViewItem::update_from_old(old); const AbstractTreeViewItem &old_tree_item = dynamic_cast(old); is_open_ = old_tree_item.is_open_; - is_renaming_ = old_tree_item.is_renaming_; } -bool AbstractTreeViewItem::matches(const AbstractTreeViewItem &other) const +bool AbstractTreeViewItem::matches_single(const AbstractTreeViewItem &other) const { return label_ == other.label_; } -void AbstractTreeViewItem::begin_renaming() -{ - AbstractTreeView &tree_view = get_tree_view(); - if (tree_view.is_renaming() || !supports_renaming()) { - return; - } - - if (tree_view.begin_renaming()) { - is_renaming_ = true; - } - - std::copy(std::begin(label_), std::end(label_), std::begin(tree_view.get_rename_buffer())); -} - -void AbstractTreeViewItem::end_renaming() -{ - if (!is_renaming()) { - return; - } - - is_renaming_ = false; - - AbstractTreeView &tree_view = get_tree_view(); - tree_view.end_renaming(); -} - AbstractTreeView &AbstractTreeViewItem::get_tree_view() const { - return static_cast(*root_); + return dynamic_cast(get_view()); } int AbstractTreeViewItem::count_parents() const @@ -417,26 +310,19 @@ void AbstractTreeViewItem::deactivate() is_active_ = false; } -bool AbstractTreeViewItem::is_active() const -{ - BLI_assert_msg(get_tree_view().is_reconstructed(), - "State can't be queried until reconstruction is completed"); - return is_active_; -} - bool AbstractTreeViewItem::is_hovered() const { BLI_assert_msg(get_tree_view().is_reconstructed(), "State can't be queried until reconstruction is completed"); - BLI_assert_msg(tree_row_but_ != nullptr, + BLI_assert_msg(view_item_but_ != nullptr, "Hovered state can't be queried before the tree row is being built"); - const uiTreeViewItemHandle *this_handle = reinterpret_cast(this); + const uiViewItemHandle *this_item_handle = reinterpret_cast(this); /* The new layout hasn't finished construction yet, so the final state of the button is unknown. * Get the matching button from the previous redraw instead. */ - uiButTreeRow *old_treerow_but = ui_block_view_find_treerow_in_old_block(tree_row_but_->but.block, - this_handle); - return old_treerow_but && (old_treerow_but->but.flag & UI_ACTIVE); + uiButViewItem *old_item_but = ui_block_view_find_matching_view_item_but_in_old_block( + view_item_but_->but.block, this_item_handle); + return old_item_but && (old_item_but->but.flag & UI_ACTIVE); } bool AbstractTreeViewItem::is_collapsed() const @@ -464,11 +350,6 @@ bool AbstractTreeViewItem::is_collapsible() const return this->supports_collapsing(); } -bool AbstractTreeViewItem::is_renaming() const -{ - return is_renaming_; -} - void AbstractTreeViewItem::ensure_parents_uncollapsed() { for (AbstractTreeViewItem *parent = parent_; parent; parent = parent->parent_) { @@ -476,19 +357,21 @@ void AbstractTreeViewItem::ensure_parents_uncollapsed() } } -bool AbstractTreeViewItem::matches_including_parents(const AbstractTreeViewItem &other) const +bool AbstractTreeViewItem::matches(const AbstractViewItem &other) const { - if (!matches(other)) { + const AbstractTreeViewItem &other_tree_item = dynamic_cast(other); + + if (!matches_single(other_tree_item)) { return false; } - if (count_parents() != other.count_parents()) { + if (count_parents() != other_tree_item.count_parents()) { return false; } - for (AbstractTreeViewItem *parent = parent_, *other_parent = other.parent_; + for (AbstractTreeViewItem *parent = parent_, *other_parent = other_tree_item.parent_; parent && other_parent; parent = parent->parent_, other_parent = other_parent->parent_) { - if (!parent->matches(*other_parent)) { + if (!parent->matches_single(*other_parent)) { return false; } } @@ -496,9 +379,9 @@ bool AbstractTreeViewItem::matches_including_parents(const AbstractTreeViewItem return true; } -uiButTreeRow *AbstractTreeViewItem::tree_row_button() +uiButViewItem *AbstractTreeViewItem::view_item_button() { - return tree_row_but_; + return view_item_but_; } void AbstractTreeViewItem::change_state_delayed() @@ -511,25 +394,6 @@ void AbstractTreeViewItem::change_state_delayed() /* ---------------------------------------------------------------------- */ -AbstractTreeViewItemDragController::AbstractTreeViewItemDragController(AbstractTreeView &tree_view) - : tree_view_(tree_view) -{ -} - -void AbstractTreeViewItemDragController::on_drag_start() -{ - /* Do nothing by default. */ -} - -/* ---------------------------------------------------------------------- */ - -AbstractTreeViewItemDropController::AbstractTreeViewItemDropController(AbstractTreeView &tree_view) - : tree_view_(tree_view) -{ -} - -/* ---------------------------------------------------------------------- */ - class TreeViewLayoutBuilder { uiBlock &block_; @@ -575,7 +439,7 @@ void TreeViewLayoutBuilder::polish_layout(const uiBlock &block) UI_but_drawflag_enable(static_cast(but->next), UI_BUT_NO_TEXT_PADDING); } - if (but->type == UI_BTYPE_TREEROW) { + if (but->type == UI_BTYPE_VIEW_ITEM) { break; } } @@ -687,154 +551,4 @@ std::optional BasicTreeViewItem::should_be_active() const return std::nullopt; } -/* ---------------------------------------------------------------------- */ - -/** - * Helper for a public (C-)API, presenting higher level functionality. Has access to internal - * data/functionality (friend of #AbstractTreeViewItem), which is sometimes needed when - * functionality of the API needs to be constructed from multiple internal conditions and/or - * functions that on their own shouldn't be part of the API. - */ -class TreeViewItemAPIWrapper { - public: - static bool matches(const AbstractTreeViewItem &a, const AbstractTreeViewItem &b) - { - /* TODO should match the tree-view as well. */ - return a.matches_including_parents(b); - } - - static bool drag_start(bContext &C, const AbstractTreeViewItem &item) - { - const std::unique_ptr drag_controller = - item.create_drag_controller(); - if (!drag_controller) { - return false; - } - - WM_event_start_drag(&C, - ICON_NONE, - drag_controller->get_drag_type(), - drag_controller->create_drag_data(), - 0, - WM_DRAG_FREE_DATA); - drag_controller->on_drag_start(); - - return true; - } - - static bool can_drop(const AbstractTreeViewItem &item, - const wmDrag &drag, - const char **r_disabled_hint) - { - const std::unique_ptr drop_controller = - item.create_drop_controller(); - if (!drop_controller) { - return false; - } - - return drop_controller->can_drop(drag, r_disabled_hint); - } - - static std::string drop_tooltip(const AbstractTreeViewItem &item, const wmDrag &drag) - { - const std::unique_ptr drop_controller = - item.create_drop_controller(); - if (!drop_controller) { - return {}; - } - - return drop_controller->drop_tooltip(drag); - } - - static bool drop_handle(bContext &C, const AbstractTreeViewItem &item, const ListBase &drags) - { - std::unique_ptr drop_controller = - item.create_drop_controller(); - - const char *disabled_hint_dummy = nullptr; - LISTBASE_FOREACH (const wmDrag *, drag, &drags) { - if (drop_controller->can_drop(*drag, &disabled_hint_dummy)) { - return drop_controller->on_drop(&C, *drag); - } - } - - return false; - } - - static bool can_rename(const AbstractTreeViewItem &item) - { - const AbstractTreeView &tree_view = item.get_tree_view(); - return !tree_view.is_renaming() && item.supports_renaming(); - } -}; - } // namespace blender::ui - -/* ---------------------------------------------------------------------- */ -/* C-API */ - -using namespace blender::ui; - -bool UI_tree_view_item_is_active(const uiTreeViewItemHandle *item_handle) -{ - const AbstractTreeViewItem &item = reinterpret_cast(*item_handle); - return item.is_active(); -} - -bool UI_tree_view_item_matches(const uiTreeViewItemHandle *a_handle, - const uiTreeViewItemHandle *b_handle) -{ - const AbstractTreeViewItem &a = reinterpret_cast(*a_handle); - const AbstractTreeViewItem &b = reinterpret_cast(*b_handle); - return TreeViewItemAPIWrapper::matches(a, b); -} - -bool UI_tree_view_item_drag_start(bContext *C, uiTreeViewItemHandle *item_) -{ - const AbstractTreeViewItem &item = reinterpret_cast(*item_); - return TreeViewItemAPIWrapper::drag_start(*C, item); -} - -bool UI_tree_view_item_can_drop(const uiTreeViewItemHandle *item_, - const wmDrag *drag, - const char **r_disabled_hint) -{ - const AbstractTreeViewItem &item = reinterpret_cast(*item_); - return TreeViewItemAPIWrapper::can_drop(item, *drag, r_disabled_hint); -} - -char *UI_tree_view_item_drop_tooltip(const uiTreeViewItemHandle *item_, const wmDrag *drag) -{ - const AbstractTreeViewItem &item = reinterpret_cast(*item_); - - const std::string tooltip = TreeViewItemAPIWrapper::drop_tooltip(item, *drag); - return tooltip.empty() ? nullptr : BLI_strdup(tooltip.c_str()); -} - -bool UI_tree_view_item_drop_handle(bContext *C, - const uiTreeViewItemHandle *item_, - const ListBase *drags) -{ - const AbstractTreeViewItem &item = reinterpret_cast(*item_); - return TreeViewItemAPIWrapper::drop_handle(*C, item, *drags); -} - -bool UI_tree_view_item_can_rename(const uiTreeViewItemHandle *item_handle) -{ - const AbstractTreeViewItem &item = reinterpret_cast(*item_handle); - return TreeViewItemAPIWrapper::can_rename(item); -} - -void UI_tree_view_item_begin_rename(uiTreeViewItemHandle *item_handle) -{ - AbstractTreeViewItem &item = reinterpret_cast(*item_handle); - item.begin_renaming(); -} - -void UI_tree_view_item_context_menu_build(bContext *C, - const uiTreeViewItemHandle *item_handle, - uiLayout *column) -{ - const AbstractTreeViewItem &item = reinterpret_cast(*item_handle); - item.build_context_menu(*C, *column); -} diff --git a/source/blender/editors/space_file/asset_catalog_tree_view.cc b/source/blender/editors/space_file/asset_catalog_tree_view.cc index 87595ecdb88..1829f19bfd6 100644 --- a/source/blender/editors/space_file/asset_catalog_tree_view.cc +++ b/source/blender/editors/space_file/asset_catalog_tree_view.cc @@ -86,12 +86,12 @@ class AssetCatalogTreeViewItem : public ui::BasicTreeViewItem { bool rename(StringRefNull new_name) override; /** Add drag support for catalog items. */ - std::unique_ptr create_drag_controller() const override; + std::unique_ptr create_drag_controller() const override; /** Add dropping support for catalog items. */ - std::unique_ptr create_drop_controller() const override; + std::unique_ptr create_drop_controller() const override; }; -class AssetCatalogDragController : public ui::AbstractTreeViewItemDragController { +class AssetCatalogDragController : public ui::AbstractViewItemDragController { AssetCatalogTreeItem &catalog_item_; public: @@ -103,7 +103,7 @@ class AssetCatalogDragController : public ui::AbstractTreeViewItemDragController void on_drag_start() override; }; -class AssetCatalogDropController : public ui::AbstractTreeViewItemDropController { +class AssetCatalogDropController : public ui::AbstractViewItemDropController { AssetCatalogTreeItem &catalog_item_; public: @@ -142,7 +142,7 @@ class AssetCatalogTreeViewAllItem : public ui::BasicTreeViewItem { void build_row(uiLayout &row) override; - struct DropController : public ui::AbstractTreeViewItemDropController { + struct DropController : public ui::AbstractViewItemDropController { DropController(AssetCatalogTreeView &tree_view); bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override; @@ -150,13 +150,13 @@ class AssetCatalogTreeViewAllItem : public ui::BasicTreeViewItem { bool on_drop(struct bContext *C, const wmDrag &drag) override; }; - std::unique_ptr create_drop_controller() const override; + std::unique_ptr create_drop_controller() const override; }; class AssetCatalogTreeViewUnassignedItem : public ui::BasicTreeViewItem { using BasicTreeViewItem::BasicTreeViewItem; - struct DropController : public ui::AbstractTreeViewItemDropController { + struct DropController : public ui::AbstractViewItemDropController { DropController(AssetCatalogTreeView &tree_view); bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override; @@ -164,7 +164,7 @@ class AssetCatalogTreeViewUnassignedItem : public ui::BasicTreeViewItem { bool on_drop(struct bContext *C, const wmDrag &drag) override; }; - std::unique_ptr create_drop_controller() const override; + std::unique_ptr create_drop_controller() const override; }; /* ---------------------------------------------------------------------- */ @@ -272,11 +272,11 @@ void AssetCatalogTreeViewItem::build_row(uiLayout &row) return; } - uiButTreeRow *tree_row_but = tree_row_button(); + uiButViewItem *view_item_but = view_item_button(); PointerRNA *props; props = UI_but_extra_operator_icon_add( - (uiBut *)tree_row_but, "ASSET_OT_catalog_new", WM_OP_INVOKE_DEFAULT, ICON_ADD); + (uiBut *)view_item_but, "ASSET_OT_catalog_new", WM_OP_INVOKE_DEFAULT, ICON_ADD); RNA_string_set(props, "parent_path", catalog_item_.catalog_path().c_str()); } @@ -305,7 +305,7 @@ void AssetCatalogTreeViewItem::build_context_menu(bContext &C, uiLayout &column) 0, &props); RNA_string_set(&props, "catalog_id", catalog_id_str_buffer); - uiItemO(&column, "Rename", ICON_NONE, "UI_OT_tree_view_item_rename"); + uiItemO(&column, "Rename", ICON_NONE, "UI_OT_view_item_rename"); /* Doesn't actually exist right now, but could be defined in Python. Reason that this isn't done * in Python yet is that catalogs are not exposed in BPY, and we'd somehow pass the clicked on @@ -333,14 +333,14 @@ bool AssetCatalogTreeViewItem::rename(StringRefNull new_name) return true; } -std::unique_ptr AssetCatalogTreeViewItem:: +std::unique_ptr AssetCatalogTreeViewItem:: create_drop_controller() const { return std::make_unique( static_cast(get_tree_view()), catalog_item_); } -std::unique_ptr AssetCatalogTreeViewItem:: +std::unique_ptr AssetCatalogTreeViewItem:: create_drag_controller() const { return std::make_unique( @@ -351,7 +351,7 @@ std::unique_ptr AssetCatalogTreeViewItem AssetCatalogDropController::AssetCatalogDropController(AssetCatalogTreeView &tree_view, AssetCatalogTreeItem &catalog_item) - : ui::AbstractTreeViewItemDropController(tree_view), catalog_item_(catalog_item) + : ui::AbstractViewItemDropController(tree_view), catalog_item_(catalog_item) { } @@ -422,10 +422,10 @@ bool AssetCatalogDropController::on_drop(struct bContext *C, const wmDrag &drag) { if (drag.type == WM_DRAG_ASSET_CATALOG) { return drop_asset_catalog_into_catalog( - drag, tree_view(), catalog_item_.get_catalog_id()); + drag, get_view(), catalog_item_.get_catalog_id()); } return drop_assets_into_catalog(C, - tree_view(), + get_view(), drag, catalog_item_.get_catalog_id(), catalog_item_.get_simple_name()); @@ -512,14 +512,14 @@ bool AssetCatalogDropController::has_droppable_asset(const wmDrag &drag, ::AssetLibrary &AssetCatalogDropController::get_asset_library() const { - return *tree_view().asset_library_; + return *get_view().asset_library_; } /* ---------------------------------------------------------------------- */ AssetCatalogDragController::AssetCatalogDragController(AssetCatalogTreeView &tree_view, AssetCatalogTreeItem &catalog_item) - : ui::AbstractTreeViewItemDragController(tree_view), catalog_item_(catalog_item) + : ui::AbstractViewItemDragController(tree_view), catalog_item_(catalog_item) { } @@ -538,7 +538,7 @@ void *AssetCatalogDragController::create_drag_data() const void AssetCatalogDragController::on_drag_start() { - AssetCatalogTreeView &tree_view_ = tree_view(); + AssetCatalogTreeView &tree_view_ = get_view(); tree_view_.activate_catalog_by_id(catalog_item_.get_catalog_id()); } @@ -551,15 +551,15 @@ void AssetCatalogTreeViewAllItem::build_row(uiLayout &row) PointerRNA *props; UI_but_extra_operator_icon_add( - (uiBut *)tree_row_button(), "ASSET_OT_catalogs_save", WM_OP_INVOKE_DEFAULT, ICON_FILE_TICK); + (uiBut *)view_item_button(), "ASSET_OT_catalogs_save", WM_OP_INVOKE_DEFAULT, ICON_FILE_TICK); props = UI_but_extra_operator_icon_add( - (uiBut *)tree_row_button(), "ASSET_OT_catalog_new", WM_OP_INVOKE_DEFAULT, ICON_ADD); + (uiBut *)view_item_button(), "ASSET_OT_catalog_new", WM_OP_INVOKE_DEFAULT, ICON_ADD); /* No parent path to use the root level. */ RNA_string_set(props, "parent_path", nullptr); } -std::unique_ptr AssetCatalogTreeViewAllItem:: +std::unique_ptr AssetCatalogTreeViewAllItem:: create_drop_controller() const { return std::make_unique( @@ -567,7 +567,7 @@ std::unique_ptr AssetCatalogTreeViewAllI } AssetCatalogTreeViewAllItem::DropController::DropController(AssetCatalogTreeView &tree_view) - : ui::AbstractTreeViewItemDropController(tree_view) + : ui::AbstractViewItemDropController(tree_view) { } @@ -579,7 +579,7 @@ bool AssetCatalogTreeViewAllItem::DropController::can_drop(const wmDrag &drag, } const AssetCatalog *drag_catalog = AssetCatalogDropController::get_drag_catalog( - drag, *tree_view().asset_library_); + drag, *get_view().asset_library_); if (drag_catalog->path.parent() == "") { *r_disabled_hint = "Catalog is already placed at the highest level"; return false; @@ -592,7 +592,7 @@ std::string AssetCatalogTreeViewAllItem::DropController::drop_tooltip(const wmDr { BLI_assert(drag.type == WM_DRAG_ASSET_CATALOG); const AssetCatalog *drag_catalog = AssetCatalogDropController::get_drag_catalog( - drag, *tree_view().asset_library_); + drag, *get_view().asset_library_); return std::string(TIP_("Move Catalog")) + " '" + drag_catalog->path.name() + "' " + TIP_("to the top level of the tree"); @@ -604,14 +604,14 @@ bool AssetCatalogTreeViewAllItem::DropController::on_drop(struct bContext *UNUSE BLI_assert(drag.type == WM_DRAG_ASSET_CATALOG); return AssetCatalogDropController::drop_asset_catalog_into_catalog( drag, - tree_view(), + get_view(), /* No value to drop into the root level. */ std::nullopt); } /* ---------------------------------------------------------------------- */ -std::unique_ptr AssetCatalogTreeViewUnassignedItem:: +std::unique_ptr AssetCatalogTreeViewUnassignedItem:: create_drop_controller() const { return std::make_unique( @@ -619,7 +619,7 @@ std::unique_ptr AssetCatalogTreeViewUnas } AssetCatalogTreeViewUnassignedItem::DropController::DropController(AssetCatalogTreeView &tree_view) - : ui::AbstractTreeViewItemDropController(tree_view) + : ui::AbstractViewItemDropController(tree_view) { } @@ -647,7 +647,7 @@ bool AssetCatalogTreeViewUnassignedItem::DropController::on_drop(struct bContext { /* Assign to nil catalog ID. */ return AssetCatalogDropController::drop_assets_into_catalog( - C, tree_view(), drag, CatalogID{}); + C, get_view(), drag, CatalogID{}); } } // namespace blender::ed::asset_browser diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc b/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc index aa9b867264a..146b6091bde 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc @@ -145,7 +145,7 @@ void GeometryDataSetTreeViewItem::build_row(uiLayout &row) * to the right side of the number, which it didn't have with the button. */ char element_count[7]; BLI_str_format_decimal_unit(element_count, *count); - UI_but_hint_drawstr_set((uiBut *)this->tree_row_button(), element_count); + UI_but_hint_drawstr_set((uiBut *)this->view_item_button(), element_count); } } -- cgit v1.2.3 From 2e3fb581280d637d92eb639b90e82f71e7f37ca3 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Tue, 19 Jul 2022 16:30:46 +0200 Subject: Cleanup: Apply clang-format --- source/blender/blenkernel/intern/camera.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/blender/blenkernel/intern/camera.c b/source/blender/blenkernel/intern/camera.c index 2e6439f7e1a..9aea3b2768f 100644 --- a/source/blender/blenkernel/intern/camera.c +++ b/source/blender/blenkernel/intern/camera.c @@ -25,8 +25,8 @@ #include "BLI_string.h" #include "BLI_utildefines.h" -#include "BKE_anim_data.h" #include "BKE_action.h" +#include "BKE_anim_data.h" #include "BKE_camera.h" #include "BKE_idtype.h" #include "BKE_layer.h" @@ -222,7 +222,8 @@ float BKE_camera_object_dof_distance(const Object *ob) if (cam->dof.focus_object) { float view_dir[3], dof_dir[3]; normalize_v3_v3(view_dir, ob->obmat[2]); - bPoseChannel *pchan = BKE_pose_channel_find_name(cam->dof.focus_object->pose, cam->dof.focus_subtarget); + bPoseChannel *pchan = BKE_pose_channel_find_name(cam->dof.focus_object->pose, + cam->dof.focus_subtarget); if (pchan) { float posemat[4][4]; mul_m4_m4m4(posemat, cam->dof.focus_object->obmat, pchan->pose_mat); -- cgit v1.2.3 From ad5e3d30a2d2d21771140c9d877e1d7dedd560f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cle=CC=81ment=20Foucault?= Date: Tue, 19 Jul 2022 16:35:53 +0200 Subject: Nishita sky: Increase elevation range MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Nishita sky texture currently only allows moving the sun to the zenith. The problem is if you want to animate the passing of a full night-day-night cycle. Currently it's not easy to do due to this limitation. The patch makes it so users can easily animate the sun moving from sunrise to sunset by expanding the max sun elevation to go 360° instead of 90°. Reviewed By: fclem Differential Revision: https://developer.blender.org/D13724 --- source/blender/makesrna/intern/rna_nodetree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 386ef3f74a3..80217decb13 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -5282,7 +5282,7 @@ static void def_sh_tex_sky(StructRNA *srna) prop = RNA_def_property(srna, "sun_elevation", PROP_FLOAT, PROP_ANGLE); RNA_def_property_ui_text(prop, "Sun Elevation", "Sun angle from horizon"); - RNA_def_property_range(prop, -M_PI_2, M_PI_2); + RNA_def_property_ui_range(prop, -M_PI, M_PI, 1, 2); RNA_def_property_float_default(prop, M_PI_2); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); -- cgit v1.2.3 From 3370c1a8a7b24cf542e368e7ae34dd0064a2d963 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 19 Jul 2022 09:54:20 -0500 Subject: Cleanup: Add comment for geometry nodes lazyness --- source/blender/blenkernel/BKE_node.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index b7962ade312..ea43fba1a85 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -314,6 +314,10 @@ typedef struct bNodeType { /* Execute a geometry node. */ NodeGeometryExecFunction geometry_node_execute; + /** + * If true, the geometry nodes evaluator can call the execute function multiple times to improve + * performance by specifying required data in one call and using it for calculations in another. + */ bool geometry_node_execute_supports_laziness; /* Declares which sockets the node has. */ -- cgit v1.2.3 From 6bba4d864e13e37b0c95efbf5dab4edb75d110a3 Mon Sep 17 00:00:00 2001 From: Jason Fielder Date: Tue, 19 Jul 2022 16:59:42 +0200 Subject: Metal: MTLQueryPool implementation adding support for occlusion queries. When a query begins, the current visibility result buffer needs to be associated with the currently active Render Pass. The MTLContext and MTLCommandBuffer are responsible for ensuring new render pass objects are created if the visibility state changes. Authored by Apple: Michael Parkin-White Ref T96261 Reviewed By: fclem Maniphest Tasks: T96261 Differential Revision: https://developer.blender.org/D15356 --- source/blender/gpu/CMakeLists.txt | 2 + source/blender/gpu/metal/mtl_backend.mm | 4 +- source/blender/gpu/metal/mtl_command_buffer.mm | 6 ++ source/blender/gpu/metal/mtl_context.hh | 18 ++++ source/blender/gpu/metal/mtl_context.mm | 42 ++++++++- source/blender/gpu/metal/mtl_query.hh | 39 ++++++++ source/blender/gpu/metal/mtl_query.mm | 120 +++++++++++++++++++++++++ 7 files changed, 228 insertions(+), 3 deletions(-) create mode 100644 source/blender/gpu/metal/mtl_query.hh create mode 100644 source/blender/gpu/metal/mtl_query.mm diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index 774f2a0f312..5a1816c5829 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -195,6 +195,7 @@ set(METAL_SRC metal/mtl_debug.mm metal/mtl_framebuffer.mm metal/mtl_memory.mm + metal/mtl_query.mm metal/mtl_state.mm metal/mtl_texture.mm metal/mtl_texture_util.mm @@ -206,6 +207,7 @@ set(METAL_SRC metal/mtl_debug.hh metal/mtl_framebuffer.hh metal/mtl_memory.hh + metal/mtl_query.hh metal/mtl_state.hh metal/mtl_texture.hh ) diff --git a/source/blender/gpu/metal/mtl_backend.mm b/source/blender/gpu/metal/mtl_backend.mm index d2524cc16e3..af1fd6a06bb 100644 --- a/source/blender/gpu/metal/mtl_backend.mm +++ b/source/blender/gpu/metal/mtl_backend.mm @@ -10,6 +10,7 @@ #include "mtl_backend.hh" #include "mtl_context.hh" #include "mtl_framebuffer.hh" +#include "mtl_query.hh" #include "gpu_capabilities_private.hh" #include "gpu_platform_private.hh" @@ -64,8 +65,7 @@ IndexBuf *MTLBackend::indexbuf_alloc() QueryPool *MTLBackend::querypool_alloc() { - /* TODO(Metal): Implement MTLQueryPool. */ - return nullptr; + return new MTLQueryPool(); }; Shader *MTLBackend::shader_alloc(const char *name) diff --git a/source/blender/gpu/metal/mtl_command_buffer.mm b/source/blender/gpu/metal/mtl_command_buffer.mm index 7b6066b3d86..9a9a2d55103 100644 --- a/source/blender/gpu/metal/mtl_command_buffer.mm +++ b/source/blender/gpu/metal/mtl_command_buffer.mm @@ -308,6 +308,12 @@ id MTLCommandBufferManager::ensure_begin_render_command active_pass_descriptor_ = active_frame_buffer_->bake_render_pass_descriptor( is_rebind && (!active_frame_buffer_->get_pending_clear())); + /* Determine if there is a visibility buffer assigned to the context. */ + gpu::MTLBuffer *visibility_buffer = context_.get_visibility_buffer(); + this->active_pass_descriptor_.visibilityResultBuffer = + (visibility_buffer) ? visibility_buffer->get_metal_buffer() : nil; + context_.clear_visibility_dirty(); + /* Ensure we have already cleaned up our previous render command encoder. */ BLI_assert(active_render_command_encoder_ == nil); diff --git a/source/blender/gpu/metal/mtl_context.hh b/source/blender/gpu/metal/mtl_context.hh index 4b87b994a3d..caabd59a1b5 100644 --- a/source/blender/gpu/metal/mtl_context.hh +++ b/source/blender/gpu/metal/mtl_context.hh @@ -3,6 +3,8 @@ /** \file * \ingroup gpu */ +#pragma once + #include "MEM_guardedalloc.h" #include "gpu_context_private.hh" @@ -588,6 +590,10 @@ class MTLContext : public Context { bool is_inside_frame_ = false; uint current_frame_index_; + /* Visibility buffer for MTLQuery results. */ + gpu::MTLBuffer *visibility_buffer_ = nullptr; + bool visibility_is_dirty_ = false; + public: /* Shaders and Pipeline state. */ MTLContextGlobalShaderPipelineState pipeline_state; @@ -660,6 +666,18 @@ class MTLContext : public Context { void set_scissor(int scissor_x, int scissor_y, int scissor_width, int scissor_height); void set_scissor_enabled(bool scissor_enabled); + /* Visibility buffer control. */ + void set_visibility_buffer(gpu::MTLBuffer *buffer); + gpu::MTLBuffer *get_visibility_buffer() const; + + /* Flag whether the visibility buffer for query results + * has changed. This requires a new RenderPass in order + * to update.*/ + bool is_visibility_dirty() const; + + /* Reset dirty flag state for visibility buffer. */ + void clear_visibility_dirty(); + /* Texture utilities. */ MTLContextTextureUtils &get_texture_utils() { diff --git a/source/blender/gpu/metal/mtl_context.mm b/source/blender/gpu/metal/mtl_context.mm index 2ab2e20158a..26cfe6632ef 100644 --- a/source/blender/gpu/metal/mtl_context.mm +++ b/source/blender/gpu/metal/mtl_context.mm @@ -188,7 +188,8 @@ id MTLContext::ensure_begin_render_pass() * framebuffer state has been modified (is_dirty). */ if (!this->main_command_buffer.is_inside_render_pass() || this->active_fb != this->main_command_buffer.get_active_framebuffer() || - this->main_command_buffer.get_active_framebuffer()->get_dirty()) { + this->main_command_buffer.get_active_framebuffer()->get_dirty() || + this->is_visibility_dirty()) { /* Validate bound framebuffer before beginning render pass. */ if (!static_cast(this->active_fb)->validate_render_pass()) { @@ -371,6 +372,45 @@ void MTLContext::set_scissor_enabled(bool scissor_enabled) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Visibility buffer control for MTLQueryPool. + * \{ */ + +void MTLContext::set_visibility_buffer(gpu::MTLBuffer *buffer) +{ + /* Flag visibility buffer as dirty if the buffer being used for visibility has changed -- + * This is required by the render pass, and we will break the pass if the results destination + * buffer is modified. */ + if (buffer) { + visibility_is_dirty_ = (buffer != visibility_buffer_) || visibility_is_dirty_; + visibility_buffer_ = buffer; + visibility_buffer_->debug_ensure_used(); + } + else { + /* If buffer is null, reset visibility state, mark dirty to break render pass if results are no + * longer needed. */ + visibility_is_dirty_ = (visibility_buffer_ != nullptr) || visibility_is_dirty_; + visibility_buffer_ = nullptr; + } +} + +gpu::MTLBuffer *MTLContext::get_visibility_buffer() const +{ + return visibility_buffer_; +} + +void MTLContext::clear_visibility_dirty() +{ + visibility_is_dirty_ = false; +} + +bool MTLContext::is_visibility_dirty() const +{ + return visibility_is_dirty_; +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Texture State Management * \{ */ diff --git a/source/blender/gpu/metal/mtl_query.hh b/source/blender/gpu/metal/mtl_query.hh new file mode 100644 index 00000000000..c8ee6998a68 --- /dev/null +++ b/source/blender/gpu/metal/mtl_query.hh @@ -0,0 +1,39 @@ +/** \file + * \ingroup gpu + */ + +#pragma once + +#include "BLI_vector.hh" + +#include "gpu_query.hh" +#include "mtl_context.hh" + +namespace blender::gpu { + +class MTLQueryPool : public QueryPool { + private: + /** Number of queries that have been issued since last initialization. + * Should be equal to query_ids_.size(). */ + uint32_t query_issued_; + /** Type of this query pool. */ + GPUQueryType type_; + /** Can only be initialized once. */ + bool initialized_ = false; + MTLVisibilityResultMode mtl_type_; + Vector buffer_; + + void allocate_buffer(); + + public: + MTLQueryPool(); + ~MTLQueryPool(); + + void init(GPUQueryType type) override; + + void begin_query() override; + void end_query() override; + + void get_occlusion_result(MutableSpan r_values) override; +}; +} // namespace blender::gpu diff --git a/source/blender/gpu/metal/mtl_query.mm b/source/blender/gpu/metal/mtl_query.mm new file mode 100644 index 00000000000..22f99f1718c --- /dev/null +++ b/source/blender/gpu/metal/mtl_query.mm @@ -0,0 +1,120 @@ +/** \file + * \ingroup gpu + */ + +#include "mtl_query.hh" + +namespace blender::gpu { + +static const size_t VISIBILITY_COUNT_PER_BUFFER = 512; +/* defined in the documentation but not queryable programmatically: + * https://developer.apple.com/documentation/metal/mtlvisibilityresultmode/mtlvisibilityresultmodeboolean?language=objc + */ +static const size_t VISIBILITY_RESULT_SIZE_IN_BYTES = 8; + +MTLQueryPool::MTLQueryPool() +{ + allocate_buffer(); +} +MTLQueryPool::~MTLQueryPool() +{ + for (gpu::MTLBuffer *buf : buffer_) { + BLI_assert(buf); + buf->free(); + } +} + +void MTLQueryPool::allocate_buffer() +{ + /* Allocate Metal buffer for visibility results. */ + size_t buffer_size_in_bytes = VISIBILITY_COUNT_PER_BUFFER * VISIBILITY_RESULT_SIZE_IN_BYTES; + gpu::MTLBuffer *buffer = MTLContext::get_global_memory_manager().allocate_buffer( + buffer_size_in_bytes, true); + BLI_assert(buffer); + buffer_.append(buffer); +} + +static inline MTLVisibilityResultMode to_mtl_type(GPUQueryType type) +{ + if (type == GPU_QUERY_OCCLUSION) { + return MTLVisibilityResultModeBoolean; + } + BLI_assert(0); + return MTLVisibilityResultModeBoolean; +} + +void MTLQueryPool::init(GPUQueryType type) +{ + BLI_assert(initialized_ == false); + initialized_ = true; + type_ = type; + mtl_type_ = to_mtl_type(type); + query_issued_ = 0; +} + +void MTLQueryPool::begin_query() +{ + MTLContext *ctx = reinterpret_cast(GPU_context_active_get()); + + /* Ensure our allocated buffer pool has enough space for the current queries. */ + int query_id = query_issued_; + int requested_buffer = query_id / VISIBILITY_COUNT_PER_BUFFER; + if (requested_buffer >= buffer_.size()) { + allocate_buffer(); + } + + BLI_assert(requested_buffer < buffer_.size()); + gpu::MTLBuffer *buffer = buffer_[requested_buffer]; + + /* Ensure visibility buffer is set on the context. If visibility buffer changes, + * we need to begin a new render pass with an updated reference in the + * MTLRenderPassDescriptor. */ + ctx->set_visibility_buffer(buffer); + + ctx->ensure_begin_render_pass(); + id rec = ctx->main_command_buffer.get_active_render_command_encoder(); + [rec setVisibilityResultMode:mtl_type_ + offset:(query_id % VISIBILITY_COUNT_PER_BUFFER) * + VISIBILITY_RESULT_SIZE_IN_BYTES]; + query_issued_ += 1; +} + +void MTLQueryPool::end_query() +{ + MTLContext *ctx = reinterpret_cast(GPU_context_active_get()); + + id rec = ctx->main_command_buffer.get_active_render_command_encoder(); + [rec setVisibilityResultMode:MTLVisibilityResultModeDisabled offset:0]; +} + +void MTLQueryPool::get_occlusion_result(MutableSpan r_values) +{ + MTLContext *ctx = reinterpret_cast(GPU_context_active_get()); + + /* Create a blit encoder to synchronize the query buffer results between + * GPU and CPU when not using shared-memory. */ + if ([ctx->device hasUnifiedMemory] == false) { + id blit_encoder = ctx->main_command_buffer.ensure_begin_blit_encoder(); + BLI_assert(blit_encoder); + for (gpu::MTLBuffer *buf : buffer_) { + [blit_encoder synchronizeResource:buf->get_metal_buffer()]; + } + BLI_assert(ctx->get_inside_frame()); + } + + /* Wait for GPU operatiosn to complete and for query buffer contents + * to be synchronised back to host memory. */ + GPU_finish(); + + /* Iterate through all possible visibility buffers and copy results into provided + * container. */ + for (const int i : IndexRange(query_issued_)) { + int requested_buffer = i / VISIBILITY_COUNT_PER_BUFFER; + const uint64_t *queries = static_cast( + buffer_[requested_buffer]->get_host_ptr()); + r_values[i] = static_cast(queries[i % VISIBILITY_COUNT_PER_BUFFER]); + } + ctx->set_visibility_buffer(nullptr); +} + +} // namespace blender::gpu -- cgit v1.2.3 From 9835d5e58ba28ff69abec74c1cb3f3959ff9451a Mon Sep 17 00:00:00 2001 From: Jason Fielder Date: Tue, 19 Jul 2022 17:11:03 +0200 Subject: Metal: MTLUniformBuffer module implementation Initial implementation. Authored by Apple: Michael Parkin-White Ref T96261 Reviewed By: fclem Differential Revision: https://developer.blender.org/D15357 --- source/blender/gpu/CMakeLists.txt | 2 + source/blender/gpu/metal/mtl_backend.mm | 4 +- source/blender/gpu/metal/mtl_context.hh | 6 + source/blender/gpu/metal/mtl_memory.hh | 18 +-- source/blender/gpu/metal/mtl_memory.mm | 48 +++++--- source/blender/gpu/metal/mtl_uniform_buffer.hh | 48 ++++++++ source/blender/gpu/metal/mtl_uniform_buffer.mm | 160 +++++++++++++++++++++++++ 7 files changed, 260 insertions(+), 26 deletions(-) create mode 100644 source/blender/gpu/metal/mtl_uniform_buffer.hh create mode 100644 source/blender/gpu/metal/mtl_uniform_buffer.mm diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index 5a1816c5829..5e97909a2b8 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -199,6 +199,7 @@ set(METAL_SRC metal/mtl_state.mm metal/mtl_texture.mm metal/mtl_texture_util.mm + metal/mtl_uniform_buffer.mm metal/mtl_backend.hh metal/mtl_capabilities.hh @@ -210,6 +211,7 @@ set(METAL_SRC metal/mtl_query.hh metal/mtl_state.hh metal/mtl_texture.hh + metal/mtl_uniform_buffer.hh ) # Select Backend source based on availability diff --git a/source/blender/gpu/metal/mtl_backend.mm b/source/blender/gpu/metal/mtl_backend.mm index af1fd6a06bb..3328855bbf8 100644 --- a/source/blender/gpu/metal/mtl_backend.mm +++ b/source/blender/gpu/metal/mtl_backend.mm @@ -10,6 +10,7 @@ #include "mtl_backend.hh" #include "mtl_context.hh" #include "mtl_framebuffer.hh" +#include "mtl_uniform_buffer.hh" #include "mtl_query.hh" #include "gpu_capabilities_private.hh" @@ -81,8 +82,7 @@ Texture *MTLBackend::texture_alloc(const char *name) UniformBuf *MTLBackend::uniformbuf_alloc(int size, const char *name) { - /* TODO(Metal): Implement MTLUniformBuf. */ - return nullptr; + return new MTLUniformBuf(size, name); }; StorageBuf *MTLBackend::storagebuf_alloc(int size, GPUUsageType usage, const char *name) diff --git a/source/blender/gpu/metal/mtl_context.hh b/source/blender/gpu/metal/mtl_context.hh index caabd59a1b5..0db87bf5da5 100644 --- a/source/blender/gpu/metal/mtl_context.hh +++ b/source/blender/gpu/metal/mtl_context.hh @@ -3,6 +3,7 @@ /** \file * \ingroup gpu */ + #pragma once #include "MEM_guardedalloc.h" @@ -625,6 +626,11 @@ class MTLContext : public Context { void memory_statistics_get(int *total_mem, int *free_mem) override; + static MTLContext *get() + { + return static_cast(Context::get()); + } + void debug_group_begin(const char *name, int index) override; void debug_group_end() override; diff --git a/source/blender/gpu/metal/mtl_memory.hh b/source/blender/gpu/metal/mtl_memory.hh index daa049e78af..dc5417dc11a 100644 --- a/source/blender/gpu/metal/mtl_memory.hh +++ b/source/blender/gpu/metal/mtl_memory.hh @@ -78,8 +78,10 @@ * Usage: * MTLContext::get_global_memory_manager(); - static routine to fetch global memory manager. * - * gpu::MTLBuffer *allocate_buffer(size, is_cpu_visibile, bytes=nullptr) - * gpu::MTLBuffer *allocate_buffer_aligned(size, alignment, is_cpu_visibile, bytes=nullptr) + * gpu::MTLBuffer *allocate(size, is_cpu_visibile) + * gpu::MTLBuffer *allocate_aligned(size, alignment, is_cpu_visibile) + * gpu::MTLBuffer *allocate_with_data(size, is_cpu_visibile, data_ptr) + * gpu::MTLBuffer *allocate_aligned_with_data(size, alignment, is_cpu_visibile, data_ptr) */ /* Debug memory statistics: Disabled by Macro rather than guarded for @@ -389,11 +391,13 @@ class MTLBufferPool { void init(id device); ~MTLBufferPool(); - gpu::MTLBuffer *allocate_buffer(uint64_t size, bool cpu_visible, const void *bytes = nullptr); - gpu::MTLBuffer *allocate_buffer_aligned(uint64_t size, - uint alignment, - bool cpu_visible, - const void *bytes = nullptr); + gpu::MTLBuffer *allocate(uint64_t size, bool cpu_visible); + gpu::MTLBuffer *allocate_aligned(uint64_t size, uint alignment, bool cpu_visible); + gpu::MTLBuffer *allocate_with_data(uint64_t size, bool cpu_visible, const void *data = nullptr); + gpu::MTLBuffer *allocate_aligned_with_data(uint64_t size, + uint alignment, + bool cpu_visible, + const void *data = nullptr); bool free_buffer(gpu::MTLBuffer *buffer); /* Flush MTLSafeFreeList buffers, for completed lists in `completed_safelist_queue_`, diff --git a/source/blender/gpu/metal/mtl_memory.mm b/source/blender/gpu/metal/mtl_memory.mm index e5db32ed1b1..48e27dd2bb6 100644 --- a/source/blender/gpu/metal/mtl_memory.mm +++ b/source/blender/gpu/metal/mtl_memory.mm @@ -57,17 +57,23 @@ void MTLBufferPool::free() buffer_pools_.clear(); } -gpu::MTLBuffer *MTLBufferPool::allocate_buffer(uint64_t size, bool cpu_visible, const void *bytes) +gpu::MTLBuffer *MTLBufferPool::allocate(uint64_t size, bool cpu_visible) { /* Allocate buffer with default HW-compatible alignment of 256 bytes. * See https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf for more. */ - return this->allocate_buffer_aligned(size, 256, cpu_visible, bytes); + return this->allocate_aligned(size, 256, cpu_visible); } -gpu::MTLBuffer *MTLBufferPool::allocate_buffer_aligned(uint64_t size, - uint alignment, - bool cpu_visible, - const void *bytes) +gpu::MTLBuffer *MTLBufferPool::allocate_with_data(uint64_t size, + bool cpu_visible, + const void *data) +{ + /* Allocate buffer with default HW-compatible alignemnt of 256 bytes. + * See https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf for more. */ + return this->allocate_aligned_with_data(size, 256, cpu_visible, data); +} + +gpu::MTLBuffer *MTLBufferPool::allocate_aligned(uint64_t size, uint alignment, bool cpu_visible) { /* Check not required. Main GPU module usage considered thread-safe. */ // BLI_assert(BLI_thread_is_main()); @@ -153,15 +159,6 @@ gpu::MTLBuffer *MTLBufferPool::allocate_buffer_aligned(uint64_t size, /* Flag buffer as actively in-use. */ new_buffer->flag_in_use(true); - /* Upload initial data if provided -- Size based on original size param, not aligned size*/ - if (bytes) { - BLI_assert(!(options & MTLResourceStorageModePrivate)); - BLI_assert(size <= aligned_alloc_size); - BLI_assert(size <= [new_buffer->get_metal_buffer() length]); - memcpy(new_buffer->get_host_ptr(), bytes, size); - new_buffer->flush_range(0, size); - } - #if MTL_DEBUG_MEMORY_STATISTICS == 1 this->per_frame_allocation_count++; #endif @@ -169,6 +166,23 @@ gpu::MTLBuffer *MTLBufferPool::allocate_buffer_aligned(uint64_t size, return new_buffer; } +gpu::MTLBuffer *MTLBufferPool::allocate_aligned_with_data(uint64_t size, + uint alignment, + bool cpu_visible, + const void *data) +{ + gpu::MTLBuffer *buf = this->allocate_aligned(size, 256, cpu_visible); + + /* Upload initial data. */ + BLI_assert(data != nullptr); + BLI_assert(!(buf->get_resource_options() & MTLResourceStorageModePrivate)); + BLI_assert(size <= buf->get_size()); + BLI_assert(size <= [buf->get_metal_buffer() length]); + memcpy(buf->get_host_ptr(), data, size); + buf->flush_range(0, size); + return buf; +} + bool MTLBufferPool::free_buffer(gpu::MTLBuffer *buffer) { /* Ensure buffer is flagged as in-use. I.e. has not already been returned to memory pools. */ @@ -356,7 +370,7 @@ void MTLBufferPool::insert_buffer_into_pool(MTLResourceOptions options, gpu::MTL #if MTL_DEBUG_MEMORY_STATISTICS == 1 /* Debug statistics. */ - allocations_in_pool_ += buffer->size; + allocations_in_pool_ += buffer->get_size(); buffers_in_pool_++; #endif } @@ -413,7 +427,7 @@ void MTLSafeFreeList::decrement_reference() { lock_.lock(); BLI_assert(in_free_queue_ == false); - int ref_count = reference_count_--; + int ref_count = --reference_count_; if (ref_count == 0) { MTLContext::get_global_memory_manager().push_completed_safe_list(this); diff --git a/source/blender/gpu/metal/mtl_uniform_buffer.hh b/source/blender/gpu/metal/mtl_uniform_buffer.hh new file mode 100644 index 00000000000..351194c2ff9 --- /dev/null +++ b/source/blender/gpu/metal/mtl_uniform_buffer.hh @@ -0,0 +1,48 @@ +/** \file + * \ingroup gpu + */ + +#pragma once + +#include "MEM_guardedalloc.h" +#include "gpu_uniform_buffer_private.hh" + +#include "mtl_context.hh" + +namespace blender::gpu { + +/** + * Implementation of Uniform Buffers using Metal. + **/ +class MTLUniformBuf : public UniformBuf { + private: + /* Allocation Handle. */ + gpu::MTLBuffer *metal_buffer_ = nullptr; + + /* Whether buffer has contents, if false, no GPU buffer will + * have yet been allocated. */ + bool has_data_ = false; + + /* Bindstate tracking. */ + int bind_slot_ = -1; + MTLContext *bound_ctx_ = nullptr; + + public: + MTLUniformBuf(size_t size, const char *name); + ~MTLUniformBuf(); + + void update(const void *data) override; + void bind(int slot) override; + void unbind() override; + + id get_metal_buffer(int *r_offset); + int get_size(); + const char *get_name() + { + return name_; + } + + MEM_CXX_CLASS_ALLOC_FUNCS("MTLUniformBuf"); +}; + +} // namespace blender::gpu diff --git a/source/blender/gpu/metal/mtl_uniform_buffer.mm b/source/blender/gpu/metal/mtl_uniform_buffer.mm new file mode 100644 index 00000000000..e45ae76c921 --- /dev/null +++ b/source/blender/gpu/metal/mtl_uniform_buffer.mm @@ -0,0 +1,160 @@ +/** \file + * \ingroup gpu + */ + +#include "BKE_global.h" + +#include "BLI_string.h" + +#include "gpu_backend.hh" +#include "gpu_context_private.hh" + +#include "mtl_backend.hh" +#include "mtl_context.hh" +#include "mtl_debug.hh" +#include "mtl_uniform_buffer.hh" + +namespace blender::gpu { + +MTLUniformBuf::MTLUniformBuf(size_t size, const char *name) : UniformBuf(size, name) +{ +} + +MTLUniformBuf::~MTLUniformBuf() +{ + if (metal_buffer_ != nullptr) { + metal_buffer_->free(); + metal_buffer_ = nullptr; + } + has_data_ = false; + + /* Ensure UBO is not bound to active CTX. + * UBO bindings are reset upon Context-switch so we do not need + * to check deactivated context's. */ + MTLContext *ctx = MTLContext::get(); + if (ctx) { + for (int i = 0; i < MTL_MAX_UNIFORM_BUFFER_BINDINGS; i++) { + MTLUniformBufferBinding &slot = ctx->pipeline_state.ubo_bindings[i]; + if (slot.bound && slot.ubo == this) { + slot.bound = false; + slot.ubo = nullptr; + } + } + } +} + +void MTLUniformBuf::update(const void *data) +{ + BLI_assert(this); + BLI_assert(size_in_bytes_ > 0); + + /* Free existing allocation. + * The previous UBO resource will be tracked by the memory manager, + * in case dependent GPU work is still executing. */ + if (metal_buffer_ != nullptr) { + metal_buffer_->free(); + metal_buffer_ = nullptr; + } + + /* Allocate MTL buffer */ + MTLContext *ctx = static_cast(unwrap(GPU_context_active_get())); + BLI_assert(ctx); + BLI_assert(ctx->device); + UNUSED_VARS_NDEBUG(ctx); + + if (data != nullptr) { + metal_buffer_ = MTLContext::get_global_memory_manager().allocate_with_data( + size_in_bytes_, true, data); + has_data_ = true; + + metal_buffer_->set_label(@"Uniform Buffer"); + BLI_assert(metal_buffer_ != nullptr); + BLI_assert(metal_buffer_->get_metal_buffer() != nil); + } + else { + /* If data is not yet present, no buffer will be allocated and MTLContext will use an empty + * null buffer, containing zeroes, if the UBO is bound. */ + metal_buffer_ = nullptr; + has_data_ = false; + } +} + +void MTLUniformBuf::bind(int slot) +{ + if (slot < 0) { + MTL_LOG_WARNING("Failed to bind UBO %p. uniform location %d invalid.\n", this, slot); + return; + } + + BLI_assert(slot < MTL_MAX_UNIFORM_BUFFER_BINDINGS); + + /* Bind current UBO to active context. */ + MTLContext *ctx = MTLContext::get(); + BLI_assert(ctx); + + MTLUniformBufferBinding &ctx_ubo_bind_slot = ctx->pipeline_state.ubo_bindings[slot]; + ctx_ubo_bind_slot.ubo = this; + ctx_ubo_bind_slot.bound = true; + + bind_slot_ = slot; + bound_ctx_ = ctx; + + /* Check if we have any deferred data to upload. */ + if (data_ != nullptr) { + this->update(data_); + MEM_SAFE_FREE(data_); + } + + /* Ensure there is atleast an empty dummy buffer. */ + if (metal_buffer_ == nullptr) { + this->update(nullptr); + } +} + +void MTLUniformBuf::unbind() +{ + /* Unbind in debug mode to validate missing binds. + * Otherwise, only perform a full unbind upon destruction + * to ensure no lingering references. */ +#ifndef NDEBUG + if (true) { +#else + if (G.debug & G_DEBUG_GPU) { +#endif + if (bound_ctx_ != nullptr && bind_slot_ > -1) { + MTLUniformBufferBinding &ctx_ubo_bind_slot = + bound_ctx_->pipeline_state.ubo_bindings[bind_slot_]; + if (ctx_ubo_bind_slot.bound && ctx_ubo_bind_slot.ubo == this) { + ctx_ubo_bind_slot.bound = false; + ctx_ubo_bind_slot.ubo = nullptr; + } + } + } + + /* Reset bind index. */ + bind_slot_ = -1; + bound_ctx_ = nullptr; +} + +id MTLUniformBuf::get_metal_buffer(int *r_offset) +{ + BLI_assert(this); + *r_offset = 0; + if (metal_buffer_ != nullptr && has_data_) { + *r_offset = 0; + metal_buffer_->debug_ensure_used(); + return metal_buffer_->get_metal_buffer(); + } + else { + *r_offset = 0; + return nil; + } +} + +int MTLUniformBuf::get_size() +{ + BLI_assert(this); + return size_in_bytes_; +} + +} // blender::gpu -- cgit v1.2.3 From 9246ff373a19df24255d924979c78793f97b2834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cle=CC=81ment=20Foucault?= Date: Tue, 19 Jul 2022 17:14:39 +0200 Subject: Metal: Add license header to new files --- source/blender/gpu/metal/mtl_query.hh | 2 ++ source/blender/gpu/metal/mtl_query.mm | 2 ++ source/blender/gpu/metal/mtl_uniform_buffer.hh | 2 ++ source/blender/gpu/metal/mtl_uniform_buffer.mm | 2 ++ 4 files changed, 8 insertions(+) diff --git a/source/blender/gpu/metal/mtl_query.hh b/source/blender/gpu/metal/mtl_query.hh index c8ee6998a68..c1ec9a2a0f5 100644 --- a/source/blender/gpu/metal/mtl_query.hh +++ b/source/blender/gpu/metal/mtl_query.hh @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + /** \file * \ingroup gpu */ diff --git a/source/blender/gpu/metal/mtl_query.mm b/source/blender/gpu/metal/mtl_query.mm index 22f99f1718c..33ae8f554c3 100644 --- a/source/blender/gpu/metal/mtl_query.mm +++ b/source/blender/gpu/metal/mtl_query.mm @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + /** \file * \ingroup gpu */ diff --git a/source/blender/gpu/metal/mtl_uniform_buffer.hh b/source/blender/gpu/metal/mtl_uniform_buffer.hh index 351194c2ff9..722d819cf96 100644 --- a/source/blender/gpu/metal/mtl_uniform_buffer.hh +++ b/source/blender/gpu/metal/mtl_uniform_buffer.hh @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + /** \file * \ingroup gpu */ diff --git a/source/blender/gpu/metal/mtl_uniform_buffer.mm b/source/blender/gpu/metal/mtl_uniform_buffer.mm index e45ae76c921..3415c41f2cc 100644 --- a/source/blender/gpu/metal/mtl_uniform_buffer.mm +++ b/source/blender/gpu/metal/mtl_uniform_buffer.mm @@ -1,3 +1,5 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + /** \file * \ingroup gpu */ -- cgit v1.2.3 From 6a1ab4747b7758017721cb191046a9db800f7894 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 19 Jul 2022 10:14:46 -0500 Subject: Geometry Nodes: Copy parameters when copying a curves data-block Previously, things like materials, symmetry, and selection options stored on `Curves` weren't copied to the result in nodes like the subdivide and resample nodes. Now they are, which fixes some unexpected behavior and allows visualization of the sculpt mode selection. In the realize instances and join nodes the behavior is the same as for meshes, the parameters are taken from the first (top) input. I also refactored some functions to return a `CurvesGeometry` by-value, which makes it the responsibility of the node to copy the parameters. That should make the algorithms more reusable in other situations. Differential Revision: https://developer.blender.org/D15408 --- source/blender/blenkernel/BKE_curves.hh | 6 ++ source/blender/blenkernel/BKE_curves_utils.hh | 7 ++ source/blender/blenkernel/intern/curves.cc | 17 +++++ source/blender/blenkernel/intern/curves_utils.cc | 12 ++++ source/blender/geometry/GEO_mesh_to_curve.hh | 12 ++-- source/blender/geometry/GEO_set_curve_type.hh | 18 ++--- source/blender/geometry/GEO_subdivide_curves.hh | 10 ++- .../geometry/intern/mesh_to_curve_convert.cc | 23 +++---- .../blender/geometry/intern/realize_instances.cc | 5 ++ source/blender/geometry/intern/set_curve_type.cc | 76 +++++++--------------- source/blender/geometry/intern/subdivide_curves.cc | 38 ++--------- .../nodes/geometry/nodes/node_geo_curve_fillet.cc | 5 +- .../geometry/nodes/node_geo_curve_resample.cc | 18 +++-- .../geometry/nodes/node_geo_curve_spline_type.cc | 13 ++-- .../geometry/nodes/node_geo_curve_subdivide.cc | 13 ++-- .../nodes/geometry/nodes/node_geo_curve_trim.cc | 8 ++- .../geometry/nodes/node_geo_duplicate_elements.cc | 2 + .../nodes/geometry/nodes/node_geo_mesh_to_curve.cc | 6 +- 18 files changed, 147 insertions(+), 142 deletions(-) diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh index fb97e52f6da..25a912b8825 100644 --- a/source/blender/blenkernel/BKE_curves.hh +++ b/source/blender/blenkernel/BKE_curves.hh @@ -735,6 +735,12 @@ Curves *curves_new_nomain(CurvesGeometry curves); */ Curves *curves_new_nomain_single(int points_num, CurveType type); +/** + * Copy data from #src to #dst, except the geometry data in #CurvesGeometry. Typically used to + * copy high-level parameters when a geometry-altering operation creates a new curves data-block. + */ +void curves_copy_parameters(const Curves &src, Curves &dst); + std::array calculate_type_counts(const VArray &types); /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/BKE_curves_utils.hh b/source/blender/blenkernel/BKE_curves_utils.hh index f223e173ea9..0fbd33002e1 100644 --- a/source/blender/blenkernel/BKE_curves_utils.hh +++ b/source/blender/blenkernel/BKE_curves_utils.hh @@ -55,6 +55,13 @@ void fill_points(const CurvesGeometry &curves, fill_points(curves, curve_selection, &value, dst); } +/** + * Copy only the information on the point domain, but not the offsets or any point attributes, + * meant for operations that change the number of points but not the number of curves. + * \warning The returned curves have invalid offsets! + */ +bke::CurvesGeometry copy_only_curve_domain(const bke::CurvesGeometry &src_curves); + /** * Copy the size of every curve in #curve_ranges to the corresponding index in #counts. */ diff --git a/source/blender/blenkernel/intern/curves.cc b/source/blender/blenkernel/intern/curves.cc index 78791b55b4d..7e9f994313b 100644 --- a/source/blender/blenkernel/intern/curves.cc +++ b/source/blender/blenkernel/intern/curves.cc @@ -388,6 +388,23 @@ Curves *curves_new_nomain(CurvesGeometry curves) return curves_id; } +void curves_copy_parameters(const Curves &src, Curves &dst) +{ + dst.flag = src.flag; + dst.attributes_active_index = src.attributes_active_index; + MEM_SAFE_FREE(dst.mat); + dst.mat = static_cast(MEM_malloc_arrayN(src.totcol, sizeof(Material *), __func__)); + dst.totcol = src.totcol; + MutableSpan(dst.mat, dst.totcol).copy_from(Span(src.mat, src.totcol)); + dst.symmetry = src.symmetry; + dst.selection_domain = src.selection_domain; + dst.surface = src.surface; + MEM_SAFE_FREE(dst.surface_uv_map); + if (src.surface_uv_map != nullptr) { + dst.surface_uv_map = BLI_strdup(src.surface_uv_map); + } +} + CurvesSurfaceTransforms::CurvesSurfaceTransforms(const Object &curves_ob, const Object *surface_ob) { this->curves_to_world = curves_ob.obmat; diff --git a/source/blender/blenkernel/intern/curves_utils.cc b/source/blender/blenkernel/intern/curves_utils.cc index f7db60ee588..d98832e796c 100644 --- a/source/blender/blenkernel/intern/curves_utils.cc +++ b/source/blender/blenkernel/intern/curves_utils.cc @@ -84,6 +84,18 @@ void fill_points(const CurvesGeometry &curves, }); } +bke::CurvesGeometry copy_only_curve_domain(const bke::CurvesGeometry &src_curves) +{ + bke::CurvesGeometry dst_curves(0, src_curves.curves_num()); + CustomData_copy(&src_curves.curve_data, + &dst_curves.curve_data, + CD_MASK_ALL, + CD_DUPLICATE, + src_curves.curves_num()); + dst_curves.runtime->type_counts = src_curves.runtime->type_counts; + return dst_curves; +} + IndexMask indices_for_type(const VArray &types, const std::array &type_counts, const CurveType type, diff --git a/source/blender/geometry/GEO_mesh_to_curve.hh b/source/blender/geometry/GEO_mesh_to_curve.hh index c480e4178cf..f619aaff217 100644 --- a/source/blender/geometry/GEO_mesh_to_curve.hh +++ b/source/blender/geometry/GEO_mesh_to_curve.hh @@ -1,12 +1,12 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ +#pragma once + #include "BLI_index_mask.hh" -#pragma once +#include "BKE_curves.hh" struct Mesh; -struct Curves; -class MeshComponent; /** \file * \ingroup geo @@ -15,10 +15,10 @@ class MeshComponent; namespace blender::geometry { /** - * Convert the mesh into one or many poly splines. Since splines cannot have branches, - * intersections of more than three edges will become breaks in splines. Attributes that + * Convert the mesh into one or many poly curves. Since curves cannot have branches, + * intersections of more than three edges will become breaks in curves. Attributes that * are not built-in on meshes and not curves are transferred to the result curve. */ -Curves *mesh_to_curve_convert(const MeshComponent &mesh_component, const IndexMask selection); +bke::CurvesGeometry mesh_to_curve_convert(const Mesh &mesh, const IndexMask selection); } // namespace blender::geometry diff --git a/source/blender/geometry/GEO_set_curve_type.hh b/source/blender/geometry/GEO_set_curve_type.hh index 6a75450006b..f38e63b1fc8 100644 --- a/source/blender/geometry/GEO_set_curve_type.hh +++ b/source/blender/geometry/GEO_set_curve_type.hh @@ -2,17 +2,10 @@ #pragma once -#include "DNA_curves_types.h" - #include "BLI_function_ref.hh" #include "BLI_index_mask.hh" -struct Curves; -class CurveComponent; - -namespace blender::bke { -class CurvesGeometry; -} +#include "BKE_curves.hh" namespace blender::geometry { @@ -27,14 +20,13 @@ namespace blender::geometry { */ bool try_curves_conversion_in_place(IndexMask selection, CurveType dst_type, - FunctionRef get_writable_curves_fn); + FunctionRef get_writable_curves_fn); /** * Change the types of the selected curves, potentially changing the total point count. */ -Curves *convert_curves(const CurveComponent &src_component, - const bke::CurvesGeometry &src_curves, - IndexMask selection, - CurveType dst_type); +bke::CurvesGeometry convert_curves(const bke::CurvesGeometry &src_curves, + IndexMask selection, + CurveType dst_type); } // namespace blender::geometry diff --git a/source/blender/geometry/GEO_subdivide_curves.hh b/source/blender/geometry/GEO_subdivide_curves.hh index 66c2eb53496..ba55118baa4 100644 --- a/source/blender/geometry/GEO_subdivide_curves.hh +++ b/source/blender/geometry/GEO_subdivide_curves.hh @@ -4,11 +4,10 @@ #include "BLI_function_ref.hh" #include "BLI_index_mask.hh" +#include "BLI_virtual_array.hh" #include "BKE_curves.hh" -class CurveComponent; - namespace blender::geometry { /** @@ -18,9 +17,8 @@ namespace blender::geometry { * * \param selection: A selection of curves to consider when subdividing. */ -Curves *subdivide_curves(const CurveComponent &src_component, - const bke::CurvesGeometry &src_curves, - IndexMask selection, - const VArray &cuts); +bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves, + IndexMask selection, + const VArray &cuts); } // namespace blender::geometry diff --git a/source/blender/geometry/intern/mesh_to_curve_convert.cc b/source/blender/geometry/intern/mesh_to_curve_convert.cc index 681dfd15137..fdacb174462 100644 --- a/source/blender/geometry/intern/mesh_to_curve_convert.cc +++ b/source/blender/geometry/intern/mesh_to_curve_convert.cc @@ -30,13 +30,12 @@ static void copy_with_map(const VArray &src, Span map, MutableSpan ds }); } -static Curves *create_curve_from_vert_indices(const MeshComponent &mesh_component, - const Span vert_indices, - const Span curve_offsets, - const IndexRange cyclic_curves) +static bke::CurvesGeometry create_curve_from_vert_indices(const Mesh &mesh, + const Span vert_indices, + const Span curve_offsets, + const IndexRange cyclic_curves) { - Curves *curves_id = bke::curves_new_nomain(vert_indices.size(), curve_offsets.size()); - bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id->geometry); + bke::CurvesGeometry curves(vert_indices.size(), curve_offsets.size()); curves.offsets_for_write().drop_back(1).copy_from(curve_offsets); curves.offsets_for_write().last() = vert_indices.size(); curves.fill_curve_types(CURVE_TYPE_POLY); @@ -44,8 +43,8 @@ static Curves *create_curve_from_vert_indices(const MeshComponent &mesh_componen curves.cyclic_for_write().fill(false); curves.cyclic_for_write().slice(cyclic_curves).fill(true); + const bke::AttributeAccessor mesh_attributes = bke::mesh_attributes(mesh); bke::MutableAttributeAccessor curves_attributes = curves.attributes_for_write(); - const bke::AttributeAccessor mesh_attributes = *mesh_component.attributes(); Set source_attribute_ids = mesh_attributes.all_ids(); @@ -76,7 +75,7 @@ static Curves *create_curve_from_vert_indices(const MeshComponent &mesh_componen }); } - return curves_id; + return curves; } struct CurveFromEdgesOutput { @@ -220,16 +219,14 @@ static Vector> get_selected_edges(const Mesh &mesh, const In return selected_edges; } -Curves *mesh_to_curve_convert(const MeshComponent &mesh_component, const IndexMask selection) +bke::CurvesGeometry mesh_to_curve_convert(const Mesh &mesh, const IndexMask selection) { - const Mesh &mesh = *mesh_component.get_for_read(); - Vector> selected_edges = get_selected_edges(*mesh_component.get_for_read(), - selection); + Vector> selected_edges = get_selected_edges(mesh, selection); CurveFromEdgesOutput output = edges_to_curve_point_indices({mesh.mvert, mesh.totvert}, selected_edges); return create_curve_from_vert_indices( - mesh_component, output.vert_indices, output.curve_offsets, output.cyclic_curves); + mesh, output.vert_indices, output.curve_offsets, output.cyclic_curves); } } // namespace blender::geometry diff --git a/source/blender/geometry/intern/realize_instances.cc b/source/blender/geometry/intern/realize_instances.cc index 66b856ee0c4..25bcead09b4 100644 --- a/source/blender/geometry/intern/realize_instances.cc +++ b/source/blender/geometry/intern/realize_instances.cc @@ -1238,6 +1238,11 @@ static void execute_realize_curve_tasks(const RealizeInstancesOptions &options, dst_component.replace(dst_curves_id); bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write(); + /* Copy settings from the first input geometry set with curves. */ + const RealizeCurveTask &first_task = tasks.first(); + const Curves &first_curves_id = *first_task.curve_info->curves; + bke::curves_copy_parameters(first_curves_id, *dst_curves_id); + /* Prepare id attribute. */ SpanAttributeWriter point_ids; if (all_curves_info.create_id_attribute) { diff --git a/source/blender/geometry/intern/set_curve_type.cc b/source/blender/geometry/intern/set_curve_type.cc index 08888a74973..40ee2488a4b 100644 --- a/source/blender/geometry/intern/set_curve_type.cc +++ b/source/blender/geometry/intern/set_curve_type.cc @@ -1,9 +1,9 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "BKE_attribute.hh" #include "BKE_attribute_math.hh" #include "BKE_curves.hh" #include "BKE_curves_utils.hh" -#include "BKE_geometry_set.hh" #include "BLI_task.hh" @@ -322,39 +322,16 @@ static void retrieve_generic_point_attributes(const bke::AttributeAccessor &src_ }); } -static Curves *create_result_curves(const bke::CurvesGeometry &src_curves, - const IndexMask selection, - const CurveType dst_type) -{ - Curves *dst_curves_id = bke::curves_new_nomain(0, src_curves.curves_num()); - bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry); - CurveComponent dst_component; - dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable); - /* Directly copy curve attributes, since they stay the same (except for curve types). */ - CustomData_copy(&src_curves.curve_data, - &dst_curves.curve_data, - CD_MASK_ALL, - CD_DUPLICATE, - src_curves.curves_num()); - - dst_curves.fill_curve_types(selection, dst_type); - - return dst_curves_id; -} - -static Curves *convert_curves_to_bezier(const CurveComponent &src_component, - const bke::CurvesGeometry &src_curves, - const IndexMask selection) +static bke::CurvesGeometry convert_curves_to_bezier(const bke::CurvesGeometry &src_curves, + const IndexMask selection) { const VArray src_knot_modes = src_curves.nurbs_knots_modes(); const VArray src_types = src_curves.curve_types(); const VArray src_cyclic = src_curves.cyclic(); const Span src_positions = src_curves.positions(); - Curves *dst_curves_id = create_result_curves(src_curves, selection, CURVE_TYPE_BEZIER); - bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry); - CurveComponent dst_component; - dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable); + bke::CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves); + dst_curves.fill_curve_types(selection, CURVE_TYPE_BEZIER); MutableSpan dst_offsets = dst_curves.offsets_for_write(); retrieve_curve_sizes(src_curves, dst_curves.offsets_for_write()); @@ -367,8 +344,8 @@ static Curves *convert_curves_to_bezier(const CurveComponent &src_component, bke::curves::accumulate_counts_to_offsets(dst_offsets); dst_curves.resize(dst_offsets.last(), dst_curves.curves_num()); - const bke::AttributeAccessor src_attributes = *src_component.attributes(); - bke::MutableAttributeAccessor dst_attributes = *dst_component.attributes_for_write(); + const bke::AttributeAccessor src_attributes = src_curves.attributes(); + bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write(); GenericAttributes attributes; retrieve_generic_point_attributes(src_attributes, dst_attributes, attributes); @@ -501,21 +478,18 @@ static Curves *convert_curves_to_bezier(const CurveComponent &src_component, attribute.finish(); } - return dst_curves_id; + return dst_curves; } -static Curves *convert_curves_to_nurbs(const CurveComponent &src_component, - const bke::CurvesGeometry &src_curves, - const IndexMask selection) +static bke::CurvesGeometry convert_curves_to_nurbs(const bke::CurvesGeometry &src_curves, + const IndexMask selection) { const VArray src_types = src_curves.curve_types(); const VArray src_cyclic = src_curves.cyclic(); const Span src_positions = src_curves.positions(); - Curves *dst_curves_id = create_result_curves(src_curves, selection, CURVE_TYPE_NURBS); - bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry); - CurveComponent dst_component; - dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable); + bke::CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves); + dst_curves.fill_curve_types(selection, CURVE_TYPE_NURBS); MutableSpan dst_offsets = dst_curves.offsets_for_write(); retrieve_curve_sizes(src_curves, dst_curves.offsets_for_write()); @@ -527,8 +501,8 @@ static Curves *convert_curves_to_nurbs(const CurveComponent &src_component, bke::curves::accumulate_counts_to_offsets(dst_offsets); dst_curves.resize(dst_offsets.last(), dst_curves.curves_num()); - const bke::AttributeAccessor src_attributes = *src_component.attributes(); - bke::MutableAttributeAccessor dst_attributes = *dst_component.attributes_for_write(); + const bke::AttributeAccessor src_attributes = src_curves.attributes(); + bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write(); GenericAttributes attributes; retrieve_generic_point_attributes(src_attributes, dst_attributes, attributes); @@ -669,7 +643,7 @@ static Curves *convert_curves_to_nurbs(const CurveComponent &src_component, attribute.finish(); } - return dst_curves_id; + return dst_curves; } static bke::CurvesGeometry convert_curves_trivial(const bke::CurvesGeometry &src_curves, @@ -682,33 +656,31 @@ static bke::CurvesGeometry convert_curves_trivial(const bke::CurvesGeometry &src return dst_curves; } -Curves *convert_curves(const CurveComponent &src_component, - const bke::CurvesGeometry &src_curves, - const IndexMask selection, - const CurveType dst_type) +bke::CurvesGeometry convert_curves(const bke::CurvesGeometry &src_curves, + const IndexMask selection, + const CurveType dst_type) { switch (dst_type) { case CURVE_TYPE_CATMULL_ROM: case CURVE_TYPE_POLY: - return bke::curves_new_nomain(convert_curves_trivial(src_curves, selection, dst_type)); + return convert_curves_trivial(src_curves, selection, dst_type); case CURVE_TYPE_BEZIER: - return convert_curves_to_bezier(src_component, src_curves, selection); + return convert_curves_to_bezier(src_curves, selection); case CURVE_TYPE_NURBS: - return convert_curves_to_nurbs(src_component, src_curves, selection); + return convert_curves_to_nurbs(src_curves, selection); } BLI_assert_unreachable(); - return nullptr; + return {}; } bool try_curves_conversion_in_place(const IndexMask selection, const CurveType dst_type, - FunctionRef get_writable_curves_fn) + FunctionRef get_writable_curves_fn) { if (conversion_can_change_point_num(dst_type)) { return false; } - Curves &curves_id = get_writable_curves_fn(); - bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry); + bke::CurvesGeometry &curves = get_writable_curves_fn(); curves.fill_curve_types(selection, dst_type); curves.remove_attributes_based_on_types(); return true; diff --git a/source/blender/geometry/intern/subdivide_curves.cc b/source/blender/geometry/intern/subdivide_curves.cc index 914174235cd..b6476d19818 100644 --- a/source/blender/geometry/intern/subdivide_curves.cc +++ b/source/blender/geometry/intern/subdivide_curves.cc @@ -11,26 +11,6 @@ namespace blender::geometry { -/** - * \warning Only the curve domain of the input is copied, so the result is invalid! - */ -static Curves *create_result_curves(const bke::CurvesGeometry &src_curves) -{ - Curves *dst_curves_id = bke::curves_new_nomain(0, src_curves.curves_num()); - bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry); - CurveComponent dst_component; - dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable); - /* Directly copy curve attributes, since they stay the same. */ - CustomData_copy(&src_curves.curve_data, - &dst_curves.curve_data, - CD_MASK_ALL, - CD_DUPLICATE, - src_curves.curves_num()); - dst_curves.runtime->type_counts = src_curves.runtime->type_counts; - - return dst_curves_id; -} - /** * Return a range used to retrieve values from an array of values stored per point, but with an * extra element at the end of each curve. This is useful for offsets within curves, where it is @@ -342,10 +322,9 @@ static void subdivide_bezier_positions(const Span src_positions, cyclic, dst_types_l, dst_types_r, dst_positions, dst_handles_l, dst_handles_r); } -Curves *subdivide_curves(const CurveComponent &src_component, - const bke::CurvesGeometry &src_curves, - const IndexMask selection, - const VArray &cuts) +bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves, + const IndexMask selection, + const VArray &cuts) { const Vector unselected_ranges = selection.extract_ranges_invert( src_curves.curves_range()); @@ -353,10 +332,7 @@ Curves *subdivide_curves(const CurveComponent &src_component, /* Cyclic is accessed a lot, it's probably worth it to make sure it's a span. */ const VArraySpan cyclic{src_curves.cyclic()}; - Curves *dst_curves_id = create_result_curves(src_curves); - bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry); - CurveComponent dst_component; - dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable); + bke::CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves); /* For each point, this contains the point offset in the corresponding result curve, * starting at zero. For example for two curves with four points each, the values might @@ -385,8 +361,8 @@ Curves *subdivide_curves(const CurveComponent &src_component, dst_curves.resize(dst_curves.offsets().last(), dst_curves.curves_num()); - const bke::AttributeAccessor src_attributes = *src_component.attributes(); - bke::MutableAttributeAccessor dst_attributes = *dst_component.attributes_for_write(); + const bke::AttributeAccessor src_attributes = src_curves.attributes(); + bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write(); auto subdivide_catmull_rom = [&](IndexMask selection) { for (auto &attribute : retrieve_point_attributes(src_attributes, dst_attributes)) { @@ -476,7 +452,7 @@ Curves *subdivide_curves(const CurveComponent &src_component, } } - return dst_curves_id; + return dst_curves; } } // namespace blender::geometry diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc index e200350dc18..057e08cc33d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc @@ -603,10 +603,13 @@ static void calculate_curve_fillet(GeometrySet &geometry_set, fillet_param.limit_radius = limit_radius; + const Curves &src_curves_id = *geometry_set.get_curves_for_read(); const std::unique_ptr input_curve = curves_to_curve_eval(*component.get_for_read()); std::unique_ptr output_curve = fillet_curve(*input_curve, fillet_param); - geometry_set.replace_curves(curve_eval_to_curves(*output_curve)); + Curves *dst_curves_id = curve_eval_to_curves(*output_curve); + bke::curves_copy_parameters(src_curves_id, *dst_curves_id); + geometry_set.replace_curves(dst_curves_id); } static void node_geo_exec(GeoNodeExecParams params) diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc index 78a132064ed..2815dd5b2e8 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc @@ -65,8 +65,10 @@ static void node_geo_exec(GeoNodeExecParams params) Field count = params.extract_input>("Count"); geometry_set.modify_geometry_sets([&](GeometrySet &geometry) { if (const CurveComponent *component = geometry.get_component_for_read()) { - if (!component->is_empty()) { - geometry.replace_curves(geometry::resample_to_count(*component, selection, count)); + if (const Curves *src_curves = component->get_for_read()) { + Curves *dst_curves = geometry::resample_to_count(*component, selection, count); + bke::curves_copy_parameters(*src_curves, *dst_curves); + geometry.replace_curves(dst_curves); } } }); @@ -76,8 +78,10 @@ static void node_geo_exec(GeoNodeExecParams params) Field length = params.extract_input>("Length"); geometry_set.modify_geometry_sets([&](GeometrySet &geometry) { if (const CurveComponent *component = geometry.get_component_for_read()) { - if (!component->is_empty()) { - geometry.replace_curves(geometry::resample_to_length(*component, selection, length)); + if (const Curves *src_curves = component->get_for_read()) { + Curves *dst_curves = geometry::resample_to_length(*component, selection, length); + bke::curves_copy_parameters(*src_curves, *dst_curves); + geometry.replace_curves(dst_curves); } } }); @@ -86,8 +90,10 @@ static void node_geo_exec(GeoNodeExecParams params) case GEO_NODE_CURVE_RESAMPLE_EVALUATED: geometry_set.modify_geometry_sets([&](GeometrySet &geometry) { if (const CurveComponent *component = geometry.get_component_for_read()) { - if (!component->is_empty()) { - geometry.replace_curves(geometry::resample_to_evaluated(*component, selection)); + if (const Curves *src_curves = component->get_for_read()) { + Curves *dst_curves = geometry::resample_to_evaluated(*component, selection); + bke::curves_copy_parameters(*src_curves, *dst_curves); + geometry.replace_curves(dst_curves); } } }); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc index 183c98e9c9f..a92479bc5f1 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc @@ -61,15 +61,18 @@ static void node_geo_exec(GeoNodeExecParams params) return; } - if (geometry::try_curves_conversion_in_place(selection, dst_type, [&]() -> Curves & { - return *geometry_set.get_curves_for_write(); - })) { + if (geometry::try_curves_conversion_in_place( + selection, dst_type, [&]() -> bke::CurvesGeometry & { + return bke::CurvesGeometry::wrap(geometry_set.get_curves_for_write()->geometry); + })) { return; } - Curves *dst_curves = geometry::convert_curves(src_component, src_curves, selection, dst_type); + bke::CurvesGeometry dst_curves = geometry::convert_curves(src_curves, selection, dst_type); - geometry_set.replace_curves(dst_curves); + Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); + bke::curves_copy_parameters(src_curves_id, *dst_curves_id); + geometry_set.replace_curves(dst_curves_id); }); params.set_output("Curve", std::move(geometry_set)); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc index 864e6289135..bd44adb35a2 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc @@ -35,11 +35,11 @@ static void node_geo_exec(GeoNodeExecParams params) } const CurveComponent &component = *geometry_set.get_component_for_read(); - const bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap( - component.get_for_read()->geometry); + const Curves &src_curves_id = *component.get_for_read(); + const bke::CurvesGeometry &src_curves = bke::CurvesGeometry::wrap(src_curves_id.geometry); GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; - fn::FieldEvaluator evaluator{field_context, curves.points_num()}; + fn::FieldEvaluator evaluator{field_context, src_curves.points_num()}; evaluator.add(cuts_field); evaluator.evaluate(); const VArray cuts = evaluator.get_evaluated(0); @@ -47,9 +47,12 @@ static void node_geo_exec(GeoNodeExecParams params) return; } - Curves *result = geometry::subdivide_curves(component, curves, curves.curves_range(), cuts); + bke::CurvesGeometry dst_curves = geometry::subdivide_curves( + src_curves, src_curves.curves_range(), cuts); - geometry_set.replace_curves(result); + Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); + bke::curves_copy_parameters(src_curves_id, *dst_curves_id); + geometry_set.replace_curves(dst_curves_id); }); params.set_output("Curve", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc index 00a79168087..51994cb8a41 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "BKE_curves.hh" #include "BKE_spline.hh" #include "BLI_task.hh" @@ -515,7 +516,8 @@ static void geometry_set_curve_trim(GeometrySet &geometry_set, const VArray starts = evaluator.get_evaluated(0); const VArray ends = evaluator.get_evaluated(1); - std::unique_ptr curve = curves_to_curve_eval(*geometry_set.get_curves_for_read()); + const Curves &src_curves_id = *geometry_set.get_curves_for_read(); + std::unique_ptr curve = curves_to_curve_eval(src_curves_id); MutableSpan splines = curve->splines(); threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { @@ -566,7 +568,9 @@ static void geometry_set_curve_trim(GeometrySet &geometry_set, } }); - geometry_set.replace_curves(curve_eval_to_curves(*curve)); + Curves *dst_curves_id = curve_eval_to_curves(*curve); + bke::curves_copy_parameters(src_curves_id, *dst_curves_id); + geometry_set.replace_curves(dst_curves_id); } static void node_geo_exec(GeoNodeExecParams params) diff --git a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc index 41136060ab7..8160bc8d589 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc @@ -369,6 +369,7 @@ static void duplicate_curves(GeometrySet &geometry_set, point_offsets.last() = dst_points_num; Curves *new_curves_id = bke::curves_new_nomain(dst_points_num, dst_curves_num); + bke::curves_copy_parameters(curves_id, *new_curves_id); bke::CurvesGeometry &new_curves = bke::CurvesGeometry::wrap(new_curves_id->geometry); MutableSpan all_dst_offsets = new_curves.offsets_for_write(); @@ -830,6 +831,7 @@ static void duplicate_points_curve(GeometrySet &geometry_set, }); Curves *new_curves_id = bke::curves_new_nomain(dst_num, dst_num); + bke::curves_copy_parameters(src_curves_id, *new_curves_id); bke::CurvesGeometry &new_curves = bke::CurvesGeometry::wrap(new_curves_id->geometry); MutableSpan new_curve_offsets = new_curves.offsets_for_write(); for (const int i : new_curves.curves_range()) { diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc index f6ee3d00dee..4a79ec159f4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc @@ -18,7 +18,8 @@ static void node_geo_exec(GeoNodeExecParams params) GeometrySet geometry_set = params.extract_input("Mesh"); geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { - if (!geometry_set.has_mesh()) { + const Mesh *mesh = geometry_set.get_mesh_for_read(); + if (mesh == nullptr) { geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); return; } @@ -34,7 +35,8 @@ static void node_geo_exec(GeoNodeExecParams params) return; } - geometry_set.replace_curves(geometry::mesh_to_curve_convert(component, selection)); + bke::CurvesGeometry curves = geometry::mesh_to_curve_convert(*mesh, selection); + geometry_set.replace_curves(bke::curves_new_nomain(std::move(curves))); geometry_set.keep_only({GEO_COMPONENT_TYPE_CURVE, GEO_COMPONENT_TYPE_INSTANCES}); }); -- cgit v1.2.3 From c771dd5e9c85335aa5e4cf60cfa61bc286c26229 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Tue, 19 Jul 2022 17:21:11 +0200 Subject: Depsgraph: Make animated properties API receive const ID Semantically it is more correct as the cache does not modify the ID. There is need to do couple of const casts since the BKE (which is in C) does not easily allow to iterate into f-curves of const ID. Should be no functional changes. --- source/blender/depsgraph/intern/builder/deg_builder.cc | 11 ++++++----- source/blender/depsgraph/intern/builder/deg_builder.h | 8 ++++---- .../depsgraph/intern/builder/deg_builder_cache.cc | 15 ++++++++------- .../depsgraph/intern/builder/deg_builder_cache.h | 18 +++++++++--------- .../depsgraph/intern/builder/pipeline_all_objects.cc | 4 ++-- .../depsgraph/intern/builder/pipeline_from_ids.cc | 4 ++-- 6 files changed, 31 insertions(+), 29 deletions(-) diff --git a/source/blender/depsgraph/intern/builder/deg_builder.cc b/source/blender/depsgraph/intern/builder/deg_builder.cc index 888e0649065..1fec1fb3219 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder.cc @@ -63,7 +63,7 @@ DepsgraphBuilder::DepsgraphBuilder(Main *bmain, Depsgraph *graph, DepsgraphBuild { } -bool DepsgraphBuilder::need_pull_base_into_graph(Base *base) +bool DepsgraphBuilder::need_pull_base_into_graph(const Base *base) { /* Simple check: enabled bases are always part of dependency graph. */ const int base_flag = (graph_->mode == DAG_EVAL_VIEWPORT) ? BASE_ENABLED_VIEWPORT : @@ -74,7 +74,7 @@ bool DepsgraphBuilder::need_pull_base_into_graph(Base *base) /* More involved check: since we don't support dynamic changes in dependency graph topology and * all visible objects are to be part of dependency graph, we pull all objects which has animated * visibility. */ - Object *object = base->object; + const Object *object = base->object; AnimatedPropertyID property_id; if (graph_->mode == DAG_EVAL_VIEWPORT) { property_id = AnimatedPropertyID(&object->id, &RNA_Object, "hide_viewport"); @@ -89,7 +89,7 @@ bool DepsgraphBuilder::need_pull_base_into_graph(Base *base) return cache_->isPropertyAnimated(&object->id, property_id); } -bool DepsgraphBuilder::check_pchan_has_bbone(Object *object, const bPoseChannel *pchan) +bool DepsgraphBuilder::check_pchan_has_bbone(const Object *object, const bPoseChannel *pchan) { BLI_assert(object->type == OB_ARMATURE); if (pchan == nullptr || pchan->bone == nullptr) { @@ -109,12 +109,13 @@ bool DepsgraphBuilder::check_pchan_has_bbone(Object *object, const bPoseChannel cache_->isPropertyAnimated(&armature->id, property_id); } -bool DepsgraphBuilder::check_pchan_has_bbone_segments(Object *object, const bPoseChannel *pchan) +bool DepsgraphBuilder::check_pchan_has_bbone_segments(const Object *object, + const bPoseChannel *pchan) { return check_pchan_has_bbone(object, pchan); } -bool DepsgraphBuilder::check_pchan_has_bbone_segments(Object *object, const char *bone_name) +bool DepsgraphBuilder::check_pchan_has_bbone_segments(const Object *object, const char *bone_name) { const bPoseChannel *pchan = BKE_pose_channel_find_name(object->pose, bone_name); return check_pchan_has_bbone_segments(object, pchan); diff --git a/source/blender/depsgraph/intern/builder/deg_builder.h b/source/blender/depsgraph/intern/builder/deg_builder.h index 6f2bde3a2ff..950ebfb35ba 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder.h +++ b/source/blender/depsgraph/intern/builder/deg_builder.h @@ -22,11 +22,11 @@ class DepsgraphBuilder { public: virtual ~DepsgraphBuilder() = default; - virtual bool need_pull_base_into_graph(Base *base); + virtual bool need_pull_base_into_graph(const Base *base); - virtual bool check_pchan_has_bbone(Object *object, const bPoseChannel *pchan); - virtual bool check_pchan_has_bbone_segments(Object *object, const bPoseChannel *pchan); - virtual bool check_pchan_has_bbone_segments(Object *object, const char *bone_name); + virtual bool check_pchan_has_bbone(const Object *object, const bPoseChannel *pchan); + virtual bool check_pchan_has_bbone_segments(const Object *object, const bPoseChannel *pchan); + virtual bool check_pchan_has_bbone_segments(const Object *object, const char *bone_name); protected: /* NOTE: The builder does NOT take ownership over any of those resources. */ diff --git a/source/blender/depsgraph/intern/builder/deg_builder_cache.cc b/source/blender/depsgraph/intern/builder/deg_builder_cache.cc index 7f88f54fdca..6474f853390 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_cache.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_cache.cc @@ -35,13 +35,13 @@ AnimatedPropertyID::AnimatedPropertyID(const PointerRNA &pointer_rna, { } -AnimatedPropertyID::AnimatedPropertyID(ID *id, StructRNA *type, const char *property_name) +AnimatedPropertyID::AnimatedPropertyID(const ID *id, StructRNA *type, const char *property_name) : data(id) { property_rna = RNA_struct_type_find_property(type, property_name); } -AnimatedPropertyID::AnimatedPropertyID(ID * /*id*/, +AnimatedPropertyID::AnimatedPropertyID(const ID * /*id*/, StructRNA *type, void *data, const char *property_name) @@ -100,13 +100,13 @@ AnimatedPropertyStorage::AnimatedPropertyStorage() : is_fully_initialized(false) { } -void AnimatedPropertyStorage::initializeFromID(DepsgraphBuilderCache *builder_cache, ID *id) +void AnimatedPropertyStorage::initializeFromID(DepsgraphBuilderCache *builder_cache, const ID *id) { AnimatedPropertyCallbackData data; - RNA_id_pointer_create(id, &data.pointer_rna); + RNA_id_pointer_create(const_cast(id), &data.pointer_rna); data.animated_property_storage = this; data.builder_cache = builder_cache; - BKE_fcurves_id_cb(id, animated_property_cb, &data); + BKE_fcurves_id_cb(const_cast(id), animated_property_cb, &data); } void AnimatedPropertyStorage::tagPropertyAsAnimated(const AnimatedPropertyID &property_id) @@ -147,13 +147,14 @@ DepsgraphBuilderCache::~DepsgraphBuilderCache() } } -AnimatedPropertyStorage *DepsgraphBuilderCache::ensureAnimatedPropertyStorage(ID *id) +AnimatedPropertyStorage *DepsgraphBuilderCache::ensureAnimatedPropertyStorage(const ID *id) { return animated_property_storage_map_.lookup_or_add_cb( id, []() { return new AnimatedPropertyStorage(); }); } -AnimatedPropertyStorage *DepsgraphBuilderCache::ensureInitializedAnimatedPropertyStorage(ID *id) +AnimatedPropertyStorage *DepsgraphBuilderCache::ensureInitializedAnimatedPropertyStorage( + const ID *id) { AnimatedPropertyStorage *animated_property_storage = ensureAnimatedPropertyStorage(id); if (!animated_property_storage->is_fully_initialized) { diff --git a/source/blender/depsgraph/intern/builder/deg_builder_cache.h b/source/blender/depsgraph/intern/builder/deg_builder_cache.h index 5568967f163..d85269c0f8b 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_cache.h +++ b/source/blender/depsgraph/intern/builder/deg_builder_cache.h @@ -27,14 +27,14 @@ class AnimatedPropertyID { AnimatedPropertyID(); AnimatedPropertyID(const PointerRNA *pointer_rna, const PropertyRNA *property_rna); AnimatedPropertyID(const PointerRNA &pointer_rna, const PropertyRNA *property_rna); - AnimatedPropertyID(ID *id, StructRNA *type, const char *property_name); - AnimatedPropertyID(ID *id, StructRNA *type, void *data, const char *property_name); + AnimatedPropertyID(const ID *id, StructRNA *type, const char *property_name); + AnimatedPropertyID(const ID *id, StructRNA *type, void *data, const char *property_name); uint64_t hash() const; friend bool operator==(const AnimatedPropertyID &a, const AnimatedPropertyID &b); /* Corresponds to PointerRNA.data. */ - void *data; + const void *data; const PropertyRNA *property_rna; MEM_CXX_CLASS_ALLOC_FUNCS("AnimatedPropertyID"); @@ -44,7 +44,7 @@ class AnimatedPropertyStorage { public: AnimatedPropertyStorage(); - void initializeFromID(DepsgraphBuilderCache *builder_cache, ID *id); + void initializeFromID(DepsgraphBuilderCache *builder_cache, const ID *id); void tagPropertyAsAnimated(const AnimatedPropertyID &property_id); void tagPropertyAsAnimated(const PointerRNA *pointer_rna, const PropertyRNA *property_rna); @@ -58,7 +58,7 @@ class AnimatedPropertyStorage { bool is_fully_initialized; /* indexed by PointerRNA.data. */ - Set animated_objects_set; + Set animated_objects_set; Set animated_properties_set; MEM_CXX_CLASS_ALLOC_FUNCS("AnimatedPropertyStorage"); @@ -70,8 +70,8 @@ class DepsgraphBuilderCache { ~DepsgraphBuilderCache(); /* Makes sure storage for animated properties exists and initialized for the given ID. */ - AnimatedPropertyStorage *ensureAnimatedPropertyStorage(ID *id); - AnimatedPropertyStorage *ensureInitializedAnimatedPropertyStorage(ID *id); + AnimatedPropertyStorage *ensureAnimatedPropertyStorage(const ID *id); + AnimatedPropertyStorage *ensureInitializedAnimatedPropertyStorage(const ID *id); /* Shortcuts to go through ensureInitializedAnimatedPropertyStorage and its * isPropertyAnimated. @@ -81,7 +81,7 @@ class DepsgraphBuilderCache { * * TODO(sergey): Technically, this makes this class something else than just a cache, but what is * the better name? */ - template bool isPropertyAnimated(ID *id, Args... args) + template bool isPropertyAnimated(const ID *id, Args... args) { AnimatedPropertyStorage *animated_property_storage = ensureInitializedAnimatedPropertyStorage( id); @@ -95,7 +95,7 @@ class DepsgraphBuilderCache { return animated_property_storage->isAnyPropertyAnimated(ptr); } - Map animated_property_storage_map_; + Map animated_property_storage_map_; MEM_CXX_CLASS_ALLOC_FUNCS("DepsgraphBuilderCache"); }; diff --git a/source/blender/depsgraph/intern/builder/pipeline_all_objects.cc b/source/blender/depsgraph/intern/builder/pipeline_all_objects.cc index 74d151c65d7..6bc3b59a9d6 100644 --- a/source/blender/depsgraph/intern/builder/pipeline_all_objects.cc +++ b/source/blender/depsgraph/intern/builder/pipeline_all_objects.cc @@ -20,7 +20,7 @@ class AllObjectsNodeBuilder : public DepsgraphNodeBuilder { { } - bool need_pull_base_into_graph(Base * /*base*/) override + bool need_pull_base_into_graph(const Base * /*base*/) override { return true; } @@ -33,7 +33,7 @@ class AllObjectsRelationBuilder : public DepsgraphRelationBuilder { { } - bool need_pull_base_into_graph(Base * /*base*/) override + bool need_pull_base_into_graph(const Base * /*base*/) override { return true; } diff --git a/source/blender/depsgraph/intern/builder/pipeline_from_ids.cc b/source/blender/depsgraph/intern/builder/pipeline_from_ids.cc index ee10b28a306..e256c8271f2 100644 --- a/source/blender/depsgraph/intern/builder/pipeline_from_ids.cc +++ b/source/blender/depsgraph/intern/builder/pipeline_from_ids.cc @@ -39,7 +39,7 @@ class DepsgraphFromIDsNodeBuilder : public DepsgraphNodeBuilder { { } - bool need_pull_base_into_graph(Base *base) override + bool need_pull_base_into_graph(const Base *base) override { if (!filter_.contains(&base->object->id)) { return false; @@ -61,7 +61,7 @@ class DepsgraphFromIDsRelationBuilder : public DepsgraphRelationBuilder { { } - bool need_pull_base_into_graph(Base *base) override + bool need_pull_base_into_graph(const Base *base) override { if (!filter_.contains(&base->object->id)) { return false; -- cgit v1.2.3 From 35b4e3a3501c9c797f2269421bc778e949fb46af Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 19 Jul 2022 10:30:10 -0500 Subject: RNA: Don't allocate empty char pointer properties Instead of allocating a single 0 char, set the `char *` DNA pointer to null. This avoids the overhead of allocating and copying single-bytes. rBeed45b655c9f didn't do this for safety reasons, but I checked the existing uses of this behavior in DNA/RNA. Out of 43 total `char *` members, this change only affects 7 recently added properties. For a complete list, see the patch description. Differential Revision: https://developer.blender.org/D14779 --- source/blender/makesrna/intern/makesrna.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/blender/makesrna/intern/makesrna.c b/source/blender/makesrna/intern/makesrna.c index b5354514205..a25fe201fa2 100644 --- a/source/blender/makesrna/intern/makesrna.c +++ b/source/blender/makesrna/intern/makesrna.c @@ -1117,8 +1117,10 @@ static char *rna_def_property_set_func( fprintf( f, " if (data->%s != NULL) { MEM_freeN(data->%s); }\n", dp->dnaname, dp->dnaname); fprintf(f, " const int length = strlen(value);\n"); - fprintf(f, " data->%s = MEM_mallocN(length + 1, __func__);\n", dp->dnaname); - fprintf(f, " %s(data->%s, value, length + 1);\n", string_copy_func, dp->dnaname); + fprintf(f, " if (length > 0) {\n"); + fprintf(f, " data->%s = MEM_mallocN(length + 1, __func__);\n", dp->dnaname); + fprintf(f, " %s(data->%s, value, length + 1);\n", string_copy_func, dp->dnaname); + fprintf(f, " } else { data->%s = NULL; }\n", dp->dnaname); } else { /* Handle char array properties. */ -- cgit v1.2.3 From 87ae10a05047c026e75519a2e8175fe51f7e8ff2 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 19 Jul 2022 10:45:01 -0500 Subject: Curves: Hide snapping menu in curves sculpt mode The menu/popover doesn't affect anything in the mode, and precision operations that would use snapping are meant for edit mode anyway. --- release/scripts/startup/bl_ui/space_view3d.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 92dc4138530..1a30c666bcb 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -603,8 +603,8 @@ class VIEW3D_HT_header(Header): show_snap = True else: if (object_mode not in { - 'SCULPT', 'VERTEX_PAINT', 'WEIGHT_PAINT', 'TEXTURE_PAINT', - 'PAINT_GPENCIL', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL', 'VERTEX_GPENCIL' + 'SCULPT', 'SCULPT_CURVES', 'VERTEX_PAINT', 'WEIGHT_PAINT', 'TEXTURE_PAINT', + 'PAINT_GPENCIL', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL', 'VERTEX_GPENCIL', }) or has_pose_mode: show_snap = True else: -- cgit v1.2.3 From 801513efa068d2d181534a599d21e425331b275d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Am=C3=A9lie=20Fondevilla?= Date: Tue, 19 Jul 2022 17:15:10 +0200 Subject: Fix T99732: Crash on F3 in the dopesheet Adding the `FCURVESONLY` filter in the filter function of the Grease Pencil dopesheet prevents calls to F-curves related functions to grease pencil channels, thereby fixing the crash. Reviewed By: antoniov, sybren Maniphest Tasks: T99732 Differential Revision: https://developer.blender.org/D15490 --- source/blender/editors/animation/anim_filter.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/source/blender/editors/animation/anim_filter.c b/source/blender/editors/animation/anim_filter.c index e20932fa53e..d9eeed94868 100644 --- a/source/blender/editors/animation/anim_filter.c +++ b/source/blender/editors/animation/anim_filter.c @@ -1807,11 +1807,13 @@ static size_t animdata_filter_gpencil_data(ListBase *anim_data, ListBase tmp_data = {NULL, NULL}; size_t tmp_items = 0; - /* add gpencil animation channels */ - BEGIN_ANIMFILTER_SUBCHANNELS (EXPANDED_GPD(gpd)) { - tmp_items += animdata_filter_gpencil_layers_data(&tmp_data, ads, gpd, filter_mode); + if (!(filter_mode & ANIMFILTER_FCURVESONLY)) { + /* add gpencil animation channels */ + BEGIN_ANIMFILTER_SUBCHANNELS (EXPANDED_GPD(gpd)) { + tmp_items += animdata_filter_gpencil_layers_data(&tmp_data, ads, gpd, filter_mode); + } + END_ANIMFILTER_SUBCHANNELS; } - END_ANIMFILTER_SUBCHANNELS; /* did we find anything? */ if (tmp_items) { -- cgit v1.2.3 From fb9dc810f17c463496381bbc9fba3b96c6437ce0 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Tue, 19 Jul 2022 16:42:17 +0200 Subject: UI Code Quality: Move view related files to own folder Part of T98518. --- source/blender/editors/interface/CMakeLists.txt | 11 +- source/blender/editors/interface/abstract_view.cc | 109 ---- .../editors/interface/abstract_view_item.cc | 373 -------------- source/blender/editors/interface/grid_view.cc | 464 ----------------- source/blender/editors/interface/interface_view.cc | 190 ------- source/blender/editors/interface/tree_view.cc | 554 --------------------- .../editors/interface/views/abstract_view.cc | 109 ++++ .../editors/interface/views/abstract_view_item.cc | 373 ++++++++++++++ .../blender/editors/interface/views/grid_view.cc | 464 +++++++++++++++++ .../editors/interface/views/interface_view.cc | 190 +++++++ .../blender/editors/interface/views/tree_view.cc | 554 +++++++++++++++++++++ 11 files changed, 1696 insertions(+), 1695 deletions(-) delete mode 100644 source/blender/editors/interface/abstract_view.cc delete mode 100644 source/blender/editors/interface/abstract_view_item.cc delete mode 100644 source/blender/editors/interface/grid_view.cc delete mode 100644 source/blender/editors/interface/interface_view.cc delete mode 100644 source/blender/editors/interface/tree_view.cc create mode 100644 source/blender/editors/interface/views/abstract_view.cc create mode 100644 source/blender/editors/interface/views/abstract_view_item.cc create mode 100644 source/blender/editors/interface/views/grid_view.cc create mode 100644 source/blender/editors/interface/views/interface_view.cc create mode 100644 source/blender/editors/interface/views/tree_view.cc diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index c6c9f1f80c8..4c206f9c057 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-or-later set(INC + . ../include ../../blenfont ../../blenkernel @@ -25,9 +26,6 @@ set(INC ) set(SRC - abstract_view.cc - abstract_view_item.cc - grid_view.cc interface.cc interface_align.c interface_anim.c @@ -69,15 +67,18 @@ set(SRC interface_templates.c interface_undo.c interface_utils.cc - interface_view.cc interface_widgets.c resources.c - tree_view.cc view2d.cc view2d_draw.cc view2d_edge_pan.cc view2d_gizmo_navigate.cc view2d_ops.cc + views/abstract_view.cc + views/abstract_view_item.cc + views/grid_view.cc + views/interface_view.cc + views/tree_view.cc interface_eyedropper_intern.h interface_intern.h diff --git a/source/blender/editors/interface/abstract_view.cc b/source/blender/editors/interface/abstract_view.cc deleted file mode 100644 index 077c76a08f1..00000000000 --- a/source/blender/editors/interface/abstract_view.cc +++ /dev/null @@ -1,109 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup edinterface - */ - -#include "interface_intern.h" - -#include "UI_abstract_view.hh" - -namespace blender::ui { - -void AbstractView::register_item(AbstractViewItem &item) -{ - /* Actually modifies the item, not the view. But for the public API it "feels" a bit nicer to - * have the view base class register the items, rather than setting the view on the item. */ - item.view_ = this; -} - -/* ---------------------------------------------------------------------- */ -/** \name View Reconstruction - * \{ */ - -bool AbstractView::is_reconstructed() const -{ - return is_reconstructed_; -} - -void AbstractView::update_from_old(uiBlock &new_block) -{ - uiBlock *old_block = new_block.oldblock; - if (!old_block) { - is_reconstructed_ = true; - return; - } - - uiViewHandle *old_view_handle = ui_block_view_find_matching_in_old_block( - &new_block, reinterpret_cast(this)); - if (old_view_handle == nullptr) { - /* Initial construction, nothing to update. */ - is_reconstructed_ = true; - return; - } - - AbstractView &old_view = reinterpret_cast(*old_view_handle); - - /* Update own persistent data. */ - /* Keep the rename buffer persistent while renaming! The rename button uses the buffer's - * pointer to identify itself over redraws. */ - rename_buffer_ = std::move(old_view.rename_buffer_); - old_view.rename_buffer_ = nullptr; - - update_children_from_old(old_view); - - /* Finished (re-)constructing the tree. */ - is_reconstructed_ = true; -} - -/** \} */ - -/* ---------------------------------------------------------------------- */ -/** \name Default implementations of virtual functions - * \{ */ - -bool AbstractView::listen(const wmNotifier & /*notifier*/) const -{ - /* Nothing by default. */ - return false; -} - -/** \} */ - -/* ---------------------------------------------------------------------- */ -/** \name Renaming - * \{ */ - -bool AbstractView::is_renaming() const -{ - return rename_buffer_ != nullptr; -} - -bool AbstractView::begin_renaming() -{ - if (is_renaming()) { - return false; - } - - rename_buffer_ = std::make_unique(); - return true; -} - -void AbstractView::end_renaming() -{ - BLI_assert(is_renaming()); - rename_buffer_ = nullptr; -} - -Span AbstractView::get_rename_buffer() const -{ - return *rename_buffer_; -} -MutableSpan AbstractView::get_rename_buffer() -{ - return *rename_buffer_; -} - -/** \} */ - -} // namespace blender::ui diff --git a/source/blender/editors/interface/abstract_view_item.cc b/source/blender/editors/interface/abstract_view_item.cc deleted file mode 100644 index f73183d07e9..00000000000 --- a/source/blender/editors/interface/abstract_view_item.cc +++ /dev/null @@ -1,373 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup edinterface - */ - -#include "BKE_context.h" - -#include "BLI_listbase.h" -#include "BLI_string.h" - -#include "WM_api.h" - -#include "UI_interface.h" -#include "interface_intern.h" - -#include "UI_abstract_view.hh" - -namespace blender::ui { - -/* ---------------------------------------------------------------------- */ -/** \name View Reconstruction - * \{ */ - -void AbstractViewItem::update_from_old(const AbstractViewItem &old) -{ - is_active_ = old.is_active_; - is_renaming_ = old.is_renaming_; -} - -/** \} */ - -/* ---------------------------------------------------------------------- */ -/** \name Renaming - * \{ */ - -bool AbstractViewItem::supports_renaming() const -{ - /* No renaming by default. */ - return false; -} -bool AbstractViewItem::rename(StringRefNull /*new_name*/) -{ - /* No renaming by default. */ - return false; -} - -StringRef AbstractViewItem::get_rename_string() const -{ - /* No rename string by default. */ - return {}; -} - -bool AbstractViewItem::is_renaming() const -{ - return is_renaming_; -} - -void AbstractViewItem::begin_renaming() -{ - AbstractView &view = get_view(); - if (view.is_renaming() || !supports_renaming()) { - return; - } - - if (view.begin_renaming()) { - is_renaming_ = true; - } - - StringRef initial_str = get_rename_string(); - std::copy(std::begin(initial_str), std::end(initial_str), std::begin(view.get_rename_buffer())); -} - -void AbstractViewItem::rename_apply() -{ - const AbstractView &view = get_view(); - rename(view.get_rename_buffer().data()); - end_renaming(); -} - -void AbstractViewItem::end_renaming() -{ - if (!is_renaming()) { - return; - } - - is_renaming_ = false; - - AbstractView &view = get_view(); - view.end_renaming(); -} - -static AbstractViewItem *find_item_from_rename_button(const uiBut &rename_but) -{ - /* A minimal sanity check, can't do much more here. */ - BLI_assert(rename_but.type == UI_BTYPE_TEXT && rename_but.poin); - - LISTBASE_FOREACH (uiBut *, but, &rename_but.block->buttons) { - if (but->type != UI_BTYPE_VIEW_ITEM) { - continue; - } - - uiButViewItem *view_item_but = (uiButViewItem *)but; - AbstractViewItem *item = reinterpret_cast(view_item_but->view_item); - const AbstractView &view = item->get_view(); - - if (item->is_renaming() && (view.get_rename_buffer().data() == rename_but.poin)) { - return item; - } - } - - return nullptr; -} - -static void rename_button_fn(bContext *UNUSED(C), void *arg, char *UNUSED(origstr)) -{ - const uiBut *rename_but = static_cast(arg); - AbstractViewItem *item = find_item_from_rename_button(*rename_but); - BLI_assert(item); - item->rename_apply(); -} - -void AbstractViewItem::add_rename_button(uiBlock &block) -{ - AbstractView &view = get_view(); - uiBut *rename_but = uiDefBut(&block, - UI_BTYPE_TEXT, - 1, - "", - 0, - 0, - UI_UNIT_X * 10, - UI_UNIT_Y, - view.get_rename_buffer().data(), - 1.0f, - view.get_rename_buffer().size(), - 0, - 0, - ""); - - /* Gotta be careful with what's passed to the `arg1` here. Any view data will be freed once the - * callback is executed. */ - UI_but_func_rename_set(rename_but, rename_button_fn, rename_but); - UI_but_flag_disable(rename_but, UI_BUT_UNDO); - - const bContext *evil_C = reinterpret_cast(block.evil_C); - ARegion *region = CTX_wm_region(evil_C); - /* Returns false if the button was removed. */ - if (UI_but_active_only(evil_C, region, &block, rename_but) == false) { - end_renaming(); - } -} - -/** \} */ - -/* ---------------------------------------------------------------------- */ -/** \name Context Menu - * \{ */ - -void AbstractViewItem::build_context_menu(bContext & /*C*/, uiLayout & /*column*/) const -{ - /* No context menu by default. */ -} - -/** \} */ - -/* ---------------------------------------------------------------------- */ -/** \name Drag 'n Drop - * \{ */ - -std::unique_ptr AbstractViewItem::create_drag_controller() const -{ - /* There's no drag controller (and hence no drag support) by default. */ - return nullptr; -} - -std::unique_ptr AbstractViewItem::create_drop_controller() const -{ - /* There's no drop controller (and hence no drop support) by default. */ - return nullptr; -} - -AbstractViewItemDragController::AbstractViewItemDragController(AbstractView &view) : view_(view) -{ -} - -void AbstractViewItemDragController::on_drag_start() -{ - /* Do nothing by default. */ -} - -AbstractViewItemDropController::AbstractViewItemDropController(AbstractView &view) : view_(view) -{ -} - -/** \} */ - -/* ---------------------------------------------------------------------- */ -/** \name General Getters & Setters - * \{ */ - -AbstractView &AbstractViewItem::get_view() const -{ - if (UNLIKELY(!view_)) { - throw std::runtime_error( - "Invalid state, item must be registered through AbstractView::register_item()"); - } - return *view_; -} - -bool AbstractViewItem::is_active() const -{ - BLI_assert_msg(get_view().is_reconstructed(), - "State can't be queried until reconstruction is completed"); - return is_active_; -} - -/** \} */ - -} // namespace blender::ui - -/* ---------------------------------------------------------------------- */ -/** \name C-API - * \{ */ - -namespace blender::ui { - -/** - * Helper class to provide a higher level public (C-)API. Has access to private/protected view item - * members and ensures some invariants that way. - */ -class ViewItemAPIWrapper { - public: - static bool matches(const AbstractViewItem &a, const AbstractViewItem &b) - { - if (typeid(a) != typeid(b)) { - return false; - } - /* TODO should match the view as well. */ - return a.matches(b); - } - - static bool can_rename(const AbstractViewItem &item) - { - const AbstractView &view = item.get_view(); - return !view.is_renaming() && item.supports_renaming(); - } - - static bool drag_start(bContext &C, const AbstractViewItem &item) - { - const std::unique_ptr drag_controller = - item.create_drag_controller(); - if (!drag_controller) { - return false; - } - - WM_event_start_drag(&C, - ICON_NONE, - drag_controller->get_drag_type(), - drag_controller->create_drag_data(), - 0, - WM_DRAG_FREE_DATA); - drag_controller->on_drag_start(); - - return true; - } - - static bool can_drop(const AbstractViewItem &item, - const wmDrag &drag, - const char **r_disabled_hint) - { - const std::unique_ptr drop_controller = - item.create_drop_controller(); - if (!drop_controller) { - return false; - } - - return drop_controller->can_drop(drag, r_disabled_hint); - } - - static std::string drop_tooltip(const AbstractViewItem &item, const wmDrag &drag) - { - const std::unique_ptr drop_controller = - item.create_drop_controller(); - if (!drop_controller) { - return {}; - } - - return drop_controller->drop_tooltip(drag); - } - - static bool drop_handle(bContext &C, const AbstractViewItem &item, const ListBase &drags) - { - std::unique_ptr drop_controller = - item.create_drop_controller(); - - const char *disabled_hint_dummy = nullptr; - LISTBASE_FOREACH (const wmDrag *, drag, &drags) { - if (drop_controller->can_drop(*drag, &disabled_hint_dummy)) { - return drop_controller->on_drop(&C, *drag); - } - } - - return false; - } -}; - -} // namespace blender::ui - -using namespace blender::ui; - -bool UI_view_item_is_active(const uiViewItemHandle *item_handle) -{ - const AbstractViewItem &item = reinterpret_cast(*item_handle); - return item.is_active(); -} - -bool UI_view_item_matches(const uiViewItemHandle *a_handle, const uiViewItemHandle *b_handle) -{ - const AbstractViewItem &a = reinterpret_cast(*a_handle); - const AbstractViewItem &b = reinterpret_cast(*b_handle); - return ViewItemAPIWrapper::matches(a, b); -} - -bool UI_view_item_can_rename(const uiViewItemHandle *item_handle) -{ - const AbstractViewItem &item = reinterpret_cast(*item_handle); - return ViewItemAPIWrapper::can_rename(item); -} - -void UI_view_item_begin_rename(uiViewItemHandle *item_handle) -{ - AbstractViewItem &item = reinterpret_cast(*item_handle); - item.begin_renaming(); -} - -void UI_view_item_context_menu_build(bContext *C, - const uiViewItemHandle *item_handle, - uiLayout *column) -{ - const AbstractViewItem &item = reinterpret_cast(*item_handle); - item.build_context_menu(*C, *column); -} - -bool UI_view_item_drag_start(bContext *C, const uiViewItemHandle *item_) -{ - const AbstractViewItem &item = reinterpret_cast(*item_); - return ViewItemAPIWrapper::drag_start(*C, item); -} - -bool UI_view_item_can_drop(const uiViewItemHandle *item_, - const wmDrag *drag, - const char **r_disabled_hint) -{ - const AbstractViewItem &item = reinterpret_cast(*item_); - return ViewItemAPIWrapper::can_drop(item, *drag, r_disabled_hint); -} - -char *UI_view_item_drop_tooltip(const uiViewItemHandle *item_, const wmDrag *drag) -{ - const AbstractViewItem &item = reinterpret_cast(*item_); - - const std::string tooltip = ViewItemAPIWrapper::drop_tooltip(item, *drag); - return tooltip.empty() ? nullptr : BLI_strdup(tooltip.c_str()); -} - -bool UI_view_item_drop_handle(bContext *C, const uiViewItemHandle *item_, const ListBase *drags) -{ - const AbstractViewItem &item = reinterpret_cast(*item_); - return ViewItemAPIWrapper::drop_handle(*C, item, *drags); -} - -/** \} */ diff --git a/source/blender/editors/interface/grid_view.cc b/source/blender/editors/interface/grid_view.cc deleted file mode 100644 index 37fbb33f83b..00000000000 --- a/source/blender/editors/interface/grid_view.cc +++ /dev/null @@ -1,464 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup edinterface - */ - -#include -#include - -#include "BLI_index_range.hh" - -#include "WM_types.h" - -#include "UI_interface.h" -#include "interface_intern.h" - -#include "UI_grid_view.hh" - -namespace blender::ui { - -/* ---------------------------------------------------------------------- */ - -AbstractGridView::AbstractGridView() : style_(UI_preview_tile_size_x(), UI_preview_tile_size_y()) -{ -} - -AbstractGridViewItem &AbstractGridView::add_item(std::unique_ptr item) -{ - items_.append(std::move(item)); - - AbstractGridViewItem &added_item = *items_.last(); - added_item.view_ = this; - - item_map_.add(added_item.identifier_, &added_item); - - return added_item; -} - -void AbstractGridView::foreach_item(ItemIterFn iter_fn) const -{ - for (const auto &item_ptr : items_) { - iter_fn(*item_ptr); - } -} - -AbstractGridViewItem *AbstractGridView::find_matching_item( - const AbstractGridViewItem &item_to_match, const AbstractGridView &view_to_search_in) const -{ - AbstractGridViewItem *const *match = view_to_search_in.item_map_.lookup_ptr( - item_to_match.identifier_); - BLI_assert(!match || item_to_match.matches(**match)); - - return match ? *match : nullptr; -} - -void AbstractGridView::change_state_delayed() -{ - BLI_assert_msg( - is_reconstructed(), - "These state changes are supposed to be delayed until reconstruction is completed"); - foreach_item([](AbstractGridViewItem &item) { item.change_state_delayed(); }); -} - -void AbstractGridView::update_children_from_old(const AbstractView &old_view) -{ - const AbstractGridView &old_grid_view = dynamic_cast(old_view); - - foreach_item([this, &old_grid_view](AbstractGridViewItem &new_item) { - const AbstractGridViewItem *matching_old_item = find_matching_item(new_item, old_grid_view); - if (!matching_old_item) { - return; - } - - new_item.update_from_old(*matching_old_item); - }); -} - -const GridViewStyle &AbstractGridView::get_style() const -{ - return style_; -} - -int AbstractGridView::get_item_count() const -{ - return items_.size(); -} - -GridViewStyle::GridViewStyle(int width, int height) : tile_width(width), tile_height(height) -{ -} - -/* ---------------------------------------------------------------------- */ - -AbstractGridViewItem::AbstractGridViewItem(StringRef identifier) : identifier_(identifier) -{ -} - -bool AbstractGridViewItem::matches(const AbstractViewItem &other) const -{ - const AbstractGridViewItem &other_grid_item = dynamic_cast(other); - return identifier_ == other_grid_item.identifier_; -} - -void AbstractGridViewItem::grid_tile_click_fn(struct bContext * /*C*/, - void *but_arg1, - void * /*arg2*/) -{ - uiButViewItem *view_item_but = (uiButViewItem *)but_arg1; - AbstractGridViewItem &grid_item = reinterpret_cast( - *view_item_but->view_item); - - grid_item.activate(); -} - -void AbstractGridViewItem::add_grid_tile_button(uiBlock &block) -{ - const GridViewStyle &style = get_view().get_style(); - view_item_but_ = (uiButViewItem *)uiDefBut(&block, - UI_BTYPE_VIEW_ITEM, - 0, - "", - 0, - 0, - style.tile_width, - style.tile_height, - nullptr, - 0, - 0, - 0, - 0, - ""); - - view_item_but_->view_item = reinterpret_cast(this); - UI_but_func_set(&view_item_but_->but, grid_tile_click_fn, view_item_but_, nullptr); -} - -void AbstractGridViewItem::on_activate() -{ - /* Do nothing by default. */ -} - -std::optional AbstractGridViewItem::should_be_active() const -{ - return std::nullopt; -} - -void AbstractGridViewItem::change_state_delayed() -{ - const std::optional should_be_active = this->should_be_active(); - if (should_be_active.has_value() && *should_be_active) { - activate(); - } -} - -void AbstractGridViewItem::activate() -{ - BLI_assert_msg(get_view().is_reconstructed(), - "Item activation can't be done until reconstruction is completed"); - - if (is_active()) { - return; - } - - /* Deactivate other items in the tree. */ - get_view().foreach_item([](auto &item) { item.deactivate(); }); - - on_activate(); - - is_active_ = true; -} - -void AbstractGridViewItem::deactivate() -{ - is_active_ = false; -} - -const AbstractGridView &AbstractGridViewItem::get_view() const -{ - if (UNLIKELY(!view_)) { - throw std::runtime_error( - "Invalid state, item must be added through AbstractGridView::add_item()"); - } - return *view_; -} - -/* ---------------------------------------------------------------------- */ - -/** - * Helper for only adding layout items for grid items that are actually in view. 3 main functions: - * - #is_item_visible(): Query if an item of a given index is visible in the view (others should be - * skipped when building the layout). - * - #fill_layout_before_visible(): Add empty space to the layout before a visible row is drawn, so - * the layout height is the same as if all items were added (important to get the correct scroll - * height). - * - #fill_layout_after_visible(): Same thing, just adds empty space for after the last visible - * row. - * - * Does two assumptions: - * - Top-to-bottom flow (ymax = 0 and ymin < 0). If that's not good enough, View2D should - * probably provide queries for the scroll offset. - * - Only vertical scrolling. For horizontal scrolling, spacers would have to be added on the - * side(s) as well. - */ -class BuildOnlyVisibleButtonsHelper { - const View2D &v2d_; - const AbstractGridView &grid_view_; - const GridViewStyle &style_; - const int cols_per_row_ = 0; - /* Indices of items within the view. Calculated by constructor */ - IndexRange visible_items_range_{}; - - public: - BuildOnlyVisibleButtonsHelper(const View2D &v2d, - const AbstractGridView &grid_view, - int cols_per_row); - - bool is_item_visible(int item_idx) const; - void fill_layout_before_visible(uiBlock &block) const; - void fill_layout_after_visible(uiBlock &block) const; - - private: - IndexRange get_visible_range() const; - void add_spacer_button(uiBlock &block, int row_count) const; -}; - -BuildOnlyVisibleButtonsHelper::BuildOnlyVisibleButtonsHelper(const View2D &v2d, - const AbstractGridView &grid_view, - const int cols_per_row) - : v2d_(v2d), grid_view_(grid_view), style_(grid_view.get_style()), cols_per_row_(cols_per_row) -{ - visible_items_range_ = get_visible_range(); -} - -IndexRange BuildOnlyVisibleButtonsHelper::get_visible_range() const -{ - int first_idx_in_view = 0; - int max_items_in_view = 0; - - const float scroll_ofs_y = abs(v2d_.cur.ymax - v2d_.tot.ymax); - if (!IS_EQF(scroll_ofs_y, 0)) { - const int scrolled_away_rows = (int)scroll_ofs_y / style_.tile_height; - - first_idx_in_view = scrolled_away_rows * cols_per_row_; - } - - const float view_height = BLI_rctf_size_y(&v2d_.cur); - const int count_rows_in_view = std::max(round_fl_to_int(view_height / style_.tile_height), 1); - max_items_in_view = (count_rows_in_view + 1) * cols_per_row_; - - BLI_assert(max_items_in_view > 0); - return IndexRange(first_idx_in_view, max_items_in_view); -} - -bool BuildOnlyVisibleButtonsHelper::is_item_visible(const int item_idx) const -{ - return visible_items_range_.contains(item_idx); -} - -void BuildOnlyVisibleButtonsHelper::fill_layout_before_visible(uiBlock &block) const -{ - const float scroll_ofs_y = abs(v2d_.cur.ymax - v2d_.tot.ymax); - - if (IS_EQF(scroll_ofs_y, 0)) { - return; - } - - const int scrolled_away_rows = (int)scroll_ofs_y / style_.tile_height; - add_spacer_button(block, scrolled_away_rows); -} - -void BuildOnlyVisibleButtonsHelper::fill_layout_after_visible(uiBlock &block) const -{ - const int last_item_idx = grid_view_.get_item_count() - 1; - const int last_visible_idx = visible_items_range_.last(); - - if (last_item_idx > last_visible_idx) { - const int remaining_rows = (cols_per_row_ > 0) ? - (last_item_idx - last_visible_idx) / cols_per_row_ : - 0; - BuildOnlyVisibleButtonsHelper::add_spacer_button(block, remaining_rows); - } -} - -void BuildOnlyVisibleButtonsHelper::add_spacer_button(uiBlock &block, const int row_count) const -{ - /* UI code only supports button dimensions of `signed short` size, the layout height we want to - * fill may be bigger than that. So add multiple labels of the maximum size if necessary. */ - for (int remaining_rows = row_count; remaining_rows > 0;) { - const short row_count_this_iter = std::min( - std::numeric_limits::max() / style_.tile_height, remaining_rows); - - uiDefBut(&block, - UI_BTYPE_LABEL, - 0, - "", - 0, - 0, - UI_UNIT_X, - row_count_this_iter * style_.tile_height, - nullptr, - 0, - 0, - 0, - 0, - ""); - remaining_rows -= row_count_this_iter; - } -} - -/* ---------------------------------------------------------------------- */ - -class GridViewLayoutBuilder { - uiBlock &block_; - - friend class GridViewBuilder; - - public: - GridViewLayoutBuilder(uiBlock &block); - - void build_from_view(const AbstractGridView &grid_view, const View2D &v2d) const; - - private: - void build_grid_tile(uiLayout &grid_layout, AbstractGridViewItem &item) const; - - uiLayout *current_layout() const; -}; - -GridViewLayoutBuilder::GridViewLayoutBuilder(uiBlock &block) : block_(block) -{ -} - -void GridViewLayoutBuilder::build_grid_tile(uiLayout &grid_layout, - AbstractGridViewItem &item) const -{ - uiLayout *overlap = uiLayoutOverlap(&grid_layout); - - item.add_grid_tile_button(block_); - item.build_grid_tile(*uiLayoutRow(overlap, false)); -} - -void GridViewLayoutBuilder::build_from_view(const AbstractGridView &grid_view, - const View2D &v2d) const -{ - uiLayout *prev_layout = current_layout(); - - uiLayout &layout = *uiLayoutColumn(current_layout(), false); - const GridViewStyle &style = grid_view.get_style(); - - const int cols_per_row = std::max(uiLayoutGetWidth(&layout) / style.tile_width, 1); - - BuildOnlyVisibleButtonsHelper build_visible_helper(v2d, grid_view, cols_per_row); - - build_visible_helper.fill_layout_before_visible(block_); - - /* Use `-cols_per_row` because the grid layout uses a multiple of the passed absolute value for - * the number of columns then, rather than distributing the number of items evenly over rows and - * stretching the items to fit (see #uiLayoutItemGridFlow.columns_len). */ - uiLayout *grid_layout = uiLayoutGridFlow(&layout, true, -cols_per_row, true, true, true); - - int item_idx = 0; - grid_view.foreach_item([&](AbstractGridViewItem &item) { - /* Skip if item isn't visible. */ - if (!build_visible_helper.is_item_visible(item_idx)) { - item_idx++; - return; - } - - build_grid_tile(*grid_layout, item); - item_idx++; - }); - - /* If there are not enough items to fill the layout, add padding items so the layout doesn't - * stretch over the entire width. */ - if (grid_view.get_item_count() < cols_per_row) { - for (int padding_item_idx = 0; padding_item_idx < (cols_per_row - grid_view.get_item_count()); - padding_item_idx++) { - uiItemS(grid_layout); - } - } - - UI_block_layout_set_current(&block_, prev_layout); - - build_visible_helper.fill_layout_after_visible(block_); -} - -uiLayout *GridViewLayoutBuilder::current_layout() const -{ - return block_.curlayout; -} - -/* ---------------------------------------------------------------------- */ - -GridViewBuilder::GridViewBuilder(uiBlock &block) : block_(block) -{ -} - -void GridViewBuilder::build_grid_view(AbstractGridView &grid_view, const View2D &v2d) -{ - grid_view.build_items(); - grid_view.update_from_old(block_); - grid_view.change_state_delayed(); - - GridViewLayoutBuilder builder(block_); - builder.build_from_view(grid_view, v2d); -} - -/* ---------------------------------------------------------------------- */ - -PreviewGridItem::PreviewGridItem(StringRef identifier, StringRef label, int preview_icon_id) - : AbstractGridViewItem(identifier), label(label), preview_icon_id(preview_icon_id) -{ -} - -void PreviewGridItem::build_grid_tile(uiLayout &layout) const -{ - const GridViewStyle &style = get_view().get_style(); - uiBlock *block = uiLayoutGetBlock(&layout); - - uiBut *but = uiDefBut(block, - UI_BTYPE_PREVIEW_TILE, - 0, - label.c_str(), - 0, - 0, - style.tile_width, - style.tile_height, - nullptr, - 0, - 0, - 0, - 0, - ""); - ui_def_but_icon(but, - preview_icon_id, - /* NOLINTNEXTLINE: bugprone-suspicious-enum-usage */ - UI_HAS_ICON | UI_BUT_ICON_PREVIEW); -} - -void PreviewGridItem::set_on_activate_fn(ActivateFn fn) -{ - activate_fn_ = fn; -} - -void PreviewGridItem::set_is_active_fn(IsActiveFn fn) -{ - is_active_fn_ = fn; -} - -void PreviewGridItem::on_activate() -{ - if (activate_fn_) { - activate_fn_(*this); - } -} - -std::optional PreviewGridItem::should_be_active() const -{ - if (is_active_fn_) { - return is_active_fn_(); - } - return std::nullopt; -} - -} // namespace blender::ui diff --git a/source/blender/editors/interface/interface_view.cc b/source/blender/editors/interface/interface_view.cc deleted file mode 100644 index b35f6d2c969..00000000000 --- a/source/blender/editors/interface/interface_view.cc +++ /dev/null @@ -1,190 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup edinterface - * - * This part of the UI-View API is mostly needed to support persistent state of items within the - * view. Views are stored in #uiBlock's, and kept alive with it until after the next redraw. So we - * can compare the old view items with the new view items and keep state persistent for matching - * ones. - */ - -#include -#include -#include - -#include "DNA_screen_types.h" - -#include "BKE_screen.h" - -#include "BLI_listbase.h" - -#include "ED_screen.h" - -#include "interface_intern.h" - -#include "UI_interface.hh" - -#include "UI_abstract_view.hh" -#include "UI_grid_view.hh" -#include "UI_tree_view.hh" - -using namespace blender; -using namespace blender::ui; - -/** - * Wrapper to store views in a #ListBase, addressable via an identifier. - */ -struct ViewLink : public Link { - std::string idname; - std::unique_ptr view; -}; - -template -static T *ui_block_add_view_impl(uiBlock &block, - StringRef idname, - std::unique_ptr view) -{ - ViewLink *view_link = MEM_new(__func__); - BLI_addtail(&block.views, view_link); - - view_link->view = std::move(view); - view_link->idname = idname; - - return dynamic_cast(view_link->view.get()); -} - -AbstractGridView *UI_block_add_view(uiBlock &block, - StringRef idname, - std::unique_ptr grid_view) -{ - return ui_block_add_view_impl(block, idname, std::move(grid_view)); -} - -AbstractTreeView *UI_block_add_view(uiBlock &block, - StringRef idname, - std::unique_ptr tree_view) -{ - return ui_block_add_view_impl(block, idname, std::move(tree_view)); -} - -void ui_block_free_views(uiBlock *block) -{ - LISTBASE_FOREACH_MUTABLE (ViewLink *, link, &block->views) { - MEM_delete(link); - } -} - -void UI_block_views_listen(const uiBlock *block, const wmRegionListenerParams *listener_params) -{ - ARegion *region = listener_params->region; - - LISTBASE_FOREACH (ViewLink *, view_link, &block->views) { - if (view_link->view->listen(*listener_params->notifier)) { - ED_region_tag_redraw(region); - } - } -} - -uiViewItemHandle *UI_block_view_find_item_at(const ARegion *region, const int xy[2]) -{ - uiButViewItem *item_but = (uiButViewItem *)ui_view_item_find_mouse_over(region, xy); - if (!item_but) { - return nullptr; - } - - return item_but->view_item; -} - -uiViewItemHandle *UI_block_view_find_active_item(const ARegion *region) -{ - uiButViewItem *item_but = (uiButViewItem *)ui_view_item_find_active(region); - if (!item_but) { - return nullptr; - } - - return item_but->view_item; -} - -static StringRef ui_block_view_find_idname(const uiBlock &block, const AbstractView &view) -{ - /* First get the idname the of the view we're looking for. */ - LISTBASE_FOREACH (ViewLink *, view_link, &block.views) { - if (view_link->view.get() == &view) { - return view_link->idname; - } - } - - return {}; -} - -template -static T *ui_block_view_find_matching_in_old_block_impl(const uiBlock &new_block, - const T &new_view) -{ - uiBlock *old_block = new_block.oldblock; - if (!old_block) { - return nullptr; - } - - StringRef idname = ui_block_view_find_idname(new_block, new_view); - if (idname.is_empty()) { - return nullptr; - } - - LISTBASE_FOREACH (ViewLink *, old_view_link, &old_block->views) { - if (old_view_link->idname == idname) { - return dynamic_cast(old_view_link->view.get()); - } - } - - return nullptr; -} - -uiViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block, - const uiViewHandle *new_view_handle) -{ - BLI_assert(new_block && new_view_handle); - const AbstractView &new_view = reinterpret_cast(*new_view_handle); - - AbstractView *old_view = ui_block_view_find_matching_in_old_block_impl(*new_block, new_view); - return reinterpret_cast(old_view); -} - -uiButViewItem *ui_block_view_find_matching_view_item_but_in_old_block( - const uiBlock *new_block, const uiViewItemHandle *new_item_handle) -{ - uiBlock *old_block = new_block->oldblock; - if (!old_block) { - return nullptr; - } - - const AbstractViewItem &new_item = *reinterpret_cast(new_item_handle); - const AbstractView *old_view = ui_block_view_find_matching_in_old_block_impl( - *new_block, new_item.get_view()); - if (!old_view) { - return nullptr; - } - - LISTBASE_FOREACH (uiBut *, old_but, &old_block->buttons) { - if (old_but->type != UI_BTYPE_VIEW_ITEM) { - continue; - } - uiButViewItem *old_item_but = (uiButViewItem *)old_but; - if (!old_item_but->view_item) { - continue; - } - AbstractViewItem &old_item = *reinterpret_cast(old_item_but->view_item); - /* Check if the item is from the expected view. */ - if (&old_item.get_view() != old_view) { - continue; - } - - if (UI_view_item_matches(reinterpret_cast(&new_item), - reinterpret_cast(&old_item))) { - return old_item_but; - } - } - - return nullptr; -} diff --git a/source/blender/editors/interface/tree_view.cc b/source/blender/editors/interface/tree_view.cc deleted file mode 100644 index c224226ba17..00000000000 --- a/source/blender/editors/interface/tree_view.cc +++ /dev/null @@ -1,554 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup edinterface - */ - -#include "DNA_userdef_types.h" -#include "DNA_windowmanager_types.h" - -#include "BKE_context.h" - -#include "BLT_translation.h" - -#include "interface_intern.h" - -#include "UI_interface.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "UI_tree_view.hh" - -namespace blender::ui { - -/* ---------------------------------------------------------------------- */ - -/** - * Add a tree-item to the container. This is the only place where items should be added, it - * handles important invariants! - */ -AbstractTreeViewItem &TreeViewItemContainer::add_tree_item( - std::unique_ptr item) -{ - children_.append(std::move(item)); - - /* The first item that will be added to the root sets this. */ - if (root_ == nullptr) { - root_ = this; - } - AbstractTreeView &tree_view = static_cast(*root_); - AbstractTreeViewItem &added_item = *children_.last(); - added_item.root_ = root_; - tree_view.register_item(added_item); - - if (root_ != this) { - /* Any item that isn't the root can be assumed to the a #AbstractTreeViewItem. Not entirely - * nice to static_cast this, but well... */ - added_item.parent_ = static_cast(this); - } - - return added_item; -} - -void TreeViewItemContainer::foreach_item_recursive(ItemIterFn iter_fn, IterOptions options) const -{ - for (const auto &child : children_) { - iter_fn(*child); - if (bool(options & IterOptions::SkipCollapsed) && child->is_collapsed()) { - continue; - } - - child->foreach_item_recursive(iter_fn, options); - } -} - -/* ---------------------------------------------------------------------- */ - -void AbstractTreeView::foreach_item(ItemIterFn iter_fn, IterOptions options) const -{ - foreach_item_recursive(iter_fn, options); -} - -void AbstractTreeView::update_children_from_old(const AbstractView &old_view) -{ - const AbstractTreeView &old_tree_view = dynamic_cast(old_view); - - update_children_from_old_recursive(*this, old_tree_view); -} - -void AbstractTreeView::update_children_from_old_recursive(const TreeViewOrItem &new_items, - const TreeViewOrItem &old_items) -{ - for (const auto &new_item : new_items.children_) { - AbstractTreeViewItem *matching_old_item = find_matching_child(*new_item, old_items); - if (!matching_old_item) { - continue; - } - - new_item->update_from_old(*matching_old_item); - - /* Recurse into children of the matched item. */ - update_children_from_old_recursive(*new_item, *matching_old_item); - } -} - -AbstractTreeViewItem *AbstractTreeView::find_matching_child( - const AbstractTreeViewItem &lookup_item, const TreeViewOrItem &items) -{ - for (const auto &iter_item : items.children_) { - if (lookup_item.matches_single(*iter_item)) { - /* We have a matching item! */ - return iter_item.get(); - } - } - - return nullptr; -} - -void AbstractTreeView::change_state_delayed() -{ - BLI_assert_msg( - is_reconstructed(), - "These state changes are supposed to be delayed until reconstruction is completed"); - foreach_item([](AbstractTreeViewItem &item) { item.change_state_delayed(); }); -} - -/* ---------------------------------------------------------------------- */ - -void AbstractTreeViewItem::tree_row_click_fn(struct bContext * /*C*/, - void *but_arg1, - void * /*arg2*/) -{ - uiButViewItem *item_but = (uiButViewItem *)but_arg1; - AbstractTreeViewItem &tree_item = reinterpret_cast(*item_but->view_item); - - tree_item.activate(); - /* Not only activate the item, also show its children. Maybe this should be optional, or - * controlled by the specific tree-view. */ - tree_item.set_collapsed(false); -} - -void AbstractTreeViewItem::add_treerow_button(uiBlock &block) -{ - /* For some reason a width > (UI_UNIT_X * 2) make the layout system use all available width. */ - view_item_but_ = (uiButViewItem *)uiDefBut( - &block, UI_BTYPE_VIEW_ITEM, 0, "", 0, 0, UI_UNIT_X * 10, UI_UNIT_Y, nullptr, 0, 0, 0, 0, ""); - - view_item_but_->view_item = reinterpret_cast(this); - UI_but_func_set(&view_item_but_->but, tree_row_click_fn, view_item_but_, nullptr); -} - -void AbstractTreeViewItem::add_indent(uiLayout &row) const -{ - uiBlock *block = uiLayoutGetBlock(&row); - uiLayout *subrow = uiLayoutRow(&row, true); - uiLayoutSetFixedSize(subrow, true); - - const float indent_size = count_parents() * UI_DPI_ICON_SIZE; - uiDefBut(block, UI_BTYPE_SEPR, 0, "", 0, 0, indent_size, 0, nullptr, 0.0, 0.0, 0, 0, ""); - - /* Indent items without collapsing icon some more within their parent. Makes it clear that they - * are actually nested and not just a row at the same level without a chevron. */ - if (!is_collapsible() && parent_) { - uiDefBut(block, UI_BTYPE_SEPR, 0, "", 0, 0, 0.2f * UI_UNIT_X, 0, nullptr, 0.0, 0.0, 0, 0, ""); - } - - /* Restore. */ - UI_block_layout_set_current(block, &row); -} - -void AbstractTreeViewItem::collapse_chevron_click_fn(struct bContext *C, - void * /*but_arg1*/, - void * /*arg2*/) -{ - /* There's no data we could pass to this callback. It must be either the button itself or a - * consistent address to match buttons over redraws. So instead of passing it somehow, just - * lookup the hovered item via context here. */ - - const wmWindow *win = CTX_wm_window(C); - const ARegion *region = CTX_wm_region(C); - uiViewItemHandle *hovered_item_handle = UI_block_view_find_item_at(region, win->eventstate->xy); - - AbstractTreeViewItem *hovered_item = from_item_handle(hovered_item_handle); - BLI_assert(hovered_item != nullptr); - - hovered_item->toggle_collapsed(); - /* When collapsing an item with an active child, make this collapsed item active instead so the - * active item stays visible. */ - if (hovered_item->has_active_child()) { - hovered_item->activate(); - } -} - -bool AbstractTreeViewItem::is_collapse_chevron_but(const uiBut *but) -{ - return but->type == UI_BTYPE_BUT_TOGGLE && ELEM(but->icon, ICON_TRIA_RIGHT, ICON_TRIA_DOWN) && - (but->func == collapse_chevron_click_fn); -} - -void AbstractTreeViewItem::add_collapse_chevron(uiBlock &block) const -{ - if (!is_collapsible()) { - return; - } - - const BIFIconID icon = is_collapsed() ? ICON_TRIA_RIGHT : ICON_TRIA_DOWN; - uiBut *but = uiDefIconBut( - &block, UI_BTYPE_BUT_TOGGLE, 0, icon, 0, 0, UI_UNIT_X, UI_UNIT_Y, nullptr, 0, 0, 0, 0, ""); - /* Note that we're passing the tree-row button here, not the chevron one. */ - UI_but_func_set(but, collapse_chevron_click_fn, nullptr, nullptr); - UI_but_flag_disable(but, UI_BUT_UNDO); - - /* Check if the query for the button matches the created button. */ - BLI_assert(is_collapse_chevron_but(but)); -} - -void AbstractTreeViewItem::add_rename_button(uiLayout &row) -{ - uiBlock *block = uiLayoutGetBlock(&row); - eUIEmbossType previous_emboss = UI_block_emboss_get(block); - - uiLayoutRow(&row, false); - /* Enable emboss for the text button. */ - UI_block_emboss_set(block, UI_EMBOSS); - - AbstractViewItem::add_rename_button(*block); - - UI_block_emboss_set(block, previous_emboss); - UI_block_layout_set_current(block, &row); -} - -bool AbstractTreeViewItem::has_active_child() const -{ - bool found = false; - foreach_item_recursive([&found](const AbstractTreeViewItem &item) { - if (item.is_active()) { - found = true; - } - }); - - return found; -} - -void AbstractTreeViewItem::on_activate() -{ - /* Do nothing by default. */ -} - -std::optional AbstractTreeViewItem::should_be_active() const -{ - return std::nullopt; -} - -bool AbstractTreeViewItem::supports_collapsing() const -{ - return true; -} - -StringRef AbstractTreeViewItem::get_rename_string() const -{ - return label_; -} - -bool AbstractTreeViewItem::rename(StringRefNull new_name) -{ - /* It is important to update the label after renaming, so #AbstractTreeViewItem::matches_single() - * recognizes the item. (It only compares labels by default.) */ - label_ = new_name; - return true; -} - -void AbstractTreeViewItem::update_from_old(const AbstractViewItem &old) -{ - AbstractViewItem::update_from_old(old); - - const AbstractTreeViewItem &old_tree_item = dynamic_cast(old); - is_open_ = old_tree_item.is_open_; -} - -bool AbstractTreeViewItem::matches_single(const AbstractTreeViewItem &other) const -{ - return label_ == other.label_; -} - -AbstractTreeView &AbstractTreeViewItem::get_tree_view() const -{ - return dynamic_cast(get_view()); -} - -int AbstractTreeViewItem::count_parents() const -{ - int i = 0; - for (AbstractTreeViewItem *parent = parent_; parent; parent = parent->parent_) { - i++; - } - return i; -} - -void AbstractTreeViewItem::activate() -{ - BLI_assert_msg(get_tree_view().is_reconstructed(), - "Item activation can't be done until reconstruction is completed"); - - if (is_active()) { - return; - } - - /* Deactivate other items in the tree. */ - get_tree_view().foreach_item([](auto &item) { item.deactivate(); }); - - on_activate(); - /* Make sure the active item is always visible. */ - ensure_parents_uncollapsed(); - - is_active_ = true; -} - -void AbstractTreeViewItem::deactivate() -{ - is_active_ = false; -} - -bool AbstractTreeViewItem::is_hovered() const -{ - BLI_assert_msg(get_tree_view().is_reconstructed(), - "State can't be queried until reconstruction is completed"); - BLI_assert_msg(view_item_but_ != nullptr, - "Hovered state can't be queried before the tree row is being built"); - - const uiViewItemHandle *this_item_handle = reinterpret_cast(this); - /* The new layout hasn't finished construction yet, so the final state of the button is unknown. - * Get the matching button from the previous redraw instead. */ - uiButViewItem *old_item_but = ui_block_view_find_matching_view_item_but_in_old_block( - view_item_but_->but.block, this_item_handle); - return old_item_but && (old_item_but->but.flag & UI_ACTIVE); -} - -bool AbstractTreeViewItem::is_collapsed() const -{ - BLI_assert_msg(get_tree_view().is_reconstructed(), - "State can't be queried until reconstruction is completed"); - return is_collapsible() && !is_open_; -} - -void AbstractTreeViewItem::toggle_collapsed() -{ - is_open_ = !is_open_; -} - -void AbstractTreeViewItem::set_collapsed(bool collapsed) -{ - is_open_ = !collapsed; -} - -bool AbstractTreeViewItem::is_collapsible() const -{ - if (children_.is_empty()) { - return false; - } - return this->supports_collapsing(); -} - -void AbstractTreeViewItem::ensure_parents_uncollapsed() -{ - for (AbstractTreeViewItem *parent = parent_; parent; parent = parent->parent_) { - parent->set_collapsed(false); - } -} - -bool AbstractTreeViewItem::matches(const AbstractViewItem &other) const -{ - const AbstractTreeViewItem &other_tree_item = dynamic_cast(other); - - if (!matches_single(other_tree_item)) { - return false; - } - if (count_parents() != other_tree_item.count_parents()) { - return false; - } - - for (AbstractTreeViewItem *parent = parent_, *other_parent = other_tree_item.parent_; - parent && other_parent; - parent = parent->parent_, other_parent = other_parent->parent_) { - if (!parent->matches_single(*other_parent)) { - return false; - } - } - - return true; -} - -uiButViewItem *AbstractTreeViewItem::view_item_button() -{ - return view_item_but_; -} - -void AbstractTreeViewItem::change_state_delayed() -{ - const std::optional should_be_active = this->should_be_active(); - if (should_be_active.has_value() && *should_be_active) { - activate(); - } -} - -/* ---------------------------------------------------------------------- */ - -class TreeViewLayoutBuilder { - uiBlock &block_; - - friend TreeViewBuilder; - - public: - void build_from_tree(const AbstractTreeView &tree_view); - void build_row(AbstractTreeViewItem &item) const; - - uiBlock &block() const; - uiLayout *current_layout() const; - - private: - /* Created through #TreeViewBuilder. */ - TreeViewLayoutBuilder(uiBlock &block); - - static void polish_layout(const uiBlock &block); -}; - -TreeViewLayoutBuilder::TreeViewLayoutBuilder(uiBlock &block) : block_(block) -{ -} - -void TreeViewLayoutBuilder::build_from_tree(const AbstractTreeView &tree_view) -{ - uiLayout *prev_layout = current_layout(); - - uiLayout *box = uiLayoutBox(prev_layout); - uiLayoutColumn(box, false); - - tree_view.foreach_item([this](AbstractTreeViewItem &item) { build_row(item); }, - AbstractTreeView::IterOptions::SkipCollapsed); - - UI_block_layout_set_current(&block(), prev_layout); -} - -void TreeViewLayoutBuilder::polish_layout(const uiBlock &block) -{ - LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block.buttons) { - if (AbstractTreeViewItem::is_collapse_chevron_but(but) && but->next && - /* Embossed buttons with padding-less text padding look weird, so don't touch them. */ - ELEM(but->next->emboss, UI_EMBOSS_NONE, UI_EMBOSS_NONE_OR_STATUS)) { - UI_but_drawflag_enable(static_cast(but->next), UI_BUT_NO_TEXT_PADDING); - } - - if (but->type == UI_BTYPE_VIEW_ITEM) { - break; - } - } -} - -void TreeViewLayoutBuilder::build_row(AbstractTreeViewItem &item) const -{ - uiBlock &block_ = block(); - - uiLayout *prev_layout = current_layout(); - eUIEmbossType previous_emboss = UI_block_emboss_get(&block_); - - uiLayout *overlap = uiLayoutOverlap(prev_layout); - - uiLayoutRow(overlap, false); - /* Every item gets one! Other buttons can be overlapped on top. */ - item.add_treerow_button(block_); - - /* After adding tree-row button (would disable hover highlighting). */ - UI_block_emboss_set(&block_, UI_EMBOSS_NONE); - - uiLayout *row = uiLayoutRow(overlap, true); - item.add_indent(*row); - item.add_collapse_chevron(block_); - - if (item.is_renaming()) { - item.add_rename_button(*row); - } - else { - item.build_row(*row); - } - polish_layout(block_); - - UI_block_emboss_set(&block_, previous_emboss); - UI_block_layout_set_current(&block_, prev_layout); -} - -uiBlock &TreeViewLayoutBuilder::block() const -{ - return block_; -} - -uiLayout *TreeViewLayoutBuilder::current_layout() const -{ - return block().curlayout; -} - -/* ---------------------------------------------------------------------- */ - -TreeViewBuilder::TreeViewBuilder(uiBlock &block) : block_(block) -{ -} - -void TreeViewBuilder::build_tree_view(AbstractTreeView &tree_view) -{ - tree_view.build_tree(); - tree_view.update_from_old(block_); - tree_view.change_state_delayed(); - - TreeViewLayoutBuilder builder(block_); - builder.build_from_tree(tree_view); -} - -/* ---------------------------------------------------------------------- */ - -BasicTreeViewItem::BasicTreeViewItem(StringRef label, BIFIconID icon_) : icon(icon_) -{ - label_ = label; -} - -void BasicTreeViewItem::build_row(uiLayout &row) -{ - add_label(row); -} - -void BasicTreeViewItem::add_label(uiLayout &layout, StringRefNull label_override) -{ - const StringRefNull label = label_override.is_empty() ? StringRefNull(label_) : label_override; - - /* Some padding for labels without collapse chevron and no icon. Looks weird without. */ - if (icon == ICON_NONE && !is_collapsible()) { - uiItemS_ex(&layout, 0.8f); - } - uiItemL(&layout, IFACE_(label.c_str()), icon); -} - -void BasicTreeViewItem::on_activate() -{ - if (activate_fn_) { - activate_fn_(*this); - } -} - -void BasicTreeViewItem::set_on_activate_fn(ActivateFn fn) -{ - activate_fn_ = fn; -} - -void BasicTreeViewItem::set_is_active_fn(IsActiveFn is_active_fn) -{ - is_active_fn_ = is_active_fn; -} - -std::optional BasicTreeViewItem::should_be_active() const -{ - if (is_active_fn_) { - return is_active_fn_(); - } - return std::nullopt; -} - -} // namespace blender::ui diff --git a/source/blender/editors/interface/views/abstract_view.cc b/source/blender/editors/interface/views/abstract_view.cc new file mode 100644 index 00000000000..077c76a08f1 --- /dev/null +++ b/source/blender/editors/interface/views/abstract_view.cc @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + */ + +#include "interface_intern.h" + +#include "UI_abstract_view.hh" + +namespace blender::ui { + +void AbstractView::register_item(AbstractViewItem &item) +{ + /* Actually modifies the item, not the view. But for the public API it "feels" a bit nicer to + * have the view base class register the items, rather than setting the view on the item. */ + item.view_ = this; +} + +/* ---------------------------------------------------------------------- */ +/** \name View Reconstruction + * \{ */ + +bool AbstractView::is_reconstructed() const +{ + return is_reconstructed_; +} + +void AbstractView::update_from_old(uiBlock &new_block) +{ + uiBlock *old_block = new_block.oldblock; + if (!old_block) { + is_reconstructed_ = true; + return; + } + + uiViewHandle *old_view_handle = ui_block_view_find_matching_in_old_block( + &new_block, reinterpret_cast(this)); + if (old_view_handle == nullptr) { + /* Initial construction, nothing to update. */ + is_reconstructed_ = true; + return; + } + + AbstractView &old_view = reinterpret_cast(*old_view_handle); + + /* Update own persistent data. */ + /* Keep the rename buffer persistent while renaming! The rename button uses the buffer's + * pointer to identify itself over redraws. */ + rename_buffer_ = std::move(old_view.rename_buffer_); + old_view.rename_buffer_ = nullptr; + + update_children_from_old(old_view); + + /* Finished (re-)constructing the tree. */ + is_reconstructed_ = true; +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Default implementations of virtual functions + * \{ */ + +bool AbstractView::listen(const wmNotifier & /*notifier*/) const +{ + /* Nothing by default. */ + return false; +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Renaming + * \{ */ + +bool AbstractView::is_renaming() const +{ + return rename_buffer_ != nullptr; +} + +bool AbstractView::begin_renaming() +{ + if (is_renaming()) { + return false; + } + + rename_buffer_ = std::make_unique(); + return true; +} + +void AbstractView::end_renaming() +{ + BLI_assert(is_renaming()); + rename_buffer_ = nullptr; +} + +Span AbstractView::get_rename_buffer() const +{ + return *rename_buffer_; +} +MutableSpan AbstractView::get_rename_buffer() +{ + return *rename_buffer_; +} + +/** \} */ + +} // namespace blender::ui diff --git a/source/blender/editors/interface/views/abstract_view_item.cc b/source/blender/editors/interface/views/abstract_view_item.cc new file mode 100644 index 00000000000..f73183d07e9 --- /dev/null +++ b/source/blender/editors/interface/views/abstract_view_item.cc @@ -0,0 +1,373 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + */ + +#include "BKE_context.h" + +#include "BLI_listbase.h" +#include "BLI_string.h" + +#include "WM_api.h" + +#include "UI_interface.h" +#include "interface_intern.h" + +#include "UI_abstract_view.hh" + +namespace blender::ui { + +/* ---------------------------------------------------------------------- */ +/** \name View Reconstruction + * \{ */ + +void AbstractViewItem::update_from_old(const AbstractViewItem &old) +{ + is_active_ = old.is_active_; + is_renaming_ = old.is_renaming_; +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Renaming + * \{ */ + +bool AbstractViewItem::supports_renaming() const +{ + /* No renaming by default. */ + return false; +} +bool AbstractViewItem::rename(StringRefNull /*new_name*/) +{ + /* No renaming by default. */ + return false; +} + +StringRef AbstractViewItem::get_rename_string() const +{ + /* No rename string by default. */ + return {}; +} + +bool AbstractViewItem::is_renaming() const +{ + return is_renaming_; +} + +void AbstractViewItem::begin_renaming() +{ + AbstractView &view = get_view(); + if (view.is_renaming() || !supports_renaming()) { + return; + } + + if (view.begin_renaming()) { + is_renaming_ = true; + } + + StringRef initial_str = get_rename_string(); + std::copy(std::begin(initial_str), std::end(initial_str), std::begin(view.get_rename_buffer())); +} + +void AbstractViewItem::rename_apply() +{ + const AbstractView &view = get_view(); + rename(view.get_rename_buffer().data()); + end_renaming(); +} + +void AbstractViewItem::end_renaming() +{ + if (!is_renaming()) { + return; + } + + is_renaming_ = false; + + AbstractView &view = get_view(); + view.end_renaming(); +} + +static AbstractViewItem *find_item_from_rename_button(const uiBut &rename_but) +{ + /* A minimal sanity check, can't do much more here. */ + BLI_assert(rename_but.type == UI_BTYPE_TEXT && rename_but.poin); + + LISTBASE_FOREACH (uiBut *, but, &rename_but.block->buttons) { + if (but->type != UI_BTYPE_VIEW_ITEM) { + continue; + } + + uiButViewItem *view_item_but = (uiButViewItem *)but; + AbstractViewItem *item = reinterpret_cast(view_item_but->view_item); + const AbstractView &view = item->get_view(); + + if (item->is_renaming() && (view.get_rename_buffer().data() == rename_but.poin)) { + return item; + } + } + + return nullptr; +} + +static void rename_button_fn(bContext *UNUSED(C), void *arg, char *UNUSED(origstr)) +{ + const uiBut *rename_but = static_cast(arg); + AbstractViewItem *item = find_item_from_rename_button(*rename_but); + BLI_assert(item); + item->rename_apply(); +} + +void AbstractViewItem::add_rename_button(uiBlock &block) +{ + AbstractView &view = get_view(); + uiBut *rename_but = uiDefBut(&block, + UI_BTYPE_TEXT, + 1, + "", + 0, + 0, + UI_UNIT_X * 10, + UI_UNIT_Y, + view.get_rename_buffer().data(), + 1.0f, + view.get_rename_buffer().size(), + 0, + 0, + ""); + + /* Gotta be careful with what's passed to the `arg1` here. Any view data will be freed once the + * callback is executed. */ + UI_but_func_rename_set(rename_but, rename_button_fn, rename_but); + UI_but_flag_disable(rename_but, UI_BUT_UNDO); + + const bContext *evil_C = reinterpret_cast(block.evil_C); + ARegion *region = CTX_wm_region(evil_C); + /* Returns false if the button was removed. */ + if (UI_but_active_only(evil_C, region, &block, rename_but) == false) { + end_renaming(); + } +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Context Menu + * \{ */ + +void AbstractViewItem::build_context_menu(bContext & /*C*/, uiLayout & /*column*/) const +{ + /* No context menu by default. */ +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Drag 'n Drop + * \{ */ + +std::unique_ptr AbstractViewItem::create_drag_controller() const +{ + /* There's no drag controller (and hence no drag support) by default. */ + return nullptr; +} + +std::unique_ptr AbstractViewItem::create_drop_controller() const +{ + /* There's no drop controller (and hence no drop support) by default. */ + return nullptr; +} + +AbstractViewItemDragController::AbstractViewItemDragController(AbstractView &view) : view_(view) +{ +} + +void AbstractViewItemDragController::on_drag_start() +{ + /* Do nothing by default. */ +} + +AbstractViewItemDropController::AbstractViewItemDropController(AbstractView &view) : view_(view) +{ +} + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name General Getters & Setters + * \{ */ + +AbstractView &AbstractViewItem::get_view() const +{ + if (UNLIKELY(!view_)) { + throw std::runtime_error( + "Invalid state, item must be registered through AbstractView::register_item()"); + } + return *view_; +} + +bool AbstractViewItem::is_active() const +{ + BLI_assert_msg(get_view().is_reconstructed(), + "State can't be queried until reconstruction is completed"); + return is_active_; +} + +/** \} */ + +} // namespace blender::ui + +/* ---------------------------------------------------------------------- */ +/** \name C-API + * \{ */ + +namespace blender::ui { + +/** + * Helper class to provide a higher level public (C-)API. Has access to private/protected view item + * members and ensures some invariants that way. + */ +class ViewItemAPIWrapper { + public: + static bool matches(const AbstractViewItem &a, const AbstractViewItem &b) + { + if (typeid(a) != typeid(b)) { + return false; + } + /* TODO should match the view as well. */ + return a.matches(b); + } + + static bool can_rename(const AbstractViewItem &item) + { + const AbstractView &view = item.get_view(); + return !view.is_renaming() && item.supports_renaming(); + } + + static bool drag_start(bContext &C, const AbstractViewItem &item) + { + const std::unique_ptr drag_controller = + item.create_drag_controller(); + if (!drag_controller) { + return false; + } + + WM_event_start_drag(&C, + ICON_NONE, + drag_controller->get_drag_type(), + drag_controller->create_drag_data(), + 0, + WM_DRAG_FREE_DATA); + drag_controller->on_drag_start(); + + return true; + } + + static bool can_drop(const AbstractViewItem &item, + const wmDrag &drag, + const char **r_disabled_hint) + { + const std::unique_ptr drop_controller = + item.create_drop_controller(); + if (!drop_controller) { + return false; + } + + return drop_controller->can_drop(drag, r_disabled_hint); + } + + static std::string drop_tooltip(const AbstractViewItem &item, const wmDrag &drag) + { + const std::unique_ptr drop_controller = + item.create_drop_controller(); + if (!drop_controller) { + return {}; + } + + return drop_controller->drop_tooltip(drag); + } + + static bool drop_handle(bContext &C, const AbstractViewItem &item, const ListBase &drags) + { + std::unique_ptr drop_controller = + item.create_drop_controller(); + + const char *disabled_hint_dummy = nullptr; + LISTBASE_FOREACH (const wmDrag *, drag, &drags) { + if (drop_controller->can_drop(*drag, &disabled_hint_dummy)) { + return drop_controller->on_drop(&C, *drag); + } + } + + return false; + } +}; + +} // namespace blender::ui + +using namespace blender::ui; + +bool UI_view_item_is_active(const uiViewItemHandle *item_handle) +{ + const AbstractViewItem &item = reinterpret_cast(*item_handle); + return item.is_active(); +} + +bool UI_view_item_matches(const uiViewItemHandle *a_handle, const uiViewItemHandle *b_handle) +{ + const AbstractViewItem &a = reinterpret_cast(*a_handle); + const AbstractViewItem &b = reinterpret_cast(*b_handle); + return ViewItemAPIWrapper::matches(a, b); +} + +bool UI_view_item_can_rename(const uiViewItemHandle *item_handle) +{ + const AbstractViewItem &item = reinterpret_cast(*item_handle); + return ViewItemAPIWrapper::can_rename(item); +} + +void UI_view_item_begin_rename(uiViewItemHandle *item_handle) +{ + AbstractViewItem &item = reinterpret_cast(*item_handle); + item.begin_renaming(); +} + +void UI_view_item_context_menu_build(bContext *C, + const uiViewItemHandle *item_handle, + uiLayout *column) +{ + const AbstractViewItem &item = reinterpret_cast(*item_handle); + item.build_context_menu(*C, *column); +} + +bool UI_view_item_drag_start(bContext *C, const uiViewItemHandle *item_) +{ + const AbstractViewItem &item = reinterpret_cast(*item_); + return ViewItemAPIWrapper::drag_start(*C, item); +} + +bool UI_view_item_can_drop(const uiViewItemHandle *item_, + const wmDrag *drag, + const char **r_disabled_hint) +{ + const AbstractViewItem &item = reinterpret_cast(*item_); + return ViewItemAPIWrapper::can_drop(item, *drag, r_disabled_hint); +} + +char *UI_view_item_drop_tooltip(const uiViewItemHandle *item_, const wmDrag *drag) +{ + const AbstractViewItem &item = reinterpret_cast(*item_); + + const std::string tooltip = ViewItemAPIWrapper::drop_tooltip(item, *drag); + return tooltip.empty() ? nullptr : BLI_strdup(tooltip.c_str()); +} + +bool UI_view_item_drop_handle(bContext *C, const uiViewItemHandle *item_, const ListBase *drags) +{ + const AbstractViewItem &item = reinterpret_cast(*item_); + return ViewItemAPIWrapper::drop_handle(*C, item, *drags); +} + +/** \} */ diff --git a/source/blender/editors/interface/views/grid_view.cc b/source/blender/editors/interface/views/grid_view.cc new file mode 100644 index 00000000000..37fbb33f83b --- /dev/null +++ b/source/blender/editors/interface/views/grid_view.cc @@ -0,0 +1,464 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + */ + +#include +#include + +#include "BLI_index_range.hh" + +#include "WM_types.h" + +#include "UI_interface.h" +#include "interface_intern.h" + +#include "UI_grid_view.hh" + +namespace blender::ui { + +/* ---------------------------------------------------------------------- */ + +AbstractGridView::AbstractGridView() : style_(UI_preview_tile_size_x(), UI_preview_tile_size_y()) +{ +} + +AbstractGridViewItem &AbstractGridView::add_item(std::unique_ptr item) +{ + items_.append(std::move(item)); + + AbstractGridViewItem &added_item = *items_.last(); + added_item.view_ = this; + + item_map_.add(added_item.identifier_, &added_item); + + return added_item; +} + +void AbstractGridView::foreach_item(ItemIterFn iter_fn) const +{ + for (const auto &item_ptr : items_) { + iter_fn(*item_ptr); + } +} + +AbstractGridViewItem *AbstractGridView::find_matching_item( + const AbstractGridViewItem &item_to_match, const AbstractGridView &view_to_search_in) const +{ + AbstractGridViewItem *const *match = view_to_search_in.item_map_.lookup_ptr( + item_to_match.identifier_); + BLI_assert(!match || item_to_match.matches(**match)); + + return match ? *match : nullptr; +} + +void AbstractGridView::change_state_delayed() +{ + BLI_assert_msg( + is_reconstructed(), + "These state changes are supposed to be delayed until reconstruction is completed"); + foreach_item([](AbstractGridViewItem &item) { item.change_state_delayed(); }); +} + +void AbstractGridView::update_children_from_old(const AbstractView &old_view) +{ + const AbstractGridView &old_grid_view = dynamic_cast(old_view); + + foreach_item([this, &old_grid_view](AbstractGridViewItem &new_item) { + const AbstractGridViewItem *matching_old_item = find_matching_item(new_item, old_grid_view); + if (!matching_old_item) { + return; + } + + new_item.update_from_old(*matching_old_item); + }); +} + +const GridViewStyle &AbstractGridView::get_style() const +{ + return style_; +} + +int AbstractGridView::get_item_count() const +{ + return items_.size(); +} + +GridViewStyle::GridViewStyle(int width, int height) : tile_width(width), tile_height(height) +{ +} + +/* ---------------------------------------------------------------------- */ + +AbstractGridViewItem::AbstractGridViewItem(StringRef identifier) : identifier_(identifier) +{ +} + +bool AbstractGridViewItem::matches(const AbstractViewItem &other) const +{ + const AbstractGridViewItem &other_grid_item = dynamic_cast(other); + return identifier_ == other_grid_item.identifier_; +} + +void AbstractGridViewItem::grid_tile_click_fn(struct bContext * /*C*/, + void *but_arg1, + void * /*arg2*/) +{ + uiButViewItem *view_item_but = (uiButViewItem *)but_arg1; + AbstractGridViewItem &grid_item = reinterpret_cast( + *view_item_but->view_item); + + grid_item.activate(); +} + +void AbstractGridViewItem::add_grid_tile_button(uiBlock &block) +{ + const GridViewStyle &style = get_view().get_style(); + view_item_but_ = (uiButViewItem *)uiDefBut(&block, + UI_BTYPE_VIEW_ITEM, + 0, + "", + 0, + 0, + style.tile_width, + style.tile_height, + nullptr, + 0, + 0, + 0, + 0, + ""); + + view_item_but_->view_item = reinterpret_cast(this); + UI_but_func_set(&view_item_but_->but, grid_tile_click_fn, view_item_but_, nullptr); +} + +void AbstractGridViewItem::on_activate() +{ + /* Do nothing by default. */ +} + +std::optional AbstractGridViewItem::should_be_active() const +{ + return std::nullopt; +} + +void AbstractGridViewItem::change_state_delayed() +{ + const std::optional should_be_active = this->should_be_active(); + if (should_be_active.has_value() && *should_be_active) { + activate(); + } +} + +void AbstractGridViewItem::activate() +{ + BLI_assert_msg(get_view().is_reconstructed(), + "Item activation can't be done until reconstruction is completed"); + + if (is_active()) { + return; + } + + /* Deactivate other items in the tree. */ + get_view().foreach_item([](auto &item) { item.deactivate(); }); + + on_activate(); + + is_active_ = true; +} + +void AbstractGridViewItem::deactivate() +{ + is_active_ = false; +} + +const AbstractGridView &AbstractGridViewItem::get_view() const +{ + if (UNLIKELY(!view_)) { + throw std::runtime_error( + "Invalid state, item must be added through AbstractGridView::add_item()"); + } + return *view_; +} + +/* ---------------------------------------------------------------------- */ + +/** + * Helper for only adding layout items for grid items that are actually in view. 3 main functions: + * - #is_item_visible(): Query if an item of a given index is visible in the view (others should be + * skipped when building the layout). + * - #fill_layout_before_visible(): Add empty space to the layout before a visible row is drawn, so + * the layout height is the same as if all items were added (important to get the correct scroll + * height). + * - #fill_layout_after_visible(): Same thing, just adds empty space for after the last visible + * row. + * + * Does two assumptions: + * - Top-to-bottom flow (ymax = 0 and ymin < 0). If that's not good enough, View2D should + * probably provide queries for the scroll offset. + * - Only vertical scrolling. For horizontal scrolling, spacers would have to be added on the + * side(s) as well. + */ +class BuildOnlyVisibleButtonsHelper { + const View2D &v2d_; + const AbstractGridView &grid_view_; + const GridViewStyle &style_; + const int cols_per_row_ = 0; + /* Indices of items within the view. Calculated by constructor */ + IndexRange visible_items_range_{}; + + public: + BuildOnlyVisibleButtonsHelper(const View2D &v2d, + const AbstractGridView &grid_view, + int cols_per_row); + + bool is_item_visible(int item_idx) const; + void fill_layout_before_visible(uiBlock &block) const; + void fill_layout_after_visible(uiBlock &block) const; + + private: + IndexRange get_visible_range() const; + void add_spacer_button(uiBlock &block, int row_count) const; +}; + +BuildOnlyVisibleButtonsHelper::BuildOnlyVisibleButtonsHelper(const View2D &v2d, + const AbstractGridView &grid_view, + const int cols_per_row) + : v2d_(v2d), grid_view_(grid_view), style_(grid_view.get_style()), cols_per_row_(cols_per_row) +{ + visible_items_range_ = get_visible_range(); +} + +IndexRange BuildOnlyVisibleButtonsHelper::get_visible_range() const +{ + int first_idx_in_view = 0; + int max_items_in_view = 0; + + const float scroll_ofs_y = abs(v2d_.cur.ymax - v2d_.tot.ymax); + if (!IS_EQF(scroll_ofs_y, 0)) { + const int scrolled_away_rows = (int)scroll_ofs_y / style_.tile_height; + + first_idx_in_view = scrolled_away_rows * cols_per_row_; + } + + const float view_height = BLI_rctf_size_y(&v2d_.cur); + const int count_rows_in_view = std::max(round_fl_to_int(view_height / style_.tile_height), 1); + max_items_in_view = (count_rows_in_view + 1) * cols_per_row_; + + BLI_assert(max_items_in_view > 0); + return IndexRange(first_idx_in_view, max_items_in_view); +} + +bool BuildOnlyVisibleButtonsHelper::is_item_visible(const int item_idx) const +{ + return visible_items_range_.contains(item_idx); +} + +void BuildOnlyVisibleButtonsHelper::fill_layout_before_visible(uiBlock &block) const +{ + const float scroll_ofs_y = abs(v2d_.cur.ymax - v2d_.tot.ymax); + + if (IS_EQF(scroll_ofs_y, 0)) { + return; + } + + const int scrolled_away_rows = (int)scroll_ofs_y / style_.tile_height; + add_spacer_button(block, scrolled_away_rows); +} + +void BuildOnlyVisibleButtonsHelper::fill_layout_after_visible(uiBlock &block) const +{ + const int last_item_idx = grid_view_.get_item_count() - 1; + const int last_visible_idx = visible_items_range_.last(); + + if (last_item_idx > last_visible_idx) { + const int remaining_rows = (cols_per_row_ > 0) ? + (last_item_idx - last_visible_idx) / cols_per_row_ : + 0; + BuildOnlyVisibleButtonsHelper::add_spacer_button(block, remaining_rows); + } +} + +void BuildOnlyVisibleButtonsHelper::add_spacer_button(uiBlock &block, const int row_count) const +{ + /* UI code only supports button dimensions of `signed short` size, the layout height we want to + * fill may be bigger than that. So add multiple labels of the maximum size if necessary. */ + for (int remaining_rows = row_count; remaining_rows > 0;) { + const short row_count_this_iter = std::min( + std::numeric_limits::max() / style_.tile_height, remaining_rows); + + uiDefBut(&block, + UI_BTYPE_LABEL, + 0, + "", + 0, + 0, + UI_UNIT_X, + row_count_this_iter * style_.tile_height, + nullptr, + 0, + 0, + 0, + 0, + ""); + remaining_rows -= row_count_this_iter; + } +} + +/* ---------------------------------------------------------------------- */ + +class GridViewLayoutBuilder { + uiBlock &block_; + + friend class GridViewBuilder; + + public: + GridViewLayoutBuilder(uiBlock &block); + + void build_from_view(const AbstractGridView &grid_view, const View2D &v2d) const; + + private: + void build_grid_tile(uiLayout &grid_layout, AbstractGridViewItem &item) const; + + uiLayout *current_layout() const; +}; + +GridViewLayoutBuilder::GridViewLayoutBuilder(uiBlock &block) : block_(block) +{ +} + +void GridViewLayoutBuilder::build_grid_tile(uiLayout &grid_layout, + AbstractGridViewItem &item) const +{ + uiLayout *overlap = uiLayoutOverlap(&grid_layout); + + item.add_grid_tile_button(block_); + item.build_grid_tile(*uiLayoutRow(overlap, false)); +} + +void GridViewLayoutBuilder::build_from_view(const AbstractGridView &grid_view, + const View2D &v2d) const +{ + uiLayout *prev_layout = current_layout(); + + uiLayout &layout = *uiLayoutColumn(current_layout(), false); + const GridViewStyle &style = grid_view.get_style(); + + const int cols_per_row = std::max(uiLayoutGetWidth(&layout) / style.tile_width, 1); + + BuildOnlyVisibleButtonsHelper build_visible_helper(v2d, grid_view, cols_per_row); + + build_visible_helper.fill_layout_before_visible(block_); + + /* Use `-cols_per_row` because the grid layout uses a multiple of the passed absolute value for + * the number of columns then, rather than distributing the number of items evenly over rows and + * stretching the items to fit (see #uiLayoutItemGridFlow.columns_len). */ + uiLayout *grid_layout = uiLayoutGridFlow(&layout, true, -cols_per_row, true, true, true); + + int item_idx = 0; + grid_view.foreach_item([&](AbstractGridViewItem &item) { + /* Skip if item isn't visible. */ + if (!build_visible_helper.is_item_visible(item_idx)) { + item_idx++; + return; + } + + build_grid_tile(*grid_layout, item); + item_idx++; + }); + + /* If there are not enough items to fill the layout, add padding items so the layout doesn't + * stretch over the entire width. */ + if (grid_view.get_item_count() < cols_per_row) { + for (int padding_item_idx = 0; padding_item_idx < (cols_per_row - grid_view.get_item_count()); + padding_item_idx++) { + uiItemS(grid_layout); + } + } + + UI_block_layout_set_current(&block_, prev_layout); + + build_visible_helper.fill_layout_after_visible(block_); +} + +uiLayout *GridViewLayoutBuilder::current_layout() const +{ + return block_.curlayout; +} + +/* ---------------------------------------------------------------------- */ + +GridViewBuilder::GridViewBuilder(uiBlock &block) : block_(block) +{ +} + +void GridViewBuilder::build_grid_view(AbstractGridView &grid_view, const View2D &v2d) +{ + grid_view.build_items(); + grid_view.update_from_old(block_); + grid_view.change_state_delayed(); + + GridViewLayoutBuilder builder(block_); + builder.build_from_view(grid_view, v2d); +} + +/* ---------------------------------------------------------------------- */ + +PreviewGridItem::PreviewGridItem(StringRef identifier, StringRef label, int preview_icon_id) + : AbstractGridViewItem(identifier), label(label), preview_icon_id(preview_icon_id) +{ +} + +void PreviewGridItem::build_grid_tile(uiLayout &layout) const +{ + const GridViewStyle &style = get_view().get_style(); + uiBlock *block = uiLayoutGetBlock(&layout); + + uiBut *but = uiDefBut(block, + UI_BTYPE_PREVIEW_TILE, + 0, + label.c_str(), + 0, + 0, + style.tile_width, + style.tile_height, + nullptr, + 0, + 0, + 0, + 0, + ""); + ui_def_but_icon(but, + preview_icon_id, + /* NOLINTNEXTLINE: bugprone-suspicious-enum-usage */ + UI_HAS_ICON | UI_BUT_ICON_PREVIEW); +} + +void PreviewGridItem::set_on_activate_fn(ActivateFn fn) +{ + activate_fn_ = fn; +} + +void PreviewGridItem::set_is_active_fn(IsActiveFn fn) +{ + is_active_fn_ = fn; +} + +void PreviewGridItem::on_activate() +{ + if (activate_fn_) { + activate_fn_(*this); + } +} + +std::optional PreviewGridItem::should_be_active() const +{ + if (is_active_fn_) { + return is_active_fn_(); + } + return std::nullopt; +} + +} // namespace blender::ui diff --git a/source/blender/editors/interface/views/interface_view.cc b/source/blender/editors/interface/views/interface_view.cc new file mode 100644 index 00000000000..b35f6d2c969 --- /dev/null +++ b/source/blender/editors/interface/views/interface_view.cc @@ -0,0 +1,190 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + * + * This part of the UI-View API is mostly needed to support persistent state of items within the + * view. Views are stored in #uiBlock's, and kept alive with it until after the next redraw. So we + * can compare the old view items with the new view items and keep state persistent for matching + * ones. + */ + +#include +#include +#include + +#include "DNA_screen_types.h" + +#include "BKE_screen.h" + +#include "BLI_listbase.h" + +#include "ED_screen.h" + +#include "interface_intern.h" + +#include "UI_interface.hh" + +#include "UI_abstract_view.hh" +#include "UI_grid_view.hh" +#include "UI_tree_view.hh" + +using namespace blender; +using namespace blender::ui; + +/** + * Wrapper to store views in a #ListBase, addressable via an identifier. + */ +struct ViewLink : public Link { + std::string idname; + std::unique_ptr view; +}; + +template +static T *ui_block_add_view_impl(uiBlock &block, + StringRef idname, + std::unique_ptr view) +{ + ViewLink *view_link = MEM_new(__func__); + BLI_addtail(&block.views, view_link); + + view_link->view = std::move(view); + view_link->idname = idname; + + return dynamic_cast(view_link->view.get()); +} + +AbstractGridView *UI_block_add_view(uiBlock &block, + StringRef idname, + std::unique_ptr grid_view) +{ + return ui_block_add_view_impl(block, idname, std::move(grid_view)); +} + +AbstractTreeView *UI_block_add_view(uiBlock &block, + StringRef idname, + std::unique_ptr tree_view) +{ + return ui_block_add_view_impl(block, idname, std::move(tree_view)); +} + +void ui_block_free_views(uiBlock *block) +{ + LISTBASE_FOREACH_MUTABLE (ViewLink *, link, &block->views) { + MEM_delete(link); + } +} + +void UI_block_views_listen(const uiBlock *block, const wmRegionListenerParams *listener_params) +{ + ARegion *region = listener_params->region; + + LISTBASE_FOREACH (ViewLink *, view_link, &block->views) { + if (view_link->view->listen(*listener_params->notifier)) { + ED_region_tag_redraw(region); + } + } +} + +uiViewItemHandle *UI_block_view_find_item_at(const ARegion *region, const int xy[2]) +{ + uiButViewItem *item_but = (uiButViewItem *)ui_view_item_find_mouse_over(region, xy); + if (!item_but) { + return nullptr; + } + + return item_but->view_item; +} + +uiViewItemHandle *UI_block_view_find_active_item(const ARegion *region) +{ + uiButViewItem *item_but = (uiButViewItem *)ui_view_item_find_active(region); + if (!item_but) { + return nullptr; + } + + return item_but->view_item; +} + +static StringRef ui_block_view_find_idname(const uiBlock &block, const AbstractView &view) +{ + /* First get the idname the of the view we're looking for. */ + LISTBASE_FOREACH (ViewLink *, view_link, &block.views) { + if (view_link->view.get() == &view) { + return view_link->idname; + } + } + + return {}; +} + +template +static T *ui_block_view_find_matching_in_old_block_impl(const uiBlock &new_block, + const T &new_view) +{ + uiBlock *old_block = new_block.oldblock; + if (!old_block) { + return nullptr; + } + + StringRef idname = ui_block_view_find_idname(new_block, new_view); + if (idname.is_empty()) { + return nullptr; + } + + LISTBASE_FOREACH (ViewLink *, old_view_link, &old_block->views) { + if (old_view_link->idname == idname) { + return dynamic_cast(old_view_link->view.get()); + } + } + + return nullptr; +} + +uiViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block, + const uiViewHandle *new_view_handle) +{ + BLI_assert(new_block && new_view_handle); + const AbstractView &new_view = reinterpret_cast(*new_view_handle); + + AbstractView *old_view = ui_block_view_find_matching_in_old_block_impl(*new_block, new_view); + return reinterpret_cast(old_view); +} + +uiButViewItem *ui_block_view_find_matching_view_item_but_in_old_block( + const uiBlock *new_block, const uiViewItemHandle *new_item_handle) +{ + uiBlock *old_block = new_block->oldblock; + if (!old_block) { + return nullptr; + } + + const AbstractViewItem &new_item = *reinterpret_cast(new_item_handle); + const AbstractView *old_view = ui_block_view_find_matching_in_old_block_impl( + *new_block, new_item.get_view()); + if (!old_view) { + return nullptr; + } + + LISTBASE_FOREACH (uiBut *, old_but, &old_block->buttons) { + if (old_but->type != UI_BTYPE_VIEW_ITEM) { + continue; + } + uiButViewItem *old_item_but = (uiButViewItem *)old_but; + if (!old_item_but->view_item) { + continue; + } + AbstractViewItem &old_item = *reinterpret_cast(old_item_but->view_item); + /* Check if the item is from the expected view. */ + if (&old_item.get_view() != old_view) { + continue; + } + + if (UI_view_item_matches(reinterpret_cast(&new_item), + reinterpret_cast(&old_item))) { + return old_item_but; + } + } + + return nullptr; +} diff --git a/source/blender/editors/interface/views/tree_view.cc b/source/blender/editors/interface/views/tree_view.cc new file mode 100644 index 00000000000..c224226ba17 --- /dev/null +++ b/source/blender/editors/interface/views/tree_view.cc @@ -0,0 +1,554 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + */ + +#include "DNA_userdef_types.h" +#include "DNA_windowmanager_types.h" + +#include "BKE_context.h" + +#include "BLT_translation.h" + +#include "interface_intern.h" + +#include "UI_interface.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "UI_tree_view.hh" + +namespace blender::ui { + +/* ---------------------------------------------------------------------- */ + +/** + * Add a tree-item to the container. This is the only place where items should be added, it + * handles important invariants! + */ +AbstractTreeViewItem &TreeViewItemContainer::add_tree_item( + std::unique_ptr item) +{ + children_.append(std::move(item)); + + /* The first item that will be added to the root sets this. */ + if (root_ == nullptr) { + root_ = this; + } + AbstractTreeView &tree_view = static_cast(*root_); + AbstractTreeViewItem &added_item = *children_.last(); + added_item.root_ = root_; + tree_view.register_item(added_item); + + if (root_ != this) { + /* Any item that isn't the root can be assumed to the a #AbstractTreeViewItem. Not entirely + * nice to static_cast this, but well... */ + added_item.parent_ = static_cast(this); + } + + return added_item; +} + +void TreeViewItemContainer::foreach_item_recursive(ItemIterFn iter_fn, IterOptions options) const +{ + for (const auto &child : children_) { + iter_fn(*child); + if (bool(options & IterOptions::SkipCollapsed) && child->is_collapsed()) { + continue; + } + + child->foreach_item_recursive(iter_fn, options); + } +} + +/* ---------------------------------------------------------------------- */ + +void AbstractTreeView::foreach_item(ItemIterFn iter_fn, IterOptions options) const +{ + foreach_item_recursive(iter_fn, options); +} + +void AbstractTreeView::update_children_from_old(const AbstractView &old_view) +{ + const AbstractTreeView &old_tree_view = dynamic_cast(old_view); + + update_children_from_old_recursive(*this, old_tree_view); +} + +void AbstractTreeView::update_children_from_old_recursive(const TreeViewOrItem &new_items, + const TreeViewOrItem &old_items) +{ + for (const auto &new_item : new_items.children_) { + AbstractTreeViewItem *matching_old_item = find_matching_child(*new_item, old_items); + if (!matching_old_item) { + continue; + } + + new_item->update_from_old(*matching_old_item); + + /* Recurse into children of the matched item. */ + update_children_from_old_recursive(*new_item, *matching_old_item); + } +} + +AbstractTreeViewItem *AbstractTreeView::find_matching_child( + const AbstractTreeViewItem &lookup_item, const TreeViewOrItem &items) +{ + for (const auto &iter_item : items.children_) { + if (lookup_item.matches_single(*iter_item)) { + /* We have a matching item! */ + return iter_item.get(); + } + } + + return nullptr; +} + +void AbstractTreeView::change_state_delayed() +{ + BLI_assert_msg( + is_reconstructed(), + "These state changes are supposed to be delayed until reconstruction is completed"); + foreach_item([](AbstractTreeViewItem &item) { item.change_state_delayed(); }); +} + +/* ---------------------------------------------------------------------- */ + +void AbstractTreeViewItem::tree_row_click_fn(struct bContext * /*C*/, + void *but_arg1, + void * /*arg2*/) +{ + uiButViewItem *item_but = (uiButViewItem *)but_arg1; + AbstractTreeViewItem &tree_item = reinterpret_cast(*item_but->view_item); + + tree_item.activate(); + /* Not only activate the item, also show its children. Maybe this should be optional, or + * controlled by the specific tree-view. */ + tree_item.set_collapsed(false); +} + +void AbstractTreeViewItem::add_treerow_button(uiBlock &block) +{ + /* For some reason a width > (UI_UNIT_X * 2) make the layout system use all available width. */ + view_item_but_ = (uiButViewItem *)uiDefBut( + &block, UI_BTYPE_VIEW_ITEM, 0, "", 0, 0, UI_UNIT_X * 10, UI_UNIT_Y, nullptr, 0, 0, 0, 0, ""); + + view_item_but_->view_item = reinterpret_cast(this); + UI_but_func_set(&view_item_but_->but, tree_row_click_fn, view_item_but_, nullptr); +} + +void AbstractTreeViewItem::add_indent(uiLayout &row) const +{ + uiBlock *block = uiLayoutGetBlock(&row); + uiLayout *subrow = uiLayoutRow(&row, true); + uiLayoutSetFixedSize(subrow, true); + + const float indent_size = count_parents() * UI_DPI_ICON_SIZE; + uiDefBut(block, UI_BTYPE_SEPR, 0, "", 0, 0, indent_size, 0, nullptr, 0.0, 0.0, 0, 0, ""); + + /* Indent items without collapsing icon some more within their parent. Makes it clear that they + * are actually nested and not just a row at the same level without a chevron. */ + if (!is_collapsible() && parent_) { + uiDefBut(block, UI_BTYPE_SEPR, 0, "", 0, 0, 0.2f * UI_UNIT_X, 0, nullptr, 0.0, 0.0, 0, 0, ""); + } + + /* Restore. */ + UI_block_layout_set_current(block, &row); +} + +void AbstractTreeViewItem::collapse_chevron_click_fn(struct bContext *C, + void * /*but_arg1*/, + void * /*arg2*/) +{ + /* There's no data we could pass to this callback. It must be either the button itself or a + * consistent address to match buttons over redraws. So instead of passing it somehow, just + * lookup the hovered item via context here. */ + + const wmWindow *win = CTX_wm_window(C); + const ARegion *region = CTX_wm_region(C); + uiViewItemHandle *hovered_item_handle = UI_block_view_find_item_at(region, win->eventstate->xy); + + AbstractTreeViewItem *hovered_item = from_item_handle(hovered_item_handle); + BLI_assert(hovered_item != nullptr); + + hovered_item->toggle_collapsed(); + /* When collapsing an item with an active child, make this collapsed item active instead so the + * active item stays visible. */ + if (hovered_item->has_active_child()) { + hovered_item->activate(); + } +} + +bool AbstractTreeViewItem::is_collapse_chevron_but(const uiBut *but) +{ + return but->type == UI_BTYPE_BUT_TOGGLE && ELEM(but->icon, ICON_TRIA_RIGHT, ICON_TRIA_DOWN) && + (but->func == collapse_chevron_click_fn); +} + +void AbstractTreeViewItem::add_collapse_chevron(uiBlock &block) const +{ + if (!is_collapsible()) { + return; + } + + const BIFIconID icon = is_collapsed() ? ICON_TRIA_RIGHT : ICON_TRIA_DOWN; + uiBut *but = uiDefIconBut( + &block, UI_BTYPE_BUT_TOGGLE, 0, icon, 0, 0, UI_UNIT_X, UI_UNIT_Y, nullptr, 0, 0, 0, 0, ""); + /* Note that we're passing the tree-row button here, not the chevron one. */ + UI_but_func_set(but, collapse_chevron_click_fn, nullptr, nullptr); + UI_but_flag_disable(but, UI_BUT_UNDO); + + /* Check if the query for the button matches the created button. */ + BLI_assert(is_collapse_chevron_but(but)); +} + +void AbstractTreeViewItem::add_rename_button(uiLayout &row) +{ + uiBlock *block = uiLayoutGetBlock(&row); + eUIEmbossType previous_emboss = UI_block_emboss_get(block); + + uiLayoutRow(&row, false); + /* Enable emboss for the text button. */ + UI_block_emboss_set(block, UI_EMBOSS); + + AbstractViewItem::add_rename_button(*block); + + UI_block_emboss_set(block, previous_emboss); + UI_block_layout_set_current(block, &row); +} + +bool AbstractTreeViewItem::has_active_child() const +{ + bool found = false; + foreach_item_recursive([&found](const AbstractTreeViewItem &item) { + if (item.is_active()) { + found = true; + } + }); + + return found; +} + +void AbstractTreeViewItem::on_activate() +{ + /* Do nothing by default. */ +} + +std::optional AbstractTreeViewItem::should_be_active() const +{ + return std::nullopt; +} + +bool AbstractTreeViewItem::supports_collapsing() const +{ + return true; +} + +StringRef AbstractTreeViewItem::get_rename_string() const +{ + return label_; +} + +bool AbstractTreeViewItem::rename(StringRefNull new_name) +{ + /* It is important to update the label after renaming, so #AbstractTreeViewItem::matches_single() + * recognizes the item. (It only compares labels by default.) */ + label_ = new_name; + return true; +} + +void AbstractTreeViewItem::update_from_old(const AbstractViewItem &old) +{ + AbstractViewItem::update_from_old(old); + + const AbstractTreeViewItem &old_tree_item = dynamic_cast(old); + is_open_ = old_tree_item.is_open_; +} + +bool AbstractTreeViewItem::matches_single(const AbstractTreeViewItem &other) const +{ + return label_ == other.label_; +} + +AbstractTreeView &AbstractTreeViewItem::get_tree_view() const +{ + return dynamic_cast(get_view()); +} + +int AbstractTreeViewItem::count_parents() const +{ + int i = 0; + for (AbstractTreeViewItem *parent = parent_; parent; parent = parent->parent_) { + i++; + } + return i; +} + +void AbstractTreeViewItem::activate() +{ + BLI_assert_msg(get_tree_view().is_reconstructed(), + "Item activation can't be done until reconstruction is completed"); + + if (is_active()) { + return; + } + + /* Deactivate other items in the tree. */ + get_tree_view().foreach_item([](auto &item) { item.deactivate(); }); + + on_activate(); + /* Make sure the active item is always visible. */ + ensure_parents_uncollapsed(); + + is_active_ = true; +} + +void AbstractTreeViewItem::deactivate() +{ + is_active_ = false; +} + +bool AbstractTreeViewItem::is_hovered() const +{ + BLI_assert_msg(get_tree_view().is_reconstructed(), + "State can't be queried until reconstruction is completed"); + BLI_assert_msg(view_item_but_ != nullptr, + "Hovered state can't be queried before the tree row is being built"); + + const uiViewItemHandle *this_item_handle = reinterpret_cast(this); + /* The new layout hasn't finished construction yet, so the final state of the button is unknown. + * Get the matching button from the previous redraw instead. */ + uiButViewItem *old_item_but = ui_block_view_find_matching_view_item_but_in_old_block( + view_item_but_->but.block, this_item_handle); + return old_item_but && (old_item_but->but.flag & UI_ACTIVE); +} + +bool AbstractTreeViewItem::is_collapsed() const +{ + BLI_assert_msg(get_tree_view().is_reconstructed(), + "State can't be queried until reconstruction is completed"); + return is_collapsible() && !is_open_; +} + +void AbstractTreeViewItem::toggle_collapsed() +{ + is_open_ = !is_open_; +} + +void AbstractTreeViewItem::set_collapsed(bool collapsed) +{ + is_open_ = !collapsed; +} + +bool AbstractTreeViewItem::is_collapsible() const +{ + if (children_.is_empty()) { + return false; + } + return this->supports_collapsing(); +} + +void AbstractTreeViewItem::ensure_parents_uncollapsed() +{ + for (AbstractTreeViewItem *parent = parent_; parent; parent = parent->parent_) { + parent->set_collapsed(false); + } +} + +bool AbstractTreeViewItem::matches(const AbstractViewItem &other) const +{ + const AbstractTreeViewItem &other_tree_item = dynamic_cast(other); + + if (!matches_single(other_tree_item)) { + return false; + } + if (count_parents() != other_tree_item.count_parents()) { + return false; + } + + for (AbstractTreeViewItem *parent = parent_, *other_parent = other_tree_item.parent_; + parent && other_parent; + parent = parent->parent_, other_parent = other_parent->parent_) { + if (!parent->matches_single(*other_parent)) { + return false; + } + } + + return true; +} + +uiButViewItem *AbstractTreeViewItem::view_item_button() +{ + return view_item_but_; +} + +void AbstractTreeViewItem::change_state_delayed() +{ + const std::optional should_be_active = this->should_be_active(); + if (should_be_active.has_value() && *should_be_active) { + activate(); + } +} + +/* ---------------------------------------------------------------------- */ + +class TreeViewLayoutBuilder { + uiBlock &block_; + + friend TreeViewBuilder; + + public: + void build_from_tree(const AbstractTreeView &tree_view); + void build_row(AbstractTreeViewItem &item) const; + + uiBlock &block() const; + uiLayout *current_layout() const; + + private: + /* Created through #TreeViewBuilder. */ + TreeViewLayoutBuilder(uiBlock &block); + + static void polish_layout(const uiBlock &block); +}; + +TreeViewLayoutBuilder::TreeViewLayoutBuilder(uiBlock &block) : block_(block) +{ +} + +void TreeViewLayoutBuilder::build_from_tree(const AbstractTreeView &tree_view) +{ + uiLayout *prev_layout = current_layout(); + + uiLayout *box = uiLayoutBox(prev_layout); + uiLayoutColumn(box, false); + + tree_view.foreach_item([this](AbstractTreeViewItem &item) { build_row(item); }, + AbstractTreeView::IterOptions::SkipCollapsed); + + UI_block_layout_set_current(&block(), prev_layout); +} + +void TreeViewLayoutBuilder::polish_layout(const uiBlock &block) +{ + LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block.buttons) { + if (AbstractTreeViewItem::is_collapse_chevron_but(but) && but->next && + /* Embossed buttons with padding-less text padding look weird, so don't touch them. */ + ELEM(but->next->emboss, UI_EMBOSS_NONE, UI_EMBOSS_NONE_OR_STATUS)) { + UI_but_drawflag_enable(static_cast(but->next), UI_BUT_NO_TEXT_PADDING); + } + + if (but->type == UI_BTYPE_VIEW_ITEM) { + break; + } + } +} + +void TreeViewLayoutBuilder::build_row(AbstractTreeViewItem &item) const +{ + uiBlock &block_ = block(); + + uiLayout *prev_layout = current_layout(); + eUIEmbossType previous_emboss = UI_block_emboss_get(&block_); + + uiLayout *overlap = uiLayoutOverlap(prev_layout); + + uiLayoutRow(overlap, false); + /* Every item gets one! Other buttons can be overlapped on top. */ + item.add_treerow_button(block_); + + /* After adding tree-row button (would disable hover highlighting). */ + UI_block_emboss_set(&block_, UI_EMBOSS_NONE); + + uiLayout *row = uiLayoutRow(overlap, true); + item.add_indent(*row); + item.add_collapse_chevron(block_); + + if (item.is_renaming()) { + item.add_rename_button(*row); + } + else { + item.build_row(*row); + } + polish_layout(block_); + + UI_block_emboss_set(&block_, previous_emboss); + UI_block_layout_set_current(&block_, prev_layout); +} + +uiBlock &TreeViewLayoutBuilder::block() const +{ + return block_; +} + +uiLayout *TreeViewLayoutBuilder::current_layout() const +{ + return block().curlayout; +} + +/* ---------------------------------------------------------------------- */ + +TreeViewBuilder::TreeViewBuilder(uiBlock &block) : block_(block) +{ +} + +void TreeViewBuilder::build_tree_view(AbstractTreeView &tree_view) +{ + tree_view.build_tree(); + tree_view.update_from_old(block_); + tree_view.change_state_delayed(); + + TreeViewLayoutBuilder builder(block_); + builder.build_from_tree(tree_view); +} + +/* ---------------------------------------------------------------------- */ + +BasicTreeViewItem::BasicTreeViewItem(StringRef label, BIFIconID icon_) : icon(icon_) +{ + label_ = label; +} + +void BasicTreeViewItem::build_row(uiLayout &row) +{ + add_label(row); +} + +void BasicTreeViewItem::add_label(uiLayout &layout, StringRefNull label_override) +{ + const StringRefNull label = label_override.is_empty() ? StringRefNull(label_) : label_override; + + /* Some padding for labels without collapse chevron and no icon. Looks weird without. */ + if (icon == ICON_NONE && !is_collapsible()) { + uiItemS_ex(&layout, 0.8f); + } + uiItemL(&layout, IFACE_(label.c_str()), icon); +} + +void BasicTreeViewItem::on_activate() +{ + if (activate_fn_) { + activate_fn_(*this); + } +} + +void BasicTreeViewItem::set_on_activate_fn(ActivateFn fn) +{ + activate_fn_ = fn; +} + +void BasicTreeViewItem::set_is_active_fn(IsActiveFn is_active_fn) +{ + is_active_fn_ = is_active_fn; +} + +std::optional BasicTreeViewItem::should_be_active() const +{ + if (is_active_fn_) { + return is_active_fn_(); + } + return std::nullopt; +} + +} // namespace blender::ui -- cgit v1.2.3 From 16b145bc628d277228f5211a9dc076e516c443fb Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Tue, 19 Jul 2022 18:00:56 +0200 Subject: Cleanup: Improve API and documentation of interface_view.cc File documentation was outdated and could use general improvement. Function names didn't really reflect the level they are operating on. --- source/blender/editors/include/UI_interface.h | 4 ++-- .../blender/editors/interface/interface_dropboxes.cc | 4 ++-- source/blender/editors/interface/interface_ops.c | 8 ++++---- .../blender/editors/interface/views/interface_view.cc | 18 ++++++++++++------ source/blender/editors/interface/views/tree_view.cc | 2 +- 5 files changed, 21 insertions(+), 15 deletions(-) diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index 99a1d038cca..a8d25b75036 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -3230,9 +3230,9 @@ bool UI_view_item_drop_handle(struct bContext *C, /** * \param xy: Coordinate to find a view item at, in window space. */ -uiViewItemHandle *UI_block_view_find_item_at(const struct ARegion *region, const int xy[2]) +uiViewItemHandle *UI_region_views_find_item_at(const struct ARegion *region, const int xy[2]) ATTR_NONNULL(); -uiViewItemHandle *UI_block_view_find_active_item(const struct ARegion *region); +uiViewItemHandle *UI_region_views_find_active_item(const struct ARegion *region); #ifdef __cplusplus } diff --git a/source/blender/editors/interface/interface_dropboxes.cc b/source/blender/editors/interface/interface_dropboxes.cc index df488fb9127..b72d8d2c238 100644 --- a/source/blender/editors/interface/interface_dropboxes.cc +++ b/source/blender/editors/interface/interface_dropboxes.cc @@ -28,7 +28,7 @@ static bool ui_view_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event) { const ARegion *region = CTX_wm_region(C); - const uiViewItemHandle *hovered_item = UI_block_view_find_item_at(region, event->xy); + const uiViewItemHandle *hovered_item = UI_region_views_find_item_at(region, event->xy); if (!hovered_item) { return false; } @@ -47,7 +47,7 @@ static char *ui_view_drop_tooltip(bContext *C, wmDropBox *UNUSED(drop)) { const ARegion *region = CTX_wm_region(C); - const uiViewItemHandle *hovered_item = UI_block_view_find_item_at(region, xy); + const uiViewItemHandle *hovered_item = UI_region_views_find_item_at(region, xy); if (!hovered_item) { return nullptr; } diff --git a/source/blender/editors/interface/interface_ops.c b/source/blender/editors/interface/interface_ops.c index 7a51ed23677..ddd805f6010 100644 --- a/source/blender/editors/interface/interface_ops.c +++ b/source/blender/editors/interface/interface_ops.c @@ -2058,7 +2058,7 @@ static bool ui_view_drop_poll(bContext *C) { const wmWindow *win = CTX_wm_window(C); const ARegion *region = CTX_wm_region(C); - const uiViewItemHandle *hovered_item = UI_block_view_find_item_at(region, win->eventstate->xy); + const uiViewItemHandle *hovered_item = UI_region_views_find_item_at(region, win->eventstate->xy); return hovered_item != NULL; } @@ -2070,7 +2070,7 @@ static int ui_view_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEven } const ARegion *region = CTX_wm_region(C); - uiViewItemHandle *hovered_item = UI_block_view_find_item_at(region, event->xy); + uiViewItemHandle *hovered_item = UI_region_views_find_item_at(region, event->xy); if (!UI_view_item_drop_handle(C, hovered_item, event->customdata)) { return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; @@ -2105,14 +2105,14 @@ static void UI_OT_view_drop(wmOperatorType *ot) static bool ui_view_item_rename_poll(bContext *C) { const ARegion *region = CTX_wm_region(C); - const uiViewItemHandle *active_item = UI_block_view_find_active_item(region); + const uiViewItemHandle *active_item = UI_region_views_find_active_item(region); return active_item != NULL && UI_view_item_can_rename(active_item); } static int ui_view_item_rename_exec(bContext *C, wmOperator *UNUSED(op)) { ARegion *region = CTX_wm_region(C); - uiViewItemHandle *active_item = UI_block_view_find_active_item(region); + uiViewItemHandle *active_item = UI_region_views_find_active_item(region); UI_view_item_begin_rename(active_item); ED_region_tag_redraw(region); diff --git a/source/blender/editors/interface/views/interface_view.cc b/source/blender/editors/interface/views/interface_view.cc index b35f6d2c969..c568a8cab74 100644 --- a/source/blender/editors/interface/views/interface_view.cc +++ b/source/blender/editors/interface/views/interface_view.cc @@ -3,10 +3,16 @@ /** \file * \ingroup edinterface * - * This part of the UI-View API is mostly needed to support persistent state of items within the - * view. Views are stored in #uiBlock's, and kept alive with it until after the next redraw. So we - * can compare the old view items with the new view items and keep state persistent for matching - * ones. + * Code to manage views as part of the regular screen hierarchy. E.g. managing ownership of views + * inside blocks (#uiBlock.views), looking up items in the region, passing WM notifiers to views, + * etc. + * + * Blocks and their contained views are reconstructed on every redraw. This file also contains + * functions related to this recreation of views inside blocks. For example to query state + * information before the view is done reconstructing (#AbstractView.is_reconstructed() returns + * false), it may be enough to query the previous version of the block/view/view-item. Since such + * queries rely on the details of the UI reconstruction process, they should remain internal to + * `interface/` code. */ #include @@ -86,7 +92,7 @@ void UI_block_views_listen(const uiBlock *block, const wmRegionListenerParams *l } } -uiViewItemHandle *UI_block_view_find_item_at(const ARegion *region, const int xy[2]) +uiViewItemHandle *UI_region_views_find_item_at(const ARegion *region, const int xy[2]) { uiButViewItem *item_but = (uiButViewItem *)ui_view_item_find_mouse_over(region, xy); if (!item_but) { @@ -96,7 +102,7 @@ uiViewItemHandle *UI_block_view_find_item_at(const ARegion *region, const int xy return item_but->view_item; } -uiViewItemHandle *UI_block_view_find_active_item(const ARegion *region) +uiViewItemHandle *UI_region_views_find_active_item(const ARegion *region) { uiButViewItem *item_but = (uiButViewItem *)ui_view_item_find_active(region); if (!item_but) { diff --git a/source/blender/editors/interface/views/tree_view.cc b/source/blender/editors/interface/views/tree_view.cc index c224226ba17..21078b711c7 100644 --- a/source/blender/editors/interface/views/tree_view.cc +++ b/source/blender/editors/interface/views/tree_view.cc @@ -168,7 +168,7 @@ void AbstractTreeViewItem::collapse_chevron_click_fn(struct bContext *C, const wmWindow *win = CTX_wm_window(C); const ARegion *region = CTX_wm_region(C); - uiViewItemHandle *hovered_item_handle = UI_block_view_find_item_at(region, win->eventstate->xy); + uiViewItemHandle *hovered_item_handle = UI_region_views_find_item_at(region, win->eventstate->xy); AbstractTreeViewItem *hovered_item = from_item_handle(hovered_item_handle); BLI_assert(hovered_item != nullptr); -- cgit v1.2.3 From 597955d0a82eb20805d1c9d567b82b1a51b39ccf Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 19 Jul 2022 12:10:30 -0500 Subject: Cleanup: Remove compile option for curves object After becb1530b1c81a408e20 the new curves object type isn't hidden behind an experimental flag anymore, and other areas depend on this, so disabling curves at compile time doesn't make sense anymore. --- intern/cycles/blender/CMakeLists.txt | 4 ---- intern/cycles/blender/curves.cpp | 21 ++------------------- intern/cycles/blender/geometry.cpp | 8 -------- source/blender/editors/object/CMakeLists.txt | 1 - source/blender/editors/space_buttons/CMakeLists.txt | 1 - .../blender/editors/space_buttons/buttons_context.c | 6 ------ source/blender/makesrna/intern/CMakeLists.txt | 1 - source/blender/makesrna/intern/makesrna.c | 2 -- source/blender/makesrna/intern/rna_ID.c | 6 ------ source/blender/makesrna/intern/rna_internal.h | 2 -- source/blender/makesrna/intern/rna_main.c | 4 ---- source/blender/makesrna/intern/rna_main_api.c | 6 ------ source/blender/makesrna/intern/rna_object.c | 4 ---- source/blender/modifiers/CMakeLists.txt | 1 - 14 files changed, 2 insertions(+), 65 deletions(-) diff --git a/intern/cycles/blender/CMakeLists.txt b/intern/cycles/blender/CMakeLists.txt index 4919b99cfe0..63d89221d20 100644 --- a/intern/cycles/blender/CMakeLists.txt +++ b/intern/cycles/blender/CMakeLists.txt @@ -128,10 +128,6 @@ if(WITH_OPENIMAGEDENOISE) ) endif() -if(WITH_EXPERIMENTAL_FEATURES) - add_definitions(-DWITH_NEW_CURVES_TYPE) -endif() - blender_add_lib(bf_intern_cycles "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") add_dependencies(bf_intern_cycles bf_rna) diff --git a/intern/cycles/blender/curves.cpp b/intern/cycles/blender/curves.cpp index 10012720bd8..263a5fc0e02 100644 --- a/intern/cycles/blender/curves.cpp +++ b/intern/cycles/blender/curves.cpp @@ -613,8 +613,6 @@ void BlenderSync::sync_particle_hair( } } -#ifdef WITH_NEW_CURVES_TYPE - static std::optional find_curves_radius_attribute(BL::Curves b_curves) { for (BL::Attribute &b_attribute : b_curves.attributes) { @@ -990,15 +988,6 @@ void BlenderSync::sync_hair(Hair *hair, BObjectInfo &b_ob_info, bool motion, int export_hair_curves(scene, hair, b_curves, need_motion, motion_scale); } } -#else -void BlenderSync::sync_hair(Hair *hair, BObjectInfo &b_ob_info, bool motion, int motion_step) -{ - (void)hair; - (void)b_ob_info; - (void)motion; - (void)motion_step; -} -#endif void BlenderSync::sync_hair(BL::Depsgraph b_depsgraph, BObjectInfo &b_ob_info, Hair *hair) { @@ -1010,14 +999,11 @@ void BlenderSync::sync_hair(BL::Depsgraph b_depsgraph, BObjectInfo &b_ob_info, H new_hair.set_used_shaders(used_shaders); if (view_layer.use_hair) { -#ifdef WITH_NEW_CURVES_TYPE if (b_ob_info.object_data.is_a(&RNA_Curves)) { /* Hair object. */ sync_hair(&new_hair, b_ob_info, false); } - else -#endif - { + else { /* Particle hair. */ bool need_undeformed = new_hair.need_attribute(scene, ATTR_STD_GENERATED); BL::Mesh b_mesh = object_to_mesh( @@ -1064,15 +1050,12 @@ void BlenderSync::sync_hair_motion(BL::Depsgraph b_depsgraph, /* Export deformed coordinates. */ if (ccl::BKE_object_is_deform_modified(b_ob_info, b_scene, preview)) { -#ifdef WITH_NEW_CURVES_TYPE if (b_ob_info.object_data.is_a(&RNA_Curves)) { /* Hair object. */ sync_hair(hair, b_ob_info, true, motion_step); return; } - else -#endif - { + else { /* Particle hair. */ BL::Mesh b_mesh = object_to_mesh( b_data, b_ob_info, b_depsgraph, false, Mesh::SUBDIVISION_NONE); diff --git a/intern/cycles/blender/geometry.cpp b/intern/cycles/blender/geometry.cpp index 215860f59e6..fc03ca6e489 100644 --- a/intern/cycles/blender/geometry.cpp +++ b/intern/cycles/blender/geometry.cpp @@ -18,11 +18,7 @@ CCL_NAMESPACE_BEGIN static Geometry::Type determine_geom_type(BObjectInfo &b_ob_info, bool use_particle_hair) { -#ifdef WITH_NEW_CURVES_TYPE if (b_ob_info.object_data.is_a(&RNA_Curves) || use_particle_hair) { -#else - if (use_particle_hair) { -#endif return Geometry::HAIR; } @@ -217,11 +213,7 @@ void BlenderSync::sync_geometry_motion(BL::Depsgraph &b_depsgraph, if (progress.get_cancel()) return; -#ifdef WITH_NEW_CURVES_TYPE if (b_ob_info.object_data.is_a(&RNA_Curves) || use_particle_hair) { -#else - if (use_particle_hair) { -#endif Hair *hair = static_cast(geom); sync_hair_motion(b_depsgraph, b_ob_info, hair, motion_step); } diff --git a/source/blender/editors/object/CMakeLists.txt b/source/blender/editors/object/CMakeLists.txt index 97376a495c1..a2f993c92b9 100644 --- a/source/blender/editors/object/CMakeLists.txt +++ b/source/blender/editors/object/CMakeLists.txt @@ -76,7 +76,6 @@ endif() if(WITH_EXPERIMENTAL_FEATURES) add_definitions(-DWITH_SIMULATION_DATABLOCK) add_definitions(-DWITH_POINT_CLOUD) - add_definitions(-DWITH_NEW_CURVES_TYPE) endif() blender_add_lib(bf_editor_object "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/editors/space_buttons/CMakeLists.txt b/source/blender/editors/space_buttons/CMakeLists.txt index e2f1df74446..7d4f38b1841 100644 --- a/source/blender/editors/space_buttons/CMakeLists.txt +++ b/source/blender/editors/space_buttons/CMakeLists.txt @@ -35,7 +35,6 @@ endif() if(WITH_EXPERIMENTAL_FEATURES) add_definitions(-DWITH_SIMULATION_DATABLOCK) add_definitions(-DWITH_POINT_CLOUD) - add_definitions(-DWITH_NEW_CURVES_TYPE) endif() blender_add_lib(bf_editor_space_buttons "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/editors/space_buttons/buttons_context.c b/source/blender/editors/space_buttons/buttons_context.c index 5780b0c9df7..c3479409f0d 100644 --- a/source/blender/editors/space_buttons/buttons_context.c +++ b/source/blender/editors/space_buttons/buttons_context.c @@ -258,11 +258,9 @@ static bool buttons_context_path_data(ButsContextPath *path, int type) if (RNA_struct_is_a(ptr->type, &RNA_GreasePencil) && (ELEM(type, -1, OB_GPENCIL))) { return true; } -#ifdef WITH_NEW_CURVES_TYPE if (RNA_struct_is_a(ptr->type, &RNA_Curves) && (ELEM(type, -1, OB_CURVES))) { return true; } -#endif #ifdef WITH_POINT_CLOUD if (RNA_struct_is_a(ptr->type, &RNA_PointCloud) && (ELEM(type, -1, OB_POINTCLOUD))) { return true; @@ -830,9 +828,7 @@ const char *buttons_context_dir[] = { "line_style", "collection", "gpencil", -#ifdef WITH_NEW_CURVES_TYPE "curves", -#endif #ifdef WITH_POINT_CLOUD "pointcloud", #endif @@ -926,12 +922,10 @@ int /*eContextResult*/ buttons_context(const bContext *C, set_pointer_type(path, result, &RNA_LightProbe); return CTX_RESULT_OK; } -#ifdef WITH_NEW_CURVES_TYPE if (CTX_data_equals(member, "curves")) { set_pointer_type(path, result, &RNA_Curves); return CTX_RESULT_OK; } -#endif #ifdef WITH_POINT_CLOUD if (CTX_data_equals(member, "pointcloud")) { set_pointer_type(path, result, &RNA_PointCloud); diff --git a/source/blender/makesrna/intern/CMakeLists.txt b/source/blender/makesrna/intern/CMakeLists.txt index af8767a1220..778c6a6bfdd 100644 --- a/source/blender/makesrna/intern/CMakeLists.txt +++ b/source/blender/makesrna/intern/CMakeLists.txt @@ -84,7 +84,6 @@ set(DEFSRC if(WITH_EXPERIMENTAL_FEATURES) add_definitions(-DWITH_SIMULATION_DATABLOCK) - add_definitions(-DWITH_NEW_CURVES_TYPE) list(APPEND DEFSRC rna_curves.c rna_simulation.c diff --git a/source/blender/makesrna/intern/makesrna.c b/source/blender/makesrna/intern/makesrna.c index a25fe201fa2..2b24bd0b39c 100644 --- a/source/blender/makesrna/intern/makesrna.c +++ b/source/blender/makesrna/intern/makesrna.c @@ -4435,9 +4435,7 @@ static RNAProcessItem PROCESS_ITEMS[] = { {"rna_dynamicpaint.c", NULL, RNA_def_dynamic_paint}, {"rna_fcurve.c", "rna_fcurve_api.c", RNA_def_fcurve}, {"rna_gpencil.c", NULL, RNA_def_gpencil}, -#ifdef WITH_NEW_CURVES_TYPE {"rna_curves.c", NULL, RNA_def_curves}, -#endif {"rna_image.c", "rna_image_api.c", RNA_def_image}, {"rna_key.c", NULL, RNA_def_key}, {"rna_light.c", NULL, RNA_def_light}, diff --git a/source/blender/makesrna/intern/rna_ID.c b/source/blender/makesrna/intern/rna_ID.c index 8d2ee0ab534..4003df4d685 100644 --- a/source/blender/makesrna/intern/rna_ID.c +++ b/source/blender/makesrna/intern/rna_ID.c @@ -375,11 +375,9 @@ short RNA_type_to_ID_code(const StructRNA *type) if (base_type == &RNA_FreestyleLineStyle) { return ID_LS; } -# ifdef WITH_NEW_CURVES_TYPE if (base_type == &RNA_Curves) { return ID_CV; } -# endif if (base_type == &RNA_Lattice) { return ID_LT; } @@ -483,11 +481,7 @@ StructRNA *ID_code_to_RNA_type(short idcode) case ID_GR: return &RNA_Collection; case ID_CV: -# ifdef WITH_NEW_CURVES_TYPE return &RNA_Curves; -# else - return &RNA_ID; -# endif case ID_IM: return &RNA_Image; case ID_KE: diff --git a/source/blender/makesrna/intern/rna_internal.h b/source/blender/makesrna/intern/rna_internal.h index 6ca8e668fa0..833060e40f8 100644 --- a/source/blender/makesrna/intern/rna_internal.h +++ b/source/blender/makesrna/intern/rna_internal.h @@ -504,9 +504,7 @@ void RNA_def_main_cachefiles(BlenderRNA *brna, PropertyRNA *cprop); void RNA_def_main_paintcurves(BlenderRNA *brna, PropertyRNA *cprop); void RNA_def_main_workspaces(BlenderRNA *brna, PropertyRNA *cprop); void RNA_def_main_lightprobes(BlenderRNA *brna, PropertyRNA *cprop); -#ifdef WITH_NEW_CURVES_TYPE void RNA_def_main_hair_curves(BlenderRNA *brna, PropertyRNA *cprop); -#endif void RNA_def_main_pointclouds(BlenderRNA *brna, PropertyRNA *cprop); void RNA_def_main_volumes(BlenderRNA *brna, PropertyRNA *cprop); #ifdef WITH_SIMULATION_DATABLOCK diff --git a/source/blender/makesrna/intern/rna_main.c b/source/blender/makesrna/intern/rna_main.c index 7200bcaa2a6..4aedb1fc611 100644 --- a/source/blender/makesrna/intern/rna_main.c +++ b/source/blender/makesrna/intern/rna_main.c @@ -96,9 +96,7 @@ RNA_MAIN_LISTBASE_FUNCS_DEF(collections) RNA_MAIN_LISTBASE_FUNCS_DEF(curves) RNA_MAIN_LISTBASE_FUNCS_DEF(fonts) RNA_MAIN_LISTBASE_FUNCS_DEF(gpencils) -# ifdef WITH_NEW_CURVES_TYPE RNA_MAIN_LISTBASE_FUNCS_DEF(hair_curves) -# endif RNA_MAIN_LISTBASE_FUNCS_DEF(images) RNA_MAIN_LISTBASE_FUNCS_DEF(lattices) RNA_MAIN_LISTBASE_FUNCS_DEF(libraries) @@ -375,7 +373,6 @@ void RNA_def_main(BlenderRNA *brna) "Light Probes", "Light Probe data-blocks", RNA_def_main_lightprobes}, -# ifdef WITH_NEW_CURVES_TYPE /** * \note The name `hair_curves` is chosen to be different than `curves`, * but they are generic curve data-blocks, not just for hair. @@ -386,7 +383,6 @@ void RNA_def_main(BlenderRNA *brna) "Hair Curves", "Hair curve data-blocks", RNA_def_main_hair_curves}, -# endif {"pointclouds", "PointCloud", "rna_Main_pointclouds_begin", diff --git a/source/blender/makesrna/intern/rna_main_api.c b/source/blender/makesrna/intern/rna_main_api.c index 6c621604e40..1f21fa3fab9 100644 --- a/source/blender/makesrna/intern/rna_main_api.c +++ b/source/blender/makesrna/intern/rna_main_api.c @@ -749,7 +749,6 @@ static bGPdata *rna_Main_gpencils_new(Main *bmain, const char *name) return gpd; } -# ifdef WITH_NEW_CURVES_TYPE static Curves *rna_Main_hair_curves_new(Main *bmain, const char *name) { char safe_name[MAX_ID_NAME - 2]; @@ -762,7 +761,6 @@ static Curves *rna_Main_hair_curves_new(Main *bmain, const char *name) return curves; } -# endif static PointCloud *rna_Main_pointclouds_new(Main *bmain, const char *name) { @@ -847,9 +845,7 @@ RNA_MAIN_ID_TAG_FUNCS_DEF(cachefiles, cachefiles, ID_CF) RNA_MAIN_ID_TAG_FUNCS_DEF(paintcurves, paintcurves, ID_PC) RNA_MAIN_ID_TAG_FUNCS_DEF(workspaces, workspaces, ID_WS) RNA_MAIN_ID_TAG_FUNCS_DEF(lightprobes, lightprobes, ID_LP) -# ifdef WITH_NEW_CURVES_TYPE RNA_MAIN_ID_TAG_FUNCS_DEF(hair_curves, hair_curves, ID_CV) -# endif RNA_MAIN_ID_TAG_FUNCS_DEF(pointclouds, pointclouds, ID_PT) RNA_MAIN_ID_TAG_FUNCS_DEF(volumes, volumes, ID_VO) # ifdef WITH_SIMULATION_DATABLOCK @@ -2255,7 +2251,6 @@ void RNA_def_main_lightprobes(BlenderRNA *brna, PropertyRNA *cprop) RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); } -# ifdef WITH_NEW_CURVES_TYPE void RNA_def_main_hair_curves(BlenderRNA *brna, PropertyRNA *cprop) { StructRNA *srna; @@ -2299,7 +2294,6 @@ void RNA_def_main_hair_curves(BlenderRNA *brna, PropertyRNA *cprop) parm = RNA_def_boolean(func, "value", 0, "Value", ""); RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); } -# endif void RNA_def_main_pointclouds(BlenderRNA *brna, PropertyRNA *cprop) { diff --git a/source/blender/makesrna/intern/rna_object.c b/source/blender/makesrna/intern/rna_object.c index a68ef361f04..103c77fa808 100644 --- a/source/blender/makesrna/intern/rna_object.c +++ b/source/blender/makesrna/intern/rna_object.c @@ -607,11 +607,7 @@ static StructRNA *rna_Object_data_typef(PointerRNA *ptr) case OB_GPENCIL: return &RNA_GreasePencil; case OB_CURVES: -# ifdef WITH_NEW_CURVES_TYPE return &RNA_Curves; -# else - return &RNA_ID; -# endif case OB_POINTCLOUD: return &RNA_PointCloud; case OB_VOLUME: diff --git a/source/blender/modifiers/CMakeLists.txt b/source/blender/modifiers/CMakeLists.txt index 1aac3c2191d..73daabec9b3 100644 --- a/source/blender/modifiers/CMakeLists.txt +++ b/source/blender/modifiers/CMakeLists.txt @@ -217,7 +217,6 @@ endif() if(WITH_EXPERIMENTAL_FEATURES) add_definitions(-DWITH_SIMULATION_DATABLOCK) - add_definitions(-DWITH_NEW_CURVES_TYPE) endif() # So we can have special tricks in modifier system. -- cgit v1.2.3 From d14c2d549b2fdde2a116f6a37837a1e3776da3cb Mon Sep 17 00:00:00 2001 From: Germano Cavalcante Date: Tue, 19 Jul 2022 11:43:38 -0300 Subject: Fix T99643: Vertex Crease fails with symmetry Create a transform conversion type that only considers the Vertex Custom Data. This reduces the complexity of converting Meshes and slightly optimizes the transformation. --- source/blender/editors/transform/CMakeLists.txt | 1 + source/blender/editors/transform/transform.h | 1 + .../blender/editors/transform/transform_convert.c | 15 +- .../blender/editors/transform/transform_convert.h | 5 + .../editors/transform/transform_convert_mesh.c | 26 +- .../transform/transform_convert_mesh_vert_cdata.c | 290 +++++++++++++++++++++ .../transform/transform_mode_edge_bevelweight.c | 11 +- .../editors/transform/transform_mode_edge_crease.c | 11 +- 8 files changed, 319 insertions(+), 41 deletions(-) create mode 100644 source/blender/editors/transform/transform_convert_mesh_vert_cdata.c diff --git a/source/blender/editors/transform/CMakeLists.txt b/source/blender/editors/transform/CMakeLists.txt index 68c4f4e76ca..6984dcb18d4 100644 --- a/source/blender/editors/transform/CMakeLists.txt +++ b/source/blender/editors/transform/CMakeLists.txt @@ -39,6 +39,7 @@ set(SRC transform_convert_mesh_edge.c transform_convert_mesh_skin.c transform_convert_mesh_uv.c + transform_convert_mesh_vert_cdata.c transform_convert_nla.c transform_convert_node.c transform_convert_object.c diff --git a/source/blender/editors/transform/transform.h b/source/blender/editors/transform/transform.h index 049d1b6a90e..b01affc7307 100644 --- a/source/blender/editors/transform/transform.h +++ b/source/blender/editors/transform/transform.h @@ -222,6 +222,7 @@ typedef enum { TC_MESH_EDGES, TC_MESH_SKIN, TC_MESH_UV, + TC_MESH_VERT_CDATA, TC_NLA_DATA, TC_NODE_DATA, TC_OBJECT, diff --git a/source/blender/editors/transform/transform_convert.c b/source/blender/editors/transform/transform_convert.c index bab700560fe..b0148ce508c 100644 --- a/source/blender/editors/transform/transform_convert.c +++ b/source/blender/editors/transform/transform_convert.c @@ -823,6 +823,7 @@ void special_aftertrans_update(bContext *C, TransInfo *t) case TC_GPENCIL: case TC_LATTICE_VERTS: case TC_MBALL_VERTS: + case TC_MESH_VERT_CDATA: case TC_MESH_UV: case TC_MESH_SKIN: case TC_OBJECT_TEXSPACE: @@ -905,6 +906,7 @@ static void init_proportional_edit(TransInfo *t) case TC_MESH_EDGES: case TC_MESH_SKIN: case TC_MESH_UV: + case TC_MESH_VERT_CDATA: case TC_NODE_DATA: case TC_OBJECT: case TC_PARTICLE_VERTS: @@ -939,7 +941,7 @@ static void init_proportional_edit(TransInfo *t) if (ELEM(convert_type, TC_ACTION_DATA, TC_GRAPH_EDIT_DATA)) { /* Distance has already been set. */ } - else if (ELEM(convert_type, TC_MESH_VERTS, TC_MESH_SKIN)) { + else if (ELEM(convert_type, TC_MESH_VERTS, TC_MESH_SKIN, TC_MESH_VERT_CDATA)) { if (t->flag & T_PROP_CONNECTED) { /* Already calculated by transform_convert_mesh_connectivity_distance. */ } @@ -984,6 +986,7 @@ static void init_TransDataContainers(TransInfo *t, case TC_MESH_EDGES: case TC_MESH_SKIN: case TC_MESH_UV: + case TC_MESH_VERT_CDATA: break; case TC_ACTION_DATA: case TC_GRAPH_EDIT_DATA: @@ -1095,6 +1098,7 @@ static eTFlag flags_from_data_type(eTConvertType data_type) case TC_MBALL_VERTS: case TC_MESH_VERTS: case TC_MESH_SKIN: + case TC_MESH_VERT_CDATA: return T_EDIT | T_POINTS; case TC_MESH_EDGES: return T_EDIT; @@ -1194,6 +1198,9 @@ static eTConvertType convert_type_get(const TransInfo *t, Object **r_obj_armatur if (t->mode == TFM_SKIN_RESIZE) { convert_type = TC_MESH_SKIN; } + else if (ELEM(t->mode, TFM_BWEIGHT, TFM_VERT_CREASE)) { + convert_type = TC_MESH_VERT_CDATA; + } else { convert_type = TC_MESH_VERTS; } @@ -1317,6 +1324,9 @@ void createTransData(bContext *C, TransInfo *t) case TC_NLA_DATA: createTransNlaData(C, t); break; + case TC_MESH_VERT_CDATA: + createTransMeshVertCData(t); + break; case TC_NODE_DATA: createTransNodeData(t); break; @@ -1623,6 +1633,9 @@ void recalcData(TransInfo *t) case TC_MESH_UV: recalcData_uv(t); break; + case TC_MESH_VERT_CDATA: + recalcData_mesh_cdata(t); + break; case TC_NLA_DATA: recalcData_nla(t); break; diff --git a/source/blender/editors/transform/transform_convert.h b/source/blender/editors/transform/transform_convert.h index f25a3db3f75..1b7c33e443c 100644 --- a/source/blender/editors/transform/transform_convert.h +++ b/source/blender/editors/transform/transform_convert.h @@ -252,6 +252,11 @@ void createTransUVs(bContext *C, TransInfo *t); /* helper for recalcData() - for Image Editor transforms */ void recalcData_uv(TransInfo *t); +/* transform_convert_mesh_vert_cdata.c */ + +void createTransMeshVertCData(TransInfo *t); +void recalcData_mesh_cdata(TransInfo *t); + /* transform_convert_nla.c */ void createTransNlaData(bContext *C, TransInfo *t); diff --git a/source/blender/editors/transform/transform_convert_mesh.c b/source/blender/editors/transform/transform_convert_mesh.c index c8d943df3fa..de736fad94e 100644 --- a/source/blender/editors/transform/transform_convert_mesh.c +++ b/source/blender/editors/transform/transform_convert_mesh.c @@ -1408,7 +1408,6 @@ static void VertsToTransData(TransInfo *t, TransDataExtension *tx, BMEditMesh *em, BMVert *eve, - float *bweight, const struct TransIslandData *island_data, const int island_index) { @@ -1449,11 +1448,7 @@ static void VertsToTransData(TransInfo *t, td->ext = NULL; td->val = NULL; td->extra = eve; - if (ELEM(t->mode, TFM_BWEIGHT, TFM_VERT_CREASE)) { - td->val = bweight; - td->ival = *bweight; - } - else if (t->mode == TFM_SHRINKFATTEN) { + if (t->mode == TFM_SHRINKFATTEN) { td->ext = tx; tx->isize[0] = BM_vert_calc_shell_factor_ex(eve, no, BM_ELEM_SELECT); } @@ -1589,17 +1584,6 @@ void createTransEditVerts(TransInfo *t) "TransObData ext"); } - int cd_vert_bweight_offset = -1; - int cd_vert_crease_offset = -1; - if (t->mode == TFM_BWEIGHT) { - BM_mesh_cd_flag_ensure(bm, BKE_mesh_from_object(tc->obedit), ME_CDFLAG_VERT_BWEIGHT); - cd_vert_bweight_offset = CustomData_get_offset(&bm->vdata, CD_BWEIGHT); - } - else if (t->mode == TFM_VERT_CREASE) { - BM_mesh_cd_flag_ensure(bm, BKE_mesh_from_object(tc->obedit), ME_CDFLAG_VERT_CREASE); - cd_vert_crease_offset = CustomData_get_offset(&bm->vdata, CD_CREASE); - } - TransData *tob = tc->data; TransDataMirror *td_mirror = tc->data_mirror; BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, a) { @@ -1632,15 +1616,9 @@ void createTransEditVerts(TransInfo *t) td_mirror++; } else if (prop_mode || BM_elem_flag_test(eve, BM_ELEM_SELECT)) { - float *bweight = (cd_vert_bweight_offset != -1) ? - BM_ELEM_CD_GET_VOID_P(eve, cd_vert_bweight_offset) : - (cd_vert_crease_offset != -1) ? - BM_ELEM_CD_GET_VOID_P(eve, cd_vert_crease_offset) : - NULL; - /* Do not use the island center in case we are using islands * only to get axis for snap/rotate to normal... */ - VertsToTransData(t, tob, tx, em, eve, bweight, &island_data, island_index); + VertsToTransData(t, tob, tx, em, eve, &island_data, island_index); if (tx) { tx++; } diff --git a/source/blender/editors/transform/transform_convert_mesh_vert_cdata.c b/source/blender/editors/transform/transform_convert_mesh_vert_cdata.c new file mode 100644 index 00000000000..ff58c2c888b --- /dev/null +++ b/source/blender/editors/transform/transform_convert_mesh_vert_cdata.c @@ -0,0 +1,290 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ + +/** \file + * \ingroup edtransform + */ + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "MEM_guardedalloc.h" + +#include "BLI_math.h" + +#include "BKE_context.h" +#include "BKE_crazyspace.h" +#include "BKE_editmesh.h" +#include "BKE_modifier.h" +#include "BKE_scene.h" + +#include "ED_mesh.h" + +#include "DEG_depsgraph_query.h" + +#include "transform.h" +#include "transform_orientations.h" + +#include "transform_convert.h" + +/* -------------------------------------------------------------------- */ +/** \name Edit Mesh #CD_BWEIGHT and #CD_CREASE Transform Creation + * \{ */ + +static float *tc_mesh_cdata_transdata_center(const struct TransIslandData *island_data, + const int island_index, + BMVert *eve) +{ + if (island_data->center && island_index != -1) { + return island_data->center[island_index]; + } + return eve->co; +} + +static void tc_mesh_cdata_transdata_create(TransDataBasic *td, + BMEditMesh *em, + BMVert *eve, + float *weight, + const struct TransIslandData *island_data, + const int island_index) +{ + BLI_assert(BM_elem_flag_test(eve, BM_ELEM_HIDDEN) == 0); + + td->loc = weight; + td->iloc[0] = *weight; + + if (BM_elem_flag_test(eve, BM_ELEM_SELECT)) { + td->flag |= TD_SELECTED; + } + + copy_v3_v3(td->center, tc_mesh_cdata_transdata_center(island_data, island_index, eve)); + td->extra = eve; +} + +void createTransMeshVertCData(TransInfo *t) +{ + BLI_assert(ELEM(t->mode, TFM_BWEIGHT, TFM_VERT_CREASE)); + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + BMEditMesh *em = BKE_editmesh_from_object(tc->obedit); + Mesh *me = tc->obedit->data; + BMesh *bm = em->bm; + BMVert *eve; + BMIter iter; + float mtx[3][3], smtx[3][3]; + int a; + const int prop_mode = (t->flag & T_PROP_EDIT) ? (t->flag & T_PROP_EDIT_ALL) : 0; + + struct TransIslandData island_data = {NULL}; + struct TransMirrorData mirror_data = {NULL}; + struct TransMeshDataCrazySpace crazyspace_data = {NULL}; + + /* Support other objects using PET to adjust these, unless connected is enabled. */ + if ((!prop_mode || (prop_mode & T_PROP_CONNECTED)) && (bm->totvertsel == 0)) { + continue; + } + + int cd_offset = -1; + if (t->mode == TFM_BWEIGHT) { + BM_mesh_cd_flag_ensure(bm, me, ME_CDFLAG_VERT_BWEIGHT); + cd_offset = CustomData_get_offset(&bm->vdata, CD_BWEIGHT); + } + else { + BM_mesh_cd_flag_ensure(bm, me, ME_CDFLAG_VERT_CREASE); + cd_offset = CustomData_get_offset(&bm->vdata, CD_CREASE); + } + + if (cd_offset == -1) { + continue; + } + + int data_len = 0; + if (prop_mode) { + BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) { + if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) { + data_len++; + } + } + } + else { + data_len = bm->totvertsel; + } + + if (data_len == 0) { + continue; + } + + const bool is_island_center = (t->around == V3D_AROUND_LOCAL_ORIGINS); + if (is_island_center) { + /* In this specific case, near-by vertices will need to know + * the island of the nearest connected vertex. */ + const bool calc_single_islands = ((prop_mode & T_PROP_CONNECTED) && + (t->around == V3D_AROUND_LOCAL_ORIGINS) && + (em->selectmode & SCE_SELECT_VERTEX)); + + const bool calc_island_center = false; + const bool calc_island_axismtx = false; + + transform_convert_mesh_islands_calc( + em, calc_single_islands, calc_island_center, calc_island_axismtx, &island_data); + } + + copy_m3_m4(mtx, tc->obedit->obmat); + /* we use a pseudo-inverse so that when one of the axes is scaled to 0, + * matrix inversion still works and we can still moving along the other */ + pseudoinverse_m3_m3(smtx, mtx, PSEUDOINVERSE_EPSILON); + + /* Original index of our connected vertex when connected distances are calculated. + * Optional, allocate if needed. */ + int *dists_index = NULL; + float *dists = NULL; + if (prop_mode & T_PROP_CONNECTED) { + dists = MEM_mallocN(bm->totvert * sizeof(float), __func__); + if (is_island_center) { + dists_index = MEM_mallocN(bm->totvert * sizeof(int), __func__); + } + transform_convert_mesh_connectivity_distance(em->bm, mtx, dists, dists_index); + } + + /* Create TransDataMirror. */ + if (tc->use_mirror_axis_any) { + bool use_topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0; + bool use_select = (t->flag & T_PROP_EDIT) == 0; + const bool mirror_axis[3] = { + tc->use_mirror_axis_x, tc->use_mirror_axis_y, tc->use_mirror_axis_z}; + transform_convert_mesh_mirrordata_calc( + em, use_select, use_topology, mirror_axis, &mirror_data); + + if (mirror_data.vert_map) { + tc->data_mirror_len = mirror_data.mirror_elem_len; + tc->data_mirror = MEM_mallocN(mirror_data.mirror_elem_len * sizeof(*tc->data_mirror), + __func__); + + BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, a) { + if (prop_mode || BM_elem_flag_test(eve, BM_ELEM_SELECT)) { + if (mirror_data.vert_map[a].index != -1) { + data_len--; + } + } + } + } + } + + /* Detect CrazySpace [tm]. */ + transform_convert_mesh_crazyspace_detect(t, tc, em, &crazyspace_data); + + /* Create TransData. */ + BLI_assert(data_len >= 1); + tc->data_len = data_len; + tc->data = MEM_callocN(data_len * sizeof(TransData), "TransObData(Mesh EditMode)"); + + TransData *td = tc->data; + TransDataMirror *td_mirror = tc->data_mirror; + BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, a) { + if (BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) { + continue; + } + + int island_index = -1; + if (island_data.island_vert_map) { + const int connected_index = (dists_index && dists_index[a] != -1) ? dists_index[a] : a; + island_index = island_data.island_vert_map[connected_index]; + } + + float *weight = BM_ELEM_CD_GET_VOID_P(eve, cd_offset); + if (mirror_data.vert_map && mirror_data.vert_map[a].index != -1) { + tc_mesh_cdata_transdata_create( + (TransDataBasic *)td_mirror, em, eve, weight, &island_data, island_index); + + int elem_index = mirror_data.vert_map[a].index; + BMVert *v_src = BM_vert_at_index(bm, elem_index); + + td_mirror->flag |= mirror_data.vert_map[a].flag; + td_mirror->loc_src = BM_ELEM_CD_GET_VOID_P(v_src, cd_offset); + td_mirror++; + } + else if (prop_mode || BM_elem_flag_test(eve, BM_ELEM_SELECT)) { + tc_mesh_cdata_transdata_create( + (TransDataBasic *)td, em, eve, weight, &island_data, island_index); + + if (t->around == V3D_AROUND_LOCAL_ORIGINS) { + createSpaceNormal(td->axismtx, eve->no); + } + else { + /* Setting normals */ + copy_v3_v3(td->axismtx[2], eve->no); + td->axismtx[0][0] = td->axismtx[0][1] = td->axismtx[0][2] = td->axismtx[1][0] = + td->axismtx[1][1] = td->axismtx[1][2] = 0.0f; + } + + if (prop_mode) { + if (prop_mode & T_PROP_CONNECTED) { + td->dist = dists[a]; + } + else { + td->flag |= TD_NOTCONNECTED; + td->dist = FLT_MAX; + } + } + + /* CrazySpace */ + transform_convert_mesh_crazyspace_transdata_set( + mtx, + smtx, + crazyspace_data.defmats ? crazyspace_data.defmats[a] : NULL, + crazyspace_data.quats && BM_elem_flag_test(eve, BM_ELEM_TAG) ? + crazyspace_data.quats[a] : + NULL, + td); + + td++; + } + } + + transform_convert_mesh_islanddata_free(&island_data); + transform_convert_mesh_mirrordata_free(&mirror_data); + transform_convert_mesh_crazyspace_free(&crazyspace_data); + if (dists) { + MEM_freeN(dists); + } + if (dists_index) { + MEM_freeN(dists_index); + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Recalc Mesh Data + * \{ */ + +static void tc_mesh_cdata_apply_to_mirror(TransInfo *t) +{ + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + if (tc->use_mirror_axis_any) { + TransDataMirror *td_mirror = tc->data_mirror; + for (int i = 0; i < tc->data_mirror_len; i++, td_mirror++) { + td_mirror->loc[0] = td_mirror->loc_src[0]; + } + } + } +} + +void recalcData_mesh_cdata(TransInfo *t) +{ + bool is_canceling = t->state == TRANS_CANCEL; + /* mirror modifier clipping? */ + if (!is_canceling) { + if (!(t->flag & T_NO_MIRROR)) { + tc_mesh_cdata_apply_to_mirror(t); + } + } + + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + DEG_id_tag_update(tc->obedit->data, ID_RECALC_GEOMETRY); + BMEditMesh *em = BKE_editmesh_from_object(tc->obedit); + BKE_editmesh_looptri_and_normals_calc(em); + } +} + +/** \} */ diff --git a/source/blender/editors/transform/transform_mode_edge_bevelweight.c b/source/blender/editors/transform/transform_mode_edge_bevelweight.c index 987d8396907..e96e74b596c 100644 --- a/source/blender/editors/transform/transform_mode_edge_bevelweight.c +++ b/source/blender/editors/transform/transform_mode_edge_bevelweight.c @@ -44,16 +44,11 @@ static void transdata_elem_bevel_weight(const TransInfo *UNUSED(t), TransData *td, const float weight) { - if (td->val == NULL) { + if (td->loc == NULL) { return; } - *td->val = td->ival + weight * td->factor; - if (*td->val < 0.0f) { - *td->val = 0.0f; - } - if (*td->val > 1.0f) { - *td->val = 1.0f; - } + *td->loc = td->iloc[0] + weight * td->factor; + CLAMP(*td->loc, 0.0f, 1.0f); } static void transdata_elem_bevel_weight_fn(void *__restrict iter_data_v, diff --git a/source/blender/editors/transform/transform_mode_edge_crease.c b/source/blender/editors/transform/transform_mode_edge_crease.c index f1acc2a4c9a..1a3ccf30387 100644 --- a/source/blender/editors/transform/transform_mode_edge_crease.c +++ b/source/blender/editors/transform/transform_mode_edge_crease.c @@ -44,17 +44,12 @@ static void transdata_elem_crease(const TransInfo *UNUSED(t), TransData *td, const float crease) { - if (td->val == NULL) { + if (td->loc == NULL) { return; } - *td->val = td->ival + crease * td->factor; - if (*td->val < 0.0f) { - *td->val = 0.0f; - } - if (*td->val > 1.0f) { - *td->val = 1.0f; - } + *td->loc = td->iloc[0] + crease * td->factor; + CLAMP(*td->loc, 0.0f, 1.0f); } static void transdata_elem_crease_fn(void *__restrict iter_data_v, -- cgit v1.2.3 From edc89c7f46f25122d88eb096f34626da035032be Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Tue, 19 Jul 2022 19:21:38 +0200 Subject: Fix build error when not using unity build --- source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc index 057e08cc33d..dd8471d2dac 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc @@ -9,6 +9,7 @@ #include "node_geometry_util.hh" +#include "BKE_curves.hh" #include "BKE_spline.hh" namespace blender::nodes::node_geo_curve_fillet_cc { -- cgit v1.2.3 From cd478fbfb3d0bf6c80b112fadbe4c0e1f550dc90 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Tue, 19 Jul 2022 18:04:34 +0200 Subject: Fix error printed in console when running Shade Flat operator Only the Shade Smooth operator has autosmooth settings. --- source/blender/editors/object/object_edit.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c index e65b5a61299..4896ddb5258 100644 --- a/source/blender/editors/object/object_edit.c +++ b/source/blender/editors/object/object_edit.c @@ -1469,8 +1469,6 @@ void OBJECT_OT_paths_clear(wmOperatorType *ot) static int shade_smooth_exec(bContext *C, wmOperator *op) { const bool use_smooth = STREQ(op->idname, "OBJECT_OT_shade_smooth"); - const bool use_auto_smooth = RNA_boolean_get(op->ptr, "use_auto_smooth"); - const float auto_smooth_angle = RNA_float_get(op->ptr, "auto_smooth_angle"); bool changed_multi = false; bool has_linked_data = false; @@ -1518,7 +1516,11 @@ static int shade_smooth_exec(bContext *C, wmOperator *op) bool changed = false; if (ob->type == OB_MESH) { BKE_mesh_smooth_flag_set(ob->data, use_smooth); - BKE_mesh_auto_smooth_flag_set(ob->data, use_auto_smooth, auto_smooth_angle); + if (use_smooth) { + const bool use_auto_smooth = RNA_boolean_get(op->ptr, "use_auto_smooth"); + const float auto_smooth_angle = RNA_float_get(op->ptr, "auto_smooth_angle"); + BKE_mesh_auto_smooth_flag_set(ob->data, use_auto_smooth, auto_smooth_angle); + } BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL); changed = true; } -- cgit v1.2.3 From 75e62df4297093b2a65e295a43b0ff0fbffe44b7 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Tue, 19 Jul 2022 19:21:51 +0200 Subject: Cleanup: compiler warning --- source/blender/editors/transform/transform_convert_mesh_vert_cdata.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/source/blender/editors/transform/transform_convert_mesh_vert_cdata.c b/source/blender/editors/transform/transform_convert_mesh_vert_cdata.c index ff58c2c888b..104b45de138 100644 --- a/source/blender/editors/transform/transform_convert_mesh_vert_cdata.c +++ b/source/blender/editors/transform/transform_convert_mesh_vert_cdata.c @@ -42,7 +42,6 @@ static float *tc_mesh_cdata_transdata_center(const struct TransIslandData *islan } static void tc_mesh_cdata_transdata_create(TransDataBasic *td, - BMEditMesh *em, BMVert *eve, float *weight, const struct TransIslandData *island_data, @@ -193,7 +192,7 @@ void createTransMeshVertCData(TransInfo *t) float *weight = BM_ELEM_CD_GET_VOID_P(eve, cd_offset); if (mirror_data.vert_map && mirror_data.vert_map[a].index != -1) { tc_mesh_cdata_transdata_create( - (TransDataBasic *)td_mirror, em, eve, weight, &island_data, island_index); + (TransDataBasic *)td_mirror, eve, weight, &island_data, island_index); int elem_index = mirror_data.vert_map[a].index; BMVert *v_src = BM_vert_at_index(bm, elem_index); @@ -204,7 +203,7 @@ void createTransMeshVertCData(TransInfo *t) } else if (prop_mode || BM_elem_flag_test(eve, BM_ELEM_SELECT)) { tc_mesh_cdata_transdata_create( - (TransDataBasic *)td, em, eve, weight, &island_data, island_index); + (TransDataBasic *)td, eve, weight, &island_data, island_index); if (t->around == V3D_AROUND_LOCAL_ORIGINS) { createSpaceNormal(td->axismtx, eve->no); -- cgit v1.2.3 From 3b7ac10d6252a2a9cecbb9be7850a1dea41e281d Mon Sep 17 00:00:00 2001 From: Tomek Gubala Date: Tue, 19 Jul 2022 19:24:20 +0200 Subject: UV: add Snap Cursor to Origin Similar to snapping to the world origin in the 3D viewport. This can be found in the Shift+S pie menu and UV > Snap menu. Differential Revision: https://developer.blender.org/D15055 --- release/scripts/startup/bl_ui/space_image.py | 6 ++++++ source/blender/editors/uvedit/uvedit_ops.c | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/release/scripts/startup/bl_ui/space_image.py b/release/scripts/startup/bl_ui/space_image.py index 5d25d02d98e..ab3c863ea2d 100644 --- a/release/scripts/startup/bl_ui/space_image.py +++ b/release/scripts/startup/bl_ui/space_image.py @@ -306,6 +306,7 @@ class IMAGE_MT_uvs_snap(Menu): layout.operator("uv.snap_cursor", text="Cursor to Pixels").target = 'PIXELS' layout.operator("uv.snap_cursor", text="Cursor to Selected").target = 'SELECTED' + layout.operator("uv.snap_cursor", text="Cursor to Origin").target = 'ORIGIN' class IMAGE_MT_uvs_mirror(Menu): @@ -572,6 +573,11 @@ class IMAGE_MT_uvs_snap_pie(Menu): text="Selected to Adjacent Unselected", icon='RESTRICT_SELECT_OFF', ).target = 'ADJACENT_UNSELECTED' + pie.operator( + "uv.snap_cursor", + text="Cursor to Origin", + icon='PIVOT_CURSOR', + ).target = 'ORIGIN' class IMAGE_MT_view_pie(Menu): diff --git a/source/blender/editors/uvedit/uvedit_ops.c b/source/blender/editors/uvedit/uvedit_ops.c index 4e99eb3fc0f..74a9989f550 100644 --- a/source/blender/editors/uvedit/uvedit_ops.c +++ b/source/blender/editors/uvedit/uvedit_ops.c @@ -1036,6 +1036,12 @@ static bool uv_snap_cursor_to_selection(Scene *scene, return ED_uvedit_center_multi(scene, objects_edit, objects_len, sima->cursor, sima->around); } +static void uv_snap_cursor_to_origin(float uvco[2]) +{ + uvco[0] = 0; + uvco[1] = 0; +} + static int uv_snap_cursor_exec(bContext *C, wmOperator *op) { SpaceImage *sima = CTX_wm_space_image(C); @@ -1058,6 +1064,10 @@ static int uv_snap_cursor_exec(bContext *C, wmOperator *op) MEM_freeN(objects); break; } + case 2: + uv_snap_cursor_to_origin(sima->cursor); + changed = true; + break; } if (!changed) { @@ -1074,6 +1084,7 @@ static void UV_OT_snap_cursor(wmOperatorType *ot) static const EnumPropertyItem target_items[] = { {0, "PIXELS", 0, "Pixels", ""}, {1, "SELECTED", 0, "Selected", ""}, + {2, "ORIGIN", 0, "Origin", ""}, {0, NULL, 0, NULL, NULL}, }; -- cgit v1.2.3 From 43d04c7eeb8a3ec50b1c022522fb8fa6b42a883c Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Tue, 19 Jul 2022 19:31:26 +0200 Subject: UI Code Quality: Move eyedropper files to own folder Part of T98518 --- source/blender/editors/interface/CMakeLists.txt | 16 +- .../interface/eyedroppers/eyedropper_color.c | 554 +++++++++++++++++++++ .../interface/eyedroppers/eyedropper_colorband.c | 367 ++++++++++++++ .../interface/eyedroppers/eyedropper_datablock.c | 378 ++++++++++++++ .../interface/eyedroppers/eyedropper_depth.c | 381 ++++++++++++++ .../interface/eyedroppers/eyedropper_driver.c | 220 ++++++++ .../eyedroppers/eyedropper_gpencil_color.c | 373 ++++++++++++++ .../interface/eyedroppers/eyedropper_intern.h | 57 +++ .../interface/eyedroppers/interface_eyedropper.c | 161 ++++++ .../editors/interface/interface_eyedropper.c | 161 ------ .../editors/interface/interface_eyedropper_color.c | 554 --------------------- .../interface/interface_eyedropper_colorband.c | 367 -------------- .../interface/interface_eyedropper_datablock.c | 378 -------------- .../editors/interface/interface_eyedropper_depth.c | 381 -------------- .../interface/interface_eyedropper_driver.c | 220 -------- .../interface/interface_eyedropper_gpencil_color.c | 373 -------------- .../interface/interface_eyedropper_intern.h | 57 --- 17 files changed, 2499 insertions(+), 2499 deletions(-) create mode 100644 source/blender/editors/interface/eyedroppers/eyedropper_color.c create mode 100644 source/blender/editors/interface/eyedroppers/eyedropper_colorband.c create mode 100644 source/blender/editors/interface/eyedroppers/eyedropper_datablock.c create mode 100644 source/blender/editors/interface/eyedroppers/eyedropper_depth.c create mode 100644 source/blender/editors/interface/eyedroppers/eyedropper_driver.c create mode 100644 source/blender/editors/interface/eyedroppers/eyedropper_gpencil_color.c create mode 100644 source/blender/editors/interface/eyedroppers/eyedropper_intern.h create mode 100644 source/blender/editors/interface/eyedroppers/interface_eyedropper.c delete mode 100644 source/blender/editors/interface/interface_eyedropper.c delete mode 100644 source/blender/editors/interface/interface_eyedropper_color.c delete mode 100644 source/blender/editors/interface/interface_eyedropper_colorband.c delete mode 100644 source/blender/editors/interface/interface_eyedropper_datablock.c delete mode 100644 source/blender/editors/interface/interface_eyedropper_depth.c delete mode 100644 source/blender/editors/interface/interface_eyedropper_driver.c delete mode 100644 source/blender/editors/interface/interface_eyedropper_gpencil_color.c delete mode 100644 source/blender/editors/interface/interface_eyedropper_intern.h diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index 4c206f9c057..07c3bfdafbf 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -26,6 +26,13 @@ set(INC ) set(SRC + eyedroppers/interface_eyedropper.c + eyedroppers/eyedropper_color.c + eyedroppers/eyedropper_colorband.c + eyedroppers/eyedropper_datablock.c + eyedroppers/eyedropper_depth.c + eyedroppers/eyedropper_driver.c + eyedroppers/eyedropper_gpencil_color.c interface.cc interface_align.c interface_anim.c @@ -35,13 +42,6 @@ set(SRC interface_drag.cc interface_draw.c interface_dropboxes.cc - interface_eyedropper.c - interface_eyedropper_color.c - interface_eyedropper_colorband.c - interface_eyedropper_datablock.c - interface_eyedropper_depth.c - interface_eyedropper_driver.c - interface_eyedropper_gpencil_color.c interface_handlers.c interface_icons.c interface_icons_event.c @@ -80,7 +80,7 @@ set(SRC views/interface_view.cc views/tree_view.cc - interface_eyedropper_intern.h + eyedroppers/eyedropper_intern.h interface_intern.h interface_regions_intern.h ) diff --git a/source/blender/editors/interface/eyedroppers/eyedropper_color.c b/source/blender/editors/interface/eyedroppers/eyedropper_color.c new file mode 100644 index 00000000000..a2161008ec4 --- /dev/null +++ b/source/blender/editors/interface/eyedroppers/eyedropper_color.c @@ -0,0 +1,554 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * Eyedropper (RGB Color) + * + * Defines: + * - #UI_OT_eyedropper_color + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "BLI_listbase.h" +#include "BLI_math_vector.h" +#include "BLI_string.h" + +#include "BKE_context.h" +#include "BKE_cryptomatte.h" +#include "BKE_image.h" +#include "BKE_main.h" +#include "BKE_node.h" +#include "BKE_screen.h" + +#include "NOD_composite.h" + +#include "RNA_access.h" +#include "RNA_prototypes.h" + +#include "UI_interface.h" + +#include "IMB_colormanagement.h" +#include "IMB_imbuf_types.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_define.h" + +#include "interface_intern.h" + +#include "ED_clip.h" +#include "ED_image.h" +#include "ED_node.h" +#include "ED_screen.h" + +#include "RE_pipeline.h" + +#include "eyedropper_intern.h" + +typedef struct Eyedropper { + struct ColorManagedDisplay *display; + + PointerRNA ptr; + PropertyRNA *prop; + int index; + bool is_undo; + + bool is_set; + float init_col[3]; /* for resetting on cancel */ + + bool accum_start; /* has mouse been pressed */ + float accum_col[3]; + int accum_tot; + + void *draw_handle_sample_text; + char sample_text[MAX_NAME]; + + bNode *crypto_node; + struct CryptomatteSession *cryptomatte_session; +} Eyedropper; + +static void eyedropper_draw_cb(const wmWindow *window, void *arg) +{ + Eyedropper *eye = arg; + eyedropper_draw_cursor_text_window(window, eye->sample_text); +} + +static bool eyedropper_init(bContext *C, wmOperator *op) +{ + Eyedropper *eye = MEM_callocN(sizeof(Eyedropper), __func__); + + uiBut *but = UI_context_active_but_prop_get(C, &eye->ptr, &eye->prop, &eye->index); + const enum PropertySubType prop_subtype = eye->prop ? RNA_property_subtype(eye->prop) : 0; + + if ((eye->ptr.data == NULL) || (eye->prop == NULL) || + (RNA_property_editable(&eye->ptr, eye->prop) == false) || + (RNA_property_array_length(&eye->ptr, eye->prop) < 3) || + (RNA_property_type(eye->prop) != PROP_FLOAT) || + (ELEM(prop_subtype, PROP_COLOR, PROP_COLOR_GAMMA) == 0)) { + MEM_freeN(eye); + return false; + } + op->customdata = eye; + + eye->is_undo = UI_but_flag_is_set(but, UI_BUT_UNDO); + + float col[4]; + RNA_property_float_get_array(&eye->ptr, eye->prop, col); + if (eye->ptr.type == &RNA_CompositorNodeCryptomatteV2) { + eye->crypto_node = (bNode *)eye->ptr.data; + eye->cryptomatte_session = ntreeCompositCryptomatteSession(CTX_data_scene(C), + eye->crypto_node); + eye->draw_handle_sample_text = WM_draw_cb_activate(CTX_wm_window(C), eyedropper_draw_cb, eye); + } + + if (prop_subtype != PROP_COLOR) { + Scene *scene = CTX_data_scene(C); + const char *display_device; + + display_device = scene->display_settings.display_device; + eye->display = IMB_colormanagement_display_get_named(display_device); + + /* store initial color */ + if (eye->display) { + IMB_colormanagement_display_to_scene_linear_v3(col, eye->display); + } + } + copy_v3_v3(eye->init_col, col); + + return true; +} + +static void eyedropper_exit(bContext *C, wmOperator *op) +{ + Eyedropper *eye = op->customdata; + wmWindow *window = CTX_wm_window(C); + WM_cursor_modal_restore(window); + + if (eye->draw_handle_sample_text) { + WM_draw_cb_exit(window, eye->draw_handle_sample_text); + eye->draw_handle_sample_text = NULL; + } + + if (eye->cryptomatte_session) { + BKE_cryptomatte_free(eye->cryptomatte_session); + eye->cryptomatte_session = NULL; + } + + MEM_SAFE_FREE(op->customdata); +} + +/* *** eyedropper_color_ helper functions *** */ + +static bool eyedropper_cryptomatte_sample_renderlayer_fl(RenderLayer *render_layer, + const char *prefix, + const float fpos[2], + float r_col[3]) +{ + if (!render_layer) { + return false; + } + + const int render_layer_name_len = BLI_strnlen(render_layer->name, sizeof(render_layer->name)); + if (strncmp(prefix, render_layer->name, render_layer_name_len) != 0) { + return false; + } + + const int prefix_len = strlen(prefix); + if (prefix_len <= render_layer_name_len + 1) { + return false; + } + + /* RenderResult from images can have no render layer name. */ + const char *render_pass_name_prefix = render_layer_name_len ? + prefix + 1 + render_layer_name_len : + prefix; + + LISTBASE_FOREACH (RenderPass *, render_pass, &render_layer->passes) { + if (STRPREFIX(render_pass->name, render_pass_name_prefix) && + !STREQLEN(render_pass->name, render_pass_name_prefix, sizeof(render_pass->name))) { + BLI_assert(render_pass->channels == 4); + const int x = (int)(fpos[0] * render_pass->rectx); + const int y = (int)(fpos[1] * render_pass->recty); + const int offset = 4 * (y * render_pass->rectx + x); + zero_v3(r_col); + r_col[0] = render_pass->rect[offset]; + return true; + } + } + + return false; +} +static bool eyedropper_cryptomatte_sample_render_fl(const bNode *node, + const char *prefix, + const float fpos[2], + float r_col[3]) +{ + bool success = false; + Scene *scene = (Scene *)node->id; + BLI_assert(GS(scene->id.name) == ID_SCE); + Render *re = RE_GetSceneRender(scene); + + if (re) { + RenderResult *rr = RE_AcquireResultRead(re); + if (rr) { + LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) { + RenderLayer *render_layer = RE_GetRenderLayer(rr, view_layer->name); + success = eyedropper_cryptomatte_sample_renderlayer_fl(render_layer, prefix, fpos, r_col); + if (success) { + break; + } + } + } + RE_ReleaseResult(re); + } + return success; +} + +static bool eyedropper_cryptomatte_sample_image_fl(const bNode *node, + NodeCryptomatte *crypto, + const char *prefix, + const float fpos[2], + float r_col[3]) +{ + bool success = false; + Image *image = (Image *)node->id; + BLI_assert((image == NULL) || (GS(image->id.name) == ID_IM)); + ImageUser *iuser = &crypto->iuser; + + if (image && image->type == IMA_TYPE_MULTILAYER) { + ImBuf *ibuf = BKE_image_acquire_ibuf(image, iuser, NULL); + if (image->rr) { + LISTBASE_FOREACH (RenderLayer *, render_layer, &image->rr->layers) { + success = eyedropper_cryptomatte_sample_renderlayer_fl(render_layer, prefix, fpos, r_col); + if (success) { + break; + } + } + } + BKE_image_release_ibuf(image, ibuf, NULL); + } + return success; +} + +static bool eyedropper_cryptomatte_sample_fl(bContext *C, + Eyedropper *eye, + const int m_xy[2], + float r_col[3]) +{ + bNode *node = eye->crypto_node; + NodeCryptomatte *crypto = node ? ((NodeCryptomatte *)node->storage) : NULL; + + if (!crypto) { + return false; + } + + bScreen *screen = CTX_wm_screen(C); + ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, m_xy); + if (!area || !ELEM(area->spacetype, SPACE_IMAGE, SPACE_NODE, SPACE_CLIP)) { + return false; + } + + ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, m_xy); + if (!region) { + return false; + } + + int mval[2] = {m_xy[0] - region->winrct.xmin, m_xy[1] - region->winrct.ymin}; + float fpos[2] = {-1.0f, -1.0}; + switch (area->spacetype) { + case SPACE_IMAGE: { + SpaceImage *sima = area->spacedata.first; + ED_space_image_get_position(sima, region, mval, fpos); + break; + } + case SPACE_NODE: { + Main *bmain = CTX_data_main(C); + SpaceNode *snode = area->spacedata.first; + ED_space_node_get_position(bmain, snode, region, mval, fpos); + break; + } + case SPACE_CLIP: { + SpaceClip *sc = area->spacedata.first; + ED_space_clip_get_position(sc, region, mval, fpos); + break; + } + default: { + break; + } + } + + if (fpos[0] < 0.0f || fpos[1] < 0.0f || fpos[0] >= 1.0f || fpos[1] >= 1.0f) { + return false; + } + + /* CMP_CRYPTOMATTE_SRC_RENDER and CMP_CRYPTOMATTE_SRC_IMAGE require a referenced image/scene to + * work properly. */ + if (!node->id) { + return false; + } + + /* TODO(jbakker): Migrate this file to cc and use std::string as return param. */ + char prefix[MAX_NAME + 1]; + const Scene *scene = CTX_data_scene(C); + ntreeCompositCryptomatteLayerPrefix(scene, node, prefix, sizeof(prefix) - 1); + prefix[MAX_NAME] = '\0'; + + if (node->custom1 == CMP_CRYPTOMATTE_SRC_RENDER) { + return eyedropper_cryptomatte_sample_render_fl(node, prefix, fpos, r_col); + } + if (node->custom1 == CMP_CRYPTOMATTE_SRC_IMAGE) { + return eyedropper_cryptomatte_sample_image_fl(node, crypto, prefix, fpos, r_col); + } + return false; +} + +void eyedropper_color_sample_fl(bContext *C, const int m_xy[2], float r_col[3]) +{ + /* we could use some clever */ + Main *bmain = CTX_data_main(C); + wmWindowManager *wm = CTX_wm_manager(C); + const char *display_device = CTX_data_scene(C)->display_settings.display_device; + struct ColorManagedDisplay *display = IMB_colormanagement_display_get_named(display_device); + + int mval[2]; + wmWindow *win; + ScrArea *area; + datadropper_win_area_find(C, m_xy, mval, &win, &area); + + if (area) { + if (area->spacetype == SPACE_IMAGE) { + ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval); + if (region) { + SpaceImage *sima = area->spacedata.first; + const int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin}; + + if (ED_space_image_color_sample(sima, region, region_mval, r_col, NULL)) { + return; + } + } + } + else if (area->spacetype == SPACE_NODE) { + ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval); + if (region) { + SpaceNode *snode = area->spacedata.first; + const int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin}; + + if (ED_space_node_color_sample(bmain, snode, region, region_mval, r_col)) { + return; + } + } + } + else if (area->spacetype == SPACE_CLIP) { + ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval); + if (region) { + SpaceClip *sc = area->spacedata.first; + const int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin}; + + if (ED_space_clip_color_sample(sc, region, region_mval, r_col)) { + return; + } + } + } + } + + if (win) { + /* Fallback to simple opengl picker. */ + WM_window_pixel_sample_read(wm, win, mval, r_col); + IMB_colormanagement_display_to_scene_linear_v3(r_col, display); + } + else { + zero_v3(r_col); + } +} + +/* sets the sample color RGB, maintaining A */ +static void eyedropper_color_set(bContext *C, Eyedropper *eye, const float col[3]) +{ + float col_conv[4]; + + /* to maintain alpha */ + RNA_property_float_get_array(&eye->ptr, eye->prop, col_conv); + + /* convert from linear rgb space to display space */ + if (eye->display) { + copy_v3_v3(col_conv, col); + IMB_colormanagement_scene_linear_to_display_v3(col_conv, eye->display); + } + else { + copy_v3_v3(col_conv, col); + } + + RNA_property_float_set_array(&eye->ptr, eye->prop, col_conv); + eye->is_set = true; + + RNA_property_update(C, &eye->ptr, eye->prop); +} + +static void eyedropper_color_sample(bContext *C, Eyedropper *eye, const int m_xy[2]) +{ + /* Accumulate color. */ + float col[3]; + if (eye->crypto_node) { + if (!eyedropper_cryptomatte_sample_fl(C, eye, m_xy, col)) { + return; + } + } + else { + eyedropper_color_sample_fl(C, m_xy, col); + } + + if (!eye->crypto_node) { + add_v3_v3(eye->accum_col, col); + eye->accum_tot++; + } + else { + copy_v3_v3(eye->accum_col, col); + eye->accum_tot = 1; + } + + /* Apply to property. */ + float accum_col[3]; + if (eye->accum_tot > 1) { + mul_v3_v3fl(accum_col, eye->accum_col, 1.0f / (float)eye->accum_tot); + } + else { + copy_v3_v3(accum_col, eye->accum_col); + } + eyedropper_color_set(C, eye, accum_col); +} + +static void eyedropper_color_sample_text_update(bContext *C, Eyedropper *eye, const int m_xy[2]) +{ + float col[3]; + eye->sample_text[0] = '\0'; + + if (eye->cryptomatte_session) { + if (eyedropper_cryptomatte_sample_fl(C, eye, m_xy, col)) { + BKE_cryptomatte_find_name( + eye->cryptomatte_session, col[0], eye->sample_text, sizeof(eye->sample_text)); + eye->sample_text[sizeof(eye->sample_text) - 1] = '\0'; + } + } +} + +static void eyedropper_cancel(bContext *C, wmOperator *op) +{ + Eyedropper *eye = op->customdata; + if (eye->is_set) { + eyedropper_color_set(C, eye, eye->init_col); + } + eyedropper_exit(C, op); +} + +/* main modal status check */ +static int eyedropper_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + Eyedropper *eye = (Eyedropper *)op->customdata; + + /* handle modal keymap */ + if (event->type == EVT_MODAL_MAP) { + switch (event->val) { + case EYE_MODAL_CANCEL: + eyedropper_cancel(C, op); + return OPERATOR_CANCELLED; + case EYE_MODAL_SAMPLE_CONFIRM: { + const bool is_undo = eye->is_undo; + if (eye->accum_tot == 0) { + eyedropper_color_sample(C, eye, event->xy); + } + eyedropper_exit(C, op); + /* Could support finished & undo-skip. */ + return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED; + } + case EYE_MODAL_SAMPLE_BEGIN: + /* enable accum and make first sample */ + eye->accum_start = true; + eyedropper_color_sample(C, eye, event->xy); + break; + case EYE_MODAL_SAMPLE_RESET: + eye->accum_tot = 0; + zero_v3(eye->accum_col); + eyedropper_color_sample(C, eye, event->xy); + break; + } + } + else if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + if (eye->accum_start) { + /* button is pressed so keep sampling */ + eyedropper_color_sample(C, eye, event->xy); + } + + if (eye->draw_handle_sample_text) { + eyedropper_color_sample_text_update(C, eye, event->xy); + ED_region_tag_redraw(CTX_wm_region(C)); + } + } + + return OPERATOR_RUNNING_MODAL; +} + +/* Modal Operator init */ +static int eyedropper_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + /* init */ + if (eyedropper_init(C, op)) { + wmWindow *win = CTX_wm_window(C); + /* Workaround for de-activating the button clearing the cursor, see T76794 */ + UI_context_active_but_clear(C, win, CTX_wm_region(C)); + WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); + + /* add temp handler */ + WM_event_add_modal_handler(C, op); + + return OPERATOR_RUNNING_MODAL; + } + return OPERATOR_PASS_THROUGH; +} + +/* Repeat operator */ +static int eyedropper_exec(bContext *C, wmOperator *op) +{ + /* init */ + if (eyedropper_init(C, op)) { + + /* do something */ + + /* cleanup */ + eyedropper_exit(C, op); + + return OPERATOR_FINISHED; + } + return OPERATOR_PASS_THROUGH; +} + +static bool eyedropper_poll(bContext *C) +{ + /* Actual test for active button happens later, since we don't + * know which one is active until mouse over. */ + return (CTX_wm_window(C) != NULL); +} + +void UI_OT_eyedropper_color(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Eyedropper"; + ot->idname = "UI_OT_eyedropper_color"; + ot->description = "Sample a color from the Blender window to store in a property"; + + /* api callbacks */ + ot->invoke = eyedropper_invoke; + ot->modal = eyedropper_modal; + ot->cancel = eyedropper_cancel; + ot->exec = eyedropper_exec; + ot->poll = eyedropper_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; +} diff --git a/source/blender/editors/interface/eyedroppers/eyedropper_colorband.c b/source/blender/editors/interface/eyedroppers/eyedropper_colorband.c new file mode 100644 index 00000000000..3f63a8020ed --- /dev/null +++ b/source/blender/editors/interface/eyedroppers/eyedropper_colorband.c @@ -0,0 +1,367 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * Eyedropper (Color Band). + * + * Operates by either: + * - Dragging a straight line, sampling pixels formed by the line to extract a gradient. + * - Clicking on points, adding each color to the end of the color-band. + * + * Defines: + * - #UI_OT_eyedropper_colorramp + * - #UI_OT_eyedropper_colorramp_point + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_screen_types.h" + +#include "BLI_bitmap_draw_2d.h" +#include "BLI_math_vector.h" + +#include "BKE_colorband.h" +#include "BKE_context.h" + +#include "RNA_access.h" +#include "RNA_prototypes.h" + +#include "UI_interface.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "interface_intern.h" + +#include "eyedropper_intern.h" + +typedef struct Colorband_RNAUpdateCb { + PointerRNA ptr; + PropertyRNA *prop; +} Colorband_RNAUpdateCb; + +typedef struct EyedropperColorband { + int event_xy_last[2]; + /* Alpha is currently fixed at 1.0, may support in future. */ + float (*color_buffer)[4]; + int color_buffer_alloc; + int color_buffer_len; + bool sample_start; + ColorBand init_color_band; + ColorBand *color_band; + PointerRNA ptr; + PropertyRNA *prop; + bool is_undo; + bool is_set; +} EyedropperColorband; + +/* For user-data only. */ +struct EyedropperColorband_Context { + bContext *context; + EyedropperColorband *eye; +}; + +static bool eyedropper_colorband_init(bContext *C, wmOperator *op) +{ + ColorBand *band = NULL; + + uiBut *but = UI_context_active_but_get(C); + + PointerRNA rna_update_ptr = PointerRNA_NULL; + PropertyRNA *rna_update_prop = NULL; + bool is_undo = true; + + if (but == NULL) { + /* pass */ + } + else { + if (but->type == UI_BTYPE_COLORBAND) { + /* When invoked with a hotkey, we can find the band in 'but->poin'. */ + band = (ColorBand *)but->poin; + } + else { + /* When invoked from a button it's in custom_data field. */ + band = (ColorBand *)but->custom_data; + } + + if (band) { + rna_update_ptr = ((Colorband_RNAUpdateCb *)but->func_argN)->ptr; + rna_update_prop = ((Colorband_RNAUpdateCb *)but->func_argN)->prop; + is_undo = UI_but_flag_is_set(but, UI_BUT_UNDO); + } + } + + if (!band) { + const PointerRNA ptr = CTX_data_pointer_get_type(C, "color_ramp", &RNA_ColorRamp); + if (ptr.data != NULL) { + band = ptr.data; + + /* Set this to a sub-member of the property to trigger an update. */ + rna_update_ptr = ptr; + rna_update_prop = &rna_ColorRamp_color_mode; + is_undo = RNA_struct_undo_check(ptr.type); + } + } + + if (!band) { + return false; + } + + EyedropperColorband *eye = MEM_callocN(sizeof(EyedropperColorband), __func__); + eye->color_buffer_alloc = 16; + eye->color_buffer = MEM_mallocN(sizeof(*eye->color_buffer) * eye->color_buffer_alloc, __func__); + eye->color_buffer_len = 0; + eye->color_band = band; + eye->init_color_band = *eye->color_band; + eye->ptr = rna_update_ptr; + eye->prop = rna_update_prop; + eye->is_undo = is_undo; + + op->customdata = eye; + + return true; +} + +static void eyedropper_colorband_sample_point(bContext *C, + EyedropperColorband *eye, + const int m_xy[2]) +{ + if (eye->event_xy_last[0] != m_xy[0] || eye->event_xy_last[1] != m_xy[1]) { + float col[4]; + col[3] = 1.0f; /* TODO: sample alpha */ + eyedropper_color_sample_fl(C, m_xy, col); + if (eye->color_buffer_len + 1 == eye->color_buffer_alloc) { + eye->color_buffer_alloc *= 2; + eye->color_buffer = MEM_reallocN(eye->color_buffer, + sizeof(*eye->color_buffer) * eye->color_buffer_alloc); + } + copy_v4_v4(eye->color_buffer[eye->color_buffer_len], col); + eye->color_buffer_len += 1; + copy_v2_v2_int(eye->event_xy_last, m_xy); + eye->is_set = true; + } +} + +static bool eyedropper_colorband_sample_callback(int mx, int my, void *userdata) +{ + struct EyedropperColorband_Context *data = userdata; + bContext *C = data->context; + EyedropperColorband *eye = data->eye; + const int cursor[2] = {mx, my}; + eyedropper_colorband_sample_point(C, eye, cursor); + return true; +} + +static void eyedropper_colorband_sample_segment(bContext *C, + EyedropperColorband *eye, + const int m_xy[2]) +{ + /* Since the mouse tends to move rather rapidly we use #BLI_bitmap_draw_2d_line_v2v2i + * to interpolate between the reported coordinates */ + struct EyedropperColorband_Context userdata = {C, eye}; + BLI_bitmap_draw_2d_line_v2v2i( + eye->event_xy_last, m_xy, eyedropper_colorband_sample_callback, &userdata); +} + +static void eyedropper_colorband_exit(bContext *C, wmOperator *op) +{ + WM_cursor_modal_restore(CTX_wm_window(C)); + + if (op->customdata) { + EyedropperColorband *eye = op->customdata; + MEM_freeN(eye->color_buffer); + MEM_freeN(eye); + op->customdata = NULL; + } +} + +static void eyedropper_colorband_apply(bContext *C, wmOperator *op) +{ + EyedropperColorband *eye = op->customdata; + /* Always filter, avoids noise in resulting color-band. */ + const bool filter_samples = true; + BKE_colorband_init_from_table_rgba( + eye->color_band, eye->color_buffer, eye->color_buffer_len, filter_samples); + eye->is_set = true; + if (eye->prop) { + RNA_property_update(C, &eye->ptr, eye->prop); + } +} + +static void eyedropper_colorband_cancel(bContext *C, wmOperator *op) +{ + EyedropperColorband *eye = op->customdata; + if (eye->is_set) { + *eye->color_band = eye->init_color_band; + if (eye->prop) { + RNA_property_update(C, &eye->ptr, eye->prop); + } + } + eyedropper_colorband_exit(C, op); +} + +/* main modal status check */ +static int eyedropper_colorband_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + EyedropperColorband *eye = op->customdata; + /* handle modal keymap */ + if (event->type == EVT_MODAL_MAP) { + switch (event->val) { + case EYE_MODAL_CANCEL: + eyedropper_colorband_cancel(C, op); + return OPERATOR_CANCELLED; + case EYE_MODAL_SAMPLE_CONFIRM: { + const bool is_undo = eye->is_undo; + eyedropper_colorband_sample_segment(C, eye, event->xy); + eyedropper_colorband_apply(C, op); + eyedropper_colorband_exit(C, op); + /* Could support finished & undo-skip. */ + return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED; + } + case EYE_MODAL_SAMPLE_BEGIN: + /* enable accum and make first sample */ + eye->sample_start = true; + eyedropper_colorband_sample_point(C, eye, event->xy); + eyedropper_colorband_apply(C, op); + copy_v2_v2_int(eye->event_xy_last, event->xy); + break; + case EYE_MODAL_SAMPLE_RESET: + break; + } + } + else if (event->type == MOUSEMOVE) { + if (eye->sample_start) { + eyedropper_colorband_sample_segment(C, eye, event->xy); + eyedropper_colorband_apply(C, op); + } + } + return OPERATOR_RUNNING_MODAL; +} + +static int eyedropper_colorband_point_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + EyedropperColorband *eye = op->customdata; + /* handle modal keymap */ + if (event->type == EVT_MODAL_MAP) { + switch (event->val) { + case EYE_MODAL_POINT_CANCEL: + eyedropper_colorband_cancel(C, op); + return OPERATOR_CANCELLED; + case EYE_MODAL_POINT_CONFIRM: + eyedropper_colorband_apply(C, op); + eyedropper_colorband_exit(C, op); + return OPERATOR_FINISHED; + case EYE_MODAL_POINT_REMOVE_LAST: + if (eye->color_buffer_len > 0) { + eye->color_buffer_len -= 1; + eyedropper_colorband_apply(C, op); + } + break; + case EYE_MODAL_POINT_SAMPLE: + eyedropper_colorband_sample_point(C, eye, event->xy); + eyedropper_colorband_apply(C, op); + if (eye->color_buffer_len == MAXCOLORBAND) { + eyedropper_colorband_exit(C, op); + return OPERATOR_FINISHED; + } + break; + case EYE_MODAL_SAMPLE_RESET: + *eye->color_band = eye->init_color_band; + if (eye->prop) { + RNA_property_update(C, &eye->ptr, eye->prop); + } + eye->color_buffer_len = 0; + break; + } + } + return OPERATOR_RUNNING_MODAL; +} + +/* Modal Operator init */ +static int eyedropper_colorband_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + /* init */ + if (eyedropper_colorband_init(C, op)) { + wmWindow *win = CTX_wm_window(C); + /* Workaround for de-activating the button clearing the cursor, see T76794 */ + UI_context_active_but_clear(C, win, CTX_wm_region(C)); + WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); + + /* add temp handler */ + WM_event_add_modal_handler(C, op); + + return OPERATOR_RUNNING_MODAL; + } + return OPERATOR_CANCELLED; +} + +/* Repeat operator */ +static int eyedropper_colorband_exec(bContext *C, wmOperator *op) +{ + /* init */ + if (eyedropper_colorband_init(C, op)) { + + /* do something */ + + /* cleanup */ + eyedropper_colorband_exit(C, op); + + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +static bool eyedropper_colorband_poll(bContext *C) +{ + uiBut *but = UI_context_active_but_get(C); + if (but && but->type == UI_BTYPE_COLORBAND) { + return true; + } + const PointerRNA ptr = CTX_data_pointer_get_type(C, "color_ramp", &RNA_ColorRamp); + if (ptr.data != NULL) { + return true; + } + return false; +} + +void UI_OT_eyedropper_colorramp(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Eyedropper Colorband"; + ot->idname = "UI_OT_eyedropper_colorramp"; + ot->description = "Sample a color band"; + + /* api callbacks */ + ot->invoke = eyedropper_colorband_invoke; + ot->modal = eyedropper_colorband_modal; + ot->cancel = eyedropper_colorband_cancel; + ot->exec = eyedropper_colorband_exec; + ot->poll = eyedropper_colorband_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; + + /* properties */ +} + +void UI_OT_eyedropper_colorramp_point(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Eyedropper Colorband (Points)"; + ot->idname = "UI_OT_eyedropper_colorramp_point"; + ot->description = "Point-sample a color band"; + + /* api callbacks */ + ot->invoke = eyedropper_colorband_invoke; + ot->modal = eyedropper_colorband_point_modal; + ot->cancel = eyedropper_colorband_cancel; + ot->exec = eyedropper_colorband_exec; + ot->poll = eyedropper_colorband_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; + + /* properties */ +} diff --git a/source/blender/editors/interface/eyedroppers/eyedropper_datablock.c b/source/blender/editors/interface/eyedroppers/eyedropper_datablock.c new file mode 100644 index 00000000000..1710d0faa4d --- /dev/null +++ b/source/blender/editors/interface/eyedroppers/eyedropper_datablock.c @@ -0,0 +1,378 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * Eyedropper (ID data-blocks) + * + * Defines: + * - #UI_OT_eyedropper_id + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_object_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "BLI_math_vector.h" +#include "BLI_string.h" + +#include "BLT_translation.h" + +#include "BKE_context.h" +#include "BKE_idtype.h" +#include "BKE_report.h" +#include "BKE_screen.h" + +#include "RNA_access.h" + +#include "UI_interface.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_outliner.h" +#include "ED_screen.h" +#include "ED_space_api.h" +#include "ED_view3d.h" + +#include "eyedropper_intern.h" +#include "interface_intern.h" + +/** + * \note #DataDropper is only internal name to avoid confusion with other kinds of eye-droppers. + */ +typedef struct DataDropper { + PointerRNA ptr; + PropertyRNA *prop; + short idcode; + const char *idcode_name; + bool is_undo; + + ID *init_id; /* for resetting on cancel */ + + ScrArea *cursor_area; /* Area under the cursor */ + ARegionType *art; + void *draw_handle_pixel; + int name_pos[2]; + char name[200]; +} DataDropper; + +static void datadropper_draw_cb(const struct bContext *UNUSED(C), + ARegion *UNUSED(region), + void *arg) +{ + DataDropper *ddr = arg; + eyedropper_draw_cursor_text_region(ddr->name_pos, ddr->name); +} + +static int datadropper_init(bContext *C, wmOperator *op) +{ + int index_dummy; + StructRNA *type; + + SpaceType *st; + ARegionType *art; + + st = BKE_spacetype_from_id(SPACE_VIEW3D); + art = BKE_regiontype_from_id(st, RGN_TYPE_WINDOW); + + DataDropper *ddr = MEM_callocN(sizeof(DataDropper), __func__); + + uiBut *but = UI_context_active_but_prop_get(C, &ddr->ptr, &ddr->prop, &index_dummy); + + if ((ddr->ptr.data == NULL) || (ddr->prop == NULL) || + (RNA_property_editable(&ddr->ptr, ddr->prop) == false) || + (RNA_property_type(ddr->prop) != PROP_POINTER)) { + MEM_freeN(ddr); + return false; + } + op->customdata = ddr; + + ddr->is_undo = UI_but_flag_is_set(but, UI_BUT_UNDO); + + ddr->cursor_area = CTX_wm_area(C); + ddr->art = art; + ddr->draw_handle_pixel = ED_region_draw_cb_activate( + art, datadropper_draw_cb, ddr, REGION_DRAW_POST_PIXEL); + + type = RNA_property_pointer_type(&ddr->ptr, ddr->prop); + ddr->idcode = RNA_type_to_ID_code(type); + BLI_assert(ddr->idcode != 0); + /* Note we can translate here (instead of on draw time), + * because this struct has very short lifetime. */ + ddr->idcode_name = TIP_(BKE_idtype_idcode_to_name(ddr->idcode)); + + const PointerRNA ptr = RNA_property_pointer_get(&ddr->ptr, ddr->prop); + ddr->init_id = ptr.owner_id; + + return true; +} + +static void datadropper_exit(bContext *C, wmOperator *op) +{ + wmWindow *win = CTX_wm_window(C); + + WM_cursor_modal_restore(win); + + if (op->customdata) { + DataDropper *ddr = (DataDropper *)op->customdata; + + if (ddr->art) { + ED_region_draw_cb_exit(ddr->art, ddr->draw_handle_pixel); + } + + MEM_freeN(op->customdata); + + op->customdata = NULL; + } + + WM_event_add_mousemove(win); +} + +/* *** datadropper id helper functions *** */ +/** + * \brief get the ID from the 3D view or outliner. + */ +static void datadropper_id_sample_pt( + bContext *C, wmWindow *win, ScrArea *area, DataDropper *ddr, const int m_xy[2], ID **r_id) +{ + wmWindow *win_prev = CTX_wm_window(C); + ScrArea *area_prev = CTX_wm_area(C); + ARegion *region_prev = CTX_wm_region(C); + + ddr->name[0] = '\0'; + + if (area) { + if (ELEM(area->spacetype, SPACE_VIEW3D, SPACE_OUTLINER)) { + ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, m_xy); + if (region) { + const int mval[2] = {m_xy[0] - region->winrct.xmin, m_xy[1] - region->winrct.ymin}; + Base *base; + + CTX_wm_window_set(C, win); + CTX_wm_area_set(C, area); + CTX_wm_region_set(C, region); + + /* Unfortunately it's necessary to always draw else we leave stale text. */ + ED_region_tag_redraw(region); + + if (area->spacetype == SPACE_VIEW3D) { + base = ED_view3d_give_base_under_cursor(C, mval); + } + else { + base = ED_outliner_give_base_under_cursor(C, mval); + } + + if (base) { + Object *ob = base->object; + ID *id = NULL; + if (ddr->idcode == ID_OB) { + id = (ID *)ob; + } + else if (ob->data) { + if (GS(((ID *)ob->data)->name) == ddr->idcode) { + id = (ID *)ob->data; + } + else { + BLI_snprintf( + ddr->name, sizeof(ddr->name), "Incompatible, expected a %s", ddr->idcode_name); + } + } + + PointerRNA idptr; + RNA_id_pointer_create(id, &idptr); + + if (id && RNA_property_pointer_poll(&ddr->ptr, ddr->prop, &idptr)) { + BLI_snprintf(ddr->name, sizeof(ddr->name), "%s: %s", ddr->idcode_name, id->name + 2); + *r_id = id; + } + + copy_v2_v2_int(ddr->name_pos, mval); + } + } + } + } + + CTX_wm_window_set(C, win_prev); + CTX_wm_area_set(C, area_prev); + CTX_wm_region_set(C, region_prev); +} + +/* sets the ID, returns success */ +static bool datadropper_id_set(bContext *C, DataDropper *ddr, ID *id) +{ + PointerRNA ptr_value; + + RNA_id_pointer_create(id, &ptr_value); + + RNA_property_pointer_set(&ddr->ptr, ddr->prop, ptr_value, NULL); + + RNA_property_update(C, &ddr->ptr, ddr->prop); + + ptr_value = RNA_property_pointer_get(&ddr->ptr, ddr->prop); + + return (ptr_value.owner_id == id); +} + +/* single point sample & set */ +static bool datadropper_id_sample(bContext *C, DataDropper *ddr, const int m_xy[2]) +{ + ID *id = NULL; + + int mval[2]; + wmWindow *win; + ScrArea *area; + datadropper_win_area_find(C, m_xy, mval, &win, &area); + + datadropper_id_sample_pt(C, win, area, ddr, mval, &id); + return datadropper_id_set(C, ddr, id); +} + +static void datadropper_cancel(bContext *C, wmOperator *op) +{ + DataDropper *ddr = op->customdata; + datadropper_id_set(C, ddr, ddr->init_id); + datadropper_exit(C, op); +} + +/* To switch the draw callback when region under mouse event changes */ +static void datadropper_set_draw_callback_region(ScrArea *area, DataDropper *ddr) +{ + if (area) { + /* If spacetype changed */ + if (area->spacetype != ddr->cursor_area->spacetype) { + /* Remove old callback */ + ED_region_draw_cb_exit(ddr->art, ddr->draw_handle_pixel); + + /* Redraw old area */ + ARegion *region = BKE_area_find_region_type(ddr->cursor_area, RGN_TYPE_WINDOW); + ED_region_tag_redraw(region); + + /* Set draw callback in new region */ + ARegionType *art = BKE_regiontype_from_id(area->type, RGN_TYPE_WINDOW); + + ddr->cursor_area = area; + ddr->art = art; + ddr->draw_handle_pixel = ED_region_draw_cb_activate( + art, datadropper_draw_cb, ddr, REGION_DRAW_POST_PIXEL); + } + } +} + +/* main modal status check */ +static int datadropper_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + DataDropper *ddr = (DataDropper *)op->customdata; + + /* handle modal keymap */ + if (event->type == EVT_MODAL_MAP) { + switch (event->val) { + case EYE_MODAL_CANCEL: + datadropper_cancel(C, op); + return OPERATOR_CANCELLED; + case EYE_MODAL_SAMPLE_CONFIRM: { + const bool is_undo = ddr->is_undo; + const bool success = datadropper_id_sample(C, ddr, event->xy); + datadropper_exit(C, op); + if (success) { + /* Could support finished & undo-skip. */ + return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED; + } + BKE_report(op->reports, RPT_WARNING, "Failed to set value"); + return OPERATOR_CANCELLED; + } + } + } + else if (event->type == MOUSEMOVE) { + ID *id = NULL; + + int mval[2]; + wmWindow *win; + ScrArea *area; + datadropper_win_area_find(C, event->xy, mval, &win, &area); + + /* Set the region for eyedropper cursor text drawing */ + datadropper_set_draw_callback_region(area, ddr); + + datadropper_id_sample_pt(C, win, area, ddr, mval, &id); + } + + return OPERATOR_RUNNING_MODAL; +} + +/* Modal Operator init */ +static int datadropper_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + /* init */ + if (datadropper_init(C, op)) { + wmWindow *win = CTX_wm_window(C); + /* Workaround for de-activating the button clearing the cursor, see T76794 */ + UI_context_active_but_clear(C, win, CTX_wm_region(C)); + WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); + + /* add temp handler */ + WM_event_add_modal_handler(C, op); + + return OPERATOR_RUNNING_MODAL; + } + return OPERATOR_CANCELLED; +} + +/* Repeat operator */ +static int datadropper_exec(bContext *C, wmOperator *op) +{ + /* init */ + if (datadropper_init(C, op)) { + /* cleanup */ + datadropper_exit(C, op); + + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +static bool datadropper_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index_dummy; + uiBut *but; + + /* data dropper only supports object data */ + if ((CTX_wm_window(C) != NULL) && + (but = UI_context_active_but_prop_get(C, &ptr, &prop, &index_dummy)) && + (but->type == UI_BTYPE_SEARCH_MENU) && (but->flag & UI_BUT_VALUE_CLEAR)) { + if (prop && RNA_property_type(prop) == PROP_POINTER) { + StructRNA *type = RNA_property_pointer_type(&ptr, prop); + const short idcode = RNA_type_to_ID_code(type); + if ((idcode == ID_OB) || OB_DATA_SUPPORT_ID(idcode)) { + return true; + } + } + } + + return false; +} + +void UI_OT_eyedropper_id(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Eyedropper Data-Block"; + ot->idname = "UI_OT_eyedropper_id"; + ot->description = "Sample a data-block from the 3D View to store in a property"; + + /* api callbacks */ + ot->invoke = datadropper_invoke; + ot->modal = datadropper_modal; + ot->cancel = datadropper_cancel; + ot->exec = datadropper_exec; + ot->poll = datadropper_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; + + /* properties */ +} diff --git a/source/blender/editors/interface/eyedroppers/eyedropper_depth.c b/source/blender/editors/interface/eyedroppers/eyedropper_depth.c new file mode 100644 index 00000000000..3fb5a74944b --- /dev/null +++ b/source/blender/editors/interface/eyedroppers/eyedropper_depth.c @@ -0,0 +1,381 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * This file defines an eyedropper for picking 3D depth value (primary use is depth-of-field). + * + * Defines: + * - #UI_OT_eyedropper_depth + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_camera_types.h" +#include "DNA_object_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" +#include "DNA_view3d_types.h" + +#include "BLI_math_vector.h" +#include "BLI_string.h" + +#include "BKE_context.h" +#include "BKE_lib_id.h" +#include "BKE_screen.h" +#include "BKE_unit.h" + +#include "RNA_access.h" +#include "RNA_prototypes.h" + +#include "UI_interface.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_screen.h" +#include "ED_space_api.h" +#include "ED_view3d.h" + +#include "eyedropper_intern.h" +#include "interface_intern.h" + +/** + * \note #DepthDropper is only internal name to avoid confusion with other kinds of eye-droppers. + */ +typedef struct DepthDropper { + PointerRNA ptr; + PropertyRNA *prop; + bool is_undo; + + bool is_set; + float init_depth; /* For resetting on cancel. */ + + bool accum_start; /* Has mouse been pressed. */ + float accum_depth; + int accum_tot; + + ARegionType *art; + void *draw_handle_pixel; + int name_pos[2]; + char name[200]; +} DepthDropper; + +static void depthdropper_draw_cb(const struct bContext *UNUSED(C), + ARegion *UNUSED(region), + void *arg) +{ + DepthDropper *ddr = arg; + eyedropper_draw_cursor_text_region(ddr->name_pos, ddr->name); +} + +static int depthdropper_init(bContext *C, wmOperator *op) +{ + int index_dummy; + + SpaceType *st; + ARegionType *art; + + st = BKE_spacetype_from_id(SPACE_VIEW3D); + art = BKE_regiontype_from_id(st, RGN_TYPE_WINDOW); + + DepthDropper *ddr = MEM_callocN(sizeof(DepthDropper), __func__); + + uiBut *but = UI_context_active_but_prop_get(C, &ddr->ptr, &ddr->prop, &index_dummy); + + /* fallback to the active camera's dof */ + if (ddr->prop == NULL) { + RegionView3D *rv3d = CTX_wm_region_view3d(C); + if (rv3d && rv3d->persp == RV3D_CAMOB) { + View3D *v3d = CTX_wm_view3d(C); + if (v3d->camera && v3d->camera->data && + BKE_id_is_editable(CTX_data_main(C), v3d->camera->data)) { + Camera *camera = (Camera *)v3d->camera->data; + RNA_pointer_create(&camera->id, &RNA_CameraDOFSettings, &camera->dof, &ddr->ptr); + ddr->prop = RNA_struct_find_property(&ddr->ptr, "focus_distance"); + ddr->is_undo = true; + } + } + } + else { + ddr->is_undo = UI_but_flag_is_set(but, UI_BUT_UNDO); + } + + if ((ddr->ptr.data == NULL) || (ddr->prop == NULL) || + (RNA_property_editable(&ddr->ptr, ddr->prop) == false) || + (RNA_property_type(ddr->prop) != PROP_FLOAT)) { + MEM_freeN(ddr); + return false; + } + op->customdata = ddr; + + ddr->art = art; + ddr->draw_handle_pixel = ED_region_draw_cb_activate( + art, depthdropper_draw_cb, ddr, REGION_DRAW_POST_PIXEL); + ddr->init_depth = RNA_property_float_get(&ddr->ptr, ddr->prop); + + return true; +} + +static void depthdropper_exit(bContext *C, wmOperator *op) +{ + WM_cursor_modal_restore(CTX_wm_window(C)); + + if (op->customdata) { + DepthDropper *ddr = (DepthDropper *)op->customdata; + + if (ddr->art) { + ED_region_draw_cb_exit(ddr->art, ddr->draw_handle_pixel); + } + + MEM_freeN(op->customdata); + + op->customdata = NULL; + } +} + +/* *** depthdropper id helper functions *** */ +/** + * \brief get the ID from the screen. + */ +static void depthdropper_depth_sample_pt(bContext *C, + DepthDropper *ddr, + const int m_xy[2], + float *r_depth) +{ + /* we could use some clever */ + bScreen *screen = CTX_wm_screen(C); + ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, m_xy); + Scene *scene = CTX_data_scene(C); + + ScrArea *area_prev = CTX_wm_area(C); + ARegion *region_prev = CTX_wm_region(C); + + ddr->name[0] = '\0'; + + if (area) { + if (area->spacetype == SPACE_VIEW3D) { + ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, m_xy); + if (region) { + struct Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + View3D *v3d = area->spacedata.first; + RegionView3D *rv3d = region->regiondata; + /* weak, we could pass in some reference point */ + const float *view_co = v3d->camera ? v3d->camera->obmat[3] : rv3d->viewinv[3]; + const int mval[2] = {m_xy[0] - region->winrct.xmin, m_xy[1] - region->winrct.ymin}; + copy_v2_v2_int(ddr->name_pos, mval); + + float co[3]; + + CTX_wm_area_set(C, area); + CTX_wm_region_set(C, region); + + /* Unfortunately it's necessary to always draw otherwise we leave stale text. */ + ED_region_tag_redraw(region); + + view3d_operator_needs_opengl(C); + + if (ED_view3d_autodist(depsgraph, region, v3d, mval, co, true, NULL)) { + const float mval_center_fl[2] = {(float)region->winx / 2, (float)region->winy / 2}; + float co_align[3]; + + /* quick way to get view-center aligned point */ + ED_view3d_win_to_3d(v3d, region, co, mval_center_fl, co_align); + + *r_depth = len_v3v3(view_co, co_align); + + BKE_unit_value_as_string(ddr->name, + sizeof(ddr->name), + (double)*r_depth, + 4, + B_UNIT_LENGTH, + &scene->unit, + false); + } + else { + BLI_strncpy(ddr->name, "Nothing under cursor", sizeof(ddr->name)); + } + } + } + } + + CTX_wm_area_set(C, area_prev); + CTX_wm_region_set(C, region_prev); +} + +/* sets the sample depth RGB, maintaining A */ +static void depthdropper_depth_set(bContext *C, DepthDropper *ddr, const float depth) +{ + RNA_property_float_set(&ddr->ptr, ddr->prop, depth); + ddr->is_set = true; + RNA_property_update(C, &ddr->ptr, ddr->prop); +} + +/* set sample from accumulated values */ +static void depthdropper_depth_set_accum(bContext *C, DepthDropper *ddr) +{ + float depth = ddr->accum_depth; + if (ddr->accum_tot) { + depth /= (float)ddr->accum_tot; + } + depthdropper_depth_set(C, ddr, depth); +} + +/* single point sample & set */ +static void depthdropper_depth_sample(bContext *C, DepthDropper *ddr, const int m_xy[2]) +{ + float depth = -1.0f; + if (depth != -1.0f) { + depthdropper_depth_sample_pt(C, ddr, m_xy, &depth); + depthdropper_depth_set(C, ddr, depth); + } +} + +static void depthdropper_depth_sample_accum(bContext *C, DepthDropper *ddr, const int m_xy[2]) +{ + float depth = -1.0f; + depthdropper_depth_sample_pt(C, ddr, m_xy, &depth); + if (depth != -1.0f) { + ddr->accum_depth += depth; + ddr->accum_tot++; + } +} + +static void depthdropper_cancel(bContext *C, wmOperator *op) +{ + DepthDropper *ddr = op->customdata; + if (ddr->is_set) { + depthdropper_depth_set(C, ddr, ddr->init_depth); + } + depthdropper_exit(C, op); +} + +/* main modal status check */ +static int depthdropper_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + DepthDropper *ddr = (DepthDropper *)op->customdata; + + /* handle modal keymap */ + if (event->type == EVT_MODAL_MAP) { + switch (event->val) { + case EYE_MODAL_CANCEL: + depthdropper_cancel(C, op); + return OPERATOR_CANCELLED; + case EYE_MODAL_SAMPLE_CONFIRM: { + const bool is_undo = ddr->is_undo; + if (ddr->accum_tot == 0) { + depthdropper_depth_sample(C, ddr, event->xy); + } + else { + depthdropper_depth_set_accum(C, ddr); + } + depthdropper_exit(C, op); + /* Could support finished & undo-skip. */ + return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED; + } + case EYE_MODAL_SAMPLE_BEGIN: + /* enable accum and make first sample */ + ddr->accum_start = true; + depthdropper_depth_sample_accum(C, ddr, event->xy); + break; + case EYE_MODAL_SAMPLE_RESET: + ddr->accum_tot = 0; + ddr->accum_depth = 0.0f; + depthdropper_depth_sample_accum(C, ddr, event->xy); + depthdropper_depth_set_accum(C, ddr); + break; + } + } + else if (event->type == MOUSEMOVE) { + if (ddr->accum_start) { + /* button is pressed so keep sampling */ + depthdropper_depth_sample_accum(C, ddr, event->xy); + depthdropper_depth_set_accum(C, ddr); + } + } + + return OPERATOR_RUNNING_MODAL; +} + +/* Modal Operator init */ +static int depthdropper_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + /* init */ + if (depthdropper_init(C, op)) { + wmWindow *win = CTX_wm_window(C); + /* Workaround for de-activating the button clearing the cursor, see T76794 */ + UI_context_active_but_clear(C, win, CTX_wm_region(C)); + WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); + + /* add temp handler */ + WM_event_add_modal_handler(C, op); + + return OPERATOR_RUNNING_MODAL; + } + return OPERATOR_CANCELLED; +} + +/* Repeat operator */ +static int depthdropper_exec(bContext *C, wmOperator *op) +{ + /* init */ + if (depthdropper_init(C, op)) { + /* cleanup */ + depthdropper_exit(C, op); + + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +static bool depthdropper_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index_dummy; + uiBut *but; + + /* check if there's an active button taking depth value */ + if ((CTX_wm_window(C) != NULL) && + (but = UI_context_active_but_prop_get(C, &ptr, &prop, &index_dummy)) && + (but->type == UI_BTYPE_NUM) && (prop != NULL)) { + if ((RNA_property_type(prop) == PROP_FLOAT) && + (RNA_property_subtype(prop) & PROP_UNIT_LENGTH) && + (RNA_property_array_check(prop) == false)) { + return true; + } + } + else { + RegionView3D *rv3d = CTX_wm_region_view3d(C); + if (rv3d && rv3d->persp == RV3D_CAMOB) { + View3D *v3d = CTX_wm_view3d(C); + if (v3d->camera && v3d->camera->data && + BKE_id_is_editable(CTX_data_main(C), v3d->camera->data)) { + return true; + } + } + } + + return false; +} + +void UI_OT_eyedropper_depth(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Eyedropper Depth"; + ot->idname = "UI_OT_eyedropper_depth"; + ot->description = "Sample depth from the 3D view"; + + /* api callbacks */ + ot->invoke = depthdropper_invoke; + ot->modal = depthdropper_modal; + ot->cancel = depthdropper_cancel; + ot->exec = depthdropper_exec; + ot->poll = depthdropper_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; + + /* properties */ +} diff --git a/source/blender/editors/interface/eyedroppers/eyedropper_driver.c b/source/blender/editors/interface/eyedroppers/eyedropper_driver.c new file mode 100644 index 00000000000..14c00c21a5c --- /dev/null +++ b/source/blender/editors/interface/eyedroppers/eyedropper_driver.c @@ -0,0 +1,220 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * Eyedropper (Animation Driver Targets). + * + * Defines: + * - #UI_OT_eyedropper_driver + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_anim_types.h" +#include "DNA_object_types.h" +#include "DNA_screen_types.h" + +#include "BKE_animsys.h" +#include "BKE_context.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "UI_interface.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_keyframing.h" + +#include "eyedropper_intern.h" +#include "interface_intern.h" + +typedef struct DriverDropper { + /* Destination property (i.e. where we'll add a driver) */ + PointerRNA ptr; + PropertyRNA *prop; + int index; + bool is_undo; + + /* TODO: new target? */ +} DriverDropper; + +static bool driverdropper_init(bContext *C, wmOperator *op) +{ + DriverDropper *ddr = MEM_callocN(sizeof(DriverDropper), __func__); + + uiBut *but = UI_context_active_but_prop_get(C, &ddr->ptr, &ddr->prop, &ddr->index); + + if ((ddr->ptr.data == NULL) || (ddr->prop == NULL) || + (RNA_property_editable(&ddr->ptr, ddr->prop) == false) || + (RNA_property_animateable(&ddr->ptr, ddr->prop) == false) || (but->flag & UI_BUT_DRIVEN)) { + MEM_freeN(ddr); + return false; + } + op->customdata = ddr; + + ddr->is_undo = UI_but_flag_is_set(but, UI_BUT_UNDO); + + return true; +} + +static void driverdropper_exit(bContext *C, wmOperator *op) +{ + WM_cursor_modal_restore(CTX_wm_window(C)); + + MEM_SAFE_FREE(op->customdata); +} + +static void driverdropper_sample(bContext *C, wmOperator *op, const wmEvent *event) +{ + DriverDropper *ddr = (DriverDropper *)op->customdata; + uiBut *but = eyedropper_get_property_button_under_mouse(C, event); + + const short mapping_type = RNA_enum_get(op->ptr, "mapping_type"); + const short flag = 0; + + /* we can only add a driver if we know what RNA property it corresponds to */ + if (but == NULL) { + return; + } + /* Get paths for src... */ + PointerRNA *target_ptr = &but->rnapoin; + PropertyRNA *target_prop = but->rnaprop; + const int target_index = but->rnaindex; + + char *target_path = RNA_path_from_ID_to_property(target_ptr, target_prop); + + /* ... and destination */ + char *dst_path = RNA_path_from_ID_to_property(&ddr->ptr, ddr->prop); + + /* Now create driver(s) */ + if (target_path && dst_path) { + int success = ANIM_add_driver_with_target(op->reports, + ddr->ptr.owner_id, + dst_path, + ddr->index, + target_ptr->owner_id, + target_path, + target_index, + flag, + DRIVER_TYPE_PYTHON, + mapping_type); + + if (success) { + /* send updates */ + UI_context_update_anim_flag(C); + DEG_relations_tag_update(CTX_data_main(C)); + DEG_id_tag_update(ddr->ptr.owner_id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_ANIMATION | ND_FCURVES_ORDER, NULL); /* XXX */ + } + } + + /* cleanup */ + if (target_path) { + MEM_freeN(target_path); + } + if (dst_path) { + MEM_freeN(dst_path); + } +} + +static void driverdropper_cancel(bContext *C, wmOperator *op) +{ + driverdropper_exit(C, op); +} + +/* main modal status check */ +static int driverdropper_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + DriverDropper *ddr = op->customdata; + + /* handle modal keymap */ + if (event->type == EVT_MODAL_MAP) { + switch (event->val) { + case EYE_MODAL_CANCEL: { + driverdropper_cancel(C, op); + return OPERATOR_CANCELLED; + } + case EYE_MODAL_SAMPLE_CONFIRM: { + const bool is_undo = ddr->is_undo; + driverdropper_sample(C, op, event); + driverdropper_exit(C, op); + /* Could support finished & undo-skip. */ + return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED; + } + } + } + + return OPERATOR_RUNNING_MODAL; +} + +/* Modal Operator init */ +static int driverdropper_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + /* init */ + if (driverdropper_init(C, op)) { + wmWindow *win = CTX_wm_window(C); + /* Workaround for de-activating the button clearing the cursor, see T76794 */ + UI_context_active_but_clear(C, win, CTX_wm_region(C)); + WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); + + /* add temp handler */ + WM_event_add_modal_handler(C, op); + + return OPERATOR_RUNNING_MODAL; + } + return OPERATOR_CANCELLED; +} + +/* Repeat operator */ +static int driverdropper_exec(bContext *C, wmOperator *op) +{ + /* init */ + if (driverdropper_init(C, op)) { + /* cleanup */ + driverdropper_exit(C, op); + + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +static bool driverdropper_poll(bContext *C) +{ + if (!CTX_wm_window(C)) { + return false; + } + return true; +} + +void UI_OT_eyedropper_driver(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Eyedropper Driver"; + ot->idname = "UI_OT_eyedropper_driver"; + ot->description = "Pick a property to use as a driver target"; + + /* api callbacks */ + ot->invoke = driverdropper_invoke; + ot->modal = driverdropper_modal; + ot->cancel = driverdropper_cancel; + ot->exec = driverdropper_exec; + ot->poll = driverdropper_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; + + /* properties */ + RNA_def_enum(ot->srna, + "mapping_type", + prop_driver_create_mapping_types, + 0, + "Mapping Type", + "Method used to match target and driven properties"); +} diff --git a/source/blender/editors/interface/eyedroppers/eyedropper_gpencil_color.c b/source/blender/editors/interface/eyedroppers/eyedropper_gpencil_color.c new file mode 100644 index 00000000000..c3879fe8bbd --- /dev/null +++ b/source/blender/editors/interface/eyedroppers/eyedropper_gpencil_color.c @@ -0,0 +1,373 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * Eyedropper (RGB Color) + * + * Defines: + * - #UI_OT_eyedropper_gpencil_color + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_listbase.h" +#include "BLI_string.h" + +#include "BLT_translation.h" + +#include "DNA_gpencil_types.h" +#include "DNA_material_types.h" +#include "DNA_space_types.h" + +#include "BKE_context.h" +#include "BKE_gpencil.h" +#include "BKE_lib_id.h" +#include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_paint.h" +#include "BKE_report.h" + +#include "UI_interface.h" + +#include "IMB_colormanagement.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "ED_gpencil.h" +#include "ED_screen.h" +#include "ED_undo.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" + +#include "eyedropper_intern.h" +#include "interface_intern.h" + +typedef struct EyedropperGPencil { + struct ColorManagedDisplay *display; + /** color under cursor RGB */ + float color[3]; + /** Mode */ + int mode; +} EyedropperGPencil; + +/* Helper: Draw status message while the user is running the operator */ +static void eyedropper_gpencil_status_indicators(bContext *C) +{ + char msg_str[UI_MAX_DRAW_STR]; + BLI_strncpy( + msg_str, TIP_("LMB: Stroke - Shift: Fill - Shift+Ctrl: Stroke + Fill"), UI_MAX_DRAW_STR); + + ED_workspace_status_text(C, msg_str); +} + +/* Initialize. */ +static bool eyedropper_gpencil_init(bContext *C, wmOperator *op) +{ + EyedropperGPencil *eye = MEM_callocN(sizeof(EyedropperGPencil), __func__); + + op->customdata = eye; + Scene *scene = CTX_data_scene(C); + + const char *display_device; + display_device = scene->display_settings.display_device; + eye->display = IMB_colormanagement_display_get_named(display_device); + + eye->mode = RNA_enum_get(op->ptr, "mode"); + return true; +} + +/* Exit and free memory. */ +static void eyedropper_gpencil_exit(bContext *C, wmOperator *op) +{ + /* Clear status message area. */ + ED_workspace_status_text(C, NULL); + + MEM_SAFE_FREE(op->customdata); +} + +static void eyedropper_add_material(bContext *C, + const float col_conv[4], + const bool only_stroke, + const bool only_fill, + const bool both) +{ + Main *bmain = CTX_data_main(C); + Object *ob = CTX_data_active_object(C); + Material *ma = NULL; + + bool found = false; + + /* Look for a similar material in grease pencil slots. */ + short *totcol = BKE_object_material_len_p(ob); + for (short i = 0; i < *totcol; i++) { + ma = BKE_object_material_get(ob, i + 1); + if (ma == NULL) { + continue; + } + + MaterialGPencilStyle *gp_style = ma->gp_style; + if (gp_style != NULL) { + /* Check stroke color. */ + bool found_stroke = compare_v3v3(gp_style->stroke_rgba, col_conv, 0.01f) && + (gp_style->flag & GP_MATERIAL_STROKE_SHOW); + /* Check fill color. */ + bool found_fill = compare_v3v3(gp_style->fill_rgba, col_conv, 0.01f) && + (gp_style->flag & GP_MATERIAL_FILL_SHOW); + + if ((only_stroke) && (found_stroke) && ((gp_style->flag & GP_MATERIAL_FILL_SHOW) == 0)) { + found = true; + } + else if ((only_fill) && (found_fill) && ((gp_style->flag & GP_MATERIAL_STROKE_SHOW) == 0)) { + found = true; + } + else if ((both) && (found_stroke) && (found_fill)) { + found = true; + } + + /* Found existing material. */ + if (found) { + ob->actcol = i + 1; + WM_main_add_notifier(NC_MATERIAL | ND_SHADING_LINKS, NULL); + WM_main_add_notifier(NC_SPACE | ND_SPACE_VIEW3D, NULL); + return; + } + } + } + + /* If material was not found add a new material with stroke and/or fill color + * depending of the secondary key (LMB: Stroke, Shift: Fill, Shift+Ctrl: Stroke/Fill) + */ + int idx; + Material *ma_new = BKE_gpencil_object_material_new(bmain, ob, "Material", &idx); + WM_main_add_notifier(NC_OBJECT | ND_OB_SHADING, &ob->id); + WM_main_add_notifier(NC_MATERIAL | ND_SHADING_LINKS, NULL); + DEG_relations_tag_update(bmain); + + BLI_assert(ma_new != NULL); + + MaterialGPencilStyle *gp_style_new = ma_new->gp_style; + BLI_assert(gp_style_new != NULL); + + /* Only create Stroke (default option). */ + if (only_stroke) { + /* Stroke color. */ + gp_style_new->flag |= GP_MATERIAL_STROKE_SHOW; + gp_style_new->flag &= ~GP_MATERIAL_FILL_SHOW; + copy_v3_v3(gp_style_new->stroke_rgba, col_conv); + zero_v4(gp_style_new->fill_rgba); + } + /* Fill Only. */ + else if (only_fill) { + /* Fill color. */ + gp_style_new->flag &= ~GP_MATERIAL_STROKE_SHOW; + gp_style_new->flag |= GP_MATERIAL_FILL_SHOW; + zero_v4(gp_style_new->stroke_rgba); + copy_v3_v3(gp_style_new->fill_rgba, col_conv); + } + /* Stroke and Fill. */ + else if (both) { + gp_style_new->flag |= GP_MATERIAL_STROKE_SHOW | GP_MATERIAL_FILL_SHOW; + copy_v3_v3(gp_style_new->stroke_rgba, col_conv); + copy_v3_v3(gp_style_new->fill_rgba, col_conv); + } + /* Push undo for new created material. */ + ED_undo_push(C, "Add Grease Pencil Material"); +} + +/* Create a new palette color and palette if needed. */ +static void eyedropper_add_palette_color(bContext *C, const float col_conv[4]) +{ + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + ToolSettings *ts = scene->toolsettings; + GpPaint *gp_paint = ts->gp_paint; + GpVertexPaint *gp_vertexpaint = ts->gp_vertexpaint; + Paint *paint = &gp_paint->paint; + Paint *vertexpaint = &gp_vertexpaint->paint; + + /* Check for Palette in Draw and Vertex Paint Mode. */ + if (paint->palette == NULL) { + Palette *palette = BKE_palette_add(bmain, "Grease Pencil"); + id_us_min(&palette->id); + + BKE_paint_palette_set(paint, palette); + + if (vertexpaint->palette == NULL) { + BKE_paint_palette_set(vertexpaint, palette); + } + } + /* Check if the color exist already. */ + Palette *palette = paint->palette; + LISTBASE_FOREACH (PaletteColor *, palcolor, &palette->colors) { + if (compare_v3v3(palcolor->rgb, col_conv, 0.01f)) { + return; + } + } + + /* Create Colors. */ + PaletteColor *palcol = BKE_palette_color_add(palette); + if (palcol) { + copy_v3_v3(palcol->rgb, col_conv); + } +} + +/* Set the material or the palette color. */ +static void eyedropper_gpencil_color_set(bContext *C, const wmEvent *event, EyedropperGPencil *eye) +{ + + const bool only_stroke = (event->modifier & (KM_CTRL | KM_SHIFT)) == 0; + const bool only_fill = ((event->modifier & KM_CTRL) == 0 && (event->modifier & KM_SHIFT)); + const bool both = ((event->modifier & KM_CTRL) && (event->modifier & KM_SHIFT)); + + float col_conv[4]; + + /* Convert from linear rgb space to display space because grease pencil colors are in display + * space, and this conversion is needed to undo the conversion to linear performed by + * eyedropper_color_sample_fl. */ + if (eye->display) { + copy_v3_v3(col_conv, eye->color); + IMB_colormanagement_scene_linear_to_display_v3(col_conv, eye->display); + } + else { + copy_v3_v3(col_conv, eye->color); + } + + /* Add material or Palette color. */ + if (eye->mode == 0) { + eyedropper_add_material(C, col_conv, only_stroke, only_fill, both); + } + else { + eyedropper_add_palette_color(C, col_conv); + } +} + +/* Sample the color below cursor. */ +static void eyedropper_gpencil_color_sample(bContext *C, EyedropperGPencil *eye, const int m_xy[2]) +{ + eyedropper_color_sample_fl(C, m_xy, eye->color); +} + +/* Cancel operator. */ +static void eyedropper_gpencil_cancel(bContext *C, wmOperator *op) +{ + eyedropper_gpencil_exit(C, op); +} + +/* Main modal status check. */ +static int eyedropper_gpencil_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + EyedropperGPencil *eye = (EyedropperGPencil *)op->customdata; + /* Handle modal keymap */ + switch (event->type) { + case EVT_MODAL_MAP: { + switch (event->val) { + case EYE_MODAL_SAMPLE_BEGIN: { + return OPERATOR_RUNNING_MODAL; + } + case EYE_MODAL_CANCEL: { + eyedropper_gpencil_cancel(C, op); + return OPERATOR_CANCELLED; + } + case EYE_MODAL_SAMPLE_CONFIRM: { + eyedropper_gpencil_color_sample(C, eye, event->xy); + + /* Create material. */ + eyedropper_gpencil_color_set(C, event, eye); + WM_main_add_notifier(NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + eyedropper_gpencil_exit(C, op); + return OPERATOR_FINISHED; + } + default: { + break; + } + } + break; + } + case MOUSEMOVE: + case INBETWEEN_MOUSEMOVE: { + eyedropper_gpencil_color_sample(C, eye, event->xy); + break; + } + default: { + break; + } + } + + return OPERATOR_RUNNING_MODAL; +} + +/* Modal Operator init */ +static int eyedropper_gpencil_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + /* Init. */ + if (eyedropper_gpencil_init(C, op)) { + /* Add modal temp handler. */ + WM_event_add_modal_handler(C, op); + /* Status message. */ + eyedropper_gpencil_status_indicators(C); + + return OPERATOR_RUNNING_MODAL; + } + return OPERATOR_PASS_THROUGH; +} + +/* Repeat operator */ +static int eyedropper_gpencil_exec(bContext *C, wmOperator *op) +{ + /* init */ + if (eyedropper_gpencil_init(C, op)) { + + /* cleanup */ + eyedropper_gpencil_exit(C, op); + + return OPERATOR_FINISHED; + } + return OPERATOR_PASS_THROUGH; +} + +static bool eyedropper_gpencil_poll(bContext *C) +{ + /* Only valid if the current active object is grease pencil. */ + Object *obact = CTX_data_active_object(C); + if ((obact == NULL) || (obact->type != OB_GPENCIL)) { + return false; + } + + /* Test we have a window below. */ + return (CTX_wm_window(C) != NULL); +} + +void UI_OT_eyedropper_gpencil_color(wmOperatorType *ot) +{ + static const EnumPropertyItem items_mode[] = { + {0, "MATERIAL", 0, "Material", ""}, + {1, "PALETTE", 0, "Palette", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + /* identifiers */ + ot->name = "Grease Pencil Eyedropper"; + ot->idname = "UI_OT_eyedropper_gpencil_color"; + ot->description = "Sample a color from the Blender Window and create Grease Pencil material"; + + /* api callbacks */ + ot->invoke = eyedropper_gpencil_invoke; + ot->modal = eyedropper_gpencil_modal; + ot->cancel = eyedropper_gpencil_cancel; + ot->exec = eyedropper_gpencil_exec; + ot->poll = eyedropper_gpencil_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING; + + /* properties */ + ot->prop = RNA_def_enum(ot->srna, "mode", items_mode, 0, "Mode", ""); +} diff --git a/source/blender/editors/interface/eyedroppers/eyedropper_intern.h b/source/blender/editors/interface/eyedroppers/eyedropper_intern.h new file mode 100644 index 00000000000..946f2145d1d --- /dev/null +++ b/source/blender/editors/interface/eyedroppers/eyedropper_intern.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + * + * Share between `interface/eyedropper/` files. + */ + +#pragma once + +/* interface_eyedropper.c */ + +void eyedropper_draw_cursor_text_window(const struct wmWindow *window, const char *name); +void eyedropper_draw_cursor_text_region(const int xy[2], const char *name); +/** + * Utility to retrieve a button representing a RNA property that is currently under the cursor. + * + * This is to be used by any eyedroppers which fetch properties (e.g. UI_OT_eyedropper_driver). + * Especially during modal operations (e.g. as with the eyedroppers), context cannot be relied + * upon to provide this information, as it is not updated until the operator finishes. + * + * \return A button under the mouse which relates to some RNA Property, or NULL + */ +uiBut *eyedropper_get_property_button_under_mouse(bContext *C, const wmEvent *event); +void datadropper_win_area_find(const struct bContext *C, + const int mval[2], + int r_mval[2], + struct wmWindow **r_win, + struct ScrArea **r_area); + +/* interface_eyedropper_color.c (expose for color-band picker) */ + +/** + * \brief get the color from the screen. + * + * Special check for image or nodes where we MAY have HDR pixels which don't display. + * + * \note Exposed by 'eyedropper_intern.h' for use with color band picking. + */ +void eyedropper_color_sample_fl(bContext *C, const int m_xy[2], float r_col[3]); + +/* Used for most eye-dropper operators. */ +enum { + EYE_MODAL_CANCEL = 1, + EYE_MODAL_SAMPLE_CONFIRM, + EYE_MODAL_SAMPLE_BEGIN, + EYE_MODAL_SAMPLE_RESET, +}; + +/* Color-band point sample. */ +enum { + EYE_MODAL_POINT_CANCEL = 1, + EYE_MODAL_POINT_SAMPLE, + EYE_MODAL_POINT_CONFIRM, + EYE_MODAL_POINT_RESET, + EYE_MODAL_POINT_REMOVE_LAST, +}; diff --git a/source/blender/editors/interface/eyedroppers/interface_eyedropper.c b/source/blender/editors/interface/eyedroppers/interface_eyedropper.c new file mode 100644 index 00000000000..c6fb8f0ab68 --- /dev/null +++ b/source/blender/editors/interface/eyedroppers/interface_eyedropper.c @@ -0,0 +1,161 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + */ + +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "BLI_math_color.h" +#include "BLI_math_vector.h" + +#include "BKE_context.h" +#include "BKE_screen.h" + +#include "UI_interface.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "interface_intern.h" + +#include "eyedropper_intern.h" /* own include */ + +/* -------------------------------------------------------------------- */ +/* Keymap + */ +/** \name Modal Keymap + * \{ */ + +wmKeyMap *eyedropper_modal_keymap(wmKeyConfig *keyconf) +{ + static const EnumPropertyItem modal_items[] = { + {EYE_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""}, + {EYE_MODAL_SAMPLE_CONFIRM, "SAMPLE_CONFIRM", 0, "Confirm Sampling", ""}, + {EYE_MODAL_SAMPLE_BEGIN, "SAMPLE_BEGIN", 0, "Start Sampling", ""}, + {EYE_MODAL_SAMPLE_RESET, "SAMPLE_RESET", 0, "Reset Sampling", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "Eyedropper Modal Map"); + + /* this function is called for each spacetype, only needs to add map once */ + if (keymap && keymap->modal_items) { + return NULL; + } + + keymap = WM_modalkeymap_ensure(keyconf, "Eyedropper Modal Map", modal_items); + + /* assign to operators */ + WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_colorramp"); + WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_color"); + WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_id"); + WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_depth"); + WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_driver"); + WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_gpencil_color"); + + return keymap; +} + +wmKeyMap *eyedropper_colorband_modal_keymap(wmKeyConfig *keyconf) +{ + static const EnumPropertyItem modal_items_point[] = { + {EYE_MODAL_POINT_CANCEL, "CANCEL", 0, "Cancel", ""}, + {EYE_MODAL_POINT_SAMPLE, "SAMPLE_SAMPLE", 0, "Sample a Point", ""}, + {EYE_MODAL_POINT_CONFIRM, "SAMPLE_CONFIRM", 0, "Confirm Sampling", ""}, + {EYE_MODAL_POINT_RESET, "SAMPLE_RESET", 0, "Reset Sampling", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "Eyedropper ColorRamp PointSampling Map"); + if (keymap && keymap->modal_items) { + return keymap; + } + + keymap = WM_modalkeymap_ensure( + keyconf, "Eyedropper ColorRamp PointSampling Map", modal_items_point); + + /* assign to operators */ + WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_colorramp_point"); + + return keymap; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/* Utility Functions + */ + +/** \name Generic Shared Functions + * \{ */ + +static void eyedropper_draw_cursor_text_ex(const int xy[2], const char *name) +{ + const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; + + /* Use the theme settings from tooltips. */ + const bTheme *btheme = UI_GetTheme(); + const uiWidgetColors *wcol = &btheme->tui.wcol_tooltip; + + float col_fg[4], col_bg[4]; + rgba_uchar_to_float(col_fg, wcol->text); + rgba_uchar_to_float(col_bg, wcol->inner); + + UI_fontstyle_draw_simple_backdrop(fstyle, xy[0], xy[1] + U.widget_unit, name, col_fg, col_bg); +} + +void eyedropper_draw_cursor_text_window(const struct wmWindow *window, const char *name) +{ + if (name[0] == '\0') { + return; + } + + eyedropper_draw_cursor_text_ex(window->eventstate->xy, name); +} + +void eyedropper_draw_cursor_text_region(const int xy[2], const char *name) +{ + if (name[0] == '\0') { + return; + } + + eyedropper_draw_cursor_text_ex(xy, name); +} + +uiBut *eyedropper_get_property_button_under_mouse(bContext *C, const wmEvent *event) +{ + bScreen *screen = CTX_wm_screen(C); + ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->xy); + const ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_ANY, event->xy); + + uiBut *but = ui_but_find_mouse_over(region, event); + + if (ELEM(NULL, but, but->rnapoin.data, but->rnaprop)) { + return NULL; + } + return but; +} + +void datadropper_win_area_find( + const bContext *C, const int mval[2], int r_mval[2], wmWindow **r_win, ScrArea **r_area) +{ + bScreen *screen = CTX_wm_screen(C); + + *r_win = CTX_wm_window(C); + *r_area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, mval); + if (*r_area == NULL) { + *r_win = WM_window_find_under_cursor(*r_win, mval, r_mval); + if (*r_win) { + screen = WM_window_get_active_screen(*r_win); + *r_area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, r_mval); + } + } + else if (mval != r_mval) { + copy_v2_v2_int(r_mval, mval); + } +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_eyedropper.c b/source/blender/editors/interface/interface_eyedropper.c deleted file mode 100644 index eaec1e249b7..00000000000 --- a/source/blender/editors/interface/interface_eyedropper.c +++ /dev/null @@ -1,161 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2009 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - */ - -#include "DNA_screen_types.h" -#include "DNA_space_types.h" - -#include "BLI_math_color.h" -#include "BLI_math_vector.h" - -#include "BKE_context.h" -#include "BKE_screen.h" - -#include "UI_interface.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "interface_intern.h" - -#include "interface_eyedropper_intern.h" /* own include */ - -/* -------------------------------------------------------------------- */ -/* Keymap - */ -/** \name Modal Keymap - * \{ */ - -wmKeyMap *eyedropper_modal_keymap(wmKeyConfig *keyconf) -{ - static const EnumPropertyItem modal_items[] = { - {EYE_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""}, - {EYE_MODAL_SAMPLE_CONFIRM, "SAMPLE_CONFIRM", 0, "Confirm Sampling", ""}, - {EYE_MODAL_SAMPLE_BEGIN, "SAMPLE_BEGIN", 0, "Start Sampling", ""}, - {EYE_MODAL_SAMPLE_RESET, "SAMPLE_RESET", 0, "Reset Sampling", ""}, - {0, NULL, 0, NULL, NULL}, - }; - - wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "Eyedropper Modal Map"); - - /* this function is called for each spacetype, only needs to add map once */ - if (keymap && keymap->modal_items) { - return NULL; - } - - keymap = WM_modalkeymap_ensure(keyconf, "Eyedropper Modal Map", modal_items); - - /* assign to operators */ - WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_colorramp"); - WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_color"); - WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_id"); - WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_depth"); - WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_driver"); - WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_gpencil_color"); - - return keymap; -} - -wmKeyMap *eyedropper_colorband_modal_keymap(wmKeyConfig *keyconf) -{ - static const EnumPropertyItem modal_items_point[] = { - {EYE_MODAL_POINT_CANCEL, "CANCEL", 0, "Cancel", ""}, - {EYE_MODAL_POINT_SAMPLE, "SAMPLE_SAMPLE", 0, "Sample a Point", ""}, - {EYE_MODAL_POINT_CONFIRM, "SAMPLE_CONFIRM", 0, "Confirm Sampling", ""}, - {EYE_MODAL_POINT_RESET, "SAMPLE_RESET", 0, "Reset Sampling", ""}, - {0, NULL, 0, NULL, NULL}, - }; - - wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "Eyedropper ColorRamp PointSampling Map"); - if (keymap && keymap->modal_items) { - return keymap; - } - - keymap = WM_modalkeymap_ensure( - keyconf, "Eyedropper ColorRamp PointSampling Map", modal_items_point); - - /* assign to operators */ - WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_colorramp_point"); - - return keymap; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/* Utility Functions - */ - -/** \name Generic Shared Functions - * \{ */ - -static void eyedropper_draw_cursor_text_ex(const int xy[2], const char *name) -{ - const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; - - /* Use the theme settings from tooltips. */ - const bTheme *btheme = UI_GetTheme(); - const uiWidgetColors *wcol = &btheme->tui.wcol_tooltip; - - float col_fg[4], col_bg[4]; - rgba_uchar_to_float(col_fg, wcol->text); - rgba_uchar_to_float(col_bg, wcol->inner); - - UI_fontstyle_draw_simple_backdrop(fstyle, xy[0], xy[1] + U.widget_unit, name, col_fg, col_bg); -} - -void eyedropper_draw_cursor_text_window(const struct wmWindow *window, const char *name) -{ - if (name[0] == '\0') { - return; - } - - eyedropper_draw_cursor_text_ex(window->eventstate->xy, name); -} - -void eyedropper_draw_cursor_text_region(const int xy[2], const char *name) -{ - if (name[0] == '\0') { - return; - } - - eyedropper_draw_cursor_text_ex(xy, name); -} - -uiBut *eyedropper_get_property_button_under_mouse(bContext *C, const wmEvent *event) -{ - bScreen *screen = CTX_wm_screen(C); - ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->xy); - const ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_ANY, event->xy); - - uiBut *but = ui_but_find_mouse_over(region, event); - - if (ELEM(NULL, but, but->rnapoin.data, but->rnaprop)) { - return NULL; - } - return but; -} - -void datadropper_win_area_find( - const bContext *C, const int mval[2], int r_mval[2], wmWindow **r_win, ScrArea **r_area) -{ - bScreen *screen = CTX_wm_screen(C); - - *r_win = CTX_wm_window(C); - *r_area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, mval); - if (*r_area == NULL) { - *r_win = WM_window_find_under_cursor(*r_win, mval, r_mval); - if (*r_win) { - screen = WM_window_get_active_screen(*r_win); - *r_area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, r_mval); - } - } - else if (mval != r_mval) { - copy_v2_v2_int(r_mval, mval); - } -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_eyedropper_color.c b/source/blender/editors/interface/interface_eyedropper_color.c deleted file mode 100644 index c015a60de89..00000000000 --- a/source/blender/editors/interface/interface_eyedropper_color.c +++ /dev/null @@ -1,554 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2009 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - * - * Eyedropper (RGB Color) - * - * Defines: - * - #UI_OT_eyedropper_color - */ - -#include "MEM_guardedalloc.h" - -#include "DNA_screen_types.h" -#include "DNA_space_types.h" - -#include "BLI_listbase.h" -#include "BLI_math_vector.h" -#include "BLI_string.h" - -#include "BKE_context.h" -#include "BKE_cryptomatte.h" -#include "BKE_image.h" -#include "BKE_main.h" -#include "BKE_node.h" -#include "BKE_screen.h" - -#include "NOD_composite.h" - -#include "RNA_access.h" -#include "RNA_prototypes.h" - -#include "UI_interface.h" - -#include "IMB_colormanagement.h" -#include "IMB_imbuf_types.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "RNA_define.h" - -#include "interface_intern.h" - -#include "ED_clip.h" -#include "ED_image.h" -#include "ED_node.h" -#include "ED_screen.h" - -#include "RE_pipeline.h" - -#include "interface_eyedropper_intern.h" - -typedef struct Eyedropper { - struct ColorManagedDisplay *display; - - PointerRNA ptr; - PropertyRNA *prop; - int index; - bool is_undo; - - bool is_set; - float init_col[3]; /* for resetting on cancel */ - - bool accum_start; /* has mouse been pressed */ - float accum_col[3]; - int accum_tot; - - void *draw_handle_sample_text; - char sample_text[MAX_NAME]; - - bNode *crypto_node; - struct CryptomatteSession *cryptomatte_session; -} Eyedropper; - -static void eyedropper_draw_cb(const wmWindow *window, void *arg) -{ - Eyedropper *eye = arg; - eyedropper_draw_cursor_text_window(window, eye->sample_text); -} - -static bool eyedropper_init(bContext *C, wmOperator *op) -{ - Eyedropper *eye = MEM_callocN(sizeof(Eyedropper), __func__); - - uiBut *but = UI_context_active_but_prop_get(C, &eye->ptr, &eye->prop, &eye->index); - const enum PropertySubType prop_subtype = eye->prop ? RNA_property_subtype(eye->prop) : 0; - - if ((eye->ptr.data == NULL) || (eye->prop == NULL) || - (RNA_property_editable(&eye->ptr, eye->prop) == false) || - (RNA_property_array_length(&eye->ptr, eye->prop) < 3) || - (RNA_property_type(eye->prop) != PROP_FLOAT) || - (ELEM(prop_subtype, PROP_COLOR, PROP_COLOR_GAMMA) == 0)) { - MEM_freeN(eye); - return false; - } - op->customdata = eye; - - eye->is_undo = UI_but_flag_is_set(but, UI_BUT_UNDO); - - float col[4]; - RNA_property_float_get_array(&eye->ptr, eye->prop, col); - if (eye->ptr.type == &RNA_CompositorNodeCryptomatteV2) { - eye->crypto_node = (bNode *)eye->ptr.data; - eye->cryptomatte_session = ntreeCompositCryptomatteSession(CTX_data_scene(C), - eye->crypto_node); - eye->draw_handle_sample_text = WM_draw_cb_activate(CTX_wm_window(C), eyedropper_draw_cb, eye); - } - - if (prop_subtype != PROP_COLOR) { - Scene *scene = CTX_data_scene(C); - const char *display_device; - - display_device = scene->display_settings.display_device; - eye->display = IMB_colormanagement_display_get_named(display_device); - - /* store initial color */ - if (eye->display) { - IMB_colormanagement_display_to_scene_linear_v3(col, eye->display); - } - } - copy_v3_v3(eye->init_col, col); - - return true; -} - -static void eyedropper_exit(bContext *C, wmOperator *op) -{ - Eyedropper *eye = op->customdata; - wmWindow *window = CTX_wm_window(C); - WM_cursor_modal_restore(window); - - if (eye->draw_handle_sample_text) { - WM_draw_cb_exit(window, eye->draw_handle_sample_text); - eye->draw_handle_sample_text = NULL; - } - - if (eye->cryptomatte_session) { - BKE_cryptomatte_free(eye->cryptomatte_session); - eye->cryptomatte_session = NULL; - } - - MEM_SAFE_FREE(op->customdata); -} - -/* *** eyedropper_color_ helper functions *** */ - -static bool eyedropper_cryptomatte_sample_renderlayer_fl(RenderLayer *render_layer, - const char *prefix, - const float fpos[2], - float r_col[3]) -{ - if (!render_layer) { - return false; - } - - const int render_layer_name_len = BLI_strnlen(render_layer->name, sizeof(render_layer->name)); - if (strncmp(prefix, render_layer->name, render_layer_name_len) != 0) { - return false; - } - - const int prefix_len = strlen(prefix); - if (prefix_len <= render_layer_name_len + 1) { - return false; - } - - /* RenderResult from images can have no render layer name. */ - const char *render_pass_name_prefix = render_layer_name_len ? - prefix + 1 + render_layer_name_len : - prefix; - - LISTBASE_FOREACH (RenderPass *, render_pass, &render_layer->passes) { - if (STRPREFIX(render_pass->name, render_pass_name_prefix) && - !STREQLEN(render_pass->name, render_pass_name_prefix, sizeof(render_pass->name))) { - BLI_assert(render_pass->channels == 4); - const int x = (int)(fpos[0] * render_pass->rectx); - const int y = (int)(fpos[1] * render_pass->recty); - const int offset = 4 * (y * render_pass->rectx + x); - zero_v3(r_col); - r_col[0] = render_pass->rect[offset]; - return true; - } - } - - return false; -} -static bool eyedropper_cryptomatte_sample_render_fl(const bNode *node, - const char *prefix, - const float fpos[2], - float r_col[3]) -{ - bool success = false; - Scene *scene = (Scene *)node->id; - BLI_assert(GS(scene->id.name) == ID_SCE); - Render *re = RE_GetSceneRender(scene); - - if (re) { - RenderResult *rr = RE_AcquireResultRead(re); - if (rr) { - LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) { - RenderLayer *render_layer = RE_GetRenderLayer(rr, view_layer->name); - success = eyedropper_cryptomatte_sample_renderlayer_fl(render_layer, prefix, fpos, r_col); - if (success) { - break; - } - } - } - RE_ReleaseResult(re); - } - return success; -} - -static bool eyedropper_cryptomatte_sample_image_fl(const bNode *node, - NodeCryptomatte *crypto, - const char *prefix, - const float fpos[2], - float r_col[3]) -{ - bool success = false; - Image *image = (Image *)node->id; - BLI_assert((image == NULL) || (GS(image->id.name) == ID_IM)); - ImageUser *iuser = &crypto->iuser; - - if (image && image->type == IMA_TYPE_MULTILAYER) { - ImBuf *ibuf = BKE_image_acquire_ibuf(image, iuser, NULL); - if (image->rr) { - LISTBASE_FOREACH (RenderLayer *, render_layer, &image->rr->layers) { - success = eyedropper_cryptomatte_sample_renderlayer_fl(render_layer, prefix, fpos, r_col); - if (success) { - break; - } - } - } - BKE_image_release_ibuf(image, ibuf, NULL); - } - return success; -} - -static bool eyedropper_cryptomatte_sample_fl(bContext *C, - Eyedropper *eye, - const int m_xy[2], - float r_col[3]) -{ - bNode *node = eye->crypto_node; - NodeCryptomatte *crypto = node ? ((NodeCryptomatte *)node->storage) : NULL; - - if (!crypto) { - return false; - } - - bScreen *screen = CTX_wm_screen(C); - ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, m_xy); - if (!area || !ELEM(area->spacetype, SPACE_IMAGE, SPACE_NODE, SPACE_CLIP)) { - return false; - } - - ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, m_xy); - if (!region) { - return false; - } - - int mval[2] = {m_xy[0] - region->winrct.xmin, m_xy[1] - region->winrct.ymin}; - float fpos[2] = {-1.0f, -1.0}; - switch (area->spacetype) { - case SPACE_IMAGE: { - SpaceImage *sima = area->spacedata.first; - ED_space_image_get_position(sima, region, mval, fpos); - break; - } - case SPACE_NODE: { - Main *bmain = CTX_data_main(C); - SpaceNode *snode = area->spacedata.first; - ED_space_node_get_position(bmain, snode, region, mval, fpos); - break; - } - case SPACE_CLIP: { - SpaceClip *sc = area->spacedata.first; - ED_space_clip_get_position(sc, region, mval, fpos); - break; - } - default: { - break; - } - } - - if (fpos[0] < 0.0f || fpos[1] < 0.0f || fpos[0] >= 1.0f || fpos[1] >= 1.0f) { - return false; - } - - /* CMP_CRYPTOMATTE_SRC_RENDER and CMP_CRYPTOMATTE_SRC_IMAGE require a referenced image/scene to - * work properly. */ - if (!node->id) { - return false; - } - - /* TODO(jbakker): Migrate this file to cc and use std::string as return param. */ - char prefix[MAX_NAME + 1]; - const Scene *scene = CTX_data_scene(C); - ntreeCompositCryptomatteLayerPrefix(scene, node, prefix, sizeof(prefix) - 1); - prefix[MAX_NAME] = '\0'; - - if (node->custom1 == CMP_CRYPTOMATTE_SRC_RENDER) { - return eyedropper_cryptomatte_sample_render_fl(node, prefix, fpos, r_col); - } - if (node->custom1 == CMP_CRYPTOMATTE_SRC_IMAGE) { - return eyedropper_cryptomatte_sample_image_fl(node, crypto, prefix, fpos, r_col); - } - return false; -} - -void eyedropper_color_sample_fl(bContext *C, const int m_xy[2], float r_col[3]) -{ - /* we could use some clever */ - Main *bmain = CTX_data_main(C); - wmWindowManager *wm = CTX_wm_manager(C); - const char *display_device = CTX_data_scene(C)->display_settings.display_device; - struct ColorManagedDisplay *display = IMB_colormanagement_display_get_named(display_device); - - int mval[2]; - wmWindow *win; - ScrArea *area; - datadropper_win_area_find(C, m_xy, mval, &win, &area); - - if (area) { - if (area->spacetype == SPACE_IMAGE) { - ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval); - if (region) { - SpaceImage *sima = area->spacedata.first; - const int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin}; - - if (ED_space_image_color_sample(sima, region, region_mval, r_col, NULL)) { - return; - } - } - } - else if (area->spacetype == SPACE_NODE) { - ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval); - if (region) { - SpaceNode *snode = area->spacedata.first; - const int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin}; - - if (ED_space_node_color_sample(bmain, snode, region, region_mval, r_col)) { - return; - } - } - } - else if (area->spacetype == SPACE_CLIP) { - ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval); - if (region) { - SpaceClip *sc = area->spacedata.first; - const int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin}; - - if (ED_space_clip_color_sample(sc, region, region_mval, r_col)) { - return; - } - } - } - } - - if (win) { - /* Fallback to simple opengl picker. */ - WM_window_pixel_sample_read(wm, win, mval, r_col); - IMB_colormanagement_display_to_scene_linear_v3(r_col, display); - } - else { - zero_v3(r_col); - } -} - -/* sets the sample color RGB, maintaining A */ -static void eyedropper_color_set(bContext *C, Eyedropper *eye, const float col[3]) -{ - float col_conv[4]; - - /* to maintain alpha */ - RNA_property_float_get_array(&eye->ptr, eye->prop, col_conv); - - /* convert from linear rgb space to display space */ - if (eye->display) { - copy_v3_v3(col_conv, col); - IMB_colormanagement_scene_linear_to_display_v3(col_conv, eye->display); - } - else { - copy_v3_v3(col_conv, col); - } - - RNA_property_float_set_array(&eye->ptr, eye->prop, col_conv); - eye->is_set = true; - - RNA_property_update(C, &eye->ptr, eye->prop); -} - -static void eyedropper_color_sample(bContext *C, Eyedropper *eye, const int m_xy[2]) -{ - /* Accumulate color. */ - float col[3]; - if (eye->crypto_node) { - if (!eyedropper_cryptomatte_sample_fl(C, eye, m_xy, col)) { - return; - } - } - else { - eyedropper_color_sample_fl(C, m_xy, col); - } - - if (!eye->crypto_node) { - add_v3_v3(eye->accum_col, col); - eye->accum_tot++; - } - else { - copy_v3_v3(eye->accum_col, col); - eye->accum_tot = 1; - } - - /* Apply to property. */ - float accum_col[3]; - if (eye->accum_tot > 1) { - mul_v3_v3fl(accum_col, eye->accum_col, 1.0f / (float)eye->accum_tot); - } - else { - copy_v3_v3(accum_col, eye->accum_col); - } - eyedropper_color_set(C, eye, accum_col); -} - -static void eyedropper_color_sample_text_update(bContext *C, Eyedropper *eye, const int m_xy[2]) -{ - float col[3]; - eye->sample_text[0] = '\0'; - - if (eye->cryptomatte_session) { - if (eyedropper_cryptomatte_sample_fl(C, eye, m_xy, col)) { - BKE_cryptomatte_find_name( - eye->cryptomatte_session, col[0], eye->sample_text, sizeof(eye->sample_text)); - eye->sample_text[sizeof(eye->sample_text) - 1] = '\0'; - } - } -} - -static void eyedropper_cancel(bContext *C, wmOperator *op) -{ - Eyedropper *eye = op->customdata; - if (eye->is_set) { - eyedropper_color_set(C, eye, eye->init_col); - } - eyedropper_exit(C, op); -} - -/* main modal status check */ -static int eyedropper_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - Eyedropper *eye = (Eyedropper *)op->customdata; - - /* handle modal keymap */ - if (event->type == EVT_MODAL_MAP) { - switch (event->val) { - case EYE_MODAL_CANCEL: - eyedropper_cancel(C, op); - return OPERATOR_CANCELLED; - case EYE_MODAL_SAMPLE_CONFIRM: { - const bool is_undo = eye->is_undo; - if (eye->accum_tot == 0) { - eyedropper_color_sample(C, eye, event->xy); - } - eyedropper_exit(C, op); - /* Could support finished & undo-skip. */ - return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED; - } - case EYE_MODAL_SAMPLE_BEGIN: - /* enable accum and make first sample */ - eye->accum_start = true; - eyedropper_color_sample(C, eye, event->xy); - break; - case EYE_MODAL_SAMPLE_RESET: - eye->accum_tot = 0; - zero_v3(eye->accum_col); - eyedropper_color_sample(C, eye, event->xy); - break; - } - } - else if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { - if (eye->accum_start) { - /* button is pressed so keep sampling */ - eyedropper_color_sample(C, eye, event->xy); - } - - if (eye->draw_handle_sample_text) { - eyedropper_color_sample_text_update(C, eye, event->xy); - ED_region_tag_redraw(CTX_wm_region(C)); - } - } - - return OPERATOR_RUNNING_MODAL; -} - -/* Modal Operator init */ -static int eyedropper_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) -{ - /* init */ - if (eyedropper_init(C, op)) { - wmWindow *win = CTX_wm_window(C); - /* Workaround for de-activating the button clearing the cursor, see T76794 */ - UI_context_active_but_clear(C, win, CTX_wm_region(C)); - WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); - - /* add temp handler */ - WM_event_add_modal_handler(C, op); - - return OPERATOR_RUNNING_MODAL; - } - return OPERATOR_PASS_THROUGH; -} - -/* Repeat operator */ -static int eyedropper_exec(bContext *C, wmOperator *op) -{ - /* init */ - if (eyedropper_init(C, op)) { - - /* do something */ - - /* cleanup */ - eyedropper_exit(C, op); - - return OPERATOR_FINISHED; - } - return OPERATOR_PASS_THROUGH; -} - -static bool eyedropper_poll(bContext *C) -{ - /* Actual test for active button happens later, since we don't - * know which one is active until mouse over. */ - return (CTX_wm_window(C) != NULL); -} - -void UI_OT_eyedropper_color(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Eyedropper"; - ot->idname = "UI_OT_eyedropper_color"; - ot->description = "Sample a color from the Blender window to store in a property"; - - /* api callbacks */ - ot->invoke = eyedropper_invoke; - ot->modal = eyedropper_modal; - ot->cancel = eyedropper_cancel; - ot->exec = eyedropper_exec; - ot->poll = eyedropper_poll; - - /* flags */ - ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; -} diff --git a/source/blender/editors/interface/interface_eyedropper_colorband.c b/source/blender/editors/interface/interface_eyedropper_colorband.c deleted file mode 100644 index a69c36fefbd..00000000000 --- a/source/blender/editors/interface/interface_eyedropper_colorband.c +++ /dev/null @@ -1,367 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2009 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - * - * Eyedropper (Color Band). - * - * Operates by either: - * - Dragging a straight line, sampling pixels formed by the line to extract a gradient. - * - Clicking on points, adding each color to the end of the color-band. - * - * Defines: - * - #UI_OT_eyedropper_colorramp - * - #UI_OT_eyedropper_colorramp_point - */ - -#include "MEM_guardedalloc.h" - -#include "DNA_screen_types.h" - -#include "BLI_bitmap_draw_2d.h" -#include "BLI_math_vector.h" - -#include "BKE_colorband.h" -#include "BKE_context.h" - -#include "RNA_access.h" -#include "RNA_prototypes.h" - -#include "UI_interface.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "interface_intern.h" - -#include "interface_eyedropper_intern.h" - -typedef struct Colorband_RNAUpdateCb { - PointerRNA ptr; - PropertyRNA *prop; -} Colorband_RNAUpdateCb; - -typedef struct EyedropperColorband { - int event_xy_last[2]; - /* Alpha is currently fixed at 1.0, may support in future. */ - float (*color_buffer)[4]; - int color_buffer_alloc; - int color_buffer_len; - bool sample_start; - ColorBand init_color_band; - ColorBand *color_band; - PointerRNA ptr; - PropertyRNA *prop; - bool is_undo; - bool is_set; -} EyedropperColorband; - -/* For user-data only. */ -struct EyedropperColorband_Context { - bContext *context; - EyedropperColorband *eye; -}; - -static bool eyedropper_colorband_init(bContext *C, wmOperator *op) -{ - ColorBand *band = NULL; - - uiBut *but = UI_context_active_but_get(C); - - PointerRNA rna_update_ptr = PointerRNA_NULL; - PropertyRNA *rna_update_prop = NULL; - bool is_undo = true; - - if (but == NULL) { - /* pass */ - } - else { - if (but->type == UI_BTYPE_COLORBAND) { - /* When invoked with a hotkey, we can find the band in 'but->poin'. */ - band = (ColorBand *)but->poin; - } - else { - /* When invoked from a button it's in custom_data field. */ - band = (ColorBand *)but->custom_data; - } - - if (band) { - rna_update_ptr = ((Colorband_RNAUpdateCb *)but->func_argN)->ptr; - rna_update_prop = ((Colorband_RNAUpdateCb *)but->func_argN)->prop; - is_undo = UI_but_flag_is_set(but, UI_BUT_UNDO); - } - } - - if (!band) { - const PointerRNA ptr = CTX_data_pointer_get_type(C, "color_ramp", &RNA_ColorRamp); - if (ptr.data != NULL) { - band = ptr.data; - - /* Set this to a sub-member of the property to trigger an update. */ - rna_update_ptr = ptr; - rna_update_prop = &rna_ColorRamp_color_mode; - is_undo = RNA_struct_undo_check(ptr.type); - } - } - - if (!band) { - return false; - } - - EyedropperColorband *eye = MEM_callocN(sizeof(EyedropperColorband), __func__); - eye->color_buffer_alloc = 16; - eye->color_buffer = MEM_mallocN(sizeof(*eye->color_buffer) * eye->color_buffer_alloc, __func__); - eye->color_buffer_len = 0; - eye->color_band = band; - eye->init_color_band = *eye->color_band; - eye->ptr = rna_update_ptr; - eye->prop = rna_update_prop; - eye->is_undo = is_undo; - - op->customdata = eye; - - return true; -} - -static void eyedropper_colorband_sample_point(bContext *C, - EyedropperColorband *eye, - const int m_xy[2]) -{ - if (eye->event_xy_last[0] != m_xy[0] || eye->event_xy_last[1] != m_xy[1]) { - float col[4]; - col[3] = 1.0f; /* TODO: sample alpha */ - eyedropper_color_sample_fl(C, m_xy, col); - if (eye->color_buffer_len + 1 == eye->color_buffer_alloc) { - eye->color_buffer_alloc *= 2; - eye->color_buffer = MEM_reallocN(eye->color_buffer, - sizeof(*eye->color_buffer) * eye->color_buffer_alloc); - } - copy_v4_v4(eye->color_buffer[eye->color_buffer_len], col); - eye->color_buffer_len += 1; - copy_v2_v2_int(eye->event_xy_last, m_xy); - eye->is_set = true; - } -} - -static bool eyedropper_colorband_sample_callback(int mx, int my, void *userdata) -{ - struct EyedropperColorband_Context *data = userdata; - bContext *C = data->context; - EyedropperColorband *eye = data->eye; - const int cursor[2] = {mx, my}; - eyedropper_colorband_sample_point(C, eye, cursor); - return true; -} - -static void eyedropper_colorband_sample_segment(bContext *C, - EyedropperColorband *eye, - const int m_xy[2]) -{ - /* Since the mouse tends to move rather rapidly we use #BLI_bitmap_draw_2d_line_v2v2i - * to interpolate between the reported coordinates */ - struct EyedropperColorband_Context userdata = {C, eye}; - BLI_bitmap_draw_2d_line_v2v2i( - eye->event_xy_last, m_xy, eyedropper_colorband_sample_callback, &userdata); -} - -static void eyedropper_colorband_exit(bContext *C, wmOperator *op) -{ - WM_cursor_modal_restore(CTX_wm_window(C)); - - if (op->customdata) { - EyedropperColorband *eye = op->customdata; - MEM_freeN(eye->color_buffer); - MEM_freeN(eye); - op->customdata = NULL; - } -} - -static void eyedropper_colorband_apply(bContext *C, wmOperator *op) -{ - EyedropperColorband *eye = op->customdata; - /* Always filter, avoids noise in resulting color-band. */ - const bool filter_samples = true; - BKE_colorband_init_from_table_rgba( - eye->color_band, eye->color_buffer, eye->color_buffer_len, filter_samples); - eye->is_set = true; - if (eye->prop) { - RNA_property_update(C, &eye->ptr, eye->prop); - } -} - -static void eyedropper_colorband_cancel(bContext *C, wmOperator *op) -{ - EyedropperColorband *eye = op->customdata; - if (eye->is_set) { - *eye->color_band = eye->init_color_band; - if (eye->prop) { - RNA_property_update(C, &eye->ptr, eye->prop); - } - } - eyedropper_colorband_exit(C, op); -} - -/* main modal status check */ -static int eyedropper_colorband_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - EyedropperColorband *eye = op->customdata; - /* handle modal keymap */ - if (event->type == EVT_MODAL_MAP) { - switch (event->val) { - case EYE_MODAL_CANCEL: - eyedropper_colorband_cancel(C, op); - return OPERATOR_CANCELLED; - case EYE_MODAL_SAMPLE_CONFIRM: { - const bool is_undo = eye->is_undo; - eyedropper_colorband_sample_segment(C, eye, event->xy); - eyedropper_colorband_apply(C, op); - eyedropper_colorband_exit(C, op); - /* Could support finished & undo-skip. */ - return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED; - } - case EYE_MODAL_SAMPLE_BEGIN: - /* enable accum and make first sample */ - eye->sample_start = true; - eyedropper_colorband_sample_point(C, eye, event->xy); - eyedropper_colorband_apply(C, op); - copy_v2_v2_int(eye->event_xy_last, event->xy); - break; - case EYE_MODAL_SAMPLE_RESET: - break; - } - } - else if (event->type == MOUSEMOVE) { - if (eye->sample_start) { - eyedropper_colorband_sample_segment(C, eye, event->xy); - eyedropper_colorband_apply(C, op); - } - } - return OPERATOR_RUNNING_MODAL; -} - -static int eyedropper_colorband_point_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - EyedropperColorband *eye = op->customdata; - /* handle modal keymap */ - if (event->type == EVT_MODAL_MAP) { - switch (event->val) { - case EYE_MODAL_POINT_CANCEL: - eyedropper_colorband_cancel(C, op); - return OPERATOR_CANCELLED; - case EYE_MODAL_POINT_CONFIRM: - eyedropper_colorband_apply(C, op); - eyedropper_colorband_exit(C, op); - return OPERATOR_FINISHED; - case EYE_MODAL_POINT_REMOVE_LAST: - if (eye->color_buffer_len > 0) { - eye->color_buffer_len -= 1; - eyedropper_colorband_apply(C, op); - } - break; - case EYE_MODAL_POINT_SAMPLE: - eyedropper_colorband_sample_point(C, eye, event->xy); - eyedropper_colorband_apply(C, op); - if (eye->color_buffer_len == MAXCOLORBAND) { - eyedropper_colorband_exit(C, op); - return OPERATOR_FINISHED; - } - break; - case EYE_MODAL_SAMPLE_RESET: - *eye->color_band = eye->init_color_band; - if (eye->prop) { - RNA_property_update(C, &eye->ptr, eye->prop); - } - eye->color_buffer_len = 0; - break; - } - } - return OPERATOR_RUNNING_MODAL; -} - -/* Modal Operator init */ -static int eyedropper_colorband_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) -{ - /* init */ - if (eyedropper_colorband_init(C, op)) { - wmWindow *win = CTX_wm_window(C); - /* Workaround for de-activating the button clearing the cursor, see T76794 */ - UI_context_active_but_clear(C, win, CTX_wm_region(C)); - WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); - - /* add temp handler */ - WM_event_add_modal_handler(C, op); - - return OPERATOR_RUNNING_MODAL; - } - return OPERATOR_CANCELLED; -} - -/* Repeat operator */ -static int eyedropper_colorband_exec(bContext *C, wmOperator *op) -{ - /* init */ - if (eyedropper_colorband_init(C, op)) { - - /* do something */ - - /* cleanup */ - eyedropper_colorband_exit(C, op); - - return OPERATOR_FINISHED; - } - return OPERATOR_CANCELLED; -} - -static bool eyedropper_colorband_poll(bContext *C) -{ - uiBut *but = UI_context_active_but_get(C); - if (but && but->type == UI_BTYPE_COLORBAND) { - return true; - } - const PointerRNA ptr = CTX_data_pointer_get_type(C, "color_ramp", &RNA_ColorRamp); - if (ptr.data != NULL) { - return true; - } - return false; -} - -void UI_OT_eyedropper_colorramp(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Eyedropper Colorband"; - ot->idname = "UI_OT_eyedropper_colorramp"; - ot->description = "Sample a color band"; - - /* api callbacks */ - ot->invoke = eyedropper_colorband_invoke; - ot->modal = eyedropper_colorband_modal; - ot->cancel = eyedropper_colorband_cancel; - ot->exec = eyedropper_colorband_exec; - ot->poll = eyedropper_colorband_poll; - - /* flags */ - ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; - - /* properties */ -} - -void UI_OT_eyedropper_colorramp_point(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Eyedropper Colorband (Points)"; - ot->idname = "UI_OT_eyedropper_colorramp_point"; - ot->description = "Point-sample a color band"; - - /* api callbacks */ - ot->invoke = eyedropper_colorband_invoke; - ot->modal = eyedropper_colorband_point_modal; - ot->cancel = eyedropper_colorband_cancel; - ot->exec = eyedropper_colorband_exec; - ot->poll = eyedropper_colorband_poll; - - /* flags */ - ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; - - /* properties */ -} diff --git a/source/blender/editors/interface/interface_eyedropper_datablock.c b/source/blender/editors/interface/interface_eyedropper_datablock.c deleted file mode 100644 index 01b958576b6..00000000000 --- a/source/blender/editors/interface/interface_eyedropper_datablock.c +++ /dev/null @@ -1,378 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2009 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - * - * Eyedropper (ID data-blocks) - * - * Defines: - * - #UI_OT_eyedropper_id - */ - -#include "MEM_guardedalloc.h" - -#include "DNA_object_types.h" -#include "DNA_screen_types.h" -#include "DNA_space_types.h" - -#include "BLI_math_vector.h" -#include "BLI_string.h" - -#include "BLT_translation.h" - -#include "BKE_context.h" -#include "BKE_idtype.h" -#include "BKE_report.h" -#include "BKE_screen.h" - -#include "RNA_access.h" - -#include "UI_interface.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "ED_outliner.h" -#include "ED_screen.h" -#include "ED_space_api.h" -#include "ED_view3d.h" - -#include "interface_eyedropper_intern.h" -#include "interface_intern.h" - -/** - * \note #DataDropper is only internal name to avoid confusion with other kinds of eye-droppers. - */ -typedef struct DataDropper { - PointerRNA ptr; - PropertyRNA *prop; - short idcode; - const char *idcode_name; - bool is_undo; - - ID *init_id; /* for resetting on cancel */ - - ScrArea *cursor_area; /* Area under the cursor */ - ARegionType *art; - void *draw_handle_pixel; - int name_pos[2]; - char name[200]; -} DataDropper; - -static void datadropper_draw_cb(const struct bContext *UNUSED(C), - ARegion *UNUSED(region), - void *arg) -{ - DataDropper *ddr = arg; - eyedropper_draw_cursor_text_region(ddr->name_pos, ddr->name); -} - -static int datadropper_init(bContext *C, wmOperator *op) -{ - int index_dummy; - StructRNA *type; - - SpaceType *st; - ARegionType *art; - - st = BKE_spacetype_from_id(SPACE_VIEW3D); - art = BKE_regiontype_from_id(st, RGN_TYPE_WINDOW); - - DataDropper *ddr = MEM_callocN(sizeof(DataDropper), __func__); - - uiBut *but = UI_context_active_but_prop_get(C, &ddr->ptr, &ddr->prop, &index_dummy); - - if ((ddr->ptr.data == NULL) || (ddr->prop == NULL) || - (RNA_property_editable(&ddr->ptr, ddr->prop) == false) || - (RNA_property_type(ddr->prop) != PROP_POINTER)) { - MEM_freeN(ddr); - return false; - } - op->customdata = ddr; - - ddr->is_undo = UI_but_flag_is_set(but, UI_BUT_UNDO); - - ddr->cursor_area = CTX_wm_area(C); - ddr->art = art; - ddr->draw_handle_pixel = ED_region_draw_cb_activate( - art, datadropper_draw_cb, ddr, REGION_DRAW_POST_PIXEL); - - type = RNA_property_pointer_type(&ddr->ptr, ddr->prop); - ddr->idcode = RNA_type_to_ID_code(type); - BLI_assert(ddr->idcode != 0); - /* Note we can translate here (instead of on draw time), - * because this struct has very short lifetime. */ - ddr->idcode_name = TIP_(BKE_idtype_idcode_to_name(ddr->idcode)); - - const PointerRNA ptr = RNA_property_pointer_get(&ddr->ptr, ddr->prop); - ddr->init_id = ptr.owner_id; - - return true; -} - -static void datadropper_exit(bContext *C, wmOperator *op) -{ - wmWindow *win = CTX_wm_window(C); - - WM_cursor_modal_restore(win); - - if (op->customdata) { - DataDropper *ddr = (DataDropper *)op->customdata; - - if (ddr->art) { - ED_region_draw_cb_exit(ddr->art, ddr->draw_handle_pixel); - } - - MEM_freeN(op->customdata); - - op->customdata = NULL; - } - - WM_event_add_mousemove(win); -} - -/* *** datadropper id helper functions *** */ -/** - * \brief get the ID from the 3D view or outliner. - */ -static void datadropper_id_sample_pt( - bContext *C, wmWindow *win, ScrArea *area, DataDropper *ddr, const int m_xy[2], ID **r_id) -{ - wmWindow *win_prev = CTX_wm_window(C); - ScrArea *area_prev = CTX_wm_area(C); - ARegion *region_prev = CTX_wm_region(C); - - ddr->name[0] = '\0'; - - if (area) { - if (ELEM(area->spacetype, SPACE_VIEW3D, SPACE_OUTLINER)) { - ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, m_xy); - if (region) { - const int mval[2] = {m_xy[0] - region->winrct.xmin, m_xy[1] - region->winrct.ymin}; - Base *base; - - CTX_wm_window_set(C, win); - CTX_wm_area_set(C, area); - CTX_wm_region_set(C, region); - - /* Unfortunately it's necessary to always draw else we leave stale text. */ - ED_region_tag_redraw(region); - - if (area->spacetype == SPACE_VIEW3D) { - base = ED_view3d_give_base_under_cursor(C, mval); - } - else { - base = ED_outliner_give_base_under_cursor(C, mval); - } - - if (base) { - Object *ob = base->object; - ID *id = NULL; - if (ddr->idcode == ID_OB) { - id = (ID *)ob; - } - else if (ob->data) { - if (GS(((ID *)ob->data)->name) == ddr->idcode) { - id = (ID *)ob->data; - } - else { - BLI_snprintf( - ddr->name, sizeof(ddr->name), "Incompatible, expected a %s", ddr->idcode_name); - } - } - - PointerRNA idptr; - RNA_id_pointer_create(id, &idptr); - - if (id && RNA_property_pointer_poll(&ddr->ptr, ddr->prop, &idptr)) { - BLI_snprintf(ddr->name, sizeof(ddr->name), "%s: %s", ddr->idcode_name, id->name + 2); - *r_id = id; - } - - copy_v2_v2_int(ddr->name_pos, mval); - } - } - } - } - - CTX_wm_window_set(C, win_prev); - CTX_wm_area_set(C, area_prev); - CTX_wm_region_set(C, region_prev); -} - -/* sets the ID, returns success */ -static bool datadropper_id_set(bContext *C, DataDropper *ddr, ID *id) -{ - PointerRNA ptr_value; - - RNA_id_pointer_create(id, &ptr_value); - - RNA_property_pointer_set(&ddr->ptr, ddr->prop, ptr_value, NULL); - - RNA_property_update(C, &ddr->ptr, ddr->prop); - - ptr_value = RNA_property_pointer_get(&ddr->ptr, ddr->prop); - - return (ptr_value.owner_id == id); -} - -/* single point sample & set */ -static bool datadropper_id_sample(bContext *C, DataDropper *ddr, const int m_xy[2]) -{ - ID *id = NULL; - - int mval[2]; - wmWindow *win; - ScrArea *area; - datadropper_win_area_find(C, m_xy, mval, &win, &area); - - datadropper_id_sample_pt(C, win, area, ddr, mval, &id); - return datadropper_id_set(C, ddr, id); -} - -static void datadropper_cancel(bContext *C, wmOperator *op) -{ - DataDropper *ddr = op->customdata; - datadropper_id_set(C, ddr, ddr->init_id); - datadropper_exit(C, op); -} - -/* To switch the draw callback when region under mouse event changes */ -static void datadropper_set_draw_callback_region(ScrArea *area, DataDropper *ddr) -{ - if (area) { - /* If spacetype changed */ - if (area->spacetype != ddr->cursor_area->spacetype) { - /* Remove old callback */ - ED_region_draw_cb_exit(ddr->art, ddr->draw_handle_pixel); - - /* Redraw old area */ - ARegion *region = BKE_area_find_region_type(ddr->cursor_area, RGN_TYPE_WINDOW); - ED_region_tag_redraw(region); - - /* Set draw callback in new region */ - ARegionType *art = BKE_regiontype_from_id(area->type, RGN_TYPE_WINDOW); - - ddr->cursor_area = area; - ddr->art = art; - ddr->draw_handle_pixel = ED_region_draw_cb_activate( - art, datadropper_draw_cb, ddr, REGION_DRAW_POST_PIXEL); - } - } -} - -/* main modal status check */ -static int datadropper_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - DataDropper *ddr = (DataDropper *)op->customdata; - - /* handle modal keymap */ - if (event->type == EVT_MODAL_MAP) { - switch (event->val) { - case EYE_MODAL_CANCEL: - datadropper_cancel(C, op); - return OPERATOR_CANCELLED; - case EYE_MODAL_SAMPLE_CONFIRM: { - const bool is_undo = ddr->is_undo; - const bool success = datadropper_id_sample(C, ddr, event->xy); - datadropper_exit(C, op); - if (success) { - /* Could support finished & undo-skip. */ - return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED; - } - BKE_report(op->reports, RPT_WARNING, "Failed to set value"); - return OPERATOR_CANCELLED; - } - } - } - else if (event->type == MOUSEMOVE) { - ID *id = NULL; - - int mval[2]; - wmWindow *win; - ScrArea *area; - datadropper_win_area_find(C, event->xy, mval, &win, &area); - - /* Set the region for eyedropper cursor text drawing */ - datadropper_set_draw_callback_region(area, ddr); - - datadropper_id_sample_pt(C, win, area, ddr, mval, &id); - } - - return OPERATOR_RUNNING_MODAL; -} - -/* Modal Operator init */ -static int datadropper_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) -{ - /* init */ - if (datadropper_init(C, op)) { - wmWindow *win = CTX_wm_window(C); - /* Workaround for de-activating the button clearing the cursor, see T76794 */ - UI_context_active_but_clear(C, win, CTX_wm_region(C)); - WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); - - /* add temp handler */ - WM_event_add_modal_handler(C, op); - - return OPERATOR_RUNNING_MODAL; - } - return OPERATOR_CANCELLED; -} - -/* Repeat operator */ -static int datadropper_exec(bContext *C, wmOperator *op) -{ - /* init */ - if (datadropper_init(C, op)) { - /* cleanup */ - datadropper_exit(C, op); - - return OPERATOR_FINISHED; - } - return OPERATOR_CANCELLED; -} - -static bool datadropper_poll(bContext *C) -{ - PointerRNA ptr; - PropertyRNA *prop; - int index_dummy; - uiBut *but; - - /* data dropper only supports object data */ - if ((CTX_wm_window(C) != NULL) && - (but = UI_context_active_but_prop_get(C, &ptr, &prop, &index_dummy)) && - (but->type == UI_BTYPE_SEARCH_MENU) && (but->flag & UI_BUT_VALUE_CLEAR)) { - if (prop && RNA_property_type(prop) == PROP_POINTER) { - StructRNA *type = RNA_property_pointer_type(&ptr, prop); - const short idcode = RNA_type_to_ID_code(type); - if ((idcode == ID_OB) || OB_DATA_SUPPORT_ID(idcode)) { - return true; - } - } - } - - return false; -} - -void UI_OT_eyedropper_id(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Eyedropper Data-Block"; - ot->idname = "UI_OT_eyedropper_id"; - ot->description = "Sample a data-block from the 3D View to store in a property"; - - /* api callbacks */ - ot->invoke = datadropper_invoke; - ot->modal = datadropper_modal; - ot->cancel = datadropper_cancel; - ot->exec = datadropper_exec; - ot->poll = datadropper_poll; - - /* flags */ - ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; - - /* properties */ -} diff --git a/source/blender/editors/interface/interface_eyedropper_depth.c b/source/blender/editors/interface/interface_eyedropper_depth.c deleted file mode 100644 index 3c6f127582a..00000000000 --- a/source/blender/editors/interface/interface_eyedropper_depth.c +++ /dev/null @@ -1,381 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2009 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - * - * This file defines an eyedropper for picking 3D depth value (primary use is depth-of-field). - * - * Defines: - * - #UI_OT_eyedropper_depth - */ - -#include "MEM_guardedalloc.h" - -#include "DNA_camera_types.h" -#include "DNA_object_types.h" -#include "DNA_screen_types.h" -#include "DNA_space_types.h" -#include "DNA_view3d_types.h" - -#include "BLI_math_vector.h" -#include "BLI_string.h" - -#include "BKE_context.h" -#include "BKE_lib_id.h" -#include "BKE_screen.h" -#include "BKE_unit.h" - -#include "RNA_access.h" -#include "RNA_prototypes.h" - -#include "UI_interface.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "ED_screen.h" -#include "ED_space_api.h" -#include "ED_view3d.h" - -#include "interface_eyedropper_intern.h" -#include "interface_intern.h" - -/** - * \note #DepthDropper is only internal name to avoid confusion with other kinds of eye-droppers. - */ -typedef struct DepthDropper { - PointerRNA ptr; - PropertyRNA *prop; - bool is_undo; - - bool is_set; - float init_depth; /* For resetting on cancel. */ - - bool accum_start; /* Has mouse been pressed. */ - float accum_depth; - int accum_tot; - - ARegionType *art; - void *draw_handle_pixel; - int name_pos[2]; - char name[200]; -} DepthDropper; - -static void depthdropper_draw_cb(const struct bContext *UNUSED(C), - ARegion *UNUSED(region), - void *arg) -{ - DepthDropper *ddr = arg; - eyedropper_draw_cursor_text_region(ddr->name_pos, ddr->name); -} - -static int depthdropper_init(bContext *C, wmOperator *op) -{ - int index_dummy; - - SpaceType *st; - ARegionType *art; - - st = BKE_spacetype_from_id(SPACE_VIEW3D); - art = BKE_regiontype_from_id(st, RGN_TYPE_WINDOW); - - DepthDropper *ddr = MEM_callocN(sizeof(DepthDropper), __func__); - - uiBut *but = UI_context_active_but_prop_get(C, &ddr->ptr, &ddr->prop, &index_dummy); - - /* fallback to the active camera's dof */ - if (ddr->prop == NULL) { - RegionView3D *rv3d = CTX_wm_region_view3d(C); - if (rv3d && rv3d->persp == RV3D_CAMOB) { - View3D *v3d = CTX_wm_view3d(C); - if (v3d->camera && v3d->camera->data && - BKE_id_is_editable(CTX_data_main(C), v3d->camera->data)) { - Camera *camera = (Camera *)v3d->camera->data; - RNA_pointer_create(&camera->id, &RNA_CameraDOFSettings, &camera->dof, &ddr->ptr); - ddr->prop = RNA_struct_find_property(&ddr->ptr, "focus_distance"); - ddr->is_undo = true; - } - } - } - else { - ddr->is_undo = UI_but_flag_is_set(but, UI_BUT_UNDO); - } - - if ((ddr->ptr.data == NULL) || (ddr->prop == NULL) || - (RNA_property_editable(&ddr->ptr, ddr->prop) == false) || - (RNA_property_type(ddr->prop) != PROP_FLOAT)) { - MEM_freeN(ddr); - return false; - } - op->customdata = ddr; - - ddr->art = art; - ddr->draw_handle_pixel = ED_region_draw_cb_activate( - art, depthdropper_draw_cb, ddr, REGION_DRAW_POST_PIXEL); - ddr->init_depth = RNA_property_float_get(&ddr->ptr, ddr->prop); - - return true; -} - -static void depthdropper_exit(bContext *C, wmOperator *op) -{ - WM_cursor_modal_restore(CTX_wm_window(C)); - - if (op->customdata) { - DepthDropper *ddr = (DepthDropper *)op->customdata; - - if (ddr->art) { - ED_region_draw_cb_exit(ddr->art, ddr->draw_handle_pixel); - } - - MEM_freeN(op->customdata); - - op->customdata = NULL; - } -} - -/* *** depthdropper id helper functions *** */ -/** - * \brief get the ID from the screen. - */ -static void depthdropper_depth_sample_pt(bContext *C, - DepthDropper *ddr, - const int m_xy[2], - float *r_depth) -{ - /* we could use some clever */ - bScreen *screen = CTX_wm_screen(C); - ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, m_xy); - Scene *scene = CTX_data_scene(C); - - ScrArea *area_prev = CTX_wm_area(C); - ARegion *region_prev = CTX_wm_region(C); - - ddr->name[0] = '\0'; - - if (area) { - if (area->spacetype == SPACE_VIEW3D) { - ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, m_xy); - if (region) { - struct Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - View3D *v3d = area->spacedata.first; - RegionView3D *rv3d = region->regiondata; - /* weak, we could pass in some reference point */ - const float *view_co = v3d->camera ? v3d->camera->obmat[3] : rv3d->viewinv[3]; - const int mval[2] = {m_xy[0] - region->winrct.xmin, m_xy[1] - region->winrct.ymin}; - copy_v2_v2_int(ddr->name_pos, mval); - - float co[3]; - - CTX_wm_area_set(C, area); - CTX_wm_region_set(C, region); - - /* Unfortunately it's necessary to always draw otherwise we leave stale text. */ - ED_region_tag_redraw(region); - - view3d_operator_needs_opengl(C); - - if (ED_view3d_autodist(depsgraph, region, v3d, mval, co, true, NULL)) { - const float mval_center_fl[2] = {(float)region->winx / 2, (float)region->winy / 2}; - float co_align[3]; - - /* quick way to get view-center aligned point */ - ED_view3d_win_to_3d(v3d, region, co, mval_center_fl, co_align); - - *r_depth = len_v3v3(view_co, co_align); - - BKE_unit_value_as_string(ddr->name, - sizeof(ddr->name), - (double)*r_depth, - 4, - B_UNIT_LENGTH, - &scene->unit, - false); - } - else { - BLI_strncpy(ddr->name, "Nothing under cursor", sizeof(ddr->name)); - } - } - } - } - - CTX_wm_area_set(C, area_prev); - CTX_wm_region_set(C, region_prev); -} - -/* sets the sample depth RGB, maintaining A */ -static void depthdropper_depth_set(bContext *C, DepthDropper *ddr, const float depth) -{ - RNA_property_float_set(&ddr->ptr, ddr->prop, depth); - ddr->is_set = true; - RNA_property_update(C, &ddr->ptr, ddr->prop); -} - -/* set sample from accumulated values */ -static void depthdropper_depth_set_accum(bContext *C, DepthDropper *ddr) -{ - float depth = ddr->accum_depth; - if (ddr->accum_tot) { - depth /= (float)ddr->accum_tot; - } - depthdropper_depth_set(C, ddr, depth); -} - -/* single point sample & set */ -static void depthdropper_depth_sample(bContext *C, DepthDropper *ddr, const int m_xy[2]) -{ - float depth = -1.0f; - if (depth != -1.0f) { - depthdropper_depth_sample_pt(C, ddr, m_xy, &depth); - depthdropper_depth_set(C, ddr, depth); - } -} - -static void depthdropper_depth_sample_accum(bContext *C, DepthDropper *ddr, const int m_xy[2]) -{ - float depth = -1.0f; - depthdropper_depth_sample_pt(C, ddr, m_xy, &depth); - if (depth != -1.0f) { - ddr->accum_depth += depth; - ddr->accum_tot++; - } -} - -static void depthdropper_cancel(bContext *C, wmOperator *op) -{ - DepthDropper *ddr = op->customdata; - if (ddr->is_set) { - depthdropper_depth_set(C, ddr, ddr->init_depth); - } - depthdropper_exit(C, op); -} - -/* main modal status check */ -static int depthdropper_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - DepthDropper *ddr = (DepthDropper *)op->customdata; - - /* handle modal keymap */ - if (event->type == EVT_MODAL_MAP) { - switch (event->val) { - case EYE_MODAL_CANCEL: - depthdropper_cancel(C, op); - return OPERATOR_CANCELLED; - case EYE_MODAL_SAMPLE_CONFIRM: { - const bool is_undo = ddr->is_undo; - if (ddr->accum_tot == 0) { - depthdropper_depth_sample(C, ddr, event->xy); - } - else { - depthdropper_depth_set_accum(C, ddr); - } - depthdropper_exit(C, op); - /* Could support finished & undo-skip. */ - return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED; - } - case EYE_MODAL_SAMPLE_BEGIN: - /* enable accum and make first sample */ - ddr->accum_start = true; - depthdropper_depth_sample_accum(C, ddr, event->xy); - break; - case EYE_MODAL_SAMPLE_RESET: - ddr->accum_tot = 0; - ddr->accum_depth = 0.0f; - depthdropper_depth_sample_accum(C, ddr, event->xy); - depthdropper_depth_set_accum(C, ddr); - break; - } - } - else if (event->type == MOUSEMOVE) { - if (ddr->accum_start) { - /* button is pressed so keep sampling */ - depthdropper_depth_sample_accum(C, ddr, event->xy); - depthdropper_depth_set_accum(C, ddr); - } - } - - return OPERATOR_RUNNING_MODAL; -} - -/* Modal Operator init */ -static int depthdropper_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) -{ - /* init */ - if (depthdropper_init(C, op)) { - wmWindow *win = CTX_wm_window(C); - /* Workaround for de-activating the button clearing the cursor, see T76794 */ - UI_context_active_but_clear(C, win, CTX_wm_region(C)); - WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); - - /* add temp handler */ - WM_event_add_modal_handler(C, op); - - return OPERATOR_RUNNING_MODAL; - } - return OPERATOR_CANCELLED; -} - -/* Repeat operator */ -static int depthdropper_exec(bContext *C, wmOperator *op) -{ - /* init */ - if (depthdropper_init(C, op)) { - /* cleanup */ - depthdropper_exit(C, op); - - return OPERATOR_FINISHED; - } - return OPERATOR_CANCELLED; -} - -static bool depthdropper_poll(bContext *C) -{ - PointerRNA ptr; - PropertyRNA *prop; - int index_dummy; - uiBut *but; - - /* check if there's an active button taking depth value */ - if ((CTX_wm_window(C) != NULL) && - (but = UI_context_active_but_prop_get(C, &ptr, &prop, &index_dummy)) && - (but->type == UI_BTYPE_NUM) && (prop != NULL)) { - if ((RNA_property_type(prop) == PROP_FLOAT) && - (RNA_property_subtype(prop) & PROP_UNIT_LENGTH) && - (RNA_property_array_check(prop) == false)) { - return true; - } - } - else { - RegionView3D *rv3d = CTX_wm_region_view3d(C); - if (rv3d && rv3d->persp == RV3D_CAMOB) { - View3D *v3d = CTX_wm_view3d(C); - if (v3d->camera && v3d->camera->data && - BKE_id_is_editable(CTX_data_main(C), v3d->camera->data)) { - return true; - } - } - } - - return false; -} - -void UI_OT_eyedropper_depth(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Eyedropper Depth"; - ot->idname = "UI_OT_eyedropper_depth"; - ot->description = "Sample depth from the 3D view"; - - /* api callbacks */ - ot->invoke = depthdropper_invoke; - ot->modal = depthdropper_modal; - ot->cancel = depthdropper_cancel; - ot->exec = depthdropper_exec; - ot->poll = depthdropper_poll; - - /* flags */ - ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; - - /* properties */ -} diff --git a/source/blender/editors/interface/interface_eyedropper_driver.c b/source/blender/editors/interface/interface_eyedropper_driver.c deleted file mode 100644 index 0cacb55c60c..00000000000 --- a/source/blender/editors/interface/interface_eyedropper_driver.c +++ /dev/null @@ -1,220 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2009 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - * - * Eyedropper (Animation Driver Targets). - * - * Defines: - * - #UI_OT_eyedropper_driver - */ - -#include "MEM_guardedalloc.h" - -#include "DNA_anim_types.h" -#include "DNA_object_types.h" -#include "DNA_screen_types.h" - -#include "BKE_animsys.h" -#include "BKE_context.h" - -#include "DEG_depsgraph.h" -#include "DEG_depsgraph_build.h" - -#include "RNA_access.h" -#include "RNA_define.h" - -#include "UI_interface.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "ED_keyframing.h" - -#include "interface_eyedropper_intern.h" -#include "interface_intern.h" - -typedef struct DriverDropper { - /* Destination property (i.e. where we'll add a driver) */ - PointerRNA ptr; - PropertyRNA *prop; - int index; - bool is_undo; - - /* TODO: new target? */ -} DriverDropper; - -static bool driverdropper_init(bContext *C, wmOperator *op) -{ - DriverDropper *ddr = MEM_callocN(sizeof(DriverDropper), __func__); - - uiBut *but = UI_context_active_but_prop_get(C, &ddr->ptr, &ddr->prop, &ddr->index); - - if ((ddr->ptr.data == NULL) || (ddr->prop == NULL) || - (RNA_property_editable(&ddr->ptr, ddr->prop) == false) || - (RNA_property_animateable(&ddr->ptr, ddr->prop) == false) || (but->flag & UI_BUT_DRIVEN)) { - MEM_freeN(ddr); - return false; - } - op->customdata = ddr; - - ddr->is_undo = UI_but_flag_is_set(but, UI_BUT_UNDO); - - return true; -} - -static void driverdropper_exit(bContext *C, wmOperator *op) -{ - WM_cursor_modal_restore(CTX_wm_window(C)); - - MEM_SAFE_FREE(op->customdata); -} - -static void driverdropper_sample(bContext *C, wmOperator *op, const wmEvent *event) -{ - DriverDropper *ddr = (DriverDropper *)op->customdata; - uiBut *but = eyedropper_get_property_button_under_mouse(C, event); - - const short mapping_type = RNA_enum_get(op->ptr, "mapping_type"); - const short flag = 0; - - /* we can only add a driver if we know what RNA property it corresponds to */ - if (but == NULL) { - return; - } - /* Get paths for src... */ - PointerRNA *target_ptr = &but->rnapoin; - PropertyRNA *target_prop = but->rnaprop; - const int target_index = but->rnaindex; - - char *target_path = RNA_path_from_ID_to_property(target_ptr, target_prop); - - /* ... and destination */ - char *dst_path = RNA_path_from_ID_to_property(&ddr->ptr, ddr->prop); - - /* Now create driver(s) */ - if (target_path && dst_path) { - int success = ANIM_add_driver_with_target(op->reports, - ddr->ptr.owner_id, - dst_path, - ddr->index, - target_ptr->owner_id, - target_path, - target_index, - flag, - DRIVER_TYPE_PYTHON, - mapping_type); - - if (success) { - /* send updates */ - UI_context_update_anim_flag(C); - DEG_relations_tag_update(CTX_data_main(C)); - DEG_id_tag_update(ddr->ptr.owner_id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_ANIMATION | ND_FCURVES_ORDER, NULL); /* XXX */ - } - } - - /* cleanup */ - if (target_path) { - MEM_freeN(target_path); - } - if (dst_path) { - MEM_freeN(dst_path); - } -} - -static void driverdropper_cancel(bContext *C, wmOperator *op) -{ - driverdropper_exit(C, op); -} - -/* main modal status check */ -static int driverdropper_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - DriverDropper *ddr = op->customdata; - - /* handle modal keymap */ - if (event->type == EVT_MODAL_MAP) { - switch (event->val) { - case EYE_MODAL_CANCEL: { - driverdropper_cancel(C, op); - return OPERATOR_CANCELLED; - } - case EYE_MODAL_SAMPLE_CONFIRM: { - const bool is_undo = ddr->is_undo; - driverdropper_sample(C, op, event); - driverdropper_exit(C, op); - /* Could support finished & undo-skip. */ - return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED; - } - } - } - - return OPERATOR_RUNNING_MODAL; -} - -/* Modal Operator init */ -static int driverdropper_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) -{ - /* init */ - if (driverdropper_init(C, op)) { - wmWindow *win = CTX_wm_window(C); - /* Workaround for de-activating the button clearing the cursor, see T76794 */ - UI_context_active_but_clear(C, win, CTX_wm_region(C)); - WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); - - /* add temp handler */ - WM_event_add_modal_handler(C, op); - - return OPERATOR_RUNNING_MODAL; - } - return OPERATOR_CANCELLED; -} - -/* Repeat operator */ -static int driverdropper_exec(bContext *C, wmOperator *op) -{ - /* init */ - if (driverdropper_init(C, op)) { - /* cleanup */ - driverdropper_exit(C, op); - - return OPERATOR_FINISHED; - } - return OPERATOR_CANCELLED; -} - -static bool driverdropper_poll(bContext *C) -{ - if (!CTX_wm_window(C)) { - return false; - } - return true; -} - -void UI_OT_eyedropper_driver(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Eyedropper Driver"; - ot->idname = "UI_OT_eyedropper_driver"; - ot->description = "Pick a property to use as a driver target"; - - /* api callbacks */ - ot->invoke = driverdropper_invoke; - ot->modal = driverdropper_modal; - ot->cancel = driverdropper_cancel; - ot->exec = driverdropper_exec; - ot->poll = driverdropper_poll; - - /* flags */ - ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; - - /* properties */ - RNA_def_enum(ot->srna, - "mapping_type", - prop_driver_create_mapping_types, - 0, - "Mapping Type", - "Method used to match target and driven properties"); -} diff --git a/source/blender/editors/interface/interface_eyedropper_gpencil_color.c b/source/blender/editors/interface/interface_eyedropper_gpencil_color.c deleted file mode 100644 index f3c70e6a96a..00000000000 --- a/source/blender/editors/interface/interface_eyedropper_gpencil_color.c +++ /dev/null @@ -1,373 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2009 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - * - * Eyedropper (RGB Color) - * - * Defines: - * - #UI_OT_eyedropper_gpencil_color - */ - -#include "MEM_guardedalloc.h" - -#include "BLI_listbase.h" -#include "BLI_string.h" - -#include "BLT_translation.h" - -#include "DNA_gpencil_types.h" -#include "DNA_material_types.h" -#include "DNA_space_types.h" - -#include "BKE_context.h" -#include "BKE_gpencil.h" -#include "BKE_lib_id.h" -#include "BKE_main.h" -#include "BKE_material.h" -#include "BKE_paint.h" -#include "BKE_report.h" - -#include "UI_interface.h" - -#include "IMB_colormanagement.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "RNA_access.h" -#include "RNA_define.h" - -#include "ED_gpencil.h" -#include "ED_screen.h" -#include "ED_undo.h" - -#include "DEG_depsgraph.h" -#include "DEG_depsgraph_build.h" - -#include "interface_eyedropper_intern.h" -#include "interface_intern.h" - -typedef struct EyedropperGPencil { - struct ColorManagedDisplay *display; - /** color under cursor RGB */ - float color[3]; - /** Mode */ - int mode; -} EyedropperGPencil; - -/* Helper: Draw status message while the user is running the operator */ -static void eyedropper_gpencil_status_indicators(bContext *C) -{ - char msg_str[UI_MAX_DRAW_STR]; - BLI_strncpy( - msg_str, TIP_("LMB: Stroke - Shift: Fill - Shift+Ctrl: Stroke + Fill"), UI_MAX_DRAW_STR); - - ED_workspace_status_text(C, msg_str); -} - -/* Initialize. */ -static bool eyedropper_gpencil_init(bContext *C, wmOperator *op) -{ - EyedropperGPencil *eye = MEM_callocN(sizeof(EyedropperGPencil), __func__); - - op->customdata = eye; - Scene *scene = CTX_data_scene(C); - - const char *display_device; - display_device = scene->display_settings.display_device; - eye->display = IMB_colormanagement_display_get_named(display_device); - - eye->mode = RNA_enum_get(op->ptr, "mode"); - return true; -} - -/* Exit and free memory. */ -static void eyedropper_gpencil_exit(bContext *C, wmOperator *op) -{ - /* Clear status message area. */ - ED_workspace_status_text(C, NULL); - - MEM_SAFE_FREE(op->customdata); -} - -static void eyedropper_add_material(bContext *C, - const float col_conv[4], - const bool only_stroke, - const bool only_fill, - const bool both) -{ - Main *bmain = CTX_data_main(C); - Object *ob = CTX_data_active_object(C); - Material *ma = NULL; - - bool found = false; - - /* Look for a similar material in grease pencil slots. */ - short *totcol = BKE_object_material_len_p(ob); - for (short i = 0; i < *totcol; i++) { - ma = BKE_object_material_get(ob, i + 1); - if (ma == NULL) { - continue; - } - - MaterialGPencilStyle *gp_style = ma->gp_style; - if (gp_style != NULL) { - /* Check stroke color. */ - bool found_stroke = compare_v3v3(gp_style->stroke_rgba, col_conv, 0.01f) && - (gp_style->flag & GP_MATERIAL_STROKE_SHOW); - /* Check fill color. */ - bool found_fill = compare_v3v3(gp_style->fill_rgba, col_conv, 0.01f) && - (gp_style->flag & GP_MATERIAL_FILL_SHOW); - - if ((only_stroke) && (found_stroke) && ((gp_style->flag & GP_MATERIAL_FILL_SHOW) == 0)) { - found = true; - } - else if ((only_fill) && (found_fill) && ((gp_style->flag & GP_MATERIAL_STROKE_SHOW) == 0)) { - found = true; - } - else if ((both) && (found_stroke) && (found_fill)) { - found = true; - } - - /* Found existing material. */ - if (found) { - ob->actcol = i + 1; - WM_main_add_notifier(NC_MATERIAL | ND_SHADING_LINKS, NULL); - WM_main_add_notifier(NC_SPACE | ND_SPACE_VIEW3D, NULL); - return; - } - } - } - - /* If material was not found add a new material with stroke and/or fill color - * depending of the secondary key (LMB: Stroke, Shift: Fill, Shift+Ctrl: Stroke/Fill) - */ - int idx; - Material *ma_new = BKE_gpencil_object_material_new(bmain, ob, "Material", &idx); - WM_main_add_notifier(NC_OBJECT | ND_OB_SHADING, &ob->id); - WM_main_add_notifier(NC_MATERIAL | ND_SHADING_LINKS, NULL); - DEG_relations_tag_update(bmain); - - BLI_assert(ma_new != NULL); - - MaterialGPencilStyle *gp_style_new = ma_new->gp_style; - BLI_assert(gp_style_new != NULL); - - /* Only create Stroke (default option). */ - if (only_stroke) { - /* Stroke color. */ - gp_style_new->flag |= GP_MATERIAL_STROKE_SHOW; - gp_style_new->flag &= ~GP_MATERIAL_FILL_SHOW; - copy_v3_v3(gp_style_new->stroke_rgba, col_conv); - zero_v4(gp_style_new->fill_rgba); - } - /* Fill Only. */ - else if (only_fill) { - /* Fill color. */ - gp_style_new->flag &= ~GP_MATERIAL_STROKE_SHOW; - gp_style_new->flag |= GP_MATERIAL_FILL_SHOW; - zero_v4(gp_style_new->stroke_rgba); - copy_v3_v3(gp_style_new->fill_rgba, col_conv); - } - /* Stroke and Fill. */ - else if (both) { - gp_style_new->flag |= GP_MATERIAL_STROKE_SHOW | GP_MATERIAL_FILL_SHOW; - copy_v3_v3(gp_style_new->stroke_rgba, col_conv); - copy_v3_v3(gp_style_new->fill_rgba, col_conv); - } - /* Push undo for new created material. */ - ED_undo_push(C, "Add Grease Pencil Material"); -} - -/* Create a new palette color and palette if needed. */ -static void eyedropper_add_palette_color(bContext *C, const float col_conv[4]) -{ - Main *bmain = CTX_data_main(C); - Scene *scene = CTX_data_scene(C); - ToolSettings *ts = scene->toolsettings; - GpPaint *gp_paint = ts->gp_paint; - GpVertexPaint *gp_vertexpaint = ts->gp_vertexpaint; - Paint *paint = &gp_paint->paint; - Paint *vertexpaint = &gp_vertexpaint->paint; - - /* Check for Palette in Draw and Vertex Paint Mode. */ - if (paint->palette == NULL) { - Palette *palette = BKE_palette_add(bmain, "Grease Pencil"); - id_us_min(&palette->id); - - BKE_paint_palette_set(paint, palette); - - if (vertexpaint->palette == NULL) { - BKE_paint_palette_set(vertexpaint, palette); - } - } - /* Check if the color exist already. */ - Palette *palette = paint->palette; - LISTBASE_FOREACH (PaletteColor *, palcolor, &palette->colors) { - if (compare_v3v3(palcolor->rgb, col_conv, 0.01f)) { - return; - } - } - - /* Create Colors. */ - PaletteColor *palcol = BKE_palette_color_add(palette); - if (palcol) { - copy_v3_v3(palcol->rgb, col_conv); - } -} - -/* Set the material or the palette color. */ -static void eyedropper_gpencil_color_set(bContext *C, const wmEvent *event, EyedropperGPencil *eye) -{ - - const bool only_stroke = (event->modifier & (KM_CTRL | KM_SHIFT)) == 0; - const bool only_fill = ((event->modifier & KM_CTRL) == 0 && (event->modifier & KM_SHIFT)); - const bool both = ((event->modifier & KM_CTRL) && (event->modifier & KM_SHIFT)); - - float col_conv[4]; - - /* Convert from linear rgb space to display space because grease pencil colors are in display - * space, and this conversion is needed to undo the conversion to linear performed by - * eyedropper_color_sample_fl. */ - if (eye->display) { - copy_v3_v3(col_conv, eye->color); - IMB_colormanagement_scene_linear_to_display_v3(col_conv, eye->display); - } - else { - copy_v3_v3(col_conv, eye->color); - } - - /* Add material or Palette color. */ - if (eye->mode == 0) { - eyedropper_add_material(C, col_conv, only_stroke, only_fill, both); - } - else { - eyedropper_add_palette_color(C, col_conv); - } -} - -/* Sample the color below cursor. */ -static void eyedropper_gpencil_color_sample(bContext *C, EyedropperGPencil *eye, const int m_xy[2]) -{ - eyedropper_color_sample_fl(C, m_xy, eye->color); -} - -/* Cancel operator. */ -static void eyedropper_gpencil_cancel(bContext *C, wmOperator *op) -{ - eyedropper_gpencil_exit(C, op); -} - -/* Main modal status check. */ -static int eyedropper_gpencil_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - EyedropperGPencil *eye = (EyedropperGPencil *)op->customdata; - /* Handle modal keymap */ - switch (event->type) { - case EVT_MODAL_MAP: { - switch (event->val) { - case EYE_MODAL_SAMPLE_BEGIN: { - return OPERATOR_RUNNING_MODAL; - } - case EYE_MODAL_CANCEL: { - eyedropper_gpencil_cancel(C, op); - return OPERATOR_CANCELLED; - } - case EYE_MODAL_SAMPLE_CONFIRM: { - eyedropper_gpencil_color_sample(C, eye, event->xy); - - /* Create material. */ - eyedropper_gpencil_color_set(C, event, eye); - WM_main_add_notifier(NC_GPENCIL | ND_DATA | NA_EDITED, NULL); - - eyedropper_gpencil_exit(C, op); - return OPERATOR_FINISHED; - } - default: { - break; - } - } - break; - } - case MOUSEMOVE: - case INBETWEEN_MOUSEMOVE: { - eyedropper_gpencil_color_sample(C, eye, event->xy); - break; - } - default: { - break; - } - } - - return OPERATOR_RUNNING_MODAL; -} - -/* Modal Operator init */ -static int eyedropper_gpencil_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) -{ - /* Init. */ - if (eyedropper_gpencil_init(C, op)) { - /* Add modal temp handler. */ - WM_event_add_modal_handler(C, op); - /* Status message. */ - eyedropper_gpencil_status_indicators(C); - - return OPERATOR_RUNNING_MODAL; - } - return OPERATOR_PASS_THROUGH; -} - -/* Repeat operator */ -static int eyedropper_gpencil_exec(bContext *C, wmOperator *op) -{ - /* init */ - if (eyedropper_gpencil_init(C, op)) { - - /* cleanup */ - eyedropper_gpencil_exit(C, op); - - return OPERATOR_FINISHED; - } - return OPERATOR_PASS_THROUGH; -} - -static bool eyedropper_gpencil_poll(bContext *C) -{ - /* Only valid if the current active object is grease pencil. */ - Object *obact = CTX_data_active_object(C); - if ((obact == NULL) || (obact->type != OB_GPENCIL)) { - return false; - } - - /* Test we have a window below. */ - return (CTX_wm_window(C) != NULL); -} - -void UI_OT_eyedropper_gpencil_color(wmOperatorType *ot) -{ - static const EnumPropertyItem items_mode[] = { - {0, "MATERIAL", 0, "Material", ""}, - {1, "PALETTE", 0, "Palette", ""}, - {0, NULL, 0, NULL, NULL}, - }; - - /* identifiers */ - ot->name = "Grease Pencil Eyedropper"; - ot->idname = "UI_OT_eyedropper_gpencil_color"; - ot->description = "Sample a color from the Blender Window and create Grease Pencil material"; - - /* api callbacks */ - ot->invoke = eyedropper_gpencil_invoke; - ot->modal = eyedropper_gpencil_modal; - ot->cancel = eyedropper_gpencil_cancel; - ot->exec = eyedropper_gpencil_exec; - ot->poll = eyedropper_gpencil_poll; - - /* flags */ - ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING; - - /* properties */ - ot->prop = RNA_def_enum(ot->srna, "mode", items_mode, 0, "Mode", ""); -} diff --git a/source/blender/editors/interface/interface_eyedropper_intern.h b/source/blender/editors/interface/interface_eyedropper_intern.h deleted file mode 100644 index 76316a85807..00000000000 --- a/source/blender/editors/interface/interface_eyedropper_intern.h +++ /dev/null @@ -1,57 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup edinterface - * - * Share between interface_eyedropper_*.c files. - */ - -#pragma once - -/* interface_eyedropper.c */ - -void eyedropper_draw_cursor_text_window(const struct wmWindow *window, const char *name); -void eyedropper_draw_cursor_text_region(const int xy[2], const char *name); -/** - * Utility to retrieve a button representing a RNA property that is currently under the cursor. - * - * This is to be used by any eyedroppers which fetch properties (e.g. UI_OT_eyedropper_driver). - * Especially during modal operations (e.g. as with the eyedroppers), context cannot be relied - * upon to provide this information, as it is not updated until the operator finishes. - * - * \return A button under the mouse which relates to some RNA Property, or NULL - */ -uiBut *eyedropper_get_property_button_under_mouse(bContext *C, const wmEvent *event); -void datadropper_win_area_find(const struct bContext *C, - const int mval[2], - int r_mval[2], - struct wmWindow **r_win, - struct ScrArea **r_area); - -/* interface_eyedropper_color.c (expose for color-band picker) */ - -/** - * \brief get the color from the screen. - * - * Special check for image or nodes where we MAY have HDR pixels which don't display. - * - * \note Exposed by 'interface_eyedropper_intern.h' for use with color band picking. - */ -void eyedropper_color_sample_fl(bContext *C, const int m_xy[2], float r_col[3]); - -/* Used for most eye-dropper operators. */ -enum { - EYE_MODAL_CANCEL = 1, - EYE_MODAL_SAMPLE_CONFIRM, - EYE_MODAL_SAMPLE_BEGIN, - EYE_MODAL_SAMPLE_RESET, -}; - -/* Color-band point sample. */ -enum { - EYE_MODAL_POINT_CANCEL = 1, - EYE_MODAL_POINT_SAMPLE, - EYE_MODAL_POINT_CONFIRM, - EYE_MODAL_POINT_RESET, - EYE_MODAL_POINT_REMOVE_LAST, -}; -- cgit v1.2.3 From 28985ccc05ff15dd22184863b8c57a0851fc9614 Mon Sep 17 00:00:00 2001 From: Pratik Borhade Date: Tue, 19 Jul 2022 14:20:44 -0500 Subject: Fix T99583: Missing update for option in particle edit mode Missing Ui refresh when property is accessed through shortcut. Use RNA_def_property_update to solve this. Differential Revision: https://developer.blender.org/D15431 --- source/blender/makesrna/intern/rna_sculpt_paint.c | 1 + 1 file changed, 1 insertion(+) diff --git a/source/blender/makesrna/intern/rna_sculpt_paint.c b/source/blender/makesrna/intern/rna_sculpt_paint.c index 3de9e632ff6..2e1fa8db7fe 100644 --- a/source/blender/makesrna/intern/rna_sculpt_paint.c +++ b/source/blender/makesrna/intern/rna_sculpt_paint.c @@ -1303,6 +1303,7 @@ static void rna_def_particle_edit(BlenderRNA *brna) RNA_def_property_boolean_sdna(prop, NULL, "flag", PE_INTERPOLATE_ADDED); RNA_def_property_ui_text( prop, "Interpolate", "Interpolate new particles from the existing ones"); + RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); prop = RNA_def_property(srna, "default_key_count", PROP_INT, PROP_NONE); RNA_def_property_int_sdna(prop, NULL, "totaddkey"); -- cgit v1.2.3 From e9f82d3dc7eebadcc52fdc43858d060c3a8214b2 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 19 Jul 2022 18:01:04 -0500 Subject: Curves: Remove redundant custom data pointers These mutable pointers present problems with ownership in relation to proper copy-on-write for attributes. The simplest solution is to just remove them and retrieve the layers from `CustomData` when they are needed. This also removes the complexity and redundancy of having to update the pointers as the curves change. A similar change will apply to meshes and point clouds. One downside of this change is that it makes random access with RNA slower. However, it's simple to just use the RNA attribute API instead, which is unaffected. In this patch I updated Cycles to do that. With the future attribute CoW changes, this generic approach makes sense because Cycles can just request ownership of the existing arrays. Differential Revision: https://developer.blender.org/D15486 --- intern/cycles/blender/curves.cpp | 37 +++++++--- source/blender/blenkernel/BKE_curves.hh | 2 - source/blender/blenkernel/intern/curves.cc | 10 --- .../blender/blenkernel/intern/curves_geometry.cc | 34 ++-------- .../blenkernel/intern/geometry_component_curves.cc | 10 +-- source/blender/geometry/intern/resample_curves.cc | 2 - source/blender/makesdna/DNA_curves_types.h | 17 ----- source/blender/makesrna/intern/rna_curves.c | 78 +++++++++++++++------- 8 files changed, 93 insertions(+), 97 deletions(-) diff --git a/intern/cycles/blender/curves.cpp b/intern/cycles/blender/curves.cpp index 263a5fc0e02..c4154bce022 100644 --- a/intern/cycles/blender/curves.cpp +++ b/intern/cycles/blender/curves.cpp @@ -630,6 +630,25 @@ static std::optional find_curves_radius_attribute(BL::Curves return std::nullopt; } +static BL::FloatVectorAttribute find_curves_position_attribute(BL::Curves b_curves) +{ + for (BL::Attribute &b_attribute : b_curves.attributes) { + if (b_attribute.name() != "position") { + continue; + } + if (b_attribute.domain() != BL::Attribute::domain_POINT) { + continue; + } + if (b_attribute.data_type() != BL::Attribute::data_type_FLOAT_VECTOR) { + continue; + } + return BL::FloatVectorAttribute{b_attribute}; + } + /* The position attribute must exist. */ + assert(false); + return BL::FloatVectorAttribute{b_curves.attributes[0]}; +} + template static void fill_generic_attribute(BL::Curves &b_curves, TypeInCycles *data, @@ -793,16 +812,16 @@ static void attr_create_generic(Scene *scene, } } -static float4 hair_point_as_float4(BL::Curves b_curves, +static float4 hair_point_as_float4(BL::FloatVectorAttribute b_attr_position, std::optional b_attr_radius, const int index) { - float4 mP = float3_to_float4(get_float3(b_curves.position_data[index].vector())); + float4 mP = float3_to_float4(get_float3(b_attr_position.data[index].vector())); mP.w = b_attr_radius ? b_attr_radius->data[index].value() : 0.0f; return mP; } -static float4 interpolate_hair_points(BL::Curves b_curves, +static float4 interpolate_hair_points(BL::FloatVectorAttribute b_attr_position, std::optional b_attr_radius, const int first_point_index, const int num_points, @@ -812,8 +831,8 @@ static float4 interpolate_hair_points(BL::Curves b_curves, const int point_a = clamp((int)curve_t, 0, num_points - 1); const int point_b = min(point_a + 1, num_points - 1); const float t = curve_t - (float)point_a; - return lerp(hair_point_as_float4(b_curves, b_attr_radius, first_point_index + point_a), - hair_point_as_float4(b_curves, b_attr_radius, first_point_index + point_b), + return lerp(hair_point_as_float4(b_attr_position, b_attr_radius, first_point_index + point_a), + hair_point_as_float4(b_attr_position, b_attr_radius, first_point_index + point_b), t); } @@ -846,6 +865,7 @@ static void export_hair_curves(Scene *scene, hair->reserve_curves(num_curves, num_keys); + BL::FloatVectorAttribute b_attr_position = find_curves_position_attribute(b_curves); std::optional b_attr_radius = find_curves_radius_attribute(b_curves); /* Export curves and points. */ @@ -864,7 +884,7 @@ static void export_hair_curves(Scene *scene, /* Position and radius. */ for (int i = 0; i < num_points; i++) { - const float3 co = get_float3(b_curves.position_data[first_point_index + i].vector()); + const float3 co = get_float3(b_attr_position.data[first_point_index + i].vector()); const float radius = b_attr_radius ? b_attr_radius->data[first_point_index + i].value() : 0.005f; hair->add_curve_key(co, radius); @@ -921,6 +941,7 @@ static void export_hair_curves_motion(Hair *hair, BL::Curves b_curves, int motio int num_motion_keys = 0; int curve_index = 0; + BL::FloatVectorAttribute b_attr_position = find_curves_position_attribute(b_curves); std::optional b_attr_radius = find_curves_radius_attribute(b_curves); for (int i = 0; i < num_curves; i++) { @@ -936,7 +957,7 @@ static void export_hair_curves_motion(Hair *hair, BL::Curves b_curves, int motio int point_index = first_point_index + i; if (point_index < num_keys) { - mP[num_motion_keys] = hair_point_as_float4(b_curves, b_attr_radius, point_index); + mP[num_motion_keys] = hair_point_as_float4(b_attr_position, b_attr_radius, point_index); num_motion_keys++; if (!have_motion) { @@ -956,7 +977,7 @@ static void export_hair_curves_motion(Hair *hair, BL::Curves b_curves, int motio for (int i = 0; i < curve.num_keys; i++) { const float step = i * step_size; mP[num_motion_keys] = interpolate_hair_points( - b_curves, b_attr_radius, first_point_index, num_points, step); + b_attr_position, b_attr_radius, first_point_index, num_points, step); num_motion_keys++; } have_motion = true; diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh index 25a912b8825..68c90a45031 100644 --- a/source/blender/blenkernel/BKE_curves.hh +++ b/source/blender/blenkernel/BKE_curves.hh @@ -385,8 +385,6 @@ class CurvesGeometry : public ::CurvesGeometry { void calculate_bezier_auto_handles(); - void update_customdata_pointers(); - void remove_points(IndexMask points_to_delete); void remove_curves(IndexMask curves_to_delete); diff --git a/source/blender/blenkernel/intern/curves.cc b/source/blender/blenkernel/intern/curves.cc index 7e9f994313b..5684a2e5b07 100644 --- a/source/blender/blenkernel/intern/curves.cc +++ b/source/blender/blenkernel/intern/curves.cc @@ -53,8 +53,6 @@ using blender::Vector; static const char *ATTR_POSITION = "position"; -static void update_custom_data_pointers(Curves &curves); - static void curves_init_data(ID *id) { Curves *curves = (Curves *)id; @@ -97,8 +95,6 @@ static void curves_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, dst.runtime->type_counts = src.runtime->type_counts; - dst.update_customdata_pointers(); - curves_dst->batch_cache = nullptr; } @@ -170,7 +166,6 @@ static void curves_blend_read_data(BlendDataReader *reader, ID *id) /* Geometry */ CustomData_blend_read(reader, &curves->geometry.point_data, curves->geometry.point_num); CustomData_blend_read(reader, &curves->geometry.curve_data, curves->geometry.curve_num); - update_custom_data_pointers(*curves); BLO_read_int32_array(reader, curves->geometry.curve_num + 1, &curves->geometry.curve_offsets); @@ -233,11 +228,6 @@ IDTypeInfo IDType_ID_CV = { /*lib_override_apply_post */ nullptr, }; -static void update_custom_data_pointers(Curves &curves) -{ - blender::bke::CurvesGeometry::wrap(curves.geometry).update_customdata_pointers(); -} - void *BKE_curves_add(Main *bmain, const char *name) { Curves *curves = static_cast(BKE_id_new(bmain, ID_CV, name)); diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc index 8d955a46275..7fc660cfbfc 100644 --- a/source/blender/blenkernel/intern/curves_geometry.cc +++ b/source/blender/blenkernel/intern/curves_geometry.cc @@ -68,8 +68,6 @@ CurvesGeometry::CurvesGeometry(const int point_num, const int curve_num) #endif this->offsets_for_write().first() = 0; - this->update_customdata_pointers(); - this->runtime = MEM_new(__func__); /* Fill the type counts with the default so they're in a valid state. */ this->runtime->type_counts[CURVE_TYPE_CATMULL_ROM] = curve_num; @@ -95,8 +93,6 @@ static void copy_curves_geometry(CurvesGeometry &dst, const CurvesGeometry &src) /* Though type counts are a cache, they must be copied because they are calculated eagerly. */ dst.runtime->type_counts = src.runtime->type_counts; - - dst.update_customdata_pointers(); } CurvesGeometry::CurvesGeometry(const CurvesGeometry &other) @@ -130,9 +126,6 @@ static void move_curves_geometry(CurvesGeometry &dst, CurvesGeometry &src) MEM_SAFE_FREE(src.curve_offsets); std::swap(dst.runtime, src.runtime); - - src.update_customdata_pointers(); - dst.update_customdata_pointers(); } CurvesGeometry::CurvesGeometry(CurvesGeometry &&other) @@ -306,13 +299,11 @@ void CurvesGeometry::update_curve_types() Span CurvesGeometry::positions() const { - return {(const float3 *)this->position, this->point_num}; + return get_span_attribute(*this, ATTR_DOMAIN_POINT, ATTR_POSITION); } MutableSpan CurvesGeometry::positions_for_write() { - this->position = (float(*)[3])CustomData_duplicate_referenced_layer_named( - &this->point_data, CD_PROP_FLOAT3, ATTR_POSITION.c_str(), this->point_num); - return {(float3 *)this->position, this->point_num}; + return get_mutable_attribute(*this, ATTR_DOMAIN_POINT, ATTR_POSITION); } Span CurvesGeometry::offsets() const @@ -961,7 +952,6 @@ void CurvesGeometry::resize(const int points_num, const int curves_num) this->curve_offsets = (int *)MEM_reallocN(this->curve_offsets, sizeof(int) * (curves_num + 1)); } this->tag_topology_changed(); - this->update_customdata_pointers(); } void CurvesGeometry::tag_positions_changed() @@ -1060,10 +1050,11 @@ void CurvesGeometry::transform(const float4x4 &matrix) static std::optional> curves_bounds(const CurvesGeometry &curves) { - Span positions = curves.positions(); - if (curves.radius) { - Span radii{curves.radius, curves.points_num()}; - return bounds::min_max_with_radii(positions, radii); + const Span positions = curves.positions(); + const VArray radii = curves.attributes().lookup_or_default( + ATTR_RADIUS, ATTR_DOMAIN_POINT, 0.0f); + if (!(radii.is_single() && radii.get_internal_single() == 0.0f)) { + return bounds::min_max_with_radii(positions, radii.get_internal_span()); } return bounds::min_max(positions); } @@ -1079,16 +1070,6 @@ bool CurvesGeometry::bounds_min_max(float3 &min, float3 &max) const return true; } -void CurvesGeometry::update_customdata_pointers() -{ - this->position = (float(*)[3])CustomData_get_layer_named( - &this->point_data, CD_PROP_FLOAT3, ATTR_POSITION.c_str()); - this->radius = (float *)CustomData_get_layer_named( - &this->point_data, CD_PROP_FLOAT, ATTR_RADIUS.c_str()); - this->curve_type = (int8_t *)CustomData_get_layer_named( - &this->point_data, CD_PROP_INT8, ATTR_CURVE_TYPE.c_str()); -} - static void *ensure_customdata_layer(CustomData &custom_data, const StringRefNull name, const eCustomDataType data_type, @@ -1497,7 +1478,6 @@ void CurvesGeometry::remove_attributes_based_on_types() if (!this->has_curve_with_type({CURVE_TYPE_BEZIER, CURVE_TYPE_CATMULL_ROM, CURVE_TYPE_NURBS})) { CustomData_free_layer_named(&this->curve_data, ATTR_RESOLUTION.c_str(), curves_num); } - this->update_customdata_pointers(); } /** \} */ diff --git a/source/blender/blenkernel/intern/geometry_component_curves.cc b/source/blender/blenkernel/intern/geometry_component_curves.cc index f803b08e740..2714c78e381 100644 --- a/source/blender/blenkernel/intern/geometry_component_curves.cc +++ b/source/blender/blenkernel/intern/geometry_component_curves.cc @@ -358,10 +358,7 @@ static ComponentAttributeProviders create_attribute_providers_for_curve() const CurvesGeometry &curves = *static_cast(owner); return curves.curves_num(); }, - [](void *owner) { - CurvesGeometry &curves = *static_cast(owner); - curves.update_customdata_pointers(); - }}; + [](void * /*owner*/) {}}; static CustomDataAccessInfo point_access = { [](void *owner) -> CustomData * { CurvesGeometry &curves = *static_cast(owner); @@ -375,10 +372,7 @@ static ComponentAttributeProviders create_attribute_providers_for_curve() const CurvesGeometry &curves = *static_cast(owner); return curves.points_num(); }, - [](void *owner) { - CurvesGeometry &curves = *static_cast(owner); - curves.update_customdata_pointers(); - }}; + [](void * /*owner*/) {}}; static BuiltinCustomDataLayerProvider position("position", ATTR_DOMAIN_POINT, diff --git a/source/blender/geometry/intern/resample_curves.cc b/source/blender/geometry/intern/resample_curves.cc index c9b8a032ce6..2c3a6c6e0cf 100644 --- a/source/blender/geometry/intern/resample_curves.cc +++ b/source/blender/geometry/intern/resample_curves.cc @@ -162,8 +162,6 @@ static void gather_point_attributes_to_interpolate(const CurveComponent &src_com result.src_no_interpolation, result.dst_no_interpolation, result.dst_attributes); - - dst_curves.update_customdata_pointers(); } static Curves *resample_to_uniform(const CurveComponent &src_component, diff --git a/source/blender/makesdna/DNA_curves_types.h b/source/blender/makesdna/DNA_curves_types.h index ed909c283c4..89deeec898b 100644 --- a/source/blender/makesdna/DNA_curves_types.h +++ b/source/blender/makesdna/DNA_curves_types.h @@ -66,23 +66,6 @@ typedef enum NormalMode { * curve-processing algorithms for multiple Blender data-block types. */ typedef struct CurvesGeometry { - /** - * A runtime pointer to the "position" attribute data. - * \note This data is owned by #point_data. - */ - float (*position)[3]; - /** - * A runtime pointer to the "radius" attribute data. - * \note This data is owned by #point_data. - */ - float *radius; - - /** - * The type of each curve. #CurveType. - * \note This data is owned by #curve_data. - */ - int8_t *curve_type; - /** * The start index of each curve in the point data. The size of each curve can be calculated by * subtracting the offset from the next offset. That is valid even for the last curve because diff --git a/source/blender/makesrna/intern/rna_curves.c b/source/blender/makesrna/intern/rna_curves.c index bc3e5203ed0..cb8b36f41d2 100644 --- a/source/blender/makesrna/intern/rna_curves.c +++ b/source/blender/makesrna/intern/rna_curves.c @@ -64,7 +64,24 @@ static int rna_CurvePoint_index_get_const(const PointerRNA *ptr) { const Curves *curves = rna_curves(ptr); const float(*co)[3] = ptr->data; - return (int)(co - curves->geometry.position); + const float(*positions)[3] = (const float(*)[3])CustomData_get_layer_named( + &curves->geometry.point_data, CD_PROP_FLOAT3, "position"); + return (int)(co - positions); +} + +static int rna_Curves_position_data_length(PointerRNA *ptr) +{ + const Curves *curves = rna_curves(ptr); + return curves->geometry.point_num; +} + +static void rna_Curves_position_data_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) +{ + const Curves *curves = rna_curves(ptr); + const float(*positions)[3] = (const float(*)[3])CustomData_get_layer_named( + &curves->geometry.point_data, CD_PROP_FLOAT3, "position"); + rna_iterator_array_begin( + iter, (void *)positions, sizeof(float[3]), curves->geometry.point_num, false, NULL); } static int rna_CurvePoint_index_get(PointerRNA *ptr) @@ -85,21 +102,23 @@ static void rna_CurvePoint_location_set(PointerRNA *ptr, const float value[3]) static float rna_CurvePoint_radius_get(PointerRNA *ptr) { const Curves *curves = rna_curves(ptr); - if (curves->geometry.radius == NULL) { + const float *radii = (const float *)CustomData_get_layer_named( + &curves->geometry.point_data, CD_PROP_FLOAT, "radius"); + if (radii == NULL) { return 0.0f; } - const float(*co)[3] = ptr->data; - return curves->geometry.radius[co - curves->geometry.position]; + return radii[rna_CurvePoint_index_get_const(ptr)]; } static void rna_CurvePoint_radius_set(PointerRNA *ptr, float value) { const Curves *curves = rna_curves(ptr); - if (curves->geometry.radius == NULL) { + float *radii = (float *)CustomData_get_layer_named( + &curves->geometry.point_data, CD_PROP_FLOAT, "radius"); + if (radii == NULL) { return; } - const float(*co)[3] = ptr->data; - curves->geometry.radius[co - curves->geometry.position] = value; + radii[rna_CurvePoint_index_get_const(ptr)] = value; } static char *rna_CurvePoint_path(const PointerRNA *ptr) @@ -123,16 +142,6 @@ static char *rna_CurveSlice_path(const PointerRNA *ptr) return BLI_sprintfN("curves[%d]", rna_CurveSlice_index_get_const(ptr)); } -static void rna_CurveSlice_points_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) -{ - Curves *curves = rna_curves(ptr); - const int *offset_ptr = (int *)ptr->data; - const int offset = *offset_ptr; - const int size = *(offset_ptr + 1) - offset; - float(*co)[3] = curves->geometry.position + *offset_ptr; - rna_iterator_array_begin(iter, co, sizeof(float[3]), size, 0, NULL); -} - static int rna_CurveSlice_first_point_index_get(PointerRNA *ptr) { const int *offset_ptr = (int *)ptr->data; @@ -146,6 +155,17 @@ static int rna_CurveSlice_points_length_get(PointerRNA *ptr) return *(offset_ptr + 1) - offset; } +static void rna_CurveSlice_points_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) +{ + Curves *curves = rna_curves(ptr); + const int offset = rna_CurveSlice_first_point_index_get(ptr); + const int size = rna_CurveSlice_points_length_get(ptr); + float(*positions)[3] = (float(*)[3])CustomData_get_layer_named( + &curves->geometry.point_data, CD_PROP_FLOAT3, "position"); + float(*co)[3] = positions + offset; + rna_iterator_array_begin(iter, co, sizeof(float[3]), size, 0, NULL); +} + static void rna_Curves_update_data(struct Main *UNUSED(bmain), struct Scene *UNUSED(scene), PointerRNA *ptr) @@ -252,20 +272,32 @@ static void rna_def_curves(BlenderRNA *brna) RNA_def_property_struct_type(prop, "CurveSlice"); RNA_def_property_ui_text(prop, "Curves", "All curves in the data-block"); - /* TODO: better solution for (*co)[3] parsing issue. */ - - RNA_define_verify_sdna(0); prop = RNA_def_property(srna, "points", PROP_COLLECTION, PROP_NONE); - RNA_def_property_collection_sdna(prop, NULL, "geometry.position", "geometry.point_num"); RNA_def_property_struct_type(prop, "CurvePoint"); + RNA_def_property_collection_funcs(prop, + "rna_Curves_position_data_begin", + "rna_iterator_array_next", + "rna_iterator_array_end", + "rna_iterator_array_get", + "rna_Curves_position_data_length", + NULL, + NULL, + NULL); RNA_def_property_ui_text(prop, "Points", "Control points of all curves"); - RNA_define_verify_sdna(1); /* Direct access to built-in attributes. */ RNA_define_verify_sdna(0); prop = RNA_def_property(srna, "position_data", PROP_COLLECTION, PROP_NONE); - RNA_def_property_collection_sdna(prop, NULL, "geometry.position", "geometry.point_num"); + RNA_def_property_collection_funcs(prop, + "rna_Curves_position_data_begin", + "rna_iterator_array_next", + "rna_iterator_array_end", + "rna_iterator_array_get", + "rna_Curves_position_data_length", + NULL, + NULL, + NULL); RNA_def_property_struct_type(prop, "FloatVectorAttributeValue"); RNA_def_property_update(prop, 0, "rna_Curves_update_data"); RNA_define_verify_sdna(1); -- cgit v1.2.3 From 410a6efb747f188da30c75074d6bf318b862d5d5 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 19 Jul 2022 18:06:56 -0500 Subject: Point Cloud: Remove redundant custom data pointers Similar to e9f82d3dc7eebadcc52, but for point clouds instead. Differential Revision: https://developer.blender.org/D15487 --- intern/cycles/blender/pointcloud.cpp | 61 +++++++++++++++++----- source/blender/blenkernel/BKE_pointcloud.h | 1 - source/blender/blenkernel/intern/bvhutils.cc | 11 ++-- .../intern/geometry_component_pointcloud.cc | 5 +- source/blender/blenkernel/intern/mesh_convert.cc | 19 ++++--- source/blender/blenkernel/intern/pointcloud.cc | 48 +++++++++-------- .../draw/intern/draw_cache_impl_pointcloud.c | 13 +++-- .../geometry/intern/point_merge_by_distance.cc | 14 ++--- .../blender/geometry/intern/realize_instances.cc | 22 +++++--- source/blender/makesdna/DNA_pointcloud_types.h | 4 -- source/blender/makesrna/intern/rna_pointcloud.c | 48 ++++++++++++----- .../nodes/geometry/nodes/node_geo_convex_hull.cc | 11 ++-- .../nodes/node_geo_distribute_points_on_faces.cc | 13 ++++- .../geometry/nodes/node_geo_instances_to_points.cc | 20 ++++--- .../nodes/geometry/nodes/node_geo_transform.cc | 21 ++++---- 15 files changed, 205 insertions(+), 106 deletions(-) diff --git a/intern/cycles/blender/pointcloud.cpp b/intern/cycles/blender/pointcloud.cpp index 0312ad87a70..b4e90859877 100644 --- a/intern/cycles/blender/pointcloud.cpp +++ b/intern/cycles/blender/pointcloud.cpp @@ -1,8 +1,10 @@ /* SPDX-License-Identifier: Apache-2.0 * Copyright 2011-2022 Blender Foundation */ -#include "scene/pointcloud.h" +#include + #include "scene/attribute.h" +#include "scene/pointcloud.h" #include "scene/scene.h" #include "blender/sync.h" @@ -138,6 +140,36 @@ static void copy_attributes(PointCloud *pointcloud, } } +static std::optional find_radius_attribute(BL::PointCloud b_pointcloud) +{ + for (BL::Attribute &b_attribute : b_pointcloud.attributes) { + if (b_attribute.name() != "radius") { + continue; + } + if (b_attribute.data_type() != BL::Attribute::data_type_FLOAT) { + continue; + } + return BL::FloatAttribute{b_attribute}; + } + return std::nullopt; +} + +static BL::FloatVectorAttribute find_position_attribute(BL::PointCloud b_pointcloud) +{ + for (BL::Attribute &b_attribute : b_pointcloud.attributes) { + if (b_attribute.name() != "position") { + continue; + } + if (b_attribute.data_type() != BL::Attribute::data_type_FLOAT_VECTOR) { + continue; + } + return BL::FloatVectorAttribute{b_attribute}; + } + /* The position attribute must exist. */ + assert(false); + return BL::FloatVectorAttribute{b_pointcloud.attributes[0]}; +} + static void export_pointcloud(Scene *scene, PointCloud *pointcloud, BL::PointCloud b_pointcloud, @@ -156,18 +188,18 @@ static void export_pointcloud(Scene *scene, const int num_points = b_pointcloud.points.length(); pointcloud->reserve(num_points); + BL::FloatVectorAttribute b_attr_position = find_position_attribute(b_pointcloud); + std::optional b_attr_radius = find_radius_attribute(b_pointcloud); + /* Export points. */ - BL::PointCloud::points_iterator b_point_iter; - for (b_pointcloud.points.begin(b_point_iter); b_point_iter != b_pointcloud.points.end(); - ++b_point_iter) { - BL::Point b_point = *b_point_iter; - const float3 co = get_float3(b_point.co()); - const float radius = b_point.radius(); + for (int i = 0; i < num_points; i++) { + const float3 co = get_float3(b_attr_position.data[i].vector()); + const float radius = b_attr_radius ? b_attr_radius->data[i].value() : 0.0f; pointcloud->add_point(co, radius); /* Random number per point. */ if (attr_random != NULL) { - attr_random->add(hash_uint2_to_float(b_point.index(), 0)); + attr_random->add(hash_uint2_to_float(i, 0)); } } @@ -195,14 +227,15 @@ static void export_pointcloud_motion(PointCloud *pointcloud, int num_motion_points = 0; const array &pointcloud_points = pointcloud->get_points(); - BL::PointCloud::points_iterator b_point_iter; - for (b_pointcloud.points.begin(b_point_iter); b_point_iter != b_pointcloud.points.end(); - ++b_point_iter) { - BL::Point b_point = *b_point_iter; + BL::FloatVectorAttribute b_attr_position = find_position_attribute(b_pointcloud); + std::optional b_attr_radius = find_radius_attribute(b_pointcloud); + for (int i = 0; i < num_points; i++) { if (num_motion_points < num_points) { - float3 P = get_float3(b_point.co()); - P.w = b_point.radius(); + const float3 co = get_float3(b_attr_position.data[i].vector()); + const float radius = b_attr_radius ? b_attr_radius->data[i].value() : 0.0f; + float3 P = co; + P.w = radius; mP[num_motion_points] = P; have_motion = have_motion || (P != pointcloud_points[num_motion_points]); num_motion_points++; diff --git a/source/blender/blenkernel/BKE_pointcloud.h b/source/blender/blenkernel/BKE_pointcloud.h index 6dbba11a56d..ee90fea6506 100644 --- a/source/blender/blenkernel/BKE_pointcloud.h +++ b/source/blender/blenkernel/BKE_pointcloud.h @@ -29,7 +29,6 @@ struct PointCloud *BKE_pointcloud_new_nomain(int totpoint); struct BoundBox *BKE_pointcloud_boundbox_get(struct Object *ob); bool BKE_pointcloud_minmax(const struct PointCloud *pointcloud, float r_min[3], float r_max[3]); -void BKE_pointcloud_update_customdata_pointers(struct PointCloud *pointcloud); bool BKE_pointcloud_customdata_required(const struct PointCloud *pointcloud, const char *name); /* Dependency Graph */ diff --git a/source/blender/blenkernel/intern/bvhutils.cc b/source/blender/blenkernel/intern/bvhutils.cc index 35c2039634a..03dd5c89b70 100644 --- a/source/blender/blenkernel/intern/bvhutils.cc +++ b/source/blender/blenkernel/intern/bvhutils.cc @@ -19,6 +19,7 @@ #include "BLI_threads.h" #include "BLI_utildefines.h" +#include "BKE_attribute.hh" #include "BKE_bvhutils.h" #include "BKE_editmesh.h" #include "BKE_mesh.h" @@ -1430,13 +1431,17 @@ BVHTree *BKE_bvhtree_from_pointcloud_get(BVHTreeFromPointCloud *data, return nullptr; } - for (int i = 0; i < pointcloud->totpoint; i++) { - BLI_bvhtree_insert(tree, i, pointcloud->co[i], 1); + blender::bke::AttributeAccessor attributes = blender::bke::pointcloud_attributes(*pointcloud); + blender::VArraySpan positions = attributes.lookup_or_default( + "position", ATTR_DOMAIN_POINT, blender::float3(0)); + + for (const int i : positions.index_range()) { + BLI_bvhtree_insert(tree, i, positions[i], 1); } BLI_assert(BLI_bvhtree_get_len(tree) == pointcloud->totpoint); bvhtree_balance(tree, false); - data->coords = pointcloud->co; + data->coords = (const float(*)[3])positions.data(); data->tree = tree; data->nearest_callback = nullptr; diff --git a/source/blender/blenkernel/intern/geometry_component_pointcloud.cc b/source/blender/blenkernel/intern/geometry_component_pointcloud.cc index ccc97f92dbc..4953da8a5ee 100644 --- a/source/blender/blenkernel/intern/geometry_component_pointcloud.cc +++ b/source/blender/blenkernel/intern/geometry_component_pointcloud.cc @@ -111,10 +111,7 @@ namespace blender::bke { */ static ComponentAttributeProviders create_attribute_providers_for_point_cloud() { - static auto update_custom_data_pointers = [](void *owner) { - PointCloud *pointcloud = static_cast(owner); - BKE_pointcloud_update_customdata_pointers(pointcloud); - }; + static auto update_custom_data_pointers = [](void * /*owner*/) {}; static CustomDataAccessInfo point_access = { [](void *owner) -> CustomData * { PointCloud *pointcloud = static_cast(owner); diff --git a/source/blender/blenkernel/intern/mesh_convert.cc b/source/blender/blenkernel/intern/mesh_convert.cc index 7ebb3e25fd4..923d2703960 100644 --- a/source/blender/blenkernel/intern/mesh_convert.cc +++ b/source/blender/blenkernel/intern/mesh_convert.cc @@ -751,6 +751,8 @@ void BKE_mesh_to_curve(Main *bmain, Depsgraph *depsgraph, Scene *UNUSED(scene), void BKE_pointcloud_from_mesh(Mesh *me, PointCloud *pointcloud) { + using namespace blender; + BLI_assert(me != nullptr); pointcloud->totpoint = me->totvert; @@ -758,14 +760,17 @@ void BKE_pointcloud_from_mesh(Mesh *me, PointCloud *pointcloud) /* Copy over all attributes. */ CustomData_merge(&me->vdata, &pointcloud->pdata, CD_MASK_PROP_ALL, CD_DUPLICATE, me->totvert); - BKE_pointcloud_update_customdata_pointers(pointcloud); - CustomData_update_typemap(&pointcloud->pdata); - MVert *mvert; - mvert = me->mvert; - for (int i = 0; i < me->totvert; i++, mvert++) { - copy_v3_v3(pointcloud->co[i], mvert->co); - } + bke::AttributeAccessor mesh_attributes = bke::mesh_attributes(*me); + bke::MutableAttributeAccessor point_attributes = bke::pointcloud_attributes_for_write( + *pointcloud); + + const VArray mesh_positions = mesh_attributes.lookup_or_default( + "position", ATTR_DOMAIN_POINT, float3(0)); + bke::SpanAttributeWriter point_positions = + point_attributes.lookup_or_add_for_write_only_span("position", ATTR_DOMAIN_POINT); + mesh_positions.materialize(point_positions.span); + point_positions.finish(); } void BKE_mesh_to_pointcloud(Main *bmain, Depsgraph *depsgraph, Scene *UNUSED(scene), Object *ob) diff --git a/source/blender/blenkernel/intern/pointcloud.cc b/source/blender/blenkernel/intern/pointcloud.cc index e38c20d8eb7..261b053e4f9 100644 --- a/source/blender/blenkernel/intern/pointcloud.cc +++ b/source/blender/blenkernel/intern/pointcloud.cc @@ -68,7 +68,6 @@ static void pointcloud_init_data(ID *id) nullptr, pointcloud->totpoint, POINTCLOUD_ATTR_POSITION); - BKE_pointcloud_update_customdata_pointers(pointcloud); } static void pointcloud_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, const int flag) @@ -83,7 +82,6 @@ static void pointcloud_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_s CD_MASK_ALL, alloc_type, pointcloud_dst->totpoint); - BKE_pointcloud_update_customdata_pointers(pointcloud_dst); pointcloud_dst->batch_cache = nullptr; } @@ -138,7 +136,6 @@ static void pointcloud_blend_read_data(BlendDataReader *reader, ID *id) /* Geometry */ CustomData_blend_read(reader, &pointcloud->pdata, pointcloud->totpoint); - BKE_pointcloud_update_customdata_pointers(pointcloud); /* Materials */ BLO_read_pointer_array(reader, (void **)&pointcloud->mat); @@ -194,17 +191,28 @@ static void pointcloud_random(PointCloud *pointcloud) { pointcloud->totpoint = 400; CustomData_realloc(&pointcloud->pdata, pointcloud->totpoint); - BKE_pointcloud_update_customdata_pointers(pointcloud); RNG *rng = BLI_rng_new(0); - for (int i = 0; i < pointcloud->totpoint; i++) { - pointcloud->co[i][0] = 2.0f * BLI_rng_get_float(rng) - 1.0f; - pointcloud->co[i][1] = 2.0f * BLI_rng_get_float(rng) - 1.0f; - pointcloud->co[i][2] = 2.0f * BLI_rng_get_float(rng) - 1.0f; - pointcloud->radius[i] = 0.05f * BLI_rng_get_float(rng); + blender::bke::MutableAttributeAccessor attributes = + blender::bke::pointcloud_attributes_for_write(*pointcloud); + blender::bke::SpanAttributeWriter positions = + attributes.lookup_or_add_for_write_only_span(POINTCLOUD_ATTR_POSITION, + ATTR_DOMAIN_POINT); + blender::bke::SpanAttributeWriter radii = + attributes.lookup_or_add_for_write_only_span(POINTCLOUD_ATTR_RADIUS, + ATTR_DOMAIN_POINT); + + for (const int i : positions.span.index_range()) { + positions.span[i] = + float3(BLI_rng_get_float(rng), BLI_rng_get_float(rng), BLI_rng_get_float(rng)) * 2.0f - + 1.0f; + radii.span[i] = 0.05f * BLI_rng_get_float(rng); } + positions.finish(); + radii.finish(); + BLI_rng_free(rng); } @@ -250,7 +258,6 @@ PointCloud *BKE_pointcloud_new_nomain(const int totpoint) pointcloud->totpoint = totpoint; CustomData_realloc(&pointcloud->pdata, pointcloud->totpoint); - BKE_pointcloud_update_customdata_pointers(pointcloud); return pointcloud; } @@ -258,10 +265,14 @@ PointCloud *BKE_pointcloud_new_nomain(const int totpoint) static std::optional> point_cloud_bounds( const PointCloud &pointcloud) { - Span positions{reinterpret_cast(pointcloud.co), pointcloud.totpoint}; - if (pointcloud.radius) { - Span radii{pointcloud.radius, pointcloud.totpoint}; - return blender::bounds::min_max_with_radii(positions, radii); + blender::bke::AttributeAccessor attributes = blender::bke::pointcloud_attributes(pointcloud); + blender::VArraySpan positions = attributes.lookup_or_default( + POINTCLOUD_ATTR_POSITION, ATTR_DOMAIN_POINT, float3(0)); + blender::VArray radii = attributes.lookup_or_default( + POINTCLOUD_ATTR_RADIUS, ATTR_DOMAIN_POINT, 0.0f); + + if (!(radii.is_single() && radii.get_internal_single() == 0.0f)) { + return blender::bounds::min_max_with_radii(positions, radii.get_internal_span()); } return blender::bounds::min_max(positions); } @@ -307,14 +318,6 @@ BoundBox *BKE_pointcloud_boundbox_get(Object *ob) return ob->runtime.bb; } -void BKE_pointcloud_update_customdata_pointers(PointCloud *pointcloud) -{ - pointcloud->co = static_cast( - CustomData_get_layer_named(&pointcloud->pdata, CD_PROP_FLOAT3, POINTCLOUD_ATTR_POSITION)); - pointcloud->radius = static_cast( - CustomData_get_layer_named(&pointcloud->pdata, CD_PROP_FLOAT, POINTCLOUD_ATTR_RADIUS)); -} - bool BKE_pointcloud_customdata_required(const PointCloud *UNUSED(pointcloud), const char *name) { return STREQ(name, POINTCLOUD_ATTR_POSITION); @@ -334,7 +337,6 @@ PointCloud *BKE_pointcloud_new_for_eval(const PointCloud *pointcloud_src, int to pointcloud_dst->totpoint = totpoint; CustomData_copy( &pointcloud_src->pdata, &pointcloud_dst->pdata, CD_MASK_ALL, CD_CALLOC, totpoint); - BKE_pointcloud_update_customdata_pointers(pointcloud_dst); return pointcloud_dst; } diff --git a/source/blender/draw/intern/draw_cache_impl_pointcloud.c b/source/blender/draw/intern/draw_cache_impl_pointcloud.c index d715899e291..55d0eee00e5 100644 --- a/source/blender/draw/intern/draw_cache_impl_pointcloud.c +++ b/source/blender/draw/intern/draw_cache_impl_pointcloud.c @@ -18,6 +18,7 @@ #include "DNA_object_types.h" #include "DNA_pointcloud_types.h" +#include "BKE_customdata.h" #include "BKE_pointcloud.h" #include "GPU_batch.h" @@ -139,7 +140,11 @@ static void pointcloud_batch_cache_ensure_pos(Object *ob, PointCloudBatchCache * } PointCloud *pointcloud = ob->data; - const bool has_radius = pointcloud->radius != NULL; + const float(*positions)[3] = (float(*)[3])CustomData_get_layer_named( + &pointcloud->pdata, CD_PROP_FLOAT3, "position"); + const float *radii = (float *)CustomData_get_layer_named( + &pointcloud->pdata, CD_PROP_FLOAT, "radius"); + const bool has_radius = radii != NULL; static GPUVertFormat format = {0}; static GPUVertFormat format_no_radius = {0}; @@ -162,14 +167,14 @@ static void pointcloud_batch_cache_ensure_pos(Object *ob, PointCloudBatchCache * if (has_radius) { float(*vbo_data)[4] = (float(*)[4])GPU_vertbuf_get_data(cache->pos); for (int i = 0; i < pointcloud->totpoint; i++) { - copy_v3_v3(vbo_data[i], pointcloud->co[i]); + copy_v3_v3(vbo_data[i], positions[i]); /* TODO(fclem): remove multiplication here. * Here only for keeping the size correct for now. */ - vbo_data[i][3] = pointcloud->radius[i] * 100.0f; + vbo_data[i][3] = radii[i] * 100.0f; } } else { - GPU_vertbuf_attr_fill(cache->pos, pos, pointcloud->co); + GPU_vertbuf_attr_fill(cache->pos, pos, positions); } } diff --git a/source/blender/geometry/intern/point_merge_by_distance.cc b/source/blender/geometry/intern/point_merge_by_distance.cc index 40bc02cbd34..ac10f4ef00c 100644 --- a/source/blender/geometry/intern/point_merge_by_distance.cc +++ b/source/blender/geometry/intern/point_merge_by_distance.cc @@ -18,8 +18,10 @@ PointCloud *point_merge_by_distance(const PointCloudComponent &src_points, const IndexMask selection) { const PointCloud &src_pointcloud = *src_points.get_for_read(); - const int src_size = src_pointcloud.totpoint; - Span positions{reinterpret_cast(src_pointcloud.co), src_size}; + bke::AttributeAccessor attributes = bke::pointcloud_attributes(src_pointcloud); + VArraySpan positions = attributes.lookup_or_default( + "position", ATTR_DOMAIN_POINT, float3(0)); + const int src_size = positions.size(); /* Create the KD tree based on only the selected points, to speed up merge detection and * balancing. */ @@ -106,10 +108,10 @@ PointCloud *point_merge_by_distance(const PointCloudComponent &src_points, const bke::AttributeAccessor src_attributes = *src_points.attributes(); bke::MutableAttributeAccessor dst_attributes = *dst_points.attributes_for_write(); - Set attributes = src_attributes.all_ids(); + Set attribute_ids = src_attributes.all_ids(); /* Transfer the ID attribute if it exists, using the ID of the first merged point. */ - if (attributes.contains("id")) { + if (attribute_ids.contains("id")) { VArraySpan src = src_attributes.lookup_or_default("id", ATTR_DOMAIN_POINT, 0); bke::SpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span( "id", ATTR_DOMAIN_POINT); @@ -122,11 +124,11 @@ PointCloud *point_merge_by_distance(const PointCloudComponent &src_points, }); dst.finish(); - attributes.remove_contained("id"); + attribute_ids.remove_contained("id"); } /* Transfer all other attributes. */ - for (const bke::AttributeIDRef &id : attributes) { + for (const bke::AttributeIDRef &id : attribute_ids) { if (!id.should_be_kept()) { continue; } diff --git a/source/blender/geometry/intern/realize_instances.cc b/source/blender/geometry/intern/realize_instances.cc index 25bcead09b4..0544f304283 100644 --- a/source/blender/geometry/intern/realize_instances.cc +++ b/source/blender/geometry/intern/realize_instances.cc @@ -69,6 +69,7 @@ struct PointCloudRealizeInfo { /** Matches the order stored in #AllPointCloudsInfo.attributes. */ Array> attributes; /** Id attribute on the point cloud. If there are no ids, this #Span is empty. */ + Span positions; Span stored_ids; }; @@ -665,6 +666,9 @@ static AllPointCloudsInfo preprocess_pointclouds(const GeometrySet &geometry_set pointcloud_info.stored_ids = ids_attribute.varray.get_internal_span().typed(); } } + const VArray position_attribute = attributes.lookup_or_default( + "position", ATTR_DOMAIN_POINT, float3(0)); + pointcloud_info.positions = position_attribute.get_internal_span(); } return info; } @@ -673,18 +677,16 @@ static void execute_realize_pointcloud_task( const RealizeInstancesOptions &options, const RealizePointCloudTask &task, const OrderedAttributes &ordered_attributes, - PointCloud &dst_pointcloud, MutableSpan dst_attribute_writers, - MutableSpan all_dst_ids) + MutableSpan all_dst_ids, + MutableSpan all_dst_positions) { const PointCloudRealizeInfo &pointcloud_info = *task.pointcloud_info; const PointCloud &pointcloud = *pointcloud_info.pointcloud; - const Span src_positions{(float3 *)pointcloud.co, pointcloud.totpoint}; const IndexRange point_slice{task.start_index, pointcloud.totpoint}; - MutableSpan dst_positions{(float3 *)dst_pointcloud.co + task.start_index, - pointcloud.totpoint}; - copy_transformed_positions(src_positions, task.transform, dst_positions); + copy_transformed_positions( + pointcloud_info.positions, task.transform, all_dst_positions.slice(point_slice)); /* Create point ids. */ if (!all_dst_ids.is_empty()) { @@ -726,6 +728,9 @@ static void execute_realize_pointcloud_tasks(const RealizeInstancesOptions &opti bke::MutableAttributeAccessor dst_attributes = bke::pointcloud_attributes_for_write( *dst_pointcloud); + SpanAttributeWriter positions = dst_attributes.lookup_or_add_for_write_only_span( + "position", ATTR_DOMAIN_POINT); + /* Prepare id attribute. */ SpanAttributeWriter point_ids; if (all_pointclouds_info.create_id_attribute) { @@ -748,9 +753,9 @@ static void execute_realize_pointcloud_tasks(const RealizeInstancesOptions &opti execute_realize_pointcloud_task(options, task, ordered_attributes, - *dst_pointcloud, dst_attribute_writers, - point_ids.span); + point_ids.span, + positions.span); } }); @@ -758,6 +763,7 @@ static void execute_realize_pointcloud_tasks(const RealizeInstancesOptions &opti for (GSpanAttributeWriter &dst_attribute : dst_attribute_writers) { dst_attribute.finish(); } + positions.finish(); if (point_ids) { point_ids.finish(); } diff --git a/source/blender/makesdna/DNA_pointcloud_types.h b/source/blender/makesdna/DNA_pointcloud_types.h index 1cb490190c3..ee829ebcf6e 100644 --- a/source/blender/makesdna/DNA_pointcloud_types.h +++ b/source/blender/makesdna/DNA_pointcloud_types.h @@ -18,13 +18,9 @@ typedef struct PointCloud { struct AnimData *adt; /* animation data (must be immediately after id) */ int flag; - int _pad1[1]; /* Geometry */ - float (*co)[3]; - float *radius; int totpoint; - int _pad2[1]; /* Custom Data */ struct CustomData pdata; diff --git a/source/blender/makesrna/intern/rna_pointcloud.c b/source/blender/makesrna/intern/rna_pointcloud.c index 4c5dcd5a587..df09bff1aea 100644 --- a/source/blender/makesrna/intern/rna_pointcloud.c +++ b/source/blender/makesrna/intern/rna_pointcloud.c @@ -20,6 +20,7 @@ # include "BLI_math_vector.h" +# include "BKE_customdata.h" # include "BKE_pointcloud.h" # include "DEG_depsgraph.h" @@ -36,7 +37,9 @@ static int rna_Point_index_get_const(const PointerRNA *ptr) { const PointCloud *pointcloud = rna_pointcloud(ptr); const float(*co)[3] = ptr->data; - return (int)(co - pointcloud->co); + const float(*positions)[3] = (const float(*)[3])CustomData_get_layer_named( + &pointcloud->pdata, CD_PROP_FLOAT3, "position"); + return (int)(co - positions); } static int rna_Point_index_get(PointerRNA *ptr) @@ -44,6 +47,21 @@ static int rna_Point_index_get(PointerRNA *ptr) return rna_Point_index_get_const(ptr); } +static int rna_PointCloud_points_length(PointerRNA *ptr) +{ + const PointCloud *pointcloud = rna_pointcloud(ptr); + return pointcloud->totpoint; +} + +static void rna_PointCloud_points_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) +{ + const PointCloud *pointcloud = rna_pointcloud(ptr); + const float(*positions)[3] = (const float(*)[3])CustomData_get_layer_named( + &pointcloud->pdata, CD_PROP_FLOAT3, "position"); + rna_iterator_array_begin( + iter, (void *)positions, sizeof(float[3]), pointcloud->totpoint, false, NULL); +} + static void rna_Point_location_get(PointerRNA *ptr, float value[3]) { copy_v3_v3(value, (const float *)ptr->data); @@ -57,21 +75,22 @@ static void rna_Point_location_set(PointerRNA *ptr, const float value[3]) static float rna_Point_radius_get(PointerRNA *ptr) { const PointCloud *pointcloud = rna_pointcloud(ptr); - if (pointcloud->radius == NULL) { + const float *radii = (const float *)CustomData_get_layer_named( + &pointcloud->pdata, CD_PROP_FLOAT, "radius"); + if (radii == NULL) { return 0.0f; } - const float(*co)[3] = ptr->data; - return pointcloud->radius[co - pointcloud->co]; + return radii[rna_Point_index_get_const(ptr)]; } static void rna_Point_radius_set(PointerRNA *ptr, float value) { - const PointCloud *pointcloud = rna_pointcloud(ptr); - if (pointcloud->radius == NULL) { + PointCloud *pointcloud = rna_pointcloud(ptr); + float *radii = (float *)CustomData_get_layer_named(&pointcloud->pdata, CD_PROP_FLOAT, "radius"); + if (radii == NULL) { return; } - const float(*co)[3] = ptr->data; - pointcloud->radius[co - pointcloud->co] = value; + radii[rna_Point_index_get_const(ptr)] = value; } static char *rna_Point_path(const PointerRNA *ptr) @@ -130,13 +149,18 @@ static void rna_def_pointcloud(BlenderRNA *brna) RNA_def_struct_ui_icon(srna, ICON_POINTCLOUD_DATA); /* geometry */ - /* TODO: better solution for (*co)[3] parsing issue. */ - RNA_define_verify_sdna(0); prop = RNA_def_property(srna, "points", PROP_COLLECTION, PROP_NONE); - RNA_def_property_collection_sdna(prop, NULL, "co", "totpoint"); RNA_def_property_struct_type(prop, "Point"); + RNA_def_property_collection_funcs(prop, + "rna_PointCloud_points_begin", + "rna_iterator_array_next", + "rna_iterator_array_end", + "rna_iterator_array_get", + "rna_PointCloud_points_length", + NULL, + NULL, + NULL); RNA_def_property_ui_text(prop, "Points", ""); - RNA_define_verify_sdna(1); /* materials */ prop = RNA_def_property(srna, "materials", PROP_COLLECTION, PROP_NONE); diff --git a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc index 3394a7cad62..3d0bc9cd462 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc @@ -149,12 +149,17 @@ static Mesh *compute_hull(const GeometrySet &geometry_set) if (geometry_set.has_pointcloud()) { count++; - span_count++; const PointCloudComponent *component = geometry_set.get_component_for_read(); const PointCloud *pointcloud = component->get_for_read(); - positions_span = {reinterpret_cast(pointcloud->co), pointcloud->totpoint}; - total_num += pointcloud->totpoint; + const bke::AttributeAccessor attributes = bke::pointcloud_attributes(*pointcloud); + const VArray positions = attributes.lookup_or_default( + "position", ATTR_DOMAIN_POINT, float3(0)); + if (positions.is_span()) { + span_count++; + positions_span = positions.get_internal_span(); + } + total_num += positions.size(); } if (geometry_set.has_curves()) { diff --git a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc index faf5b7f65fa..44793926bbd 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc @@ -497,8 +497,17 @@ static void point_distribution_calculate(GeometrySet &geometry_set, } PointCloud *pointcloud = BKE_pointcloud_new_nomain(positions.size()); - memcpy(pointcloud->co, positions.data(), sizeof(float3) * positions.size()); - uninitialized_fill_n(pointcloud->radius, pointcloud->totpoint, 0.05f); + bke::MutableAttributeAccessor point_attributes = bke::pointcloud_attributes_for_write( + *pointcloud); + bke::SpanAttributeWriter point_positions = + point_attributes.lookup_or_add_for_write_only_span("position", ATTR_DOMAIN_POINT); + bke::SpanAttributeWriter point_radii = + point_attributes.lookup_or_add_for_write_only_span("radius", ATTR_DOMAIN_POINT); + point_positions.span.copy_from(positions); + point_radii.span.fill(0.05f); + point_positions.finish(); + point_radii.finish(); + geometry_set.replace_pointcloud(pointcloud); PointCloudComponent &point_component = diff --git a/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc index edbe6e1593f..b3f04186c63 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc @@ -51,16 +51,24 @@ static void convert_instances_to_points(GeometrySet &geometry_set, if (selection.is_empty()) { return; } + const VArray &positions = evaluator.get_evaluated(0); + const VArray radii = evaluator.get_evaluated(1); PointCloud *pointcloud = BKE_pointcloud_new_nomain(selection.size()); geometry_set.replace_pointcloud(pointcloud); - PointCloudComponent &points = geometry_set.get_component_for_write(); + bke::MutableAttributeAccessor point_attributes = bke::pointcloud_attributes_for_write( + *pointcloud); - const VArray &positions = evaluator.get_evaluated(0); - copy_attribute_to_points(positions, selection, {(float3 *)pointcloud->co, pointcloud->totpoint}); - const VArray radii = evaluator.get_evaluated(1); - copy_attribute_to_points(radii, selection, {pointcloud->radius, pointcloud->totpoint}); + bke::SpanAttributeWriter point_positions = + point_attributes.lookup_or_add_for_write_only_span("position", ATTR_DOMAIN_POINT); + bke::SpanAttributeWriter point_radii = + point_attributes.lookup_or_add_for_write_only_span("radius", ATTR_DOMAIN_POINT); + + copy_attribute_to_points(positions, selection, point_positions.span); + copy_attribute_to_points(radii, selection, point_radii.span); + point_positions.finish(); + point_radii.finish(); Map attributes_to_propagate; geometry_set.gather_attributes_for_propagation({GEO_COMPONENT_TYPE_INSTANCES}, @@ -78,7 +86,7 @@ static void convert_instances_to_points(GeometrySet &geometry_set, const GVArray src = instances.attributes()->lookup_or_default( attribute_id, ATTR_DOMAIN_INSTANCE, attribute_kind.data_type); BLI_assert(src); - GSpanAttributeWriter dst = points.attributes_for_write()->lookup_or_add_for_write_only_span( + GSpanAttributeWriter dst = point_attributes.lookup_or_add_for_write_only_span( attribute_id, ATTR_DOMAIN_POINT, attribute_kind.data_type); BLI_assert(dst); diff --git a/source/blender/nodes/geometry/nodes/node_geo_transform.cc b/source/blender/nodes/geometry/nodes/node_geo_transform.cc index e95db205920..8e65e73d1e2 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_transform.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_transform.cc @@ -47,21 +47,24 @@ static void transform_mesh(Mesh &mesh, const float4x4 &transform) static void translate_pointcloud(PointCloud &pointcloud, const float3 translation) { - CustomData_duplicate_referenced_layer(&pointcloud.pdata, CD_PROP_FLOAT3, pointcloud.totpoint); - BKE_pointcloud_update_customdata_pointers(&pointcloud); - for (const int i : IndexRange(pointcloud.totpoint)) { - add_v3_v3(pointcloud.co[i], translation); + MutableAttributeAccessor attributes = bke::pointcloud_attributes_for_write(pointcloud); + SpanAttributeWriter position = attributes.lookup_or_add_for_write_span( + "position", ATTR_DOMAIN_POINT); + for (const int i : position.span.index_range()) { + position.span[i] += translation; } + position.finish(); } static void transform_pointcloud(PointCloud &pointcloud, const float4x4 &transform) { - CustomData_duplicate_referenced_layer(&pointcloud.pdata, CD_PROP_FLOAT3, pointcloud.totpoint); - BKE_pointcloud_update_customdata_pointers(&pointcloud); - for (const int i : IndexRange(pointcloud.totpoint)) { - float3 &co = *(float3 *)pointcloud.co[i]; - co = transform * co; + MutableAttributeAccessor attributes = bke::pointcloud_attributes_for_write(pointcloud); + SpanAttributeWriter position = attributes.lookup_or_add_for_write_span( + "position", ATTR_DOMAIN_POINT); + for (const int i : position.span.index_range()) { + position.span[i] = transform * position.span[i]; } + position.finish(); } static void translate_instances(InstancesComponent &instances, const float3 translation) -- cgit v1.2.3 From 40ffb94ab47c29cf989d561922e1970e89a836ca Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 19 Jul 2022 18:16:12 -0500 Subject: Cleanup: Use generic utility for copying compressed attribute In the future, `materialize_compressed_to_uninitialized_threaded` could be moved somewhere else and reused. --- .../geometry/nodes/node_geo_instances_to_points.cc | 19 +++---------------- .../geometry/nodes/node_geo_points_to_vertices.cc | 15 +-------------- 2 files changed, 4 insertions(+), 30 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc index b3f04186c63..f14329c96da 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc @@ -22,16 +22,6 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output(N_("Points")); } -template -static void copy_attribute_to_points(const VArray &src, - const IndexMask mask, - MutableSpan dst) -{ - for (const int i : mask.index_range()) { - dst[i] = src[mask[i]]; - } -} - static void convert_instances_to_points(GeometrySet &geometry_set, Field position_field, Field radius_field, @@ -65,8 +55,8 @@ static void convert_instances_to_points(GeometrySet &geometry_set, bke::SpanAttributeWriter point_radii = point_attributes.lookup_or_add_for_write_only_span("radius", ATTR_DOMAIN_POINT); - copy_attribute_to_points(positions, selection, point_positions.span); - copy_attribute_to_points(radii, selection, point_radii.span); + positions.materialize_compressed_to_uninitialized(selection, point_positions.span); + radii.materialize_compressed_to_uninitialized(selection, point_radii.span); point_positions.finish(); point_radii.finish(); @@ -90,10 +80,7 @@ static void convert_instances_to_points(GeometrySet &geometry_set, attribute_id, ATTR_DOMAIN_POINT, attribute_kind.data_type); BLI_assert(dst); - attribute_math::convert_to_static_type(attribute_kind.data_type, [&](auto dummy) { - using T = decltype(dummy); - copy_attribute_to_points(src.typed(), selection, dst.span.typed()); - }); + src.materialize_compressed_to_uninitialized(selection, dst.span.data()); dst.finish(); } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc index 9cc64d4bc44..74fff8efeee 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc @@ -18,14 +18,6 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output(N_("Mesh")); } -template -static void copy_attribute_to_vertices(const Span src, const IndexMask mask, MutableSpan dst) -{ - for (const int i : mask.index_range()) { - dst[i] = src[mask[i]]; - } -} - /* One improvement would be to move the attribute arrays directly to the mesh when possible. */ static void geometry_set_points_to_vertices(GeometrySet &geometry_set, Field &selection_field) @@ -66,12 +58,7 @@ static void geometry_set_points_to_vertices(GeometrySet &geometry_set, mesh_component.attributes_for_write()->lookup_or_add_for_write_only_span( attribute_id, ATTR_DOMAIN_POINT, data_type); if (dst && src) { - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { - using T = decltype(dummy); - VArray src_typed = src.typed(); - VArraySpan src_typed_span{src_typed}; - copy_attribute_to_vertices(src_typed_span, selection, dst.span.typed()); - }); + src.materialize_compressed_to_uninitialized(selection, dst.span.data()); dst.finish(); } } -- cgit v1.2.3 From 5d6e4822d85bf4db099e2810555af17962929e49 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 19 Jul 2022 18:29:23 -0500 Subject: Cleanup: Combine geometry null checks in if statements Testing if components or virtual arrays are null in the same line they are retrieved can make this boilerplate code a bit easier to read. --- .../nodes/node_geo_attribute_domain_size.cc | 28 +++++----- .../nodes/geometry/nodes/node_geo_convex_hull.cc | 61 ++++++++++------------ 2 files changed, 42 insertions(+), 47 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc index 7e4904a7a6a..f6ea6073459 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc @@ -65,13 +65,12 @@ static void node_update(bNodeTree *ntree, bNode *node) static void node_geo_exec(GeoNodeExecParams params) { - GeometryComponentType component = (GeometryComponentType)params.node().custom1; - GeometrySet geometry_set = params.extract_input("Geometry"); + const GeometryComponentType component = (GeometryComponentType)params.node().custom1; + const GeometrySet geometry_set = params.extract_input("Geometry"); switch (component) { case GEO_COMPONENT_TYPE_MESH: { - if (geometry_set.has_mesh()) { - const MeshComponent *component = geometry_set.get_component_for_read(); + if (const MeshComponent *component = geometry_set.get_component_for_read()) { const AttributeAccessor attributes = *component->attributes(); params.set_output("Point Count", attributes.domain_size(ATTR_DOMAIN_POINT)); params.set_output("Edge Count", attributes.domain_size(ATTR_DOMAIN_EDGE)); @@ -84,8 +83,8 @@ static void node_geo_exec(GeoNodeExecParams params) break; } case GEO_COMPONENT_TYPE_CURVE: { - if (geometry_set.has_curves()) { - const CurveComponent *component = geometry_set.get_component_for_read(); + if (const CurveComponent *component = + geometry_set.get_component_for_read()) { const AttributeAccessor attributes = *component->attributes(); params.set_output("Point Count", attributes.domain_size(ATTR_DOMAIN_POINT)); params.set_output("Spline Count", attributes.domain_size(ATTR_DOMAIN_CURVE)); @@ -96,10 +95,10 @@ static void node_geo_exec(GeoNodeExecParams params) break; } case GEO_COMPONENT_TYPE_POINT_CLOUD: { - if (geometry_set.has_pointcloud()) { - const PointCloudComponent *component = - geometry_set.get_component_for_read(); - params.set_output("Point Count", component->attributes()->domain_size(ATTR_DOMAIN_POINT)); + if (const PointCloudComponent *component = + geometry_set.get_component_for_read()) { + const AttributeAccessor attributes = *component->attributes(); + params.set_output("Point Count", attributes.domain_size(ATTR_DOMAIN_POINT)); } else { params.set_default_remaining_outputs(); @@ -107,11 +106,10 @@ static void node_geo_exec(GeoNodeExecParams params) break; } case GEO_COMPONENT_TYPE_INSTANCES: { - if (geometry_set.has_instances()) { - const InstancesComponent *component = - geometry_set.get_component_for_read(); - params.set_output("Instance Count", - component->attributes()->domain_size(ATTR_DOMAIN_INSTANCE)); + if (const InstancesComponent *component = + geometry_set.get_component_for_read()) { + const AttributeAccessor attributes = *component->attributes(); + params.set_output("Instance Count", attributes.domain_size(ATTR_DOMAIN_INSTANCE)); } else { params.set_default_remaining_outputs(); diff --git a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc index 3d0bc9cd462..7c26ffc2099 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc @@ -140,33 +140,35 @@ static Mesh *compute_hull(const GeometrySet &geometry_set) Span positions_span; - if (geometry_set.has_mesh()) { + if (const MeshComponent *component = geometry_set.get_component_for_read()) { count++; - const MeshComponent *component = geometry_set.get_component_for_read(); - const Mesh *mesh = component->get_for_read(); - total_num += mesh->totvert; + if (const VArray positions = component->attributes()->lookup( + "position", ATTR_DOMAIN_POINT)) { + if (positions.is_span()) { + span_count++; + positions_span = positions.get_internal_span(); + } + total_num += positions.size(); + } } - if (geometry_set.has_pointcloud()) { + if (const PointCloudComponent *component = + geometry_set.get_component_for_read()) { count++; - const PointCloudComponent *component = - geometry_set.get_component_for_read(); - const PointCloud *pointcloud = component->get_for_read(); - const bke::AttributeAccessor attributes = bke::pointcloud_attributes(*pointcloud); - const VArray positions = attributes.lookup_or_default( - "position", ATTR_DOMAIN_POINT, float3(0)); - if (positions.is_span()) { - span_count++; - positions_span = positions.get_internal_span(); + if (const VArray positions = component->attributes()->lookup( + "position", ATTR_DOMAIN_POINT)) { + if (positions.is_span()) { + span_count++; + positions_span = positions.get_internal_span(); + } + total_num += positions.size(); } - total_num += positions.size(); } - if (geometry_set.has_curves()) { + if (const Curves *curves_id = geometry_set.get_curves_for_read()) { count++; span_count++; - const Curves &curves_id = *geometry_set.get_curves_for_read(); - const bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry); + const bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id->geometry); positions_span = curves.evaluated_positions(); total_num += positions_span.size(); } @@ -184,30 +186,25 @@ static Mesh *compute_hull(const GeometrySet &geometry_set) Array positions(total_num); int offset = 0; - if (geometry_set.has_mesh()) { - const MeshComponent *component = geometry_set.get_component_for_read(); - const VArray varray = component->attributes()->lookup("position", - ATTR_DOMAIN_POINT); - if (varray) { + if (const MeshComponent *component = geometry_set.get_component_for_read()) { + if (const VArray varray = component->attributes()->lookup("position", + ATTR_DOMAIN_POINT)) { varray.materialize(positions.as_mutable_span().slice(offset, varray.size())); offset += varray.size(); } } - if (geometry_set.has_pointcloud()) { - const PointCloudComponent *component = - geometry_set.get_component_for_read(); - const VArray varray = component->attributes()->lookup("position", - ATTR_DOMAIN_POINT); - if (varray) { + if (const PointCloudComponent *component = + geometry_set.get_component_for_read()) { + if (const VArray varray = component->attributes()->lookup("position", + ATTR_DOMAIN_POINT)) { varray.materialize(positions.as_mutable_span().slice(offset, varray.size())); offset += varray.size(); } } - if (geometry_set.has_curves()) { - const Curves &curves_id = *geometry_set.get_curves_for_read(); - const bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry); + if (const Curves *curves_id = geometry_set.get_curves_for_read()) { + const bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id->geometry); Span array = curves.evaluated_positions(); positions.as_mutable_span().slice(offset, array.size()).copy_from(array); offset += array.size(); -- cgit v1.2.3 From 2551cf908732bab2865654571164d74e8bbad47e Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 19 Jul 2022 18:50:27 -0500 Subject: Curves: Port fillet node to the new data-block This commit ports the fillet curves node to the new curves data-block, and moves the fillet node implementation to the geometry module to help separate the implementation from the node. The changes are similar to the subdivide node or resample node. I've resused common utilities where it makes sense, though some things like the iteration over attributes can be generalized further. The node is now multi-threaded per-curve and inside each curve, and some buffers are reused per curve to avoid many allocations. The code is more explicit now, and though there is more boilerplate to pass around many spans, the more complex logic should be more readable. Differential Revision: https://developer.blender.org/D15346 --- source/blender/blenkernel/BKE_attribute.hh | 16 + .../blender/blenkernel/intern/attribute_access.cc | 31 ++ source/blender/blenlib/BLI_index_range.hh | 10 + source/blender/blenlib/BLI_math_rotation.hh | 9 + source/blender/blenlib/intern/math_rotation.cc | 13 + .../blender/blenlib/tests/BLI_index_range_test.cc | 6 + source/blender/geometry/CMakeLists.txt | 2 + source/blender/geometry/GEO_fillet_curves.hh | 23 + source/blender/geometry/intern/fillet_curves.cc | 561 +++++++++++++++++++ source/blender/geometry/intern/subdivide_curves.cc | 58 +- .../nodes/geometry/nodes/node_geo_curve_fillet.cc | 600 ++------------------- 11 files changed, 722 insertions(+), 607 deletions(-) create mode 100644 source/blender/geometry/GEO_fillet_curves.hh create mode 100644 source/blender/geometry/intern/fillet_curves.cc diff --git a/source/blender/blenkernel/BKE_attribute.hh b/source/blender/blenkernel/BKE_attribute.hh index 05ab4f1f1f1..108993d91c0 100644 --- a/source/blender/blenkernel/BKE_attribute.hh +++ b/source/blender/blenkernel/BKE_attribute.hh @@ -666,6 +666,22 @@ class MutableAttributeAccessor : public AttributeAccessor { void remove_anonymous(); }; +struct AttributeTransferData { + /* Expect that if an attribute exists, it is stored as a contiguous array internally anyway. */ + GVArraySpan src; + AttributeMetaData meta_data; + bke::GSpanAttributeWriter dst; +}; +/** + * Retrieve attribute arrays and writers for attributes that should be transferred between + * data-blocks of the same type. + */ +Vector retrieve_attributes_for_transfer( + const bke::AttributeAccessor &src_attributes, + bke::MutableAttributeAccessor &dst_attributes, + eAttrDomainMask domain_mask, + const Set &skip = {}); + bool allow_procedural_attribute_access(StringRef attribute_name); extern const char *no_procedural_access_message; diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index ac1ee19927c..a834b77d65e 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -1011,6 +1011,37 @@ GSpanAttributeWriter MutableAttributeAccessor::lookup_or_add_for_write_only_span return {}; } +Vector retrieve_attributes_for_transfer( + const bke::AttributeAccessor &src_attributes, + bke::MutableAttributeAccessor &dst_attributes, + const eAttrDomainMask domain_mask, + const Set &skip) +{ + Vector attributes; + src_attributes.for_all( + [&](const bke::AttributeIDRef &id, const bke::AttributeMetaData meta_data) { + if (!(ATTR_DOMAIN_AS_MASK(meta_data.domain) & domain_mask)) { + return true; + } + if (id.is_named() && skip.contains(id.name())) { + return true; + } + if (!id.should_be_kept()) { + return true; + } + + GVArray src = src_attributes.lookup(id, meta_data.domain); + BLI_assert(src); + bke::GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span( + id, meta_data.domain, meta_data.data_type); + BLI_assert(dst); + attributes.append({std::move(src), meta_data, std::move(dst)}); + + return true; + }); + return attributes; +} + } // namespace blender::bke /** \} */ diff --git a/source/blender/blenlib/BLI_index_range.hh b/source/blender/blenlib/BLI_index_range.hh index bd0a7e5bb7a..6fcc560d856 100644 --- a/source/blender/blenlib/BLI_index_range.hh +++ b/source/blender/blenlib/BLI_index_range.hh @@ -197,6 +197,16 @@ class IndexRange { return start_ + size_ - 1 - n; } + /** + * Get the element one before the beginning. The returned value is undefined when the range is + * empty, and the range must start after zero already. + */ + constexpr int64_t one_before_start() const + { + BLI_assert(start_ > 0); + return start_ - 1; + } + /** * Get the element one after the end. The returned value is undefined when the range is empty. */ diff --git a/source/blender/blenlib/BLI_math_rotation.hh b/source/blender/blenlib/BLI_math_rotation.hh index e8b746b34df..50a062162ad 100644 --- a/source/blender/blenlib/BLI_math_rotation.hh +++ b/source/blender/blenlib/BLI_math_rotation.hh @@ -15,4 +15,13 @@ namespace blender::math { */ float3 rotate_direction_around_axis(const float3 &direction, const float3 &axis, float angle); +/** + * Rotate any arbitrary \a vector around the \a center position, with a unit-length \a axis + * and the specified \a angle. + */ +float3 rotate_around_axis(const float3 &vector, + const float3 ¢er, + const float3 &axis, + float angle); + } // namespace blender::math diff --git a/source/blender/blenlib/intern/math_rotation.cc b/source/blender/blenlib/intern/math_rotation.cc index 74300d55954..091e8af85d9 100644 --- a/source/blender/blenlib/intern/math_rotation.cc +++ b/source/blender/blenlib/intern/math_rotation.cc @@ -23,4 +23,17 @@ float3 rotate_direction_around_axis(const float3 &direction, const float3 &axis, return axis_scaled + diff * std::cos(angle) + cross * std::sin(angle); } +float3 rotate_around_axis(const float3 &vector, + const float3 ¢er, + const float3 &axis, + const float angle) + +{ + float3 result = vector - center; + float mat[3][3]; + axis_angle_normalized_to_mat3(mat, axis, angle); + mul_m3_v3(mat, result); + return result + center; +} + } // namespace blender::math diff --git a/source/blender/blenlib/tests/BLI_index_range_test.cc b/source/blender/blenlib/tests/BLI_index_range_test.cc index 10f6784cd44..f5b994d409a 100644 --- a/source/blender/blenlib/tests/BLI_index_range_test.cc +++ b/source/blender/blenlib/tests/BLI_index_range_test.cc @@ -105,6 +105,12 @@ TEST(index_range, OneAfterEnd) EXPECT_EQ(range.one_after_last(), 8); } +TEST(index_range, OneBeforeStart) +{ + IndexRange range = IndexRange(5, 3); + EXPECT_EQ(range.one_before_start(), 4); +} + TEST(index_range, Start) { IndexRange range = IndexRange(6, 2); diff --git a/source/blender/geometry/CMakeLists.txt b/source/blender/geometry/CMakeLists.txt index df66a806c16..da83d9e8957 100644 --- a/source/blender/geometry/CMakeLists.txt +++ b/source/blender/geometry/CMakeLists.txt @@ -16,6 +16,7 @@ set(INC set(SRC intern/add_curves_on_mesh.cc + intern/fillet_curves.cc intern/mesh_merge_by_distance.cc intern/mesh_primitive_cuboid.cc intern/mesh_to_curve_convert.cc @@ -29,6 +30,7 @@ set(SRC intern/uv_parametrizer.c GEO_add_curves_on_mesh.hh + GEO_fillet_curves.hh GEO_mesh_merge_by_distance.hh GEO_mesh_primitive_cuboid.hh GEO_mesh_to_curve.hh diff --git a/source/blender/geometry/GEO_fillet_curves.hh b/source/blender/geometry/GEO_fillet_curves.hh new file mode 100644 index 00000000000..1f832f8b6cc --- /dev/null +++ b/source/blender/geometry/GEO_fillet_curves.hh @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_function_ref.hh" +#include "BLI_index_mask.hh" + +#include "BKE_curves.hh" + +namespace blender::geometry { + +bke::CurvesGeometry fillet_curves_poly(const bke::CurvesGeometry &src_curves, + IndexMask curve_selection, + const VArray &radius, + const VArray &counts, + bool limit_radius); + +bke::CurvesGeometry fillet_curves_bezier(const bke::CurvesGeometry &src_curves, + IndexMask curve_selection, + const VArray &radius, + bool limit_radius); + +} // namespace blender::geometry diff --git a/source/blender/geometry/intern/fillet_curves.cc b/source/blender/geometry/intern/fillet_curves.cc new file mode 100644 index 00000000000..2cca91f40ae --- /dev/null +++ b/source/blender/geometry/intern/fillet_curves.cc @@ -0,0 +1,561 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_attribute_math.hh" +#include "BKE_curves.hh" +#include "BKE_curves_utils.hh" +#include "BKE_geometry_set.hh" + +#include "BLI_devirtualize_parameters.hh" +#include "BLI_math_geom.h" +#include "BLI_math_rotation.hh" +#include "BLI_task.hh" + +#include "GEO_fillet_curves.hh" + +namespace blender::geometry { + +/** + * Return a range used to retrieve values from an array of values stored per point, but with an + * extra element at the end of each curve. This is useful for offsets within curves, where it is + * convenient to store the first 0 and have the last offset be the total result curve size. + */ +static IndexRange curve_dst_offsets(const IndexRange points, const int curve_index) +{ + return {curve_index + points.start(), points.size() + 1}; +} + +template +static void threaded_slice_fill(const Span src, const Span offsets, MutableSpan dst) +{ + threading::parallel_for(src.index_range(), 512, [&](IndexRange range) { + for (const int i : range) { + dst.slice(bke::offsets_to_range(offsets, i)).fill(src[i]); + } + }); +} + +template +static void duplicate_fillet_point_data(const bke::CurvesGeometry &src_curves, + const bke::CurvesGeometry &dst_curves, + const IndexMask curve_selection, + const Span point_offsets, + const Span src, + MutableSpan dst) +{ + threading::parallel_for(curve_selection.index_range(), 512, [&](IndexRange range) { + for (const int curve_i : curve_selection.slice(range)) { + const IndexRange src_points = src_curves.points_for_curve(curve_i); + const IndexRange dst_points = dst_curves.points_for_curve(curve_i); + const Span offsets = point_offsets.slice(curve_dst_offsets(src_points, curve_i)); + threaded_slice_fill(src.slice(src_points), offsets, dst.slice(dst_points)); + } + }); +} + +static void duplicate_fillet_point_data(const bke::CurvesGeometry &src_curves, + const bke::CurvesGeometry &dst_curves, + const IndexMask selection, + const Span point_offsets, + const GSpan src, + GMutableSpan dst) +{ + attribute_math::convert_to_static_type(dst.type(), [&](auto dummy) { + using T = decltype(dummy); + duplicate_fillet_point_data( + src_curves, dst_curves, selection, point_offsets, src.typed(), dst.typed()); + }); +} + +static void calculate_result_offsets(const bke::CurvesGeometry &src_curves, + const IndexMask selection, + const Span unselected_ranges, + const VArray &radii, + const VArray &counts, + const Span cyclic, + MutableSpan dst_curve_offsets, + MutableSpan dst_point_offsets) +{ + /* Fill the offsets array with the curve point counts, then accumulate them to form offsets. */ + bke::curves::fill_curve_counts(src_curves, unselected_ranges, dst_curve_offsets); + threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) { + for (const int curve_i : selection.slice(range)) { + const IndexRange src_points = src_curves.points_for_curve(curve_i); + const IndexRange offsets_range = curve_dst_offsets(src_points, curve_i); + + MutableSpan point_offsets = dst_point_offsets.slice(offsets_range); + MutableSpan point_counts = point_offsets.drop_back(1); + + counts.materialize_compressed(src_points, point_counts); + for (int &count : point_counts) { + /* Make sure the number of cuts is greater than zero and add one for the existing point. */ + count = std::max(count, 0) + 1; + } + if (!cyclic[curve_i]) { + /* Endpoints on non-cyclic curves cannot be filleted. */ + point_counts.first() = 1; + point_counts.last() = 1; + } + /* Implicitly "deselect" points with zero radius. */ + devirtualize_varray(radii, [&](const auto radii) { + for (const int i : IndexRange(src_points.size())) { + if (radii[i] == 0.0f) { + point_counts[i] = 1; + } + } + }); + + bke::curves::accumulate_counts_to_offsets(point_offsets); + + dst_curve_offsets[curve_i] = point_offsets.last(); + } + }); + bke::curves::accumulate_counts_to_offsets(dst_curve_offsets); +} + +static void calculate_directions(const Span positions, MutableSpan directions) +{ + for (const int i : positions.index_range().drop_back(1)) { + directions[i] = math::normalize(positions[i + 1] - positions[i]); + } + directions.last() = math::normalize(positions.first() - positions.last()); +} + +static void calculate_angles(const Span directions, MutableSpan angles) +{ + angles.first() = M_PI - angle_v3v3(-directions.last(), directions.first()); + for (const int i : directions.index_range().drop_front(1)) { + angles[i] = M_PI - angle_v3v3(-directions[i - 1], directions[i]); + } +} + +/** + * Find the portion of the previous and next segments used by the current and next point fillets. + * If more than the total length of the segment would be used, scale the current point's radius + * just enough to make the two points meet in the middle. + */ +static float limit_radius(const float3 &position_prev, + const float3 &position, + const float3 &position_next, + const float angle_prev, + const float angle, + const float angle_next, + const float radius_prev, + const float radius, + const float radius_next) +{ + const float displacement = radius * std::tan(angle / 2.0f); + + const float displacement_prev = radius_prev * std::tan(angle_prev / 2.0f); + const float segment_length_prev = math::distance(position, position_prev); + const float total_displacement_prev = displacement_prev + displacement; + const float factor_prev = std::clamp(segment_length_prev / total_displacement_prev, 0.0f, 1.0f); + + const float displacement_next = radius_next * std::tan(angle_next / 2.0f); + const float segment_length_next = math::distance(position, position_next); + const float total_displacement_next = displacement_next + displacement; + const float factor_next = std::clamp(segment_length_next / total_displacement_next, 0.0f, 1.0f); + + return radius * std::min(factor_prev, factor_next); +} + +static void limit_radii(const Span positions, + const Span angles, + const Span radii, + const bool cyclic, + MutableSpan radii_clamped) +{ + if (cyclic) { + /* First point. */ + radii_clamped.first() = limit_radius(positions.last(), + positions.first(), + positions[1], + angles.last(), + angles.first(), + angles[1], + radii.last(), + radii.first(), + radii[1]); + /* All middle points. */ + for (const int i : positions.index_range().drop_back(1).drop_front(1)) { + const int i_prev = i - 1; + const int i_next = i + 1; + radii_clamped[i] = limit_radius(positions[i_prev], + positions[i], + positions[i_next], + angles[i_prev], + angles[i], + angles[i_next], + radii[i_prev], + radii[i], + radii[i_next]); + } + /* Last point. */ + radii_clamped.last() = limit_radius(positions.last(1), + positions.last(), + positions.first(), + angles.last(1), + angles.last(), + angles.first(), + radii.last(1), + radii.last(), + radii.first()); + } + else { + const int i_last = positions.index_range().last(); + /* First point. */ + radii_clamped.first() = 0.0f; + /* All middle points. */ + for (const int i : positions.index_range().drop_back(1).drop_front(1)) { + const int i_prev = i - 1; + const int i_next = i + 1; + /* Use a zero radius for the first and last points, because they don't have fillets. + * This logic could potentially be unrolled, but it doesn't seem worth it. */ + const float radius_prev = i_prev == 0 ? 0.0f : radii[i_prev]; + const float radius_next = i_next == i_last ? 0.0f : radii[i_next]; + radii_clamped[i] = limit_radius(positions[i_prev], + positions[i], + positions[i_next], + angles[i_prev], + angles[i], + angles[i_next], + radius_prev, + radii[i], + radius_next); + } + /* Last point. */ + radii_clamped.last() = 0.0f; + } +} + +static void calculate_fillet_positions(const Span src_positions, + const Span angles, + const Span radii, + const Span directions, + const Span dst_offsets, + MutableSpan dst) +{ + const int i_src_last = src_positions.index_range().last(); + threading::parallel_for(src_positions.index_range(), 512, [&](IndexRange range) { + for (const int i_src : range) { + const IndexRange arc = bke::offsets_to_range(dst_offsets, i_src); + const float3 &src = src_positions[i_src]; + if (arc.size() == 1) { + dst[arc.first()] = src; + continue; + } + + const int i_src_prev = i_src == 0 ? i_src_last : i_src - 1; + const float angle = angles[i_src]; + const float radius = radii[i_src]; + const float displacement = radius * std::tan(angle / 2.0f); + const float3 prev_dir = -directions[i_src_prev]; + const float3 &next_dir = directions[i_src]; + const float3 arc_start = src + prev_dir * displacement; + const float3 arc_end = src + next_dir * displacement; + + dst[arc.first()] = arc_start; + dst[arc.last()] = arc_end; + + const IndexRange middle = arc.drop_front(1).drop_back(1); + if (middle.is_empty()) { + continue; + } + + const float3 axis = -math::normalize(math::cross(prev_dir, next_dir)); + const float3 center_direction = math::normalize(math::midpoint(next_dir, prev_dir)); + const float distance_to_center = std::sqrt(pow2f(radius) + pow2f(displacement)); + const float3 center = src + center_direction * distance_to_center; + + /* Rotate each middle fillet point around the center. */ + const float segment_angle = angle / (middle.size() + 1); + for (const int i : IndexRange(middle.size())) { + const int point_i = middle[i]; + dst[point_i] = math::rotate_around_axis(arc_start, center, axis, segment_angle * (i + 1)); + } + } + }); +} + +/** + * Set handles for the "Bezier" mode where we rely on setting the inner handles to approximate a + * circular arc. The outer (previous and next) handles outside the result fillet segment are set + * to vector handles. + */ +static void calculate_bezier_handles_bezier_mode(const Span src_handles_l, + const Span src_handles_r, + const Span src_types_l, + const Span src_types_r, + const Span angles, + const Span radii, + const Span directions, + const Span dst_offsets, + const Span dst_positions, + MutableSpan dst_handles_l, + MutableSpan dst_handles_r, + MutableSpan dst_types_l, + MutableSpan dst_types_r) +{ + const int i_src_last = src_handles_l.index_range().last(); + const int i_dst_last = dst_positions.index_range().last(); + threading::parallel_for(src_handles_l.index_range(), 512, [&](IndexRange range) { + for (const int i_src : range) { + const IndexRange arc = bke::offsets_to_range(dst_offsets, i_src); + if (arc.size() == 1) { + dst_handles_l[arc.first()] = src_handles_l[i_src]; + dst_handles_r[arc.first()] = src_handles_r[i_src]; + dst_types_l[arc.first()] = src_types_l[i_src]; + dst_types_r[arc.first()] = src_types_r[i_src]; + continue; + } + BLI_assert(arc.size() == 2); + const int i_dst_a = arc.first(); + const int i_dst_b = arc.last(); + + const int i_src_prev = i_src == 0 ? i_src_last : i_src - 1; + const float angle = angles[i_src]; + const float radius = radii[i_src]; + const float3 prev_dir = -directions[i_src_prev]; + const float3 &next_dir = directions[i_src]; + + const float3 &arc_start = dst_positions[arc.first()]; + const float3 &arc_end = dst_positions[arc.last()]; + + /* Calculate the point's handles on the outside of the fillet segment, + * connecting to the next or previous result points. */ + const int i_dst_prev = i_dst_a == 0 ? i_dst_last : i_dst_a - 1; + const int i_dst_next = i_dst_b == i_dst_last ? 0 : i_dst_b + 1; + dst_handles_l[i_dst_a] = bke::curves::bezier::calculate_vector_handle( + dst_positions[i_dst_a], dst_positions[i_dst_prev]); + dst_handles_r[i_dst_b] = bke::curves::bezier::calculate_vector_handle( + dst_positions[i_dst_b], dst_positions[i_dst_next]); + dst_types_l[i_dst_a] = BEZIER_HANDLE_VECTOR; + dst_types_r[i_dst_b] = BEZIER_HANDLE_VECTOR; + + /* The inner handles are aligned with the aligned with the outer vector + * handles, but have a specific length to best approximate a circle. */ + const float handle_length = (4.0f / 3.0f) * radius * std::tan(angle / 4.0f); + dst_handles_r[i_dst_a] = arc_start - prev_dir * handle_length; + dst_handles_l[i_dst_b] = arc_end - next_dir * handle_length; + dst_types_r[i_dst_a] = BEZIER_HANDLE_ALIGN; + dst_types_l[i_dst_b] = BEZIER_HANDLE_ALIGN; + } + }); +} + +/** + * In the poly fillet mode, all the inner handles are set to vector handles, along with the "outer" + * (previous and next) handles at each fillet. + */ +static void calculate_bezier_handles_poly_mode(const Span src_handles_l, + const Span src_handles_r, + const Span src_types_l, + const Span src_types_r, + const Span dst_offsets, + const Span dst_positions, + MutableSpan dst_handles_l, + MutableSpan dst_handles_r, + MutableSpan dst_types_l, + MutableSpan dst_types_r) +{ + const int i_dst_last = dst_positions.index_range().last(); + threading::parallel_for(src_handles_l.index_range(), 512, [&](IndexRange range) { + for (const int i_src : range) { + const IndexRange arc = bke::offsets_to_range(dst_offsets, i_src); + if (arc.size() == 1) { + dst_handles_l[arc.first()] = src_handles_l[i_src]; + dst_handles_r[arc.first()] = src_handles_r[i_src]; + dst_types_l[arc.first()] = src_types_l[i_src]; + dst_types_r[arc.first()] = src_types_r[i_src]; + continue; + } + + /* The fillet's next and previous handles are vector handles, as are the inner handles. */ + dst_types_l.slice(arc).fill(BEZIER_HANDLE_VECTOR); + dst_types_r.slice(arc).fill(BEZIER_HANDLE_VECTOR); + + /* Calculate the point's handles on the outside of the fillet segment. This point + * won't be selected for a fillet if it is the first or last in a non-cyclic curve. */ + + const int i_dst_prev = arc.first() == 0 ? i_dst_last : arc.one_before_start(); + const int i_dst_next = arc.last() == i_dst_last ? 0 : arc.one_after_last(); + dst_handles_l[arc.first()] = bke::curves::bezier::calculate_vector_handle( + dst_positions[arc.first()], dst_positions[i_dst_prev]); + dst_handles_r[arc.last()] = bke::curves::bezier::calculate_vector_handle( + dst_positions[arc.last()], dst_positions[i_dst_next]); + + /* Set the values for the inner handles. */ + const IndexRange middle = arc.drop_front(1).drop_back(1); + for (const int i : middle) { + dst_handles_r[i] = bke::curves::bezier::calculate_vector_handle(dst_positions[i], + dst_positions[i - 1]); + dst_handles_l[i] = bke::curves::bezier::calculate_vector_handle(dst_positions[i], + dst_positions[i + 1]); + } + } + }); +} + +static bke::CurvesGeometry fillet_curves(const bke::CurvesGeometry &src_curves, + const IndexMask curve_selection, + const VArray &radius_input, + const VArray &counts, + const bool limit_radius, + const bool use_bezier_mode) +{ + const Vector unselected_ranges = curve_selection.extract_ranges_invert( + src_curves.curves_range()); + + const Span positions = src_curves.positions(); + const VArraySpan cyclic{src_curves.cyclic()}; + const bke::AttributeAccessor src_attributes = src_curves.attributes(); + + bke::CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves); + /* Stores the offset of every result point for every original point. + * The extra length is used in order to store an extra zero for every curve. */ + Array dst_point_offsets(src_curves.points_num() + src_curves.curves_num()); + calculate_result_offsets(src_curves, + curve_selection, + unselected_ranges, + radius_input, + counts, + cyclic, + dst_curves.offsets_for_write(), + dst_point_offsets); + const Span point_offsets = dst_point_offsets.as_span(); + + dst_curves.resize(dst_curves.offsets().last(), dst_curves.curves_num()); + bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write(); + MutableSpan dst_positions = dst_curves.positions_for_write(); + + VArraySpan src_types_l; + VArraySpan src_types_r; + Span src_handles_l; + Span src_handles_r; + MutableSpan dst_types_l; + MutableSpan dst_types_r; + MutableSpan dst_handles_l; + MutableSpan dst_handles_r; + if (src_curves.has_curve_with_type(CURVE_TYPE_BEZIER)) { + src_types_l = src_curves.handle_types_left(); + src_types_r = src_curves.handle_types_right(); + src_handles_l = src_curves.handle_positions_left(); + src_handles_r = src_curves.handle_positions_right(); + + dst_types_l = dst_curves.handle_types_left_for_write(); + dst_types_r = dst_curves.handle_types_right_for_write(); + dst_handles_l = dst_curves.handle_positions_left_for_write(); + dst_handles_r = dst_curves.handle_positions_right_for_write(); + } + + threading::parallel_for(curve_selection.index_range(), 512, [&](IndexRange range) { + Array directions; + Array angles; + Array radii; + Array input_radii_buffer; + + for (const int curve_i : curve_selection.slice(range)) { + const IndexRange src_points = src_curves.points_for_curve(curve_i); + const Span offsets = point_offsets.slice(curve_dst_offsets(src_points, curve_i)); + const IndexRange dst_points = dst_curves.points_for_curve(curve_i); + const Span src_positions = positions.slice(src_points); + + directions.reinitialize(src_points.size()); + calculate_directions(src_positions, directions); + + angles.reinitialize(src_points.size()); + calculate_angles(directions, angles); + + radii.reinitialize(src_points.size()); + if (limit_radius) { + input_radii_buffer.reinitialize(src_points.size()); + radius_input.materialize_compressed(src_points, input_radii_buffer); + limit_radii(src_positions, angles, input_radii_buffer, cyclic[curve_i], radii); + } + else { + radius_input.materialize_compressed(src_points, radii); + } + + calculate_fillet_positions(positions.slice(src_points), + angles, + radii, + directions, + offsets, + dst_positions.slice(dst_points)); + + if (src_curves.has_curve_with_type(CURVE_TYPE_BEZIER)) { + if (use_bezier_mode) { + calculate_bezier_handles_bezier_mode(src_handles_l.slice(src_points), + src_handles_r.slice(src_points), + src_types_l.slice(src_points), + src_types_r.slice(src_points), + angles, + radii, + directions, + offsets, + dst_positions.slice(dst_points), + dst_handles_l.slice(dst_points), + dst_handles_r.slice(dst_points), + dst_types_l.slice(dst_points), + dst_types_r.slice(dst_points)); + } + else { + calculate_bezier_handles_poly_mode(src_handles_l.slice(src_points), + src_handles_r.slice(src_points), + src_types_l.slice(src_points), + src_types_r.slice(src_points), + offsets, + dst_positions.slice(dst_points), + dst_handles_l.slice(dst_points), + dst_handles_r.slice(dst_points), + dst_types_l.slice(dst_points), + dst_types_r.slice(dst_points)); + } + } + } + }); + + for (auto &attribute : bke::retrieve_attributes_for_transfer( + src_attributes, + dst_attributes, + ATTR_DOMAIN_MASK_POINT, + {"position", "handle_type_left", "handle_type_right", "handle_right", "handle_left"})) { + duplicate_fillet_point_data( + src_curves, dst_curves, curve_selection, point_offsets, attribute.src, attribute.dst.span); + attribute.dst.finish(); + } + + if (!unselected_ranges.is_empty()) { + for (auto &attribute : bke::retrieve_attributes_for_transfer( + src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT)) { + bke::curves::copy_point_data( + src_curves, dst_curves, unselected_ranges, attribute.src, attribute.dst.span); + attribute.dst.finish(); + } + } + + return dst_curves; +} + +bke::CurvesGeometry fillet_curves_poly(const bke::CurvesGeometry &src_curves, + const IndexMask curve_selection, + const VArray &radius, + const VArray &count, + const bool limit_radius) +{ + return fillet_curves(src_curves, curve_selection, radius, count, limit_radius, false); +} + +bke::CurvesGeometry fillet_curves_bezier(const bke::CurvesGeometry &src_curves, + const IndexMask curve_selection, + const VArray &radius, + const bool limit_radius) +{ + return fillet_curves(src_curves, + curve_selection, + radius, + VArray::ForSingle(1, src_curves.points_num()), + limit_radius, + true); +} + +} // namespace blender::geometry diff --git a/source/blender/geometry/intern/subdivide_curves.cc b/source/blender/geometry/intern/subdivide_curves.cc index b6476d19818..8a6f3cbd5e3 100644 --- a/source/blender/geometry/intern/subdivide_curves.cc +++ b/source/blender/geometry/intern/subdivide_curves.cc @@ -56,40 +56,6 @@ static void calculate_result_offsets(const bke::CurvesGeometry &src_curves, bke::curves::accumulate_counts_to_offsets(dst_curve_offsets); } -struct AttributeTransferData { - /* Expect that if an attribute exists, it is stored as a contiguous array internally anyway. */ - GVArraySpan src; - bke::GSpanAttributeWriter dst; -}; - -static Vector retrieve_point_attributes( - const bke::AttributeAccessor &src_attributes, - bke::MutableAttributeAccessor &dst_attributes, - const Set &skip = {}) -{ - Vector attributes; - src_attributes.for_all( - [&](const bke::AttributeIDRef &id, const bke::AttributeMetaData meta_data) { - if (meta_data.domain != ATTR_DOMAIN_POINT) { - /* Curve domain attributes are all copied directly to the result in one step. */ - return true; - } - if (id.is_named() && skip.contains(id.name())) { - return true; - } - - GVArray src = src_attributes.lookup(id, ATTR_DOMAIN_POINT); - BLI_assert(src); - bke::GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span( - id, ATTR_DOMAIN_POINT, meta_data.data_type); - BLI_assert(dst); - attributes.append({std::move(src), std::move(dst)}); - - return true; - }); - return attributes; -} - template static inline void linear_interpolation(const T &a, const T &b, MutableSpan dst) { @@ -365,7 +331,8 @@ bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves, bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write(); auto subdivide_catmull_rom = [&](IndexMask selection) { - for (auto &attribute : retrieve_point_attributes(src_attributes, dst_attributes)) { + for (auto &attribute : bke::retrieve_attributes_for_transfer( + src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT)) { subdivide_attribute_catmull_rom(src_curves, dst_curves, selection, @@ -378,7 +345,8 @@ bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves, }; auto subdivide_poly = [&](IndexMask selection) { - for (auto &attribute : retrieve_point_attributes(src_attributes, dst_attributes)) { + for (auto &attribute : bke::retrieve_attributes_for_transfer( + src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT)) { subdivide_attribute_linear( src_curves, dst_curves, selection, point_offsets, attribute.src, attribute.dst.span); attribute.dst.finish(); @@ -419,13 +387,14 @@ bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves, } }); - for (auto &attribute : retrieve_point_attributes(src_attributes, - dst_attributes, - {"position", - "handle_type_left", - "handle_type_right", - "handle_right", - "handle_left"})) { + for (auto &attribute : bke::retrieve_attributes_for_transfer(src_attributes, + dst_attributes, + ATTR_DOMAIN_MASK_POINT, + {"position", + "handle_type_left", + "handle_type_right", + "handle_right", + "handle_left"})) { subdivide_attribute_linear( src_curves, dst_curves, selection, point_offsets, attribute.src, attribute.dst.span); attribute.dst.finish(); @@ -445,7 +414,8 @@ bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves, subdivide_nurbs); if (!unselected_ranges.is_empty()) { - for (auto &attribute : retrieve_point_attributes(src_attributes, dst_attributes)) { + for (auto &attribute : bke::retrieve_attributes_for_transfer( + src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT)) { bke::curves::copy_point_data( src_curves, dst_curves, unselected_ranges, attribute.src, attribute.dst.span); attribute.dst.finish(); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc index dd8471d2dac..ab1f8269c39 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc @@ -1,17 +1,12 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include "BLI_task.hh" - #include "UI_interface.h" #include "UI_resources.h" -#include "DNA_node_types.h" +#include "GEO_fillet_curves.hh" #include "node_geometry_util.hh" -#include "BKE_curves.hh" -#include "BKE_spline.hh" - namespace blender::nodes::node_geo_curve_fillet_cc { NODE_STORAGE_FUNCS(NodeGeometryCurveFillet) @@ -45,574 +40,18 @@ static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) static void node_init(bNodeTree *UNUSED(tree), bNode *node) { NodeGeometryCurveFillet *data = MEM_cnew(__func__); - data->mode = GEO_NODE_CURVE_FILLET_BEZIER; node->storage = data; } -struct FilletParam { - GeometryNodeCurveFilletMode mode; - - /* Number of points to be added. */ - VArray counts; - - /* Radii for fillet arc at all vertices. */ - VArray radii; - - /* Whether or not fillets are allowed to overlap. */ - bool limit_radius; -}; - -/* A data structure used to store fillet data about all vertices to be filleted. */ -struct FilletData { - Span positions; - Array directions, axes; - Array radii, angles; - Array counts; -}; - static void node_update(bNodeTree *ntree, bNode *node) { const NodeGeometryCurveFillet &storage = node_storage(*node); const GeometryNodeCurveFilletMode mode = (GeometryNodeCurveFilletMode)storage.mode; - bNodeSocket *poly_socket = ((bNodeSocket *)node->inputs.first)->next; - nodeSetSocketAvailability(ntree, poly_socket, mode == GEO_NODE_CURVE_FILLET_POLY); } -/* Function to get the center of a fillet. */ -static float3 get_center(const float3 vec_pos2prev, - const float3 pos, - const float3 axis, - const float angle) -{ - float3 vec_pos2center; - rotate_normalized_v3_v3v3fl(vec_pos2center, vec_pos2prev, axis, M_PI_2 - angle / 2.0f); - vec_pos2center *= 1.0f / sinf(angle / 2.0f); - - return vec_pos2center + pos; -} - -/* Function to get the center of the fillet using fillet data */ -static float3 get_center(const float3 vec_pos2prev, const FilletData &fd, const int index) -{ - const float angle = fd.angles[index]; - const float3 axis = fd.axes[index]; - const float3 pos = fd.positions[index]; - - return get_center(vec_pos2prev, pos, axis, angle); -} - -/* Calculate the direction vectors from each vertex to their previous vertex. */ -static Array calculate_directions(const Span positions) -{ - const int num = positions.size(); - Array directions(num); - - for (const int i : IndexRange(num - 1)) { - directions[i] = math::normalize(positions[i + 1] - positions[i]); - } - directions[num - 1] = math::normalize(positions[0] - positions[num - 1]); - - return directions; -} - -/* Calculate the axes around which the fillet is built. */ -static Array calculate_axes(const Span directions) -{ - const int num = directions.size(); - Array axes(num); - - axes[0] = math::normalize(math::cross(-directions[num - 1], directions[0])); - for (const int i : IndexRange(1, num - 1)) { - axes[i] = math::normalize(math::cross(-directions[i - 1], directions[i])); - } - - return axes; -} - -/* Calculate the angle of the arc formed by the fillet. */ -static Array calculate_angles(const Span directions) -{ - const int num = directions.size(); - Array angles(num); - - angles[0] = M_PI - angle_v3v3(-directions[num - 1], directions[0]); - for (const int i : IndexRange(1, num - 1)) { - angles[i] = M_PI - angle_v3v3(-directions[i - 1], directions[i]); - } - - return angles; -} - -/* Calculate the segment count in each filleted arc. */ -static Array calculate_counts(const FilletParam &fillet_param, - const int num, - const int spline_offset, - const bool cyclic) -{ - Array counts(num, 1); - if (fillet_param.mode == GEO_NODE_CURVE_FILLET_POLY) { - for (const int i : IndexRange(num)) { - counts[i] = fillet_param.counts[spline_offset + i]; - } - } - if (!cyclic) { - counts[0] = counts[num - 1] = 0; - } - - return counts; -} - -/* Calculate the radii for the vertices to be filleted. */ -static Array calculate_radii(const FilletParam &fillet_param, - const int num, - const int spline_offset) -{ - Array radii(num, 0.0f); - if (fillet_param.limit_radius) { - for (const int i : IndexRange(num)) { - radii[i] = std::max(fillet_param.radii[spline_offset + i], 0.0f); - } - } - else { - for (const int i : IndexRange(num)) { - radii[i] = fillet_param.radii[spline_offset + i]; - } - } - - return radii; -} - -/* Calculate the number of vertices added per vertex on the source spline. */ -static int calculate_point_counts(MutableSpan point_counts, - const Span radii, - const Span counts) -{ - int added_count = 0; - for (const int i : IndexRange(point_counts.size())) { - /* Calculate number of points to be added for the vertex. */ - if (radii[i] != 0.0f) { - added_count += counts[i]; - point_counts[i] = counts[i] + 1; - } - } - - return added_count; -} - -static FilletData calculate_fillet_data(const Spline &spline, - const FilletParam &fillet_param, - int &added_count, - MutableSpan point_counts, - const int spline_offset) -{ - const int num = spline.size(); - - FilletData fd; - fd.directions = calculate_directions(spline.positions()); - fd.positions = spline.positions(); - fd.axes = calculate_axes(fd.directions); - fd.angles = calculate_angles(fd.directions); - fd.counts = calculate_counts(fillet_param, num, spline_offset, spline.is_cyclic()); - fd.radii = calculate_radii(fillet_param, num, spline_offset); - - added_count = calculate_point_counts(point_counts, fd.radii, fd.counts); - - return fd; -} - -/* Limit the radius based on angle and radii to prevent overlapping. */ -static void limit_radii(FilletData &fd, const bool cyclic) -{ - MutableSpan radii(fd.radii); - Span angles(fd.angles); - Span positions(fd.positions); - - const int num = radii.size(); - const int fillet_count = cyclic ? num : num - 2; - const int start = cyclic ? 0 : 1; - Array max_radii(num, FLT_MAX); - - if (cyclic) { - /* Calculate lengths between adjacent control points. */ - const float len_prev = math::distance(positions[0], positions[num - 1]); - const float len_next = math::distance(positions[0], positions[1]); - - /* Calculate tangent lengths of fillets in control points. */ - const float tan_len = radii[0] * tan(angles[0] / 2.0f); - const float tan_len_prev = radii[num - 1] * tan(angles[num - 1] / 2.0f); - const float tan_len_next = radii[1] * tan(angles[1] / 2.0f); - - float factor_prev = 1.0f, factor_next = 1.0f; - if (tan_len + tan_len_prev > len_prev) { - factor_prev = len_prev / (tan_len + tan_len_prev); - } - if (tan_len + tan_len_next > len_next) { - factor_next = len_next / (tan_len + tan_len_next); - } - - /* Scale max radii by calculated factors. */ - max_radii[0] = radii[0] * std::min(factor_next, factor_prev); - max_radii[1] = radii[1] * factor_next; - max_radii[num - 1] = radii[num - 1] * factor_prev; - } - - /* Initialize max_radii to largest possible radii. */ - float prev_dist = math::distance(positions[1], positions[0]); - for (const int i : IndexRange(1, num - 2)) { - const float temp_dist = math::distance(positions[i], positions[i + 1]); - max_radii[i] = std::min(prev_dist, temp_dist) / tan(angles[i] / 2.0f); - prev_dist = temp_dist; - } - - /* Max radii calculations for each index. */ - for (const int i : IndexRange(start, fillet_count - 1)) { - const float len_next = math::distance(positions[i], positions[i + 1]); - const float tan_len = radii[i] * tan(angles[i] / 2.0f); - const float tan_len_next = radii[i + 1] * tan(angles[i + 1] / 2.0f); - - /* Scale down radii if too large for segment. */ - float factor = 1.0f; - if (tan_len + tan_len_next > len_next) { - factor = len_next / (tan_len + tan_len_next); - } - max_radii[i] = std::min(max_radii[i], radii[i] * factor); - max_radii[i + 1] = std::min(max_radii[i + 1], radii[i + 1] * factor); - } - - /* Assign the max_radii to the fillet data's radii. */ - for (const int i : IndexRange(num)) { - radii[i] = std::min(radii[i], max_radii[i]); - } -} - -/* - * Create a mapping from each vertex in the destination spline to that of the source spline. - * Used for copying the data from the source spline. - */ -static Array create_dst_to_src_map(const Span point_counts, const int total_points) -{ - Array map(total_points); - MutableSpan map_span{map}; - int index = 0; - - for (const int i : point_counts.index_range()) { - map_span.slice(index, point_counts[i]).fill(i); - index += point_counts[i]; - } - - BLI_assert(index == total_points); - - return map; -} - -template -static void copy_attribute_by_mapping(const Span src, - MutableSpan dst, - const Span mapping) -{ - for (const int i : dst.index_range()) { - dst[i] = src[mapping[i]]; - } -} - -/* Copy radii and tilts from source spline to destination. Positions are handled later in update - * positions methods. */ -static void copy_common_attributes_by_mapping(const Spline &src, - Spline &dst, - const Span mapping) -{ - copy_attribute_by_mapping(src.radii(), dst.radii(), mapping); - copy_attribute_by_mapping(src.tilts(), dst.tilts(), mapping); - - src.attributes.foreach_attribute( - [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { - std::optional src_attribute = src.attributes.get_for_read(attribute_id); - if (dst.attributes.create(attribute_id, meta_data.data_type)) { - std::optional dst_attribute = dst.attributes.get_for_write(attribute_id); - if (dst_attribute) { - attribute_math::convert_to_static_type(dst_attribute->type(), [&](auto dummy) { - using T = decltype(dummy); - copy_attribute_by_mapping( - src_attribute->typed(), dst_attribute->typed(), mapping); - }); - return true; - } - } - BLI_assert_unreachable(); - return false; - }, - ATTR_DOMAIN_POINT); -} - -/* Update the vertex positions and handle positions of a Bezier spline based on fillet data. */ -static void update_bezier_positions(const FilletData &fd, - BezierSpline &dst_spline, - const BezierSpline &src_spline, - const Span point_counts) -{ - Span radii(fd.radii); - Span angles(fd.angles); - Span axes(fd.axes); - Span positions(fd.positions); - Span directions(fd.directions); - - const int num = radii.size(); - - int i_dst = 0; - for (const int i_src : IndexRange(num)) { - const int count = point_counts[i_src]; - - /* Skip if the point count for the vertex is 1. */ - if (count == 1) { - dst_spline.positions()[i_dst] = src_spline.positions()[i_src]; - dst_spline.handle_types_left()[i_dst] = src_spline.handle_types_left()[i_src]; - dst_spline.handle_types_right()[i_dst] = src_spline.handle_types_right()[i_src]; - dst_spline.handle_positions_left()[i_dst] = src_spline.handle_positions_left()[i_src]; - dst_spline.handle_positions_right()[i_dst] = src_spline.handle_positions_right()[i_src]; - i_dst++; - continue; - } - - /* Calculate the angle to be formed between any 2 adjacent vertices within the fillet. */ - const float segment_angle = angles[i_src] / (count - 1); - /* Calculate the handle length for each added vertex. Equation: L = 4R/3 * tan(A/4) */ - const float handle_length = 4.0f * radii[i_src] / 3.0f * tan(segment_angle / 4.0f); - /* Calculate the distance by which each vertex should be displaced from their initial position. - */ - const float displacement = radii[i_src] * tan(angles[i_src] / 2.0f); - - /* Position the end points of the arc and their handles. */ - const int end_i = i_dst + count - 1; - const float3 prev_dir = i_src == 0 ? -directions[num - 1] : -directions[i_src - 1]; - const float3 next_dir = directions[i_src]; - dst_spline.positions()[i_dst] = positions[i_src] + displacement * prev_dir; - dst_spline.positions()[end_i] = positions[i_src] + displacement * next_dir; - dst_spline.handle_positions_right()[i_dst] = dst_spline.positions()[i_dst] - - handle_length * prev_dir; - dst_spline.handle_positions_left()[end_i] = dst_spline.positions()[end_i] - - handle_length * next_dir; - dst_spline.handle_types_right()[i_dst] = dst_spline.handle_types_left()[end_i] = - BEZIER_HANDLE_ALIGN; - dst_spline.handle_types_left()[i_dst] = dst_spline.handle_types_right()[end_i] = - BEZIER_HANDLE_VECTOR; - dst_spline.mark_cache_invalid(); - - /* Calculate the center of the radius to be formed. */ - const float3 center = get_center(dst_spline.positions()[i_dst] - positions[i_src], fd, i_src); - /* Calculate the vector of the radius formed by the first vertex. */ - float3 radius_vec = dst_spline.positions()[i_dst] - center; - float radius; - radius_vec = math::normalize_and_get_length(radius_vec, radius); - - dst_spline.handle_types_right().slice(1, count - 2).fill(BEZIER_HANDLE_ALIGN); - dst_spline.handle_types_left().slice(1, count - 2).fill(BEZIER_HANDLE_ALIGN); - - /* For each of the vertices in between the end points. */ - for (const int j : IndexRange(1, count - 2)) { - int index = i_dst + j; - /* Rotate the radius by the segment angle and determine its tangent (used for getting handle - * directions). */ - float3 new_radius_vec, tangent_vec; - rotate_normalized_v3_v3v3fl(new_radius_vec, radius_vec, -axes[i_src], segment_angle); - rotate_normalized_v3_v3v3fl(tangent_vec, new_radius_vec, axes[i_src], M_PI_2); - radius_vec = new_radius_vec; - tangent_vec *= handle_length; - - /* Adjust the positions of the respective vertex and its handles. */ - dst_spline.positions()[index] = center + new_radius_vec * radius; - dst_spline.handle_positions_left()[index] = dst_spline.positions()[index] + tangent_vec; - dst_spline.handle_positions_right()[index] = dst_spline.positions()[index] - tangent_vec; - } - - i_dst += count; - } -} - -/* Update the vertex positions of a Poly spline based on fillet data. */ -static void update_poly_positions(const FilletData &fd, - Spline &dst_spline, - const Spline &src_spline, - const Span point_counts) -{ - Span radii(fd.radii); - Span angles(fd.angles); - Span axes(fd.axes); - Span positions(fd.positions); - Span directions(fd.directions); - - const int num = radii.size(); - - int i_dst = 0; - for (const int i_src : IndexRange(num)) { - const int count = point_counts[i_src]; - - /* Skip if the point count for the vertex is 1. */ - if (count == 1) { - dst_spline.positions()[i_dst] = src_spline.positions()[i_src]; - i_dst++; - continue; - } - - const float segment_angle = angles[i_src] / (count - 1); - const float displacement = radii[i_src] * tan(angles[i_src] / 2.0f); - - /* Position the end points of the arc. */ - const int end_i = i_dst + count - 1; - const float3 prev_dir = i_src == 0 ? -directions[num - 1] : -directions[i_src - 1]; - const float3 next_dir = directions[i_src]; - dst_spline.positions()[i_dst] = positions[i_src] + displacement * prev_dir; - dst_spline.positions()[end_i] = positions[i_src] + displacement * next_dir; - - /* Calculate the center of the radius to be formed. */ - const float3 center = get_center(dst_spline.positions()[i_dst] - positions[i_src], fd, i_src); - /* Calculate the vector of the radius formed by the first vertex. */ - float3 radius_vec = dst_spline.positions()[i_dst] - center; - - for (const int j : IndexRange(1, count - 2)) { - /* Rotate the radius by the segment angle */ - float3 new_radius_vec; - rotate_normalized_v3_v3v3fl(new_radius_vec, radius_vec, -axes[i_src], segment_angle); - radius_vec = new_radius_vec; - - dst_spline.positions()[i_dst + j] = center + new_radius_vec; - } - - i_dst += count; - } -} - -static SplinePtr fillet_spline(const Spline &spline, - const FilletParam &fillet_param, - const int spline_offset) -{ - const int num = spline.size(); - const bool cyclic = spline.is_cyclic(); - - if (num < 3) { - return spline.copy(); - } - - /* Initialize the point_counts with 1s (at least one vertex on dst for each vertex on src). */ - Array point_counts(num, 1); - - int added_count = 0; - /* Update point_counts array and added_count. */ - FilletData fd = calculate_fillet_data( - spline, fillet_param, added_count, point_counts, spline_offset); - if (fillet_param.limit_radius) { - limit_radii(fd, cyclic); - } - - const int total_points = added_count + num; - const Array dst_to_src = create_dst_to_src_map(point_counts, total_points); - SplinePtr dst_spline_ptr = spline.copy_only_settings(); - (*dst_spline_ptr).resize(total_points); - copy_common_attributes_by_mapping(spline, *dst_spline_ptr, dst_to_src); - - switch (spline.type()) { - case CURVE_TYPE_BEZIER: { - const BezierSpline &src_spline = static_cast(spline); - BezierSpline &dst_spline = static_cast(*dst_spline_ptr); - if (fillet_param.mode == GEO_NODE_CURVE_FILLET_POLY) { - dst_spline.handle_types_left().fill(BEZIER_HANDLE_VECTOR); - dst_spline.handle_types_right().fill(BEZIER_HANDLE_VECTOR); - update_poly_positions(fd, dst_spline, src_spline, point_counts); - } - else { - update_bezier_positions(fd, dst_spline, src_spline, point_counts); - } - break; - } - case CURVE_TYPE_POLY: { - update_poly_positions(fd, *dst_spline_ptr, spline, point_counts); - break; - } - case CURVE_TYPE_NURBS: { - const NURBSpline &src_spline = static_cast(spline); - NURBSpline &dst_spline = static_cast(*dst_spline_ptr); - copy_attribute_by_mapping(src_spline.weights(), dst_spline.weights(), dst_to_src); - update_poly_positions(fd, dst_spline, src_spline, point_counts); - break; - } - case CURVE_TYPE_CATMULL_ROM: { - BLI_assert_unreachable(); - break; - } - } - - return dst_spline_ptr; -} - -static std::unique_ptr fillet_curve(const CurveEval &input_curve, - const FilletParam &fillet_param) -{ - Span input_splines = input_curve.splines(); - - std::unique_ptr output_curve = std::make_unique(); - const int splines_num = input_splines.size(); - output_curve->resize(splines_num); - MutableSpan output_splines = output_curve->splines(); - Array spline_offsets = input_curve.control_point_offsets(); - - threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { - for (const int i : range) { - output_splines[i] = fillet_spline(*input_splines[i], fillet_param, spline_offsets[i]); - } - }); - output_curve->attributes = input_curve.attributes; - - return output_curve; -} - -static void calculate_curve_fillet(GeometrySet &geometry_set, - const GeometryNodeCurveFilletMode mode, - const Field &radius_field, - const std::optional> &count_field, - const bool limit_radius) -{ - if (!geometry_set.has_curves()) { - return; - } - - FilletParam fillet_param; - fillet_param.mode = mode; - - CurveComponent &component = geometry_set.get_component_for_write(); - GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; - const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT); - fn::FieldEvaluator field_evaluator{field_context, domain_size}; - - field_evaluator.add(radius_field); - - if (mode == GEO_NODE_CURVE_FILLET_POLY) { - field_evaluator.add(*count_field); - } - - field_evaluator.evaluate(); - - fillet_param.radii = field_evaluator.get_evaluated(0); - if (fillet_param.radii.is_single() && fillet_param.radii.get_internal_single() < 0.0f) { - return; - } - - if (mode == GEO_NODE_CURVE_FILLET_POLY) { - fillet_param.counts = field_evaluator.get_evaluated(1); - } - - fillet_param.limit_radius = limit_radius; - - const Curves &src_curves_id = *geometry_set.get_curves_for_read(); - const std::unique_ptr input_curve = curves_to_curve_eval(*component.get_for_read()); - std::unique_ptr output_curve = fillet_curve(*input_curve, fillet_param); - - Curves *dst_curves_id = curve_eval_to_curves(*output_curve); - bke::curves_copy_parameters(src_curves_id, *dst_curves_id); - geometry_set.replace_curves(dst_curves_id); -} - static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input("Curve"); @@ -629,7 +68,42 @@ static void node_geo_exec(GeoNodeExecParams params) } geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { - calculate_curve_fillet(geometry_set, mode, radius_field, count_field, limit_radius); + if (!geometry_set.has_curves()) { + return; + } + + const CurveComponent &component = *geometry_set.get_component_for_read(); + const Curves &curves_id = *component.get_for_read(); + const bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry); + GeometryComponentFieldContext context{component, ATTR_DOMAIN_POINT}; + fn::FieldEvaluator evaluator{context, curves.points_num()}; + evaluator.add(radius_field); + + switch (mode) { + case GEO_NODE_CURVE_FILLET_BEZIER: { + evaluator.evaluate(); + bke::CurvesGeometry dst_curves = geometry::fillet_curves_bezier( + curves, curves.curves_range(), evaluator.get_evaluated(0), limit_radius); + Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); + bke::curves_copy_parameters(curves_id, *dst_curves_id); + geometry_set.replace_curves(dst_curves_id); + break; + } + case GEO_NODE_CURVE_FILLET_POLY: { + evaluator.add(*count_field); + evaluator.evaluate(); + bke::CurvesGeometry dst_curves = geometry::fillet_curves_poly( + curves, + curves.curves_range(), + evaluator.get_evaluated(0), + evaluator.get_evaluated(1), + limit_radius); + Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); + bke::curves_copy_parameters(curves_id, *dst_curves_id); + geometry_set.replace_curves(dst_curves_id); + break; + } + } }); params.set_output("Curve", std::move(geometry_set)); -- cgit v1.2.3 From 215f805ce6b540177dedd71721e62c56a764a5ea Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 19 Jul 2022 21:48:32 -0500 Subject: Curves: Remove use of CurveEval in sculpt brushes This commit removes the use of PolySpline for resampling curves and replaces it with the length parameterization utility for that purpose. I didn't test performance, but I would expect the shrinking to be slightly faster because I reused some arrays to avoid allocating them for every curve. I noted some potential improvements in the "add curves" function. Differential Revision: https://developer.blender.org/D15342 --- .../sculpt_paint/curves_sculpt_grow_shrink.cc | 73 ++++++++++++---------- .../blender/geometry/intern/add_curves_on_mesh.cc | 67 ++++++++++++-------- 2 files changed, 80 insertions(+), 60 deletions(-) diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc b/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc index 709ecc79967..408139d6653 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc @@ -4,8 +4,7 @@ #include "BLI_enumerable_thread_specific.hh" #include "BLI_float4x4.hh" -#include "BLI_kdtree.h" -#include "BLI_rand.hh" +#include "BLI_length_parameterize.hh" #include "BLI_vector.hh" #include "PIL_time.h" @@ -14,19 +13,13 @@ #include "BKE_attribute_math.hh" #include "BKE_brush.h" -#include "BKE_bvhutils.h" #include "BKE_context.h" #include "BKE_curves.hh" -#include "BKE_mesh.h" -#include "BKE_mesh_runtime.h" #include "BKE_paint.h" -#include "BKE_spline.hh" #include "DNA_brush_enums.h" #include "DNA_brush_types.h" #include "DNA_curves_types.h" -#include "DNA_mesh_types.h" -#include "DNA_meshdata_types.h" #include "DNA_object_types.h" #include "DNA_screen_types.h" #include "DNA_space_types.h" @@ -70,6 +63,24 @@ class ShrinkCurvesEffect : public CurvesEffect { private: const Brush &brush_; + /** Storage of per-curve parameterization data to avoid reallocation. */ + struct ParameterizationBuffers { + Array old_positions; + Array old_lengths; + Array sample_lengths; + Array indices; + Array factors; + + void reinitialize(const int points_num) + { + this->old_positions.reinitialize(points_num); + this->old_lengths.reinitialize(length_parameterize::segments_num(points_num, false)); + this->sample_lengths.reinitialize(points_num); + this->indices.reinitialize(points_num); + this->factors.reinitialize(points_num); + } + }; + public: ShrinkCurvesEffect(const Brush &brush) : brush_(brush) { @@ -81,46 +92,42 @@ class ShrinkCurvesEffect : public CurvesEffect { { MutableSpan positions_cu = curves.positions_for_write(); threading::parallel_for(curve_indices.index_range(), 256, [&](const IndexRange range) { + ParameterizationBuffers data; for (const int influence_i : range) { const int curve_i = curve_indices[influence_i]; const float move_distance_cu = move_distances_cu[influence_i]; - const IndexRange curve_points = curves.points_for_curve(curve_i); - this->shrink_curve(positions_cu, curve_points, move_distance_cu); + const IndexRange points = curves.points_for_curve(curve_i); + this->shrink_curve(positions_cu.slice(points), move_distance_cu, data); } }); } + private: void shrink_curve(MutableSpan positions, - const IndexRange curve_points, - const float shrink_length) const + const float shrink_length, + ParameterizationBuffers &data) const { - PolySpline spline; - spline.resize(curve_points.size()); - MutableSpan spline_positions = spline.positions(); - spline_positions.copy_from(positions.slice(curve_points)); - spline.mark_cache_invalid(); + namespace lp = length_parameterize; + data.reinitialize(positions.size()); + + /* Copy the old positions to facilitate mixing from neighbors for the resulting curve. */ + data.old_positions.as_mutable_span().copy_from(positions); + + lp::accumulate_lengths(data.old_positions, false, data.old_lengths); + const float min_length = brush_.curves_sculpt_settings->minimum_length; - const float old_length = spline.length(); + const float old_length = data.old_lengths.last(); const float new_length = std::max(min_length, old_length - shrink_length); const float length_factor = std::clamp(new_length / old_length, 0.0f, 1.0f); - Vector old_point_lengths; - old_point_lengths.append(0.0f); - for (const int i : spline_positions.index_range().drop_back(1)) { - const float3 &p1 = spline_positions[i]; - const float3 &p2 = spline_positions[i + 1]; - const float length = math::distance(p1, p2); - old_point_lengths.append(old_point_lengths.last() + length); + data.sample_lengths.first() = 0.0f; + for (const int i : data.old_lengths.index_range()) { + data.sample_lengths[i + 1] = data.old_lengths[i] * length_factor; } - for (const int i : spline_positions.index_range()) { - const float eval_length = old_point_lengths[i] * length_factor; - const Spline::LookupResult lookup = spline.lookup_evaluated_length(eval_length); - const float index_factor = lookup.evaluated_index + lookup.factor; - float3 p; - spline.sample_with_index_factors(spline_positions, {&index_factor, 1}, {&p, 1}); - positions[curve_points[i]] = p; - } + lp::sample_at_lengths(data.old_lengths, data.sample_lengths, data.indices, data.factors); + + lp::linear_interpolation(data.old_positions, data.indices, data.factors, positions); } }; diff --git a/source/blender/geometry/intern/add_curves_on_mesh.cc b/source/blender/geometry/intern/add_curves_on_mesh.cc index 34551bd474f..a69073af207 100644 --- a/source/blender/geometry/intern/add_curves_on_mesh.cc +++ b/source/blender/geometry/intern/add_curves_on_mesh.cc @@ -1,7 +1,9 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "BLI_length_parameterize.hh" + +#include "BKE_attribute_math.hh" #include "BKE_mesh_sample.hh" -#include "BKE_spline.hh" #include "GEO_add_curves_on_mesh.hh" @@ -145,16 +147,16 @@ static void interpolate_position_with_interpolation(CurvesGeometry &curves, const int added_curves_num = root_positions_cu.size(); threading::parallel_for(IndexRange(added_curves_num), 256, [&](const IndexRange range) { - for (const int i : range) { - const NeighborCurves &neighbors = neighbors_per_curve[i]; - const int curve_i = old_curves_num + i; + for (const int added_curve_i : range) { + const NeighborCurves &neighbors = neighbors_per_curve[added_curve_i]; + const int curve_i = old_curves_num + added_curve_i; const IndexRange points = curves.points_for_curve(curve_i); - const float length_cu = new_lengths_cu[i]; - const float3 &normal_su = new_normals_su[i]; + const float length_cu = new_lengths_cu[added_curve_i]; + const float3 &normal_su = new_normals_su[added_curve_i]; const float3 normal_cu = math::normalize(surface_to_curves_normal_mat * normal_su); - const float3 &root_cu = root_positions_cu[i]; + const float3 &root_cu = root_positions_cu[added_curve_i]; if (neighbors.is_empty()) { /* If there are no neighbors, just make a straight line. */ @@ -197,30 +199,41 @@ static void interpolate_position_with_interpolation(CurvesGeometry &curves, const IndexRange neighbor_points = curves.points_for_curve(neighbor_curve_i); const float3 &neighbor_root_cu = positions_cu[neighbor_points[0]]; - /* Use a temporary #PolySpline, because that's the easiest way to resample an - * existing curve right now. Resampling is necessary if the length of the new curve - * does not match the length of the neighbors or the number of handle points is - * different. */ - PolySpline neighbor_spline; - neighbor_spline.resize(neighbor_points.size()); - neighbor_spline.positions().copy_from(positions_cu.slice(neighbor_points)); - neighbor_spline.mark_cache_invalid(); + /* Sample the positions on neighbors and mix them into the final positions of the curve. + * Resampling is necessary if the length of the new curve does not match the length of the + * neighbors or the number of handle points is different. + * + * TODO: The lengths can be cached so they aren't recomputed if a curve is a neighbor for + * multiple new curves. Also, allocations could be avoided by reusing some arrays. */ + + const Span neighbor_positions_cu = positions_cu.slice(neighbor_points); + if (neighbor_positions_cu.size() == 1) { + /* Skip interpolating positions from neighbors with only one point. */ + continue; + } + Array lengths(length_parameterize::segments_num(neighbor_points.size(), false)); + length_parameterize::accumulate_lengths(neighbor_positions_cu, false, lengths); + const float neighbor_length_cu = lengths.last(); - const float neighbor_length_cu = neighbor_spline.length(); + Array sample_lengths(points.size()); const float length_factor = std::min(1.0f, length_cu / neighbor_length_cu); - const float resample_factor = (1.0f / (points.size() - 1.0f)) * length_factor; - for (const int j : IndexRange(points.size())) { - const Spline::LookupResult lookup = neighbor_spline.lookup_evaluated_factor( - j * resample_factor); - const float index_factor = lookup.evaluated_index + lookup.factor; - float3 p; - neighbor_spline.sample_with_index_factors( - neighbor_spline.positions(), {&index_factor, 1}, {&p, 1}); - const float3 relative_coord = p - neighbor_root_cu; - float3 rotated_relative_coord = relative_coord; + for (const int i : sample_lengths.index_range()) { + sample_lengths[i] = i * resample_factor * neighbor_length_cu; + } + + Array indices(points.size()); + Array factors(points.size()); + length_parameterize::sample_at_lengths(lengths, sample_lengths, indices, factors); + + for (const int i : IndexRange(points.size())) { + const float3 sample_cu = math::interpolate(neighbor_positions_cu[indices[i]], + neighbor_positions_cu[indices[i] + 1], + factors[i]); + const float3 relative_to_root_cu = sample_cu - neighbor_root_cu; + float3 rotated_relative_coord = relative_to_root_cu; mul_m3_v3(normal_rotation_cu, rotated_relative_coord); - positions_cu[points[j]] += neighbor.weight * rotated_relative_coord; + positions_cu[points[i]] += neighbor.weight * rotated_relative_coord; } } } -- cgit v1.2.3 From c3b9a4e001057f9f9ec9296b53dd9cca86dfc21c Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 19 Jul 2022 22:34:32 -0500 Subject: Cleanup: Access attributes with new API instead of geometry components Remove the boilerplate of using a local geometry component just to use the attribute API that was necessary before b876ce2a4a4638142439. --- .../geometry/GEO_point_merge_by_distance.hh | 2 +- .../geometry/intern/point_merge_by_distance.cc | 13 +- .../geometry/nodes/node_geo_delete_geometry.cc | 114 ++++++------ .../nodes/geometry/nodes/node_geo_dual_mesh.cc | 13 +- .../geometry/nodes/node_geo_duplicate_elements.cc | 207 +++++++++++---------- .../nodes/geometry/nodes/node_geo_extrude_mesh.cc | 5 +- .../geometry/nodes/node_geo_merge_by_distance.cc | 2 +- 7 files changed, 181 insertions(+), 175 deletions(-) diff --git a/source/blender/geometry/GEO_point_merge_by_distance.hh b/source/blender/geometry/GEO_point_merge_by_distance.hh index 1e977cf3bdc..92d871c62ab 100644 --- a/source/blender/geometry/GEO_point_merge_by_distance.hh +++ b/source/blender/geometry/GEO_point_merge_by_distance.hh @@ -17,7 +17,7 @@ namespace blender::geometry { * Merge selected points into other selected points within the \a merge_distance. The merged * indices favor speed over accuracy, since the results will depend on the order of the points. */ -PointCloud *point_merge_by_distance(const PointCloudComponent &src_points, +PointCloud *point_merge_by_distance(const PointCloud &src_points, const float merge_distance, const IndexMask selection); diff --git a/source/blender/geometry/intern/point_merge_by_distance.cc b/source/blender/geometry/intern/point_merge_by_distance.cc index ac10f4ef00c..42fac849667 100644 --- a/source/blender/geometry/intern/point_merge_by_distance.cc +++ b/source/blender/geometry/intern/point_merge_by_distance.cc @@ -13,13 +13,12 @@ namespace blender::geometry { -PointCloud *point_merge_by_distance(const PointCloudComponent &src_points, +PointCloud *point_merge_by_distance(const PointCloud &src_points, const float merge_distance, const IndexMask selection) { - const PointCloud &src_pointcloud = *src_points.get_for_read(); - bke::AttributeAccessor attributes = bke::pointcloud_attributes(src_pointcloud); - VArraySpan positions = attributes.lookup_or_default( + const bke::AttributeAccessor src_attributes = bke::pointcloud_attributes(src_points); + VArraySpan positions = src_attributes.lookup_or_default( "position", ATTR_DOMAIN_POINT, float3(0)); const int src_size = positions.size(); @@ -42,8 +41,8 @@ PointCloud *point_merge_by_distance(const PointCloudComponent &src_points, /* Create the new point cloud and add it to a temporary component for the attribute API. */ const int dst_size = src_size - duplicate_count; PointCloud *dst_pointcloud = BKE_pointcloud_new_nomain(dst_size); - PointCloudComponent dst_points; - dst_points.replace(dst_pointcloud, GeometryOwnershipType::Editable); + bke::MutableAttributeAccessor dst_attributes = bke::pointcloud_attributes_for_write( + *dst_pointcloud); /* By default, every point is just "merged" with itself. Then fill in the results of the merge * finding, converting from indices into the selection to indices into the full input point @@ -106,8 +105,6 @@ PointCloud *point_merge_by_distance(const PointCloudComponent &src_points, point_merge_counts[dst_index]++; } - const bke::AttributeAccessor src_attributes = *src_points.attributes(); - bke::MutableAttributeAccessor dst_attributes = *dst_points.attributes_for_write(); Set attribute_ids = src_attributes.all_ids(); /* Transfer the ID attribute if it exists, using the ID of the first merged point. */ diff --git a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc index 408596b65aa..582bcbc3395 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc @@ -43,13 +43,13 @@ static void copy_data_based_on_map(Span src, MutableSpan dst, Span in * Copies the attributes with a domain in `domains` to `result_component`. */ static void copy_attributes(const Map &attributes, - const GeometryComponent &in_component, - GeometryComponent &result_component, + const bke::AttributeAccessor src_attributes, + bke::MutableAttributeAccessor dst_attributes, const Span domains) { for (Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; - GAttributeReader attribute = in_component.attributes()->lookup(attribute_id); + GAttributeReader attribute = src_attributes.lookup(attribute_id); if (!attribute) { continue; } @@ -60,9 +60,8 @@ static void copy_attributes(const Map &attributes } const eCustomDataType data_type = bke::cpp_type_to_custom_data_type(attribute.varray.type()); - GSpanAttributeWriter result_attribute = - result_component.attributes_for_write()->lookup_or_add_for_write_only_span( - attribute_id, attribute.domain, data_type); + GSpanAttributeWriter result_attribute = dst_attributes.lookup_or_add_for_write_only_span( + attribute_id, attribute.domain, data_type); if (!result_attribute) { continue; @@ -83,14 +82,14 @@ static void copy_attributes(const Map &attributes * the mask to `result_component`. */ static void copy_attributes_based_on_mask(const Map &attributes, - const GeometryComponent &in_component, - GeometryComponent &result_component, + const bke::AttributeAccessor src_attributes, + bke::MutableAttributeAccessor dst_attributes, const eAttrDomain domain, const IndexMask mask) { for (Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; - GAttributeReader attribute = in_component.attributes()->lookup(attribute_id); + GAttributeReader attribute = src_attributes.lookup(attribute_id); if (!attribute) { continue; } @@ -101,9 +100,8 @@ static void copy_attributes_based_on_mask(const Maplookup_or_add_for_write_only_span( - attribute_id, attribute.domain, data_type); + GSpanAttributeWriter result_attribute = dst_attributes.lookup_or_add_for_write_only_span( + attribute_id, attribute.domain, data_type); if (!result_attribute) { continue; @@ -120,14 +118,14 @@ static void copy_attributes_based_on_mask(const Map &attributes, - const GeometryComponent &in_component, - GeometryComponent &result_component, + const bke::AttributeAccessor src_attributes, + bke::MutableAttributeAccessor dst_attributes, const eAttrDomain domain, const Span index_map) { for (Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; - GAttributeReader attribute = in_component.attributes()->lookup(attribute_id); + GAttributeReader attribute = src_attributes.lookup(attribute_id); if (!attribute) { continue; } @@ -138,9 +136,8 @@ static void copy_attributes_based_on_map(const Maplookup_or_add_for_write_only_span( - attribute_id, attribute.domain, data_type); + GSpanAttributeWriter result_attribute = dst_attributes.lookup_or_add_for_write_only_span( + attribute_id, attribute.domain, data_type); if (!result_attribute) { continue; @@ -157,8 +154,8 @@ static void copy_attributes_based_on_map(const Map &attributes, - const GeometryComponent &in_component, - GeometryComponent &out_component, + const bke::AttributeAccessor src_attributes, + bke::MutableAttributeAccessor dst_attributes, const int selected_loops_num, const Span selected_poly_indices, const Mesh &mesh_in) @@ -174,7 +171,7 @@ static void copy_face_corner_attributes(const Map } } copy_attributes_based_on_mask( - attributes, in_component, out_component, ATTR_DOMAIN_CORNER, IndexMask(indices)); + attributes, src_attributes, dst_attributes, ATTR_DOMAIN_CORNER, IndexMask(indices)); } static void copy_masked_vertices_to_new_mesh(const Mesh &src_mesh, @@ -365,14 +362,15 @@ static void separate_point_cloud_selection(GeometrySet &geometry_set, PointCloud *pointcloud = BKE_pointcloud_new_nomain(selection.size()); - PointCloudComponent dst_points; - dst_points.replace(pointcloud, GeometryOwnershipType::Editable); - Map attributes; geometry_set.gather_attributes_for_propagation( {GEO_COMPONENT_TYPE_POINT_CLOUD}, GEO_COMPONENT_TYPE_POINT_CLOUD, false, attributes); - copy_attributes_based_on_mask(attributes, src_points, dst_points, ATTR_DOMAIN_POINT, selection); + copy_attributes_based_on_mask(attributes, + bke::pointcloud_attributes(*src_points.get_for_read()), + bke::pointcloud_attributes_for_write(*pointcloud), + ATTR_DOMAIN_POINT, + selection); geometry_set.replace_pointcloud(pointcloud); } @@ -817,7 +815,7 @@ static void compute_selected_mesh_data_from_poly_selection(const Mesh &mesh, * Keep the parts of the mesh that are in the selection. */ static void do_mesh_separation(GeometrySet &geometry_set, - const MeshComponent &in_component, + const Mesh &mesh_in, const Span selection, const eAttrDomain domain, const GeometryNodeDeleteGeometryMode mode) @@ -828,9 +826,7 @@ static void do_mesh_separation(GeometrySet &geometry_set, int selected_polys_num = 0; int selected_loops_num = 0; - const Mesh &mesh_in = *in_component.get_for_read(); Mesh *mesh_out; - MeshComponent out_component; Map attributes; geometry_set.gather_attributes_for_propagation( @@ -892,7 +888,6 @@ static void do_mesh_separation(GeometrySet &geometry_set, 0, selected_loops_num, selected_polys_num); - out_component.replace(mesh_out, GeometryOwnershipType::Editable); /* Copy the selected parts of the mesh over to the new mesh. */ copy_masked_vertices_to_new_mesh(mesh_in, *mesh_out, vertex_map); @@ -901,18 +896,24 @@ static void do_mesh_separation(GeometrySet &geometry_set, mesh_in, *mesh_out, vertex_map, edge_map, selected_poly_indices, new_loop_starts); /* Copy attributes. */ - copy_attributes_based_on_map( - attributes, in_component, out_component, ATTR_DOMAIN_POINT, vertex_map); - copy_attributes_based_on_map( - attributes, in_component, out_component, ATTR_DOMAIN_EDGE, edge_map); + copy_attributes_based_on_map(attributes, + bke::mesh_attributes(mesh_in), + bke::mesh_attributes_for_write(*mesh_out), + ATTR_DOMAIN_POINT, + vertex_map); + copy_attributes_based_on_map(attributes, + bke::mesh_attributes(mesh_in), + bke::mesh_attributes_for_write(*mesh_out), + ATTR_DOMAIN_EDGE, + edge_map); copy_attributes_based_on_mask(attributes, - in_component, - out_component, + bke::mesh_attributes(mesh_in), + bke::mesh_attributes_for_write(*mesh_out), ATTR_DOMAIN_FACE, IndexMask(Vector(selected_poly_indices.as_span()))); copy_face_corner_attributes(attributes, - in_component, - out_component, + bke::mesh_attributes(mesh_in), + bke::mesh_attributes_for_write(*mesh_out), selected_loops_num, selected_poly_indices, mesh_in); @@ -964,7 +965,6 @@ static void do_mesh_separation(GeometrySet &geometry_set, 0, selected_loops_num, selected_polys_num); - out_component.replace(mesh_out, GeometryOwnershipType::Editable); /* Copy the selected parts of the mesh over to the new mesh. */ memcpy(mesh_out->mvert, mesh_in.mvert, mesh_in.totvert * sizeof(MVert)); @@ -973,17 +973,23 @@ static void do_mesh_separation(GeometrySet &geometry_set, mesh_in, *mesh_out, edge_map, selected_poly_indices, new_loop_starts); /* Copy attributes. */ - copy_attributes(attributes, in_component, out_component, {ATTR_DOMAIN_POINT}); - copy_attributes_based_on_map( - attributes, in_component, out_component, ATTR_DOMAIN_EDGE, edge_map); + copy_attributes(attributes, + bke::mesh_attributes(mesh_in), + bke::mesh_attributes_for_write(*mesh_out), + {ATTR_DOMAIN_POINT}); + copy_attributes_based_on_map(attributes, + bke::mesh_attributes(mesh_in), + bke::mesh_attributes_for_write(*mesh_out), + ATTR_DOMAIN_EDGE, + edge_map); copy_attributes_based_on_mask(attributes, - in_component, - out_component, + bke::mesh_attributes(mesh_in), + bke::mesh_attributes_for_write(*mesh_out), ATTR_DOMAIN_FACE, IndexMask(Vector(selected_poly_indices.as_span()))); copy_face_corner_attributes(attributes, - in_component, - out_component, + bke::mesh_attributes(mesh_in), + bke::mesh_attributes_for_write(*mesh_out), selected_loops_num, selected_poly_indices, mesh_in); @@ -1022,7 +1028,6 @@ static void do_mesh_separation(GeometrySet &geometry_set, } mesh_out = BKE_mesh_new_nomain_from_template( &mesh_in, mesh_in.totvert, mesh_in.totedge, 0, selected_loops_num, selected_polys_num); - out_component.replace(mesh_out, GeometryOwnershipType::Editable); /* Copy the selected parts of the mesh over to the new mesh. */ memcpy(mesh_out->mvert, mesh_in.mvert, mesh_in.totvert * sizeof(MVert)); @@ -1030,16 +1035,18 @@ static void do_mesh_separation(GeometrySet &geometry_set, copy_masked_polys_to_new_mesh(mesh_in, *mesh_out, selected_poly_indices, new_loop_starts); /* Copy attributes. */ - copy_attributes( - attributes, in_component, out_component, {ATTR_DOMAIN_POINT, ATTR_DOMAIN_EDGE}); + copy_attributes(attributes, + bke::mesh_attributes(mesh_in), + bke::mesh_attributes_for_write(*mesh_out), + {ATTR_DOMAIN_POINT, ATTR_DOMAIN_EDGE}); copy_attributes_based_on_mask(attributes, - in_component, - out_component, + bke::mesh_attributes(mesh_in), + bke::mesh_attributes_for_write(*mesh_out), ATTR_DOMAIN_FACE, IndexMask(Vector(selected_poly_indices.as_span()))); copy_face_corner_attributes(attributes, - in_component, - out_component, + bke::mesh_attributes(mesh_in), + bke::mesh_attributes_for_write(*mesh_out), selected_loops_num, selected_poly_indices, mesh_in); @@ -1071,7 +1078,8 @@ static void separate_mesh_selection(GeometrySet &geometry_set, const VArraySpan selection_span{selection}; - do_mesh_separation(geometry_set, src_component, selection_span, selection_domain, mode); + do_mesh_separation( + geometry_set, *src_component.get_for_read(), selection_span, selection_domain, mode); } } // namespace blender::nodes::node_geo_delete_geometry_cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc index bf5479d552e..76eeee95239 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc @@ -143,12 +143,9 @@ static void transfer_attributes( const Span new_to_old_edges_map, const Span new_to_old_face_corners_map, const Span> boundary_vertex_to_relevant_face_map, - const GeometryComponent &src_component, - GeometryComponent &dst_component) + const AttributeAccessor src_attributes, + MutableAttributeAccessor dst_attributes) { - const AttributeAccessor src_attributes = *src_component.attributes(); - MutableAttributeAccessor dst_attributes = *dst_component.attributes_for_write(); - for (Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; GAttributeReader src_attribute = src_attributes.lookup(attribute_id); @@ -875,16 +872,14 @@ static void calc_dual_mesh(GeometrySet &geometry_set, } Mesh *mesh_out = BKE_mesh_new_nomain( vertex_positions.size(), new_edges.size(), 0, loops.size(), loop_lengths.size()); - MeshComponent out_component; - out_component.replace(mesh_out, GeometryOwnershipType::Editable); transfer_attributes(attributes, vertex_types, keep_boundaries, new_to_old_edges_map, new_to_old_face_corners_map, boundary_vertex_to_relevant_face_map, - in_component, - out_component); + bke::mesh_attributes(mesh_in), + bke::mesh_attributes_for_write(*mesh_out)); int loop_start = 0; for (const int i : IndexRange(mesh_out->totpoly)) { diff --git a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc index 8160bc8d589..679a8ba4f8c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc @@ -141,15 +141,14 @@ static void threaded_id_offset_copy(const Span offsets, } /** Create the copy indices for the duplication domain. */ -static void create_duplicate_index_attribute(GeometryComponent &component, +static void create_duplicate_index_attribute(bke::MutableAttributeAccessor attributes, const eAttrDomain output_domain, const IndexMask selection, const IndexAttributes &attribute_outputs, const Span offsets) { - SpanAttributeWriter duplicate_indices = - component.attributes_for_write()->lookup_or_add_for_write_only_span( - attribute_outputs.duplicate_index.get(), output_domain); + SpanAttributeWriter duplicate_indices = attributes.lookup_or_add_for_write_only_span( + attribute_outputs.duplicate_index.get(), output_domain); for (const int i : IndexRange(selection.size())) { const IndexRange range = range_for_offsets_index(offsets, i); MutableSpan indices = duplicate_indices.span.slice(range); @@ -165,16 +164,15 @@ static void create_duplicate_index_attribute(GeometryComponent &component, * and the duplicate number. This function is used for the point domain elements. */ static void copy_stable_id_point(const Span offsets, - const GeometryComponent &src_component, - GeometryComponent &dst_component) + const bke::AttributeAccessor src_attributes, + bke::MutableAttributeAccessor dst_attributes) { - GAttributeReader src_attribute = src_component.attributes()->lookup("id"); + GAttributeReader src_attribute = src_attributes.lookup("id"); if (!src_attribute) { return; } - GSpanAttributeWriter dst_attribute = - dst_component.attributes_for_write()->lookup_or_add_for_write_only_span( - "id", ATTR_DOMAIN_POINT, CD_PROP_INT32); + GSpanAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write_only_span( + "id", ATTR_DOMAIN_POINT, CD_PROP_INT32); if (!dst_attribute) { return; } @@ -190,24 +188,23 @@ static void copy_attributes_without_id(GeometrySet &geometry_set, const eAttrDomain domain, const Span offsets, const IndexMask selection, - const GeometryComponent &src_component, - GeometryComponent &dst_component) + const bke::AttributeAccessor src_attributes, + bke::MutableAttributeAccessor dst_attributes) { const Map attributes = gather_attributes_without_id( geometry_set, component_type); for (const Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; - GAttributeReader src_attribute = src_component.attributes()->lookup(attribute_id); + GAttributeReader src_attribute = src_attributes.lookup(attribute_id); if (!src_attribute || src_attribute.domain != domain) { continue; } eAttrDomain out_domain = src_attribute.domain; const eCustomDataType data_type = bke::cpp_type_to_custom_data_type( src_attribute.varray.type()); - GSpanAttributeWriter dst_attribute = - dst_component.attributes_for_write()->lookup_or_add_for_write_only_span( - attribute_id, out_domain, data_type); + GSpanAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write_only_span( + attribute_id, out_domain, data_type); if (!dst_attribute) { continue; } @@ -232,19 +229,17 @@ static void copy_attributes_without_id(GeometrySet &geometry_set, * copied with an offset fill, otherwise a mapping is used. */ static void copy_curve_attributes_without_id(const GeometrySet &geometry_set, - const CurveComponent &src_component, const bke::CurvesGeometry &src_curves, const IndexMask selection, const Span curve_offsets, - bke::CurvesGeometry &dst_curves, - CurveComponent &dst_component) + bke::CurvesGeometry &dst_curves) { Map attributes = gather_attributes_without_id( geometry_set, GEO_COMPONENT_TYPE_CURVE); for (const Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; - GAttributeReader src_attribute = src_component.attributes()->lookup(attribute_id); + GAttributeReader src_attribute = src_curves.attributes().lookup(attribute_id); if (!src_attribute) { continue; } @@ -253,7 +248,7 @@ static void copy_curve_attributes_without_id(const GeometrySet &geometry_set, const eCustomDataType data_type = bke::cpp_type_to_custom_data_type( src_attribute.varray.type()); GSpanAttributeWriter dst_attribute = - dst_component.attributes_for_write()->lookup_or_add_for_write_only_span( + dst_curves.attributes_for_write().lookup_or_add_for_write_only_span( attribute_id, out_domain, data_type); if (!dst_attribute) { continue; @@ -296,16 +291,14 @@ static void copy_curve_attributes_without_id(const GeometrySet &geometry_set, static void copy_stable_id_curves(const bke::CurvesGeometry &src_curves, const IndexMask selection, const Span curve_offsets, - const CurveComponent &src_component, - bke::CurvesGeometry &dst_curves, - CurveComponent &dst_component) + bke::CurvesGeometry &dst_curves) { - GAttributeReader src_attribute = src_component.attributes()->lookup("id"); + GAttributeReader src_attribute = src_curves.attributes().lookup("id"); if (!src_attribute) { return; } GSpanAttributeWriter dst_attribute = - dst_component.attributes_for_write()->lookup_or_add_for_write_only_span( + dst_curves.attributes_for_write().lookup_or_add_for_write_only_span( "id", ATTR_DOMAIN_POINT, CD_PROP_INT32); if (!dst_attribute) { return; @@ -387,18 +380,16 @@ static void duplicate_curves(GeometrySet &geometry_set, }); all_dst_offsets.last() = dst_points_num; - CurveComponent dst_component; - dst_component.replace(new_curves_id, GeometryOwnershipType::Editable); + copy_curve_attributes_without_id(geometry_set, curves, selection, curve_offsets, new_curves); - copy_curve_attributes_without_id( - geometry_set, src_component, curves, selection, curve_offsets, new_curves, dst_component); - - copy_stable_id_curves( - curves, selection, curve_offsets, src_component, new_curves, dst_component); + copy_stable_id_curves(curves, selection, curve_offsets, new_curves); if (attribute_outputs.duplicate_index) { - create_duplicate_index_attribute( - dst_component, ATTR_DOMAIN_CURVE, selection, attribute_outputs, curve_offsets); + create_duplicate_index_attribute(new_curves.attributes_for_write(), + ATTR_DOMAIN_CURVE, + selection, + attribute_outputs, + curve_offsets); } geometry_set.replace_curves(new_curves_id); @@ -420,15 +411,15 @@ static void copy_face_attributes_without_id(GeometrySet &geometry_set, const Span loop_mapping, const Span offsets, const IndexMask selection, - const GeometryComponent &src_component, - GeometryComponent &dst_component) + const bke::AttributeAccessor src_attributes, + bke::MutableAttributeAccessor dst_attributes) { Map attributes = gather_attributes_without_id( geometry_set, GEO_COMPONENT_TYPE_MESH); for (const Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; - GAttributeReader src_attribute = src_component.attributes()->lookup(attribute_id); + GAttributeReader src_attribute = src_attributes.lookup(attribute_id); if (!src_attribute) { continue; } @@ -436,9 +427,8 @@ static void copy_face_attributes_without_id(GeometrySet &geometry_set, eAttrDomain out_domain = src_attribute.domain; const eCustomDataType data_type = bke::cpp_type_to_custom_data_type( src_attribute.varray.type()); - GSpanAttributeWriter dst_attribute = - dst_component.attributes_for_write()->lookup_or_add_for_write_only_span( - attribute_id, out_domain, data_type); + GSpanAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write_only_span( + attribute_id, out_domain, data_type); if (!dst_attribute) { continue; } @@ -480,16 +470,15 @@ static void copy_stable_id_faces(const Mesh &mesh, const IndexMask selection, const Span poly_offsets, const Span vert_mapping, - const MeshComponent &src_component, - MeshComponent &dst_component) + const bke::AttributeAccessor src_attributes, + bke::MutableAttributeAccessor dst_attributes) { - GAttributeReader src_attribute = src_component.attributes()->lookup("id"); + GAttributeReader src_attribute = src_attributes.lookup("id"); if (!src_attribute) { return; } - GSpanAttributeWriter dst_attribute = - dst_component.attributes_for_write()->lookup_or_add_for_write_only_span( - "id", ATTR_DOMAIN_POINT, CD_PROP_INT32); + GSpanAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write_only_span( + "id", ATTR_DOMAIN_POINT, CD_PROP_INT32); if (!dst_attribute) { return; } @@ -599,23 +588,28 @@ static void duplicate_faces(GeometrySet &geometry_set, } } - MeshComponent dst_component; - dst_component.replace(new_mesh, GeometryOwnershipType::Editable); - copy_face_attributes_without_id(geometry_set, edge_mapping, vert_mapping, loop_mapping, offsets, selection, - src_component, - dst_component); + bke::mesh_attributes(mesh), + bke::mesh_attributes_for_write(*new_mesh)); - copy_stable_id_faces(mesh, selection, offsets, vert_mapping, src_component, dst_component); + copy_stable_id_faces(mesh, + selection, + offsets, + vert_mapping, + bke::mesh_attributes(mesh), + bke::mesh_attributes_for_write(*new_mesh)); if (attribute_outputs.duplicate_index) { - create_duplicate_index_attribute( - dst_component, ATTR_DOMAIN_FACE, selection, attribute_outputs, offsets); + create_duplicate_index_attribute(bke::mesh_attributes_for_write(*new_mesh), + ATTR_DOMAIN_FACE, + selection, + attribute_outputs, + offsets); } geometry_set.replace_mesh(new_mesh); @@ -635,15 +629,15 @@ static void copy_edge_attributes_without_id(GeometrySet &geometry_set, const Span point_mapping, const Span offsets, const IndexMask selection, - const GeometryComponent &src_component, - GeometryComponent &dst_component) + const bke::AttributeAccessor src_attributes, + bke::MutableAttributeAccessor dst_attributes) { Map attributes = gather_attributes_without_id( geometry_set, GEO_COMPONENT_TYPE_MESH); for (const Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; - GAttributeReader src_attribute = src_component.attributes()->lookup(attribute_id); + GAttributeReader src_attribute = src_attributes.lookup(attribute_id); if (!src_attribute) { continue; } @@ -651,9 +645,8 @@ static void copy_edge_attributes_without_id(GeometrySet &geometry_set, const eAttrDomain out_domain = src_attribute.domain; const eCustomDataType data_type = bke::cpp_type_to_custom_data_type( src_attribute.varray.type()); - GSpanAttributeWriter dst_attribute = - dst_component.attributes_for_write()->lookup_or_add_for_write_only_span( - attribute_id, out_domain, data_type); + GSpanAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write_only_span( + attribute_id, out_domain, data_type); if (!dst_attribute) { continue; } @@ -684,16 +677,15 @@ static void copy_edge_attributes_without_id(GeometrySet &geometry_set, static void copy_stable_id_edges(const Mesh &mesh, const IndexMask selection, const Span edge_offsets, - const MeshComponent &src_component, - MeshComponent &dst_component) + const bke::AttributeAccessor src_attributes, + bke::MutableAttributeAccessor dst_attributes) { - GAttributeReader src_attribute = src_component.attributes()->lookup("id"); + GAttributeReader src_attribute = src_attributes.lookup("id"); if (!src_attribute) { return; } - GSpanAttributeWriter dst_attribute = - dst_component.attributes_for_write()->lookup_or_add_for_write_only_span( - "id", ATTR_DOMAIN_POINT, CD_PROP_INT32); + GSpanAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write_only_span( + "id", ATTR_DOMAIN_POINT, CD_PROP_INT32); if (!dst_attribute) { return; } @@ -777,17 +769,25 @@ static void duplicate_edges(GeometrySet &geometry_set, } }); - MeshComponent dst_component; - dst_component.replace(new_mesh, GeometryOwnershipType::Editable); - - copy_edge_attributes_without_id( - geometry_set, vert_orig_indices, edge_offsets, selection, src_component, dst_component); + copy_edge_attributes_without_id(geometry_set, + vert_orig_indices, + edge_offsets, + selection, + bke::mesh_attributes(mesh), + bke::mesh_attributes_for_write(*new_mesh)); - copy_stable_id_edges(mesh, selection, edge_offsets, src_component, dst_component); + copy_stable_id_edges(mesh, + selection, + edge_offsets, + bke::mesh_attributes(mesh), + bke::mesh_attributes_for_write(*new_mesh)); if (attribute_outputs.duplicate_index) { - create_duplicate_index_attribute( - dst_component, ATTR_DOMAIN_EDGE, selection, attribute_outputs, edge_offsets); + create_duplicate_index_attribute(bke::mesh_attributes_for_write(*new_mesh), + ATTR_DOMAIN_EDGE, + selection, + attribute_outputs, + edge_offsets); } geometry_set.replace_mesh(new_mesh); @@ -839,9 +839,6 @@ static void duplicate_points_curve(GeometrySet &geometry_set, } new_curve_offsets.last() = dst_num; - CurveComponent dst_component; - dst_component.replace(new_curves_id, GeometryOwnershipType::Editable); - Map attributes = gather_attributes_without_id( geometry_set, GEO_COMPONENT_TYPE_CURVE); @@ -856,7 +853,7 @@ static void duplicate_points_curve(GeometrySet &geometry_set, const eCustomDataType data_type = bke::cpp_type_to_custom_data_type( src_attribute.varray.type()); GSpanAttributeWriter dst_attribute = - dst_component.attributes_for_write()->lookup_or_add_for_write_only_span( + new_curves.attributes_for_write().lookup_or_add_for_write_only_span( attribute_id, domain, data_type); if (!dst_attribute) { continue; @@ -887,11 +884,14 @@ static void duplicate_points_curve(GeometrySet &geometry_set, dst_attribute.finish(); } - copy_stable_id_point(offsets, src_component, dst_component); + copy_stable_id_point(offsets, src_curves.attributes(), new_curves.attributes_for_write()); if (attribute_outputs.duplicate_index) { - create_duplicate_index_attribute( - dst_component, ATTR_DOMAIN_POINT, selection, attribute_outputs, offsets.as_span()); + create_duplicate_index_attribute(new_curves.attributes_for_write(), + ATTR_DOMAIN_POINT, + selection, + attribute_outputs, + offsets.as_span()); } geometry_set.replace_curves(new_curves_id); @@ -927,21 +927,23 @@ static void duplicate_points_mesh(GeometrySet &geometry_set, threaded_slice_fill(offsets.as_span(), selection, src_verts, dst_verts); - MeshComponent dst_component; - dst_component.replace(new_mesh, GeometryOwnershipType::Editable); copy_attributes_without_id(geometry_set, GEO_COMPONENT_TYPE_MESH, ATTR_DOMAIN_POINT, offsets, selection, - src_component, - dst_component); + bke::mesh_attributes(mesh), + bke::mesh_attributes_for_write(*new_mesh)); - copy_stable_id_point(offsets, src_component, dst_component); + copy_stable_id_point( + offsets, bke::mesh_attributes(mesh), bke::mesh_attributes_for_write(*new_mesh)); if (attribute_outputs.duplicate_index) { - create_duplicate_index_attribute( - dst_component, ATTR_DOMAIN_POINT, selection, attribute_outputs, offsets.as_span()); + create_duplicate_index_attribute(bke::mesh_attributes_for_write(*new_mesh), + ATTR_DOMAIN_POINT, + selection, + attribute_outputs, + offsets.as_span()); } geometry_set.replace_mesh(new_mesh); @@ -973,22 +975,24 @@ static void duplicate_points_pointcloud(GeometrySet &geometry_set, Array offsets = accumulate_counts_to_offsets(selection, counts); PointCloud *pointcloud = BKE_pointcloud_new_nomain(offsets.last()); - PointCloudComponent dst_component; - dst_component.replace(pointcloud, GeometryOwnershipType::Editable); copy_attributes_without_id(geometry_set, GEO_COMPONENT_TYPE_POINT_CLOUD, ATTR_DOMAIN_POINT, offsets, selection, - src_points, - dst_component); + *src_points.attributes(), + bke::pointcloud_attributes_for_write(*pointcloud)); - copy_stable_id_point(offsets, src_points, dst_component); + copy_stable_id_point( + offsets, *src_points.attributes(), bke::pointcloud_attributes_for_write(*pointcloud)); if (attribute_outputs.duplicate_index) { - create_duplicate_index_attribute( - dst_component, ATTR_DOMAIN_POINT, selection, attribute_outputs, offsets); + create_duplicate_index_attribute(bke::pointcloud_attributes_for_write(*pointcloud), + ATTR_DOMAIN_POINT, + selection, + attribute_outputs, + offsets); } geometry_set.replace_pointcloud(pointcloud); } @@ -1085,12 +1089,15 @@ static void duplicate_instances(GeometrySet &geometry_set, ATTR_DOMAIN_INSTANCE, offsets, selection, - src_instances, - dst_instances); + *src_instances.attributes(), + *dst_instances.attributes_for_write()); if (attribute_outputs.duplicate_index) { - create_duplicate_index_attribute( - dst_instances, ATTR_DOMAIN_INSTANCE, selection, attribute_outputs, offsets); + create_duplicate_index_attribute(*dst_instances.attributes_for_write(), + ATTR_DOMAIN_INSTANCE, + selection, + attribute_outputs, + offsets); } geometry_set = std::move(dst_geometry); diff --git a/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc index baa9952b950..acf85e74353 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc @@ -164,11 +164,10 @@ static CustomData &get_customdata(Mesh &mesh, const eAttrDomain domain) static MutableSpan get_orig_index_layer(Mesh &mesh, const eAttrDomain domain) { - MeshComponent component; - component.replace(&mesh, GeometryOwnershipType::ReadOnly); + const bke::AttributeAccessor attributes = bke::mesh_attributes(mesh); CustomData &custom_data = get_customdata(mesh, domain); if (int *orig_indices = static_cast(CustomData_get_layer(&custom_data, CD_ORIGINDEX))) { - return {orig_indices, component.attribute_domain_size(domain)}; + return {orig_indices, attributes.domain_size(domain)}; } return {}; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_merge_by_distance.cc b/source/blender/nodes/geometry/nodes/node_geo_merge_by_distance.cc index 4bc84a7a08a..a4fb79bef7a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_merge_by_distance.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_merge_by_distance.cc @@ -50,7 +50,7 @@ static PointCloud *pointcloud_merge_by_distance(const PointCloudComponent &src_p return nullptr; } - return geometry::point_merge_by_distance(src_points, merge_distance, selection); + return geometry::point_merge_by_distance(*src_points.get_for_read(), merge_distance, selection); } static std::optional mesh_merge_by_distance_connected(const MeshComponent &mesh_component, -- cgit v1.2.3 From ecf4f4a71f1f1a0fecf1944599d408e1e26058f5 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 20 Jul 2022 10:47:13 +0200 Subject: Curves: fix uninitialized curve type when adding new curves --- source/blender/geometry/intern/add_curves_on_mesh.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/source/blender/geometry/intern/add_curves_on_mesh.cc b/source/blender/geometry/intern/add_curves_on_mesh.cc index a69073af207..e54e2bdd3b0 100644 --- a/source/blender/geometry/intern/add_curves_on_mesh.cc +++ b/source/blender/geometry/intern/add_curves_on_mesh.cc @@ -361,6 +361,9 @@ void add_curves_on_mesh(CurvesGeometry &curves, const AddCurvesOnMeshInputs &inp inputs.surface_to_curves_normal_mat); } + /* Set curve types. */ + MutableSpan types_span = curves.curve_types_for_write(); + types_span.drop_front(old_curves_num).fill(CURVE_TYPE_CATMULL_ROM); curves.update_curve_types(); } -- cgit v1.2.3 From 92ca920c52b90dd5552fc43582b0a965d7d173bb Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 20 Jul 2022 10:45:23 +0200 Subject: Add a 'Apply and Delete All' operation to shapekeys. Adds a new option to the 'Delete ShpaKeys' operator, which first applies the current mix to the object data, before removing all shapekeys. Request from @JulienKaspar from Blender studio. Reviewed By: JulienKaspar Differential Revision: https://developer.blender.org/D15443 --- .../scripts/startup/bl_ui/properties_data_mesh.py | 7 +- source/blender/blenkernel/BKE_key.h | 5 +- source/blender/blenkernel/intern/key.c | 121 +++++++++++++-------- source/blender/editors/object/object_shapekey.c | 43 +++++++- source/blender/modifiers/intern/MOD_shapekey.c | 2 +- 5 files changed, 129 insertions(+), 49 deletions(-) diff --git a/release/scripts/startup/bl_ui/properties_data_mesh.py b/release/scripts/startup/bl_ui/properties_data_mesh.py index 2fc949f4aae..80b9b773d9b 100644 --- a/release/scripts/startup/bl_ui/properties_data_mesh.py +++ b/release/scripts/startup/bl_ui/properties_data_mesh.py @@ -58,7 +58,12 @@ class MESH_MT_shape_key_context_menu(Menu): layout.operator("object.join_shapes") layout.operator("object.shape_key_transfer") layout.separator() - layout.operator("object.shape_key_remove", icon='X', text="Delete All Shape Keys").all = True + op = layout.operator("object.shape_key_remove", icon='X', text="Delete All Shape Keys") + op.all = True + op.apply_mix = False + op = layout.operator("object.shape_key_remove", text="Apply All Shape Keys") + op.all = True + op.apply_mix = True layout.separator() layout.operator("object.shape_key_move", icon='TRIA_UP_BAR', text="Move to Top").type = 'TOP' layout.operator("object.shape_key_move", icon='TRIA_DOWN_BAR', text="Move to Bottom").type = 'BOTTOM' diff --git a/source/blender/blenkernel/BKE_key.h b/source/blender/blenkernel/BKE_key.h index 676ac6fe37b..37b30d63722 100644 --- a/source/blender/blenkernel/BKE_key.h +++ b/source/blender/blenkernel/BKE_key.h @@ -46,8 +46,11 @@ void key_curve_normal_weights(float t, float data[4], int type); /** * Returns key coordinates (+ tilt) when key applied, NULL otherwise. + * + * \param obdata if given, also update that geometry with the result of the shape keys evaluation. */ -float *BKE_key_evaluate_object_ex(struct Object *ob, int *r_totelem, float *arr, size_t arr_size); +float *BKE_key_evaluate_object_ex( + struct Object *ob, int *r_totelem, float *arr, size_t arr_size, struct ID *obdata); float *BKE_key_evaluate_object(struct Object *ob, int *r_totelem); /** diff --git a/source/blender/blenkernel/intern/key.c b/source/blender/blenkernel/intern/key.c index 97eac0b9f91..7ef15912567 100644 --- a/source/blender/blenkernel/intern/key.c +++ b/source/blender/blenkernel/intern/key.c @@ -1501,7 +1501,14 @@ static void do_latt_key(Object *ob, Key *key, char *out, const int tot) } } -float *BKE_key_evaluate_object_ex(Object *ob, int *r_totelem, float *arr, size_t arr_size) +static void keyblock_data_convert_to_mesh(const float (*fp)[3], MVert *mvert, const int totvert); +static void keyblock_data_convert_to_lattice(const float (*fp)[3], + BPoint *bpoint, + const int totpoint); +static void keyblock_data_convert_to_curve(const float *fp, ListBase *nurb, const int totpoint); + +float *BKE_key_evaluate_object_ex( + Object *ob, int *r_totelem, float *arr, size_t arr_size, ID *obdata) { Key *key = BKE_key_from_object(ob); KeyBlock *actkb = BKE_keyblock_from_object(ob); @@ -1576,7 +1583,6 @@ float *BKE_key_evaluate_object_ex(Object *ob, int *r_totelem, float *arr, size_t } } else { - if (ob->type == OB_MESH) { do_mesh_key(ob, key, out, tot); } @@ -1591,6 +1597,31 @@ float *BKE_key_evaluate_object_ex(Object *ob, int *r_totelem, float *arr, size_t } } + if (obdata != NULL) { + switch (GS(obdata->name)) { + case ID_ME: { + Mesh *mesh = (Mesh *)obdata; + const int totvert = min_ii(tot, mesh->totvert); + keyblock_data_convert_to_mesh((const float(*)[3])out, mesh->mvert, totvert); + break; + } + case ID_LT: { + Lattice *lattice = (Lattice *)obdata; + const int totpoint = min_ii(tot, lattice->pntsu * lattice->pntsv * lattice->pntsw); + keyblock_data_convert_to_lattice((const float(*)[3])out, lattice->def, totpoint); + break; + } + case ID_CU_LEGACY: { + Curve *curve = (Curve *)obdata; + const int totpoint = min_ii(tot, BKE_keyblock_curve_element_count(&curve->nurb)); + keyblock_data_convert_to_curve((const float *)out, &curve->nurb, totpoint); + break; + } + default: + BLI_assert_unreachable(); + } + } + if (r_totelem) { *r_totelem = tot; } @@ -1599,7 +1630,7 @@ float *BKE_key_evaluate_object_ex(Object *ob, int *r_totelem, float *arr, size_t float *BKE_key_evaluate_object(Object *ob, int *r_totelem) { - return BKE_key_evaluate_object_ex(ob, r_totelem, NULL, 0); + return BKE_key_evaluate_object_ex(ob, r_totelem, NULL, 0, NULL); } int BKE_keyblock_element_count_from_shape(const Key *key, const int shape_index) @@ -1971,21 +2002,22 @@ void BKE_keyblock_convert_from_lattice(const Lattice *lt, KeyBlock *kb) BKE_keyblock_update_from_lattice(lt, kb); } -void BKE_keyblock_convert_to_lattice(const KeyBlock *kb, Lattice *lt) +static void keyblock_data_convert_to_lattice(const float (*fp)[3], + BPoint *bpoint, + const int totpoint) { - BPoint *bp; - const float(*fp)[3]; - int a, tot; - - bp = lt->def; - fp = kb->data; + for (int i = 0; i < totpoint; i++, fp++, bpoint++) { + copy_v3_v3(bpoint->vec, *fp); + } +} - tot = lt->pntsu * lt->pntsv * lt->pntsw; - tot = min_ii(kb->totelem, tot); +void BKE_keyblock_convert_to_lattice(const KeyBlock *kb, Lattice *lt) +{ + BPoint *bp = lt->def; + const float(*fp)[3] = kb->data; + const int tot = min_ii(kb->totelem, lt->pntsu * lt->pntsv * lt->pntsw); - for (a = 0; a < tot; a++, fp++, bp++) { - copy_v3_v3(bp->vec, *fp); - } + keyblock_data_convert_to_lattice(fp, bp, tot); } /************************* Curve ************************/ @@ -2097,42 +2129,40 @@ void BKE_keyblock_convert_from_curve(const Curve *cu, KeyBlock *kb, const ListBa BKE_keyblock_update_from_curve(cu, kb, nurb); } -void BKE_keyblock_convert_to_curve(KeyBlock *kb, Curve *UNUSED(cu), ListBase *nurb) +static void keyblock_data_convert_to_curve(const float *fp, ListBase *nurb, int totpoint) { - Nurb *nu; - BezTriple *bezt; - BPoint *bp; - const float *fp; - int a, tot; - - tot = BKE_keyblock_curve_element_count(nurb); - tot = min_ii(kb->totelem, tot); - - fp = kb->data; - for (nu = nurb->first; nu && tot > 0; nu = nu->next) { - if (nu->bezt) { - for (a = nu->pntsu, bezt = nu->bezt; a && (tot -= KEYELEM_ELEM_LEN_BEZTRIPLE) >= 0; - a--, bezt++) { - for (int i = 0; i < 3; i++) { - copy_v3_v3(bezt->vec[i], &fp[i * 3]); + for (Nurb *nu = nurb->first; nu && totpoint > 0; nu = nu->next) { + if (nu->bezt != NULL) { + BezTriple *bezt = nu->bezt; + for (int i = nu->pntsu; i && (totpoint -= KEYELEM_ELEM_LEN_BEZTRIPLE) >= 0; + i--, bezt++, fp += KEYELEM_FLOAT_LEN_BEZTRIPLE) { + for (int j = 0; j < 3; j++) { + copy_v3_v3(bezt->vec[j], &fp[j * 3]); } bezt->tilt = fp[9]; bezt->radius = fp[10]; - fp += KEYELEM_FLOAT_LEN_BEZTRIPLE; } } else { - for (a = nu->pntsu * nu->pntsv, bp = nu->bp; a && (tot -= KEYELEM_ELEM_LEN_BPOINT) >= 0; - a--, bp++) { + BPoint *bp = nu->bp; + for (int i = nu->pntsu * nu->pntsv; i && (totpoint -= KEYELEM_ELEM_LEN_BPOINT) >= 0; + i--, bp++, fp += KEYELEM_FLOAT_LEN_BPOINT) { copy_v3_v3(bp->vec, fp); bp->tilt = fp[3]; bp->radius = fp[4]; - fp += KEYELEM_FLOAT_LEN_BPOINT; } } } } +void BKE_keyblock_convert_to_curve(KeyBlock *kb, Curve *UNUSED(cu), ListBase *nurb) +{ + const float *fp = kb->data; + const int tot = min_ii(kb->totelem, BKE_keyblock_curve_element_count(nurb)); + + keyblock_data_convert_to_curve(fp, nurb, tot); +} + /************************* Mesh ************************/ void BKE_keyblock_update_from_mesh(const Mesh *me, KeyBlock *kb) @@ -2171,20 +2201,21 @@ void BKE_keyblock_convert_from_mesh(const Mesh *me, const Key *key, KeyBlock *kb BKE_keyblock_update_from_mesh(me, kb); } -void BKE_keyblock_convert_to_mesh(const KeyBlock *kb, MVert *mvert, const int totvert) +static void keyblock_data_convert_to_mesh(const float (*fp)[3], MVert *mvert, const int totvert) { - const float(*fp)[3]; - int a, tot; - - fp = kb->data; - - tot = min_ii(kb->totelem, totvert); - - for (a = 0; a < tot; a++, fp++, mvert++) { + for (int i = 0; i < totvert; i++, fp++, mvert++) { copy_v3_v3(mvert->co, *fp); } } +void BKE_keyblock_convert_to_mesh(const KeyBlock *kb, MVert *mvert, const int totvert) +{ + const float(*fp)[3] = kb->data; + const int tot = min_ii(kb->totelem, totvert); + + keyblock_data_convert_to_mesh(fp, mvert, tot); +} + void BKE_keyblock_mesh_calc_normals(const KeyBlock *kb, const Mesh *mesh, float (*r_vertnors)[3], diff --git a/source/blender/editors/object/object_shapekey.c b/source/blender/editors/object/object_shapekey.c index ebcf8573ccd..7d8c04d4d18 100644 --- a/source/blender/editors/object/object_shapekey.c +++ b/source/blender/editors/object/object_shapekey.c @@ -20,6 +20,8 @@ #include "BLI_math.h" #include "BLI_utildefines.h" +#include "BLT_translation.h" + #include "DNA_key_types.h" #include "DNA_lattice_types.h" #include "DNA_mesh_types.h" @@ -299,6 +301,10 @@ static int shape_key_remove_exec(bContext *C, wmOperator *op) bool changed = false; if (RNA_boolean_get(op->ptr, "all")) { + if (RNA_boolean_get(op->ptr, "apply_mix")) { + float *arr = BKE_key_evaluate_object_ex(ob, NULL, NULL, 0, ob->data); + MEM_freeN(arr); + } changed = BKE_object_shapekey_free(bmain, ob); } else { @@ -315,6 +321,34 @@ static int shape_key_remove_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } +static bool shape_key_remove_poll_property(const bContext *C, + wmOperator *op, + const PropertyRNA *prop) +{ + const char *prop_id = RNA_property_identifier(prop); + const bool do_all = RNA_enum_get(op->ptr, "all"); + + /* Only show seed for randomize action! */ + if (STREQ(prop_id, "apply_mix") && !do_all) { + return false; + } + return true; +} + +static char *shape_key_remove_get_description(bContext *UNUSED(C), + wmOperatorType *UNUSED(ot), + PointerRNA *ptr) +{ + const bool do_apply_mix = RNA_boolean_get(ptr, "apply_mix"); + + if (do_apply_mix) { + return BLI_strdup( + TIP_("Apply current visible shape to the object data, and delete all shape keys")); + } + + return NULL; +} + void OBJECT_OT_shape_key_remove(wmOperatorType *ot) { /* identifiers */ @@ -325,12 +359,19 @@ void OBJECT_OT_shape_key_remove(wmOperatorType *ot) /* api callbacks */ ot->poll = shape_key_mode_exists_poll; ot->exec = shape_key_remove_exec; + ot->poll_property = shape_key_remove_poll_property; + ot->get_description = shape_key_remove_get_description; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ - RNA_def_boolean(ot->srna, "all", 0, "All", "Remove all shape keys"); + RNA_def_boolean(ot->srna, "all", false, "All", "Remove all shape keys"); + RNA_def_boolean(ot->srna, + "apply_mix", + false, + "Apply Mix", + "Apply current mix of shape keys to the geometry before removing them"); } /** \} */ diff --git a/source/blender/modifiers/intern/MOD_shapekey.c b/source/blender/modifiers/intern/MOD_shapekey.c index cc214c1147a..56dd1fc50f8 100644 --- a/source/blender/modifiers/intern/MOD_shapekey.c +++ b/source/blender/modifiers/intern/MOD_shapekey.c @@ -36,7 +36,7 @@ static void deformVerts(ModifierData *UNUSED(md), if (key && key->block.first) { int deformedVerts_tot; BKE_key_evaluate_object_ex( - ctx->object, &deformedVerts_tot, (float *)vertexCos, sizeof(*vertexCos) * verts_num); + ctx->object, &deformedVerts_tot, (float *)vertexCos, sizeof(*vertexCos) * verts_num, NULL); } } -- cgit v1.2.3 From c48dc61749fbf2f118651bb757e186df72b85acf Mon Sep 17 00:00:00 2001 From: Damien Picard Date: Wed, 20 Jul 2022 11:04:38 +0200 Subject: I18n: fixes to add-on message extraction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit fixes several issues in add-ons UI messages extraction code: - In multi-file modules, the script would crash because it tried to write to the dir instead of a `translations.py` file; - The add-on message extraction works by enabling the add-on, getting all messages; disabling the add-on, getting all messages; then comparing the two message sets. But often a bug happens where a class gets a description from somewhere else in memory. I couldn’t debug that, so a workaround is to check that the message isn’t a corrupted one before removing it; - `printf()` doesn't exist in Python and would crash the script; - `self.src[self.settings.PARSER_PY_ID]` can be replaced by `self.py_file` in class `I18n`, since a property exists to do that; - At one point a generator was printed instead of its values, so let's unpack the generator to get the values. Maybe the print could be deleted entirely; - Use SPDX license identifier instead of GPL license block, to be more in line with other scripts from the codebase. Reviewed By: mont29 Differential Revision: https://developer.blender.org/D15474 --- .../modules/bl_i18n_utils/bl_extract_messages.py | 7 ++++- release/scripts/modules/bl_i18n_utils/utils.py | 36 ++++++---------------- 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/release/scripts/modules/bl_i18n_utils/bl_extract_messages.py b/release/scripts/modules/bl_i18n_utils/bl_extract_messages.py index 3edb5b445fe..bfc111dd3c1 100644 --- a/release/scripts/modules/bl_i18n_utils/bl_extract_messages.py +++ b/release/scripts/modules/bl_i18n_utils/bl_extract_messages.py @@ -962,7 +962,12 @@ def dump_addon_messages(module_name, do_checks, settings): # and make the diff! for key in minus_msgs: if key != settings.PO_HEADER_KEY: - del msgs[key] + if key in msgs: + del msgs[key] + else: + # This should not happen, but some messages seem to have + # leaked on add-on unregister and register? + print(f"Key not found in msgs: {key}") if check_ctxt: _diff_check_ctxt(check_ctxt, minus_check_ctxt) diff --git a/release/scripts/modules/bl_i18n_utils/utils.py b/release/scripts/modules/bl_i18n_utils/utils.py index b1e3fa07ac5..324c3ea261d 100644 --- a/release/scripts/modules/bl_i18n_utils/utils.py +++ b/release/scripts/modules/bl_i18n_utils/utils.py @@ -154,7 +154,7 @@ def get_po_files_from_dir(root_dir, langs=set()): else: continue if uid in found_uids: - printf("WARNING! {} id has been found more than once! only first one has been loaded!".format(uid)) + print("WARNING! {} id has been found more than once! only first one has been loaded!".format(uid)) continue found_uids.add(uid) yield uid, po_file @@ -1240,8 +1240,8 @@ class I18n: return os.path.join(os.path.dirname(path), uid + ".po") elif kind == 'PY': if not path.endswith(".py"): - if self.src.get(self.settings.PARSER_PY_ID): - return self.src[self.settings.PARSER_PY_ID] + if os.path.isdir(path): + return os.path.join(path, "translations.py") return os.path.join(os.path.dirname(path), "translations.py") return path @@ -1392,15 +1392,15 @@ class I18n: if langs set is void, all languages found are loaded. """ default_context = self.settings.DEFAULT_CONTEXT - self.src[self.settings.PARSER_PY_ID], msgs = self.check_py_module_has_translations(src, self.settings) + self.py_file, msgs = self.check_py_module_has_translations(src, self.settings) if msgs is None: - self.src[self.settings.PARSER_PY_ID] = src + self.py_file = src msgs = () for key, (sources, gen_comments), *translations in msgs: if self.settings.PARSER_TEMPLATE_ID not in self.trans: self.trans[self.settings.PARSER_TEMPLATE_ID] = I18nMessages(self.settings.PARSER_TEMPLATE_ID, settings=self.settings) - self.src[self.settings.PARSER_TEMPLATE_ID] = self.src[self.settings.PARSER_PY_ID] + self.src[self.settings.PARSER_TEMPLATE_ID] = self.py_file if key in self.trans[self.settings.PARSER_TEMPLATE_ID].msgs: print("ERROR! key {} is defined more than once! Skipping re-definitions!") continue @@ -1416,7 +1416,7 @@ class I18n: for uid, msgstr, (is_fuzzy, user_comments) in translations: if uid not in self.trans: self.trans[uid] = I18nMessages(uid, settings=self.settings) - self.src[uid] = self.src[self.settings.PARSER_PY_ID] + self.src[uid] = self.py_file comment_lines = [self.settings.PO_COMMENT_PREFIX + c for c in user_comments] + common_comment_lines self.trans[uid].msgs[key] = I18nMessage(ctxt, [key[1]], [msgstr], comment_lines, False, is_fuzzy, settings=self.settings) @@ -1479,7 +1479,7 @@ class I18n: if langs: translations &= langs translations = [('"' + lng + '"', " " * (len(lng) + 6), self.trans[lng]) for lng in sorted(translations)] - print(k for k in keys.keys()) + print(*(k for k in keys.keys())) for key in keys.keys(): if ref.msgs[key].is_commented: continue @@ -1565,25 +1565,9 @@ class I18n: # We completely replace the text found between start and end markers... txt = _gen_py(self, langs) else: - printf("Creating python file {} containing translations.".format(dst)) + print("Creating python file {} containing translations.".format(dst)) txt = [ - "# ***** 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.", - "#", - "# ***** END GPL LICENSE BLOCK *****", + "# SPDX-License-Identifier: GPL-2.0-or-later", "", self.settings.PARSER_PY_MARKER_BEGIN, "", -- cgit v1.2.3 From ae49e0e8be590dfbf6604d19f862931380bf1bdd Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 20 Jul 2022 11:09:16 +0200 Subject: Cleanup: unused parameter in own recent commit rB92ca920c52b9. --- source/blender/editors/object/object_shapekey.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/editors/object/object_shapekey.c b/source/blender/editors/object/object_shapekey.c index 7d8c04d4d18..0e3945bff15 100644 --- a/source/blender/editors/object/object_shapekey.c +++ b/source/blender/editors/object/object_shapekey.c @@ -321,7 +321,7 @@ static int shape_key_remove_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } -static bool shape_key_remove_poll_property(const bContext *C, +static bool shape_key_remove_poll_property(const bContext *UNUSED(C), wmOperator *op, const PropertyRNA *prop) { -- cgit v1.2.3 From 054a169be04850c9356917904e04cdf24a73e04c Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 24 Jun 2022 17:08:54 +0300 Subject: Use appropriate context for the DopeSheet Action Custom Properties panel. Refactor D14646 to use context.active_action for the Action Custom Properties panel, matching the already existing Action panel. This has the advantage that it allows access to the properties of any actions with channels visible in the Dope Sheet, e.g. Shape Keys, Materials etc; while using just the active object is limited to just the object animation. Also move both panels from Item to the Action tab. Differential Revision: https://developer.blender.org/D15288 --- release/scripts/startup/bl_ui/space_dopesheet.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/release/scripts/startup/bl_ui/space_dopesheet.py b/release/scripts/startup/bl_ui/space_dopesheet.py index 4d1514c2777..a2e691c2d9f 100644 --- a/release/scripts/startup/bl_ui/space_dopesheet.py +++ b/release/scripts/startup/bl_ui/space_dopesheet.py @@ -548,22 +548,20 @@ class DopesheetActionPanelBase: class DOPESHEET_PT_custom_props_action(PropertyPanel, Panel): bl_space_type = 'DOPESHEET_EDITOR' - bl_category = "Item" + bl_category = "Action" bl_region_type = 'UI' bl_context = 'data' - _context_path = "active_object.animation_data.action" + _context_path = "active_action" _property_type = bpy.types.Action @classmethod def poll(cls, context): - return context.active_object \ - and context.active_object.animation_data \ - and context.active_object.animation_data.action + return bool(context.active_action) class DOPESHEET_PT_action(DopesheetActionPanelBase, Panel): bl_space_type = 'DOPESHEET_EDITOR' - bl_category = "Item" + bl_category = "Action" @classmethod def poll(cls, context): -- cgit v1.2.3 From e6855507a5a0c1611eef98b23acfbd009ec49eb4 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 3 Jun 2022 16:28:09 +0300 Subject: Constraints: add missing calls to initialize custom space. Add calls to a few locations that look like they may need to initialize the Custom Space matrix, i.e. generally any place that computes target matrices. Differential Revision: https://developer.blender.org/D9732 --- source/blender/blenkernel/intern/constraint.c | 3 +++ source/blender/draw/engines/overlay/overlay_extra.c | 2 ++ source/blender/editors/transform/transform_mode.c | 3 +++ 3 files changed, 8 insertions(+) diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c index 8400d610a32..c32a6c6c515 100644 --- a/source/blender/blenkernel/intern/constraint.c +++ b/source/blender/blenkernel/intern/constraint.c @@ -6259,6 +6259,9 @@ void BKE_constraint_target_matrix_get(struct Depsgraph *depsgraph, } } + /* Initialize the custom space for use in calculating the matrices. */ + BKE_constraint_custom_object_space_init(cob, con); + /* get targets - we only need the first one though (and there should only be one) */ cti->get_constraint_targets(con, &targets); diff --git a/source/blender/draw/engines/overlay/overlay_extra.c b/source/blender/draw/engines/overlay/overlay_extra.c index 9d478310104..738ea5dd8e7 100644 --- a/source/blender/draw/engines/overlay/overlay_extra.c +++ b/source/blender/draw/engines/overlay/overlay_extra.c @@ -1315,6 +1315,8 @@ static void OVERLAY_relationship_lines(OVERLAY_ExtraCallBuffers *cb, if ((curcon->ui_expand_flag & (1 << 0)) && BKE_constraint_targets_get(curcon, &targets)) { bConstraintTarget *ct; + BKE_constraint_custom_object_space_init(cob, curcon); + for (ct = targets.first; ct; ct = ct->next) { /* calculate target's matrix */ if (cti->get_target_matrix) { diff --git a/source/blender/editors/transform/transform_mode.c b/source/blender/editors/transform/transform_mode.c index 5ba0f08ee1c..356828f513f 100644 --- a/source/blender/editors/transform/transform_mode.c +++ b/source/blender/editors/transform/transform_mode.c @@ -292,6 +292,9 @@ void constraintTransLim(const TransInfo *t, TransData *td) continue; } + /* Initialize the custom space for use in calculating the matrices. */ + BKE_constraint_custom_object_space_init(&cob, con); + /* get constraint targets if needed */ BKE_constraint_targets_for_solving_get(t->depsgraph, con, &cob, &targets, ctime); -- cgit v1.2.3 From 8d69c6c4e7c5417f88603d5ccb2c4bb0e156aa1e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 18 Jun 2022 19:08:51 +0300 Subject: Constraints: add checks to specially handle the custom space target. - The custom space target never needs B-Bone data (used by depsgraph). - When drawing the relationship lines use the space matrix directly. - Don't use the custom target to control the target space type dropdown. Differential Revision: https://developer.blender.org/D9732 --- source/blender/blenkernel/intern/constraint.c | 7 +++++-- source/blender/draw/engines/overlay/overlay_extra.c | 5 ++++- source/blender/makesrna/intern/rna_constraint.c | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c index c32a6c6c515..0534899a86c 100644 --- a/source/blender/blenkernel/intern/constraint.c +++ b/source/blender/blenkernel/intern/constraint.c @@ -5844,9 +5844,12 @@ static bConstraint *add_new_constraint(Object *ob, return con; } -bool BKE_constraint_target_uses_bbone(struct bConstraint *con, - struct bConstraintTarget *UNUSED(ct)) +bool BKE_constraint_target_uses_bbone(struct bConstraint *con, struct bConstraintTarget *ct) { + if (ct->flag & CONSTRAINT_TAR_CUSTOM_SPACE) { + return false; + } + return (con->flag & CONSTRAINT_BBONE_SHAPE) || (con->type == CONSTRAINT_TYPE_ARMATURE); } diff --git a/source/blender/draw/engines/overlay/overlay_extra.c b/source/blender/draw/engines/overlay/overlay_extra.c index 738ea5dd8e7..4354777986c 100644 --- a/source/blender/draw/engines/overlay/overlay_extra.c +++ b/source/blender/draw/engines/overlay/overlay_extra.c @@ -1319,7 +1319,10 @@ static void OVERLAY_relationship_lines(OVERLAY_ExtraCallBuffers *cb, for (ct = targets.first; ct; ct = ct->next) { /* calculate target's matrix */ - if (cti->get_target_matrix) { + if (ct->flag & CONSTRAINT_TAR_CUSTOM_SPACE) { + copy_m4_m4(ct->matrix, cob->space_obj_world_matrix); + } + else if (cti->get_target_matrix) { cti->get_target_matrix(depsgraph, curcon, cob, ct, DEG_get_ctime(depsgraph)); } else { diff --git a/source/blender/makesrna/intern/rna_constraint.c b/source/blender/makesrna/intern/rna_constraint.c index 1420ef36493..986de0930ed 100644 --- a/source/blender/makesrna/intern/rna_constraint.c +++ b/source/blender/makesrna/intern/rna_constraint.c @@ -603,7 +603,7 @@ static const EnumPropertyItem *rna_Constraint_target_space_itemf(bContext *UNUSE if (BKE_constraint_targets_get(con, &targets)) { for (ct = targets.first; ct; ct = ct->next) { - if (ct->tar && ct->tar->type == OB_ARMATURE) { + if (ct->tar && ct->tar->type == OB_ARMATURE && !(ct->flag & CONSTRAINT_TAR_CUSTOM_SPACE)) { break; } } -- cgit v1.2.3 From 7f8d05131a7738327ae125d065df44be492ff1f2 Mon Sep 17 00:00:00 2001 From: Aras Pranckevicius Date: Wed, 20 Jul 2022 14:27:14 +0300 Subject: IDManagement: Speedup ID unique name assignment by tracking used names/basenames/suffixes An implementation of T73412, roughly as outlined there: Track the names that are in use, as well as base names (before numeric suffix) plus a bit map for each base name, indicating which numeric suffixes are already used. This is done per-Main/Library, per-object-type. Timings (Windows, VS2022 Release build, AMD Ryzen 5950X): - Scene with 10k cubes, Shift+D to duplicate them all: 8.7s -> 1.9s. Name map memory usage for resulting 20k objects: 4.3MB. - Importing a 2.5GB .obj file of exported Blender 3.0 splash scene (24k objects), using the new C++ importer: 34.2s-> 22.0s. Name map memory usage for resulting scene: 8.6MB. - Importing Disney Moana USD scene (almost half a million objects): 56min -> 10min. Name map usage: ~100MB. Blender crashes later on when trying to render it, in the same place in both cases, but that's for another day. Reviewed By: Bastien Montagne Differential Revision: https://developer.blender.org/D14162 --- source/blender/blenkernel/BKE_lib_id.h | 8 +- source/blender/blenkernel/BKE_main.h | 4 + source/blender/blenkernel/BKE_main_namemap.h | 51 +++ source/blender/blenkernel/CMakeLists.txt | 2 + source/blender/blenkernel/intern/lib_id.c | 286 +--------------- source/blender/blenkernel/intern/lib_id_delete.c | 3 + source/blender/blenkernel/intern/lib_id_test.cc | 23 +- source/blender/blenkernel/intern/library.c | 30 +- source/blender/blenkernel/intern/main.c | 5 + source/blender/blenkernel/intern/main_namemap.cc | 361 +++++++++++++++++++++ source/blender/blenloader/intern/versioning_250.c | 12 +- source/blender/blenloader/intern/versioning_280.c | 2 +- source/blender/blenloader/intern/versioning_290.c | 2 +- source/blender/blenloader/intern/versioning_300.c | 2 +- .../blenloader/intern/versioning_defaults.c | 6 +- .../editors/space_outliner/outliner_draw.cc | 4 + source/blender/makesdna/DNA_ID.h | 8 + source/blender/makesrna/intern/rna_ID.c | 2 + 18 files changed, 522 insertions(+), 289 deletions(-) create mode 100644 source/blender/blenkernel/BKE_main_namemap.h create mode 100644 source/blender/blenkernel/intern/main_namemap.cc diff --git a/source/blender/blenkernel/BKE_lib_id.h b/source/blender/blenkernel/BKE_lib_id.h index beac608a138..59c842d614e 100644 --- a/source/blender/blenkernel/BKE_lib_id.h +++ b/source/blender/blenkernel/BKE_lib_id.h @@ -478,10 +478,12 @@ void BKE_lib_id_expand_local(struct Main *bmain, struct ID *id, int flags); * * \return true if a new name had to be created. */ -bool BKE_id_new_name_validate(struct ListBase *lb, +bool BKE_id_new_name_validate(struct Main *bmain, + struct ListBase *lb, struct ID *id, const char *name, - bool do_linked_data) ATTR_NONNULL(1, 2); + bool do_linked_data) ATTR_NONNULL(1, 2, 3); + /** * Pull an ID out of a library (make it local). Only call this for IDs that * don't have other library users. @@ -526,7 +528,7 @@ void BKE_main_lib_objects_recalc_all(struct Main *bmain); /** * Only for repairing files via versioning, avoid for general use. */ -void BKE_main_id_repair_duplicate_names_listbase(struct ListBase *lb); +void BKE_main_id_repair_duplicate_names_listbase(struct Main *bmain, struct ListBase *lb); #define MAX_ID_FULL_NAME (64 + 64 + 3 + 1) /* 64 is MAX_ID_NAME - 2 */ #define MAX_ID_FULL_NAME_UI (MAX_ID_FULL_NAME + 3) /* Adds 'keycode' two letters at beginning. */ diff --git a/source/blender/blenkernel/BKE_main.h b/source/blender/blenkernel/BKE_main.h index 2c444f42c46..4d26ed11f1b 100644 --- a/source/blender/blenkernel/BKE_main.h +++ b/source/blender/blenkernel/BKE_main.h @@ -36,6 +36,7 @@ struct IDNameLib_Map; struct ImBuf; struct Library; struct MainLock; +struct UniqueName_Map; /* Blender thumbnail, as written on file (width, height, and data as char RGBA). */ /* We pack pixel data after that struct. */ @@ -193,6 +194,9 @@ typedef struct Main { /* IDMap of IDs. Currently used when reading (expanding) libraries. */ struct IDNameLib_Map *id_map; + /* Used for efficient calculations of unique names. */ + struct UniqueName_Map *name_map; + struct MainLock *lock; } Main; diff --git a/source/blender/blenkernel/BKE_main_namemap.h b/source/blender/blenkernel/BKE_main_namemap.h new file mode 100644 index 00000000000..d201e45a2c9 --- /dev/null +++ b/source/blender/blenkernel/BKE_main_namemap.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#pragma once + +/** \file + * \ingroup bke + * + * API to ensure name uniqueness. + * + * Main database contains the UniqueName_Map which is a cache that tracks names, base + * names and their suffixes currently in use. So that whenever a new name has to be + * assigned or validated, it can quickly ensure uniqueness and adjust the name in case + * of collisions. + * + * \section Function Names + * + * - `BKE_main_namemap_` Should be used for functions in this file. + */ + +#include "BLI_compiler_attrs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct ID; +struct Main; +struct UniqueName_Map; + +struct UniqueName_Map *BKE_main_namemap_create(void) ATTR_WARN_UNUSED_RESULT; +void BKE_main_namemap_destroy(struct UniqueName_Map **r_name_map) ATTR_NONNULL(); + +/** + * Ensures the given name is unique within the given ID type. + * + * In case of name collisions, the name will be adjusted to be unique. + * + * \return true if the name had to be adjusted for uniqueness. + */ +bool BKE_main_namemap_get_name(struct Main *bmain, struct ID *id, char *name) ATTR_NONNULL(); + +/** + * Remove a given name from usage. + * + * Call this whenever deleting or renaming an object. + */ +void BKE_main_namemap_remove_name(struct Main *bmain, struct ID *id, const char *name) + ATTR_NONNULL(); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 044a306a1b9..45a9e85874d 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -185,6 +185,7 @@ set(SRC intern/linestyle.c intern/main.c intern/main_idmap.c + intern/main_namemap.cc intern/mask.c intern/mask_evaluate.c intern/mask_rasterize.c @@ -412,6 +413,7 @@ set(SRC BKE_linestyle.h BKE_main.h BKE_main_idmap.h + BKE_main_namemap.h BKE_mask.h BKE_material.h BKE_mball.h diff --git a/source/blender/blenkernel/intern/lib_id.c b/source/blender/blenkernel/intern/lib_id.c index 90a4853fd3e..affa1e72ad0 100644 --- a/source/blender/blenkernel/intern/lib_id.c +++ b/source/blender/blenkernel/intern/lib_id.c @@ -53,6 +53,7 @@ #include "BKE_lib_query.h" #include "BKE_lib_remap.h" #include "BKE_main.h" +#include "BKE_main_namemap.h" #include "BKE_node.h" #include "BKE_rigidbody.h" @@ -186,7 +187,7 @@ void BKE_lib_id_clear_library_data(Main *bmain, ID *id, const int flags) id->tag &= ~(LIB_TAG_INDIRECT | LIB_TAG_EXTERN); id->flag &= ~LIB_INDIRECT_WEAK_LINK; if (id_in_mainlist) { - if (BKE_id_new_name_validate(which_libbase(bmain, GS(id->name)), id, NULL, false)) { + if (BKE_id_new_name_validate(bmain, which_libbase(bmain, GS(id->name)), id, NULL, false)) { bmain->is_memfile_undo_written = false; } } @@ -842,7 +843,7 @@ void BKE_libblock_management_main_add(Main *bmain, void *idv) BLI_addtail(lb, id); /* We need to allow adding extra datablocks into libraries too, e.g. to support generating new * overrides for recursive resync. */ - BKE_id_new_name_validate(lb, id, NULL, true); + BKE_id_new_name_validate(bmain, lb, id, NULL, true); /* alphabetic insertion: is in new_id */ id->tag &= ~(LIB_TAG_NO_MAIN | LIB_TAG_NO_USER_REFCOUNT); bmain->is_memfile_undo_written = false; @@ -865,6 +866,7 @@ void BKE_libblock_management_main_remove(Main *bmain, void *idv) ListBase *lb = which_libbase(bmain, GS(id->name)); BKE_main_lock(bmain); BLI_remlink(lb, id); + BKE_main_namemap_remove_name(bmain, id, id->name + 2); id->tag |= LIB_TAG_NO_MAIN; bmain->is_memfile_undo_written = false; BKE_main_unlock(bmain); @@ -958,7 +960,7 @@ void BKE_main_id_flag_all(Main *bmain, const int flag, const bool value) } } -void BKE_main_id_repair_duplicate_names_listbase(ListBase *lb) +void BKE_main_id_repair_duplicate_names_listbase(Main *bmain, ListBase *lb) { int lb_len = 0; LISTBASE_FOREACH (ID *, id, lb) { @@ -982,7 +984,7 @@ void BKE_main_id_repair_duplicate_names_listbase(ListBase *lb) } for (i = 0; i < lb_len; i++) { if (!BLI_gset_add(gset, id_array[i]->name + 2)) { - BKE_id_new_name_validate(lb, id_array[i], NULL, false); + BKE_id_new_name_validate(bmain, lb, id_array[i], NULL, false); } } BLI_gset_free(gset, NULL); @@ -1073,7 +1075,7 @@ void *BKE_libblock_alloc(Main *bmain, short type, const char *name, const int fl BKE_main_lock(bmain); BLI_addtail(lb, id); - BKE_id_new_name_validate(lb, id, name, false); + BKE_id_new_name_validate(bmain, lb, id, name, false); bmain->is_memfile_undo_written = false; /* alphabetic insertion: is in new_id */ BKE_main_unlock(bmain); @@ -1415,255 +1417,8 @@ void id_sort_by_name(ListBase *lb, ID *id, ID *id_sorting_hint) #undef ID_SORT_STEP_SIZE } -/* NOTE: this code assumes and ensures that the suffix number can never go beyond 1 billion. */ -#define MAX_NUMBER 1000000000 -/* We do not want to get "name.000", so minimal number is 1. */ -#define MIN_NUMBER 1 -/* The maximum value up to which we search for the actual smallest unused number. Beyond that - * value, we will only use the first biggest unused number, without trying to 'fill the gaps' - * in-between already used numbers... */ -#define MAX_NUMBERS_IN_USE 1024 - -/** - * Helper building final ID name from given base_name and number. - * - * If everything goes well and we do generate a valid final ID name in given name, we return - * true. In case the final name would overflow the allowed ID name length, or given number is - * bigger than maximum allowed value, we truncate further the base_name (and given name, which is - * assumed to have the same 'base_name' part), and return false. - */ -static bool id_name_final_build(char *name, char *base_name, size_t base_name_len, int number) -{ - char number_str[11]; /* Dot + nine digits + NULL terminator. */ - size_t number_str_len = BLI_snprintf_rlen(number_str, ARRAY_SIZE(number_str), ".%.3d", number); - - /* If the number would lead to an overflow of the maximum ID name length, we need to truncate - * the base name part and do all the number checks again. */ - if (base_name_len + number_str_len >= MAX_ID_NAME - 2 || number >= MAX_NUMBER) { - if (base_name_len + number_str_len >= MAX_ID_NAME - 2) { - base_name_len = MAX_ID_NAME - 2 - number_str_len - 1; - } - else { - base_name_len--; - } - base_name[base_name_len] = '\0'; - - /* Code above may have generated invalid utf-8 string, due to raw truncation. - * Ensure we get a valid one now. */ - base_name_len -= (size_t)BLI_str_utf8_invalid_strip(base_name, base_name_len); - - /* Also truncate orig name, and start the whole check again. */ - name[base_name_len] = '\0'; - return false; - } - - /* We have our final number, we can put it in name and exit the function. */ - BLI_strncpy(name + base_name_len, number_str, number_str_len + 1); - return true; -} - -/** - * Check to see if an ID name is already used, and find a new one if so. - * Return true if a new name was created (returned in name). - * - * Normally the ID that's being checked is already in the ListBase, so ID *id points at the new - * entry. The Python Library module needs to know what the name of a data-block will be before it - * is appended, in this case ID *id is NULL. - */ -static bool check_for_dupid(ListBase *lb, ID *id, char *name, ID **r_id_sorting_hint) -{ - BLI_assert(strlen(name) < MAX_ID_NAME - 2); - - *r_id_sorting_hint = NULL; - - ID *id_test = lb->first; - bool is_name_changed = false; - - if (id_test == NULL) { - return is_name_changed; - } - - const short id_type = (short)GS(id_test->name); - - /* Static storage of previous handled ID/name info, used to perform a quicker test and optimize - * creation of huge number of IDs using the same given base name. */ - static char prev_orig_base_name[MAX_ID_NAME - 2] = {0}; - static char prev_final_base_name[MAX_ID_NAME - 2] = {0}; - static short prev_id_type = ID_LINK_PLACEHOLDER; /* Should never exist in actual ID list. */ - static int prev_number = MIN_NUMBER - 1; - - /* Initial test to check whether we can 'shortcut' the more complex loop of the main code - * below. Note that we do not do that for low numbers, as that would prevent using actual - * smallest available number in some cases, and benefits of this special case handling mostly - * show up with high numbers anyway. */ - if (id_type == prev_id_type && prev_number >= MAX_NUMBERS_IN_USE && - prev_number < MAX_NUMBER - 1 && name[0] == prev_final_base_name[0]) { - - /* Get the name and number parts ("name.number"). */ - char base_name[MAX_ID_NAME - 2]; - int number = MIN_NUMBER; - size_t base_name_len = BLI_split_name_num(base_name, &number, name, '.'); - size_t prev_final_base_name_len = strlen(prev_final_base_name); - size_t prev_orig_base_name_len = strlen(prev_orig_base_name); - - if (base_name_len == prev_orig_base_name_len && - STREQLEN(base_name, prev_orig_base_name, prev_orig_base_name_len)) { - /* Once we have ensured given base_name and original previous one are the same, we can - * check that previously used number is actually used, and that next one is free. */ - /* Note that from now on, we only used previous final base name, as it might have been - * truncated from original one due to number suffix length. */ - char final_name[MAX_ID_NAME - 2]; - char prev_final_name[MAX_ID_NAME - 2]; - BLI_strncpy(final_name, prev_final_base_name, prev_final_base_name_len + 1); - BLI_strncpy(prev_final_name, prev_final_base_name, prev_final_base_name_len + 1); - - if (id_name_final_build(final_name, base_name, prev_final_base_name_len, prev_number + 1) && - id_name_final_build(prev_final_name, base_name, prev_final_base_name_len, prev_number)) { - /* We successfully built valid final names of previous and current iterations, - * now we have to ensure that previous final name is indeed used in current ID list, - * and that current one is not. */ - bool is_valid = false; - for (id_test = lb->first; id_test; id_test = id_test->next) { - if (id != id_test && id_test->lib == id->lib) { - if (id_test->name[2] == final_name[0] && STREQ(final_name, id_test->name + 2)) { - /* We expect final_name to not be already used, so this is a failure. */ - is_valid = false; - break; - } - /* Previous final name should only be found once in the list, so if it was found - * already, no need to do a string comparison again. */ - if (!is_valid && id_test->name[2] == prev_final_name[0] && - STREQ(prev_final_name, id_test->name + 2)) { - is_valid = true; - *r_id_sorting_hint = id_test; - } - } - } - - if (is_valid) { - /* Only the number changed, prev_orig_base_name, prev_final_base_name and prev_id_type - * remain the same. */ - prev_number++; - - strcpy(name, final_name); - return true; - } - } - } - } - - /* To speed up finding smallest unused number within [0 .. MAX_NUMBERS_IN_USE - 1]. - * We do not bother beyond that point. */ - ID *ids_in_use[MAX_NUMBERS_IN_USE] = {NULL}; - - bool is_first_run = true; - while (true) { - /* Get the name and number parts ("name.number"). */ - char base_name[MAX_ID_NAME - 2]; - int number = MIN_NUMBER; - size_t base_name_len = BLI_split_name_num(base_name, &number, name, '.'); - - /* Store previous original given base name now, as we might alter it later in code below. */ - if (is_first_run) { - strcpy(prev_orig_base_name, base_name); - is_first_run = false; - } - - /* In case we get an insane initial number suffix in given name. */ - /* NOTE: BLI_split_name_num() cannot return negative numbers, so we do not have to check for - * that here. */ - if (number >= MAX_NUMBER || number < MIN_NUMBER) { - number = MIN_NUMBER; - } - - bool is_orig_name_used = false; - for (id_test = lb->first; id_test; id_test = id_test->next) { - char base_name_test[MAX_ID_NAME - 2]; - int number_test; - if ((id != id_test) && (id_test->lib == id->lib) && (name[0] == id_test->name[2]) && - (ELEM(id_test->name[base_name_len + 2], '.', '\0')) && - STREQLEN(name, id_test->name + 2, base_name_len) && - (BLI_split_name_num(base_name_test, &number_test, id_test->name + 2, '.') == - base_name_len)) { - /* If we did not yet encounter exact same name as the given one, check the remaining - * parts of the strings. */ - if (!is_orig_name_used) { - is_orig_name_used = STREQ(name + base_name_len, id_test->name + 2 + base_name_len); - } - /* Mark number of current id_test name as used, if possible. */ - if (number_test < MAX_NUMBERS_IN_USE) { - ids_in_use[number_test] = id_test; - } - /* Keep track of first largest unused number. */ - if (number <= number_test) { - *r_id_sorting_hint = id_test; - number = number_test + 1; - } - } - } - - /* If there is no double, we are done. - * Note however that name might have been changed (truncated) in a previous iteration - * already. - */ - if (!is_orig_name_used) { - /* Don't bother updating `prev_*` static variables here, this case is not supposed to happen - * that often, and is not straight-forward here, so just ignore and reset them to default. */ - prev_id_type = ID_LINK_PLACEHOLDER; - prev_final_base_name[0] = '\0'; - prev_number = MIN_NUMBER - 1; - - /* Value set previously is meaningless in that case. */ - *r_id_sorting_hint = NULL; - - return is_name_changed; - } - - /* Decide which value of number to use, either the smallest unused one if possible, or - * default to the first largest unused one we got from previous loop. */ - for (int i = MIN_NUMBER; i < MAX_NUMBERS_IN_USE; i++) { - if (ids_in_use[i] == NULL) { - number = i; - if (i > 0) { - *r_id_sorting_hint = ids_in_use[i - 1]; - } - break; - } - } - /* At this point, number is either the lowest unused number within - * [MIN_NUMBER .. MAX_NUMBERS_IN_USE - 1], or 1 greater than the largest used number if all - * those low ones are taken. - * We can't be bothered to look for the lowest unused number beyond - * (MAX_NUMBERS_IN_USE - 1). - */ - /* We know for wure that name will be changed. */ - is_name_changed = true; - - /* If id_name_final_build helper returns false, it had to truncate further given name, hence - * we have to go over the whole check again. */ - if (!id_name_final_build(name, base_name, base_name_len, number)) { - /* We have to clear our list of small used numbers before we do the whole check again. */ - memset(ids_in_use, 0, sizeof(ids_in_use)); - - continue; - } - - /* Update `prev_*` static variables, in case next call is for the same type of IDs and with the - * same initial base name, we can skip a lot of above process. */ - prev_id_type = id_type; - strcpy(prev_final_base_name, base_name); - prev_number = number; - - return is_name_changed; - } - -#undef MAX_NUMBERS_IN_USE -} - -#undef MIN_NUMBER -#undef MAX_NUMBER - -bool BKE_id_new_name_validate(ListBase *lb, ID *id, const char *tname, const bool do_linked_data) +bool BKE_id_new_name_validate( + struct Main *bmain, ListBase *lb, ID *id, const char *tname, const bool do_linked_data) { bool result = false; char name[MAX_ID_NAME - 2]; @@ -1693,22 +1448,10 @@ bool BKE_id_new_name_validate(ListBase *lb, ID *id, const char *tname, const boo BLI_str_utf8_invalid_strip(name, strlen(name)); } - ID *id_sorting_hint = NULL; - result = check_for_dupid(lb, id, name, &id_sorting_hint); - strcpy(id->name + 2, name); - - /* This was in 2.43 and previous releases - * however all data in blender should be sorted, not just duplicate names - * sorting should not hurt, but noting just in case it alters the way other - * functions work, so sort every time. */ -#if 0 - if (result) { - id_sort_by_name(lb, id, id_sorting_hint); - } -#endif - - id_sort_by_name(lb, id, id_sorting_hint); + result = BKE_main_namemap_get_name(bmain, id, name); + strcpy(id->name + 2, name); + id_sort_by_name(lb, id, NULL); return result; } @@ -2074,7 +1817,7 @@ void BLI_libblock_ensure_unique_name(Main *bmain, const char *name) idtest = BLI_findstring(lb, name + 2, offsetof(ID, name) + 2); if (idtest != NULL && !ID_IS_LINKED(idtest)) { /* BKE_id_new_name_validate also takes care of sorting. */ - BKE_id_new_name_validate(lb, idtest, NULL, false); + BKE_id_new_name_validate(bmain, lb, idtest, NULL, false); bmain->is_memfile_undo_written = false; } } @@ -2082,8 +1825,9 @@ void BLI_libblock_ensure_unique_name(Main *bmain, const char *name) void BKE_libblock_rename(Main *bmain, ID *id, const char *name) { BLI_assert(!ID_IS_LINKED(id)); + BKE_main_namemap_remove_name(bmain, id, id->name + 2); ListBase *lb = which_libbase(bmain, GS(id->name)); - if (BKE_id_new_name_validate(lb, id, name, false)) { + if (BKE_id_new_name_validate(bmain, lb, id, name, false)) { bmain->is_memfile_undo_written = false; } } diff --git a/source/blender/blenkernel/intern/lib_id_delete.c b/source/blender/blenkernel/intern/lib_id_delete.c index f14c11a949e..405b0be70f9 100644 --- a/source/blender/blenkernel/intern/lib_id_delete.c +++ b/source/blender/blenkernel/intern/lib_id_delete.c @@ -28,6 +28,7 @@ #include "BKE_lib_remap.h" #include "BKE_library.h" #include "BKE_main.h" +#include "BKE_main_namemap.h" #include "lib_intern.h" @@ -151,6 +152,7 @@ void BKE_id_free_ex(Main *bmain, void *idv, int flag, const bool use_flag_from_i if ((flag & LIB_ID_FREE_NO_MAIN) == 0) { ListBase *lb = which_libbase(bmain, type); BLI_remlink(lb, id); + BKE_main_namemap_remove_name(bmain, id, id->name + 2); } BKE_libblock_free_data(id, (flag & LIB_ID_FREE_NO_USER_REFCOUNT) == 0); @@ -237,6 +239,7 @@ static size_t id_delete(Main *bmain, const bool do_tagged_deletion) /* NOTE: in case we delete a library, we also delete all its datablocks! */ if ((id->tag & tag) || (ID_IS_LINKED(id) && (id->lib->id.tag & tag))) { BLI_remlink(lb, id); + BKE_main_namemap_remove_name(bmain, id, id->name + 2); BLI_addtail(&tagged_deleted_ids, id); /* Do not tag as no_main now, we want to unlink it first (lower-level ID management * code has some specific handling of 'no main' IDs that would be a problem in that diff --git a/source/blender/blenkernel/intern/lib_id_test.cc b/source/blender/blenkernel/intern/lib_id_test.cc index 1aba78eed8f..ea3f5395f1f 100644 --- a/source/blender/blenkernel/intern/lib_id_test.cc +++ b/source/blender/blenkernel/intern/lib_id_test.cc @@ -10,6 +10,7 @@ #include "BKE_idtype.h" #include "BKE_lib_id.h" #include "BKE_main.h" +#include "BKE_main_namemap.h" #include "DNA_ID.h" #include "DNA_mesh_types.h" @@ -57,15 +58,20 @@ TEST(lib_id_main_sort, local_ids_1) test_lib_id_main_sort_check_order({id_a, id_b, id_c}); } -static void change_lib(Main * /*bmain*/, ID *id, Library *lib) +static void change_lib(Main *bmain, ID *id, Library *lib) { + if (id->lib == lib) { + return; + } + BKE_main_namemap_remove_name(bmain, id, id->name + 2); id->lib = lib; } static void change_name(Main *bmain, ID *id, const char *name) { + BKE_main_namemap_remove_name(bmain, id, id->name + 2); BLI_strncpy(id->name + 2, name, MAX_NAME); - BKE_id_new_name_validate(&bmain->objects, id, nullptr, true); + BKE_id_new_name_validate(bmain, &bmain->objects, id, nullptr, true); } TEST(lib_id_main_sort, linked_ids_1) @@ -389,4 +395,17 @@ TEST(lib_id_main_unique_name, names_are_unique_per_id_type) EXPECT_STREQ(id_c->name + 2, "Foo.001"); } +TEST(lib_id_main_unique_name, name_huge_number_suffix) +{ + LibIDMainSortTestContext ctx; + + /* Use numeric suffix that is really large: should come through + * fine, since no duplicates with other names. */ + ID *id_a = static_cast(BKE_id_new(ctx.bmain, ID_OB, "SuperLong.1234567890")); + EXPECT_STREQ(id_a->name + 2, "SuperLong.1234567890"); + /* Now create with the same name again: should get 001 suffix. */ + ID *id_b = static_cast(BKE_id_new(ctx.bmain, ID_OB, "SuperLong.1234567890")); + EXPECT_STREQ(id_b->name + 2, "SuperLong.001"); +} + } // namespace blender::bke::tests diff --git a/source/blender/blenkernel/intern/library.c b/source/blender/blenkernel/intern/library.c index 4962b1c448e..fee4cae2701 100644 --- a/source/blender/blenkernel/intern/library.c +++ b/source/blender/blenkernel/intern/library.c @@ -26,14 +26,26 @@ #include "BKE_lib_query.h" #include "BKE_library.h" #include "BKE_main.h" +#include "BKE_main_namemap.h" #include "BKE_packedFile.h" /* Unused currently. */ // static CLG_LogRef LOG = {.identifier = "bke.library"}; +struct BlendWriter; +struct BlendDataReader; + +static void library_runtime_reset(Library *lib) +{ + if (lib->runtime.name_map) { + BKE_main_namemap_destroy(&lib->runtime.name_map); + } +} + static void library_free_data(ID *id) { Library *library = (Library *)id; + library_runtime_reset(library); if (library->packedfile) { BKE_packedfile_free(library->packedfile); } @@ -61,6 +73,20 @@ static void library_foreach_path(ID *id, BPathForeachPathData *bpath_data) } } +static void library_blend_write(struct BlendWriter *UNUSED(writer), + ID *id, + const void *UNUSED(id_address)) +{ + Library *lib = (Library *)id; + library_runtime_reset(lib); +} + +static void library_blend_read_data(struct BlendDataReader *UNUSED(reader), ID *id) +{ + Library *lib = (Library *)id; + library_runtime_reset(lib); +} + IDTypeInfo IDType_ID_LI = { .id_code = ID_LI, .id_filter = FILTER_ID_LI, @@ -81,8 +107,8 @@ IDTypeInfo IDType_ID_LI = { .foreach_path = library_foreach_path, .owner_get = NULL, - .blend_write = NULL, - .blend_read_data = NULL, + .blend_write = library_blend_write, + .blend_read_data = library_blend_read_data, .blend_read_lib = NULL, .blend_read_expand = NULL, diff --git a/source/blender/blenkernel/intern/main.c b/source/blender/blenkernel/intern/main.c index b9ed783fa8c..239aacf28d6 100644 --- a/source/blender/blenkernel/intern/main.c +++ b/source/blender/blenkernel/intern/main.c @@ -24,6 +24,7 @@ #include "BKE_lib_query.h" #include "BKE_main.h" #include "BKE_main_idmap.h" +#include "BKE_main_namemap.h" #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" @@ -184,6 +185,10 @@ void BKE_main_free(Main *mainvar) BKE_main_idmap_destroy(mainvar->id_map); } + if (mainvar->name_map) { + BKE_main_namemap_destroy(&mainvar->name_map); + } + BLI_spin_end((SpinLock *)mainvar->lock); MEM_freeN(mainvar->lock); MEM_freeN(mainvar); diff --git a/source/blender/blenkernel/intern/main_namemap.cc b/source/blender/blenkernel/intern/main_namemap.cc new file mode 100644 index 00000000000..32f4c6be639 --- /dev/null +++ b/source/blender/blenkernel/intern/main_namemap.cc @@ -0,0 +1,361 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup bke + */ + +#include "BKE_idtype.h" +#include "BKE_main.h" +#include "BKE_main_namemap.h" + +#include "BLI_assert.h" +#include "BLI_bitmap.h" +#include "BLI_ghash.h" +#include "BLI_listbase.h" +#include "BLI_map.hh" +#include "BLI_math_base.hh" +#include "BLI_set.hh" +#include "BLI_string_utf8.h" +#include "BLI_string_utils.h" + +#include "DNA_ID.h" + +#include "MEM_guardedalloc.h" + +//#define DEBUG_PRINT_MEMORY_USAGE + +using namespace blender; + +/* Assumes and ensure that the suffix number can never go beyond 1 billion. */ +#define MAX_NUMBER 1000000000 +/* We do not want to get "name.000", so minimal number is 1. */ +#define MIN_NUMBER 1 + +/** + * Helper building final ID name from given base_name and number. + * + * If everything goes well and we do generate a valid final ID name in given name, we return + * true. In case the final name would overflow the allowed ID name length, or given number is + * bigger than maximum allowed value, we truncate further the base_name (and given name, which is + * assumed to have the same 'base_name' part), and return false. + */ +static bool id_name_final_build(char *name, char *base_name, size_t base_name_len, int number) +{ + char number_str[11]; /* Dot + nine digits + NULL terminator. */ + size_t number_str_len = BLI_snprintf_rlen(number_str, ARRAY_SIZE(number_str), ".%.3d", number); + + /* If the number would lead to an overflow of the maximum ID name length, we need to truncate + * the base name part and do all the number checks again. */ + if (base_name_len + number_str_len >= MAX_NAME || number >= MAX_NUMBER) { + if (base_name_len + number_str_len >= MAX_NAME) { + base_name_len = MAX_NAME - number_str_len - 1; + } + else { + base_name_len--; + } + base_name[base_name_len] = '\0'; + + /* Code above may have generated invalid utf-8 string, due to raw truncation. + * Ensure we get a valid one now. */ + base_name_len -= (size_t)BLI_str_utf8_invalid_strip(base_name, base_name_len); + + /* Also truncate orig name, and start the whole check again. */ + name[base_name_len] = '\0'; + return false; + } + + /* We have our final number, we can put it in name and exit the function. */ + BLI_strncpy(name + base_name_len, number_str, number_str_len + 1); + return true; +} + +/* Key used in set/map lookups: just a string name. */ +struct UniqueName_Key { + char name[MAX_NAME]; + uint64_t hash() const + { + return BLI_ghashutil_strhash_n(name, MAX_NAME); + } + bool operator==(const UniqueName_Key &o) const + { + return !BLI_ghashutil_strcmp(name, o.name); + } +}; + +/* Tracking of used numeric suffixes. For each base name: + * + * - Exactly track which of the lowest 1024 suffixes are in use, + * whenever there is a name collision we pick the lowest "unused" + * one. This is done with a bit map. + * - Above 1024, do not track them exactly, just track the maximum + * suffix value seen so far. Upon collision, assign number that is + * one larger. + */ +struct UniqueName_Value { + static constexpr unsigned max_exact_tracking = 1024; + BLI_BITMAP_DECLARE(mask, max_exact_tracking); + int max_value = 0; + + void mark_used(int number) + { + if (number >= 0 && number < max_exact_tracking) { + BLI_BITMAP_ENABLE(mask, number); + } + if (number < MAX_NUMBER) { + math::max_inplace(max_value, number); + } + } + + void mark_unused(int number) + { + if (number >= 0 && number < max_exact_tracking) { + BLI_BITMAP_DISABLE(mask, number); + } + if (number > 0 && number == max_value) { + --max_value; + } + } + + bool use_if_unused(int number) + { + if (number >= 0 && number < max_exact_tracking) { + if (!BLI_BITMAP_TEST_BOOL(mask, number)) { + BLI_BITMAP_ENABLE(mask, number); + math::max_inplace(max_value, number); + return true; + } + } + return false; + } + + int use_smallest_unused() + { + /* Find the smallest available one <1k. + * However we never want to pick zero ("none") suffix, even if it is + * available, e.g. if Foo.001 was used and we want to create another + * Foo.001, we should return Foo.002 and not Foo. + * So while searching, mark #0 as "used" to make sure we don't find it, + * and restore the value afterwards. */ + + BLI_bitmap prev_first = mask[0]; + mask[0] |= 1; + int result = BLI_bitmap_find_first_unset(mask, max_exact_tracking); + if (result >= 0) { + BLI_BITMAP_ENABLE(mask, result); + math::max_inplace(max_value, result); + } + mask[0] |= prev_first & 1; /* Restore previous value of #0 bit. */ + return result; + } +}; + +/* Tracking of names for a single ID type. */ +struct UniqueName_TypeMap { + /* Set of full names that are in use. */ + Set full_names; + /* For each base name (i.e. without numeric suffix), track the + * numeric suffixes that are in use. */ + Map base_name_to_num_suffix; +}; + +struct UniqueName_Map { + UniqueName_TypeMap type_maps[INDEX_ID_MAX]; + + UniqueName_TypeMap *find_by_type(short id_type) + { + int index = BKE_idtype_idcode_to_index(id_type); + return index >= 0 ? &type_maps[index] : nullptr; + } +}; + +struct UniqueName_Map *BKE_main_namemap_create() +{ + struct UniqueName_Map *map = MEM_new(__func__); + return map; +} + +void BKE_main_namemap_destroy(struct UniqueName_Map **r_name_map) +{ +#ifdef DEBUG_PRINT_MEMORY_USAGE + int64_t size_sets = 0; + int64_t size_maps = 0; + for (const UniqueName_TypeMap &type_map : (*r_name_map)->type_maps) { + size_sets += type_map.full_names.size_in_bytes(); + size_maps += type_map.base_name_to_num_suffix.size_in_bytes(); + } + printf( + "NameMap memory usage: sets %.1fKB, maps %.1fKB\n", size_sets / 1024.0, size_maps / 1024.0); +#endif + MEM_delete(*r_name_map); + *r_name_map = nullptr; +} + +static void main_namemap_populate(UniqueName_Map *name_map, struct Main *bmain, ID *ignore_id) +{ + BLI_assert_msg(name_map != nullptr, "name_map should not be null"); + for (UniqueName_TypeMap &type_map : name_map->type_maps) { + type_map.base_name_to_num_suffix.clear(); + } + Library *library = ignore_id->lib; + ID *id; + FOREACH_MAIN_ID_BEGIN (bmain, id) { + if ((id == ignore_id) || (id->lib != library)) { + continue; + } + UniqueName_TypeMap *type_map = name_map->find_by_type(GS(id->name)); + BLI_assert(type_map != nullptr); + + /* Insert the full name into the set. */ + UniqueName_Key key; + strncpy(key.name, id->name + 2, MAX_NAME); + type_map->full_names.add(key); + + /* Get the name and number parts ("name.number"). */ + int number = MIN_NUMBER; + BLI_split_name_num(key.name, &number, id->name + 2, '.'); + + /* Get and update the entry for this base name. */ + UniqueName_Value &val = type_map->base_name_to_num_suffix.lookup_or_add_default(key); + val.mark_used(number); + } + FOREACH_MAIN_ID_END; +} + +/* Get the name map object used for the given Main/ID. + * Lazily creates and populates the contents of the name map, if ensure_created is true. + * Note: if the contents are populated, the name of the given ID itself is not added. */ +static UniqueName_Map *get_namemap_for(Main *bmain, ID *id, bool ensure_created) +{ + if (id->lib != nullptr) { + if (ensure_created && id->lib->runtime.name_map == nullptr) { + id->lib->runtime.name_map = BKE_main_namemap_create(); + main_namemap_populate(id->lib->runtime.name_map, bmain, id); + } + return id->lib->runtime.name_map; + } + if (ensure_created && bmain->name_map == nullptr) { + bmain->name_map = BKE_main_namemap_create(); + main_namemap_populate(bmain->name_map, bmain, id); + } + return bmain->name_map; +} + +bool BKE_main_namemap_get_name(struct Main *bmain, struct ID *id, char *name) +{ + BLI_assert(bmain != nullptr); + BLI_assert(id != nullptr); + UniqueName_Map *name_map = get_namemap_for(bmain, id, true); + BLI_assert(name_map != nullptr); + BLI_assert(strlen(name) < MAX_NAME); + UniqueName_TypeMap *type_map = name_map->find_by_type(GS(id->name)); + BLI_assert(type_map != nullptr); + + bool is_name_changed = false; + + UniqueName_Key key; + while (true) { + /* Check if the full original name has a duplicate. */ + strncpy(key.name, name, MAX_NAME); + const bool has_dup = type_map->full_names.contains(key); + + /* Get the name and number parts ("name.number"). */ + int number = MIN_NUMBER; + size_t base_name_len = BLI_split_name_num(key.name, &number, name, '.'); + + bool added_new = false; + UniqueName_Value &val = type_map->base_name_to_num_suffix.lookup_or_add_cb(key, [&]() { + added_new = true; + return UniqueName_Value(); + }); + if (added_new || !has_dup) { + /* This base name is not used at all yet, or the full original + * name has no duplicates. The latter could happen if splitting + * by number would produce the same values, for different name + * strings (e.g. Foo.001 and Foo.1). */ + val.mark_used(number); + + if (!has_dup) { + strncpy(key.name, name, MAX_NAME); + type_map->full_names.add(key); + } + return is_name_changed; + } + + /* The base name is already used. But our number suffix might not be used yet. */ + int number_to_use = -1; + if (val.use_if_unused(number)) { + /* Our particular number suffix is not used yet: use it. */ + number_to_use = number; + } + else { + /* Find lowest free under 1k and use it. */ + number_to_use = val.use_smallest_unused(); + + /* Did not find one under 1k. */ + if (number_to_use == -1) { + if (number >= MIN_NUMBER && number > val.max_value) { + val.max_value = number; + number_to_use = number; + } + else { + val.max_value++; + number_to_use = val.max_value; + } + } + } + + /* Try to build final name from the current base name and the number. + * Note that this can fail due to too long base name, or a too large number, + * in which case it will shorten the base name, and we'll start again. */ + BLI_assert(number_to_use >= MIN_NUMBER); + if (id_name_final_build(name, key.name, base_name_len, number_to_use)) { + /* All good, add final name to the set. */ + strncpy(key.name, name, MAX_NAME); + type_map->full_names.add(key); + break; + } + + /* Name had to be truncated, or number too large: mark + * the output name as definitely changed, and proceed with the + * truncated name again. */ + is_name_changed = true; + } + return is_name_changed; +} + +void BKE_main_namemap_remove_name(struct Main *bmain, struct ID *id, const char *name) +{ + BLI_assert(bmain != nullptr); + BLI_assert(id != nullptr); + BLI_assert(name != nullptr); + /* Name is empty or not initialized yet, nothing to remove. */ + if (name[0] == '\0') { + return; + } + + struct UniqueName_Map *name_map = get_namemap_for(bmain, id, false); + if (name_map == nullptr) { + return; + } + BLI_assert(strlen(name) < MAX_NAME); + UniqueName_TypeMap *type_map = name_map->find_by_type(GS(id->name)); + BLI_assert(type_map != nullptr); + + UniqueName_Key key; + /* Remove full name from the set. */ + strncpy(key.name, name, MAX_NAME); + type_map->full_names.remove(key); + + int number = MIN_NUMBER; + BLI_split_name_num(key.name, &number, name, '.'); + UniqueName_Value *val = type_map->base_name_to_num_suffix.lookup_ptr(key); + if (val == nullptr) { + return; + } + if (number == 0 && val->max_value == 0) { + /* This was the only base name usage, remove whole key. */ + type_map->base_name_to_num_suffix.remove(key); + return; + } + val->mark_unused(number); +} diff --git a/source/blender/blenloader/intern/versioning_250.c b/source/blender/blenloader/intern/versioning_250.c index 67b6ad25d8b..ffa224ea9e0 100644 --- a/source/blender/blenloader/intern/versioning_250.c +++ b/source/blender/blenloader/intern/versioning_250.c @@ -425,14 +425,14 @@ static void do_versions_windowmanager_2_50(bScreen *screen) } } -static void versions_gpencil_add_main(ListBase *lb, ID *id, const char *name) +static void versions_gpencil_add_main(Main *bmain, ListBase *lb, ID *id, const char *name) { BLI_addtail(lb, id); id->us = 1; id->flag = LIB_FAKEUSER; *((short *)id->name) = ID_GD; - BKE_id_new_name_validate(lb, id, name, false); + BKE_id_new_name_validate(bmain, lb, id, name, false); /* alphabetic insertion: is in BKE_id_new_name_validate */ if ((id->tag & LIB_TAG_TEMP_MAIN) == 0) { @@ -455,21 +455,21 @@ static void do_versions_gpencil_2_50(Main *main, bScreen *screen) if (sl->spacetype == SPACE_VIEW3D) { View3D *v3d = (View3D *)sl; if (v3d->gpd) { - versions_gpencil_add_main(&main->gpencils, (ID *)v3d->gpd, "GPencil View3D"); + versions_gpencil_add_main(main, &main->gpencils, (ID *)v3d->gpd, "GPencil View3D"); v3d->gpd = NULL; } } else if (sl->spacetype == SPACE_NODE) { SpaceNode *snode = (SpaceNode *)sl; if (snode->gpd) { - versions_gpencil_add_main(&main->gpencils, (ID *)snode->gpd, "GPencil Node"); + versions_gpencil_add_main(main, &main->gpencils, (ID *)snode->gpd, "GPencil Node"); snode->gpd = NULL; } } else if (sl->spacetype == SPACE_SEQ) { SpaceSeq *sseq = (SpaceSeq *)sl; if (sseq->gpd) { - versions_gpencil_add_main(&main->gpencils, (ID *)sseq->gpd, "GPencil Node"); + versions_gpencil_add_main(main, &main->gpencils, (ID *)sseq->gpd, "GPencil Node"); sseq->gpd = NULL; } } @@ -477,7 +477,7 @@ static void do_versions_gpencil_2_50(Main *main, bScreen *screen) SpaceImage *sima = (SpaceImage *)sl; #if 0 /* see comment on r28002 */ if (sima->gpd) { - versions_gpencil_add_main(&main->gpencil, (ID *)sima->gpd, "GPencil Image"); + versions_gpencil_add_main(main, &main->gpencil, (ID *)sima->gpd, "GPencil Image"); sima->gpd = NULL; } #else diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c index 82e24801b66..cd2132ddae9 100644 --- a/source/blender/blenloader/intern/versioning_280.c +++ b/source/blender/blenloader/intern/versioning_280.c @@ -3549,7 +3549,7 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain) if (!MAIN_VERSION_ATLEAST(bmain, 280, 43)) { ListBase *lb = which_libbase(bmain, ID_BR); - BKE_main_id_repair_duplicate_names_listbase(lb); + BKE_main_id_repair_duplicate_names_listbase(bmain, lb); } if (!MAIN_VERSION_ATLEAST(bmain, 280, 44)) { diff --git a/source/blender/blenloader/intern/versioning_290.c b/source/blender/blenloader/intern/versioning_290.c index de652b40590..9ab744337a8 100644 --- a/source/blender/blenloader/intern/versioning_290.c +++ b/source/blender/blenloader/intern/versioning_290.c @@ -849,7 +849,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) short id_codes[] = {ID_BR, ID_PAL}; for (int i = 0; i < ARRAY_SIZE(id_codes); i++) { ListBase *lb = which_libbase(bmain, id_codes[i]); - BKE_main_id_repair_duplicate_names_listbase(lb); + BKE_main_id_repair_duplicate_names_listbase(bmain, lb); } } diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index 14204479849..bbbeba4d687 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -2017,7 +2017,7 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) /* Font names were copied directly into ID names, see: T90417. */ if (!MAIN_VERSION_ATLEAST(bmain, 300, 16)) { ListBase *lb = which_libbase(bmain, ID_VF); - BKE_main_id_repair_duplicate_names_listbase(lb); + BKE_main_id_repair_duplicate_names_listbase(bmain, lb); } if (!MAIN_VERSION_ATLEAST(bmain, 300, 17)) { diff --git a/source/blender/blenloader/intern/versioning_defaults.c b/source/blender/blenloader/intern/versioning_defaults.c index 6ce53e4a648..dfd98cb94f3 100644 --- a/source/blender/blenloader/intern/versioning_defaults.c +++ b/source/blender/blenloader/intern/versioning_defaults.c @@ -479,8 +479,10 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template) /* Default only has one window. */ if (layout->screen) { bScreen *screen = layout->screen; - BLI_strncpy(screen->id.name + 2, workspace->id.name + 2, sizeof(screen->id.name) - 2); - BLI_libblock_ensure_unique_name(bmain, screen->id.name); + if (!STREQ(screen->id.name + 2, workspace->id.name + 2)) { + BLI_strncpy(screen->id.name + 2, workspace->id.name + 2, sizeof(screen->id.name) - 2); + BLI_libblock_ensure_unique_name(bmain, screen->id.name); + } } /* For some reason we have unused screens, needed until re-saving. diff --git a/source/blender/editors/space_outliner/outliner_draw.cc b/source/blender/editors/space_outliner/outliner_draw.cc index 753de83a10d..6bab0b938e8 100644 --- a/source/blender/editors/space_outliner/outliner_draw.cc +++ b/source/blender/editors/space_outliner/outliner_draw.cc @@ -37,6 +37,7 @@ #include "BKE_lib_override.h" #include "BKE_library.h" #include "BKE_main.h" +#include "BKE_main_namemap.h" #include "BKE_modifier.h" #include "BKE_node.h" #include "BKE_object.h" @@ -677,6 +678,7 @@ static void namebutton_fn(bContext *C, void *tsep, char *oldname) TreeElement *te = outliner_find_tree_element(&space_outliner->tree, tselem); if (tselem->type == TSE_SOME_ID) { + BKE_main_namemap_remove_name(bmain, tselem->id, oldname); BLI_libblock_ensure_unique_name(bmain, tselem->id->name); WM_msg_publish_rna_prop(mbus, tselem->id, tselem->id, ID, name); @@ -742,6 +744,7 @@ static void namebutton_fn(bContext *C, void *tsep, char *oldname) } case TSE_NLA_ACTION: { bAction *act = (bAction *)tselem->id; + BKE_main_namemap_remove_name(bmain, &act->id, oldname); BLI_libblock_ensure_unique_name(bmain, act->id.name); WM_msg_publish_rna_prop(mbus, &act->id, &act->id, ID, name); break; @@ -852,6 +855,7 @@ static void namebutton_fn(bContext *C, void *tsep, char *oldname) case TSE_LAYER_COLLECTION: { /* The ID is a #Collection, not a #LayerCollection */ Collection *collection = (Collection *)tselem->id; + BKE_main_namemap_remove_name(bmain, &collection->id, oldname); BLI_libblock_ensure_unique_name(bmain, collection->id.name); WM_msg_publish_rna_prop(mbus, &collection->id, &collection->id, ID, name); WM_event_add_notifier(C, NC_ID | NA_RENAME, nullptr); diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index d3fc279381f..a77b7034241 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -22,6 +22,7 @@ struct GPUTexture; struct ID; struct Library; struct PackedFile; +struct UniqueName_Map; /* Runtime display data */ struct DrawData; @@ -444,6 +445,11 @@ typedef struct ID { struct ID_Runtime runtime; } ID; +typedef struct Library_Runtime { + /* Used for efficient calculations of unique names. */ + struct UniqueName_Map *name_map; +} Library_Runtime; + /** * For each library file used, a Library struct is added to Main * WARNING: readfile.c, expand_doit() reads this struct without DNA check! @@ -476,6 +482,8 @@ typedef struct Library { int temp_index; /** See BLENDER_FILE_VERSION, BLENDER_FILE_SUBVERSION, needed for do_versions. */ short versionfile, subversionfile; + + struct Library_Runtime runtime; } Library; /** #Library.tag */ diff --git a/source/blender/makesrna/intern/rna_ID.c b/source/blender/makesrna/intern/rna_ID.c index 4003df4d685..b4fa7088d38 100644 --- a/source/blender/makesrna/intern/rna_ID.c +++ b/source/blender/makesrna/intern/rna_ID.c @@ -16,6 +16,7 @@ #include "BKE_icons.h" #include "BKE_lib_id.h" +#include "BKE_main_namemap.h" #include "BKE_object.h" #include "RNA_access.h" @@ -273,6 +274,7 @@ int rna_ID_name_length(PointerRNA *ptr) void rna_ID_name_set(PointerRNA *ptr, const char *value) { ID *id = (ID *)ptr->data; + BKE_main_namemap_remove_name(G_MAIN, id, id->name + 2); BLI_strncpy_utf8(id->name + 2, value, sizeof(id->name) - 2); BLI_assert(BKE_id_is_in_global_main(id)); BLI_libblock_ensure_unique_name(G_MAIN, id->name); -- cgit v1.2.3 From a814c7091b9a22067517b24b1f9c2de366717cbd Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Wed, 20 Jul 2022 14:49:14 +0200 Subject: Fix T99847: Dragging image from Image Editor to Node Editor broken Oversight in b0da080c2cb9. The `session_uuid` operator property wouldn't be checked by the invoke callback, and if neither the `filepath` nor the `name` property were set, the File Browser would open. --- source/blender/editors/space_node/node_add.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/editors/space_node/node_add.cc b/source/blender/editors/space_node/node_add.cc index 04bf5ef469e..a89b5444a4d 100644 --- a/source/blender/editors/space_node/node_add.cc +++ b/source/blender/editors/space_node/node_add.cc @@ -690,8 +690,8 @@ static int node_add_file_invoke(bContext *C, wmOperator *op, const wmEvent *even snode->runtime->cursor[0] /= UI_DPI_FAC; snode->runtime->cursor[1] /= UI_DPI_FAC; - if (RNA_struct_property_is_set(op->ptr, "filepath") || - RNA_struct_property_is_set(op->ptr, "name")) { + if (WM_operator_properties_id_lookup_is_set(op->ptr) || + RNA_struct_property_is_set(op->ptr, "filepath")) { return node_add_file_exec(C, op); } return WM_operator_filesel(C, op, event); -- cgit v1.2.3 From 29c68e2523565125f8c258d84480555925fd093e Mon Sep 17 00:00:00 2001 From: Germano Cavalcante Date: Wed, 20 Jul 2022 10:28:14 -0300 Subject: Fix T99869: Edge crease no longer working Missed in d14c2d549b2fdde2a116f6a37837a1e3776da3cb --- source/blender/editors/transform/transform_convert_mesh_edge.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/editors/transform/transform_convert_mesh_edge.c b/source/blender/editors/transform/transform_convert_mesh_edge.c index 2d6c6a933d6..4a8ddb78587 100644 --- a/source/blender/editors/transform/transform_convert_mesh_edge.c +++ b/source/blender/editors/transform/transform_convert_mesh_edge.c @@ -99,8 +99,8 @@ void createTransEdge(TransInfo *t) td->ext = NULL; fl_ptr = BM_ELEM_CD_GET_VOID_P(eed, cd_edge_float_offset); - td->val = fl_ptr; - td->ival = *fl_ptr; + td->loc = fl_ptr; + td->iloc[0] = *fl_ptr; td++; } -- cgit v1.2.3 From 75611838300be7c01cf7020006e81f64ec75aaf2 Mon Sep 17 00:00:00 2001 From: Wannes Malfait Date: Wed, 20 Jul 2022 15:48:48 +0200 Subject: Fix T99667: regression in Delete Geometry node Differential Revision: https://developer.blender.org/D15445 --- source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc index 582bcbc3395..b74b4e45199 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc @@ -1072,7 +1072,7 @@ static void separate_mesh_selection(GeometrySet &geometry_set, evaluator.evaluate(); const VArray selection = evaluator.get_evaluated(0); /* Check if there is anything to delete. */ - if (selection.is_single() && selection.get_internal_single()) { + if (selection.is_empty() || (selection.is_single() && selection.get_internal_single())) { return; } -- cgit v1.2.3 From 3ea91cecc9396a44e8dd4e141e6be8f19ceac6a6 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 20 Jul 2022 15:57:16 +0200 Subject: Cleanup: remove unused get_cage_mesh parameter All callers passed `false` for this parameter, making it more confusing than useful. If this functionality is needed again in the future, a separate function should be added. Differential Revision: https://developer.blender.org/D15401 --- source/blender/blenkernel/BKE_modifier.h | 6 +----- source/blender/blenkernel/intern/data_transfer.c | 2 +- source/blender/blenkernel/intern/geometry_set_instances.cc | 4 ++-- source/blender/blenkernel/intern/gpencil_modifier.c | 2 +- source/blender/blenkernel/intern/modifier.c | 13 +++---------- source/blender/blenkernel/intern/shrinkwrap.c | 4 ++-- .../space_spreadsheet/spreadsheet_data_source_geometry.cc | 2 +- .../gpencil_modifiers/intern/MOD_gpencilshrinkwrap.c | 2 +- source/blender/modifiers/intern/MOD_array.c | 4 ++-- source/blender/modifiers/intern/MOD_boolean.cc | 10 ++++------ source/blender/modifiers/intern/MOD_mesh_to_volume.cc | 2 +- source/blender/modifiers/intern/MOD_meshdeform.c | 2 +- source/blender/modifiers/intern/MOD_surfacedeform.c | 2 +- source/blender/modifiers/intern/MOD_weightvgproximity.c | 2 +- .../geometry/nodes/node_geo_deform_curves_on_surface.cc | 3 +-- 15 files changed, 23 insertions(+), 37 deletions(-) diff --git a/source/blender/blenkernel/BKE_modifier.h b/source/blender/blenkernel/BKE_modifier.h index 46d609f9aa3..cda1fd01e44 100644 --- a/source/blender/blenkernel/BKE_modifier.h +++ b/source/blender/blenkernel/BKE_modifier.h @@ -598,12 +598,8 @@ void BKE_modifier_deform_vertsEM(ModifierData *md, * e.g. second operand for boolean modifier. * Note that modifiers in stack always get fully evaluated COW ID pointers, * never original ones. Makes things simpler. - * - * \param get_cage_mesh: Return evaluated mesh with only deforming modifiers applied - * (i.e. mesh topology remains the same as original one, a.k.a. 'cage' mesh). */ -struct Mesh *BKE_modifier_get_evaluated_mesh_from_evaluated_object(struct Object *ob_eval, - bool get_cage_mesh); +struct Mesh *BKE_modifier_get_evaluated_mesh_from_evaluated_object(struct Object *ob_eval); void BKE_modifier_check_uuids_unique_and_report(const struct Object *object); diff --git a/source/blender/blenkernel/intern/data_transfer.c b/source/blender/blenkernel/intern/data_transfer.c index 196a6a00ade..17a74b5564a 100644 --- a/source/blender/blenkernel/intern/data_transfer.c +++ b/source/blender/blenkernel/intern/data_transfer.c @@ -1410,7 +1410,7 @@ bool BKE_object_data_transfer_ex(struct Depsgraph *depsgraph, BKE_mesh_remap_calc_source_cddata_masks_from_map_modes( map_vert_mode, map_edge_mode, map_loop_mode, map_poly_mode, &me_src_mask); if (is_modifier) { - me_src = BKE_modifier_get_evaluated_mesh_from_evaluated_object(ob_src, false); + me_src = BKE_modifier_get_evaluated_mesh_from_evaluated_object(ob_src); if (me_src == NULL || !CustomData_MeshMasks_are_matching(&ob_src->runtime.last_data_mask, &me_src_mask)) { diff --git a/source/blender/blenkernel/intern/geometry_set_instances.cc b/source/blender/blenkernel/intern/geometry_set_instances.cc index fbd676e4bee..df48a99f706 100644 --- a/source/blender/blenkernel/intern/geometry_set_instances.cc +++ b/source/blender/blenkernel/intern/geometry_set_instances.cc @@ -27,8 +27,8 @@ static void geometry_set_collect_recursive_collection(const Collection &collecti static void add_final_mesh_as_geometry_component(const Object &object, GeometrySet &geometry_set) { - Mesh *mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(&const_cast(object), - false); + Mesh *mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object( + &const_cast(object)); if (mesh != nullptr) { BKE_mesh_wrapper_ensure_mdata(mesh); diff --git a/source/blender/blenkernel/intern/gpencil_modifier.c b/source/blender/blenkernel/intern/gpencil_modifier.c index 8739f2f7082..82899b974bc 100644 --- a/source/blender/blenkernel/intern/gpencil_modifier.c +++ b/source/blender/blenkernel/intern/gpencil_modifier.c @@ -95,7 +95,7 @@ void BKE_gpencil_cache_data_init(Depsgraph *depsgraph, Object *ob) MEM_SAFE_FREE(mmd->cache_data); } Object *ob_target = DEG_get_evaluated_object(depsgraph, ob); - Mesh *target = BKE_modifier_get_evaluated_mesh_from_evaluated_object(ob_target, false); + Mesh *target = BKE_modifier_get_evaluated_mesh_from_evaluated_object(ob_target); mmd->cache_data = MEM_callocN(sizeof(ShrinkwrapTreeData), __func__); if (BKE_shrinkwrap_init_tree( mmd->cache_data, target, mmd->shrink_type, mmd->shrink_mode, false)) { diff --git a/source/blender/blenkernel/intern/modifier.c b/source/blender/blenkernel/intern/modifier.c index 831ea084961..01eb4970f7e 100644 --- a/source/blender/blenkernel/intern/modifier.c +++ b/source/blender/blenkernel/intern/modifier.c @@ -1035,8 +1035,7 @@ void BKE_modifier_deform_vertsEM(ModifierData *md, /* end modifier callback wrappers */ -Mesh *BKE_modifier_get_evaluated_mesh_from_evaluated_object(Object *ob_eval, - const bool get_cage_mesh) +Mesh *BKE_modifier_get_evaluated_mesh_from_evaluated_object(Object *ob_eval) { Mesh *me = NULL; @@ -1045,17 +1044,11 @@ Mesh *BKE_modifier_get_evaluated_mesh_from_evaluated_object(Object *ob_eval, BMEditMesh *em = BKE_editmesh_from_object(ob_eval); /* 'em' might not exist yet in some cases, just after loading a .blend file, see T57878. */ if (em != NULL) { - Mesh *editmesh_eval_final = BKE_object_get_editmesh_eval_final(ob_eval); - Mesh *editmesh_eval_cage = BKE_object_get_editmesh_eval_cage(ob_eval); - - me = (get_cage_mesh && editmesh_eval_cage != NULL) ? editmesh_eval_cage : - editmesh_eval_final; + me = BKE_object_get_editmesh_eval_final(ob_eval); } } if (me == NULL) { - me = (get_cage_mesh && ob_eval->runtime.mesh_deform_eval != NULL) ? - ob_eval->runtime.mesh_deform_eval : - BKE_object_get_evaluated_mesh(ob_eval); + me = BKE_object_get_evaluated_mesh(ob_eval); } return me; diff --git a/source/blender/blenkernel/intern/shrinkwrap.c b/source/blender/blenkernel/intern/shrinkwrap.c index 7c7aa80402d..82cc250c6d1 100644 --- a/source/blender/blenkernel/intern/shrinkwrap.c +++ b/source/blender/blenkernel/intern/shrinkwrap.c @@ -666,7 +666,7 @@ static void shrinkwrap_calc_normal_projection(ShrinkwrapCalcData *calc) } if (calc->aux_target) { - auxMesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(calc->aux_target, false); + auxMesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(calc->aux_target); if (!auxMesh) { return; } @@ -1397,7 +1397,7 @@ void shrinkwrapModifier_deform(ShrinkwrapModifierData *smd, if (smd->target != NULL) { Object *ob_target = DEG_get_evaluated_object(ctx->depsgraph, smd->target); - calc.target = BKE_modifier_get_evaluated_mesh_from_evaluated_object(ob_target, false); + calc.target = BKE_modifier_get_evaluated_mesh_from_evaluated_object(ob_target); /* TODO: there might be several "bugs" with non-uniform scales matrices * because it will no longer be nearest surface, not sphere projection diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc index 2a87c51da5d..c7653e94b4d 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc @@ -412,7 +412,7 @@ GeometrySet spreadsheet_get_display_geometry_set(const SpaceSpreadsheet *sspread } else { if (object_eval->mode == OB_MODE_EDIT && object_eval->type == OB_MESH) { - Mesh *mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(object_eval, false); + Mesh *mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(object_eval); if (mesh == nullptr) { return geometry_set; } diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilshrinkwrap.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilshrinkwrap.c index 017287239ea..09c1005ecac 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilshrinkwrap.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilshrinkwrap.c @@ -144,7 +144,7 @@ static void bakeModifier(Main *UNUSED(bmain), MEM_SAFE_FREE(mmd->cache_data); } Object *ob_target = DEG_get_evaluated_object(depsgraph, mmd->target); - Mesh *target = BKE_modifier_get_evaluated_mesh_from_evaluated_object(ob_target, false); + Mesh *target = BKE_modifier_get_evaluated_mesh_from_evaluated_object(ob_target); mmd->cache_data = MEM_callocN(sizeof(ShrinkwrapTreeData), __func__); if (BKE_shrinkwrap_init_tree( mmd->cache_data, target, mmd->shrink_type, mmd->shrink_mode, false)) { diff --git a/source/blender/modifiers/intern/MOD_array.c b/source/blender/modifiers/intern/MOD_array.c index a7361f6d0b6..569b0fd0fda 100644 --- a/source/blender/modifiers/intern/MOD_array.c +++ b/source/blender/modifiers/intern/MOD_array.c @@ -402,7 +402,7 @@ static Mesh *arrayModifier_doArray(ArrayModifierData *amd, start_cap_ob, ctx->object, &vgroup_start_cap_remap_len); } - start_cap_mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(start_cap_ob, false); + start_cap_mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(start_cap_ob); if (start_cap_mesh) { start_cap_nverts = start_cap_mesh->totvert; start_cap_nedges = start_cap_mesh->totedge; @@ -417,7 +417,7 @@ static Mesh *arrayModifier_doArray(ArrayModifierData *amd, end_cap_ob, ctx->object, &vgroup_end_cap_remap_len); } - end_cap_mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(end_cap_ob, false); + end_cap_mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(end_cap_ob); if (end_cap_mesh) { end_cap_nverts = end_cap_mesh->totvert; end_cap_nedges = end_cap_mesh->totedge; diff --git a/source/blender/modifiers/intern/MOD_boolean.cc b/source/blender/modifiers/intern/MOD_boolean.cc index c9dc14b3b20..aa64c1f83bc 100644 --- a/source/blender/modifiers/intern/MOD_boolean.cc +++ b/source/blender/modifiers/intern/MOD_boolean.cc @@ -429,7 +429,7 @@ static Mesh *exact_boolean_mesh(BooleanModifierData *bmd, } if (bmd->flag & eBooleanModifierFlag_Object) { - Mesh *mesh_operand = BKE_modifier_get_evaluated_mesh_from_evaluated_object(bmd->object, false); + Mesh *mesh_operand = BKE_modifier_get_evaluated_mesh_from_evaluated_object(bmd->object); if (!mesh_operand) { return mesh; } @@ -444,7 +444,7 @@ static Mesh *exact_boolean_mesh(BooleanModifierData *bmd, if (collection) { FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (collection, ob) { if (ob->type == OB_MESH && ob != ctx->object) { - Mesh *collection_mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(ob, false); + Mesh *collection_mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(ob); if (!collection_mesh) { continue; } @@ -505,8 +505,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * Object *operand_ob = bmd->object; - Mesh *mesh_operand_ob = BKE_modifier_get_evaluated_mesh_from_evaluated_object(operand_ob, - false); + Mesh *mesh_operand_ob = BKE_modifier_get_evaluated_mesh_from_evaluated_object(operand_ob); if (mesh_operand_ob) { /* XXX This is utterly non-optimal, we may go from a bmesh to a mesh back to a bmesh! @@ -540,8 +539,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (collection, operand_ob) { if (operand_ob->type == OB_MESH && operand_ob != ctx->object) { - Mesh *mesh_operand_ob = BKE_modifier_get_evaluated_mesh_from_evaluated_object(operand_ob, - false); + Mesh *mesh_operand_ob = BKE_modifier_get_evaluated_mesh_from_evaluated_object(operand_ob); if (mesh_operand_ob == nullptr) { continue; diff --git a/source/blender/modifiers/intern/MOD_mesh_to_volume.cc b/source/blender/modifiers/intern/MOD_mesh_to_volume.cc index 7d4affa2dce..9ac410eb3de 100644 --- a/source/blender/modifiers/intern/MOD_mesh_to_volume.cc +++ b/source/blender/modifiers/intern/MOD_mesh_to_volume.cc @@ -128,7 +128,7 @@ static Volume *mesh_to_volume(ModifierData *md, if (object_to_convert == nullptr) { return input_volume; } - Mesh *mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(object_to_convert, false); + Mesh *mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(object_to_convert); if (mesh == nullptr) { return input_volume; } diff --git a/source/blender/modifiers/intern/MOD_meshdeform.c b/source/blender/modifiers/intern/MOD_meshdeform.c index 40aa0f84f92..334f5d75279 100644 --- a/source/blender/modifiers/intern/MOD_meshdeform.c +++ b/source/blender/modifiers/intern/MOD_meshdeform.c @@ -353,7 +353,7 @@ static void meshdeformModifier_do(ModifierData *md, * We'll support this case once granular dependency graph is landed. */ Object *ob_target = mmd->object; - cagemesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(ob_target, false); + cagemesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(ob_target); if (cagemesh == NULL) { BKE_modifier_set_error(ctx->object, md, "Cannot get mesh from cage object"); return; diff --git a/source/blender/modifiers/intern/MOD_surfacedeform.c b/source/blender/modifiers/intern/MOD_surfacedeform.c index edc6819a26c..ad19ecf5720 100644 --- a/source/blender/modifiers/intern/MOD_surfacedeform.c +++ b/source/blender/modifiers/intern/MOD_surfacedeform.c @@ -1449,7 +1449,7 @@ static void surfacedeformModifier_do(ModifierData *md, } Object *ob_target = smd->target; - target = BKE_modifier_get_evaluated_mesh_from_evaluated_object(ob_target, false); + target = BKE_modifier_get_evaluated_mesh_from_evaluated_object(ob_target); if (!target) { BKE_modifier_set_error(ob, md, "No valid target mesh"); return; diff --git a/source/blender/modifiers/intern/MOD_weightvgproximity.c b/source/blender/modifiers/intern/MOD_weightvgproximity.c index d798bf88bbc..b68d36366fd 100644 --- a/source/blender/modifiers/intern/MOD_weightvgproximity.c +++ b/source/blender/modifiers/intern/MOD_weightvgproximity.c @@ -544,7 +544,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * const bool use_trgt_faces = (wmd->proximity_flags & MOD_WVG_PROXIMITY_GEOM_FACES) != 0; if (use_trgt_verts || use_trgt_edges || use_trgt_faces) { - Mesh *target_mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(obr, false); + Mesh *target_mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(obr); /* We must check that we do have a valid target_mesh! */ if (target_mesh != NULL) { diff --git a/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc index 7f03d025db7..bd08abbd070 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc @@ -248,8 +248,7 @@ static void node_geo_exec(GeoNodeExecParams params) else { surface_mesh_orig = &surface_object_data; } - Mesh *surface_mesh_eval = BKE_modifier_get_evaluated_mesh_from_evaluated_object(surface_ob_eval, - false); + Mesh *surface_mesh_eval = BKE_modifier_get_evaluated_mesh_from_evaluated_object(surface_ob_eval); if (surface_mesh_eval == nullptr) { pass_through_input(); params.error_message_add(NodeWarningType::Error, "Surface has no mesh"); -- cgit v1.2.3 From 712960cefd671433b58da903e45000820d364fee Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 20 Jul 2022 16:08:02 +0200 Subject: Cleanup: use BLI_strncpy instead of strcpy Using `strcpy` resulted in `stringop-truncation` warnings for me. --- source/blender/blenkernel/intern/main_namemap.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/blender/blenkernel/intern/main_namemap.cc b/source/blender/blenkernel/intern/main_namemap.cc index 32f4c6be639..3cbd33057a8 100644 --- a/source/blender/blenkernel/intern/main_namemap.cc +++ b/source/blender/blenkernel/intern/main_namemap.cc @@ -207,7 +207,7 @@ static void main_namemap_populate(UniqueName_Map *name_map, struct Main *bmain, /* Insert the full name into the set. */ UniqueName_Key key; - strncpy(key.name, id->name + 2, MAX_NAME); + BLI_strncpy(key.name, id->name + 2, MAX_NAME); type_map->full_names.add(key); /* Get the name and number parts ("name.number"). */ @@ -255,7 +255,7 @@ bool BKE_main_namemap_get_name(struct Main *bmain, struct ID *id, char *name) UniqueName_Key key; while (true) { /* Check if the full original name has a duplicate. */ - strncpy(key.name, name, MAX_NAME); + BLI_strncpy(key.name, name, MAX_NAME); const bool has_dup = type_map->full_names.contains(key); /* Get the name and number parts ("name.number"). */ @@ -275,7 +275,7 @@ bool BKE_main_namemap_get_name(struct Main *bmain, struct ID *id, char *name) val.mark_used(number); if (!has_dup) { - strncpy(key.name, name, MAX_NAME); + BLI_strncpy(key.name, name, MAX_NAME); type_map->full_names.add(key); } return is_name_changed; @@ -310,7 +310,7 @@ bool BKE_main_namemap_get_name(struct Main *bmain, struct ID *id, char *name) BLI_assert(number_to_use >= MIN_NUMBER); if (id_name_final_build(name, key.name, base_name_len, number_to_use)) { /* All good, add final name to the set. */ - strncpy(key.name, name, MAX_NAME); + BLI_strncpy(key.name, name, MAX_NAME); type_map->full_names.add(key); break; } @@ -343,7 +343,7 @@ void BKE_main_namemap_remove_name(struct Main *bmain, struct ID *id, const char UniqueName_Key key; /* Remove full name from the set. */ - strncpy(key.name, name, MAX_NAME); + BLI_strncpy(key.name, name, MAX_NAME); type_map->full_names.remove(key); int number = MIN_NUMBER; -- cgit v1.2.3 From 0c6ae51d9f043c0914d68d36b221d91285a2da11 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Wed, 20 Jul 2022 16:44:15 +0200 Subject: Fix missing registration of grid view items in the view --- source/blender/editors/interface/views/grid_view.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/source/blender/editors/interface/views/grid_view.cc b/source/blender/editors/interface/views/grid_view.cc index 37fbb33f83b..54ed3fe1631 100644 --- a/source/blender/editors/interface/views/grid_view.cc +++ b/source/blender/editors/interface/views/grid_view.cc @@ -32,6 +32,7 @@ AbstractGridViewItem &AbstractGridView::add_item(std::unique_ptr Date: Wed, 20 Jul 2022 17:13:15 +0200 Subject: UI: Remove redundant view reference in view items The new view item base class already holds a reference to the view, no need to have one in the derived class as well. --- source/blender/editors/include/UI_grid_view.hh | 2 -- source/blender/editors/interface/views/grid_view.cc | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/source/blender/editors/include/UI_grid_view.hh b/source/blender/editors/include/UI_grid_view.hh index 805198f38ef..402c0c8512f 100644 --- a/source/blender/editors/include/UI_grid_view.hh +++ b/source/blender/editors/include/UI_grid_view.hh @@ -36,8 +36,6 @@ class AbstractGridViewItem : public AbstractViewItem { friend class AbstractGridView; friend class GridViewLayoutBuilder; - const AbstractGridView *view_; - protected: /** Reference to a string that uniquely identifies this item in the view. */ StringRef identifier_{}; diff --git a/source/blender/editors/interface/views/grid_view.cc b/source/blender/editors/interface/views/grid_view.cc index 54ed3fe1631..52ff1460cbd 100644 --- a/source/blender/editors/interface/views/grid_view.cc +++ b/source/blender/editors/interface/views/grid_view.cc @@ -29,8 +29,6 @@ AbstractGridViewItem &AbstractGridView::add_item(std::unique_ptr(*view_); } /* ---------------------------------------------------------------------- */ -- cgit v1.2.3 From 5d4574ea0e7a010c130f1728408f198886ea5c44 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 20 Jul 2022 18:19:37 +0200 Subject: Fix T99340: Image.frame_duration returning wrong value when image not loaded The logic here was broken in d5f1b9c, it should load the image first. --- source/blender/makesrna/intern/rna_image.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/source/blender/makesrna/intern/rna_image.c b/source/blender/makesrna/intern/rna_image.c index 39f5b6e0e9f..7f134c5055f 100644 --- a/source/blender/makesrna/intern/rna_image.c +++ b/source/blender/makesrna/intern/rna_image.c @@ -475,18 +475,19 @@ static int rna_Image_frame_duration_get(PointerRNA *ptr) Image *ima = (Image *)ptr->owner_id; int duration = 1; + if (!BKE_image_has_anim(ima)) { + /* Ensure image has been loaded into memory and frame duration is known. */ + void *lock; + ImBuf *ibuf = BKE_image_acquire_ibuf(ima, NULL, &lock); + BKE_image_release_ibuf(ima, ibuf, lock); + } + if (BKE_image_has_anim(ima)) { struct anim *anim = ((ImageAnim *)ima->anims.first)->anim; if (anim) { duration = IMB_anim_get_duration(anim, IMB_TC_RECORD_RUN); } } - else { - /* acquire ensures ima->anim is set, if possible! */ - void *lock; - ImBuf *ibuf = BKE_image_acquire_ibuf(ima, NULL, &lock); - BKE_image_release_ibuf(ima, ibuf, lock); - } return duration; } -- cgit v1.2.3 From d34f8ac3d9a4c3402fd620253d70bbf71e09abd2 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 20 Jul 2022 13:17:49 -0500 Subject: Cleanup: Remove unnecessary handling of normals for fluid colliders The normals are transformed, but not used. It looks like this logic was just copied from below where the mesh is transformed for creating emitters, which do use vertex normals. --- source/blender/blenkernel/intern/fluid.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/source/blender/blenkernel/intern/fluid.c b/source/blender/blenkernel/intern/fluid.c index 8e95bf18c6b..0fc09803088 100644 --- a/source/blender/blenkernel/intern/fluid.c +++ b/source/blender/blenkernel/intern/fluid.c @@ -1038,8 +1038,6 @@ static void obstacles_from_mesh(Object *coll_ob, /* Transform mesh vertices to domain grid space for fast lookups. * This is valid because the mesh is copied above. */ - BKE_mesh_vertex_normals_ensure(me); - float(*vert_normals)[3] = BKE_mesh_vertex_normals_for_write(me); for (i = 0; i < numverts; i++) { float co[3]; @@ -1047,11 +1045,6 @@ static void obstacles_from_mesh(Object *coll_ob, mul_m4_v3(coll_ob->obmat, mvert[i].co); manta_pos_to_cell(fds, mvert[i].co); - /* Vertex normal. */ - mul_mat3_m4_v3(coll_ob->obmat, vert_normals[i]); - mul_mat3_m4_v3(fds->imat, vert_normals[i]); - normalize_v3(vert_normals[i]); - /* Vertex velocity. */ add_v3fl_v3fl_v3i(co, mvert[i].co, fds->shift); if (has_velocity) { -- cgit v1.2.3 From fe108d85b4d7f5ac0261f0b95b9c799beb982d9f Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 20 Jul 2022 14:30:44 -0500 Subject: Cleanup: Remove unused function --- source/blender/draw/intern/DRW_render.h | 1 - source/blender/draw/intern/draw_manager.c | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/source/blender/draw/intern/DRW_render.h b/source/blender/draw/intern/DRW_render.h index bb913c53500..c2d26badc4c 100644 --- a/source/blender/draw/intern/DRW_render.h +++ b/source/blender/draw/intern/DRW_render.h @@ -887,7 +887,6 @@ bool DRW_object_is_in_edit_mode(const struct Object *ob); * we are rendering or drawing in the viewport. */ int DRW_object_visibility_in_active_context(const struct Object *ob); -bool DRW_object_is_flat_normal(const struct Object *ob); bool DRW_object_use_hide_faces(const struct Object *ob); bool DRW_object_is_visible_psys_in_active_context(const struct Object *object, diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c index 5d21ab75650..89ae1c73a76 100644 --- a/source/blender/draw/intern/draw_manager.c +++ b/source/blender/draw/intern/draw_manager.c @@ -212,16 +212,6 @@ int DRW_object_visibility_in_active_context(const Object *ob) return BKE_object_visibility(ob, mode); } -bool DRW_object_is_flat_normal(const Object *ob) -{ - if (ob->type == OB_MESH) { - const Mesh *me = ob->data; - if (me->mpoly && me->mpoly[0].flag & ME_SMOOTH) { - return false; - } - } - return true; -} bool DRW_object_use_hide_faces(const struct Object *ob) { -- cgit v1.2.3 From eb281e4b245f69fcf343cd11966fab0107e65cae Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 20 Jul 2022 16:40:05 -0500 Subject: Fix T99878: Deleting curves or points removes anonymous attributes Use the attribute API instead of the CustomData API, to correctly handle anonymous attributes and simplify the code. One non-obvious thing to note is that the type counts are recalculated by the "finish" function of the `curve_type` attribute, so they don't need to be copied explicitly. Also, the mutable attribute accessor cannot be an reference if we want to give it an rvalue, which is convenient in this case. --- source/blender/blenkernel/BKE_attribute.hh | 4 +- .../blender/blenkernel/intern/attribute_access.cc | 4 +- .../blender/blenkernel/intern/curves_geometry.cc | 136 +++++++-------------- 3 files changed, 46 insertions(+), 98 deletions(-) diff --git a/source/blender/blenkernel/BKE_attribute.hh b/source/blender/blenkernel/BKE_attribute.hh index 108993d91c0..835fd58026f 100644 --- a/source/blender/blenkernel/BKE_attribute.hh +++ b/source/blender/blenkernel/BKE_attribute.hh @@ -677,8 +677,8 @@ struct AttributeTransferData { * data-blocks of the same type. */ Vector retrieve_attributes_for_transfer( - const bke::AttributeAccessor &src_attributes, - bke::MutableAttributeAccessor &dst_attributes, + const bke::AttributeAccessor src_attributes, + bke::MutableAttributeAccessor dst_attributes, eAttrDomainMask domain_mask, const Set &skip = {}); diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index a834b77d65e..b2ac15a71d7 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -1012,8 +1012,8 @@ GSpanAttributeWriter MutableAttributeAccessor::lookup_or_add_for_write_only_span } Vector retrieve_attributes_for_transfer( - const bke::AttributeAccessor &src_attributes, - bke::MutableAttributeAccessor &dst_attributes, + const bke::AttributeAccessor src_attributes, + bke::MutableAttributeAccessor dst_attributes, const eAttrDomainMask domain_mask, const Set &skip) { diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc index 7fc660cfbfc..f5c040a6fee 100644 --- a/source/blender/blenkernel/intern/curves_geometry.cc +++ b/source/blender/blenkernel/intern/curves_geometry.cc @@ -1070,21 +1070,6 @@ bool CurvesGeometry::bounds_min_max(float3 &min, float3 &max) const return true; } -static void *ensure_customdata_layer(CustomData &custom_data, - const StringRefNull name, - const eCustomDataType data_type, - const int tot_elements) -{ - for (const int other_layer_i : IndexRange(custom_data.totlayer)) { - CustomDataLayer &new_layer = custom_data.layers[other_layer_i]; - if (name == StringRef(new_layer.name)) { - return new_layer.data; - } - } - return CustomData_add_layer_named( - &custom_data, data_type, CD_DEFAULT, nullptr, tot_elements, name.c_str()); -} - static void copy_between_buffers(const CPPType &type, const void *src_buffer, void *dst_buffer, @@ -1180,56 +1165,37 @@ static CurvesGeometry copy_with_removed_points(const CurvesGeometry &curves, [&]() { new_curves.offsets_for_write().copy_from(new_curve_offsets); }, /* Copy over point attributes. */ [&]() { - const CustomData &old_point_data = curves.point_data; - CustomData &new_point_data = new_curves.point_data; - for (const int layer_i : IndexRange(old_point_data.totlayer)) { - const CustomDataLayer &old_layer = old_point_data.layers[layer_i]; - const eCustomDataType data_type = static_cast(old_layer.type); - const CPPType &type = *bke::custom_data_type_to_cpp_type(data_type); - - void *dst_buffer = ensure_customdata_layer( - new_point_data, old_layer.name, data_type, new_point_count); - - threading::parallel_for( - copy_point_ranges.index_range(), 128, [&](const IndexRange ranges_range) { - for (const int range_i : ranges_range) { - const IndexRange src_range = copy_point_ranges[range_i]; - copy_between_buffers(type, - old_layer.data, - dst_buffer, - src_range, - {copy_point_range_dst_offsets[range_i], src_range.size()}); - } - }); + for (auto &attribute : bke::retrieve_attributes_for_transfer( + curves.attributes(), new_curves.attributes_for_write(), ATTR_DOMAIN_MASK_POINT)) { + threading::parallel_for(copy_point_ranges.index_range(), 128, [&](IndexRange range) { + for (const int range_i : range) { + const IndexRange src_range = copy_point_ranges[range_i]; + copy_between_buffers(attribute.src.type(), + attribute.src.data(), + attribute.dst.span.data(), + src_range, + {copy_point_range_dst_offsets[range_i], src_range.size()}); + } + }); + attribute.dst.finish(); } }, /* Copy over curve attributes. * In some cases points are just dissolved, so the the number of * curves will be the same. That could be optimized in the future. */ [&]() { - const CustomData &old_curve_data = curves.curve_data; - CustomData &new_curve_data = new_curves.curve_data; - for (const int layer_i : IndexRange(old_curve_data.totlayer)) { - const CustomDataLayer &old_layer = old_curve_data.layers[layer_i]; - const eCustomDataType data_type = static_cast(old_layer.type); - const CPPType &type = *bke::custom_data_type_to_cpp_type(data_type); - - void *dst_buffer = ensure_customdata_layer( - new_curve_data, old_layer.name, data_type, new_curve_count); - + for (auto &attribute : bke::retrieve_attributes_for_transfer( + curves.attributes(), new_curves.attributes_for_write(), ATTR_DOMAIN_MASK_CURVE)) { if (new_curves.curves_num() == curves.curves_num()) { - type.copy_construct_n(old_layer.data, dst_buffer, new_curves.curves_num()); + attribute.dst.span.copy_from(attribute.src); } else { - copy_with_map({type, old_layer.data, curves.curves_num()}, - new_curve_orig_indices, - {type, dst_buffer, new_curves.curves_num()}); + copy_with_map(attribute.src, new_curve_orig_indices, attribute.dst.span); } + attribute.dst.finish(); } }); - new_curves.update_curve_types(); - return new_curves; } @@ -1296,55 +1262,37 @@ static CurvesGeometry copy_with_removed_curves(const CurvesGeometry &curves, }, /* Copy over point attributes. */ [&]() { - const CustomData &old_point_data = curves.point_data; - CustomData &new_point_data = new_curves.point_data; - for (const int layer_i : IndexRange(old_point_data.totlayer)) { - const CustomDataLayer &old_layer = old_point_data.layers[layer_i]; - const eCustomDataType data_type = static_cast(old_layer.type); - const CPPType &type = *bke::custom_data_type_to_cpp_type(data_type); - - void *dst_buffer = ensure_customdata_layer( - new_point_data, old_layer.name, data_type, new_tot_points); - - threading::parallel_for( - old_curve_ranges.index_range(), 128, [&](const IndexRange ranges_range) { - for (const int range_i : ranges_range) { - copy_between_buffers(type, - old_layer.data, - dst_buffer, - old_point_ranges[range_i], - new_point_ranges[range_i]); - } - }); + for (auto &attribute : bke::retrieve_attributes_for_transfer( + curves.attributes(), new_curves.attributes_for_write(), ATTR_DOMAIN_MASK_POINT)) { + threading::parallel_for(old_curve_ranges.index_range(), 128, [&](IndexRange range) { + for (const int range_i : range) { + copy_between_buffers(attribute.src.type(), + attribute.src.data(), + attribute.dst.span.data(), + old_point_ranges[range_i], + new_point_ranges[range_i]); + } + }); + attribute.dst.finish(); } }, /* Copy over curve attributes. */ [&]() { - const CustomData &old_curve_data = curves.curve_data; - CustomData &new_curve_data = new_curves.curve_data; - for (const int layer_i : IndexRange(old_curve_data.totlayer)) { - const CustomDataLayer &old_layer = old_curve_data.layers[layer_i]; - const eCustomDataType data_type = static_cast(old_layer.type); - const CPPType &type = *bke::custom_data_type_to_cpp_type(data_type); - - void *dst_buffer = ensure_customdata_layer( - new_curve_data, old_layer.name, data_type, new_tot_curves); - - threading::parallel_for( - old_curve_ranges.index_range(), 128, [&](const IndexRange ranges_range) { - for (const int range_i : ranges_range) { - copy_between_buffers(type, - old_layer.data, - dst_buffer, - old_curve_ranges[range_i], - new_curve_ranges[range_i]); - } - }); + for (auto &attribute : bke::retrieve_attributes_for_transfer( + curves.attributes(), new_curves.attributes_for_write(), ATTR_DOMAIN_MASK_CURVE)) { + threading::parallel_for(old_curve_ranges.index_range(), 128, [&](IndexRange range) { + for (const int range_i : range) { + copy_between_buffers(attribute.src.type(), + attribute.src.data(), + attribute.dst.span.data(), + old_curve_ranges[range_i], + new_curve_ranges[range_i]); + } + }); + attribute.dst.finish(); } }); - new_curves.update_curve_types(); - return new_curves; } -- cgit v1.2.3 From 9f6836924739049384116d5b8054811b08bb0d97 Mon Sep 17 00:00:00 2001 From: Joseph Eagar Date: Wed, 20 Jul 2022 15:15:54 -0700 Subject: Fix T99687: Cloth filter crash The code was failing to exclude the sculpt object from the list of collision objects. --- source/blender/editors/sculpt_paint/sculpt.c | 2 +- source/blender/editors/sculpt_paint/sculpt_cloth.c | 15 ++++++++++----- source/blender/editors/sculpt_paint/sculpt_intern.h | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index 2951706ebd8..7cb370efb94 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -3362,7 +3362,7 @@ static void do_brush_action(Sculpt *sd, if (brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM) { if (!ss->cache->cloth_sim) { ss->cache->cloth_sim = SCULPT_cloth_brush_simulation_create( - ss, 1.0f, 0.0f, 0.0f, false, true); + ob, 1.0f, 0.0f, 0.0f, false, true); SCULPT_cloth_brush_simulation_init(ss, ss->cache->cloth_sim); } SCULPT_cloth_brush_store_simulation_state(ss, ss->cache->cloth_sim); diff --git a/source/blender/editors/sculpt_paint/sculpt_cloth.c b/source/blender/editors/sculpt_paint/sculpt_cloth.c index 9d231f2ccd2..bf3e8a24fdf 100644 --- a/source/blender/editors/sculpt_paint/sculpt_cloth.c +++ b/source/blender/editors/sculpt_paint/sculpt_cloth.c @@ -611,13 +611,17 @@ static void do_cloth_brush_apply_forces_task_cb_ex(void *__restrict userdata, BKE_pbvh_vertex_iter_end; } -static ListBase *cloth_brush_collider_cache_create(Depsgraph *depsgraph) +static ListBase *cloth_brush_collider_cache_create(Object *object, Depsgraph *depsgraph) { ListBase *cache = NULL; DEG_OBJECT_ITER_BEGIN (depsgraph, ob, DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY | DEG_ITER_OBJECT_FLAG_VISIBLE | DEG_ITER_OBJECT_FLAG_DUPLI) { + if (STREQ(object->id.name, ob->id.name)) { + continue; + } + CollisionModifierData *cmd = (CollisionModifierData *)BKE_modifiers_findby_type( ob, eModifierType_Collision); if (!cmd) { @@ -1029,13 +1033,14 @@ static void cloth_sim_initialize_default_node_state(SculptSession *ss, MEM_SAFE_FREE(nodes); } -SculptClothSimulation *SCULPT_cloth_brush_simulation_create(SculptSession *ss, +SculptClothSimulation *SCULPT_cloth_brush_simulation_create(Object *ob, const float cloth_mass, const float cloth_damping, const float cloth_softbody_strength, const bool use_collisions, const bool needs_deform_coords) { + SculptSession *ss = ob->sculpt; const int totverts = SCULPT_vertex_count_get(ss); SculptClothSimulation *cloth_sim; @@ -1073,7 +1078,7 @@ SculptClothSimulation *SCULPT_cloth_brush_simulation_create(SculptSession *ss, cloth_sim->softbody_strength = cloth_softbody_strength; if (use_collisions) { - cloth_sim->collider_list = cloth_brush_collider_cache_create(ss->depsgraph); + cloth_sim->collider_list = cloth_brush_collider_cache_create(ob, ss->depsgraph); } cloth_sim_initialize_default_node_state(ss, cloth_sim); @@ -1184,7 +1189,7 @@ void SCULPT_do_cloth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode /* The simulation structure only needs to be created on the first symmetry pass. */ if (SCULPT_stroke_is_first_brush_step(ss->cache) || !ss->cache->cloth_sim) { ss->cache->cloth_sim = SCULPT_cloth_brush_simulation_create( - ss, + ob, brush->cloth_mass, brush->cloth_damping, brush->cloth_constraint_softbody_strength, @@ -1571,7 +1576,7 @@ static int sculpt_cloth_filter_invoke(bContext *C, wmOperator *op, const wmEvent const float cloth_damping = RNA_float_get(op->ptr, "cloth_damping"); const bool use_collisions = RNA_boolean_get(op->ptr, "use_collisions"); ss->filter_cache->cloth_sim = SCULPT_cloth_brush_simulation_create( - ss, + ob, cloth_mass, cloth_damping, 0.0f, diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index 83526006d7d..179e07cc7fa 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h @@ -1344,7 +1344,7 @@ void SCULPT_cloth_simulation_free(struct SculptClothSimulation *cloth_sim); /* Public functions. */ -struct SculptClothSimulation *SCULPT_cloth_brush_simulation_create(struct SculptSession *ss, +struct SculptClothSimulation *SCULPT_cloth_brush_simulation_create(struct Object *ob, float cloth_mass, float cloth_damping, float cloth_softbody_strength, -- cgit v1.2.3 From e75adb979b0acefbf5f01b83f4139d6a43163b92 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 21 Jul 2022 12:52:24 +1000 Subject: Fix T99678: Crash applying non-existent modifiers Regression in [0] accessed the modifier type before NULL check. [0]: 78fc5ea1c398f70d22cda72be33c105146c0d542 --- source/blender/editors/object/object_modifier.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/blender/editors/object/object_modifier.cc b/source/blender/editors/object/object_modifier.cc index 69edd00ae24..a3a41a5c5b1 100644 --- a/source/blender/editors/object/object_modifier.cc +++ b/source/blender/editors/object/object_modifier.cc @@ -1439,7 +1439,6 @@ static int modifier_apply_exec_ex(bContext *C, wmOperator *op, int apply_as, boo Scene *scene = CTX_data_scene(C); Object *ob = ED_object_active_context(C); ModifierData *md = edit_modifier_property_get(op, ob, 0); - const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type); const bool do_report = RNA_boolean_get(op->ptr, "report"); const bool do_single_user = RNA_boolean_get(op->ptr, "single_user"); const bool do_merge_customdata = RNA_boolean_get(op->ptr, "merge_customdata"); @@ -1448,6 +1447,8 @@ static int modifier_apply_exec_ex(bContext *C, wmOperator *op, int apply_as, boo return OPERATOR_CANCELLED; } + const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type); + if (do_single_user && ID_REAL_USERS(ob->data) > 1) { ED_object_single_obdata_user(bmain, scene, ob); BKE_main_id_newptr_and_tag_clear(bmain); -- cgit v1.2.3 From 46a2592eef90782bea6124767c072f275330bd00 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 21 Jul 2022 13:21:53 +1000 Subject: Cleanup: spelling in comments, typos in tool-tips --- intern/cycles/kernel/bvh/util.h | 2 +- intern/cycles/kernel/data_template.h | 2 +- intern/cycles/kernel/types.h | 2 +- source/blender/editors/interface/eyedroppers/eyedropper_driver.c | 4 ++-- source/blender/editors/sculpt_paint/paint_stroke.c | 2 +- source/blender/gpu/GPU_context.h | 4 ++-- source/blender/gpu/metal/mtl_memory.mm | 2 +- source/blender/gpu/metal/mtl_query.mm | 4 ++-- source/blender/gpu/metal/mtl_uniform_buffer.hh | 2 +- source/blender/gpu/metal/mtl_uniform_buffer.mm | 2 +- source/blender/makesrna/intern/rna_sequencer.c | 4 ++-- 11 files changed, 15 insertions(+), 15 deletions(-) diff --git a/intern/cycles/kernel/bvh/util.h b/intern/cycles/kernel/bvh/util.h index 1795ae4c790..385e904d20f 100644 --- a/intern/cycles/kernel/bvh/util.h +++ b/intern/cycles/kernel/bvh/util.h @@ -11,7 +11,7 @@ CCL_NAMESPACE_BEGIN * intersection we'll be comparing against the exact same distances. */ ccl_device_forceinline float intersection_t_offset(const float t) { - /* This is a simplified version of nextafterf(t, FLT_MAX), only dealing with + /* This is a simplified version of `nextafterf(t, FLT_MAX)`, only dealing with * non-negative and finite t. */ kernel_assert(t >= 0.0f && isfinite_safe(t)); const uint32_t bits = (t == 0.0f) ? 1 : __float_as_uint(t) + 1; diff --git a/intern/cycles/kernel/data_template.h b/intern/cycles/kernel/data_template.h index b06ac62a5d8..807d0650fc3 100644 --- a/intern/cycles/kernel/data_template.h +++ b/intern/cycles/kernel/data_template.h @@ -70,7 +70,7 @@ KERNEL_STRUCT_MEMBER(film, float4, rec709_to_r) KERNEL_STRUCT_MEMBER(film, float4, rec709_to_g) KERNEL_STRUCT_MEMBER(film, float4, rec709_to_b) KERNEL_STRUCT_MEMBER(film, int, is_rec709) -/* Exposuse. */ +/* Exposure. */ KERNEL_STRUCT_MEMBER(film, float, exposure) /* Passed used. */ KERNEL_STRUCT_MEMBER(film, int, pass_flag) diff --git a/intern/cycles/kernel/types.h b/intern/cycles/kernel/types.h index 05320deed19..4f4b811a8e7 100644 --- a/intern/cycles/kernel/types.h +++ b/intern/cycles/kernel/types.h @@ -1168,7 +1168,7 @@ typedef struct KernelData { uint max_shaders; uint volume_stack_size; - /* Always dynamic data mambers. */ + /* Always dynamic data members. */ KernelCamera cam; KernelBake bake; KernelTables tables; diff --git a/source/blender/editors/interface/eyedroppers/eyedropper_driver.c b/source/blender/editors/interface/eyedroppers/eyedropper_driver.c index 14c00c21a5c..a9314df44a5 100644 --- a/source/blender/editors/interface/eyedroppers/eyedropper_driver.c +++ b/source/blender/editors/interface/eyedroppers/eyedropper_driver.c @@ -83,14 +83,14 @@ static void driverdropper_sample(bContext *C, wmOperator *op, const wmEvent *eve if (but == NULL) { return; } - /* Get paths for src... */ + /* Get paths for the source. */ PointerRNA *target_ptr = &but->rnapoin; PropertyRNA *target_prop = but->rnaprop; const int target_index = but->rnaindex; char *target_path = RNA_path_from_ID_to_property(target_ptr, target_prop); - /* ... and destination */ + /* Get paths for the destination. */ char *dst_path = RNA_path_from_ID_to_property(&ddr->ptr, ddr->prop); /* Now create driver(s) */ diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c index 88e7a786a47..736942b9f72 100644 --- a/source/blender/editors/sculpt_paint/paint_stroke.c +++ b/source/blender/editors/sculpt_paint/paint_stroke.c @@ -123,7 +123,7 @@ typedef struct PaintStroke { StrokeRedraw redraw; StrokeDone done; - bool original; /* Raycast original mesh at start of stroke */ + bool original; /* Ray-cast original mesh at start of stroke. */ } PaintStroke; /*** Cursors ***/ diff --git a/source/blender/gpu/GPU_context.h b/source/blender/gpu/GPU_context.h index 9d92ea2cad9..a242bb7cc94 100644 --- a/source/blender/gpu/GPU_context.h +++ b/source/blender/gpu/GPU_context.h @@ -17,8 +17,8 @@ extern "C" { #endif -/* GPU backends abstract the differences between different APIs. GPU_context_create - * automatically initializes the backend, and GPU_context_discard frees it when there +/* GPU back-ends abstract the differences between different APIs. #GPU_context_create + * automatically initializes the back-end, and #GPU_context_discard frees it when there * are no more contexts. */ bool GPU_backend_supported(void); eGPUBackendType GPU_backend_get_type(void); diff --git a/source/blender/gpu/metal/mtl_memory.mm b/source/blender/gpu/metal/mtl_memory.mm index 48e27dd2bb6..07da489bdbb 100644 --- a/source/blender/gpu/metal/mtl_memory.mm +++ b/source/blender/gpu/metal/mtl_memory.mm @@ -68,7 +68,7 @@ gpu::MTLBuffer *MTLBufferPool::allocate_with_data(uint64_t size, bool cpu_visible, const void *data) { - /* Allocate buffer with default HW-compatible alignemnt of 256 bytes. + /* Allocate buffer with default HW-compatible alignment of 256 bytes. * See https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf for more. */ return this->allocate_aligned_with_data(size, 256, cpu_visible, data); } diff --git a/source/blender/gpu/metal/mtl_query.mm b/source/blender/gpu/metal/mtl_query.mm index 33ae8f554c3..dfda0a8de7f 100644 --- a/source/blender/gpu/metal/mtl_query.mm +++ b/source/blender/gpu/metal/mtl_query.mm @@ -104,8 +104,8 @@ void MTLQueryPool::get_occlusion_result(MutableSpan r_values) BLI_assert(ctx->get_inside_frame()); } - /* Wait for GPU operatiosn to complete and for query buffer contents - * to be synchronised back to host memory. */ + /* Wait for GPU operations to complete and for query buffer contents + * to be synchronized back to host memory. */ GPU_finish(); /* Iterate through all possible visibility buffers and copy results into provided diff --git a/source/blender/gpu/metal/mtl_uniform_buffer.hh b/source/blender/gpu/metal/mtl_uniform_buffer.hh index 722d819cf96..789a85f0a92 100644 --- a/source/blender/gpu/metal/mtl_uniform_buffer.hh +++ b/source/blender/gpu/metal/mtl_uniform_buffer.hh @@ -25,7 +25,7 @@ class MTLUniformBuf : public UniformBuf { * have yet been allocated. */ bool has_data_ = false; - /* Bindstate tracking. */ + /* Bind-state tracking. */ int bind_slot_ = -1; MTLContext *bound_ctx_ = nullptr; diff --git a/source/blender/gpu/metal/mtl_uniform_buffer.mm b/source/blender/gpu/metal/mtl_uniform_buffer.mm index 3415c41f2cc..4893014dedf 100644 --- a/source/blender/gpu/metal/mtl_uniform_buffer.mm +++ b/source/blender/gpu/metal/mtl_uniform_buffer.mm @@ -107,7 +107,7 @@ void MTLUniformBuf::bind(int slot) MEM_SAFE_FREE(data_); } - /* Ensure there is atleast an empty dummy buffer. */ + /* Ensure there is at least an empty dummy buffer. */ if (metal_buffer_ == nullptr) { this->update(nullptr); } diff --git a/source/blender/makesrna/intern/rna_sequencer.c b/source/blender/makesrna/intern/rna_sequencer.c index 04037a64426..3bcd9cd0441 100644 --- a/source/blender/makesrna/intern/rna_sequencer.c +++ b/source/blender/makesrna/intern/rna_sequencer.c @@ -1754,12 +1754,12 @@ static void rna_def_color_balance(BlenderRNA *brna) prop = RNA_def_property(srna, "invert_gain", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_COLOR_BALANCE_INVERSE_GAIN); - RNA_def_property_ui_text(prop, "Inverse Gain", "Invert the gain color`"); + RNA_def_property_ui_text(prop, "Inverse Gain", "Invert the gain color"); RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); prop = RNA_def_property(srna, "invert_slope", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_COLOR_BALANCE_INVERSE_SLOPE); - RNA_def_property_ui_text(prop, "Inverse Slope", "Invert the slope color`"); + RNA_def_property_ui_text(prop, "Inverse Slope", "Invert the slope color"); RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); prop = RNA_def_property(srna, "invert_offset", PROP_BOOLEAN, PROP_NONE); -- cgit v1.2.3 From c171e8b95c0035eb92d2dcec6e9d82e094954d84 Mon Sep 17 00:00:00 2001 From: Chris Blackbourn Date: Thu, 21 Jul 2022 15:15:38 +1200 Subject: Fix T90620: Ignore missing UV data caused by corrupt .blend file Add crash protection and partial recovery for corrupt .blend files, particularly for missing UV data. Differential Revision: https://developer.blender.org/D15489 --- source/blender/blenkernel/intern/customdata.cc | 56 +++++++++++++++++++++----- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/source/blender/blenkernel/intern/customdata.cc b/source/blender/blenkernel/intern/customdata.cc index 277218033e9..b990ccbec80 100644 --- a/source/blender/blenkernel/intern/customdata.cc +++ b/source/blender/blenkernel/intern/customdata.cc @@ -4443,9 +4443,48 @@ bool CustomData_verify_versions(CustomData *data, int index) return keeplayer; } +static bool CustomData_layer_ensure_data_exists(CustomDataLayer *layer, size_t count) +{ + BLI_assert(layer); + const LayerTypeInfo *typeInfo = layerType_getInfo(layer->type); + BLI_assert(typeInfo); + + if (layer->data || count == 0) { + return false; + } + + switch (layer->type) { + /* When more instances of corrupt files are found, add them here. */ + case CD_PROP_BOOL: /* See T84935. */ + case CD_MLOOPUV: /* See T90620. */ + layer->data = MEM_calloc_arrayN(count, typeInfo->size, layerType_getName(layer->type)); + BLI_assert(layer->data); + if (typeInfo->set_default) { + typeInfo->set_default(layer->data, count); + } + return true; + break; + + default: + /* Log an error so we can collect instances of bad files. */ + CLOG_ERROR(&LOG, "CustomDataLayer->data is NULL for type %d.", layer->type); + break; + } + return false; +} + bool CustomData_layer_validate(CustomDataLayer *layer, const uint totitems, const bool do_fixes) { + BLI_assert(layer); const LayerTypeInfo *typeInfo = layerType_getInfo(layer->type); + BLI_assert(typeInfo); + + if (do_fixes) { + CustomData_layer_ensure_data_exists(layer, totitems); + } + + BLI_assert((totitems == 0) || layer->data); + BLI_assert(MEM_allocN_len(layer->data) >= totitems * typeInfo->size); if (typeInfo->validate != nullptr) { return typeInfo->validate(layer->data, totitems, do_fixes); @@ -5206,16 +5245,15 @@ void CustomData_blend_read(BlendDataReader *reader, CustomData *data, int count) if (CustomData_verify_versions(data, i)) { BLO_read_data_address(reader, &layer->data); - if (layer->data == nullptr && count > 0 && layer->type == CD_PROP_BOOL) { - /* Usually this should never happen, except when a custom data layer has not been written - * to a file correctly. */ - CLOG_WARN(&LOG, "Reallocating custom data layer that was not saved correctly."); - const LayerTypeInfo *info = layerType_getInfo(layer->type); - layer->data = MEM_calloc_arrayN((size_t)count, info->size, layerType_getName(layer->type)); - if (info->set_default) { - info->set_default(layer->data, count); - } + if (CustomData_layer_ensure_data_exists(layer, count)) { + /* Under normal operations, this shouldn't happen, but... + * For a CD_PROP_BOOL example, see T84935. + * For a CD_MLOOPUV example, see T90620. */ + CLOG_WARN(&LOG, + "Allocated custom data layer that was not saved correctly for layer->type = %d.", + layer->type); } + if (layer->type == CD_MDISPS) { blend_read_mdisps( reader, count, static_cast(layer->data), layer->flag & CD_FLAG_EXTERNAL); -- cgit v1.2.3 From dd158f1caba48810462c33855b1e877aba8d7324 Mon Sep 17 00:00:00 2001 From: Chris Blackbourn Date: Thu, 21 Jul 2022 16:25:01 +1200 Subject: Fix failing cycles test from previous commit Deprecated custom data type CD_MTEXPOLY has inconsistent data usage. Reviewed By: Campbell Barton --- source/blender/blenkernel/intern/customdata.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/source/blender/blenkernel/intern/customdata.cc b/source/blender/blenkernel/intern/customdata.cc index b990ccbec80..b12eafa9cef 100644 --- a/source/blender/blenkernel/intern/customdata.cc +++ b/source/blender/blenkernel/intern/customdata.cc @@ -4465,9 +4465,13 @@ static bool CustomData_layer_ensure_data_exists(CustomDataLayer *layer, size_t c return true; break; + case CD_MTEXPOLY: + /* TODO: Investigate multiple test failures on cycles, e.g. cycles_shadow_catcher_cpu. */ + break; + default: /* Log an error so we can collect instances of bad files. */ - CLOG_ERROR(&LOG, "CustomDataLayer->data is NULL for type %d.", layer->type); + CLOG_WARN(&LOG, "CustomDataLayer->data is NULL for type %d.", layer->type); break; } return false; -- cgit v1.2.3 From 4ec0a8705b86e589c32fc532280dc2e443c87f84 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 21 Jul 2022 15:23:32 +1000 Subject: WM: categorize smart-zoom as a gesture Event handling and the enum definition documents MOUSESMARTZOOM as a gesture however it wasn't accepted by ISMOUSE_GESTURE, instead it was added to the ISMOUSE macro. Move the type check to ISMOUSE_GESTURE. --- source/blender/windowmanager/wm_event_types.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/source/blender/windowmanager/wm_event_types.h b/source/blender/windowmanager/wm_event_types.h index edac3ada73b..2911c74e5a4 100644 --- a/source/blender/windowmanager/wm_event_types.h +++ b/source/blender/windowmanager/wm_event_types.h @@ -380,12 +380,11 @@ enum { (event_type) == EVT_OSKEY) /** Test whether the event is a mouse button. */ -#define ISMOUSE(event_type) \ - (((event_type) >= LEFTMOUSE && (event_type) <= BUTTON7MOUSE) || (event_type) == MOUSESMARTZOOM) +#define ISMOUSE(event_type) ((event_type) >= LEFTMOUSE && (event_type) <= BUTTON7MOUSE) /** Test whether the event is a mouse wheel. */ #define ISMOUSE_WHEEL(event_type) ((event_type) >= WHEELUPMOUSE && (event_type) <= WHEELOUTMOUSE) /** Test whether the event is a mouse (track-pad) gesture. */ -#define ISMOUSE_GESTURE(event_type) ((event_type) >= MOUSEPAN && (event_type) <= MOUSEROTATE) +#define ISMOUSE_GESTURE(event_type) ((event_type) >= MOUSEPAN && (event_type) <= MOUSESMARTZOOM) /** Test whether the event is a mouse button (excluding mouse-wheel). */ #define ISMOUSE_BUTTON(event_type) \ (ELEM(event_type, \ -- cgit v1.2.3 From 095b8d8688f689ac3428e72e8831808fe9263db2 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 21 Jul 2022 15:54:39 +1000 Subject: WM: replace ISMOUSE with ISMOUSE_BUTTON The ISMOUSE macro was used in situations only button events needed to be checked. The only functional difference would be MOUSEMOVE events were previously accepted for these checks. --- source/blender/editors/interface/interface_handlers.c | 3 +-- source/blender/editors/object/object_transform.cc | 2 +- source/blender/editors/transform/transform_generics.c | 2 +- source/blender/windowmanager/intern/wm_event_query.c | 4 ++-- source/blender/windowmanager/intern/wm_event_system.cc | 2 +- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 0a50522a141..141c1c50fa6 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -10096,8 +10096,7 @@ static int ui_handle_menu_button(bContext *C, const wmEvent *event, uiPopupBlock /* Pass, needed to click-exit outside of non-floating menus. */ ui_region_auto_open_clear(but->active->region); } - else if ((!ELEM(event->type, MOUSEMOVE, WHEELUPMOUSE, WHEELDOWNMOUSE, MOUSEPAN)) && - ISMOUSE(event->type)) { + else if (ISMOUSE_BUTTON(event->type)) { if (!ui_but_contains_point_px(but, but->active->region, event->xy)) { but = NULL; } diff --git a/source/blender/editors/object/object_transform.cc b/source/blender/editors/object/object_transform.cc index 70c3eed3768..c9c96900af3 100644 --- a/source/blender/editors/object/object_transform.cc +++ b/source/blender/editors/object/object_transform.cc @@ -2222,7 +2222,7 @@ static int object_transform_axis_target_modal(bContext *C, wmOperator *op, const bool is_finished = false; - if (ISMOUSE(xfd->init_event)) { + if (ISMOUSE_BUTTON(xfd->init_event)) { if ((event->type == xfd->init_event) && (event->val == KM_RELEASE)) { is_finished = true; } diff --git a/source/blender/editors/transform/transform_generics.c b/source/blender/editors/transform/transform_generics.c index e45cac36736..56a7d045dfd 100644 --- a/source/blender/editors/transform/transform_generics.c +++ b/source/blender/editors/transform/transform_generics.c @@ -555,7 +555,7 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve } else { /* Release confirms preference should not affect node editor (T69288, T70504). */ - if (ISMOUSE(t->launch_event) && + if (ISMOUSE_BUTTON(t->launch_event) && ((U.flag & USER_RELEASECONFIRM) || (t->spacetype == SPACE_NODE))) { /* Global "release confirm" on mouse bindings */ t->flag |= T_RELEASE_CONFIRM; diff --git a/source/blender/windowmanager/intern/wm_event_query.c b/source/blender/windowmanager/intern/wm_event_query.c index 81044197ae7..a1d94c33f27 100644 --- a/source/blender/windowmanager/intern/wm_event_query.c +++ b/source/blender/windowmanager/intern/wm_event_query.c @@ -346,8 +346,8 @@ bool WM_cursor_test_motion_and_update(const int mval[2]) int WM_event_drag_threshold(const struct wmEvent *event) { int drag_threshold; - if (ISMOUSE(event->prev_press_type)) { - BLI_assert(event->prev_press_type != MOUSEMOVE); + BLI_assert(event->prev_press_type != MOUSEMOVE); + if (ISMOUSE_BUTTON(event->prev_press_type)) { /* Using the previous type is important is we want to check the last pressed/released button, * The `event->type` would include #MOUSEMOVE which is always the case when dragging * and does not help us know which threshold to use. */ diff --git a/source/blender/windowmanager/intern/wm_event_system.cc b/source/blender/windowmanager/intern/wm_event_system.cc index 79b303364d8..2a7b17645d7 100644 --- a/source/blender/windowmanager/intern/wm_event_system.cc +++ b/source/blender/windowmanager/intern/wm_event_system.cc @@ -4975,7 +4975,7 @@ static bool wm_event_is_double_click(const wmEvent *event) { if ((event->type == event->prev_type) && (event->prev_val == KM_RELEASE) && (event->val == KM_PRESS)) { - if (ISMOUSE(event->type) && WM_event_drag_test(event, event->prev_press_xy)) { + if (ISMOUSE_BUTTON(event->type) && WM_event_drag_test(event, event->prev_press_xy)) { /* Pass. */ } else { -- cgit v1.2.3 From 7a736854603b640ca2a384a6cf05d0afc7fbe6dd Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 21 Jul 2022 15:59:54 +1000 Subject: Fix WM_event_type_mask_test ignoring wheel and gesture events WM_event_type_mask_test checks assumed ISMOUSE macro worked for any kind of mouse event when it only accepted buttons & motion. Now ISMOUSE checks for any kind of mouse event, use ISMOUSE_BUTTON/WHEEL/GESTURE for more specific checks. --- source/blender/windowmanager/wm_event_types.h | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/source/blender/windowmanager/wm_event_types.h b/source/blender/windowmanager/wm_event_types.h index 2911c74e5a4..c356b275a7e 100644 --- a/source/blender/windowmanager/wm_event_types.h +++ b/source/blender/windowmanager/wm_event_types.h @@ -44,7 +44,10 @@ enum { /* non-event, for example disabled timer */ EVENT_NONE = 0x0000, - /* ********** Start of Input devices. ********** */ +/* ********** Start of Input devices. ********** */ + +/* Minimum mouse value (inclusive). */ +#define _EVT_MOUSE_MIN 0x0001 /* MOUSE: 0x000x, 0x001x */ LEFTMOUSE = 0x0001, @@ -74,6 +77,9 @@ enum { * paint and drawing tools however will want to handle these. */ INBETWEEN_MOUSEMOVE = 0x0011, +/* Maximum keyboard value (inclusive). */ +#define _EVT_MOUSE_MAX 0x0011 /* 17 */ + /* IME event, GHOST_kEventImeCompositionStart in ghost */ WM_IME_COMPOSITE_START = 0x0014, /* IME event, GHOST_kEventImeComposition in ghost */ @@ -379,8 +385,14 @@ enum { (((event_type) >= EVT_LEFTCTRLKEY && (event_type) <= EVT_LEFTSHIFTKEY) || \ (event_type) == EVT_OSKEY) -/** Test whether the event is a mouse button. */ -#define ISMOUSE(event_type) ((event_type) >= LEFTMOUSE && (event_type) <= BUTTON7MOUSE) +/** + * Test whether the event is any kind: + * #ISMOUSE_BUTTON, #ISMOUSE_WHEEL, #ISMOUSE_GESTURE & motion. + * + * \note It's best to use more specific check if possible as mixing motion/buttons/gestures + * is very broad and not necessarily obvious which kinds of events are important. + */ +#define ISMOUSE(event_type) ((event_type) >= _EVT_MOUSE_MIN && (event_type) <= _EVT_MOUSE_MAX) /** Test whether the event is a mouse wheel. */ #define ISMOUSE_WHEEL(event_type) ((event_type) >= WHEELUPMOUSE && (event_type) <= WHEELOUTMOUSE) /** Test whether the event is a mouse (track-pad) gesture. */ -- cgit v1.2.3 From 2eeedbbca96306971a7605ade18ee42053b17e41 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 21 Jul 2022 16:23:33 +1000 Subject: Cleanup: add ISMOUSE_MOTION macro Replace verbose ELEM(..) usage, now each kind of mouse event has it's own macro. --- source/blender/editors/curve/editcurve_paint.c | 2 +- source/blender/editors/curve/editcurve_pen.c | 2 +- source/blender/editors/gpencil/annotate_paint.c | 2 +- source/blender/editors/gpencil/gpencil_paint.c | 2 +- .../editors/interface/eyedroppers/eyedropper_color.c | 2 +- source/blender/editors/interface/interface_handlers.c | 6 +++--- source/blender/editors/interface/interface_panel.c | 2 +- source/blender/editors/mesh/editmesh_tools.c | 2 +- source/blender/editors/sculpt_paint/paint_stroke.c | 5 ++--- source/blender/editors/space_view3d/view3d_navigate_walk.c | 2 +- source/blender/editors/transform/transform.c | 2 +- source/blender/makesrna/intern/rna_wm_api.c | 2 +- source/blender/windowmanager/intern/wm_event_system.cc | 13 ++++++------- source/blender/windowmanager/intern/wm_operators.c | 2 +- source/blender/windowmanager/wm_event_types.h | 12 +++++++----- 15 files changed, 29 insertions(+), 29 deletions(-) diff --git a/source/blender/editors/curve/editcurve_paint.c b/source/blender/editors/curve/editcurve_paint.c index 6946c09e6f1..7632f1b1e64 100644 --- a/source/blender/editors/curve/editcurve_paint.c +++ b/source/blender/editors/curve/editcurve_paint.c @@ -1162,7 +1162,7 @@ static int curve_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) curve_draw_event_add_first(op, event); } } - else if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + else if (ISMOUSE_MOTION(event->type)) { if (cdd->state == CURVE_DRAW_PAINTING) { const float mval_fl[2] = {UNPACK2(event->mval)}; if (len_squared_v2v2(mval_fl, cdd->prev.mval) > square_f(STROKE_SAMPLE_DIST_MIN_PX)) { diff --git a/source/blender/editors/curve/editcurve_pen.c b/source/blender/editors/curve/editcurve_pen.c index 729ad46877a..27f4e4fca61 100644 --- a/source/blender/editors/curve/editcurve_pen.c +++ b/source/blender/editors/curve/editcurve_pen.c @@ -1622,7 +1622,7 @@ static int curve_pen_modal(bContext *C, wmOperator *op, const wmEvent *event) } } - if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + if (ISMOUSE_MOTION(event->type)) { /* Check if dragging */ if (!cpd->dragging && WM_event_drag_test(event, event->prev_press_xy)) { cpd->dragging = true; diff --git a/source/blender/editors/gpencil/annotate_paint.c b/source/blender/editors/gpencil/annotate_paint.c index 2d613e2f433..d08d56a354a 100644 --- a/source/blender/editors/gpencil/annotate_paint.c +++ b/source/blender/editors/gpencil/annotate_paint.c @@ -2641,7 +2641,7 @@ static int annotation_draw_modal(bContext *C, wmOperator *op, const wmEvent *eve /* handle mode-specific events */ if (p->status == GP_STATUS_PAINTING) { /* handle painting mouse-movements? */ - if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE) || (p->flags & GP_PAINTFLAG_FIRSTRUN)) { + if (ISMOUSE_MOTION(event->type) || (p->flags & GP_PAINTFLAG_FIRSTRUN)) { /* handle drawing event */ if ((p->flags & GP_PAINTFLAG_FIRSTRUN) == 0) { annotation_add_missing_events(C, op, event, p); diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index 70486138556..13ea5179b23 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -3755,7 +3755,7 @@ static int gpencil_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) /* handle mode-specific events */ if (p->status == GP_STATUS_PAINTING) { /* handle painting mouse-movements? */ - if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE) || (p->flags & GP_PAINTFLAG_FIRSTRUN)) { + if (ISMOUSE_MOTION(event->type) || (p->flags & GP_PAINTFLAG_FIRSTRUN)) { /* handle drawing event */ bool is_speed_guide = ((guide->use_guide) && (p->brush && (p->brush->gpencil_tool == GPAINT_TOOL_DRAW))); diff --git a/source/blender/editors/interface/eyedroppers/eyedropper_color.c b/source/blender/editors/interface/eyedroppers/eyedropper_color.c index a2161008ec4..9c430afd5f0 100644 --- a/source/blender/editors/interface/eyedroppers/eyedropper_color.c +++ b/source/blender/editors/interface/eyedroppers/eyedropper_color.c @@ -479,7 +479,7 @@ static int eyedropper_modal(bContext *C, wmOperator *op, const wmEvent *event) break; } } - else if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + else if (ISMOUSE_MOTION(event->type)) { if (eye->accum_start) { /* button is pressed so keep sampling */ eyedropper_color_sample(C, eye, event->xy); diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 141c1c50fa6..80fd0cbe16e 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -4509,7 +4509,7 @@ static int ui_do_but_HOTKEYEVT(bContext *C, } } else if (data->state == BUTTON_STATE_WAIT_KEY_EVENT) { - if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + if (ISMOUSE_MOTION(event->type)) { return WM_UI_HANDLER_CONTINUE; } if (event->type == EVT_UNKNOWNKEY) { @@ -4575,7 +4575,7 @@ static int ui_do_but_KEYEVT(bContext *C, } } else if (data->state == BUTTON_STATE_WAIT_KEY_EVENT) { - if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + if (ISMOUSE_MOTION(event->type)) { return WM_UI_HANDLER_CONTINUE; } @@ -8097,7 +8097,7 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent * #ifdef USE_DRAG_MULTINUM data = but->active; if (data) { - if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE) || + if (ISMOUSE_MOTION(event->type) || /* if we started dragging, progress on any event */ (data->multi_data.init == BUTTON_MULTI_INIT_SETUP)) { if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER) && diff --git a/source/blender/editors/interface/interface_panel.c b/source/blender/editors/interface/interface_panel.c index 87bfb7ca0f7..d4a9a4ca4cd 100644 --- a/source/blender/editors/interface/interface_panel.c +++ b/source/blender/editors/interface/interface_panel.c @@ -2318,7 +2318,7 @@ int ui_handler_panel_region(bContext *C, const uiBut *active_but) { /* Mouse-move events are handled by separate handlers for dragging and drag collapsing. */ - if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + if (ISMOUSE_MOTION(event->type)) { return WM_UI_HANDLER_CONTINUE; } diff --git a/source/blender/editors/mesh/editmesh_tools.c b/source/blender/editors/mesh/editmesh_tools.c index 1febc429edc..c5add97fb00 100644 --- a/source/blender/editors/mesh/editmesh_tools.c +++ b/source/blender/editors/mesh/editmesh_tools.c @@ -8692,7 +8692,7 @@ static int edbm_point_normals_modal(bContext *C, wmOperator *op, const wmEvent * * Free the data here, then use #point_normals_ensure to add it back on demand. */ if (ret == OPERATOR_PASS_THROUGH) { /* Don't free on mouse-move, causes creation/freeing of the loop data in an inefficient way. */ - if (!ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + if (!ISMOUSE_MOTION(event->type)) { point_normals_free(op); } } diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c index 736942b9f72..97fa2b936e0 100644 --- a/source/blender/editors/sculpt_paint/paint_stroke.c +++ b/source/blender/editors/sculpt_paint/paint_stroke.c @@ -1550,8 +1550,7 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event, PaintS copy_v2_fl2(mouse, event->mval[0], event->mval[1]); paint_stroke_line_constrain(stroke, mouse); - if (stroke->stroke_started && - (first_modal || (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)))) { + if (stroke->stroke_started && (first_modal || ISMOUSE_MOTION(event->type))) { if ((br->mtex.brush_angle_mode & MTEX_ANGLE_RAKE) || (br->mask_mtex.brush_angle_mode & MTEX_ANGLE_RAKE)) { copy_v2_v2(stroke->ups->last_rake, stroke->last_mouse_position); @@ -1561,7 +1560,7 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event, PaintS } else if (first_modal || /* regular dabs */ - (!(br->flag & BRUSH_AIRBRUSH) && (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE))) || + (!(br->flag & BRUSH_AIRBRUSH) && ISMOUSE_MOTION(event->type)) || /* airbrush */ ((br->flag & BRUSH_AIRBRUSH) && event->type == TIMER && event->customdata == stroke->timer)) { diff --git a/source/blender/editors/space_view3d/view3d_navigate_walk.c b/source/blender/editors/space_view3d/view3d_navigate_walk.c index 471231b5f27..f1e9ac22882 100644 --- a/source/blender/editors/space_view3d/view3d_navigate_walk.c +++ b/source/blender/editors/space_view3d/view3d_navigate_walk.c @@ -659,7 +659,7 @@ static void walkEvent(WalkInfo *walk, const wmEvent *event) if (event->type == TIMER && event->customdata == walk->timer) { walk->redraw = true; } - else if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + else if (ISMOUSE_MOTION(event->type)) { #ifdef USE_TABLET_SUPPORT if ((walk->is_cursor_absolute == false) && event->tablet.is_motion_absolute) { diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c index d9e23b98c66..6e47b30ae9d 100644 --- a/source/blender/editors/transform/transform.c +++ b/source/blender/editors/transform/transform.c @@ -1327,7 +1327,7 @@ int transformEvent(TransInfo *t, const wmEvent *event) handled = true; } - if (t->redraw && !ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + if (t->redraw && !ISMOUSE_MOTION(event->type)) { WM_window_status_area_tag_redraw(CTX_wm_window(t->context)); } diff --git a/source/blender/makesrna/intern/rna_wm_api.c b/source/blender/makesrna/intern/rna_wm_api.c index eb5e2549b1d..b82458c4442 100644 --- a/source/blender/makesrna/intern/rna_wm_api.c +++ b/source/blender/makesrna/intern/rna_wm_api.c @@ -625,7 +625,7 @@ static wmEvent *rna_Window_event_add_simulate(wmWindow *win, return NULL; } } - if (ELEM(type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + if (ISMOUSE_MOTION(type)) { if (value != KM_NOTHING) { BKE_report(reports, RPT_ERROR, "Value: must be 'NOTHING' for motion"); return NULL; diff --git a/source/blender/windowmanager/intern/wm_event_system.cc b/source/blender/windowmanager/intern/wm_event_system.cc index 2a7b17645d7..102441f1b4d 100644 --- a/source/blender/windowmanager/intern/wm_event_system.cc +++ b/source/blender/windowmanager/intern/wm_event_system.cc @@ -190,7 +190,7 @@ void wm_event_free(wmEvent *event) printf("%s: 'is_repeat=true' for non-keyboard event, this should not happen.\n", __func__); WM_event_print(event); } - if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE) && (event->val != KM_NOTHING)) { + if (ISMOUSE_MOTION(event->type) && (event->val != KM_NOTHING)) { printf("%s: 'val != NOTHING' for a cursor motion event, this should not happen.\n", __func__); WM_event_print(event); } @@ -3095,7 +3095,7 @@ static int wm_handlers_do_intern(bContext *C, wmWindow *win, wmEvent *event, Lis const bool do_debug_handler = (G.debug & G_DEBUG_HANDLERS) && /* Comment this out to flood the console! (if you really want to test). */ - !ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE); + !ISMOUSE_MOTION(event->type); wmWindowManager *wm = CTX_wm_manager(C); int action = WM_HANDLER_CONTINUE; @@ -3286,7 +3286,7 @@ static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers) return action; } - if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + if (ISMOUSE_MOTION(event->type)) { /* Test for #KM_CLICK_DRAG events. */ /* NOTE(@campbellbarton): Needed so drag can be used for editors that support both click @@ -3821,15 +3821,14 @@ void wm_event_do_handlers(bContext *C) /* Active screen might change during handlers, update pointer. */ screen = WM_window_get_active_screen(win); - if (G.debug & (G_DEBUG_HANDLERS | G_DEBUG_EVENTS) && - !ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + if (G.debug & (G_DEBUG_HANDLERS | G_DEBUG_EVENTS) && !ISMOUSE_MOTION(event->type)) { printf("\n%s: Handling event\n", __func__); WM_event_print(event); } /* Take care of pie event filter. */ if (wm_event_pie_filter(win, event)) { - if (!ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + if (!ISMOUSE_MOTION(event->type)) { CLOG_INFO(WM_LOG_HANDLERS, 1, "event filtered due to pie button pressed"); } BLI_remlink(&win->event_queue, event); @@ -3851,7 +3850,7 @@ void wm_event_do_handlers(bContext *C) /* Clear tool-tip on mouse move. */ if (screen->tool_tip && screen->tool_tip->exit_on_event) { - if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + if (ISMOUSE_MOTION(event->type)) { if (len_manhattan_v2v2_int(screen->tool_tip->event_xy, event->xy) > WM_EVENT_CURSOR_MOTION_THRESHOLD) { WM_tooltip_clear(C, win); diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c index 33c69a23558..315e4c994ad 100644 --- a/source/blender/windowmanager/intern/wm_operators.c +++ b/source/blender/windowmanager/intern/wm_operators.c @@ -939,7 +939,7 @@ int WM_generic_select_modal(bContext *C, wmOperator *op, const wmEvent *event) return ret_value | OPERATOR_PASS_THROUGH; } - if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + if (ISMOUSE_MOTION(event->type)) { const int drag_delta[2] = { mval[0] - event->mval[0], mval[1] - event->mval[1], diff --git a/source/blender/windowmanager/wm_event_types.h b/source/blender/windowmanager/wm_event_types.h index c356b275a7e..b4e81dc54c8 100644 --- a/source/blender/windowmanager/wm_event_types.h +++ b/source/blender/windowmanager/wm_event_types.h @@ -387,16 +387,14 @@ enum { /** * Test whether the event is any kind: - * #ISMOUSE_BUTTON, #ISMOUSE_WHEEL, #ISMOUSE_GESTURE & motion. + * #ISMOUSE_MOTION, #ISMOUSE_BUTTON, #ISMOUSE_WHEEL & #ISMOUSE_GESTURE. * * \note It's best to use more specific check if possible as mixing motion/buttons/gestures * is very broad and not necessarily obvious which kinds of events are important. */ #define ISMOUSE(event_type) ((event_type) >= _EVT_MOUSE_MIN && (event_type) <= _EVT_MOUSE_MAX) -/** Test whether the event is a mouse wheel. */ -#define ISMOUSE_WHEEL(event_type) ((event_type) >= WHEELUPMOUSE && (event_type) <= WHEELOUTMOUSE) -/** Test whether the event is a mouse (track-pad) gesture. */ -#define ISMOUSE_GESTURE(event_type) ((event_type) >= MOUSEPAN && (event_type) <= MOUSESMARTZOOM) +/** Test whether the event is a mouse button (excluding mouse-wheel). */ +#define ISMOUSE_MOTION(event_type) ELEM(event_type, MOUSEMOVE, INBETWEEN_MOUSEMOVE) /** Test whether the event is a mouse button (excluding mouse-wheel). */ #define ISMOUSE_BUTTON(event_type) \ (ELEM(event_type, \ @@ -407,6 +405,10 @@ enum { BUTTON5MOUSE, \ BUTTON6MOUSE, \ BUTTON7MOUSE)) +/** Test whether the event is a mouse wheel. */ +#define ISMOUSE_WHEEL(event_type) ((event_type) >= WHEELUPMOUSE && (event_type) <= WHEELOUTMOUSE) +/** Test whether the event is a mouse (track-pad) gesture. */ +#define ISMOUSE_GESTURE(event_type) ((event_type) >= MOUSEPAN && (event_type) <= MOUSESMARTZOOM) /** Test whether the event is a NDOF event. */ #define ISNDOF(event_type) ((event_type) >= _NDOF_MIN && (event_type) <= _NDOF_MAX) -- cgit v1.2.3 From d6faee2824b9647a96a3679ead7220bf32b2430b Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 21 Jul 2022 17:45:36 +1000 Subject: Cleanup: format --- release/scripts/startup/bl_ui/space_view3d.py | 2 +- source/blender/draw/intern/draw_manager.c | 1 - source/blender/editors/interface/views/tree_view.cc | 3 ++- source/blender/editors/object/object_add.cc | 3 ++- source/blender/gpu/metal/mtl_backend.mm | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 1a30c666bcb..49a1a115edf 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -604,7 +604,7 @@ class VIEW3D_HT_header(Header): else: if (object_mode not in { 'SCULPT', 'SCULPT_CURVES', 'VERTEX_PAINT', 'WEIGHT_PAINT', 'TEXTURE_PAINT', - 'PAINT_GPENCIL', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL', 'VERTEX_GPENCIL', + 'PAINT_GPENCIL', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL', 'VERTEX_GPENCIL', }) or has_pose_mode: show_snap = True else: diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c index 89ae1c73a76..b2422504825 100644 --- a/source/blender/draw/intern/draw_manager.c +++ b/source/blender/draw/intern/draw_manager.c @@ -212,7 +212,6 @@ int DRW_object_visibility_in_active_context(const Object *ob) return BKE_object_visibility(ob, mode); } - bool DRW_object_use_hide_faces(const struct Object *ob) { if (ob->type == OB_MESH) { diff --git a/source/blender/editors/interface/views/tree_view.cc b/source/blender/editors/interface/views/tree_view.cc index 21078b711c7..43fdf741ac5 100644 --- a/source/blender/editors/interface/views/tree_view.cc +++ b/source/blender/editors/interface/views/tree_view.cc @@ -168,7 +168,8 @@ void AbstractTreeViewItem::collapse_chevron_click_fn(struct bContext *C, const wmWindow *win = CTX_wm_window(C); const ARegion *region = CTX_wm_region(C); - uiViewItemHandle *hovered_item_handle = UI_region_views_find_item_at(region, win->eventstate->xy); + uiViewItemHandle *hovered_item_handle = UI_region_views_find_item_at(region, + win->eventstate->xy); AbstractTreeViewItem *hovered_item = from_item_handle(hovered_item_handle); BLI_assert(hovered_item != nullptr); diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc index a8e11afba65..9c34e203bee 100644 --- a/source/blender/editors/object/object_add.cc +++ b/source/blender/editors/object/object_add.cc @@ -2079,7 +2079,8 @@ static int object_curves_empty_hair_add_exec(bContext *C, wmOperator *op) Object *surface_ob = CTX_data_active_object(C); BLI_assert(surface_ob != nullptr); - Object *curves_ob = ED_object_add_type(C, OB_CURVES, nullptr, nullptr, nullptr, false, local_view_bits); + Object *curves_ob = ED_object_add_type( + C, OB_CURVES, nullptr, nullptr, nullptr, false, local_view_bits); BKE_object_apply_mat4(curves_ob, surface_ob->obmat, false, false); /* Set surface object. */ diff --git a/source/blender/gpu/metal/mtl_backend.mm b/source/blender/gpu/metal/mtl_backend.mm index 3328855bbf8..83cf3af0804 100644 --- a/source/blender/gpu/metal/mtl_backend.mm +++ b/source/blender/gpu/metal/mtl_backend.mm @@ -10,8 +10,8 @@ #include "mtl_backend.hh" #include "mtl_context.hh" #include "mtl_framebuffer.hh" -#include "mtl_uniform_buffer.hh" #include "mtl_query.hh" +#include "mtl_uniform_buffer.hh" #include "gpu_capabilities_private.hh" #include "gpu_platform_private.hh" -- cgit v1.2.3 From 4089b7b80ba291dc04266a0dc58820ceeb48eb7b Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Wed, 20 Jul 2022 09:59:33 +0200 Subject: Depsgraph: Clear operation evaluation flags early on The goal is to make it possible to evaluate the graph in multiple passes without evaluating the same node multiple times. Currently should not be any functional changes. --- source/blender/depsgraph/intern/eval/deg_eval.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/blender/depsgraph/intern/eval/deg_eval.cc b/source/blender/depsgraph/intern/eval/deg_eval.cc index 9ac1f5275ac..47f2a8ca219 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval.cc +++ b/source/blender/depsgraph/intern/eval/deg_eval.cc @@ -101,6 +101,12 @@ void evaluate_node(const DepsgraphEvalState *state, OperationNode *operation_nod else { operation_node->evaluate(depsgraph); } + + /* Clear the flag early on, allowing partial updates without re-evaluating the same node multiple + * times. + * This is a thread-safe modification as the node's flags are only read for a non-scheduled nodes + * and this node has been scheduled. */ + operation_node->flag &= ~DEPSOP_FLAG_NEEDS_UPDATE; } void deg_task_run_func(TaskPool *pool, void *taskdata) -- cgit v1.2.3 From 0dcee6a386645bb1e976d11aa2d1ae45b01f968a Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Wed, 20 Jul 2022 10:04:02 +0200 Subject: Fix T99733: Objects with driven visibility are evaluated when not needed The issue was caused by the fact that objects with driven or animated visibility were considered visible by the dependency graph evaluation. This change makes it so the dependency graph evaluation is aware of visibility which might be changing. This is achieved by evaluating the path of the graph which affects objects visibility and adjusts to it before evaluating the rest of the graph. There is some time penalty to this, but there does not seem to be a way to fully avoid this penalty. With the production shot from the heist project the FPS drops by a tenth of a frame (~9.4 vs ~9.3 fps) when adding a driver to an object which keeps it visible. Note that this is a bit hard to measure since the FPS fluctuates quite a bit throughout the playback. On the other hand, having a driver on a visibility of a heavy object from character and setting visibility to false gives big speedup. Also worth noting that there is no penalty at all when there are no animated visibilities in the scene. Differential Revision: https://developer.blender.org/D15498 --- source/blender/depsgraph/CMakeLists.txt | 2 + .../depsgraph/intern/builder/deg_builder.cc | 85 ++---------- .../blender/depsgraph/intern/builder/deg_builder.h | 2 + .../depsgraph/intern/builder/deg_builder_nodes.cc | 33 +++-- .../intern/builder/deg_builder_nodes_view_layer.cc | 22 +-- .../intern/builder/deg_builder_relations.cc | 7 + source/blender/depsgraph/intern/depsgraph.cc | 2 + source/blender/depsgraph/intern/depsgraph.h | 6 + .../depsgraph/intern/depsgraph_query_iter.cc | 9 +- source/blender/depsgraph/intern/eval/deg_eval.cc | 82 +++++++++--- .../depsgraph/intern/eval/deg_eval_visibility.cc | 147 +++++++++++++++++++++ .../depsgraph/intern/eval/deg_eval_visibility.h | 26 ++++ .../depsgraph/intern/node/deg_node_component.cc | 7 +- .../depsgraph/intern/node/deg_node_component.h | 18 ++- .../blender/depsgraph/intern/node/deg_node_id.cc | 9 +- source/blender/depsgraph/intern/node/deg_node_id.h | 17 ++- .../depsgraph/intern/node/deg_node_operation.h | 3 + 17 files changed, 348 insertions(+), 129 deletions(-) create mode 100644 source/blender/depsgraph/intern/eval/deg_eval_visibility.cc create mode 100644 source/blender/depsgraph/intern/eval/deg_eval_visibility.h diff --git a/source/blender/depsgraph/CMakeLists.txt b/source/blender/depsgraph/CMakeLists.txt index 3d539018cef..e799c5ce32a 100644 --- a/source/blender/depsgraph/CMakeLists.txt +++ b/source/blender/depsgraph/CMakeLists.txt @@ -67,6 +67,8 @@ set(SRC intern/eval/deg_eval_runtime_backup_sound.cc intern/eval/deg_eval_runtime_backup_volume.cc intern/eval/deg_eval_stats.cc + intern/eval/deg_eval_visibility.cc + intern/eval/deg_eval_visibility.h intern/node/deg_node.cc intern/node/deg_node_component.cc intern/node/deg_node_factory.cc diff --git a/source/blender/depsgraph/intern/builder/deg_builder.cc b/source/blender/depsgraph/intern/builder/deg_builder.cc index 1fec1fb3219..5353f71685c 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder.cc @@ -29,6 +29,7 @@ #include "intern/depsgraph_tag.h" #include "intern/depsgraph_type.h" #include "intern/eval/deg_eval_copy_on_write.h" +#include "intern/eval/deg_eval_visibility.h" #include "intern/node/deg_node.h" #include "intern/node/deg_node_component.h" #include "intern/node/deg_node_id.h" @@ -71,10 +72,15 @@ bool DepsgraphBuilder::need_pull_base_into_graph(const Base *base) if (base->flag & base_flag) { return true; } + /* More involved check: since we don't support dynamic changes in dependency graph topology and * all visible objects are to be part of dependency graph, we pull all objects which has animated * visibility. */ - const Object *object = base->object; + return is_object_visibility_animated(base->object); +} + +bool DepsgraphBuilder::is_object_visibility_animated(const Object *object) +{ AnimatedPropertyID property_id; if (graph_->mode == DAG_EVAL_VIEWPORT) { property_id = AnimatedPropertyID(&object->id, &RNA_Object, "hide_viewport"); @@ -127,84 +133,9 @@ bool DepsgraphBuilder::check_pchan_has_bbone_segments(const Object *object, cons /** \name Builder Finalizer. * \{ */ -namespace { - -void deg_graph_build_flush_visibility(Depsgraph *graph) -{ - enum { - DEG_NODE_VISITED = (1 << 0), - }; - - BLI_Stack *stack = BLI_stack_new(sizeof(OperationNode *), "DEG flush layers stack"); - for (IDNode *id_node : graph->id_nodes) { - for (ComponentNode *comp_node : id_node->components.values()) { - comp_node->affects_directly_visible |= id_node->is_directly_visible; - } - } - - for (OperationNode *op_node : graph->operations) { - op_node->custom_flags = 0; - op_node->num_links_pending = 0; - for (Relation *rel : op_node->outlinks) { - if ((rel->from->type == NodeType::OPERATION) && (rel->flag & RELATION_FLAG_CYCLIC) == 0) { - ++op_node->num_links_pending; - } - } - if (op_node->num_links_pending == 0) { - BLI_stack_push(stack, &op_node); - op_node->custom_flags |= DEG_NODE_VISITED; - } - } - - while (!BLI_stack_is_empty(stack)) { - OperationNode *op_node; - BLI_stack_pop(stack, &op_node); - /* Flush layers to parents. */ - for (Relation *rel : op_node->inlinks) { - if (rel->from->type == NodeType::OPERATION) { - OperationNode *op_from = (OperationNode *)rel->from; - ComponentNode *comp_from = op_from->owner; - const bool target_directly_visible = op_node->owner->affects_directly_visible; - - /* Visibility component forces all components of the current ID to be considered as - * affecting directly visible. */ - if (comp_from->type == NodeType::VISIBILITY) { - if (target_directly_visible) { - IDNode *id_node_from = comp_from->owner; - for (ComponentNode *comp_node : id_node_from->components.values()) { - comp_node->affects_directly_visible |= target_directly_visible; - } - } - } - else { - comp_from->affects_directly_visible |= target_directly_visible; - } - } - } - /* Schedule parent nodes. */ - for (Relation *rel : op_node->inlinks) { - if (rel->from->type == NodeType::OPERATION) { - OperationNode *op_from = (OperationNode *)rel->from; - if ((rel->flag & RELATION_FLAG_CYCLIC) == 0) { - BLI_assert(op_from->num_links_pending > 0); - --op_from->num_links_pending; - } - if ((op_from->num_links_pending == 0) && (op_from->custom_flags & DEG_NODE_VISITED) == 0) { - BLI_stack_push(stack, &op_from); - op_from->custom_flags |= DEG_NODE_VISITED; - } - } - } - } - BLI_stack_free(stack); -} - -} // namespace - void deg_graph_build_finalize(Main *bmain, Depsgraph *graph) { - /* Make sure dependencies of visible ID data-blocks are visible. */ - deg_graph_build_flush_visibility(graph); + deg_graph_flush_visibility_flags(graph); deg_graph_remove_unused_noops(graph); /* Re-tag IDs for update if it was tagged before the relations diff --git a/source/blender/depsgraph/intern/builder/deg_builder.h b/source/blender/depsgraph/intern/builder/deg_builder.h index 950ebfb35ba..c44e5fd5f4d 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder.h +++ b/source/blender/depsgraph/intern/builder/deg_builder.h @@ -24,6 +24,8 @@ class DepsgraphBuilder { virtual bool need_pull_base_into_graph(const Base *base); + virtual bool is_object_visibility_animated(const Object *object); + virtual bool check_pchan_has_bbone(const Object *object, const bPoseChannel *pchan); virtual bool check_pchan_has_bbone_segments(const Object *object, const bPoseChannel *pchan); virtual bool check_pchan_has_bbone_segments(const Object *object, const char *bone_name); diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc index 473dda2290c..c65c4beeed6 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc @@ -107,6 +107,7 @@ #include "intern/depsgraph_tag.h" #include "intern/depsgraph_type.h" #include "intern/eval/deg_eval_copy_on_write.h" +#include "intern/eval/deg_eval_visibility.h" #include "intern/node/deg_node.h" #include "intern/node/deg_node_component.h" #include "intern/node/deg_node_id.h" @@ -179,11 +180,27 @@ IDNode *DepsgraphNodeBuilder::add_id_node(ID *id) } ComponentNode *visibility_component = id_node->add_component(NodeType::VISIBILITY); - OperationNode *visibility_operation = visibility_component->add_operation( - nullptr, OperationCode::VISIBILITY); + OperationNode *visibility_operation; + + /* Optimization: currently only objects need a special visibility evaluation. For the rest ID + * types keep the node as a NO-OP so that relations can still be routed, but without penalty + * during the graph evaluation. */ + if (id_type == ID_OB) { + visibility_operation = visibility_component->add_operation( + [id_node](::Depsgraph *depsgraph) { + deg_evaluate_object_node_visibility(depsgraph, id_node); + }, + OperationCode::VISIBILITY); + } + else { + visibility_operation = visibility_component->add_operation(nullptr, + OperationCode::VISIBILITY); + } + /* Pin the node so that it and its relations are preserved by the unused nodes/relations * deletion. This is mainly to make it easier to debug visibility. */ - visibility_operation->flag |= OperationFlag::DEPSOP_FLAG_PINNED; + visibility_operation->flag |= (OperationFlag::DEPSOP_FLAG_PINNED | + OperationFlag::DEPSOP_FLAG_AFFECTS_VISIBILITY); graph_->operations.append(visibility_operation); } return id_node; @@ -652,7 +669,7 @@ void DepsgraphNodeBuilder::build_collection(LayerCollection *from_layer_collecti IDNode *id_node; if (built_map_.checkIsBuiltAndTag(collection)) { id_node = find_id_node(&collection->id); - if (is_collection_visible && id_node->is_directly_visible == false && + if (is_collection_visible && id_node->is_visible_on_build == false && id_node->is_collection_fully_expanded == true) { /* Collection became visible, make sure nested collections and * objects are poked with the new visibility flag, since they @@ -669,7 +686,7 @@ void DepsgraphNodeBuilder::build_collection(LayerCollection *from_layer_collecti else { /* Collection itself. */ id_node = add_id_node(&collection->id); - id_node->is_directly_visible = is_collection_visible; + id_node->is_visible_on_build = is_collection_visible; build_idproperties(collection->id.properties); add_operation_node(&collection->id, NodeType::GEOMETRY, OperationCode::GEOMETRY_EVAL_DONE); @@ -716,7 +733,7 @@ void DepsgraphNodeBuilder::build_object(int base_index, build_object_flags(base_index, object, linked_state); } id_node->linked_state = max(id_node->linked_state, linked_state); - id_node->is_directly_visible |= is_visible; + id_node->is_visible_on_build |= is_visible; id_node->has_base |= (base_index != -1); /* There is no relation path which will connect current object with all the ones which come @@ -735,10 +752,10 @@ void DepsgraphNodeBuilder::build_object(int base_index, * Probably need to assign that to something non-nullptr, but then the logic here will still be * somewhat weird. */ if (scene_ != nullptr && object == scene_->camera) { - id_node->is_directly_visible = true; + id_node->is_visible_on_build = true; } else { - id_node->is_directly_visible = is_visible; + id_node->is_visible_on_build = is_visible; } id_node->has_base |= (base_index != -1); /* Various flags, flushing from bases/collections. */ diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes_view_layer.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes_view_layer.cc index 32ec94211a0..5af9e7d4fe9 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes_view_layer.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes_view_layer.cc @@ -92,14 +92,20 @@ void DepsgraphNodeBuilder::build_view_layer(Scene *scene, int base_index = 0; LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { /* object itself */ - if (need_pull_base_into_graph(base)) { - /* NOTE: We consider object visible even if it's currently - * restricted by the base/restriction flags. Otherwise its drivers - * will never be evaluated. - * - * TODO(sergey): Need to go more granular on visibility checks. */ - build_object(base_index, base->object, linked_state, true); - base_index++; + if (!need_pull_base_into_graph(base)) { + continue; + } + + /* NOTE: We consider object visible even if it's currently + * restricted by the base/restriction flags. Otherwise its drivers + * will never be evaluated. + * + * TODO(sergey): Need to go more granular on visibility checks. */ + build_object(base_index, base->object, linked_state, true); + base_index++; + + if (!graph_->has_animated_visibility) { + graph_->has_animated_visibility |= is_object_visibility_animated(base->object); } } build_layer_collections(&view_layer->layer_collections); diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc index 4fe8a626af5..f2646ebc1f1 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc @@ -810,6 +810,13 @@ void DepsgraphRelationBuilder::build_object(Object *object) /* Parameters. */ build_parameters(&object->id); + + /* Visibility. + * Evaluate visibility node after the object's base_flags has been updated to the current state + * of collections restrict and object's restrict flags. */ + const ComponentKey object_from_layer_entry_key(&object->id, NodeType::OBJECT_FROM_LAYER); + const ComponentKey visibility_key(&object->id, NodeType::VISIBILITY); + add_relation(object_from_layer_entry_key, visibility_key, "Object Visibility"); } /* NOTE: Implies that the object has base in the current view layer. */ diff --git a/source/blender/depsgraph/intern/depsgraph.cc b/source/blender/depsgraph/intern/depsgraph.cc index d460a68747d..316d0b615c6 100644 --- a/source/blender/depsgraph/intern/depsgraph.cc +++ b/source/blender/depsgraph/intern/depsgraph.cc @@ -45,7 +45,9 @@ namespace blender::deg { Depsgraph::Depsgraph(Main *bmain, Scene *scene, ViewLayer *view_layer, eEvaluationMode mode) : time_source(nullptr), + has_animated_visibility(false), need_update_relations(true), + need_update_nodes_visibility(true), need_tag_id_on_graph_visibility_update(true), need_tag_id_on_graph_visibility_time_update(false), bmain(bmain), diff --git a/source/blender/depsgraph/intern/depsgraph.h b/source/blender/depsgraph/intern/depsgraph.h index 33d97e4b8b2..2f88199384d 100644 --- a/source/blender/depsgraph/intern/depsgraph.h +++ b/source/blender/depsgraph/intern/depsgraph.h @@ -88,9 +88,15 @@ struct Depsgraph { /* Top-level time source node. */ TimeSourceNode *time_source; + /* The graph contains data-blocks whose visibility depends on evaluation (driven or animated). */ + bool has_animated_visibility; + /* Indicates whether relations needs to be updated. */ bool need_update_relations; + /* Indicates whether indirect effect of nodes on a directly visible ones needs to be updated. */ + bool need_update_nodes_visibility; + /* Indicated whether IDs in this graph are to be tagged as if they first appear visible, with * an optional tag for their animation (time) update. */ bool need_tag_id_on_graph_visibility_update; diff --git a/source/blender/depsgraph/intern/depsgraph_query_iter.cc b/source/blender/depsgraph/intern/depsgraph_query_iter.cc index bcde1839a0f..171c9875e2a 100644 --- a/source/blender/depsgraph/intern/depsgraph_query_iter.cc +++ b/source/blender/depsgraph/intern/depsgraph_query_iter.cc @@ -219,7 +219,9 @@ bool deg_iterator_objects_step(DEGObjectIterData *data) for (; data->id_node_index < data->num_id_nodes; data->id_node_index++) { deg::IDNode *id_node = deg_graph->id_nodes[data->id_node_index]; - if (!id_node->is_directly_visible) { + /* Use the build time visibility so that the ID is not appearing/disappearing throughout + * animation export. */ + if (!id_node->is_visible_on_build) { continue; } @@ -338,10 +340,13 @@ static void DEG_iterator_ids_step(BLI_Iterator *iter, deg::IDNode *id_node, bool { ID *id_cow = id_node->id_cow; - if (!id_node->is_directly_visible) { + /* Use the build time visibility so that the ID is not appearing/disappearing throughout + * animation export. */ + if (!id_node->is_visible_on_build) { iter->skip = true; return; } + if (only_updated && !(id_cow->recalc & ID_RECALC_ALL)) { /* Node-tree is considered part of the data-block. */ bNodeTree *ntree = ntreeFromID(id_cow); diff --git a/source/blender/depsgraph/intern/eval/deg_eval.cc b/source/blender/depsgraph/intern/eval/deg_eval.cc index 47f2a8ca219..426e04a5cf9 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval.cc +++ b/source/blender/depsgraph/intern/eval/deg_eval.cc @@ -37,6 +37,7 @@ #include "intern/eval/deg_eval_copy_on_write.h" #include "intern/eval/deg_eval_flush.h" #include "intern/eval/deg_eval_stats.h" +#include "intern/eval/deg_eval_visibility.h" #include "intern/node/deg_node.h" #include "intern/node/deg_node_component.h" #include "intern/node/deg_node_id.h" @@ -69,6 +70,9 @@ enum class EvaluationStage { * involved. */ COPY_ON_WRITE, + /* Evaluate actual ID nodes visiblity based on the current state of animation and drivers. */ + DYNAMIC_VISIBILITY, + /* Threaded evaluation of all possible operations. */ THREADED_EVALUATION, @@ -83,7 +87,8 @@ struct DepsgraphEvalState { Depsgraph *graph; bool do_stats; EvaluationStage stage; - bool need_single_thread_pass; + bool need_update_pending_parents = true; + bool need_single_thread_pass = false; }; void evaluate_node(const DepsgraphEvalState *state, OperationNode *operation_node) @@ -122,24 +127,31 @@ void deg_task_run_func(TaskPool *pool, void *taskdata) schedule_children(state, operation_node, schedule_node_to_pool, pool); } -bool check_operation_node_visible(OperationNode *op_node) +bool check_operation_node_visible(const DepsgraphEvalState *state, OperationNode *op_node) { const ComponentNode *comp_node = op_node->owner; - /* Special exception, copy on write component is to be always evaluated, - * to keep copied "database" in a consistent state. */ + /* Special case for copy on write component: it is to be always evaluated, to keep copied + * "database" in a consistent state. */ if (comp_node->type == NodeType::COPY_ON_WRITE) { return true; } - return comp_node->affects_directly_visible; + + /* Special case for dynamic visiblity pass: the actual visibility is not yet known, so limit to + * only operations which affects visibility. */ + if (state->stage == EvaluationStage::DYNAMIC_VISIBILITY) { + return op_node->flag & OperationFlag::DEPSOP_FLAG_AFFECTS_VISIBILITY; + } + + return comp_node->affects_visible_id; } -void calculate_pending_parents_for_node(OperationNode *node) +void calculate_pending_parents_for_node(const DepsgraphEvalState *state, OperationNode *node) { /* Update counters, applies for both visible and invisible IDs. */ node->num_links_pending = 0; node->scheduled = false; /* Invisible IDs requires no pending operations. */ - if (!check_operation_node_visible(node)) { + if (!check_operation_node_visible(state, node)) { return; } /* No need to bother with anything if node is not tagged for update. */ @@ -153,7 +165,7 @@ void calculate_pending_parents_for_node(OperationNode *node) * calculation, but how is it possible that visible object depends * on an invisible? This is something what is prohibited after * deg_graph_build_flush_layers(). */ - if (!check_operation_node_visible(from)) { + if (!check_operation_node_visible(state, from)) { continue; } /* No need to wait for operation which is up to date. */ @@ -165,20 +177,24 @@ void calculate_pending_parents_for_node(OperationNode *node) } } -void calculate_pending_parents(Depsgraph *graph) +void calculate_pending_parents_if_needed(DepsgraphEvalState *state) { - for (OperationNode *node : graph->operations) { - calculate_pending_parents_for_node(node); + if (!state->need_update_pending_parents) { + return; } + + for (OperationNode *node : state->graph->operations) { + calculate_pending_parents_for_node(state, node); + } + + state->need_update_pending_parents = false; } void initialize_execution(DepsgraphEvalState *state, Depsgraph *graph) { - const bool do_stats = state->do_stats; - calculate_pending_parents(graph); /* Clear tags and other things which needs to be clear. */ - for (OperationNode *node : graph->operations) { - if (do_stats) { + if (state->do_stats) { + for (OperationNode *node : graph->operations) { node->stats.reset_current(); } } @@ -203,11 +219,10 @@ bool need_evaluate_operation_at_stage(DepsgraphEvalState *state, case EvaluationStage::COPY_ON_WRITE: return (component_node->type == NodeType::COPY_ON_WRITE); + case EvaluationStage::DYNAMIC_VISIBILITY: + return operation_node->flag & OperationFlag::DEPSOP_FLAG_AFFECTS_VISIBILITY; + case EvaluationStage::THREADED_EVALUATION: - /* Sanity check: copy-on-write node should be evaluated already. This will be indicated by - * scheduled flag (we assume that scheduled operations have been actually handled by previous - * stage). */ - BLI_assert(operation_node->scheduled || component_node->type != NodeType::COPY_ON_WRITE); if (is_metaball_object_operation(operation_node)) { state->need_single_thread_pass = true; return false; @@ -233,7 +248,7 @@ void schedule_node(DepsgraphEvalState *state, ScheduleFunctionArgs... schedule_function_args) { /* No need to schedule nodes of invisible ID. */ - if (!check_operation_node_visible(node)) { + if (!check_operation_node_visible(state, node)) { return; } /* No need to schedule operations which are not tagged for update, they are @@ -317,6 +332,8 @@ void evaluate_graph_threaded_stage(DepsgraphEvalState *state, { state->stage = stage; + calculate_pending_parents_if_needed(state); + schedule_graph(state, schedule_node_to_pool, task_pool); BLI_task_pool_work_and_wait(task_pool); } @@ -328,6 +345,8 @@ void evaluate_graph_single_threaded_if_needed(DepsgraphEvalState *state) return; } + BLI_assert(!state->need_update_pending_parents); + state->stage = EvaluationStage::SINGLE_THREADED_WORKAROUND; GSQueue *evaluation_queue = BLI_gsqueue_new(sizeof(OperationNode *)); @@ -392,7 +411,6 @@ void deg_evaluate_on_refresh(Depsgraph *graph) DepsgraphEvalState state; state.graph = graph; state.do_stats = graph->debug.do_time_debug(); - state.need_single_thread_pass = false; /* Prepare all nodes for evaluation. */ initialize_execution(&state, graph); @@ -403,6 +421,10 @@ void deg_evaluate_on_refresh(Depsgraph *graph) * that if a dependency graph has a cycle evaluation functions will always "see" valid expanded * datablock. It might not be evaluated yet, but at least the datablock will be valid. * + * - If there is potentially dynamically changing visibility in the graph update the actual + * nodes visibilities, so that actual heavy data evaluation can benefit from knowledge that + * something heavy is not currently visible. + * * - Multi-threaded evaluation of all possible nodes. * Certain operations (and their subtrees) could be ignored. For example, meta-balls are not * safe from threading point of view, so the threaded evaluation will stop at the metaball @@ -413,6 +435,24 @@ void deg_evaluate_on_refresh(Depsgraph *graph) TaskPool *task_pool = deg_evaluate_task_pool_create(&state); evaluate_graph_threaded_stage(&state, task_pool, EvaluationStage::COPY_ON_WRITE); + + if (graph->has_animated_visibility) { + /* Update pending parents including only the ones which are affecting operations which are + * affecting visibility. */ + state.need_update_pending_parents = true; + + evaluate_graph_threaded_stage(&state, task_pool, EvaluationStage::DYNAMIC_VISIBILITY); + + deg_graph_flush_visibility_flags_if_needed(graph); + + /* Update parents to an updated visibility and evaluation stage. + * + * Need to do it regardless of whether visibility is actually changed or not: current state of + * the pending parents are all zeroes because it was previously calculated for only visibility + * related nodes and those are fully evaluated by now. */ + state.need_update_pending_parents = true; + } + evaluate_graph_threaded_stage(&state, task_pool, EvaluationStage::THREADED_EVALUATION); BLI_task_pool_free(task_pool); diff --git a/source/blender/depsgraph/intern/eval/deg_eval_visibility.cc b/source/blender/depsgraph/intern/eval/deg_eval_visibility.cc new file mode 100644 index 00000000000..8bf26e45e2a --- /dev/null +++ b/source/blender/depsgraph/intern/eval/deg_eval_visibility.cc @@ -0,0 +1,147 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup depsgraph + */ + +#include "intern/eval/deg_eval_visibility.h" + +#include "DNA_layer_types.h" +#include "DNA_object_types.h" + +#include "BLI_assert.h" +#include "BLI_stack.h" + +#include "DEG_depsgraph.h" + +#include "intern/depsgraph.h" +#include "intern/depsgraph_relation.h" +#include "intern/node/deg_node.h" +#include "intern/node/deg_node_component.h" +#include "intern/node/deg_node_id.h" +#include "intern/node/deg_node_operation.h" + +namespace blender::deg { + +void deg_evaluate_object_node_visibility(::Depsgraph *depsgraph, IDNode *id_node) +{ + BLI_assert(GS(id_node->id_cow->name) == ID_OB); + + Depsgraph *graph = reinterpret_cast(depsgraph); + const Object *object = reinterpret_cast(id_node->id_cow); + + DEG_debug_print_eval(depsgraph, __func__, object->id.name, &object->id); + + const int required_flags = (graph->mode == DAG_EVAL_VIEWPORT) ? BASE_ENABLED_VIEWPORT : + BASE_ENABLED_RENDER; + + const bool is_enabled = object->base_flag & required_flags; + + if (id_node->is_enabled_on_eval != is_enabled) { + id_node->is_enabled_on_eval = is_enabled; + + /* Tag dependency graph for changed visibility, so that it is updated on all dependencies prior + * to a pass of an actual evaluation. */ + graph->need_update_nodes_visibility = true; + } +} + +void deg_graph_flush_visibility_flags(Depsgraph *graph) +{ + enum { + DEG_NODE_VISITED = (1 << 0), + }; + + for (IDNode *id_node : graph->id_nodes) { + for (ComponentNode *comp_node : id_node->components.values()) { + comp_node->possibly_affects_visible_id = id_node->is_visible_on_build; + comp_node->affects_visible_id = id_node->is_visible_on_build && id_node->is_enabled_on_eval; + } + } + + BLI_Stack *stack = BLI_stack_new(sizeof(OperationNode *), "DEG flush layers stack"); + + for (OperationNode *op_node : graph->operations) { + op_node->custom_flags = 0; + op_node->num_links_pending = 0; + for (Relation *rel : op_node->outlinks) { + if ((rel->from->type == NodeType::OPERATION) && (rel->flag & RELATION_FLAG_CYCLIC) == 0) { + ++op_node->num_links_pending; + } + } + if (op_node->num_links_pending == 0) { + BLI_stack_push(stack, &op_node); + op_node->custom_flags |= DEG_NODE_VISITED; + } + } + + while (!BLI_stack_is_empty(stack)) { + OperationNode *op_node; + BLI_stack_pop(stack, &op_node); + + /* Flush flags to parents. */ + for (Relation *rel : op_node->inlinks) { + if (rel->from->type == NodeType::OPERATION) { + const OperationNode *op_to = reinterpret_cast(rel->to); + const ComponentNode *comp_to = op_to->owner; + OperationNode *op_from = reinterpret_cast(rel->from); + ComponentNode *comp_from = op_from->owner; + + const bool target_possibly_affects_visible_id = comp_to->possibly_affects_visible_id; + const bool target_affects_visible_id = comp_to->affects_visible_id; + + op_from->flag |= (op_to->flag & OperationFlag::DEPSOP_FLAG_AFFECTS_VISIBILITY); + + /* Visibility component forces all components of the current ID to be considered as + * affecting directly visible. */ + if (comp_from->type == NodeType::VISIBILITY) { + const IDNode *id_node_from = comp_from->owner; + if (target_possibly_affects_visible_id) { + for (ComponentNode *comp_node : id_node_from->components.values()) { + comp_node->possibly_affects_visible_id |= target_possibly_affects_visible_id; + } + } + if (target_affects_visible_id) { + for (ComponentNode *comp_node : id_node_from->components.values()) { + comp_node->affects_visible_id |= target_affects_visible_id; + } + } + } + else { + comp_from->possibly_affects_visible_id |= target_possibly_affects_visible_id; + comp_from->affects_visible_id |= target_affects_visible_id; + } + } + } + + /* Schedule parent nodes. */ + for (Relation *rel : op_node->inlinks) { + if (rel->from->type == NodeType::OPERATION) { + OperationNode *op_from = (OperationNode *)rel->from; + if ((rel->flag & RELATION_FLAG_CYCLIC) == 0) { + BLI_assert(op_from->num_links_pending > 0); + --op_from->num_links_pending; + } + if ((op_from->num_links_pending == 0) && (op_from->custom_flags & DEG_NODE_VISITED) == 0) { + BLI_stack_push(stack, &op_from); + op_from->custom_flags |= DEG_NODE_VISITED; + } + } + } + } + BLI_stack_free(stack); + + graph->need_update_nodes_visibility = false; +} + +void deg_graph_flush_visibility_flags_if_needed(Depsgraph *graph) +{ + if (!graph->need_update_nodes_visibility) { + return; + } + + deg_graph_flush_visibility_flags(graph); +} + +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/eval/deg_eval_visibility.h b/source/blender/depsgraph/intern/eval/deg_eval_visibility.h new file mode 100644 index 00000000000..9e9db8ab34a --- /dev/null +++ b/source/blender/depsgraph/intern/eval/deg_eval_visibility.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup depsgraph + */ + +#pragma once + +struct Depsgraph; + +namespace blender::deg { + +struct Depsgraph; +struct IDNode; + +/* Evaluate actual node visibility flags based on the current state of object's visibility + * restriction flags. */ +void deg_evaluate_object_node_visibility(::Depsgraph *depsgraph, IDNode *id_node); + +/* Flush both static and dynamic visibility flags from leaves up to the roots, making it possible + * to know whether a node has affect on something (potentially) visible. */ +void deg_graph_flush_visibility_flags(Depsgraph *graph); +void deg_graph_flush_visibility_flags_if_needed(Depsgraph *graph); + +} // namespace blender::deg diff --git a/source/blender/depsgraph/intern/node/deg_node_component.cc b/source/blender/depsgraph/intern/node/deg_node_component.cc index cfcbec46569..32942f45a4a 100644 --- a/source/blender/depsgraph/intern/node/deg_node_component.cc +++ b/source/blender/depsgraph/intern/node/deg_node_component.cc @@ -67,7 +67,10 @@ uint64_t ComponentNode::OperationIDKey::hash() const } ComponentNode::ComponentNode() - : entry_operation(nullptr), exit_operation(nullptr), affects_directly_visible(false) + : entry_operation(nullptr), + exit_operation(nullptr), + possibly_affects_visible_id(false), + affects_visible_id(false) { operations_map = new Map(); } @@ -90,7 +93,7 @@ string ComponentNode::identifier() const const string idname = this->owner->name; const string typebuf = "" + to_string(static_cast(type)) + ")"; return typebuf + name + " : " + idname + - "( affects_directly_visible: " + (affects_directly_visible ? "true" : "false") + ")"; + "( affects_visible_id: " + (affects_visible_id ? "true" : "false") + ")"; } OperationNode *ComponentNode::find_operation(OperationIDKey key) const diff --git a/source/blender/depsgraph/intern/node/deg_node_component.h b/source/blender/depsgraph/intern/node/deg_node_component.h index 375a26ab49b..cbc86ccd78d 100644 --- a/source/blender/depsgraph/intern/node/deg_node_component.h +++ b/source/blender/depsgraph/intern/node/deg_node_component.h @@ -137,9 +137,17 @@ struct ComponentNode : public Node { return true; } - /* Denotes whether this component affects (possibly indirectly) on a - * directly visible object. */ - bool affects_directly_visible; + /* The component has (possibly indirect) effect on a data-block whose node has + * is_visible_on_build set to true. + * + * This field is ensured to be up-to-date prior to `IDNode::finalize_build()`. */ + bool possibly_affects_visible_id; + + /* Denotes whether this component actually affects (possibly indirectly) on a directly visible + * object. Includes possibly run-time visibility update of ID nodes. + * + * NOTE: Is only reliable after `deg_graph_flush_visibility()`. */ + bool affects_visible_id; }; /* ---------------------------------------- */ @@ -197,7 +205,7 @@ DEG_COMPONENT_NODE_DECLARE_GENERIC(Dupli); DEG_COMPONENT_NODE_DECLARE_GENERIC(Audio); DEG_COMPONENT_NODE_DECLARE_GENERIC(Armature); DEG_COMPONENT_NODE_DECLARE_GENERIC(GenericDatablock); -DEG_COMPONENT_NODE_DECLARE_NO_COW(Visibility); +DEG_COMPONENT_NODE_DECLARE_NO_COW_TAG_ON_UPDATE(Visibility); DEG_COMPONENT_NODE_DECLARE_GENERIC(Simulation); DEG_COMPONENT_NODE_DECLARE_GENERIC(NTreeOutput); @@ -214,7 +222,7 @@ struct SynchronizationComponentNode : public ComponentNode { * The design is such that the synchronization is supposed to happen whenever any part of the * ID changed/evaluated. Here we mark the component as "visible" so that genetic recalc flag * flushing and scheduling will handle the component in a generic manner. */ - affects_directly_visible = true; + affects_visible_id = true; } DEG_COMPONENT_NODE_DECLARE; diff --git a/source/blender/depsgraph/intern/node/deg_node_id.cc b/source/blender/depsgraph/intern/node/deg_node_id.cc index 99224501e98..735d606ac9e 100644 --- a/source/blender/depsgraph/intern/node/deg_node_id.cc +++ b/source/blender/depsgraph/intern/node/deg_node_id.cc @@ -69,7 +69,8 @@ void IDNode::init(const ID *id, const char *UNUSED(subdata)) customdata_masks = DEGCustomDataMeshMasks(); previous_customdata_masks = DEGCustomDataMeshMasks(); linked_state = DEG_ID_LINKED_INDIRECTLY; - is_directly_visible = true; + is_visible_on_build = true; + is_enabled_on_eval = true; is_collection_fully_expanded = false; has_base = false; is_user_modified = false; @@ -138,8 +139,8 @@ string IDNode::identifier() const BLI_snprintf(orig_ptr, sizeof(orig_ptr), "%p", id_orig); BLI_snprintf(cow_ptr, sizeof(cow_ptr), "%p", id_cow); return string(nodeTypeAsString(type)) + " : " + name + " (orig: " + orig_ptr + - ", eval: " + cow_ptr + ", is_directly_visible " + - (is_directly_visible ? "true" : "false") + ")"; + ", eval: " + cow_ptr + ", is_visible_on_build " + + (is_visible_on_build ? "true" : "false") + ")"; } ComponentNode *IDNode::find_component(NodeType type, const char *name) const @@ -188,7 +189,7 @@ IDComponentsMask IDNode::get_visible_components_mask() const { IDComponentsMask result = 0; for (ComponentNode *comp_node : components.values()) { - if (comp_node->affects_directly_visible) { + if (comp_node->possibly_affects_visible_id) { const int component_type_as_int = static_cast(comp_node->type); BLI_assert(component_type_as_int < 64); result |= (1ULL << component_type_as_int); diff --git a/source/blender/depsgraph/intern/node/deg_node_id.h b/source/blender/depsgraph/intern/node/deg_node_id.h index 406ca828049..7f0a656cb8d 100644 --- a/source/blender/depsgraph/intern/node/deg_node_id.h +++ b/source/blender/depsgraph/intern/node/deg_node_id.h @@ -91,8 +91,21 @@ struct IDNode : public Node { eDepsNode_LinkedState_Type linked_state; - /* Indicates the data-block is visible in the evaluated scene. */ - bool is_directly_visible; + /* Indicates the data-block is to be considered visible in the evaluated scene. + * + * This flag is set during dependency graph build where check for an actual visibility might not + * be available yet due to driven or animated restriction flags. So it is more of an intent or, + * in other words, plausibility of the data-block to be visible. */ + bool is_visible_on_build; + + /* Evaluated state of whether evaluation considered this data-block "enabled". + * + * For objects this is derived from the base restriction flags, which might be animated or + * driven. It is set to `BASE_ENABLED_` (depending on the graph mode) after + * the object's flags from layer were evaluated. + * + * For other data-types is currently always true. */ + bool is_enabled_on_eval; /* For the collection type of ID, denotes whether collection was fully * recursed into. */ diff --git a/source/blender/depsgraph/intern/node/deg_node_operation.h b/source/blender/depsgraph/intern/node/deg_node_operation.h index fd772fbce9d..656b27550f6 100644 --- a/source/blender/depsgraph/intern/node/deg_node_operation.h +++ b/source/blender/depsgraph/intern/node/deg_node_operation.h @@ -214,6 +214,9 @@ enum OperationFlag { * and not for connecting to an operation. */ DEPSOP_FLAG_PINNED = (1 << 3), + /* The operation directly or indirectly affects ID node visibility. */ + DEPSOP_FLAG_AFFECTS_VISIBILITY = (1 << 4), + /* Set of flags which gets flushed along the relations. */ DEPSOP_FLAG_FLUSH = (DEPSOP_FLAG_USER_MODIFIED), }; -- cgit v1.2.3 From ee3facd0879d48895febdd66661567c2af1b9e33 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 20 Jul 2022 18:01:58 +0200 Subject: LibOverride: support 'make override' for all selected items. This commit allows to select several data-blocks in the outliner and create overrides from all of them, not only the active one. It properly creates a single hierarchy when several IDs from a same hierarchy root data are selected. Reviewed By: Severin Differential Revision: https://developer.blender.org/D15497 --- .../editors/space_outliner/outliner_tools.cc | 408 +++++++++++++-------- 1 file changed, 255 insertions(+), 153 deletions(-) diff --git a/source/blender/editors/space_outliner/outliner_tools.cc b/source/blender/editors/space_outliner/outliner_tools.cc index 9d52103a266..e1aa695a262 100644 --- a/source/blender/editors/space_outliner/outliner_tools.cc +++ b/source/blender/editors/space_outliner/outliner_tools.cc @@ -31,8 +31,11 @@ #include "BLI_blenlib.h" #include "BLI_ghash.h" +#include "BLI_linklist.h" +#include "BLI_map.hh" #include "BLI_set.hh" #include "BLI_utildefines.h" +#include "BLI_vector.hh" #include "BKE_anim_data.h" #include "BKE_animsys.h" @@ -90,7 +93,9 @@ static CLG_LogRef LOG = {"ed.outliner.tools"}; using namespace blender::ed::outliner; +using blender::Map; using blender::Set; +using blender::Vector; /* -------------------------------------------------------------------- */ /** \name ID/Library/Data Set/Un-link Utilities @@ -777,6 +782,25 @@ static void id_local_fn(bContext *C, } } +struct OutlinerLiboverrideDataIDRoot { + /** The linked ID that was selected for override. */ + ID *id_root_reference; + + /** The root of the override hierarchy to which the override of `id_root` belongs, once + * known/created. */ + ID *id_hierarchy_root_override; + + /** The ID that was detected as being a good candidate as instanciation hint for newly overridden + * objects, may be null. + * + * \note Typically currently only used when the root ID to override is a collection instanced by + * an emtpy object. */ + ID *id_instance_hint; + + /** If this override comes from an instancing object (which would be `id_instance_hint` then). */ + bool is_override_instancing_object; +}; + struct OutlinerLibOverrideData { bool do_hierarchy; @@ -789,21 +813,44 @@ struct OutlinerLibOverrideData { * solving broken overrides while not losing *all* of your overrides. */ bool do_resync_hierarchy_enforce; - /** The override hierarchy root, when known/created. */ - ID *id_hierarchy_root_override; - - /** A hash of the selected tree elements' ID 'uuid'. Used to clear 'system override' flags on + /** A set of the selected tree elements' ID 'uuid'. Used to clear 'system override' flags on * their newly-created liboverrides in post-process step of override hierarchy creation. */ Set selected_id_uid; + + /** A mapping from the found hierarchy roots to a linked list of IDs to override for each of + * these roots. + * + * \note the key may be either linked (in which case it will be replaced by the newly created + * override), or an actual already existing override. */ + Map> id_hierarchy_roots; + + /** All 'session_uuid' of all hierarchy root IDs used or created by the operation. */ + Set id_hierarchy_roots_uid; + + void id_root_add(ID *id_hierarchy_root_reference, + ID *id_root_reference, + ID *id_instance_hint, + const bool is_override_instancing_object) + { + OutlinerLiboverrideDataIDRoot id_root_data; + id_root_data.id_root_reference = id_root_reference; + id_root_data.id_hierarchy_root_override = nullptr; + id_root_data.id_instance_hint = id_instance_hint; + id_root_data.is_override_instancing_object = is_override_instancing_object; + + Vector &value = id_hierarchy_roots.lookup_or_add_default( + id_hierarchy_root_reference); + value.append(id_root_data); + } }; /* Store 'UUID' of IDs of selected elements in the Outliner tree, before generating the override * hierarchy. */ -static void id_override_library_create_hierarchy_pre_process_fn(bContext *UNUSED(C), - ReportList *UNUSED(reports), +static void id_override_library_create_hierarchy_pre_process_fn(bContext *C, + ReportList *reports, Scene *UNUSED(scene), - TreeElement *UNUSED(te), - TreeStoreElem *UNUSED(tsep), + TreeElement *te, + TreeStoreElem *tsep, TreeStoreElem *tselem, void *user_data) { @@ -829,28 +876,6 @@ static void id_override_library_create_hierarchy_pre_process_fn(bContext *UNUSED } FOREACH_COLLECTION_OBJECT_RECURSIVE_END; } -} - -static void id_override_library_create_fn(bContext *C, - ReportList *reports, - Scene *scene, - TreeElement *te, - TreeStoreElem *tsep, - TreeStoreElem *tselem, - void *user_data) -{ - BLI_assert(TSE_IS_REAL_ID(tselem)); - - /* We can only safely apply this operation on one item at a time, so only do it on the active - * one. */ - if ((tselem->flag & TSE_ACTIVE) == 0) { - return; - } - - ID *id_root_reference = tselem->id; - OutlinerLibOverrideData *data = reinterpret_cast(user_data); - const bool do_hierarchy = data->do_hierarchy; - bool success = false; ID *id_instance_hint = nullptr; bool is_override_instancing_object = false; @@ -866,172 +891,239 @@ static void id_override_library_create_fn(bContext *C, } } - if (ID_IS_OVERRIDABLE_LIBRARY(id_root_reference) || - (ID_IS_LINKED(id_root_reference) && do_hierarchy)) { - Main *bmain = CTX_data_main(C); + if (!ID_IS_OVERRIDABLE_LIBRARY(id_root_reference) && + !(ID_IS_LINKED(id_root_reference) && do_hierarchy)) { + return; + } - id_root_reference->tag |= LIB_TAG_DOIT; + Main *bmain = CTX_data_main(C); - /* For now, remap all local usages of linked ID to local override one here. */ - ID *id_iter; - FOREACH_MAIN_ID_BEGIN (bmain, id_iter) { - if (ID_IS_LINKED(id_iter) || ID_IS_OVERRIDE_LIBRARY(id_iter)) { - id_iter->tag &= ~LIB_TAG_DOIT; - } - else { - id_iter->tag |= LIB_TAG_DOIT; + if (do_hierarchy) { + /* Tag all linked parents in tree hierarchy to be also overridden. */ + ID *id_hierarchy_root_reference = id_root_reference; + while ((te = te->parent) != nullptr) { + if (!TSE_IS_REAL_ID(te->store_elem)) { + continue; } - } - FOREACH_MAIN_ID_END; - if (do_hierarchy) { - /* Tag all linked parents in tree hierarchy to be also overridden. */ - ID *id_hierarchy_root_reference = id_root_reference; - while ((te = te->parent) != nullptr) { - if (!TSE_IS_REAL_ID(te->store_elem)) { + /* Tentative hierarchy root. */ + ID *id_current_hierarchy_root = te->store_elem->id; + + /* If the parent ID is from a different library than the reference root one, we are done + * with upwards tree processing in any case. */ + if (id_current_hierarchy_root->lib != id_root_reference->lib) { + if (ID_IS_OVERRIDE_LIBRARY_VIRTUAL(id_current_hierarchy_root)) { + /* Virtual overrides (i.e. embedded IDs), we can simply keep processing their parent to + * get an actual real override. */ continue; } - /* Tentative hierarchy root. */ - ID *id_current_hierarchy_root = te->store_elem->id; - - /* If the parent ID is from a different library than the reference root one, we are done - * with upwards tree processing in any case. */ - if (id_current_hierarchy_root->lib != id_root_reference->lib) { - if (ID_IS_OVERRIDE_LIBRARY_VIRTUAL(id_current_hierarchy_root)) { - /* Virtual overrides (i.e. embedded IDs), we can simply keep processing their parent to - * get an actual real override. */ - continue; - } - - /* If the parent ID is already an override, and is valid (i.e. local override), we can - * access its hierarchy root directly. */ - if (!ID_IS_LINKED(id_current_hierarchy_root) && - ID_IS_OVERRIDE_LIBRARY_REAL(id_current_hierarchy_root) && - id_current_hierarchy_root->override_library->reference->lib == - id_root_reference->lib) { - id_hierarchy_root_reference = - id_current_hierarchy_root->override_library->hierarchy_root; - BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_reference)); - break; - } - - if (ID_IS_LINKED(id_current_hierarchy_root)) { - /* No local 'anchor' was found for the hierarchy to override, do not proceed, as this - * would most likely generate invisible/confusing/hard to use and manage overrides. */ - BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); - BKE_reportf(reports, - RPT_WARNING, - "Invalid anchor ('%s') found, needed to create library override from " - "data-block '%s'", - id_current_hierarchy_root->name, - id_root_reference->name); - return; - } - - /* In all other cases, `id_current_hierarchy_root` cannot be a valid hierarchy root, so - * current `id_hierarchy_root_reference` is our best candidate. */ - + /* If the parent ID is already an override, and is valid (i.e. local override), we can + * access its hierarchy root directly. */ + if (!ID_IS_LINKED(id_current_hierarchy_root) && + ID_IS_OVERRIDE_LIBRARY_REAL(id_current_hierarchy_root) && + id_current_hierarchy_root->override_library->reference->lib == + id_root_reference->lib) { + id_hierarchy_root_reference = + id_current_hierarchy_root->override_library->hierarchy_root; + BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_reference)); break; } - /* If some element in the tree needs to be overridden, but its ID is not overridable, - * abort. */ - if (!ID_IS_OVERRIDABLE_LIBRARY_HIERARCHY(id_current_hierarchy_root)) { + if (ID_IS_LINKED(id_current_hierarchy_root)) { + /* No local 'anchor' was found for the hierarchy to override, do not proceed, as this + * would most likely generate invisible/confusing/hard to use and manage overrides. */ BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); BKE_reportf(reports, RPT_WARNING, - "Could not create library override from data-block '%s', one of its parents " - "is not overridable ('%s')", - id_root_reference->name, - id_current_hierarchy_root->name); + "Invalid anchor ('%s') found, needed to create library override from " + "data-block '%s'", + id_current_hierarchy_root->name, + id_root_reference->name); return; } - id_current_hierarchy_root->tag |= LIB_TAG_DOIT; - id_hierarchy_root_reference = id_current_hierarchy_root; + + /* In all other cases, `id_current_hierarchy_root` cannot be a valid hierarchy root, so + * current `id_hierarchy_root_reference` is our best candidate. */ + + break; } - /* That case can happen when linked data is a complex mix involving several libraries and/or - * linked overrides. E.g. a mix of overrides from one library, and indirectly linked data - * from another library. Do not try to support such cases for now. */ - if (!((id_hierarchy_root_reference->lib == id_root_reference->lib) || - (!ID_IS_LINKED(id_hierarchy_root_reference) && - ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_reference) && - id_hierarchy_root_reference->override_library->reference->lib == - id_root_reference->lib))) { + /* If some element in the tree needs to be overridden, but its ID is not overridable, + * abort. */ + if (!ID_IS_OVERRIDABLE_LIBRARY_HIERARCHY(id_current_hierarchy_root)) { BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); BKE_reportf(reports, RPT_WARNING, - "Invalid hierarchy root ('%s') found, needed to create library override from " - "data-block '%s'", - id_hierarchy_root_reference->name, - id_root_reference->name); + "Could not create library override from data-block '%s', one of its parents " + "is not overridable ('%s')", + id_root_reference->name, + id_current_hierarchy_root->name); return; } + id_current_hierarchy_root->tag |= LIB_TAG_DOIT; + id_hierarchy_root_reference = id_current_hierarchy_root; + } + + /* That case can happen when linked data is a complex mix involving several libraries and/or + * linked overrides. E.g. a mix of overrides from one library, and indirectly linked data + * from another library. Do not try to support such cases for now. */ + if (!((id_hierarchy_root_reference->lib == id_root_reference->lib) || + (!ID_IS_LINKED(id_hierarchy_root_reference) && + ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_reference) && + id_hierarchy_root_reference->override_library->reference->lib == + id_root_reference->lib))) { + BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); + BKE_reportf(reports, + RPT_WARNING, + "Invalid hierarchy root ('%s') found, needed to create library override from " + "data-block '%s'", + id_hierarchy_root_reference->name, + id_root_reference->name); + return; + } + + data->id_root_add(id_hierarchy_root_reference, + id_root_reference, + id_instance_hint, + is_override_instancing_object); + } + else if (ID_IS_OVERRIDABLE_LIBRARY(id_root_reference)) { + data->id_root_add( + id_root_reference, id_root_reference, id_instance_hint, is_override_instancing_object); + } +} + +static void id_override_library_create_hierarchy( + Main &bmain, + Scene *scene, + ViewLayer *view_layer, + OutlinerLibOverrideData &data, + ID *id_hierarchy_root_reference, + Vector &data_idroots, + bool &r_aggregated_success) +{ + BLI_assert(ID_IS_LINKED(id_hierarchy_root_reference) || + ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_reference)); + + const bool do_hierarchy = data.do_hierarchy; + + /* NOTE: This process is not the most efficient, but allows to re-use existing code. + * If this becomes a bottle-neck at some point, we need to implement a new + * `BKE_lib_override_library_hierarchy_create()` function able to process several roots inside of + * a same hierarchy in a single call. */ + for (OutlinerLiboverrideDataIDRoot &data_idroot : data_idroots) { + /* For now, remap all local usages of linked ID to local override one here. */ + ID *id_iter; + FOREACH_MAIN_ID_BEGIN (&bmain, id_iter) { + if (ID_IS_LINKED(id_iter) || ID_IS_OVERRIDE_LIBRARY(id_iter)) { + id_iter->tag &= ~LIB_TAG_DOIT; + } + else { + id_iter->tag |= LIB_TAG_DOIT; + } + } + FOREACH_MAIN_ID_END; + bool success; + if (do_hierarchy) { ID *id_root_override = nullptr; - success = BKE_lib_override_library_create(bmain, - CTX_data_scene(C), - CTX_data_view_layer(C), + success = BKE_lib_override_library_create(&bmain, + scene, + view_layer, nullptr, - id_root_reference, + data_idroot.id_root_reference, id_hierarchy_root_reference, - id_instance_hint, + data_idroot.id_instance_hint, &id_root_override, - data->do_fully_editable); - - BLI_assert(id_root_override != nullptr); - BLI_assert(!ID_IS_LINKED(id_root_override)); - BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root_override)); - if (ID_IS_LINKED(id_hierarchy_root_reference)) { - BLI_assert( - id_root_override->override_library->hierarchy_root->override_library->reference == - id_hierarchy_root_reference); - data->id_hierarchy_root_override = id_root_override->override_library->hierarchy_root; - } - else { - BLI_assert(id_root_override->override_library->hierarchy_root == - id_hierarchy_root_reference); - data->id_hierarchy_root_override = id_root_override->override_library->hierarchy_root; + data.do_fully_editable); + + if (success) { + BLI_assert(id_root_override != nullptr); + BLI_assert(!ID_IS_LINKED(id_root_override)); + BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root_override)); + + ID *id_hierarchy_root_override = id_root_override->override_library->hierarchy_root; + BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_override)); + if (ID_IS_LINKED(id_hierarchy_root_reference)) { + BLI_assert(id_hierarchy_root_override->override_library->reference == + id_hierarchy_root_reference); + /* If the hierarchy root reference was a linked data, after the first iteration there is + * now a matching override, which shall be used for all further partial overrides with + * this same hierarchy. */ + id_hierarchy_root_reference = id_hierarchy_root_override; + } + else { + BLI_assert(id_hierarchy_root_override == id_hierarchy_root_reference); + } + data_idroot.id_hierarchy_root_override = id_hierarchy_root_override; + data.id_hierarchy_roots_uid.add(id_hierarchy_root_override->session_uuid); } } - else if (ID_IS_OVERRIDABLE_LIBRARY(id_root_reference)) { - success = BKE_lib_override_library_create_from_id(bmain, id_root_reference, true) != nullptr; + else if (ID_IS_OVERRIDABLE_LIBRARY(data_idroot.id_root_reference)) { + ID *id_root_override = BKE_lib_override_library_create_from_id( + &bmain, data_idroot.id_root_reference, true); + success = id_root_override != nullptr; + if (success) { + BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root_override)); + id_root_override->override_library->flag &= ~IDOVERRIDE_LIBRARY_FLAG_SYSTEM_DEFINED; + } /* Cleanup. */ - BKE_main_id_newptr_and_tag_clear(bmain); - BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); + BKE_main_id_newptr_and_tag_clear(&bmain); + BKE_main_id_tag_all(&bmain, LIB_TAG_DOIT, false); } /* Remove the instance empty from this scene, the items now have an overridden collection * instead. */ - if (success && is_override_instancing_object) { - ED_object_base_free_and_unlink(bmain, scene, (Object *)id_instance_hint); + if (success && data_idroot.is_override_instancing_object) { + BLI_assert(GS(data_idroot.id_instance_hint) == ID_OB); + ED_object_base_free_and_unlink( + &bmain, scene, reinterpret_cast(data_idroot.id_instance_hint)); } - } - if (!success) { - BKE_reportf(reports, - RPT_WARNING, - "Could not create library override from data-block '%s'", - id_root_reference->name); + + r_aggregated_success = r_aggregated_success && success; } } /* Clear system override flag from newly created overrides which linked reference were previously * selected in the Outliner tree. */ -static void id_override_library_create_hierarchy_post_process(bContext *C, - OutlinerLibOverrideData *data) +static void id_override_library_create_hierarchy_process(bContext *C, + ReportList *reports, + OutlinerLibOverrideData &data) { Main *bmain = CTX_data_main(C); - ID *id_hierarchy_root_override = data->id_hierarchy_root_override; + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + const bool do_hierarchy = data.do_hierarchy; + + bool success = true; + for (auto &&[id_hierarchy_root_reference, data_idroots] : data.id_hierarchy_roots.items()) { + id_override_library_create_hierarchy( + *bmain, scene, view_layer, data, id_hierarchy_root_reference, data_idroots, success); + } + + if (!success) { + BKE_reportf(reports, + RPT_WARNING, + "Could not create library override from one or more of the selected data-blocks"); + } + + if (!do_hierarchy) { + return; + } ID *id_iter; FOREACH_MAIN_ID_BEGIN (bmain, id_iter) { - if (ID_IS_LINKED(id_iter) || !ID_IS_OVERRIDE_LIBRARY_REAL(id_iter) || - id_iter->override_library->hierarchy_root != id_hierarchy_root_override) { + if (ID_IS_LINKED(id_iter) || !ID_IS_OVERRIDE_LIBRARY_REAL(id_iter)) { continue; } - if (data->selected_id_uid.contains(id_iter->override_library->reference->session_uuid)) { + if (!data.id_hierarchy_roots_uid.contains( + id_iter->override_library->hierarchy_root->session_uuid)) { + continue; + } + if (data.selected_id_uid.contains(id_iter->override_library->reference->session_uuid) || + data.selected_id_uid.contains(id_iter->session_uuid)) { id_iter->override_library->flag &= ~IDOVERRIDE_LIBRARY_FLAG_SYSTEM_DEFINED; } } @@ -2254,8 +2346,18 @@ static int outliner_id_operation_exec(bContext *C, wmOperator *op) } case OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE: { OutlinerLibOverrideData override_data{}; - outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, id_override_library_create_fn, &override_data); + override_data.do_hierarchy = false; + override_data.do_fully_editable = true; + + outliner_do_libdata_operation(C, + op->reports, + scene, + space_outliner, + id_override_library_create_hierarchy_pre_process_fn, + &override_data); + + id_override_library_create_hierarchy_process(C, op->reports, override_data); + ED_undo_push(C, "Overridden Data"); break; } @@ -2263,15 +2365,15 @@ static int outliner_id_operation_exec(bContext *C, wmOperator *op) OutlinerLibOverrideData override_data{}; override_data.do_hierarchy = true; override_data.do_fully_editable = U.experimental.use_override_new_fully_editable; + outliner_do_libdata_operation(C, op->reports, scene, space_outliner, id_override_library_create_hierarchy_pre_process_fn, &override_data); - outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, id_override_library_create_fn, &override_data); - id_override_library_create_hierarchy_post_process(C, &override_data); + + id_override_library_create_hierarchy_process(C, op->reports, override_data); ED_undo_push(C, "Overridden Data Hierarchy"); break; -- cgit v1.2.3 From 10b048fd9e51affa7d36022b32b24e489cfd0cbd Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Thu, 21 Jul 2022 11:25:24 +0200 Subject: Fix T99885: Invalid dependency graph state when curves surface is invisible Differential Revision: https://developer.blender.org/D15510 --- source/blender/depsgraph/intern/builder/deg_builder_nodes.cc | 7 +++++++ .../blender/depsgraph/intern/builder/deg_builder_relations.cc | 11 ++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc index c65c4beeed6..4cbb2ce7060 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc @@ -26,6 +26,7 @@ #include "DNA_collection_types.h" #include "DNA_constraint_types.h" #include "DNA_curve_types.h" +#include "DNA_curves_types.h" #include "DNA_effect_types.h" #include "DNA_gpencil_types.h" #include "DNA_key_types.h" @@ -1563,8 +1564,14 @@ void DepsgraphNodeBuilder::build_object_data_geometry_datablock(ID *obdata) break; } case ID_CV: { + Curves *curves_id = reinterpret_cast(obdata); + op_node = add_operation_node(obdata, NodeType::GEOMETRY, OperationCode::GEOMETRY_EVAL); op_node->set_as_entry(); + + if (curves_id->surface != nullptr) { + build_object(-1, curves_id->surface, DEG_ID_LINKED_INDIRECTLY, false); + } break; } case ID_PT: { diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc index f2646ebc1f1..1269ebeda4e 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc @@ -27,6 +27,7 @@ #include "DNA_collection_types.h" #include "DNA_constraint_types.h" #include "DNA_curve_types.h" +#include "DNA_curves_types.h" #include "DNA_effect_types.h" #include "DNA_gpencil_types.h" #include "DNA_key_types.h" @@ -2426,8 +2427,16 @@ void DepsgraphRelationBuilder::build_object_data_geometry_datablock(ID *obdata) } break; } - case ID_CV: + case ID_CV: { + Curves *curves_id = reinterpret_cast(obdata); + if (curves_id->surface != nullptr) { + build_object(curves_id->surface); + + /* The relations between the surface and the curves are handled as part of the modifier + * stack building. */ + } break; + } case ID_PT: break; case ID_VO: { -- cgit v1.2.3 From f7252e9692ba22b9ca42394e5e21d0b52cbb872c Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Thu, 21 Jul 2022 12:16:31 +0200 Subject: Cleanup: Unused forward declaration --- source/blender/depsgraph/intern/node/deg_node_component.h | 1 - 1 file changed, 1 deletion(-) diff --git a/source/blender/depsgraph/intern/node/deg_node_component.h b/source/blender/depsgraph/intern/node/deg_node_component.h index cbc86ccd78d..a616dfdfe4c 100644 --- a/source/blender/depsgraph/intern/node/deg_node_component.h +++ b/source/blender/depsgraph/intern/node/deg_node_component.h @@ -24,7 +24,6 @@ struct bPoseChannel; namespace blender::deg { -struct BoneComponentNode; struct Depsgraph; struct IDNode; struct OperationNode; -- cgit v1.2.3 From d099e0d2a4875b03c6b91c39817ffc11c4357797 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Thu, 21 Jul 2022 12:17:30 +0200 Subject: Cleanup: Make automated code check happy. - Assert that one of the thwo branches in `id_override_library_create_hierarchy` are always processed. - Init success value regardless. --- source/blender/editors/space_outliner/outliner_tools.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/blender/editors/space_outliner/outliner_tools.cc b/source/blender/editors/space_outliner/outliner_tools.cc index e1aa695a262..80ee3bbb6a1 100644 --- a/source/blender/editors/space_outliner/outliner_tools.cc +++ b/source/blender/editors/space_outliner/outliner_tools.cc @@ -1025,7 +1025,7 @@ static void id_override_library_create_hierarchy( } FOREACH_MAIN_ID_END; - bool success; + bool success = false; if (do_hierarchy) { ID *id_root_override = nullptr; success = BKE_lib_override_library_create(&bmain, @@ -1073,6 +1073,9 @@ static void id_override_library_create_hierarchy( BKE_main_id_newptr_and_tag_clear(&bmain); BKE_main_id_tag_all(&bmain, LIB_TAG_DOIT, false); } + else { + BLI_assert_unreachable(); + } /* Remove the instance empty from this scene, the items now have an overridden collection * instead. */ -- cgit v1.2.3 From 538da79c6d17a6e660a9484c280220240b306282 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Thu, 21 Jul 2022 12:23:10 +0200 Subject: Curves: fix applying materials when applying modifier The issue was that geometry nodes was run on the original curves, and set a pointer to an evaluated material id on it. The fix is to not mix up original and evaluated data by making sure that geometry nodes does not modify the original data. --- source/blender/editors/object/object_modifier.cc | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/source/blender/editors/object/object_modifier.cc b/source/blender/editors/object/object_modifier.cc index a3a41a5c5b1..e7cfcf48fd3 100644 --- a/source/blender/editors/object/object_modifier.cc +++ b/source/blender/editors/object/object_modifier.cc @@ -818,7 +818,7 @@ static bool modifier_apply_obdata( /* Create a temporary geometry set and component. */ GeometrySet geometry_set; geometry_set.get_component_for_write().replace( - &curves, GeometryOwnershipType::Editable); + &curves, GeometryOwnershipType::ReadOnly); ModifierEvalContext mectx = {depsgraph, ob, (ModifierApplyFlag)0}; mti->modifyGeometrySet(md_eval, &mectx, &geometry_set); @@ -833,14 +833,11 @@ static bool modifier_apply_obdata( .attributes_for_write() .remove_anonymous(); - /* If the modifier's output is a different curves data-block, copy the relevant information to - * the original. */ - if (&curves_eval != &curves) { - blender::bke::CurvesGeometry::wrap(curves.geometry) = std::move( - blender::bke::CurvesGeometry::wrap(curves_eval.geometry)); - Main *bmain = DEG_get_bmain(depsgraph); - BKE_object_material_from_eval_data(bmain, ob, &curves_eval.id); - } + /* Copy the relevant information to the original. */ + blender::bke::CurvesGeometry::wrap(curves.geometry) = std::move( + blender::bke::CurvesGeometry::wrap(curves_eval.geometry)); + Main *bmain = DEG_get_bmain(depsgraph); + BKE_object_material_from_eval_data(bmain, ob, &curves_eval.id); } else { /* TODO: implement for point clouds and volumes. */ -- cgit v1.2.3 From 2034e8c42dd9c09952196fb12a82adde696ac05d Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Thu, 21 Jul 2022 12:47:44 +0200 Subject: Geometry Nodes: add debug check for whether AttributeWriter.finish is called Calling `finish` after writing to generic attributes is currently necessary for correctness. Previously, this was easy to forget. Now there is a check for this in debug builds. --- source/blender/blenkernel/BKE_attribute.hh | 5 +-- .../blender/blenkernel/intern/attribute_access.cc | 43 ++++++++++++++++++++++ .../blenlib/intern/generic_virtual_array.cc | 2 +- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/source/blender/blenkernel/BKE_attribute.hh b/source/blender/blenkernel/BKE_attribute.hh index 835fd58026f..1e61e477759 100644 --- a/source/blender/blenkernel/BKE_attribute.hh +++ b/source/blender/blenkernel/BKE_attribute.hh @@ -537,10 +537,7 @@ class MutableAttributeAccessor : public AttributeAccessor { * Get a writable attribute or none if it does not exist. * Make sure to call #finish after changes are done. */ - GAttributeWriter lookup_for_write(const AttributeIDRef &attribute_id) - { - return fn_->lookup_for_write(owner_, attribute_id); - } + GAttributeWriter lookup_for_write(const AttributeIDRef &attribute_id); /** * Get a writable attribute or non if it does not exist. diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index b2ac15a71d7..19faddc5727 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -968,6 +968,49 @@ void MutableAttributeAccessor::remove_anonymous() } } +/** + * Debug utility that checks whether the #finish function of an #AttributeWriter has been called. + */ +#ifdef DEBUG +struct FinishCallChecker { + std::string name; + bool finish_called = false; + std::function real_finish_fn; + + ~FinishCallChecker() + { + if (!this->finish_called) { + std::cerr << "Forgot to call `finish()` for '" << this->name << "'.\n"; + } + } +}; +#endif + +GAttributeWriter MutableAttributeAccessor::lookup_for_write(const AttributeIDRef &attribute_id) +{ + GAttributeWriter attribute = fn_->lookup_for_write(owner_, attribute_id); + /* Check that the #finish method is called in debug builds. */ +#ifdef DEBUG + if (attribute) { + auto checker = std::make_shared(); + if (attribute_id.is_named()) { + checker->name = attribute_id.name(); + } + else { + checker->name = BKE_anonymous_attribute_id_debug_name(&attribute_id.anonymous_id()); + } + checker->real_finish_fn = attribute.tag_modified_fn; + attribute.tag_modified_fn = [checker]() { + if (checker->real_finish_fn) { + checker->real_finish_fn(); + } + checker->finish_called = true; + }; + } +#endif + return attribute; +} + GAttributeWriter MutableAttributeAccessor::lookup_or_add_for_write( const AttributeIDRef &attribute_id, const eAttrDomain domain, diff --git a/source/blender/blenlib/intern/generic_virtual_array.cc b/source/blender/blenlib/intern/generic_virtual_array.cc index 1e548d006b2..f66b1e14fc6 100644 --- a/source/blender/blenlib/intern/generic_virtual_array.cc +++ b/source/blender/blenlib/intern/generic_virtual_array.cc @@ -404,7 +404,7 @@ GMutableVArraySpan::~GMutableVArraySpan() if (varray_) { if (show_not_saved_warning_) { if (!save_has_been_called_) { - std::cout << "Warning: Call `apply()` to make sure that changes persist in all cases.\n"; + std::cout << "Warning: Call `save()` to make sure that changes persist in all cases.\n"; } } } -- cgit v1.2.3 From a06b04f92dc6741cc5079bf7c67eb38f973b8e45 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Thu, 21 Jul 2022 12:53:47 +0200 Subject: Cleanup: Simplify relation flags assignment --- source/blender/depsgraph/intern/builder/deg_builder_relations.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc index 1269ebeda4e..f36d94c7563 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc @@ -2198,8 +2198,7 @@ void DepsgraphRelationBuilder::build_object_data_geometry(Object *object) * data mask to be used. We add relation here to ensure object is never * evaluated prior to Scene's CoW is ready. */ OperationKey scene_key(&scene_->id, NodeType::PARAMETERS, OperationCode::SCENE_EVAL); - Relation *rel = add_relation(scene_key, obdata_ubereval_key, "CoW Relation"); - rel->flag |= RELATION_FLAG_NO_FLUSH; + add_relation(scene_key, obdata_ubereval_key, "CoW Relation", RELATION_FLAG_NO_FLUSH); /* Modifiers */ if (object->modifiers.first != nullptr) { ModifierUpdateDepsgraphContext ctx = {}; -- cgit v1.2.3 From 03338e027081744b742e69fff21a74b1732f76ab Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 21 Jul 2022 21:43:15 +1000 Subject: GHOST/Wayland: fix cursor glitch after grabbing while hidden When the cursor grabbing was disabled, Blender's internal location (wmWindow.eventstate) kept the location before un-hiding. This caused the paint cursor to show in the wrong location after adjusting the color wheel for e.g. --- intern/ghost/intern/GHOST_SystemWayland.cpp | 41 ++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index d7520f1243f..07202ff972d 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -3852,11 +3852,15 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod input->relative_pointer = nullptr; } if (input->locked_pointer) { + /* Potentially add a motion event so the applicate has updated X/Y coordinates. */ + int32_t xy_motion[2] = {0, 0}; + bool xy_motion_create_event = false; + /* Request location to restore to. */ if (mode_current == GHOST_kGrabWrap) { /* Since this call is initiated by Blender, we can be sure the window wasn't closed * by logic outside this function - as the window was needed to make this call. */ - int32_t xy_new[2] = {UNPACK2(input->pointer.xy)}; + int32_t xy_next[2] = {UNPACK2(input->pointer.xy)}; GHOST_Rect bounds_scale; @@ -3865,21 +3869,18 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod bounds_scale.m_r = wl_fixed_from_int(wrap_bounds->m_r) / scale; bounds_scale.m_b = wl_fixed_from_int(wrap_bounds->m_b) / scale; - bounds_scale.wrapPoint(UNPACK2(xy_new), 0, wrap_axis); + bounds_scale.wrapPoint(UNPACK2(xy_next), 0, wrap_axis); /* Push an event so the new location is registered. */ - if ((xy_new[0] != input->pointer.xy[0]) || (xy_new[1] != input->pointer.xy[1])) { - input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(), - GHOST_kEventCursorMove, - ghost_wl_surface_user_data(surface), - wl_fixed_to_int(scale * xy_new[0]), - wl_fixed_to_int(scale * xy_new[1]), - GHOST_TABLET_DATA_NONE)); + if ((xy_next[0] != input->pointer.xy[0]) || (xy_next[1] != input->pointer.xy[1])) { + xy_motion[0] = xy_next[0]; + xy_motion[1] = xy_next[1]; + xy_motion_create_event = true; } - input->pointer.xy[0] = xy_new[0]; - input->pointer.xy[1] = xy_new[1]; + input->pointer.xy[0] = xy_next[0]; + input->pointer.xy[1] = xy_next[1]; - zwp_locked_pointer_v1_set_cursor_position_hint(input->locked_pointer, UNPACK2(xy_new)); + zwp_locked_pointer_v1_set_cursor_position_hint(input->locked_pointer, UNPACK2(xy_next)); wl_surface_commit(surface); } else if (mode_current == GHOST_kGrabHide) { @@ -3891,6 +3892,13 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod }; zwp_locked_pointer_v1_set_cursor_position_hint(input->locked_pointer, UNPACK2(xy_next)); wl_surface_commit(surface); + + /* NOTE(@campbellbarton): The new cursor position is a hint, + * it's possible the hint is ignored. It doesn't seem like there is a good way to + * know if the hint will be used or not, at least not immediately. */ + xy_motion[0] = xy_next[0]; + xy_motion[1] = xy_next[1]; + xy_motion_create_event = true; } } #ifdef USE_GNOME_CONFINE_HACK @@ -3903,6 +3911,15 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod } #endif + if (xy_motion_create_event) { + input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(), + GHOST_kEventCursorMove, + ghost_wl_surface_user_data(surface), + wl_fixed_to_int(scale * xy_motion[0]), + wl_fixed_to_int(scale * xy_motion[1]), + GHOST_TABLET_DATA_NONE)); + } + zwp_locked_pointer_v1_destroy(input->locked_pointer); input->locked_pointer = nullptr; } -- cgit v1.2.3 From 95ab16004d1e55cd4796b35d2a9f0e9695644f92 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 21 Jul 2022 08:00:30 -0500 Subject: Cleanup: Remove debug print in test --- source/blender/blenlib/tests/BLI_length_parameterize_test.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/blender/blenlib/tests/BLI_length_parameterize_test.cc b/source/blender/blenlib/tests/BLI_length_parameterize_test.cc index 11f4997f563..d59f7ae3c28 100644 --- a/source/blender/blenlib/tests/BLI_length_parameterize_test.cc +++ b/source/blender/blenlib/tests/BLI_length_parameterize_test.cc @@ -210,7 +210,6 @@ TEST(length_parameterize, ArbitraryFloatSimple) sample_at_lengths(lengths, sample_lengths, indices, factors); Array results(4); linear_interpolation(values, indices, factors, results); - results.as_span().print_as_lines("results"); Array expected({ 0.5f, 1.5f, @@ -234,7 +233,6 @@ TEST(length_parameterize, ArbitraryFloat2) sample_at_lengths(lengths, sample_lengths, indices, factors); Array results(12); linear_interpolation(values, indices, factors, results); - results.as_span().print_as_lines("results"); Array expected({ {0.5f, 0.0f}, {1.0f, 0.5f}, -- cgit v1.2.3 From 63be57307eba75c05e7584a2e43373e489b451fb Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 21 Jul 2022 08:15:06 -0500 Subject: Cleanup: Rename length parameterization interpolation function The name makes more sense as an action, other interpolation methods besides linear probably don't make sense here anyway. --- source/blender/blenlib/BLI_length_parameterize.hh | 8 ++++---- .../editors/sculpt_paint/curves_sculpt_brush.cc | 2 +- .../sculpt_paint/curves_sculpt_grow_shrink.cc | 2 +- source/blender/geometry/intern/resample_curves.cc | 24 +++++++++++----------- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/source/blender/blenlib/BLI_length_parameterize.hh b/source/blender/blenlib/BLI_length_parameterize.hh index c44bb94f65d..035d4d04787 100644 --- a/source/blender/blenlib/BLI_length_parameterize.hh +++ b/source/blender/blenlib/BLI_length_parameterize.hh @@ -42,10 +42,10 @@ void accumulate_lengths(const Span values, const bool cyclic, MutableSpan -inline void linear_interpolation(const Span src, - const Span indices, - const Span factors, - MutableSpan dst) +inline void interpolate(const Span src, + const Span indices, + const Span factors, + MutableSpan dst) { BLI_assert(indices.size() == factors.size()); BLI_assert(indices.size() == dst.size()); diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc b/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc index 10564942ab9..ff27c16dc36 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc @@ -366,7 +366,7 @@ void move_last_point_and_resample(MutableSpan positions, const float3 &n length_parameterize::sample_at_lengths(orig_lengths, new_lengths, indices, factors); Array new_positions(positions.size() - 1); - length_parameterize::linear_interpolation(positions, indices, factors, new_positions); + length_parameterize::interpolate(positions, indices, factors, new_positions); positions.drop_back(1).copy_from(new_positions); positions.last() = new_last_position; } diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc b/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc index 408139d6653..8ef18ba7da7 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc @@ -127,7 +127,7 @@ class ShrinkCurvesEffect : public CurvesEffect { lp::sample_at_lengths(data.old_lengths, data.sample_lengths, data.indices, data.factors); - lp::linear_interpolation(data.old_positions, data.indices, data.factors, positions); + lp::interpolate(data.old_positions, data.indices, data.factors, positions); } }; diff --git a/source/blender/geometry/intern/resample_curves.cc b/source/blender/geometry/intern/resample_curves.cc index 2c3a6c6e0cf..29e358cc3f4 100644 --- a/source/blender/geometry/intern/resample_curves.cc +++ b/source/blender/geometry/intern/resample_curves.cc @@ -252,10 +252,10 @@ static Curves *resample_to_uniform(const CurveComponent &src_component, const IndexRange dst_points = dst_curves.points_for_curve(i_curve); if (curve_types[i_curve] == CURVE_TYPE_POLY) { - length_parameterize::linear_interpolation(src.slice(src_points), - sample_indices.as_span().slice(dst_points), - sample_factors.as_span().slice(dst_points), - dst.slice(dst_points)); + length_parameterize::interpolate(src.slice(src_points), + sample_indices.as_span().slice(dst_points), + sample_factors.as_span().slice(dst_points), + dst.slice(dst_points)); } else { const int evaluated_size = src_curves.evaluated_points_for_curve(i_curve).size(); @@ -264,10 +264,10 @@ static Curves *resample_to_uniform(const CurveComponent &src_component, MutableSpan evaluated = evaluated_buffer.as_mutable_span().cast(); src_curves.interpolate_to_evaluated(i_curve, src.slice(src_points), evaluated); - length_parameterize::linear_interpolation(evaluated.as_span(), - sample_indices.as_span().slice(dst_points), - sample_factors.as_span().slice(dst_points), - dst.slice(dst_points)); + length_parameterize::interpolate(evaluated.as_span(), + sample_indices.as_span().slice(dst_points), + sample_factors.as_span().slice(dst_points), + dst.slice(dst_points)); } } }); @@ -277,10 +277,10 @@ static Curves *resample_to_uniform(const CurveComponent &src_component, for (const int i_curve : sliced_selection) { const IndexRange src_points = src_curves.evaluated_points_for_curve(i_curve); const IndexRange dst_points = dst_curves.points_for_curve(i_curve); - length_parameterize::linear_interpolation(evaluated_positions.slice(src_points), - sample_indices.as_span().slice(dst_points), - sample_factors.as_span().slice(dst_points), - dst_positions.slice(dst_points)); + length_parameterize::interpolate(evaluated_positions.slice(src_points), + sample_indices.as_span().slice(dst_points), + sample_factors.as_span().slice(dst_points), + dst_positions.slice(dst_points)); } /* Fill the default value for non-interpolating attributes that still must be copied. */ -- cgit v1.2.3 From 4ba6bac2f1a45fbd0715dd076992cf21f41f262f Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 21 Jul 2022 08:30:07 -0500 Subject: Fix build error in tests binary after previous commit Also remove an unused include and add a comment, const, use the math namespace. --- source/blender/blenlib/BLI_length_parameterize.hh | 6 +++--- .../blenlib/tests/BLI_length_parameterize_test.cc | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/source/blender/blenlib/BLI_length_parameterize.hh b/source/blender/blenlib/BLI_length_parameterize.hh index 035d4d04787..1b494c021a3 100644 --- a/source/blender/blenlib/BLI_length_parameterize.hh +++ b/source/blender/blenlib/BLI_length_parameterize.hh @@ -9,7 +9,6 @@ #include "BLI_math_base.hh" #include "BLI_math_color.hh" #include "BLI_math_vector.hh" -#include "BLI_vector.hh" namespace blender::length_parameterize { @@ -75,6 +74,7 @@ struct SampleSegmentHint { /** * \param accumulated_segment_lengths: Lengths of individual segments added up. + * Each value describes the total length at the end of the segment following a point. * \param sample_length: The position to sample at. * \param r_segment_index: Returns the index of the segment that #sample_length is in. * \param r_factor: Returns the position within the segment. @@ -82,7 +82,7 @@ struct SampleSegmentHint { * \note #sample_length must not be outside of any segment. */ inline void sample_at_length(const Span accumulated_segment_lengths, - float sample_length, + const float sample_length, int &r_segment_index, float &r_factor, SampleSegmentHint *hint = nullptr) @@ -117,7 +117,7 @@ inline void sample_at_length(const Span accumulated_segment_lengths, const float segment_start = prev_point_index == 0 ? 0.0f : lengths[prev_point_index - 1]; const float segment_end = lengths[prev_point_index]; const float segment_length = segment_end - segment_start; - const float segment_length_inv = safe_divide(1.0f, segment_length); + const float segment_length_inv = math::safe_divide(1.0f, segment_length); const float length_in_segment = sample_length - segment_start; const float factor = length_in_segment * segment_length_inv; diff --git a/source/blender/blenlib/tests/BLI_length_parameterize_test.cc b/source/blender/blenlib/tests/BLI_length_parameterize_test.cc index d59f7ae3c28..3b41a7aed0c 100644 --- a/source/blender/blenlib/tests/BLI_length_parameterize_test.cc +++ b/source/blender/blenlib/tests/BLI_length_parameterize_test.cc @@ -32,7 +32,7 @@ TEST(length_parameterize, FloatSimple) Array factors(4); sample_uniform(lengths, true, indices, factors); Array results(4); - linear_interpolation(values, indices, factors, results); + interpolate(values, indices, factors, results); Array expected({ 0.0f, 1.33333f, @@ -54,7 +54,7 @@ TEST(length_parameterize, Float) Array factors(20); sample_uniform(lengths, true, indices, factors); Array results(20); - linear_interpolation(values, indices, factors, results); + interpolate(values, indices, factors, results); Array expected({ 1.0f, 1.47368f, 1.94737f, 2.42105f, 2.89474f, 3.36842f, 3.84211f, 4.31579f, 4.78947f, 5.26316f, 5.73684f, 6.21053f, 6.68421f, 7.1579f, @@ -75,7 +75,7 @@ TEST(length_parameterize, Float2) Array factors(12); sample_uniform(lengths, true, indices, factors); Array results(12); - linear_interpolation(values, indices, factors, results); + interpolate(values, indices, factors, results); Array expected({ {0.0f, 0.0f}, {0.272727f, 0.0f}, @@ -105,7 +105,7 @@ TEST(length_parameterize, Float2Cyclic) Array factors(12); sample_uniform(lengths, false, indices, factors); Array results(12); - linear_interpolation(values, indices, factors, results); + interpolate(values, indices, factors, results); Array expected({ {0.0f, 0.0f}, {0.333333f, 0.0f}, @@ -135,7 +135,7 @@ TEST(length_parameterize, LineMany) Array factors(5007); sample_uniform(lengths, true, indices, factors); Array results(5007); - linear_interpolation(values, indices, factors, results); + interpolate(values, indices, factors, results); Array expected({ 1.9962f, 1.9964f, 1.9966f, 1.9968f, 1.997f, 1.9972f, 1.9974f, 1.9976f, 1.9978f, 1.998f, 1.9982f, 1.9984f, 1.9986f, 1.9988f, 1.999f, 1.9992f, 1.9994f, 1.9996f, 1.9998f, 2.0f, @@ -154,7 +154,7 @@ TEST(length_parameterize, CyclicMany) Array factors(5007); sample_uniform(lengths, false, indices, factors); Array results(5007); - linear_interpolation(values, indices, factors, results); + interpolate(values, indices, factors, results); Array expected({ {0, 0.0159776}, {0, 0.0151787}, {0, 0.0143797}, {0, 0.013581}, {0, 0.0127821}, {0, 0.0119832}, {0, 0.0111842}, {0, 0.0103855}, {0, 0.00958657}, {0, 0.00878763}, @@ -178,7 +178,7 @@ TEST(length_parameterize, InterpolateColor) Array factors(10); sample_uniform(lengths, false, indices, factors); Array results(10); - linear_interpolation(colors, indices, factors, results); + interpolate(colors, indices, factors, results); Array expected({ {0, 0, 0, 1}, {0.4, 0, 0, 1}, @@ -209,7 +209,7 @@ TEST(length_parameterize, ArbitraryFloatSimple) Array factors(4); sample_at_lengths(lengths, sample_lengths, indices, factors); Array results(4); - linear_interpolation(values, indices, factors, results); + interpolate(values, indices, factors, results); Array expected({ 0.5f, 1.5f, @@ -232,7 +232,7 @@ TEST(length_parameterize, ArbitraryFloat2) Array factors(12); sample_at_lengths(lengths, sample_lengths, indices, factors); Array results(12); - linear_interpolation(values, indices, factors, results); + interpolate(values, indices, factors, results); Array expected({ {0.5f, 0.0f}, {1.0f, 0.5f}, -- cgit v1.2.3 From 2bad3577c079a738030ec252e906122c24315e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Wed, 13 Jul 2022 15:01:47 +0200 Subject: DRW: common_math_geom_lib.glsl: Add line_aabb_clipping_dist --- .../draw/intern/shaders/common_math_geom_lib.glsl | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/source/blender/draw/intern/shaders/common_math_geom_lib.glsl b/source/blender/draw/intern/shaders/common_math_geom_lib.glsl index 6d4452c18c8..af8a8cd8784 100644 --- a/source/blender/draw/intern/shaders/common_math_geom_lib.glsl +++ b/source/blender/draw/intern/shaders/common_math_geom_lib.glsl @@ -90,6 +90,23 @@ float line_unit_box_intersect_dist_safe(vec3 lineorigin, vec3 linedirection) return line_unit_box_intersect_dist(lineorigin, safe_linedirection); } +/** + * Returns clipping distance (intersection with the nearest plane) with the given axis-aligned + * bound box along \a line_direction. + * Safe even if \a line_direction is degenerate. + * It assumes that an intersection exists (i.e: that \a line_direction points towards the AABB). + */ +float line_aabb_clipping_dist(vec3 line_origin, vec3 line_direction, vec3 aabb_min, vec3 aabb_max) +{ + vec3 safe_dir = select(line_direction, vec3(1e-5), lessThan(abs(line_direction), vec3(1e-5))); + vec3 dir_inv = 1.0 / safe_dir; + + vec3 first_plane = (aabb_min - line_origin) * dir_inv; + vec3 second_plane = (aabb_max - line_origin) * dir_inv; + vec3 nearest_plane = min(first_plane, second_plane); + return max_v3(nearest_plane); +} + /** \} */ /* ---------------------------------------------------------------------- */ -- cgit v1.2.3 From e022753d7a675604e507b156b2dae658cd6d7a0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Wed, 13 Jul 2022 17:31:04 +0200 Subject: EEVEE-Next: Add Temporal-AntiAliasing The improvements over the old implementation are: - Improved history reprojection filter (catmull-rom) - Use proper velocity for history reprojection. - History clipping is now done in YCoCg color space using better algorithm. - Velocity is dilated to keep correct edge anti-aliasing on moving objects. As a result, the 3x3 blocks that made the image smoother in the previous implementation are no longer visible is replaced by correct antialiasing. This removes the velocity resolve pass in order to reduce the bandwidth usage. The velocities are just resolved as they are loadded in the film pass. --- source/blender/draw/CMakeLists.txt | 1 - .../draw/engines/eevee_next/eevee_camera.cc | 18 +- .../draw/engines/eevee_next/eevee_camera.hh | 18 +- .../blender/draw/engines/eevee_next/eevee_film.cc | 44 ++- .../blender/draw/engines/eevee_next/eevee_film.hh | 11 +- .../draw/engines/eevee_next/eevee_instance.cc | 2 +- .../draw/engines/eevee_next/eevee_renderbuffers.cc | 7 +- .../draw/engines/eevee_next/eevee_sampling.cc | 29 +- .../draw/engines/eevee_next/eevee_sampling.hh | 25 +- .../draw/engines/eevee_next/eevee_shader.cc | 2 - .../draw/engines/eevee_next/eevee_shader.hh | 2 - .../draw/engines/eevee_next/eevee_shader_shared.hh | 10 +- .../draw/engines/eevee_next/eevee_velocity.cc | 89 +---- .../draw/engines/eevee_next/eevee_velocity.hh | 61 +--- .../blender/draw/engines/eevee_next/eevee_view.cc | 15 +- .../blender/draw/engines/eevee_next/eevee_view.hh | 3 +- .../eevee_next/shaders/eevee_camera_lib.glsl | 20 +- .../eevee_next/shaders/eevee_film_frag.glsl | 2 +- .../engines/eevee_next/shaders/eevee_film_lib.glsl | 373 ++++++++++++++++++--- .../eevee_next/shaders/eevee_surf_depth_frag.glsl | 11 +- .../eevee_next/shaders/eevee_velocity_lib.glsl | 87 ++--- .../shaders/eevee_velocity_resolve_comp.glsl | 58 ---- .../eevee_next/shaders/infos/eevee_film_info.hh | 8 +- .../shaders/infos/eevee_velocity_info.hh | 21 +- 24 files changed, 517 insertions(+), 400 deletions(-) delete mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_velocity_resolve_comp.glsl diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt index 55d789f64b0..d20745f28c0 100644 --- a/source/blender/draw/CMakeLists.txt +++ b/source/blender/draw/CMakeLists.txt @@ -375,7 +375,6 @@ set(GLSL_SRC engines/eevee_next/shaders/eevee_surf_lib.glsl engines/eevee_next/shaders/eevee_surf_world_frag.glsl engines/eevee_next/shaders/eevee_velocity_lib.glsl - engines/eevee_next/shaders/eevee_velocity_resolve_comp.glsl engines/eevee_next/eevee_defines.hh engines/eevee_next/eevee_shader_shared.hh diff --git a/source/blender/draw/engines/eevee_next/eevee_camera.cc b/source/blender/draw/engines/eevee_next/eevee_camera.cc index 1f65f887d46..b9040f0f3ab 100644 --- a/source/blender/draw/engines/eevee_next/eevee_camera.cc +++ b/source/blender/draw/engines/eevee_next/eevee_camera.cc @@ -29,10 +29,8 @@ namespace blender::eevee { void Camera::init() { const Object *camera_eval = inst_.camera_eval_object; - synced_ = false; - data_.swap(); - CameraData &data = data_.current(); + CameraData &data = data_; if (camera_eval) { const ::Camera *cam = reinterpret_cast(camera_eval->data); @@ -78,9 +76,7 @@ void Camera::sync() { const Object *camera_eval = inst_.camera_eval_object; - data_.swap(); - - CameraData &data = data_.current(); + CameraData &data = data_; if (inst_.drw_view) { DRW_view_viewmat_get(inst_.drw_view, data.viewmat.ptr(), false); @@ -142,14 +138,8 @@ void Camera::sync() data.equirect_scale = float2(0.0f); } - data_.current().push_update(); - - synced_ = true; - - /* Detect changes in parameters. */ - if (data_.current() != data_.previous()) { - inst_.sampling.reset(); - } + data_.initialized = true; + data_.push_update(); } /** \} */ diff --git a/source/blender/draw/engines/eevee_next/eevee_camera.hh b/source/blender/draw/engines/eevee_next/eevee_camera.hh index 3b3586190c4..8bf64199246 100644 --- a/source/blender/draw/engines/eevee_next/eevee_camera.hh +++ b/source/blender/draw/engines/eevee_next/eevee_camera.hh @@ -83,9 +83,7 @@ class Camera { Instance &inst_; /** Double buffered to detect changes and have history for re-projection. */ - SwapChain data_; - /** Detects wrong usage. */ - bool synced_ = false; + CameraDataBuf data_; public: Camera(Instance &inst) : inst_(inst){}; @@ -99,28 +97,28 @@ class Camera { **/ const CameraData &data_get() const { - BLI_assert(synced_); - return data_.current(); + BLI_assert(data_.initialized); + return data_; } const GPUUniformBuf *ubo_get() const { - return data_.current(); + return data_; } bool is_panoramic() const { - return eevee::is_panoramic(data_.current().type); + return eevee::is_panoramic(data_.type); } bool is_orthographic() const { - return data_.current().type == CAMERA_ORTHO; + return data_.type == CAMERA_ORTHO; } const float3 &position() const { - return *reinterpret_cast(data_.current().viewinv[3]); + return *reinterpret_cast(data_.viewinv[3]); } const float3 &forward() const { - return *reinterpret_cast(data_.current().viewinv[2]); + return *reinterpret_cast(data_.viewinv[2]); } }; diff --git a/source/blender/draw/engines/eevee_next/eevee_film.cc b/source/blender/draw/engines/eevee_next/eevee_film.cc index 1fd4c278c88..a1becaed9c4 100644 --- a/source/blender/draw/engines/eevee_next/eevee_film.cc +++ b/source/blender/draw/engines/eevee_next/eevee_film.cc @@ -163,11 +163,13 @@ inline bool operator!=(const FilmData &a, const FilmData &b) void Film::init(const int2 &extent, const rcti *output_rect) { + Sampling &sampling = inst_.sampling; + init_aovs(); { /* Enable passes that need to be rendered. */ - eViewLayerEEVEEPassType render_passes; + eViewLayerEEVEEPassType render_passes = eViewLayerEEVEEPassType(0); if (inst_.is_viewport()) { /* Viewport Case. */ @@ -178,6 +180,8 @@ void Film::init(const int2 &extent, const rcti *output_rect) * Using the render pass ensure we store the center depth. */ render_passes |= EEVEE_RENDER_PASS_Z; } + /* TEST */ + render_passes |= EEVEE_RENDER_PASS_VECTOR; } else { /* Render Case. */ @@ -211,7 +215,7 @@ void Film::init(const int2 &extent, const rcti *output_rect) /* TODO(@fclem): Can't we rely on depsgraph update notification? */ if (assign_if_different(enabled_passes_, render_passes)) { - inst_.sampling.reset(); + sampling.reset(); } } { @@ -224,14 +228,18 @@ void Film::init(const int2 &extent, const rcti *output_rect) FilmData data = data_; data.extent = int2(BLI_rcti_size_x(output_rect), BLI_rcti_size_y(output_rect)); data.offset = int2(output_rect->xmin, output_rect->ymin); - data.filter_size = clamp_f(inst_.scene->r.gauss, 0.0f, 100.0f); + data.extent_inv = 1.0f / float2(data.extent); + /* Disable filtering if sample count is 1. */ + data.filter_size = (sampling.sample_count() == 1) ? + 0.0f : + clamp_f(inst_.scene->r.gauss, 0.0f, 100.0f); /* TODO(fclem): parameter hidden in experimental. * We need to figure out LOD bias first in order to preserve texture crispiness. */ data.scaling_factor = 1; FilmData &data_prev_ = data_; if (assign_if_different(data_prev_, data)) { - inst_.sampling.reset(); + sampling.reset(); } const eViewLayerEEVEEPassType data_passes = EEVEE_RENDER_PASS_Z | EEVEE_RENDER_PASS_NORMAL | @@ -325,7 +333,7 @@ void Film::init(const int2 &extent, const rcti *output_rect) (data_.value_len > 0) ? data_.value_len : 1); if (reset > 0) { - inst_.sampling.reset(); + sampling.reset(); data_.use_history = 0; data_.use_reprojection = 0; @@ -349,12 +357,22 @@ void Film::sync() /* TODO(fclem): Shader variation for panoramic & scaled resolution. */ RenderBuffers &rbuffers = inst_.render_buffers; + VelocityModule &velocity = inst_.velocity; + + eGPUSamplerState filter = GPU_SAMPLER_FILTER; + + /* For viewport, only previous motion is supported. + * Still bind previous step to avoid undefined behavior. */ + eVelocityStep step_next = inst_.is_viewport() ? STEP_PREVIOUS : STEP_NEXT; DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_ALWAYS; accumulate_ps_ = DRW_pass_create("Film.Accumulate", state); GPUShader *sh = inst_.shaders.static_shader_get(shader); DRWShadingGroup *grp = DRW_shgroup_create(sh, accumulate_ps_); DRW_shgroup_uniform_block_ref(grp, "film_buf", &data_); + DRW_shgroup_uniform_block_ref(grp, "camera_prev", &(*velocity.camera_steps[STEP_PREVIOUS])); + DRW_shgroup_uniform_block_ref(grp, "camera_curr", &(*velocity.camera_steps[STEP_CURRENT])); + DRW_shgroup_uniform_block_ref(grp, "camera_next", &(*velocity.camera_steps[step_next])); DRW_shgroup_uniform_texture_ref(grp, "depth_tx", &rbuffers.depth_tx); DRW_shgroup_uniform_texture_ref(grp, "combined_tx", &rbuffers.combined_tx); DRW_shgroup_uniform_texture_ref(grp, "normal_tx", &rbuffers.normal_tx); @@ -375,7 +393,7 @@ void Film::sync() * use image binding instead. */ DRW_shgroup_uniform_image_ref(grp, "in_weight_img", &weight_tx_.current()); DRW_shgroup_uniform_image_ref(grp, "out_weight_img", &weight_tx_.next()); - DRW_shgroup_uniform_image_ref(grp, "in_combined_img", &combined_tx_.current()); + DRW_shgroup_uniform_texture_ref_ex(grp, "in_combined_tx", &combined_tx_.current(), filter); DRW_shgroup_uniform_image_ref(grp, "out_combined_img", &combined_tx_.next()); DRW_shgroup_uniform_image_ref(grp, "depth_img", &depth_tx_); DRW_shgroup_uniform_image_ref(grp, "color_accum_img", &color_accum_tx_); @@ -395,13 +413,7 @@ void Film::sync() void Film::end_sync() { - if (inst_.sampling.is_reset()) { - data_.use_history = 0; - } - - // if (camera.changed_type) { - // data_.use_reprojection = false; - // } + data_.use_reprojection = inst_.sampling.interactive_mode(); aovs_info.push_update(); @@ -429,6 +441,11 @@ float2 Film::pixel_jitter_get() const return jitter; } +eViewLayerEEVEEPassType Film::enabled_passes_get() const +{ + return enabled_passes_; +} + void Film::update_sample_table() { data_.subpixel_offset = pixel_jitter_get(); @@ -528,7 +545,6 @@ void Film::accumulate(const DRWView *view) /* Use history after first sample. */ if (data_.use_history == 0) { data_.use_history = 1; - data_.use_reprojection = 1; } } diff --git a/source/blender/draw/engines/eevee_next/eevee_film.hh b/source/blender/draw/engines/eevee_next/eevee_film.hh index 7ffbd4e45c6..c8ffa0e62c9 100644 --- a/source/blender/draw/engines/eevee_next/eevee_film.hh +++ b/source/blender/draw/engines/eevee_next/eevee_film.hh @@ -8,6 +8,12 @@ * The film class handles accumulation of samples with any distorted camera_type * using a pixel filter. Inputs needs to be jittered so that the filter converges to the right * result. + * + * In viewport, we switch between 2 accumulation mode depending on the scene state. + * - For static scene, we use a classic weighted accumulation. + * - For dynamic scene (if an update is detected), we use a more temporally stable accumulation + * following the Temporal Anti-Aliasing method (a.k.a. Temporal Super-Sampling). This does + * history reprojection and rectification to avoid most of the flickering. */ #pragma once @@ -75,10 +81,7 @@ class Film { float2 pixel_jitter_get() const; - eViewLayerEEVEEPassType enabled_passes_get() const - { - return enabled_passes_; - } + eViewLayerEEVEEPassType enabled_passes_get() const; static bool pass_is_value(eViewLayerEEVEEPassType pass_type) { diff --git a/source/blender/draw/engines/eevee_next/eevee_instance.cc b/source/blender/draw/engines/eevee_next/eevee_instance.cc index 6098d78b81c..9f8cf6dc6ba 100644 --- a/source/blender/draw/engines/eevee_next/eevee_instance.cc +++ b/source/blender/draw/engines/eevee_next/eevee_instance.cc @@ -199,7 +199,7 @@ void Instance::render_sync() **/ void Instance::render_sample() { - if (sampling.finished()) { + if (sampling.finished_viewport()) { film.display(); return; } diff --git a/source/blender/draw/engines/eevee_next/eevee_renderbuffers.cc b/source/blender/draw/engines/eevee_next/eevee_renderbuffers.cc index 6e30ba989df..c60054496c1 100644 --- a/source/blender/draw/engines/eevee_next/eevee_renderbuffers.cc +++ b/source/blender/draw/engines/eevee_next/eevee_renderbuffers.cc @@ -56,8 +56,13 @@ void RenderBuffers::acquire(int2 extent, void *owner) depth_tx.acquire(extent, GPU_DEPTH24_STENCIL8, owner); combined_tx.acquire(extent, color_format, owner); + bool do_vector_render_pass = inst_.film.enabled_passes_get() & EEVEE_RENDER_PASS_VECTOR; + /* Only RG16F when only doing only reprojection or motion blur. */ + eGPUTextureFormat vector_format = do_vector_render_pass ? GPU_RGBA16F : GPU_RG16F; + /* TODO(fclem): Make vector pass allocation optional if no TAA or motion blur is needed. */ + vector_tx.acquire(extent, vector_format, owner); + normal_tx.acquire(pass_extent(EEVEE_RENDER_PASS_NORMAL), color_format, owner); - vector_tx.acquire(pass_extent(EEVEE_RENDER_PASS_VECTOR), color_format, owner); diffuse_light_tx.acquire(pass_extent(EEVEE_RENDER_PASS_DIFFUSE_LIGHT), color_format, owner); diffuse_color_tx.acquire(pass_extent(EEVEE_RENDER_PASS_DIFFUSE_COLOR), color_format, owner); specular_light_tx.acquire(pass_extent(EEVEE_RENDER_PASS_SPECULAR_LIGHT), color_format, owner); diff --git a/source/blender/draw/engines/eevee_next/eevee_sampling.cc b/source/blender/draw/engines/eevee_next/eevee_sampling.cc index 2f180e58a0b..ef2469647ef 100644 --- a/source/blender/draw/engines/eevee_next/eevee_sampling.cc +++ b/source/blender/draw/engines/eevee_next/eevee_sampling.cc @@ -57,22 +57,21 @@ void Sampling::end_sync() { if (reset_) { viewport_sample_ = 0; - if (inst_.is_viewport()) { - interactive_mode_ = true; - } } - if (interactive_mode_) { - int interactive_sample_count = min_ii(interactive_sample_max_, sample_count_); + if (inst_.is_viewport()) { + interactive_mode_ = viewport_sample_ < interactive_mode_threshold; + if (interactive_mode_) { + int interactive_sample_count = min_ii(interactive_sample_max_, sample_count_); - if (viewport_sample_ < interactive_sample_count) { - /* Loop over the same starting samples. */ - sample_ = sample_ % interactive_sample_count; - } - else { - /* Break out of the loop and resume normal pattern. */ - sample_ = interactive_sample_count; - interactive_mode_ = false; + if (viewport_sample_ < interactive_sample_count) { + /* Loop over the same starting samples. */ + sample_ = sample_ % interactive_sample_count; + } + else { + /* Break out of the loop and resume normal pattern. */ + sample_ = interactive_sample_count; + } } } } @@ -138,8 +137,6 @@ void Sampling::step() viewport_sample_++; sample_++; - std::cout << sample_ << " " << viewport_sample_ << std::endl; - reset_ = false; } @@ -218,7 +215,7 @@ void Sampling::dof_disk_sample_get(float *r_radius, float *r_theta) const /** \} */ /* -------------------------------------------------------------------- */ -/** \name Sampling patterns +/** \name Cumulative Distribution Function (CDF) * \{ */ /* Creates a discrete cumulative distribution function table from a given curvemapping. diff --git a/source/blender/draw/engines/eevee_next/eevee_sampling.hh b/source/blender/draw/engines/eevee_next/eevee_sampling.hh index 11daa21629a..c604ecef40b 100644 --- a/source/blender/draw/engines/eevee_next/eevee_sampling.hh +++ b/source/blender/draw/engines/eevee_next/eevee_sampling.hh @@ -33,7 +33,7 @@ class Sampling { /* During interactive rendering, loop over the first few samples. */ constexpr static uint64_t interactive_sample_max_ = 8; - /** 0 based current sample. */ + /** 0 based current sample. Might not increase sequentially in viewport. */ uint64_t sample_ = 0; /** Target sample count. */ uint64_t sample_count_ = 64; @@ -43,7 +43,7 @@ class Sampling { uint64_t dof_sample_count_ = 1; /** Motion blur steps. */ uint64_t motion_blur_steps_ = 1; - /** Increases if the view and the scene is static. */ + /** Increases if the view and the scene is static. Does increase sequentially. */ int64_t viewport_sample_ = 0; /** Tag to reset sampling for the next sample. */ bool reset_ = false; @@ -52,6 +52,12 @@ class Sampling { * In interactive mode, image stability is prioritized over quality. */ bool interactive_mode_ = false; + /** + * Sample count after which we use the static accumulation. + * Interactive sampling from sample 0 to (interactive_mode_threshold - 1). + * Accumulation sampling from sample interactive_mode_threshold to sample_count_. + */ + static constexpr int interactive_mode_threshold = 3; SamplingDataBuf data_; @@ -102,13 +108,24 @@ class Sampling { /* Returns true if rendering has finished. */ bool finished() const { - return (sample_ >= sample_count_ - 1); + return (sample_ >= sample_count_); } /* Returns true if viewport smoothing and sampling has finished. */ bool finished_viewport() const { - return finished() && (viewport_sample_ >= interactive_sample_max_); + return (viewport_sample_ >= sample_count_) && !interactive_mode_; + } + + /* Returns true if viewport renderer is in interactive mode and should use TAA. */ + bool interactive_mode() const + { + return interactive_mode_; + } + + uint64_t sample_count() const + { + return sample_count_; } /* Return true if we are starting a new motion blur step. We need to run sync again since diff --git a/source/blender/draw/engines/eevee_next/eevee_shader.cc b/source/blender/draw/engines/eevee_next/eevee_shader.cc index f5d4af2914e..7db9692783a 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader.cc +++ b/source/blender/draw/engines/eevee_next/eevee_shader.cc @@ -82,8 +82,6 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_ return "eevee_film_frag"; case FILM_COMP: return "eevee_film_comp"; - case VELOCITY_RESOLVE: - return "eevee_velocity_resolve"; /* To avoid compiler warning about missing case. */ case MAX_SHADER_TYPE: return ""; diff --git a/source/blender/draw/engines/eevee_next/eevee_shader.hh b/source/blender/draw/engines/eevee_next/eevee_shader.hh index 7a0867820af..280aaab4e1c 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader.hh +++ b/source/blender/draw/engines/eevee_next/eevee_shader.hh @@ -29,8 +29,6 @@ enum eShaderType { FILM_FRAG = 0, FILM_COMP, - VELOCITY_RESOLVE, - MAX_SHADER_TYPE, }; diff --git a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh index 62eb5a2b965..9cf7f75b2c3 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh +++ b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh @@ -124,7 +124,12 @@ struct CameraData { float clip_far; eCameraType type; - int _pad0; + bool initialized; + +#ifdef __cplusplus + /* Small constructor to allow detecting new buffers. */ + CameraData() : initialized(false){}; +#endif }; BLI_STATIC_ASSERT_ALIGN(CameraData, 16) @@ -156,6 +161,8 @@ struct FilmData { * pixel if using scaled resolution rendering. */ float2 subpixel_offset; + /** Scaling factor to convert texel to uvs. */ + float2 extent_inv; /** Is true if history is valid and can be sampled. Bypass history to resets accumulation. */ bool1 use_history; /** Is true if combined buffer is valid and can be re-projected to reduce variance. */ @@ -165,7 +172,6 @@ struct FilmData { /** Is true if accumulation of filtered passes is needed. */ bool1 any_render_pass_1; bool1 any_render_pass_2; - int _pad0, _pad1; /** Output counts per type. */ int color_len, value_len; /** Index in color_accum_img or value_accum_img of each pass. -1 if pass is not enabled. */ diff --git a/source/blender/draw/engines/eevee_next/eevee_velocity.cc b/source/blender/draw/engines/eevee_next/eevee_velocity.cc index 4bd0af8204e..048daf1b2db 100644 --- a/source/blender/draw/engines/eevee_next/eevee_velocity.cc +++ b/source/blender/draw/engines/eevee_next/eevee_velocity.cc @@ -9,10 +9,6 @@ * temporal re-projection or motion blur. * * It is the module that tracks the objects between frames updates. - * - * #VelocityModule contains all motion steps data and logic. - * #VelocityPass contains the resolve pass for static geometry. - * #VelocityView is a per view instance that contain the velocity buffer. */ #include "BKE_duplilist.h" @@ -36,8 +32,7 @@ namespace blender::eevee { void VelocityModule::init() { -#if 0 /* TODO renderpasses */ - if (inst_.render && (inst_.render_passes.vector != nullptr)) { + if (inst_.render && (inst_.film.enabled_passes_get() & EEVEE_RENDER_PASS_VECTOR)) { /* No motion blur and the vector pass was requested. Do the step sync here. */ const Scene *scene = inst_.scene; float initial_time = scene->r.cfra + scene->r.subframe; @@ -45,7 +40,6 @@ void VelocityModule::init() step_sync(STEP_NEXT, initial_time + 1.0f); inst_.set_time(initial_time); } -#endif } static void step_object_sync_render(void *velocity, @@ -70,6 +64,11 @@ void VelocityModule::step_camera_sync() { inst_.camera.sync(); *camera_steps[step_] = inst_.camera.data_get(); + /* Fix undefined camera steps when rendering is starting. */ + if ((step_ == STEP_CURRENT) && (camera_steps[STEP_PREVIOUS]->initialized == false)) { + *camera_steps[STEP_PREVIOUS] = *static_cast(camera_steps[step_]); + camera_steps[STEP_PREVIOUS]->initialized = true; + } } bool VelocityModule::step_object_sync(Object *ob, @@ -267,6 +266,10 @@ void VelocityModule::end_sync() inst_.sampling.reset(); } + if (inst_.is_viewport() && camera_has_motion()) { + inst_.sampling.reset(); + } + for (auto key : deleted_obj) { velocity_map.remove(key); } @@ -300,19 +303,6 @@ void VelocityModule::end_sync() camera_steps[STEP_CURRENT]->push_update(); camera_steps[STEP_NEXT]->push_update(); indirection_buf.push_update(); - - { - resolve_ps_ = DRW_pass_create("Velocity.Resolve", (DRWState)0); - GPUShader *sh = inst_.shaders.static_shader_get(VELOCITY_RESOLVE); - DRWShadingGroup *grp = DRW_shgroup_create(sh, resolve_ps_); - DRW_shgroup_uniform_texture_ref(grp, "depth_tx", &input_depth_tx_); - DRW_shgroup_uniform_image_ref(grp, "velocity_view_img", &velocity_view_tx_); - DRW_shgroup_uniform_image_ref(grp, "velocity_camera_img", &velocity_camera_tx_); - DRW_shgroup_uniform_block(grp, "camera_prev", *camera_steps[STEP_PREVIOUS]); - DRW_shgroup_uniform_block(grp, "camera_curr", *camera_steps[STEP_CURRENT]); - DRW_shgroup_uniform_block(grp, "camera_next", *camera_steps[STEP_NEXT]); - DRW_shgroup_call_compute_ref(grp, resolve_dispatch_size_); - } } bool VelocityModule::object_has_velocity(const Object *ob) @@ -359,60 +349,15 @@ void VelocityModule::bind_resources(DRWShadingGroup *grp) DRW_shgroup_storage_block_ref(grp, "velocity_indirection_buf", &indirection_buf); } -/* Resolve pass for static geometry and to camera space projection. */ -void VelocityModule::resolve_camera_motion(GPUTexture *depth_tx, - GPUTexture *velocity_view_tx, - GPUTexture *velocity_camera_tx) +bool VelocityModule::camera_has_motion() const { - input_depth_tx_ = depth_tx; - velocity_view_tx_ = velocity_view_tx; - velocity_camera_tx_ = velocity_camera_tx; - - resolve_dispatch_size_.x = divide_ceil_u(GPU_texture_width(depth_tx), 8); - resolve_dispatch_size_.y = divide_ceil_u(GPU_texture_height(depth_tx), 8); - - DRW_draw_pass(resolve_ps_); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Velocity View - * \{ */ - -void VelocityView::sync() -{ - /* TODO: Remove. */ - velocity_view_tx_.sync(); - velocity_camera_tx_.sync(); -} - -void VelocityView::acquire(int2 extent) -{ - /* WORKAROUND: View name should be unique and static. - * With this, we can reuse the same texture across views. */ - DrawEngineType *owner = (DrawEngineType *)view_name_.c_str(); - - /* Only RG16F when only doing only reprojection or motion blur. */ - eGPUTextureFormat format = inst_.is_viewport() ? GPU_RG16F : GPU_RGBA16F; - velocity_view_tx_.acquire(extent, format, owner); - if (false /* TODO(fclem): Panoramic camera. */) { - velocity_camera_tx_.acquire(extent, format, owner); - } - else { - velocity_camera_tx_.acquire(int2(1), format, owner); + /* Only valid after sync. */ + if (inst_.is_viewport()) { + /* Viewport has no next step. */ + return *camera_steps[STEP_PREVIOUS] != *camera_steps[STEP_CURRENT]; } -} - -void VelocityView::resolve(GPUTexture *depth_tx) -{ - inst_.velocity.resolve_camera_motion(depth_tx, velocity_view_tx_, velocity_camera_tx_); -} - -void VelocityView::release() -{ - velocity_view_tx_.release(); - velocity_camera_tx_.release(); + return *camera_steps[STEP_PREVIOUS] != *camera_steps[STEP_CURRENT] && + *camera_steps[STEP_NEXT] != *camera_steps[STEP_CURRENT]; } /** \} */ diff --git a/source/blender/draw/engines/eevee_next/eevee_velocity.hh b/source/blender/draw/engines/eevee_next/eevee_velocity.hh index e2606c061e1..826cd631a96 100644 --- a/source/blender/draw/engines/eevee_next/eevee_velocity.hh +++ b/source/blender/draw/engines/eevee_next/eevee_velocity.hh @@ -27,8 +27,6 @@ namespace blender::eevee { /** Container for scene velocity data. */ class VelocityModule { - friend class VelocityView; - public: struct VelocityObjectData : public VelocityIndex { /** ID to retrieve the corresponding #VelocityGeometryData after copy. */ @@ -69,15 +67,6 @@ class VelocityModule { eVelocityStep step_ = STEP_CURRENT; - DRWPass *resolve_ps_ = nullptr; - - /** Reference only. Not owned. */ - GPUTexture *input_depth_tx_; - GPUTexture *velocity_view_tx_; - GPUTexture *velocity_camera_tx_; - - int3 resolve_dispatch_size_ = int3(1, 1, 1); - public: VelocityModule(Instance &inst) : inst_(inst) { @@ -89,6 +78,7 @@ class VelocityModule { } for (CameraDataBuf *&step_buf : camera_steps) { step_buf = new CameraDataBuf(); + /* */ } }; @@ -121,56 +111,11 @@ class VelocityModule { void bind_resources(DRWShadingGroup *grp); + bool camera_has_motion() const; + private: bool object_has_velocity(const Object *ob); bool object_is_deform(const Object *ob); - - void resolve_camera_motion(GPUTexture *depth_tx, - GPUTexture *velocity_view_tx, - GPUTexture *velocity_camera_tx); -}; - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Velocity - * - * \{ */ - -/** - * Per view module. - */ -class VelocityView { - private: - Instance &inst_; - - StringRefNull view_name_; - - TextureFromPool velocity_camera_tx_ = {"velocity_camera_tx_"}; - TextureFromPool velocity_view_tx_ = {"velocity_view_tx_"}; - - public: - VelocityView(Instance &inst, const char *name) : inst_(inst), view_name_(name){}; - ~VelocityView(){}; - - void sync(); - - void acquire(int2 extent); - void release(); - - void resolve(GPUTexture *depth_tx); - - /** - * Getters - **/ - GPUTexture *view_vectors_get() const - { - return velocity_view_tx_; - } - GPUTexture *camera_vectors_get() const - { - return (velocity_camera_tx_.is_valid()) ? velocity_camera_tx_ : velocity_view_tx_; - } }; /** \} */ diff --git a/source/blender/draw/engines/eevee_next/eevee_view.cc b/source/blender/draw/engines/eevee_next/eevee_view.cc index f4dba47721d..1a222dc4ebd 100644 --- a/source/blender/draw/engines/eevee_next/eevee_view.cc +++ b/source/blender/draw/engines/eevee_next/eevee_view.cc @@ -80,7 +80,6 @@ void ShadingView::sync() // dof_.sync(winmat_p, extent_); // mb_.sync(extent_); - velocity_.sync(); // rt_buffer_opaque_.sync(extent_); // rt_buffer_refract_.sync(extent_); // inst_.hiz_back.view_sync(extent_); @@ -103,18 +102,20 @@ void ShadingView::render() RenderBuffers &rbufs = inst_.render_buffers; rbufs.acquire(extent_, owner); - velocity_.acquire(extent_); combined_fb_.ensure(GPU_ATTACHMENT_TEXTURE(rbufs.depth_tx), GPU_ATTACHMENT_TEXTURE(rbufs.combined_tx)); prepass_fb_.ensure(GPU_ATTACHMENT_TEXTURE(rbufs.depth_tx), - GPU_ATTACHMENT_TEXTURE(velocity_.view_vectors_get())); + GPU_ATTACHMENT_TEXTURE(rbufs.vector_tx)); update_view(); DRW_stats_group_start(name_); DRW_view_set_active(render_view_); - float4 clear_velocity(VELOCITY_INVALID); + /* If camera has any motion, compute motion vector in the film pass. Otherwise, we avoid float + * precision issue by setting the motion of all static geometry to 0. */ + float4 clear_velocity = float4(inst_.velocity.camera_has_motion() ? VELOCITY_INVALID : 0.0f); + GPU_framebuffer_bind(prepass_fb_); GPU_framebuffer_clear_color(prepass_fb_, clear_velocity); /* Alpha stores transmittance. So start at 1. */ @@ -137,18 +138,14 @@ void ShadingView::render() // inst_.lights.debug_draw(view_fb_); // inst_.shadows.debug_draw(view_fb_); - velocity_.resolve(rbufs.depth_tx); - // GPUTexture *final_radiance_tx = render_post(combined_tx_); inst_.film.accumulate(sub_view_); rbufs.release(); + postfx_tx_.release(); DRW_stats_group_end(); - - postfx_tx_.release(); - velocity_.release(); } GPUTexture *ShadingView::render_post(GPUTexture *input_tx) diff --git a/source/blender/draw/engines/eevee_next/eevee_view.hh b/source/blender/draw/engines/eevee_next/eevee_view.hh index 30e06df9716..c6faebdd0e5 100644 --- a/source/blender/draw/engines/eevee_next/eevee_view.hh +++ b/source/blender/draw/engines/eevee_next/eevee_view.hh @@ -44,7 +44,6 @@ class ShadingView { /** Post-FX modules. */ // DepthOfField dof_; // MotionBlur mb_; - VelocityView velocity_; /** Raytracing persistent buffers. Only opaque and refraction can have surface tracing. */ // RaytraceBuffer rt_buffer_opaque_; @@ -69,7 +68,7 @@ class ShadingView { public: ShadingView(Instance &inst, const char *name, const float (*face_matrix)[4]) - : inst_(inst), name_(name), face_matrix_(face_matrix), velocity_(inst, name){}; + : inst_(inst), name_(name), face_matrix_(face_matrix){}; ~ShadingView(){}; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_camera_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_camera_lib.glsl index f79e9102d76..2611f714b59 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_camera_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_camera_lib.glsl @@ -143,24 +143,10 @@ vec2 camera_uv_from_view(CameraData cam, vec3 vV) } } -vec2 camera_uv_from_world(CameraData cam, vec3 V) +vec2 camera_uv_from_world(CameraData cam, vec3 P) { - vec3 vV = transform_point(cam.viewmat, V); - switch (cam.type) { - default: - case CAMERA_ORTHO: - return camera_uv_from_view(cam.persmat, false, V); - case CAMERA_PERSP: - return camera_uv_from_view(cam.persmat, true, V); - case CAMERA_PANO_EQUIRECT: - return camera_equirectangular_from_direction(cam, vV); - case CAMERA_PANO_EQUISOLID: - /* ATTR_FALLTHROUGH; */ - case CAMERA_PANO_EQUIDISTANT: - return camera_fisheye_from_direction(cam, vV); - case CAMERA_PANO_MIRROR: - return camera_mirror_ball_from_direction(cam, vV); - } + vec3 vV = transform_direction(cam.viewmat, normalize(P)); + return camera_uv_from_view(cam, vV); } /** \} */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_film_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_film_frag.glsl index 6716c0f126e..454c835673b 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_film_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_film_frag.glsl @@ -11,7 +11,7 @@ void main() out_depth = imageLoad(depth_img, texel_film).r; if (film_buf.display_id == -1) { - out_color = imageLoad(in_combined_img, texel_film); + out_color = texelFetch(in_combined_tx, texel_film, 0); } else if (film_buf.display_is_value) { out_color.rgb = imageLoad(value_accum_img, ivec3(texel_film, film_buf.display_id)).rrr; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl index 03af34f27ef..1bafa26924e 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl @@ -4,7 +4,9 @@ **/ #pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl) #pragma BLENDER_REQUIRE(eevee_camera_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl) /* Return scene linear Z depth from the camera or radial depth for panoramic cameras. */ float film_depth_convert_to_scene(float depth) @@ -16,6 +18,54 @@ float film_depth_convert_to_scene(float depth) return abs(get_view_z_from_depth(depth)); } +vec3 film_YCoCg_from_scene_linear(vec3 rgb_color) +{ + const mat3 colorspace_tx = transpose(mat3(vec3(1, 2, 1), /* Y */ + vec3(2, 0, -2), /* Co */ + vec3(-1, 2, -1))); /* Cg */ + return colorspace_tx * rgb_color; +} + +vec4 film_YCoCg_from_scene_linear(vec4 rgba_color) +{ + return vec4(film_YCoCg_from_scene_linear(rgba_color.rgb), rgba_color.a); +} + +vec3 film_scene_linear_from_YCoCg(vec3 ycocg_color) +{ + float Y = ycocg_color.x; + float Co = ycocg_color.y; + float Cg = ycocg_color.z; + + vec3 rgb_color; + rgb_color.r = Y + Co - Cg; + rgb_color.g = Y + Cg; + rgb_color.b = Y - Co - Cg; + return rgb_color * 0.25; +} + +/* Load a texture sample in a specific format. Combined pass needs to use this. */ +vec4 film_texelfetch_as_YCoCg_opacity(sampler2D tx, ivec2 texel) +{ + vec4 color = texelFetch(combined_tx, texel, 0); + /* Can we assume safe color from earlier pass? */ + // color = safe_color(color); + /* Convert transmittance to opacity. */ + color.a = saturate(1.0 - color.a); + /* Transform to YCoCg for accumulation. */ + color.rgb = film_YCoCg_from_scene_linear(color.rgb); + return color; +} + +/* Returns a weight based on Luma to reduce the flickering introduced by high energy pixels. */ +float film_luma_weight(float luma) +{ + /* Slide 20 of "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014. */ + /* To preserve more details in dark areas, we use a bigger bias. */ + /* TODO(fclem): exposure weighting. */ + return 1.0 / (4.0 + luma); +} + /* -------------------------------------------------------------------- */ /** \name Filter * \{ */ @@ -116,18 +166,18 @@ void film_sample_accum_mist(FilmSample samp, inout float accum) accum += mist * samp.weight; } -void film_sample_accum_combined(FilmSample samp, inout vec4 accum) +void film_sample_accum_combined(FilmSample samp, inout vec4 accum, inout float weight_accum) { if (film_buf.combined_id == -1) { return; } - vec4 color = texelFetch(combined_tx, samp.texel, 0); - /* Convert transmittance to opacity. */ - color.a = saturate(1.0 - color.a); - /* TODO(fclem) Pre-expose. */ - color.rgb = log2(1.0 + color.rgb); + vec4 color = film_texelfetch_as_YCoCg_opacity(combined_tx, samp.texel); + + /* Weight by luma to remove fireflies. */ + float weight = film_luma_weight(color.x) * samp.weight; - accum += color * samp.weight; + accum += color * weight; + weight_accum += weight; } /** \} */ @@ -156,46 +206,281 @@ float film_weight_load(ivec2 texel) /* Repeat texture coordinates as the weight can be optimized to a small portion of the film. */ texel = texel % imageSize(in_weight_img).xy; - if (film_buf.use_history == false) { + if (!film_buf.use_history || film_buf.use_reprojection) { return 0.0; } return imageLoad(in_weight_img, ivec3(texel, WEIGHT_lAYER_ACCUMULATION)).x; } -/* Return the motion in pixels. */ -void film_motion_load() +/* Returns motion in pixel space to retrieve the pixel history. */ +vec2 film_pixel_history_motion_vector(ivec2 texel_sample) +{ + /** + * Dilate velocity by using the nearest pixel in a cross pattern. + * "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014 (Slide 27) + */ + const ivec2 corners[4] = ivec2[4](ivec2(-2, -2), ivec2(2, -2), ivec2(-2, 2), ivec2(2, 2)); + float min_depth = texelFetch(depth_tx, texel_sample, 0).x; + ivec2 nearest_texel = texel_sample; + for (int i = 0; i < 4; i++) { + ivec2 texel = clamp(texel_sample + corners[i], ivec2(0), textureSize(depth_tx, 0).xy); + float depth = texelFetch(depth_tx, texel, 0).x; + if (min_depth > depth) { + min_depth = depth; + nearest_texel = texel; + } + } + + vec4 vector = velocity_resolve(vector_tx, nearest_texel, min_depth); + + /* Transform to pixel space. */ + vector.xy *= vec2(film_buf.extent); + + return vector.xy; +} + +/* \a t is inter-pixel position. 0 means perfectly on a pixel center. + * Returns weights in both dimensions. + * Multiply each dimension weights to get final pixel weights. */ +void film_get_catmull_rom_weights(vec2 t, out vec2 weights[4]) { - // ivec2 texel_sample = film_sample_get(0, texel_film, distance_sample); - // vec4 vector = texelFetch(vector_tx, texel_sample); + vec2 t2 = t * t; + vec2 t3 = t2 * t; + float fc = 0.5; /* Catmull-Rom. */ + + vec2 fct = t * fc; + vec2 fct2 = t2 * fc; + vec2 fct3 = t3 * fc; + weights[0] = (fct2 * 2.0 - fct3) - fct; + weights[1] = (t3 * 2.0 - fct3) + (-t2 * 3.0 + fct2) + 1.0; + weights[2] = (-t3 * 2.0 + fct3) + (t2 * 3.0 - (2.0 * fct2)) + fct; + weights[3] = fct3 - fct2; +} - // vector.xy *= film_buf.extent; +/* Load color using a special filter to avoid loosing detail. + * \a texel is sample position with subpixel accuracy. */ +vec4 film_sample_catmull_rom(sampler2D color_tx, vec2 input_texel) +{ + vec2 center_texel; + vec2 inter_texel = modf(input_texel, center_texel); + vec2 weights[4]; + film_get_catmull_rom_weights(inter_texel, weights); + +#if 0 /* Reference. 16 Taps. */ + vec4 color = vec4(0.0); + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + ivec2 texel = ivec2(center_texel) + ivec2(x, y) - 1; + texel = clamp(texel, ivec2(0), textureSize(color_tx, 0).xy - 1); + color += texelFetch(color_tx, texel, 0) * weights[x].x * weights[y].y; + } + } + return color; + +#elif 1 /* Optimize version. 5 Bilinear Taps. */ + /** + * Use optimized version by leveraging bilinear filtering from hardware sampler and by removing + * corner taps. + * From "Filmic SMAA" by Jorge Jimenez at Siggraph 2016 + * http://advances.realtimerendering.com/s2016/Filmic%20SMAA%20v7.pptx + */ + center_texel += 0.5; + + /* Slide 92. */ + vec2 weight_12 = weights[1] + weights[2]; + vec2 uv_12 = (center_texel + weights[2] / weight_12) * film_buf.extent_inv; + vec2 uv_0 = (center_texel - 1.0) * film_buf.extent_inv; + vec2 uv_3 = (center_texel + 2.0) * film_buf.extent_inv; + + vec4 color; + vec4 weight_cross = weight_12.xyyx * vec4(weights[0].yx, weights[3].xy); + float weight_center = weight_12.x * weight_12.y; + + color = textureLod(color_tx, uv_12, 0.0) * weight_center; + color += textureLod(color_tx, vec2(uv_12.x, uv_0.y), 0.0) * weight_cross.x; + color += textureLod(color_tx, vec2(uv_0.x, uv_12.y), 0.0) * weight_cross.y; + color += textureLod(color_tx, vec2(uv_3.x, uv_12.y), 0.0) * weight_cross.z; + color += textureLod(color_tx, vec2(uv_12.x, uv_3.y), 0.0) * weight_cross.w; + /* Re-normalize for the removed corners. */ + return color / (weight_center + sum(weight_cross)); + +#else /* Nearest interpolation for debugging. 1 Tap. */ + ivec2 texel = ivec2(center_texel) + ivec2(greaterThan(inter_texel, vec2(0.5))); + texel = clamp(texel, ivec2(0), textureSize(color_tx, 0).xy - 1); + return texelFetch(color_tx, texel, 0); +#endif +} + +/* Return history clipping bounding box in YCoCg color space. */ +void film_combined_neighbor_boundbox(ivec2 texel, out vec4 min_c, out vec4 max_c) +{ + /* Plus (+) shape offsets. */ + const ivec2 plus_offsets[5] = ivec2[5](ivec2(0, 0), /* Center */ + ivec2(-1, 0), + ivec2(0, -1), + ivec2(1, 0), + ivec2(0, 1)); +#if 0 + /** + * Compute Variance of neighborhood as described in: + * "An Excursion in Temporal Supersampling" by Marco Salvi at GDC 2016. + * and: + * "A Survey of Temporal Antialiasing Techniques" by Yang et al. + */ + + /* First 2 moments. */ + vec4 mu1 = vec4(0), mu2 = vec4(0); + for (int i = 0; i < 5; i++) { + vec4 color = film_texelfetch_as_YCoCg_opacity(combined_tx, texel + plus_offsets[i]); + mu1 += color; + mu2 += sqr(color); + } + mu1 *= (1.0 / 5.0); + mu2 *= (1.0 / 5.0); + + /* Extent scaling. Range [0.75..1.25]. + * Balance between more flickering (0.75) or more ghosting (1.25). */ + const float gamma = 1.25; + /* Standard deviation. */ + vec4 sigma = sqrt(abs(mu2 - sqr(mu1))); + /* eq. 6 in "A Survey of Temporal Antialiasing Techniques". */ + min_c = mu1 - gamma * sigma; + max_c = mu1 + gamma * sigma; +#else + /** + * Simple bounding box calculation in YCoCg as described in: + * "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014 + */ + min_c = vec4(1e16); + max_c = vec4(-1e16); + for (int i = 0; i < 5; i++) { + vec4 color = film_texelfetch_as_YCoCg_opacity(combined_tx, texel + plus_offsets[i]); + min_c = min(min_c, color); + max_c = max(max_c, color); + } + /* (Slide 32) Simple clamp to min/max of 8 neighbors results in 3x3 box artifacts. + * Round bbox shape by averaging 2 different min/max from 2 different neighborhood. */ + vec4 min_c_3x3 = min_c; + vec4 max_c_3x3 = max_c; + const ivec2 corners[4] = ivec2[4](ivec2(-1, -1), ivec2(1, -1), ivec2(-1, 1), ivec2(1, 1)); + for (int i = 0; i < 4; i++) { + vec4 color = film_texelfetch_as_YCoCg_opacity(combined_tx, texel + corners[i]); + min_c_3x3 = min(min_c_3x3, color); + max_c_3x3 = max(max_c_3x3, color); + } + min_c = (min_c + min_c_3x3) * 0.5; + max_c = (max_c + max_c_3x3) * 0.5; +#endif +} + +/* 1D equivalent of line_aabb_clipping_dist(). */ +float film_aabb_clipping_dist_alpha(float origin, float direction, float aabb_min, float aabb_max) +{ + if (abs(direction) < 1e-5) { + return 0.0; + } + float nearest_plane = (direction > 0.0) ? aabb_min : aabb_max; + return (nearest_plane - origin) / direction; +} + +/* Modulate the history color to avoid ghosting artifact. */ +vec4 film_amend_combined_history(vec4 color_history, vec4 src_color, ivec2 src_texel) +{ + /* Get local color bounding box of source neighboorhood. */ + vec4 min_color, max_color; + film_combined_neighbor_boundbox(src_texel, min_color, max_color); + + /* Clip instead of clamping to avoid color accumulating in the AABB corners. */ + vec4 clip_dir = src_color - color_history; + + float t = line_aabb_clipping_dist(color_history.rgb, clip_dir.rgb, min_color.rgb, max_color.rgb); + color_history.rgb += clip_dir.rgb * saturate(t); + + /* Clip alpha on its own to avoid interference with other chanels. */ + float t_a = film_aabb_clipping_dist_alpha(color_history.a, clip_dir.a, min_color.a, max_color.a); + color_history.a += clip_dir.a * saturate(t_a); + + return color_history; +} + +float film_history_blend_factor(float velocity, + vec2 texel, + float luma_incoming, + float luma_history) +{ + /* 5% of incoming color by default. */ + float blend = 0.05; + /* Blend less history if the pixel has substential velocity. */ + blend = mix(blend, 0.20, saturate(velocity * 0.02)); + /* Weight by luma. */ + blend = max(blend, saturate(0.01 * luma_history / abs(luma_history - luma_incoming))); + /* Discard out of view history. */ + if (any(lessThan(texel, vec2(0))) || any(greaterThanEqual(texel, film_buf.extent))) { + blend = 1.0; + } + /* Discard history if invalid. */ + if (film_buf.use_history == false) { + blend = 1.0; + } + return blend; } /* Returns resolved final color. */ -void film_store_combined(FilmSample dst, vec4 color, inout vec4 display) +void film_store_combined( + FilmSample dst, ivec2 src_texel, vec4 color, float color_weight, inout vec4 display) { if (film_buf.combined_id == -1) { return; } - /* Could we assume safe color from earlier pass? */ - color = safe_color(color); - if (false) { - /* Re-projection using motion vectors. */ - // ivec2 texel_combined = texel_film + film_motion_load(texel_film); - // float weight_combined = film_weight_load(texel_combined); - } -#ifdef USE_NEIGHBORHOOD_CLAMPING - /* Only do that for combined pass as it has a non-negligeable performance impact. */ - // color = clamp_bbox(color, min, max); -#endif + vec4 color_src, color_dst; + float weight_src, weight_dst; + + /* Undo the weighting to get final spatialy-filtered color. */ + color_src = color / color_weight; + + if (film_buf.use_reprojection) { + /* Interactive accumulation. Do reprojection and Temporal Anti-Aliasing. */ - vec4 dst_color = imageLoad(in_combined_img, dst.texel); + /* Reproject by finding where this pixel was in the previous frame. */ + vec2 motion = film_pixel_history_motion_vector(dst.texel); + vec2 history_texel = vec2(dst.texel) + motion; - color = (dst_color * dst.weight + color) * dst.weight_sum_inv; + float velocity = length(motion); - /* TODO(fclem) undo Pre-expose. */ - // color.rgb = exp2(color.rgb) - 1.0; + /* Load weight if it is not uniform accross the whole buffer (i.e: upsampling, panoramic). */ + // dst.weight = film_weight_load(texel_combined); + + color_dst = film_sample_catmull_rom(in_combined_tx, history_texel); + color_dst.rgb = film_YCoCg_from_scene_linear(color_dst.rgb); + + float blend = film_history_blend_factor(velocity, history_texel, color_src.x, color_dst.x); + + color_dst = film_amend_combined_history(color_dst, color_src, src_texel); + + /* Luma weighted blend to avoid flickering. */ + weight_dst = film_luma_weight(color_dst.x) * (1.0 - blend); + weight_src = film_luma_weight(color_src.x) * (blend); + } + else { + /* Everything is static. Use render accumulation. */ + color_dst = texelFetch(in_combined_tx, dst.texel, 0); + color_dst.rgb = film_YCoCg_from_scene_linear(color_dst.rgb); + + /* Luma weighted blend to avoid flickering. */ + weight_dst = film_luma_weight(color_dst.x) * dst.weight; + weight_src = color_weight; + } + /* Weighted blend. */ + color = color_dst * weight_dst + color_src * weight_src; + color /= weight_src + weight_dst; + + color.rgb = film_scene_linear_from_YCoCg(color.rgb); + + /* Fix alpha not accumulating to 1 because of float imprecision. */ + if (color.a > 0.995) { + color.a = 1.0; + } if (film_buf.display_id == -1) { display = color; @@ -290,16 +575,28 @@ void film_process_data(ivec2 texel_film, out vec4 out_color, out float out_depth /* NOTE: We split the accumulations into separate loops to avoid using too much registers and * maximize occupancy. */ + if (film_buf.combined_id != -1) { + /* NOTE: Do weight accumulation again since we use custom weights. */ + float weight_accum = 0.0; + vec4 combined_accum = vec4(0.0); + + for (int i = 0; i < film_buf.samples_len; i++) { + FilmSample src = film_sample_get(i, texel_film); + film_sample_accum_combined(src, combined_accum, weight_accum); + } + film_store_combined(dst, texel_film, combined_accum, weight_accum, out_color); + } + if (film_buf.has_data) { - float film_weight = film_distance_load(texel_film); + float film_distance = film_distance_load(texel_film); /* Get sample closest to target texel. It is always sample 0. */ FilmSample film_sample = film_sample_get(0, texel_film); - if (film_sample.weight < film_weight) { - float depth = texelFetch(depth_tx, film_sample.texel, 0).x; + if (film_buf.use_reprojection || film_sample.weight < film_distance) { vec4 normal = texelFetch(normal_tx, film_sample.texel, 0); - vec4 vector = texelFetch(vector_tx, film_sample.texel, 0); + float depth = texelFetch(depth_tx, film_sample.texel, 0).x; + vec4 vector = velocity_resolve(vector_tx, film_sample.texel, depth); film_store_depth(texel_film, depth, out_depth); film_store_data(texel_film, film_buf.normal_id, normal, out_color); @@ -311,16 +608,6 @@ void film_process_data(ivec2 texel_film, out vec4 out_color, out float out_depth } } - if (film_buf.combined_id != -1) { - vec4 combined_accum = vec4(0.0); - - for (int i = 0; i < film_buf.samples_len; i++) { - FilmSample src = film_sample_get(i, texel_film); - film_sample_accum_combined(src, combined_accum); - } - film_store_combined(dst, combined_accum, out_color); - } - if (film_buf.any_render_pass_1) { vec4 diffuse_light_accum = vec4(0.0); vec4 specular_light_accum = vec4(0.0); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_depth_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_depth_frag.glsl index 7ddf941df7c..f19b6038a6a 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_depth_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_depth_frag.glsl @@ -72,14 +72,7 @@ void main() #endif #ifdef MAT_VELOCITY - vec4 out_velocity_camera; /* TODO(fclem): Panoramic cameras. */ - velocity_camera(interp.P + motion.prev, - interp.P, - interp.P - motion.next, - out_velocity_camera, - out_velocity_view); - - /* For testing in viewport. */ - out_velocity_view.zw = vec2(0.0); + out_velocity = velocity_surface(interp.P + motion.prev, interp.P, interp.P - motion.next); + out_velocity = velocity_pack(out_velocity); #endif } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_velocity_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_velocity_lib.glsl index 435ae6658c9..fb9c9faaca2 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_velocity_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_velocity_lib.glsl @@ -4,21 +4,28 @@ #ifdef VELOCITY_CAMERA +vec4 velocity_pack(vec4 data) +{ + return data * 0.01; +} + +vec4 velocity_unpack(vec4 data) +{ + return data * 100.0; +} + /** * Given a triple of position, compute the previous and next motion vectors. - * Returns uv space motion vectors in pairs (motion_prev.xy, motion_next.xy) + * Returns uv space motion vectors in pairs (motion_prev.xy, motion_next.xy). */ -vec4 velocity_view(vec3 P_prev, vec3 P, vec3 P_next) +vec4 velocity_surface(vec3 P_prv, vec3 P, vec3 P_nxt) { - vec2 prev_uv, curr_uv, next_uv; - - prev_uv = transform_point(ProjectionMatrix, transform_point(camera_prev.viewmat, P_prev)).xy; - curr_uv = transform_point(ViewProjectionMatrix, P).xy; - next_uv = transform_point(ProjectionMatrix, transform_point(camera_next.viewmat, P_next)).xy; + /* NOTE: We don't use the drw_view.persmat to avoid adding the TAA jitter to the velocity. */ + vec2 prev_uv = project_point(camera_prev.persmat, P_prv).xy; + vec2 curr_uv = project_point(camera_curr.persmat, P).xy; + vec2 next_uv = project_point(camera_next.persmat, P_nxt).xy; - vec4 motion; - motion.xy = prev_uv - curr_uv; - motion.zw = curr_uv - next_uv; + vec4 motion = vec4(prev_uv - curr_uv, curr_uv - next_uv); /* Convert NDC velocity to UV velocity */ motion *= 0.5; @@ -26,37 +33,41 @@ vec4 velocity_view(vec3 P_prev, vec3 P, vec3 P_next) } /** - * Given a triple of position, compute the previous and next motion vectors. - * Returns uv space motion vectors in pairs (motion_prev.xy, motion_next.xy) - * \a velocity_camera is the motion in film UV space after camera projection. - * \a velocity_view is the motion in ShadingView UV space. It is different - * from velocity_camera for multi-view rendering. + * Given a view space view vector \a vV, compute the previous and next motion vectors for + * background pixels. + * Returns uv space motion vectors in pairs (motion_prev.xy, motion_next.xy). */ -void velocity_camera(vec3 P_prev, vec3 P, vec3 P_next, out vec4 vel_camera, out vec4 vel_view) +vec4 velocity_background(vec3 vV) { - vec2 prev_uv, curr_uv, next_uv; - prev_uv = camera_uv_from_world(camera_prev, P_prev); - curr_uv = camera_uv_from_world(camera_curr, P); - next_uv = camera_uv_from_world(camera_next, P_next); - - vel_camera.xy = prev_uv - curr_uv; - vel_camera.zw = curr_uv - next_uv; - - if (is_panoramic(camera_curr.type)) { - /* This path is only used if using using panoramic projections. Since the views always have - * the same 45° aperture angle, we can safely reuse the projection matrix. */ - prev_uv = transform_point(ProjectionMatrix, transform_point(camera_prev.viewmat, P_prev)).xy; - curr_uv = transform_point(ViewProjectionMatrix, P).xy; - next_uv = transform_point(ProjectionMatrix, transform_point(camera_next.viewmat, P_next)).xy; - - vel_view.xy = prev_uv - curr_uv; - vel_view.zw = curr_uv - next_uv; - /* Convert NDC velocity to UV velocity */ - vel_view *= 0.5; - } - else { - vel_view = vel_camera; + /* Only transform direction to avoid loosing precision. */ + vec3 V = transform_direction(camera_curr.viewinv, vV); + + return velocity_surface(V, V, V); +} + +/** + * Load and resolve correct velocity as some pixels might still not have correct + * motion data for performance reasons. + */ +vec4 velocity_resolve(sampler2D vector_tx, ivec2 texel, float depth) +{ + vec2 uv = (vec2(texel) + 0.5) / vec2(textureSize(vector_tx, 0).xy); + vec4 vector = texelFetch(vector_tx, texel, 0); + + if (vector.x == VELOCITY_INVALID) { + bool is_background = (depth == 1.0); + if (is_background) { + /* NOTE: Use viewCameraVec to avoid imprecision if camera is far from origin. */ + vec3 vV = viewCameraVec(get_view_space_from_depth(uv, 1.0)); + return velocity_background(vV); + } + else { + /* Static geometry. No translation in world space. */ + vec3 P = get_world_space_from_depth(uv, depth); + return velocity_surface(P, P, P); + } } + return velocity_unpack(vector); } #endif diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_velocity_resolve_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_velocity_resolve_comp.glsl deleted file mode 100644 index b68b2eaf117..00000000000 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_velocity_resolve_comp.glsl +++ /dev/null @@ -1,58 +0,0 @@ - -/** - * Fullscreen pass that compute motion vector for static geometry. - * Animated geometry has already written correct motion vectors. - */ - -#pragma BLENDER_REQUIRE(common_view_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl) - -#define is_valid_output(img_) (imageSize(img_).x > 1) - -void main() -{ - ivec2 texel = ivec2(gl_GlobalInvocationID.xy); - vec4 motion = imageLoad(velocity_view_img, texel); - - bool pixel_has_valid_motion = (motion.x != VELOCITY_INVALID); - float depth = texelFetch(depth_tx, texel, 0).r; - bool is_background = (depth == 1.0f); - - vec2 uv = vec2(texel) * drw_view.viewport_size_inverse; - vec3 P_next, P_prev, P_curr; - - if (pixel_has_valid_motion) { - /* Animated geometry. View motion already computed during prepass. Convert only to camera. */ - // P_prev = get_world_space_from_depth(uv + motion.xy, 0.5); - // P_curr = get_world_space_from_depth(uv, 0.5); - // P_next = get_world_space_from_depth(uv + motion.zw, 0.5); - return; - } - else if (is_background) { - /* NOTE: Use viewCameraVec to avoid imprecision if camera is far from origin. */ - vec3 vV = viewCameraVec(get_view_space_from_depth(uv, 1.0)); - vec3 V = transform_direction(ViewMatrixInverse, vV); - /* Background has no motion under camera translation. Translate view vector with the camera. */ - /* WATCH(fclem): Might create precision issues. */ - P_next = camera_next.viewinv[3].xyz + V; - P_curr = camera_curr.viewinv[3].xyz + V; - P_prev = camera_prev.viewinv[3].xyz + V; - } - else { - /* Static geometry. No translation in world space. */ - P_curr = get_world_space_from_depth(uv, depth); - P_prev = P_curr; - P_next = P_curr; - } - - vec4 vel_camera, vel_view; - velocity_camera(P_prev, P_curr, P_next, vel_camera, vel_view); - - if (in_texture_range(texel, depth_tx)) { - imageStore(velocity_view_img, texel, vel_view); - - if (is_valid_output(velocity_camera_img)) { - imageStore(velocity_camera_img, texel, vel_camera); - } - } -} diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_film_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_film_info.hh index eec7b8ae615..a5baaca51f9 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_film_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_film_info.hh @@ -4,7 +4,7 @@ #include "gpu_shader_create_info.hh" GPU_SHADER_CREATE_INFO(eevee_film) - .uniform_buf(1, "FilmData", "film_buf") + .uniform_buf(4, "FilmData", "film_buf") .sampler(0, ImageType::DEPTH_2D, "depth_tx") .sampler(1, ImageType::FLOAT_2D, "combined_tx") .sampler(2, ImageType::FLOAT_2D, "normal_tx") @@ -20,15 +20,19 @@ GPU_SHADER_CREATE_INFO(eevee_film) .sampler(12, ImageType::FLOAT_2D, "ambient_occlusion_tx") .sampler(13, ImageType::FLOAT_2D_ARRAY, "aov_color_tx") .sampler(14, ImageType::FLOAT_2D_ARRAY, "aov_value_tx") + /* Color History for TAA needs to be sampler to leverage bilinear sampling. */ + .sampler(15, ImageType::FLOAT_2D, "in_combined_tx") // .sampler(15, ImageType::FLOAT_2D, "cryptomatte_tx") /* TODO */ .image(0, GPU_R32F, Qualifier::READ, ImageType::FLOAT_2D_ARRAY, "in_weight_img") .image(1, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_2D_ARRAY, "out_weight_img") - .image(2, GPU_RGBA16F, Qualifier::READ, ImageType::FLOAT_2D, "in_combined_img") + /* Color History for TAA needs to be sampler to leverage bilinear sampling. */ + //.image(2, GPU_RGBA16F, Qualifier::READ, ImageType::FLOAT_2D, "in_combined_img") .image(3, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_combined_img") .image(4, GPU_R32F, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "depth_img") .image(5, GPU_RGBA16F, Qualifier::READ_WRITE, ImageType::FLOAT_2D_ARRAY, "color_accum_img") .image(6, GPU_R16F, Qualifier::READ_WRITE, ImageType::FLOAT_2D_ARRAY, "value_accum_img") .additional_info("eevee_shared") + .additional_info("eevee_velocity_camera") .additional_info("draw_view"); GPU_SHADER_CREATE_INFO(eevee_film_frag) diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_velocity_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_velocity_info.hh index c6cbf9b1456..6e8e8fb020a 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_velocity_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_velocity_info.hh @@ -31,26 +31,7 @@ GPU_SHADER_CREATE_INFO(eevee_velocity_geom) .storage_buf( 7, Qualifier::READ, "VelocityIndex", "velocity_indirection_buf[]", Frequency::PASS) .vertex_out(eevee_velocity_surface_iface) - .fragment_out(0, Type::VEC4, "out_velocity_view") + .fragment_out(0, Type::VEC4, "out_velocity") .additional_info("eevee_velocity_camera"); /** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Velocity Resolve - * - * Computes velocity for static objects. - * Also converts motion to camera space (as opposed to view space) if needed. - * \{ */ - -GPU_SHADER_CREATE_INFO(eevee_velocity_resolve) - .do_static_compilation(true) - .local_group_size(8, 8) - .sampler(0, ImageType::DEPTH_2D, "depth_tx") - .image(0, GPU_RG16F, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "velocity_view_img") - .image(1, GPU_RG16F, Qualifier::WRITE, ImageType::FLOAT_2D, "velocity_camera_img") - .additional_info("eevee_shared") - .compute_source("eevee_velocity_resolve_comp.glsl") - .additional_info("draw_view", "eevee_velocity_camera"); - -/** \} */ -- cgit v1.2.3 From 9f00e138ac05acc504f0a8325ba1d21422e09ac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Thu, 21 Jul 2022 15:18:46 +0200 Subject: Cleanup: DRW: common_math_geom_lib.glsl: Fix variable name style --- .../draw/intern/shaders/common_math_geom_lib.glsl | 91 ++++++++++++---------- 1 file changed, 52 insertions(+), 39 deletions(-) diff --git a/source/blender/draw/intern/shaders/common_math_geom_lib.glsl b/source/blender/draw/intern/shaders/common_math_geom_lib.glsl index af8a8cd8784..ae82277d9a6 100644 --- a/source/blender/draw/intern/shaders/common_math_geom_lib.glsl +++ b/source/blender/draw/intern/shaders/common_math_geom_lib.glsl @@ -5,63 +5,71 @@ /** \name Math intersection & projection functions. * \{ */ -float point_plane_projection_dist(vec3 lineorigin, vec3 planeorigin, vec3 planenormal) +float point_plane_projection_dist(vec3 line_origin, vec3 plane_origin, vec3 plane_normal) { - return dot(planenormal, planeorigin - lineorigin); + return dot(plane_normal, plane_origin - line_origin); } -float line_plane_intersect_dist(vec3 lineorigin, - vec3 linedirection, - vec3 planeorigin, - vec3 planenormal) +float line_plane_intersect_dist(vec3 line_origin, + vec3 line_direction, + vec3 plane_origin, + vec3 plane_normal) { - return dot(planenormal, planeorigin - lineorigin) / dot(planenormal, linedirection); + return dot(plane_normal, plane_origin - line_origin) / dot(plane_normal, line_direction); } -float line_plane_intersect_dist(vec3 lineorigin, vec3 linedirection, vec4 plane) +float line_plane_intersect_dist(vec3 line_origin, vec3 line_direction, vec4 plane) { vec3 plane_co = plane.xyz * (-plane.w / len_squared(plane.xyz)); - vec3 h = lineorigin - plane_co; - return -dot(plane.xyz, h) / dot(plane.xyz, linedirection); + vec3 h = line_origin - plane_co; + return -dot(plane.xyz, h) / dot(plane.xyz, line_direction); } -vec3 line_plane_intersect(vec3 lineorigin, vec3 linedirection, vec3 planeorigin, vec3 planenormal) +vec3 line_plane_intersect(vec3 line_origin, + vec3 line_direction, + vec3 plane_origin, + vec3 plane_normal) { - float dist = line_plane_intersect_dist(lineorigin, linedirection, planeorigin, planenormal); - return lineorigin + linedirection * dist; + float dist = line_plane_intersect_dist(line_origin, line_direction, plane_origin, plane_normal); + return line_origin + line_direction * dist; } -vec3 line_plane_intersect(vec3 lineorigin, vec3 linedirection, vec4 plane) +vec3 line_plane_intersect(vec3 line_origin, vec3 line_direction, vec4 plane) { - float dist = line_plane_intersect_dist(lineorigin, linedirection, plane); - return lineorigin + linedirection * dist; + float dist = line_plane_intersect_dist(line_origin, line_direction, plane); + return line_origin + line_direction * dist; } -float line_aligned_plane_intersect_dist(vec3 lineorigin, vec3 linedirection, vec3 planeorigin) +float line_aligned_plane_intersect_dist(vec3 line_origin, vec3 line_direction, vec3 plane_origin) { /* aligned plane normal */ - vec3 L = planeorigin - lineorigin; - float diskdist = length(L); - vec3 planenormal = -normalize(L); - return -diskdist / dot(planenormal, linedirection); + vec3 L = plane_origin - line_origin; + float disk_dist = length(L); + vec3 plane_normal = -normalize(L); + return -disk_dist / dot(plane_normal, line_direction); } -vec3 line_aligned_plane_intersect(vec3 lineorigin, vec3 linedirection, vec3 planeorigin) +vec3 line_aligned_plane_intersect(vec3 line_origin, vec3 line_direction, vec3 plane_origin) { - float dist = line_aligned_plane_intersect_dist(lineorigin, linedirection, planeorigin); + float dist = line_aligned_plane_intersect_dist(line_origin, line_direction, plane_origin); if (dist < 0) { /* if intersection is behind we fake the intersection to be * really far and (hopefully) not inside the radius of interest */ dist = 1e16; } - return lineorigin + linedirection * dist; + return line_origin + line_direction * dist; } -float line_unit_sphere_intersect_dist(vec3 lineorigin, vec3 linedirection) +/** + * Returns intersection distance between the unit sphere and the line + * with the assumption that \a line_origin is contained in the unit sphere. + * It will always returns the farthest intersection. + */ +float line_unit_sphere_intersect_dist(vec3 line_origin, vec3 line_direction) { - float a = dot(linedirection, linedirection); - float b = dot(linedirection, lineorigin); - float c = dot(lineorigin, lineorigin) - 1; + float a = dot(line_direction, line_direction); + float b = dot(line_direction, line_origin); + float c = dot(line_origin, line_origin) - 1; float dist = 1e15; float determinant = b * b - a * c; @@ -72,22 +80,27 @@ float line_unit_sphere_intersect_dist(vec3 lineorigin, vec3 linedirection) return dist; } -float line_unit_box_intersect_dist(vec3 lineorigin, vec3 linedirection) +/** + * Returns minimum intersection distance between the unit box and the line + * with the assumption that \a line_origin is contained in the unit box. + * In other words, it will always returns the farthest intersection. + */ +float line_unit_box_intersect_dist(vec3 line_origin, vec3 line_direction) { /* https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/ */ - vec3 firstplane = (vec3(1.0) - lineorigin) / linedirection; - vec3 secondplane = (vec3(-1.0) - lineorigin) / linedirection; - vec3 furthestplane = max(firstplane, secondplane); + vec3 first_plane = (vec3(1.0) - line_origin) / line_direction; + vec3 second_plane = (vec3(-1.0) - line_origin) / line_direction; + vec3 farthest_plane = max(first_plane, second_plane); - return min_v3(furthestplane); + return min_v3(farthest_plane); } -float line_unit_box_intersect_dist_safe(vec3 lineorigin, vec3 linedirection) +float line_unit_box_intersect_dist_safe(vec3 line_origin, vec3 line_direction) { - vec3 safe_linedirection = max(vec3(1e-8), abs(linedirection)) * - select(vec3(1.0), -vec3(1.0), lessThan(linedirection, vec3(0.0))); - return line_unit_box_intersect_dist(lineorigin, safe_linedirection); + vec3 safe_line_direction = max(vec3(1e-8), abs(line_direction)) * + select(vec3(1.0), -vec3(1.0), lessThan(line_direction, vec3(0.0))); + return line_unit_box_intersect_dist(line_origin, safe_line_direction); } /** @@ -115,8 +128,8 @@ float line_aabb_clipping_dist(vec3 line_origin, vec3 line_direction, vec3 aabb_m void make_orthonormal_basis(vec3 N, out vec3 T, out vec3 B) { - vec3 UpVector = abs(N.z) < 0.99999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); - T = normalize(cross(UpVector, N)); + vec3 up_vector = abs(N.z) < 0.99999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + T = normalize(cross(up_vector, N)); B = cross(N, T); } -- cgit v1.2.3 From 92eb59341c93b49b603b072e672a276f3751b4a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Thu, 21 Jul 2022 15:30:41 +0200 Subject: EEVEE-Next: Filter NaN at output to avoid propagation. --- .../draw/engines/eevee_next/shaders/eevee_film_lib.glsl | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl index 1bafa26924e..451b8e8fca7 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl @@ -482,6 +482,11 @@ void film_store_combined( color.a = 1.0; } + /* Filter NaNs. */ + if (any(isnan(color))) { + color = vec4(0.0, 0.0, 0.0, 1.0); + } + if (film_buf.display_id == -1) { display = color; } @@ -498,6 +503,11 @@ void film_store_color(FilmSample dst, int pass_id, vec4 color, inout vec4 displa color = (data_film * dst.weight + color) * dst.weight_sum_inv; + /* Filter NaNs. */ + if (any(isnan(color))) { + color = vec4(0.0, 0.0, 0.0, 1.0); + } + if (film_buf.display_id == pass_id) { display = color; } @@ -514,6 +524,11 @@ void film_store_value(FilmSample dst, int pass_id, float value, inout vec4 displ value = (data_film * dst.weight + value) * dst.weight_sum_inv; + /* Filter NaNs. */ + if (isnan(value)) { + value = 0.0; + } + if (film_buf.display_id == pass_id) { display = vec4(value, value, value, 1.0); } -- cgit v1.2.3 From 412d93c2989f285a366f87200af2f4235598e559 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Thu, 21 Jul 2022 15:50:29 +0200 Subject: GPU: Fix compilation with WITH_GPU_BUILDTIME_SHADER_BUILDER option --- source/blender/gpu/intern/gpu_shader_builder.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/source/blender/gpu/intern/gpu_shader_builder.cc b/source/blender/gpu/intern/gpu_shader_builder.cc index fc99b892554..9b699c60126 100644 --- a/source/blender/gpu/intern/gpu_shader_builder.cc +++ b/source/blender/gpu/intern/gpu_shader_builder.cc @@ -51,7 +51,6 @@ void ShaderBuilder::init() void ShaderBuilder::exit() { - GPU_backend_exit(); GPU_exit(); GPU_context_discard(gpu_context_); -- cgit v1.2.3 From 396b7a6ec8fdcc7e8f14ba6694e24093744622f6 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 21 Jul 2022 09:34:48 -0500 Subject: Spreadsheet: Implement selection filter for curves sculpt mode The spreadsheet can retrieve the float selection using the same utilities as curves sculpt brushes. Theoretically this can work in original, evaluated, and viewer node modes, at least when the sculpt selection attributes are able to be propagated. Differential Revision: https://developer.blender.org/D15393 --- release/scripts/startup/bl_ui/space_spreadsheet.py | 6 +- source/blender/editors/include/ED_curves_sculpt.h | 25 ++++ .../editors/sculpt_paint/curves_sculpt_intern.hh | 8 +- .../sculpt_paint/curves_sculpt_selection.cc | 51 ++++++- .../spreadsheet_data_source_geometry.cc | 147 +++++++++++++-------- .../spreadsheet_data_source_geometry.hh | 4 - 6 files changed, 170 insertions(+), 71 deletions(-) diff --git a/release/scripts/startup/bl_ui/space_spreadsheet.py b/release/scripts/startup/bl_ui/space_spreadsheet.py index d6ee747f263..741ad544d67 100644 --- a/release/scripts/startup/bl_ui/space_spreadsheet.py +++ b/release/scripts/startup/bl_ui/space_spreadsheet.py @@ -95,8 +95,10 @@ class SPREADSHEET_HT_header(bpy.types.Header): obj = root_context.object if obj is None: return False - if obj.type != 'MESH' or obj.mode != 'EDIT': - return False + if obj.type == 'MESH': + return obj.mode == 'EDIT' + if obj.type == 'CURVES': + return obj.mode == 'SCULPT_CURVES' return True diff --git a/source/blender/editors/include/ED_curves_sculpt.h b/source/blender/editors/include/ED_curves_sculpt.h index 8aab1533e25..625af914280 100644 --- a/source/blender/editors/include/ED_curves_sculpt.h +++ b/source/blender/editors/include/ED_curves_sculpt.h @@ -10,8 +10,33 @@ extern "C" { #endif +struct Curves; + void ED_operatortypes_sculpt_curves(void); +#ifdef __cplusplus + +# include "BLI_index_mask.hh" +# include "BLI_vector.hh" + +namespace blender::ed::sculpt_paint { + +/** + * Find curves that have any point selected (a selection factor greater than zero), + * or curves that have their own selection factor greater than zero. + */ +IndexMask retrieve_selected_curves(const Curves &curves_id, Vector &r_indices); + +/** + * Find points that are selected (a selection factor greater than zero), + * or points in curves with a selection factor greater than zero). + */ +IndexMask retrieve_selected_points(const Curves &curves_id, Vector &r_indices); + +} // namespace blender::ed::sculpt_paint + +#endif + #ifdef __cplusplus } #endif diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh index c31bba2fe1e..61aa7d201b1 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh +++ b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh @@ -14,6 +14,8 @@ #include "BKE_attribute.h" #include "BKE_curves.hh" +#include "ED_curves_sculpt.h" + struct ARegion; struct RegionView3D; struct Depsgraph; @@ -98,12 +100,6 @@ VArray get_curves_selection(const Curves &curves_id); */ VArray get_point_selection(const Curves &curves_id); -/** - * Find curves that have any point selected (a selection factor greater than zero), - * or curves that have their own selection factor greater than zero. - */ -IndexMask retrieve_selected_curves(const Curves &curves_id, Vector &r_indices); - void move_last_point_and_resample(MutableSpan positions, const float3 &new_last_position); class CurvesSculptCommonContext { diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_selection.cc b/source/blender/editors/sculpt_paint/curves_sculpt_selection.cc index f620fed5761..5bfc8ccc667 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_selection.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_selection.cc @@ -6,6 +6,8 @@ #include "curves_sculpt_intern.hh" +#include "ED_curves_sculpt.h" + namespace blender::ed::sculpt_paint { static VArray get_curves_selection(const CurvesGeometry &curves, const eAttrDomain domain) @@ -62,7 +64,7 @@ static IndexMask retrieve_selected_curves(const CurvesGeometry &curves, case ATTR_DOMAIN_POINT: { const VArray selection = curves.selection_point_float(); if (selection.is_single()) { - return selection.get_internal_single() == 0.0f ? IndexMask(0) : + return selection.get_internal_single() <= 0.0f ? IndexMask(0) : IndexMask(curves.curves_num()); } return index_mask_ops::find_indices_based_on_predicate( @@ -78,7 +80,7 @@ static IndexMask retrieve_selected_curves(const CurvesGeometry &curves, case ATTR_DOMAIN_CURVE: { const VArray selection = curves.selection_curve_float(); if (selection.is_single()) { - return selection.get_internal_single() == 0.0f ? IndexMask(0) : + return selection.get_internal_single() <= 0.0f ? IndexMask(0) : IndexMask(curves.curves_num()); } return index_mask_ops::find_indices_based_on_predicate( @@ -102,4 +104,49 @@ IndexMask retrieve_selected_curves(const Curves &curves_id, Vector &r_i r_indices); } +static IndexMask retrieve_selected_points(const CurvesGeometry &curves, + const eAttrDomain domain, + Vector &r_indices) +{ + switch (domain) { + case ATTR_DOMAIN_POINT: { + const VArray selection = curves.selection_point_float(); + if (selection.is_single()) { + return selection.get_internal_single() <= 0.0f ? IndexMask(0) : + IndexMask(curves.points_num()); + } + return index_mask_ops::find_indices_based_on_predicate( + curves.points_range(), 2048, r_indices, [&](const int i) { + return selection[i] > 0.0f; + }); + } + case ATTR_DOMAIN_CURVE: { + const VArray selection = curves.selection_curve_float(); + if (selection.is_single()) { + return selection.get_internal_single() <= 0.0f ? IndexMask(0) : + IndexMask(curves.points_num()); + } + const VArray point_selection = curves.adapt_domain( + selection, ATTR_DOMAIN_CURVE, ATTR_DOMAIN_POINT); + return index_mask_ops::find_indices_based_on_predicate( + curves.points_range(), 2048, r_indices, [&](const int i) { + return point_selection[i] > 0.0f; + }); + } + default: + BLI_assert_unreachable(); + return {}; + } +} + +IndexMask retrieve_selected_points(const Curves &curves_id, Vector &r_indices) +{ + if (!(curves_id.flag & CV_SCULPT_SELECTION_ENABLED)) { + return CurvesGeometry::wrap(curves_id.geometry).points_range(); + } + return retrieve_selected_points(CurvesGeometry::wrap(curves_id.geometry), + eAttrDomain(curves_id.selection_domain), + r_indices); +} + } // namespace blender::ed::sculpt_paint diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc index c7653e94b4d..629a0b5802b 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc @@ -5,6 +5,7 @@ #include "BKE_attribute.hh" #include "BKE_context.h" +#include "BKE_curves.hh" #include "BKE_editmesh.h" #include "BKE_geometry_fields.hh" #include "BKE_global.h" @@ -22,6 +23,7 @@ #include "DEG_depsgraph_query.h" +#include "ED_curves_sculpt.h" #include "ED_spreadsheet.h" #include "NOD_geometry_nodes_eval_log.hh" @@ -232,23 +234,31 @@ int GeometryDataSource::tot_rows() const return attributes.domain_size(domain_); } -/** - * Only data sets corresponding to mesh objects in edit mode currently support selection filtering. - */ bool GeometryDataSource::has_selection_filter() const { Object *object_orig = DEG_get_original_object(object_eval_); - if (object_orig->type != OB_MESH) { - return false; - } - if (object_orig->mode != OB_MODE_EDIT) { - return false; - } - if (component_->type() != GEO_COMPONENT_TYPE_MESH) { - return false; + switch (component_->type()) { + case GEO_COMPONENT_TYPE_MESH: { + if (object_orig->type != OB_MESH) { + return false; + } + if (object_orig->mode != OB_MODE_EDIT) { + return false; + } + return true; + } + case GEO_COMPONENT_TYPE_CURVE: { + if (object_orig->type != OB_CURVES) { + return false; + } + if (object_orig->mode != OB_MODE_SCULPT_CURVES) { + return false; + } + return true; + } + default: + return false; } - - return true; } IndexMask GeometryDataSource::apply_selection_filter(Vector &indices) const @@ -256,50 +266,73 @@ IndexMask GeometryDataSource::apply_selection_filter(Vector &indices) c std::lock_guard lock{mutex_}; const IndexMask full_range(this->tot_rows()); - BLI_assert(object_eval_->mode == OB_MODE_EDIT); - BLI_assert(component_->type() == GEO_COMPONENT_TYPE_MESH); - Object *object_orig = DEG_get_original_object(object_eval_); - const MeshComponent *mesh_component = static_cast(component_); - const Mesh *mesh_eval = mesh_component->get_for_read(); - Mesh *mesh_orig = (Mesh *)object_orig->data; - BMesh *bm = mesh_orig->edit_mesh->bm; - BM_mesh_elem_table_ensure(bm, BM_VERT); - - const int *orig_indices = (int *)CustomData_get_layer(&mesh_eval->vdata, CD_ORIGINDEX); - if (orig_indices != nullptr) { - /* Use CD_ORIGINDEX layer if it exists. */ - VArray selection = mesh_component->attributes()->adapt_domain( - VArray::ForFunc(mesh_eval->totvert, - [bm, orig_indices](int vertex_index) -> bool { - const int i_orig = orig_indices[vertex_index]; - if (i_orig < 0) { - return false; - } - if (i_orig >= bm->totvert) { - return false; - } - BMVert *vert = bm->vtable[i_orig]; - return BM_elem_flag_test(vert, BM_ELEM_SELECT); - }), - ATTR_DOMAIN_POINT, - domain_); - return index_mask_ops::find_indices_from_virtual_array(full_range, selection, 1024, indices); - } - - if (mesh_eval->totvert == bm->totvert) { - /* Use a simple heuristic to match original vertices to evaluated ones. */ - VArray selection = mesh_component->attributes()->adapt_domain( - VArray::ForFunc(mesh_eval->totvert, - [bm](int vertex_index) -> bool { - BMVert *vert = bm->vtable[vertex_index]; - return BM_elem_flag_test(vert, BM_ELEM_SELECT); - }), - ATTR_DOMAIN_POINT, - domain_); - return index_mask_ops::find_indices_from_virtual_array(full_range, selection, 2048, indices); - } - - return full_range; + switch (component_->type()) { + case GEO_COMPONENT_TYPE_MESH: { + BLI_assert(object_eval_->type == OB_MESH); + BLI_assert(object_eval_->mode == OB_MODE_EDIT); + Object *object_orig = DEG_get_original_object(object_eval_); + const Mesh *mesh_eval = geometry_set_.get_mesh_for_read(); + const bke::AttributeAccessor attributes_eval = bke::mesh_attributes(*mesh_eval); + Mesh *mesh_orig = (Mesh *)object_orig->data; + BMesh *bm = mesh_orig->edit_mesh->bm; + BM_mesh_elem_table_ensure(bm, BM_VERT); + + const int *orig_indices = (int *)CustomData_get_layer(&mesh_eval->vdata, CD_ORIGINDEX); + if (orig_indices != nullptr) { + /* Use CD_ORIGINDEX layer if it exists. */ + VArray selection = attributes_eval.adapt_domain( + VArray::ForFunc(mesh_eval->totvert, + [bm, orig_indices](int vertex_index) -> bool { + const int i_orig = orig_indices[vertex_index]; + if (i_orig < 0) { + return false; + } + if (i_orig >= bm->totvert) { + return false; + } + const BMVert *vert = BM_vert_at_index(bm, i_orig); + return BM_elem_flag_test(vert, BM_ELEM_SELECT); + }), + ATTR_DOMAIN_POINT, + domain_); + return index_mask_ops::find_indices_from_virtual_array( + full_range, selection, 1024, indices); + } + + if (mesh_eval->totvert == bm->totvert) { + /* Use a simple heuristic to match original vertices to evaluated ones. */ + VArray selection = attributes_eval.adapt_domain( + VArray::ForFunc(mesh_eval->totvert, + [bm](int vertex_index) -> bool { + const BMVert *vert = BM_vert_at_index(bm, vertex_index); + return BM_elem_flag_test(vert, BM_ELEM_SELECT); + }), + ATTR_DOMAIN_POINT, + domain_); + return index_mask_ops::find_indices_from_virtual_array( + full_range, selection, 2048, indices); + } + + return full_range; + } + case GEO_COMPONENT_TYPE_CURVE: { + BLI_assert(object_eval_->type == OB_CURVES); + BLI_assert(object_eval_->mode == OB_MODE_SCULPT_CURVES); + const CurveComponent &component = static_cast(*component_); + const Curves &curves_id = *component.get_for_read(); + switch (domain_) { + case ATTR_DOMAIN_POINT: + return sculpt_paint::retrieve_selected_points(curves_id, indices); + case ATTR_DOMAIN_CURVE: + return sculpt_paint::retrieve_selected_curves(curves_id, indices); + default: + BLI_assert_unreachable(); + } + return full_range; + } + default: + return full_range; + } } void VolumeDataSource::foreach_default_column_ids( diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh index 04b4f6d8d06..71bc4768949 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh @@ -68,10 +68,6 @@ class GeometryDataSource : public DataSource { return object_eval_; } - /** - * Only data sets corresponding to mesh objects in edit mode currently support selection - * filtering. - */ bool has_selection_filter() const override; IndexMask apply_selection_filter(Vector &indices) const; -- cgit v1.2.3 From b0f9639733500e7c3deedc36c2ef6b9685a756ee Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Thu, 21 Jul 2022 16:36:06 +0200 Subject: Fix crash due to improper handling of new library runtime name_map data on read/write. Code handling read/write of libraries is still particular... but trying to call `library_runtime_reset` on a random address at readtime was an obvious mistake I should have caught during review :( Regression from rB7f8d05131a77. --- source/blender/blenkernel/intern/library.c | 12 ++---------- source/blender/blenloader/intern/writefile.c | 5 +++++ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/source/blender/blenkernel/intern/library.c b/source/blender/blenkernel/intern/library.c index fee4cae2701..dd58c9cc4fe 100644 --- a/source/blender/blenkernel/intern/library.c +++ b/source/blender/blenkernel/intern/library.c @@ -73,18 +73,10 @@ static void library_foreach_path(ID *id, BPathForeachPathData *bpath_data) } } -static void library_blend_write(struct BlendWriter *UNUSED(writer), - ID *id, - const void *UNUSED(id_address)) -{ - Library *lib = (Library *)id; - library_runtime_reset(lib); -} - static void library_blend_read_data(struct BlendDataReader *UNUSED(reader), ID *id) { Library *lib = (Library *)id; - library_runtime_reset(lib); + lib->runtime.name_map = NULL; } IDTypeInfo IDType_ID_LI = { @@ -107,7 +99,7 @@ IDTypeInfo IDType_ID_LI = { .foreach_path = library_foreach_path, .owner_get = NULL, - .blend_write = library_blend_write, + .blend_write = NULL, .blend_read_data = library_blend_read_data, .blend_read_lib = NULL, .blend_read_expand = NULL, diff --git a/source/blender/blenloader/intern/writefile.c b/source/blender/blenloader/intern/writefile.c index 68171f26a66..1ec056a9f50 100644 --- a/source/blender/blenloader/intern/writefile.c +++ b/source/blender/blenloader/intern/writefile.c @@ -970,10 +970,15 @@ static void write_libraries(WriteData *wd, Main *main) if (found_one) { /* Not overridable. */ + void *runtime_name_data = main->curlib->runtime.name_map; + main->curlib->runtime.name_map = NULL; + BlendWriter writer = {wd}; writestruct(wd, ID_LI, Library, 1, main->curlib); BKE_id_blend_write(&writer, &main->curlib->id); + main->curlib->runtime.name_map = runtime_name_data; + if (main->curlib->packedfile) { BKE_packedfile_blend_write(&writer, main->curlib->packedfile); if (wd->use_memfile == false) { -- cgit v1.2.3 From d431b1416b6eefa96d343a4d49c2871720393c7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Thu, 21 Jul 2022 16:05:51 +0200 Subject: EEVEE-Next: Add back option to disable TAA (Viewport Denoising --- release/scripts/startup/bl_ui/properties_render.py | 3 +++ source/blender/draw/engines/eevee_next/eevee_film.cc | 8 ++++++++ source/blender/draw/engines/eevee_next/eevee_film.hh | 2 ++ 3 files changed, 13 insertions(+) diff --git a/release/scripts/startup/bl_ui/properties_render.py b/release/scripts/startup/bl_ui/properties_render.py index 148dd3ce22d..f217df9b599 100644 --- a/release/scripts/startup/bl_ui/properties_render.py +++ b/release/scripts/startup/bl_ui/properties_render.py @@ -410,6 +410,9 @@ class RENDER_PT_eevee_next_sampling(RenderButtonsPanel, Panel): col.prop(props, "taa_render_samples", text="Render") col.prop(props, "taa_samples", text="Viewport") + col = layout.column() + col.prop(props, "use_taa_reprojection") + class RENDER_PT_eevee_indirect_lighting(RenderButtonsPanel, Panel): bl_label = "Indirect Lighting" diff --git a/source/blender/draw/engines/eevee_next/eevee_film.cc b/source/blender/draw/engines/eevee_next/eevee_film.cc index a1becaed9c4..d3b09beedaa 100644 --- a/source/blender/draw/engines/eevee_next/eevee_film.cc +++ b/source/blender/draw/engines/eevee_next/eevee_film.cc @@ -345,6 +345,8 @@ void Film::init(const int2 &extent, const rcti *output_rect) depth_tx_.clear(float4(0.0f)); } } + + force_disable_reprojection_ = (inst_.scene->eevee.flag & SCE_EEVEE_TAA_REPROJECTION) == 0; } void Film::sync() @@ -415,6 +417,12 @@ void Film::end_sync() { data_.use_reprojection = inst_.sampling.interactive_mode(); + /* Just bypass the reprojection and reset the accumulation. */ + if (force_disable_reprojection_ && inst_.sampling.is_reset()) { + data_.use_reprojection = false; + data_.use_history = false; + } + aovs_info.push_update(); sync_mist(); diff --git a/source/blender/draw/engines/eevee_next/eevee_film.hh b/source/blender/draw/engines/eevee_next/eevee_film.hh index c8ffa0e62c9..1be95b3ac6b 100644 --- a/source/blender/draw/engines/eevee_next/eevee_film.hh +++ b/source/blender/draw/engines/eevee_next/eevee_film.hh @@ -49,6 +49,8 @@ class Film { SwapChain weight_tx_; /** Extent used by the render buffers when rendering the main views. */ int2 render_extent_ = int2(-1); + /** User setting to disable reprojection. Useful for debugging or have a more precise render. */ + bool force_disable_reprojection_ = false; DRWPass *accumulate_ps_ = nullptr; -- cgit v1.2.3 From ef5b435e8fa3e244a5f68bdde999dea8e6b8bc92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Thu, 21 Jul 2022 16:41:41 +0200 Subject: DRW: Volume: Fix crash in command line render caused by null textures This was caused by the world volume shader needing placeholder textures that were not available until cache populate begins. Adding a check and creating on the fly fixes the issue. --- source/blender/draw/intern/draw_volume.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/blender/draw/intern/draw_volume.cc b/source/blender/draw/intern/draw_volume.cc index c4e58ab24cb..8f4383a98d8 100644 --- a/source/blender/draw/intern/draw_volume.cc +++ b/source/blender/draw/intern/draw_volume.cc @@ -89,6 +89,10 @@ void DRW_volume_free(void) static GPUTexture *grid_default_texture(eGPUDefaultValue default_value) { + if (g_data.dummy_one == nullptr) { + drw_volume_globals_init(); + } + switch (default_value) { case GPU_DEFAULT_0: return g_data.dummy_zero; -- cgit v1.2.3 From a36f029459f34acb457c566d0d95e058122623f1 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Thu, 21 Jul 2022 18:11:13 +0200 Subject: Fix crash in some very rare case in remapping code. Actualy 'safe' building of the base has in view layers (as part of `BKE_main_collection_sync_remap`) would only happen when there was already an existing one, otherwise it was skipped, and rebuilt later (without the support for doublons) in collection sync code. Very odd that that error was never spotted before, issue in code has been there for a long time already. Probably only happens in rare cases (specific conjuction of factors during remapping of old ID into itelf new id)? Reported by @hjalti from Blender studio. Reproducing case: `heist/pro/shots/050_alarm/050_0160/050_0160.anim.blend`, r1407 --- source/blender/blenkernel/intern/layer.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/blender/blenkernel/intern/layer.c b/source/blender/blenkernel/intern/layer.c index 534ff7f1fbc..ac582ff69ca 100644 --- a/source/blender/blenkernel/intern/layer.c +++ b/source/blender/blenkernel/intern/layer.c @@ -1377,12 +1377,12 @@ void BKE_main_collection_sync_remap(const Main *bmain) if (view_layer->object_bases_hash) { BLI_ghash_free(view_layer->object_bases_hash, NULL, NULL); view_layer->object_bases_hash = NULL; - - /* Directly re-create the mapping here, so that we can also deal with duplicates in - * `view_layer->object_bases` list of bases properly. This is the only place where such - * duplicates should be fixed, and not considered as a critical error. */ - view_layer_bases_hash_create(view_layer, true); } + + /* Directly re-create the mapping here, so that we can also deal with duplicates in + * `view_layer->object_bases` list of bases properly. This is the only place where such + * duplicates should be fixed, and not considered as a critical error. */ + view_layer_bases_hash_create(view_layer, true); } BKE_collection_object_cache_free(scene->master_collection); -- cgit v1.2.3 From 611be46cc9443c673fb1517de3ebae53bdedd4f4 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Thu, 21 Jul 2022 18:58:04 +0200 Subject: Cleanup: compiler warning --- source/blender/editors/include/ED_curves_sculpt.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/blender/editors/include/ED_curves_sculpt.h b/source/blender/editors/include/ED_curves_sculpt.h index 625af914280..b1c0b649d2b 100644 --- a/source/blender/editors/include/ED_curves_sculpt.h +++ b/source/blender/editors/include/ED_curves_sculpt.h @@ -14,6 +14,10 @@ struct Curves; void ED_operatortypes_sculpt_curves(void); +#ifdef __cplusplus +} +#endif + #ifdef __cplusplus # include "BLI_index_mask.hh" @@ -36,7 +40,3 @@ IndexMask retrieve_selected_points(const Curves &curves_id, Vector &r_i } // namespace blender::ed::sculpt_paint #endif - -#ifdef __cplusplus -} -#endif -- cgit v1.2.3 From a5c2d0018cd9a5844d618aaed21f64f11f35a97a Mon Sep 17 00:00:00 2001 From: Sebastiano Barrera Date: Thu, 21 Jul 2022 18:51:36 +0200 Subject: Fix T91932: number sliders wrap around when dragged for long distance on X11 The value of number sliders (e.g. the "end frame" button) wrap around to their pre-click value when dragging them for a very long distance (e.g. by lifting the mouse off the desk and placing it back on to keep dragging in the same direction). The problem is X11-specific, and due to XTranslateCoordinates using a signed int16 behind the curtains, while its signature and the rest of Blender uses int32. The solution is to only use XTranslateCoordinates on (0, 0) to get the delta between the screen and client reference systems, and applying the delta in a second step. Differential Revision: https://developer.blender.org/D15507 --- intern/ghost/intern/GHOST_WindowX11.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/intern/ghost/intern/GHOST_WindowX11.cpp b/intern/ghost/intern/GHOST_WindowX11.cpp index 01045c516c1..2276e90387b 100644 --- a/intern/ghost/intern/GHOST_WindowX11.cpp +++ b/intern/ghost/intern/GHOST_WindowX11.cpp @@ -658,15 +658,15 @@ GHOST_TSuccess GHOST_WindowX11::setClientSize(uint32_t width, uint32_t height) void GHOST_WindowX11::screenToClient(int32_t inX, int32_t inY, int32_t &outX, int32_t &outY) const { - /* This is correct! */ - int ax, ay; Window temp; + /* Use (0, 0) instead of (inX, inY) to work around overflow of signed int16 in + * the implementation of this function. */ XTranslateCoordinates( - m_display, RootWindow(m_display, m_visualInfo->screen), m_window, inX, inY, &ax, &ay, &temp); - outX = ax; - outY = ay; + m_display, RootWindow(m_display, m_visualInfo->screen), m_window, 0, 0, &ax, &ay, &temp); + outX = ax + inX; + outY = ay + inY; } void GHOST_WindowX11::clientToScreen(int32_t inX, int32_t inY, int32_t &outX, int32_t &outY) const -- cgit v1.2.3 From ada601251889c2344a4a89c269cc85877eeb9ebc Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 21 Jul 2022 12:13:25 -0500 Subject: Fix T99854: Crash converting legacy NURBS curves to new type Creating the attributes was done inside a parallel loop. Also correct a typo for the parallel grain size, which was meant to be a power of two. --- source/blender/blenkernel/intern/curve_legacy_convert.cc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/source/blender/blenkernel/intern/curve_legacy_convert.cc b/source/blender/blenkernel/intern/curve_legacy_convert.cc index ff5bbc32afe..5c62f292832 100644 --- a/source/blender/blenkernel/intern/curve_legacy_convert.cc +++ b/source/blender/blenkernel/intern/curve_legacy_convert.cc @@ -115,7 +115,7 @@ Curves *curve_legacy_to_curves(const Curve &curve_legacy, const ListBase &nurbs_ MutableSpan tilts = curves.tilt_for_write(); auto create_poly = [&](IndexMask selection) { - threading::parallel_for(selection.index_range(), 246, [&](IndexRange range) { + threading::parallel_for(selection.index_range(), 256, [&](IndexRange range) { for (const int curve_i : selection.slice(range)) { const Nurb &src_curve = *src_curves[curve_i]; const Span src_points(src_curve.bp, src_curve.pntsu); @@ -142,7 +142,7 @@ Curves *curve_legacy_to_curves(const Curve &curve_legacy, const ListBase &nurbs_ MutableSpan handle_types_l = curves.handle_types_left_for_write(); MutableSpan handle_types_r = curves.handle_types_right_for_write(); - threading::parallel_for(selection.index_range(), 246, [&](IndexRange range) { + threading::parallel_for(selection.index_range(), 256, [&](IndexRange range) { for (const int curve_i : selection.slice(range)) { const Nurb &src_curve = *src_curves[curve_i]; const Span src_points(src_curve.bezt, src_curve.pntsu); @@ -165,12 +165,12 @@ Curves *curve_legacy_to_curves(const Curve &curve_legacy, const ListBase &nurbs_ }; auto create_nurbs = [&](IndexMask selection) { - threading::parallel_for(selection.index_range(), 246, [&](IndexRange range) { - MutableSpan resolutions = curves.resolution_for_write(); - MutableSpan nurbs_weights = curves.nurbs_weights_for_write(); - MutableSpan nurbs_orders = curves.nurbs_orders_for_write(); - MutableSpan nurbs_knots_modes = curves.nurbs_knots_modes_for_write(); - + MutableSpan resolutions = curves.resolution_for_write(); + MutableSpan nurbs_weights = curves.nurbs_weights_for_write(); + MutableSpan nurbs_orders = curves.nurbs_orders_for_write(); + MutableSpan nurbs_knots_modes = curves.nurbs_knots_modes_for_write(); + + threading::parallel_for(selection.index_range(), 256, [&](IndexRange range) { for (const int curve_i : selection.slice(range)) { const Nurb &src_curve = *src_curves[curve_i]; const Span src_points(src_curve.bp, src_curve.pntsu); -- cgit v1.2.3 From 7a4a6ccad74f9a2094e9f0928f5ac61ffd6346ff Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 21 Jul 2022 17:21:56 -0500 Subject: Cleanups: Small changes to armature deform Use const pointers, remove unused data member for parallel callback, use listbase macro. --- source/blender/blenkernel/intern/armature_deform.c | 30 ++++++++++------------ 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/source/blender/blenkernel/intern/armature_deform.c b/source/blender/blenkernel/intern/armature_deform.c index 0769049e9a9..8532c7d1c15 100644 --- a/source/blender/blenkernel/intern/armature_deform.c +++ b/source/blender/blenkernel/intern/armature_deform.c @@ -159,9 +159,9 @@ float distfactor_to_bone( } static float dist_bone_deform( - bPoseChannel *pchan, float vec[3], DualQuat *dq, float mat[3][3], const float co[3]) + const bPoseChannel *pchan, float vec[3], DualQuat *dq, float mat[3][3], const float co[3]) { - Bone *bone = pchan->bone; + const Bone *bone = pchan->bone; float fac, contrib = 0.0; if (bone == NULL) { @@ -188,7 +188,7 @@ static float dist_bone_deform( return contrib; } -static void pchan_bone_deform(bPoseChannel *pchan, +static void pchan_bone_deform(const bPoseChannel *pchan, float weight, float vec[3], DualQuat *dq, @@ -196,7 +196,7 @@ static void pchan_bone_deform(bPoseChannel *pchan, const float co[3], float *contrib) { - Bone *bone = pchan->bone; + const Bone *bone = pchan->bone; if (!weight) { return; @@ -223,7 +223,6 @@ static void pchan_bone_deform(bPoseChannel *pchan, typedef struct ArmatureUserdata { const Object *ob_arm; - const Object *ob_target; const Mesh *me_target; float (*vert_coords)[3]; float (*vert_deform_mats)[3][3]; @@ -264,7 +263,7 @@ static void armature_vert_task_with_dvert(const ArmatureUserdata *data, const int armature_def_nr = data->armature_def_nr; DualQuat sumdq, *dq = NULL; - bPoseChannel *pchan; + const bPoseChannel *pchan; float *co, dco[3]; float sumvec[3], summat[3][3]; float *vec = NULL, (*smat)[3] = NULL; @@ -319,7 +318,7 @@ static void armature_vert_task_with_dvert(const ArmatureUserdata *data, const uint index = dw->def_nr; if (index < data->defbase_len && (pchan = data->pchan_from_defbase[index])) { float weight = dw->weight; - Bone *bone = pchan->bone; + const Bone *bone = pchan->bone; deformed = 1; @@ -434,7 +433,7 @@ static void armature_vert_task_editmesh(void *__restrict userdata, { const ArmatureUserdata *data = userdata; BMVert *v = (BMVert *)iter; - MDeformVert *dvert = BM_ELEM_CD_GET_VOID_P(v, data->bmesh.cd_dvert_offset); + const MDeformVert *dvert = BM_ELEM_CD_GET_VOID_P(v, data->bmesh.cd_dvert_offset); armature_vert_task_with_dvert(data, BM_elem_index_get(v), dvert); } @@ -459,15 +458,14 @@ static void armature_deform_coords_impl(const Object *ob_arm, BMEditMesh *em_target, bGPDstroke *gps_target) { - bArmature *arm = ob_arm->data; + const bArmature *arm = ob_arm->data; bPoseChannel **pchan_from_defbase = NULL; const MDeformVert *dverts = NULL; - bDeformGroup *dg; const bool use_envelope = (deformflag & ARM_DEF_ENVELOPE) != 0; const bool use_quaternion = (deformflag & ARM_DEF_QUATERNION) != 0; const bool invert_vgroup = (deformflag & ARM_DEF_INVERT_VGROUP) != 0; - int defbase_len = 0; /* safety for vertexgroup index overflow */ - int i, dverts_len = 0; /* safety for vertexgroup overflow */ + int defbase_len = 0; /* safety for vertexgroup index overflow */ + int dverts_len = 0; /* safety for vertexgroup overflow */ bool use_dverts = false; int armature_def_nr = -1; int cd_dvert_offset = -1; @@ -492,7 +490,7 @@ static void armature_deform_coords_impl(const Object *ob_arm, if (ob_target->type == OB_MESH) { if (em_target == NULL) { - Mesh *me = ob_target->data; + const Mesh *me = ob_target->data; dverts = me->dvert; if (dverts) { dverts_len = me->totvert; @@ -500,7 +498,7 @@ static void armature_deform_coords_impl(const Object *ob_arm, } } else if (ob_target->type == OB_LATTICE) { - Lattice *lt = ob_target->data; + const Lattice *lt = ob_target->data; dverts = lt->dvert; if (dverts) { dverts_len = lt->pntsu * lt->pntsv * lt->pntsw; @@ -534,7 +532,8 @@ static void armature_deform_coords_impl(const Object *ob_arm, * - Check whether keeping this consistent across frames gives speedup. */ const ListBase *defbase = BKE_object_defgroup_list(ob_target); - for (i = 0, dg = defbase->first; dg; i++, dg = dg->next) { + int i; + LISTBASE_FOREACH_INDEX (bDeformGroup *, dg, defbase, i) { pchan_from_defbase[i] = BKE_pose_channel_find_name(ob_arm->pose, dg->name); /* exclude non-deforming bones */ if (pchan_from_defbase[i]) { @@ -549,7 +548,6 @@ static void armature_deform_coords_impl(const Object *ob_arm, ArmatureUserdata data = { .ob_arm = ob_arm, - .ob_target = ob_target, .me_target = me_target, .vert_coords = vert_coords, .vert_deform_mats = vert_deform_mats, -- cgit v1.2.3 From aa1ffc093c4711a40932c854670730944008118b Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 21 Jul 2022 19:44:06 -0500 Subject: Fix T99884: Crash when converting to old curve type The conversion from Curves to CurveEval used an incorrect type for one of the builtin attributes. Also, an incorrect default was used for reading the nurbs_weight attribute. --- source/blender/blenkernel/intern/curve_eval.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc index 424fa311dc7..3bee82fadab 100644 --- a/source/blender/blenkernel/intern/curve_eval.cc +++ b/source/blender/blenkernel/intern/curve_eval.cc @@ -382,7 +382,7 @@ std::unique_ptr curves_to_curve_eval(const Curves &curves_id) VArray normal_mode = curves.normal_mode(); VArraySpan nurbs_weights{ - src_attributes.lookup_or_default("nurbs_weight", ATTR_DOMAIN_POINT, 0.0f)}; + src_attributes.lookup_or_default("nurbs_weight", ATTR_DOMAIN_POINT, 1.0f)}; VArraySpan nurbs_orders{ src_attributes.lookup_or_default("nurbs_order", ATTR_DOMAIN_CURVE, 4)}; VArraySpan nurbs_knots_modes{ @@ -475,13 +475,13 @@ Curves *curve_eval_to_curves(const CurveEval &curve_eval) blender::bke::SpanAttributeWriter normal_mode = dst_attributes.lookup_or_add_for_write_only_span("normal_mode", ATTR_DOMAIN_CURVE); blender::bke::SpanAttributeWriter nurbs_weight; - blender::bke::SpanAttributeWriter nurbs_order; + blender::bke::SpanAttributeWriter nurbs_order; blender::bke::SpanAttributeWriter nurbs_knots_mode; if (curve_eval.has_spline_with_type(CURVE_TYPE_NURBS)) { nurbs_weight = dst_attributes.lookup_or_add_for_write_only_span("nurbs_weight", ATTR_DOMAIN_POINT); - nurbs_order = dst_attributes.lookup_or_add_for_write_only_span("nurbs_order", - ATTR_DOMAIN_CURVE); + nurbs_order = dst_attributes.lookup_or_add_for_write_only_span("nurbs_order", + ATTR_DOMAIN_CURVE); nurbs_knots_mode = dst_attributes.lookup_or_add_for_write_only_span("knots_mode", ATTR_DOMAIN_CURVE); } -- cgit v1.2.3 From 77257405437336dbd91a481926013f8c747cacae Mon Sep 17 00:00:00 2001 From: Siddhartha Jejurkar Date: Fri, 22 Jul 2022 10:47:28 +1000 Subject: UV: Edge support for select shortest path operator Calculating shortest path selection in UV edge mode was done using vertex path logic. Since the UV editor now supports proper edge selection [0], this approach can sometimes give incorrect results. This problem is now fixed by adding separate logic to calculate the shortest path in UV edge mode. Resolves T99344. [0]: ffaaa0bcbf477c30cf3665b9330bbbb767397169 Reviewed By: campbellbarton Ref D15511. --- source/blender/bmesh/tools/bmesh_path_uv.c | 203 ++++++++++++++++++- source/blender/bmesh/tools/bmesh_path_uv.h | 8 + source/blender/editors/uvedit/uvedit_path.c | 299 ++++++++++++++-------------- 3 files changed, 351 insertions(+), 159 deletions(-) diff --git a/source/blender/bmesh/tools/bmesh_path_uv.c b/source/blender/bmesh/tools/bmesh_path_uv.c index 76697f51ac7..3d736cdc3b8 100644 --- a/source/blender/bmesh/tools/bmesh_path_uv.c +++ b/source/blender/bmesh/tools/bmesh_path_uv.c @@ -47,9 +47,7 @@ static float step_cost_3_v2_ex( return cost * (1.0f + 0.5f * (2.0f - sqrtf(fabsf(dot_v2v2(d1, d2))))); } -static float UNUSED_FUNCTION(step_cost_3_v2)(const float v1[2], - const float v2[2], - const float v3[2]) +static float step_cost_3_v2(const float v1[2], const float v2[2], const float v3[2]) { return step_cost_3_v2_ex(v1, v2, v3, false, false); } @@ -60,7 +58,7 @@ static float UNUSED_FUNCTION(step_cost_3_v2)(const float v1[2], /** \name BM_mesh_calc_path_uv_vert * \{ */ -static void looptag_add_adjacent_uv(HeapSimple *heap, +static void verttag_add_adjacent_uv(HeapSimple *heap, BMLoop *l_a, BMLoop **loops_prev, float *cost, @@ -162,7 +160,7 @@ struct LinkNode *BM_mesh_calc_path_uv_vert(BMesh *bm, if (!BM_elem_flag_test(l, BM_ELEM_TAG)) { /* Adjacent loops are tagged while stepping to avoid 2x loops. */ BM_elem_flag_enable(l, BM_ELEM_TAG); - looptag_add_adjacent_uv(heap, l, loops_prev, cost, params); + verttag_add_adjacent_uv(heap, l, loops_prev, cost, params); } } @@ -185,8 +183,199 @@ struct LinkNode *BM_mesh_calc_path_uv_vert(BMesh *bm, /** \name BM_mesh_calc_path_uv_edge * \{ */ -/* TODO(@sidd017): Setting this as todo, since we now support proper UV edge selection (D12028). - * Till then, continue using vertex path to fake shortest path calculation for edges. */ +static float edgetag_cut_cost_vert_uv( + BMLoop *l_e_a, BMLoop *l_e_b, BMLoop *l_v, const float aspect_y, const int cd_loop_uv_offset) +{ + BMLoop *l_v1 = (l_v->v == l_e_a->v) ? l_e_a->next : l_e_a; + BMLoop *l_v2 = (l_v->v == l_e_b->v) ? l_e_b->next : l_e_b; + + MLoopUV *luv_v1 = BM_ELEM_CD_GET_VOID_P(l_v1, cd_loop_uv_offset); + MLoopUV *luv_v2 = BM_ELEM_CD_GET_VOID_P(l_v2, cd_loop_uv_offset); + MLoopUV *luv_v = BM_ELEM_CD_GET_VOID_P(l_v, cd_loop_uv_offset); + + float uv_v1[2] = {luv_v1->uv[0], luv_v1->uv[1] / aspect_y}; + float uv_v2[2] = {luv_v2->uv[0], luv_v2->uv[1] / aspect_y}; + float uv_v[2] = {luv_v->uv[0], luv_v->uv[1] / aspect_y}; + + return step_cost_3_v2(uv_v1, uv_v, uv_v2); +} + +static float edgetag_cut_cost_face_uv( + BMLoop *l_e_a, BMLoop *l_e_b, BMFace *f, const float aspect_v2[2], const int cd_loop_uv_offset) +{ + float l_e_a_cent[2], l_e_b_cent[2], f_cent[2]; + MLoopUV *luv_e_a = BM_ELEM_CD_GET_VOID_P(l_e_a, cd_loop_uv_offset); + MLoopUV *luv_e_b = BM_ELEM_CD_GET_VOID_P(l_e_b, cd_loop_uv_offset); + + mid_v2_v2v2(l_e_a_cent, luv_e_a->uv, luv_e_a->uv); + mid_v2_v2v2(l_e_b_cent, luv_e_b->uv, luv_e_b->uv); + + mul_v2_v2(l_e_a_cent, aspect_v2); + mul_v2_v2(l_e_b_cent, aspect_v2); + + BM_face_uv_calc_center_median_weighted(f, aspect_v2, cd_loop_uv_offset, f_cent); + + return step_cost_3_v2(l_e_a_cent, l_e_b_cent, f_cent); +} + +static void edgetag_add_adjacent_uv(HeapSimple *heap, + BMLoop *l_a, + BMLoop **loops_prev, + float *cost, + const struct BMCalcPathUVParams *params) +{ + BLI_assert(params->aspect_y != 0.0f); + const uint cd_loop_uv_offset = params->cd_loop_uv_offset; + BMLoop *l_a_verts[2] = {l_a, l_a->next}; + const int l_a_index = BM_elem_index_get(l_a); + + if (params->use_step_face == false) { + for (int i = 0; i < ARRAY_SIZE(l_a_verts); i++) { + + /* Skip current UV vert if it is part of the previous UV edge in the path. */ + if (loops_prev[l_a_index]) { + BMLoop *l_prev = loops_prev[l_a_index]; + if (l_a_verts[i]->v != l_prev->v) { + l_prev = (l_a_verts[i]->v == l_prev->next->v) ? l_prev->next : NULL; + } + if (l_prev && BM_loop_uv_share_vert_check(l_a_verts[i], l_prev, cd_loop_uv_offset)) { + continue; + } + } + + BMEdge *e_b; + BMIter eiter; + BM_ITER_ELEM (e_b, &eiter, l_a_verts[i]->v, BM_EDGES_OF_VERT) { + BMLoop *l_first, *l_b; + l_first = l_b = e_b->l; + do { + if (!BM_elem_flag_test(l_b, BM_ELEM_TAG)) { + BMLoop *l_b_vert = (l_a_verts[i]->v == l_b->v) ? l_b : l_b->next; + if (BM_loop_uv_share_vert_check(l_a_verts[i], l_b_vert, cd_loop_uv_offset)) { + /* We know 'l_b' is not visited, check it out! */ + const int l_b_index = BM_elem_index_get(l_b); + const float cost_cut = params->use_topology_distance ? + 1.0f : + edgetag_cut_cost_vert_uv(l_a, + l_b, + l_a_verts[i], + params->aspect_y, + cd_loop_uv_offset); + const float cost_new = cost[l_a_index] + cost_cut; + + if (cost[l_b_index] > cost_new) { + cost[l_b_index] = cost_new; + loops_prev[l_b_index] = l_a; + BLI_heapsimple_insert(heap, cost_new, l_b); + } + } + } + } while ((l_b = l_b->radial_next) != l_first); + } + } + } + else { + const float aspect_v2[2] = {1.0f, 1.0f / params->aspect_y}; + BMLoop *l_first, *l_iter; + l_iter = l_first = l_a; + do { + /* Ensures connected UVs and that they lie on the same island. */ + if (!BM_loop_uv_share_edge_check(l_a, l_iter, cd_loop_uv_offset)) { + continue; + } + + BMLoop *l_cycle_iter, *l_cycle_end; + l_cycle_iter = l_iter->next; + l_cycle_end = l_iter; + do { + BMLoop *l_b = l_cycle_iter; + if (!BM_elem_flag_test(l_b, BM_ELEM_TAG)) { + /* We know 'l_b' is not visited, check it out! */ + const int l_b_index = BM_elem_index_get(l_b); + const float cost_cut = params->use_topology_distance ? + 1.0f : + edgetag_cut_cost_face_uv(l_a, + l_b, + l_iter->f, + aspect_v2, + params->cd_loop_uv_offset); + const float cost_new = cost[l_a_index] + cost_cut; + + if (cost[l_b_index] > cost_new) { + cost[l_b_index] = cost_new; + loops_prev[l_b_index] = l_a; + BLI_heapsimple_insert(heap, cost_new, l_b); + } + } + } while ((l_cycle_iter = l_cycle_iter->next) != l_cycle_end); + } while ((l_iter = l_iter->radial_next) != l_first); + } +} + +struct LinkNode *BM_mesh_calc_path_uv_edge(BMesh *bm, + BMLoop *l_src, + BMLoop *l_dst, + const struct BMCalcPathUVParams *params, + bool (*filter_fn)(BMLoop *, void *), + void *user_data) +{ + LinkNode *path = NULL; + + BMFace *f; + BMIter iter; + HeapSimple *heap; + float *cost; + BMLoop **loops_prev; + int i = 0, totloop; + + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + BMLoop *l_first = BM_FACE_FIRST_LOOP(f); + BMLoop *l_iter = l_first; + do { + BM_elem_flag_set(l_iter, BM_ELEM_TAG, !filter_fn(l_iter, user_data)); + BM_elem_index_set(l_iter, i); + i += 1; + } while ((l_iter = l_iter->next) != l_first); + } + bm->elem_index_dirty &= ~BM_LOOP; + + totloop = bm->totloop; + loops_prev = MEM_callocN(sizeof(*loops_prev) * totloop, __func__); + cost = MEM_mallocN(sizeof(*cost) * totloop, __func__); + + copy_vn_fl(cost, totloop, COST_INIT_MAX); + + /* Regular dijkstra shortest path, but over UV loops/edges instead of vertices. */ + heap = BLI_heapsimple_new(); + BLI_heapsimple_insert(heap, 0.0f, l_src); + cost[BM_elem_index_get(l_src)] = 0.0f; + + BMLoop *l = NULL; + while (!BLI_heapsimple_is_empty(heap)) { + l = BLI_heapsimple_pop_min(heap); + + if ((l->e == l_dst->e) && (BM_loop_uv_share_edge_check(l, l_dst, params->cd_loop_uv_offset))) { + break; + } + + if (!BM_elem_flag_test(l, BM_ELEM_TAG)) { + BM_elem_flag_enable(l, BM_ELEM_TAG); + edgetag_add_adjacent_uv(heap, l, loops_prev, cost, params); + } + } + + if ((l->e == l_dst->e) && (BM_loop_uv_share_edge_check(l, l_dst, params->cd_loop_uv_offset))) { + do { + BLI_linklist_prepend(&path, l); + } while ((l = loops_prev[BM_elem_index_get(l)])); + } + + MEM_freeN(loops_prev); + MEM_freeN(cost); + BLI_heapsimple_free(heap, NULL); + + return path; +} /** \} */ diff --git a/source/blender/bmesh/tools/bmesh_path_uv.h b/source/blender/bmesh/tools/bmesh_path_uv.h index af7341e2219..d7b5faa70e5 100644 --- a/source/blender/bmesh/tools/bmesh_path_uv.h +++ b/source/blender/bmesh/tools/bmesh_path_uv.h @@ -21,6 +21,14 @@ struct LinkNode *BM_mesh_calc_path_uv_vert(BMesh *bm, void *user_data) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2, 3, 5); +struct LinkNode *BM_mesh_calc_path_uv_edge(BMesh *bm, + BMLoop *l_src, + BMLoop *l_dst, + const struct BMCalcPathUVParams *params, + bool (*filter_fn)(BMLoop *, void *), + void *user_data) ATTR_WARN_UNUSED_RESULT + ATTR_NONNULL(1, 2, 3, 5); + struct LinkNode *BM_mesh_calc_path_uv_face(BMesh *bm, BMFace *f_src, BMFace *f_dst, diff --git a/source/blender/editors/uvedit/uvedit_path.c b/source/blender/editors/uvedit/uvedit_path.c index 7c6960a634a..31a1b60167e 100644 --- a/source/blender/editors/uvedit/uvedit_path.c +++ b/source/blender/editors/uvedit/uvedit_path.c @@ -55,75 +55,6 @@ #include "bmesh_tools.h" -/* -------------------------------------------------------------------- */ -/** \name Local Utilities - * \{ */ - -/** - * Support edge-path using vert-path calculation code. - * - * Cheat! Pick 2 closest loops and do vertex path, - * in practices only obscure/contrived cases will make give noticeably worse behavior. - * - * While the code below is a bit awkward, it's significantly less overhead than - * adding full edge selection which is nearly the same as vertex path in the case of UV's. - * - * \param use_nearest: When false use the post distant pair of loops, - * use when filling a region as we want both verts from each edge to be included in the region. - */ -static void bm_loop_calc_vert_pair_from_edge_pair(const bool use_nearest, - const int cd_loop_uv_offset, - const float aspect_y, - BMElem **ele_src_p, - BMElem **ele_dst_p, - BMElem **r_ele_dst_final) -{ - BMLoop *l_src = (BMLoop *)*ele_src_p; - BMLoop *l_dst = (BMLoop *)*ele_dst_p; - - const MLoopUV *luv_src_v1 = BM_ELEM_CD_GET_VOID_P(l_src, cd_loop_uv_offset); - const MLoopUV *luv_src_v2 = BM_ELEM_CD_GET_VOID_P(l_src->next, cd_loop_uv_offset); - const MLoopUV *luv_dst_v1 = BM_ELEM_CD_GET_VOID_P(l_dst, cd_loop_uv_offset); - const MLoopUV *luv_dst_v2 = BM_ELEM_CD_GET_VOID_P(l_dst->next, cd_loop_uv_offset); - - const float uv_src_v1[2] = {luv_src_v1->uv[0], luv_src_v1->uv[1] / aspect_y}; - const float uv_src_v2[2] = {luv_src_v2->uv[0], luv_src_v2->uv[1] / aspect_y}; - const float uv_dst_v1[2] = {luv_dst_v1->uv[0], luv_dst_v1->uv[1] / aspect_y}; - const float uv_dst_v2[2] = {luv_dst_v2->uv[0], luv_dst_v2->uv[1] / aspect_y}; - - struct { - int src_index; - int dst_index; - float len_sq; - } tests[4] = { - {0, 0, len_squared_v2v2(uv_src_v1, uv_dst_v1)}, - {0, 1, len_squared_v2v2(uv_src_v1, uv_dst_v2)}, - {1, 0, len_squared_v2v2(uv_src_v2, uv_dst_v1)}, - {1, 1, len_squared_v2v2(uv_src_v2, uv_dst_v2)}, - }; - int i_best = 0; - for (int i = 1; i < ARRAY_SIZE(tests); i++) { - if (use_nearest) { - if (tests[i].len_sq < tests[i_best].len_sq) { - i_best = i; - } - } - else { - if (tests[i].len_sq > tests[i_best].len_sq) { - i_best = i; - } - } - } - - *ele_src_p = (BMElem *)(tests[i_best].src_index ? l_src->next : l_src); - *ele_dst_p = (BMElem *)(tests[i_best].dst_index ? l_dst->next : l_dst); - - /* Ensure the edge is selected, not just the vertices up until we hit it. */ - *r_ele_dst_final = (BMElem *)(tests[i_best].dst_index ? l_dst : l_dst->next); -} - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Path Select Struct & Properties * \{ */ @@ -180,12 +111,12 @@ static void path_select_params_from_op(wmOperator *op, struct PathSelectParams * * \{ */ /* callbacks */ -static bool looptag_filter_cb(BMLoop *l, void *user_data_v) +static bool verttag_filter_cb(BMLoop *l, void *user_data_v) { struct UserData_UV *user_data = user_data_v; return uvedit_face_visible_test(user_data->scene, l->f); } -static bool looptag_test_cb(BMLoop *l, void *user_data_v) +static bool verttag_test_cb(BMLoop *l, void *user_data_v) { /* All connected loops are selected or we return false. */ struct UserData_UV *user_data = user_data_v; @@ -195,7 +126,7 @@ static bool looptag_test_cb(BMLoop *l, void *user_data_v) BMIter iter; BMLoop *l_iter; BM_ITER_ELEM (l_iter, &iter, l->v, BM_LOOPS_OF_VERT) { - if (looptag_filter_cb(l_iter, user_data)) { + if (verttag_filter_cb(l_iter, user_data)) { const MLoopUV *luv_iter = BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset); if (equals_v2v2(luv->uv, luv_iter->uv)) { if (!uvedit_uv_select_test(scene, l_iter, cd_loop_uv_offset)) { @@ -206,7 +137,7 @@ static bool looptag_test_cb(BMLoop *l, void *user_data_v) } return true; } -static void looptag_set_cb(BMLoop *l, bool val, void *user_data_v) +static void verttag_set_cb(BMLoop *l, bool val, void *user_data_v) { struct UserData_UV *user_data = user_data_v; const Scene *scene = user_data->scene; @@ -216,7 +147,7 @@ static void looptag_set_cb(BMLoop *l, bool val, void *user_data_v) BMIter iter; BMLoop *l_iter; BM_ITER_ELEM (l_iter, &iter, l->v, BM_LOOPS_OF_VERT) { - if (looptag_filter_cb(l_iter, user_data)) { + if (verttag_filter_cb(l_iter, user_data)) { MLoopUV *luv_iter = BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset); if (equals_v2v2(luv->uv, luv_iter->uv)) { uvedit_uv_select_set(scene, em, l_iter, val, false, cd_loop_uv_offset); @@ -233,42 +164,10 @@ static int mouse_mesh_uv_shortest_path_vert(Scene *scene, const float aspect_y, const int cd_loop_uv_offset) { - const char uv_selectmode = ED_uvedit_select_mode_get(scene); - /* TODO(@sidd017): Implement logic to calculate shortest path for UV edges, since we now support - * proper edge selection for UVs (D12028). - * Till then continue using vertex path to fake shortest path calculation for edges. */ - const bool use_fake_edge_select = (uv_selectmode & UV_SELECT_EDGE); BMEditMesh *em = BKE_editmesh_from_object(obedit); BMesh *bm = em->bm; int flush = 0; - /* Variables to use when `use_fake_edge_select` is set. */ - struct { - BMLoop *l_dst_activate; - BMLoop *l_dst_add_to_path; - } fake_edge_select = {NULL}; - - if (use_fake_edge_select) { - fake_edge_select.l_dst_activate = l_dst; - - /* Use most distant when doing region selection. - * without this we get dangling edges outside the region. */ - bool use_neaerst = (op_params->use_fill == false); - BMElem *ele_src = (BMElem *)l_src; - BMElem *ele_dst = (BMElem *)l_dst; - BMElem *ele_dst_final = NULL; - bm_loop_calc_vert_pair_from_edge_pair( - use_neaerst, cd_loop_uv_offset, aspect_y, &ele_src, &ele_dst, &ele_dst_final); - - if (op_params->use_fill == false) { - /* Always activate the item under the cursor. */ - fake_edge_select.l_dst_add_to_path = (BMLoop *)ele_dst_final; - } - - l_src = (BMLoop *)ele_src; - l_dst = (BMLoop *)ele_dst; - } - struct UserData_UV user_data = { .scene = scene, .em = em, @@ -291,33 +190,23 @@ static int mouse_mesh_uv_shortest_path_vert(Scene *scene, (BMElem *)l_src, (BMElem *)l_dst, params.cd_loop_uv_offset, - looptag_filter_cb, + verttag_filter_cb, &user_data); } else { is_path_ordered = true; - path = BM_mesh_calc_path_uv_vert(bm, l_src, l_dst, ¶ms, looptag_filter_cb, &user_data); + path = BM_mesh_calc_path_uv_vert(bm, l_src, l_dst, ¶ms, verttag_filter_cb, &user_data); } } BMLoop *l_dst_last = l_dst; if (path) { - if (use_fake_edge_select) { - if ((fake_edge_select.l_dst_add_to_path != NULL) && - (BLI_linklist_index(path, fake_edge_select.l_dst_add_to_path) == -1)) { - /* Append, this isn't optimal compared to #BLI_linklist_append, it's a one-off lookup. */ - LinkNode *path_last = BLI_linklist_find_last(path); - BLI_linklist_insert_after(&path_last, fake_edge_select.l_dst_add_to_path); - BLI_assert(BLI_linklist_find_last(path)->link == fake_edge_select.l_dst_add_to_path); - } - } - /* toggle the flag */ bool all_set = true; LinkNode *node = path; do { - if (!looptag_test_cb((BMLoop *)node->link, &user_data)) { + if (!verttag_test_cb((BMLoop *)node->link, &user_data)) { all_set = false; break; } @@ -328,7 +217,7 @@ static int mouse_mesh_uv_shortest_path_vert(Scene *scene, do { if ((is_path_ordered == false) || WM_operator_properties_checker_interval_test(&op_params->interval_params, depth)) { - looptag_set_cb((BMLoop *)node->link, !all_set, &user_data); + verttag_set_cb((BMLoop *)node->link, !all_set, &user_data); if (is_path_ordered) { l_dst_last = node->link; } @@ -339,23 +228,133 @@ static int mouse_mesh_uv_shortest_path_vert(Scene *scene, flush = all_set ? -1 : 1; } else { - const bool is_act = !looptag_test_cb(l_dst, &user_data); - looptag_set_cb(l_dst, is_act, &user_data); /* switch the face option */ + const bool is_act = !verttag_test_cb(l_dst, &user_data); + verttag_set_cb(l_dst, is_act, &user_data); /* switch the face option */ } if (op_params->track_active) { - /* Fake edge selection. */ - if (use_fake_edge_select) { - BMLoop *l_dst_activate = fake_edge_select.l_dst_activate; - /* TODO(campbell): Search for an active loop attached to 'l_dst'. - * when `BLI_linklist_index(path, l_dst_activate) == -1` - * In practice this rarely happens though. */ - ED_uvedit_active_edge_loop_set(bm, l_dst_activate); + ED_uvedit_active_vert_loop_set(bm, l_dst_last); + } + return flush; +} + +/* -------------------------------------------------------------------- */ +/** \name UV Edge Path + * \{ */ + +/* callbacks */ +static bool edgetag_filter_cb(BMLoop *l, void *user_data_v) +{ + struct UserData_UV *user_data = user_data_v; + return uvedit_face_visible_test(user_data->scene, l->f); +} +static bool edgetag_test_cb(BMLoop *l, void *user_data_v) +{ + /* All connected loops (UV) are selected or we return false. */ + struct UserData_UV *user_data = user_data_v; + const Scene *scene = user_data->scene; + const uint cd_loop_uv_offset = user_data->cd_loop_uv_offset; + BMIter iter; + BMLoop *l_iter; + BM_ITER_ELEM (l_iter, &iter, l->e, BM_LOOPS_OF_EDGE) { + if (edgetag_filter_cb(l_iter, user_data)) { + if (BM_loop_uv_share_edge_check(l, l_iter, cd_loop_uv_offset)) { + if (!uvedit_edge_select_test(scene, l_iter, cd_loop_uv_offset)) { + return false; + } + } + } + } + return true; +} +static void edgetag_set_cb(BMLoop *l, bool val, void *user_data_v) +{ + struct UserData_UV *user_data = user_data_v; + const Scene *scene = user_data->scene; + BMEditMesh *em = user_data->em; + const uint cd_loop_uv_offset = user_data->cd_loop_uv_offset; + uvedit_edge_select_set_with_sticky(scene, em, l, val, false, cd_loop_uv_offset); +} + +static int mouse_mesh_uv_shortest_path_edge(Scene *scene, + Object *obedit, + const struct PathSelectParams *op_params, + BMLoop *l_src, + BMLoop *l_dst, + const float aspect_y, + const int cd_loop_uv_offset) +{ + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + int flush = 0; + + struct UserData_UV user_data = { + .scene = scene, + .em = em, + .cd_loop_uv_offset = cd_loop_uv_offset, + }; + + const struct BMCalcPathUVParams params = { + .use_topology_distance = op_params->use_topology_distance, + .use_step_face = op_params->use_face_step, + .aspect_y = aspect_y, + .cd_loop_uv_offset = cd_loop_uv_offset, + }; + + LinkNode *path = NULL; + bool is_path_ordered = false; + + if (l_src != l_dst) { + if (op_params->use_fill) { + path = BM_mesh_calc_path_uv_region_edge(bm, + (BMElem *)l_src, + (BMElem *)l_dst, + params.cd_loop_uv_offset, + edgetag_filter_cb, + &user_data); } else { - ED_uvedit_active_vert_loop_set(bm, l_dst_last); + is_path_ordered = true; + path = BM_mesh_calc_path_uv_edge(bm, l_src, l_dst, ¶ms, edgetag_filter_cb, &user_data); } } + + BMLoop *l_dst_last = l_dst; + + if (path) { + /* toggle the flag */ + bool all_set = true; + LinkNode *node = path; + do { + if (!edgetag_test_cb((BMLoop *)node->link, &user_data)) { + all_set = false; + break; + } + } while ((node = node->next)); + + int depth = -1; + node = path; + do { + if ((is_path_ordered == false) || + WM_operator_properties_checker_interval_test(&op_params->interval_params, depth)) { + edgetag_set_cb((BMLoop *)node->link, !all_set, &user_data); + if (is_path_ordered) { + l_dst_last = node->link; + } + } + } while ((void)depth++, (node = node->next)); + + BLI_linklist_free(path, NULL); + flush = all_set ? -1 : 1; + } + else { + const bool is_act = !edgetag_test_cb(l_dst, &user_data); + edgetag_set_cb(l_dst, is_act, &user_data); /* switch the face option */ + } + + if (op_params->track_active) { + ED_uvedit_active_edge_loop_set(bm, l_dst_last); + } return flush; } @@ -514,13 +513,24 @@ static bool uv_shortest_path_pick_ex(Scene *scene, ok = true; } else if (ele_src->head.htype == BM_LOOP) { - flush = mouse_mesh_uv_shortest_path_vert(scene, - obedit, - op_params, - (BMLoop *)ele_src, - (BMLoop *)ele_dst, - aspect_y, - cd_loop_uv_offset); + if (uv_selectmode & UV_SELECT_EDGE) { + flush = mouse_mesh_uv_shortest_path_edge(scene, + obedit, + op_params, + (BMLoop *)ele_src, + (BMLoop *)ele_dst, + aspect_y, + cd_loop_uv_offset); + } + else { + flush = mouse_mesh_uv_shortest_path_vert(scene, + obedit, + op_params, + (BMLoop *)ele_src, + (BMLoop *)ele_dst, + aspect_y, + cd_loop_uv_offset); + } ok = true; } @@ -529,24 +539,9 @@ static bool uv_shortest_path_pick_ex(Scene *scene, const bool select = (flush == 1); BMEditMesh *em = BKE_editmesh_from_object(obedit); if (ts->uv_flag & UV_SYNC_SELECTION) { - if (uv_selectmode & UV_SELECT_EDGE) { - /* Special case as we don't use true edge selection, - * flush the selection from the vertices. */ - BM_mesh_select_mode_flush_ex(em->bm, SCE_SELECT_VERTEX, BM_SELECT_LEN_FLUSH_RECALC_ALL); - } ED_uvedit_select_sync_flush(scene->toolsettings, em, select); } else { - if (uv_selectmode & UV_SELECT_EDGE) { - /* TODO(@sidd017): Remove this case when adding proper uv edge support for this operator. - * In the meantime, this case helps ensures proper UV selection states for edge mode. */ - if (select) { - uvedit_select_flush(scene, em); - } - else { - uvedit_deselect_flush(scene, em); - } - } ED_uvedit_selectmode_flush(scene, em); } } -- cgit v1.2.3 From d3db38cfb1f223b81ab89c38bae0652a0d40c03e Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 22 Jul 2022 12:23:33 +1000 Subject: Cleanup: quiet nonull-compare warnings with GCC --- source/blender/blenkernel/intern/main_namemap.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/blender/blenkernel/intern/main_namemap.cc b/source/blender/blenkernel/intern/main_namemap.cc index 3cbd33057a8..00115d2a0be 100644 --- a/source/blender/blenkernel/intern/main_namemap.cc +++ b/source/blender/blenkernel/intern/main_namemap.cc @@ -242,8 +242,10 @@ static UniqueName_Map *get_namemap_for(Main *bmain, ID *id, bool ensure_created) bool BKE_main_namemap_get_name(struct Main *bmain, struct ID *id, char *name) { +#ifndef __GNUC__ /* GCC warns with `nonull-compare`. */ BLI_assert(bmain != nullptr); BLI_assert(id != nullptr); +#endif UniqueName_Map *name_map = get_namemap_for(bmain, id, true); BLI_assert(name_map != nullptr); BLI_assert(strlen(name) < MAX_NAME); @@ -325,9 +327,11 @@ bool BKE_main_namemap_get_name(struct Main *bmain, struct ID *id, char *name) void BKE_main_namemap_remove_name(struct Main *bmain, struct ID *id, const char *name) { +#ifndef __GNUC__ /* GCC warns with `nonull-compare`. */ BLI_assert(bmain != nullptr); BLI_assert(id != nullptr); BLI_assert(name != nullptr); +#endif /* Name is empty or not initialized yet, nothing to remove. */ if (name[0] == '\0') { return; -- cgit v1.2.3 From 72e249974aa7f6a3bd6d5c35c5d5e59cd1c3bded Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 22 Jul 2022 12:25:10 +1000 Subject: Fix crash loading factory settings in image paint mode Loading factory settings left the region NULL, causing the brushes poll function to crash. --- source/blender/editors/sculpt_paint/paint_image.cc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/source/blender/editors/sculpt_paint/paint_image.cc b/source/blender/editors/sculpt_paint/paint_image.cc index 56cd4751881..6ed43fe6403 100644 --- a/source/blender/editors/sculpt_paint/paint_image.cc +++ b/source/blender/editors/sculpt_paint/paint_image.cc @@ -276,10 +276,11 @@ static bool image_paint_poll_ex(bContext *C, bool check_tool) (ID_IS_LINKED(sima->image) || ID_IS_OVERRIDE_LIBRARY(sima->image))) { return false; } - ARegion *region = CTX_wm_region(C); - - if ((sima->mode == SI_MODE_PAINT) && region->regiontype == RGN_TYPE_WINDOW) { - return true; + if (sima->mode == SI_MODE_PAINT) { + const ARegion *region = CTX_wm_region(C); + if (region->regiontype == RGN_TYPE_WINDOW) { + return true; + } } } } -- cgit v1.2.3 From 08c5d99e88ee3e9f807dfe69c188660eae347f31 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 22 Jul 2022 13:05:26 +1000 Subject: Cleanup: add BKE_image_find_nearest_tile_with_offset Every caller BKE_image_find_nearest_tile was calculating the tile offset so add a version of this function that returns the offset too. --- source/blender/blenkernel/BKE_image.h | 7 ++++++- source/blender/blenkernel/intern/image.cc | 17 +++++++++++++++-- .../blender/editors/transform/transform_mode_resize.c | 8 +------- .../editors/transform/transform_mode_translate.c | 8 +------- source/blender/editors/uvedit/uvedit_islands.c | 10 +++------- 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/source/blender/blenkernel/BKE_image.h b/source/blender/blenkernel/BKE_image.h index 4e622a5708f..e3c249e56f9 100644 --- a/source/blender/blenkernel/BKE_image.h +++ b/source/blender/blenkernel/BKE_image.h @@ -6,6 +6,7 @@ * \ingroup bke */ +#include "BLI_compiler_attrs.h" #include "BLI_utildefines.h" #include "BLI_rect.h" @@ -424,7 +425,11 @@ void BKE_image_get_tile_uv(const struct Image *ima, const int tile_number, float /** * Return the tile_number for the closest UDIM tile. */ -int BKE_image_find_nearest_tile(const struct Image *image, const float co[2]); +int BKE_image_find_nearest_tile_with_offset(const struct Image *image, + const float co[2], + float r_uv_offset[2]) ATTR_NONNULL(1, 2, 3); +int BKE_image_find_nearest_tile(const struct Image *image, const float co[2]) + ATTR_NONNULL(1, 2) ATTR_WARN_UNUSED_RESULT; void BKE_image_get_size(struct Image *image, struct ImageUser *iuser, int *r_width, int *r_height); void BKE_image_get_size_fl(struct Image *image, struct ImageUser *iuser, float r_size[2]); diff --git a/source/blender/blenkernel/intern/image.cc b/source/blender/blenkernel/intern/image.cc index f8b2d841028..7fa3a5ad14d 100644 --- a/source/blender/blenkernel/intern/image.cc +++ b/source/blender/blenkernel/intern/image.cc @@ -863,11 +863,14 @@ void BKE_image_get_tile_uv(const Image *ima, const int tile_number, float r_uv[2 } } -int BKE_image_find_nearest_tile(const Image *image, const float co[2]) +int BKE_image_find_nearest_tile_with_offset(const Image *image, + const float co[2], + float r_uv_offset[2]) { const float co_floor[2] = {floorf(co[0]), floorf(co[1])}; /* Distance to the closest UDIM tile. */ float dist_best_sq = FLT_MAX; + float uv_offset_best[2] = {0, 0}; int tile_number_best = -1; LISTBASE_FOREACH (const ImageTile *, tile, &image->tiles) { @@ -875,6 +878,7 @@ int BKE_image_find_nearest_tile(const Image *image, const float co[2]) BKE_image_get_tile_uv(image, tile->tile_number, uv_offset); if (equals_v2v2(co_floor, uv_offset)) { + copy_v2_v2(r_uv_offset, uv_offset); return tile->tile_number; } @@ -884,12 +888,21 @@ int BKE_image_find_nearest_tile(const Image *image, const float co[2]) if (dist_sq < dist_best_sq) { dist_best_sq = dist_sq; tile_number_best = tile->tile_number; + copy_v2_v2(uv_offset_best, uv_offset); } } - + if (tile_number_best != -1) { + copy_v2_v2(r_uv_offset, uv_offset_best); + } return tile_number_best; } +int BKE_image_find_nearest_tile(const struct Image *image, const float co[2]) +{ + float uv_offset_dummy[2]; + return BKE_image_find_nearest_tile_with_offset(image, co, uv_offset_dummy); +} + static void image_init_color_management(Image *ima) { ImBuf *ibuf; diff --git a/source/blender/editors/transform/transform_mode_resize.c b/source/blender/editors/transform/transform_mode_resize.c index bbe1cfdf521..1ccda96fecb 100644 --- a/source/blender/editors/transform/transform_mode_resize.c +++ b/source/blender/editors/transform/transform_mode_resize.c @@ -137,13 +137,7 @@ static bool clip_uv_transform_resize(TransInfo *t, float vec[2]) /* If tiled image then constrain to correct/closest UDIM tile, else 0-1 UV space. */ if (is_tiled_image) { - int nearest_tile_index = BKE_image_find_nearest_tile(image, t->center_global); - if (nearest_tile_index != -1) { - nearest_tile_index -= 1001; - /* Getting coordinates of nearest tile from the tile index. */ - base_offset[0] = nearest_tile_index % 10; - base_offset[1] = nearest_tile_index / 10; - } + BKE_image_find_nearest_tile_with_offset(image, t->center_global, base_offset); } /* Assume no change is required. */ diff --git a/source/blender/editors/transform/transform_mode_translate.c b/source/blender/editors/transform/transform_mode_translate.c index 67bdeb3fed0..04a41814b53 100644 --- a/source/blender/editors/transform/transform_mode_translate.c +++ b/source/blender/editors/transform/transform_mode_translate.c @@ -448,13 +448,7 @@ static bool clip_uv_transform_translation(TransInfo *t, float vec[2]) /* If tiled image then constrain to correct/closest UDIM tile, else 0-1 UV space. */ if (is_tiled_image) { - int nearest_tile_index = BKE_image_find_nearest_tile(image, t->center_global); - if (nearest_tile_index != -1) { - nearest_tile_index -= 1001; - /* Getting coordinates of nearest tile from the tile index. */ - base_offset[0] = nearest_tile_index % 10; - base_offset[1] = nearest_tile_index / 10; - } + BKE_image_find_nearest_tile_with_offset(image, t->center_global, base_offset); } float min[2], max[2]; diff --git a/source/blender/editors/uvedit/uvedit_islands.c b/source/blender/editors/uvedit/uvedit_islands.c index e1752ae5a29..9a31fd6469d 100644 --- a/source/blender/editors/uvedit/uvedit_islands.c +++ b/source/blender/editors/uvedit/uvedit_islands.c @@ -256,16 +256,12 @@ bool uv_coords_isect_udim(const Image *image, const int udim_grid[2], const floa * Calculates distance to nearest UDIM image tile in UV space and its UDIM tile number. */ static float uv_nearest_image_tile_distance(const Image *image, - float coords[2], + const float coords[2], float nearest_tile_co[2]) { - int nearest_image_tile_index = BKE_image_find_nearest_tile(image, coords); - if (nearest_image_tile_index == -1) { - nearest_image_tile_index = 1001; + if (BKE_image_find_nearest_tile_with_offset(image, coords, nearest_tile_co) == -1) { + zero_v2(nearest_tile_co); } - - nearest_tile_co[0] = (nearest_image_tile_index - 1001) % 10; - nearest_tile_co[1] = (nearest_image_tile_index - 1001) / 10; /* Add 0.5 to get tile center coordinates. */ float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]}; add_v2_fl(nearest_tile_center_co, 0.5f); -- cgit v1.2.3 From 087f27a52f7857887e90754d87a7a73715ebc3fb Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 22 Jul 2022 13:57:04 +1000 Subject: Fix T87779: Asymmetric vertex positions in circles primitives Add sin_cos_from_fraction which ensures each quadrant has matching values when their sign is flipped. --- source/blender/blenlib/BLI_math_rotation.h | 20 +++++++++++ source/blender/blenlib/intern/math_rotation.c | 49 ++++++++++++++++++++++++++ source/blender/bmesh/operators/bmo_primitive.c | 28 +++++++-------- 3 files changed, 83 insertions(+), 14 deletions(-) diff --git a/source/blender/blenlib/BLI_math_rotation.h b/source/blender/blenlib/BLI_math_rotation.h index 192ad482a69..fef51fa780e 100644 --- a/source/blender/blenlib/BLI_math_rotation.h +++ b/source/blender/blenlib/BLI_math_rotation.h @@ -176,6 +176,26 @@ void mat3_to_quat_is_ok(float q[4], const float mat[3][3]); /* Other. */ +/** + * Utility function that performs `sinf` & `cosf` where the quadrants of the circle + * will have exactly matching values when their sign is flipped. + * This works as long as the denominator can be divided by 2 or 4, + * otherwise `sinf` & `cosf` are used without any additional logic. + * + * Besides adjustments to precision, this function is the equivalent of: + * \code {.c} + * float phi = (2 * M_PI) * (float)i / (float)denominator; + * *r_sin = sinf(phi); + * *r_cos = cosf(phi); + * \endcode + * + * \param numerator: An integer factor in [0..denominator] (inclusive). + * \param denominator: The faction denominator (typically the number of segments of the circle). + * \param r_sin: The resulting sine. + * \param r_cos: The resulting cosine. + */ +void sin_cos_from_fraction(const int numerator, const int denominator, float *r_sin, float *r_cos); + void print_qt(const char *str, const float q[4]); #define print_qt_id(q) print_qt(STRINGIFY(q), q) diff --git a/source/blender/blenlib/intern/math_rotation.c b/source/blender/blenlib/intern/math_rotation.c index 92223bdf1d5..f0bfc7c21e1 100644 --- a/source/blender/blenlib/intern/math_rotation.c +++ b/source/blender/blenlib/intern/math_rotation.c @@ -915,6 +915,55 @@ float tri_to_quat(float q[4], const float a[3], const float b[3], const float c[ return len; } +void sin_cos_from_fraction(const int numerator, const int denominator, float *r_sin, float *r_cos) +{ + BLI_assert((numerator <= denominator) && (denominator > 0)); + if ((denominator & 3) == 0) { + const int denominator_4 = denominator / 4; + if (numerator <= denominator_4) { + /* Fall through. */ + } + else { + if (numerator <= denominator_4 * 2) { + const float phi = (float)(2.0 * M_PI) * + ((float)(numerator - denominator_4) / (float)denominator); + *r_sin = cosf(phi); + *r_cos = -sinf(phi); + } + else if (numerator <= denominator_4 * 3) { + const float phi = (float)(2.0 * M_PI) * + ((float)(numerator - (denominator_4 * 2)) / (float)denominator); + *r_sin = -sinf(phi); + *r_cos = -cosf(phi); + } + else { + const float phi = (float)(2.0 * M_PI) * + ((float)(numerator - (denominator_4 * 3)) / (float)denominator); + *r_cos = sinf(phi); + *r_sin = -cosf(phi); + } + return; + } + } + else if ((denominator & 1) == 0) { + const int denominator_2 = denominator / 2; + if (numerator <= denominator_2) { + /* Fall through. */ + } + else { + const float phi = (float)(2.0 * M_PI) * + ((float)(numerator - denominator_2) / (float)denominator); + *r_sin = -sinf(phi); + *r_cos = -cosf(phi); + return; + } + } + + const float phi = (float)(2.0 * M_PI) * ((float)numerator / (float)denominator); + *r_sin = sinf(phi); + *r_cos = cosf(phi); +} + void print_qt(const char *str, const float q[4]) { printf("%s: %.3f %.3f %.3f %.3f\n", str, q[0], q[1], q[2], q[3]); diff --git a/source/blender/bmesh/operators/bmo_primitive.c b/source/blender/bmesh/operators/bmo_primitive.c index 432c7590f3c..2ed0964d735 100644 --- a/source/blender/bmesh/operators/bmo_primitive.c +++ b/source/blender/bmesh/operators/bmo_primitive.c @@ -855,12 +855,12 @@ void bmo_create_uvsphere_exec(BMesh *bm, BMOperator *op) /* one segment first */ for (a = 0; a <= tot; a++) { /* Going in this direction, then edge extruding, makes normals face outward */ - /* Calculate with doubles for higher precision, see: T87779. */ - const float phi = M_PI * ((double)a / (double)tot); + float sin_phi, cos_phi; + sin_cos_from_fraction(a, tot, &sin_phi, &cos_phi); vec[0] = 0.0; - vec[1] = rad * sinf(phi); - vec[2] = rad * cosf(phi); + vec[1] = rad * sin_phi; + vec[2] = rad * cos_phi; eve = BM_vert_create(bm, vec, NULL, BM_CREATE_NOP); BMO_vert_flag_enable(bm, eve, VERT_MARK); @@ -1262,11 +1262,9 @@ void bmo_create_circle_exec(BMesh *bm, BMOperator *op) for (a = 0; a < segs; a++) { /* Going this way ends up with normal(s) upward */ - - /* Calculate with doubles for higher precision, see: T87779. */ - const float phi = (2.0 * M_PI) * ((double)a / (double)segs); - vec[0] = -radius * sinf(phi); - vec[1] = radius * cosf(phi); + sin_cos_from_fraction(a, segs, &vec[0], &vec[1]); + vec[0] *= -radius; + vec[1] *= radius; vec[2] = 0.0f; mul_m4_v3(mat, vec); v1 = BM_vert_create(bm, vec, NULL, BM_CREATE_NOP); @@ -1394,15 +1392,17 @@ void bmo_create_cone_exec(BMesh *bm, BMOperator *op) for (int i = 0; i < segs; i++) { /* Calculate with doubles for higher precision, see: T87779. */ - const float phi = (2.0 * M_PI) * ((double)i / (double)segs); - vec[0] = rad1 * sinf(phi); - vec[1] = rad1 * cosf(phi); + float sin_phi, cos_phi; + sin_cos_from_fraction(i, segs, &sin_phi, &cos_phi); + + vec[0] = rad1 * sin_phi; + vec[1] = rad1 * cos_phi; vec[2] = -depth_half; mul_m4_v3(mat, vec); v1 = BM_vert_create(bm, vec, NULL, BM_CREATE_NOP); - vec[0] = rad2 * sinf(phi); - vec[1] = rad2 * cosf(phi); + vec[0] = rad2 * sin_phi; + vec[1] = rad2 * cos_phi; vec[2] = depth_half; mul_m4_v3(mat, vec); v2 = BM_vert_create(bm, vec, NULL, BM_CREATE_NOP); -- cgit v1.2.3 From 95e60b4ffd6c325d2658f318d05ab52d712ca953 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Fri, 22 Jul 2022 12:33:08 +0200 Subject: Cleanup: move crazyspace.c to c++ Doing this in preparation for D15407. --- source/blender/blenkernel/CMakeLists.txt | 2 +- source/blender/blenkernel/intern/crazyspace.c | 584 ------------------------ source/blender/blenkernel/intern/crazyspace.cc | 588 +++++++++++++++++++++++++ 3 files changed, 589 insertions(+), 585 deletions(-) delete mode 100644 source/blender/blenkernel/intern/crazyspace.c create mode 100644 source/blender/blenkernel/intern/crazyspace.cc diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 45a9e85874d..df4b70d4fe6 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -101,7 +101,7 @@ set(SRC intern/colortools.c intern/constraint.c intern/context.c - intern/crazyspace.c + intern/crazyspace.cc intern/cryptomatte.cc intern/curve.cc intern/curve_bevel.c diff --git a/source/blender/blenkernel/intern/crazyspace.c b/source/blender/blenkernel/intern/crazyspace.c deleted file mode 100644 index 14e862c2377..00000000000 --- a/source/blender/blenkernel/intern/crazyspace.c +++ /dev/null @@ -1,584 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2005 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup bke - */ - -#include "MEM_guardedalloc.h" - -#include "DNA_mesh_types.h" -#include "DNA_meshdata_types.h" -#include "DNA_modifier_types.h" -#include "DNA_object_types.h" -#include "DNA_scene_types.h" - -#include "BLI_bitmap.h" -#include "BLI_linklist.h" -#include "BLI_utildefines.h" - -#include "BKE_DerivedMesh.h" -#include "BKE_crazyspace.h" -#include "BKE_editmesh.h" -#include "BKE_lib_id.h" -#include "BKE_mesh.h" -#include "BKE_mesh_wrapper.h" -#include "BKE_modifier.h" -#include "BKE_multires.h" -#include "BKE_report.h" - -#include "DEG_depsgraph_query.h" - -BLI_INLINE void tan_calc_quat_v3(float r_quat[4], - const float co_1[3], - const float co_2[3], - const float co_3[3]) -{ - float vec_u[3], vec_v[3]; - float nor[3]; - - sub_v3_v3v3(vec_u, co_1, co_2); - sub_v3_v3v3(vec_v, co_1, co_3); - - cross_v3_v3v3(nor, vec_u, vec_v); - - if (normalize_v3(nor) > FLT_EPSILON) { - const float zero_vec[3] = {0.0f}; - tri_to_quat_ex(r_quat, zero_vec, vec_u, vec_v, nor); - } - else { - unit_qt(r_quat); - } -} - -static void set_crazy_vertex_quat(float r_quat[4], - const float co_1[3], - const float co_2[3], - const float co_3[3], - const float vd_1[3], - const float vd_2[3], - const float vd_3[3]) -{ - float q1[4], q2[4]; - - tan_calc_quat_v3(q1, co_1, co_2, co_3); - tan_calc_quat_v3(q2, vd_1, vd_2, vd_3); - - sub_qt_qtqt(r_quat, q2, q1); -} - -static bool modifiers_disable_subsurf_temporary(struct Scene *scene, Object *ob) -{ - bool disabled = false; - int cageIndex = BKE_modifiers_get_cage_index(scene, ob, NULL, 1); - - ModifierData *md = ob->modifiers.first; - for (int i = 0; md && i <= cageIndex; i++, md = md->next) { - if (md->type == eModifierType_Subsurf) { - md->mode ^= eModifierMode_DisableTemporary; - disabled = true; - } - } - - return disabled; -} - -float (*BKE_crazyspace_get_mapped_editverts(struct Depsgraph *depsgraph, Object *obedit))[3] -{ - Scene *scene = DEG_get_input_scene(depsgraph); - Scene *scene_eval = DEG_get_evaluated_scene(depsgraph); - Object *obedit_eval = DEG_get_evaluated_object(depsgraph, obedit); - Mesh *mesh_eval = obedit_eval->data; - BMEditMesh *editmesh_eval = mesh_eval->edit_mesh; - - /* disable subsurf temporal, get mapped cos, and enable it */ - if (modifiers_disable_subsurf_temporary(scene_eval, obedit_eval)) { - /* need to make new derivemesh */ - makeDerivedMesh(depsgraph, scene_eval, obedit_eval, &CD_MASK_BAREMESH); - } - - /* now get the cage */ - Mesh *mesh_eval_cage = editbmesh_get_eval_cage_from_orig( - depsgraph, scene, obedit, &CD_MASK_BAREMESH); - - const int nverts = editmesh_eval->bm->totvert; - float(*vertexcos)[3] = MEM_mallocN(sizeof(*vertexcos) * nverts, "vertexcos map"); - mesh_get_mapped_verts_coords(mesh_eval_cage, vertexcos, nverts); - - /* set back the flag, no new cage needs to be built, transform does it */ - modifiers_disable_subsurf_temporary(scene_eval, obedit_eval); - - return vertexcos; -} - -void BKE_crazyspace_set_quats_editmesh(BMEditMesh *em, - float (*origcos)[3], - float (*mappedcos)[3], - float (*quats)[4], - const bool use_select) -{ - BMFace *f; - BMIter iter; - int index; - - { - BMVert *v; - BM_ITER_MESH_INDEX (v, &iter, em->bm, BM_VERTS_OF_MESH, index) { - BM_elem_flag_disable(v, BM_ELEM_TAG); - BM_elem_index_set(v, index); /* set_inline */ - } - em->bm->elem_index_dirty &= ~BM_VERT; - } - - BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { - BMLoop *l_iter, *l_first; - - l_iter = l_first = BM_FACE_FIRST_LOOP(f); - do { - if (BM_elem_flag_test(l_iter->v, BM_ELEM_HIDDEN) || - BM_elem_flag_test(l_iter->v, BM_ELEM_TAG) || - (use_select && !BM_elem_flag_test(l_iter->v, BM_ELEM_SELECT))) { - continue; - } - - if (!BM_elem_flag_test(l_iter->v, BM_ELEM_TAG)) { - const float *co_prev, *co_curr, *co_next; /* orig */ - const float *vd_prev, *vd_curr, *vd_next; /* deform */ - - const int i_prev = BM_elem_index_get(l_iter->prev->v); - const int i_curr = BM_elem_index_get(l_iter->v); - const int i_next = BM_elem_index_get(l_iter->next->v); - - /* retrieve mapped coordinates */ - vd_prev = mappedcos[i_prev]; - vd_curr = mappedcos[i_curr]; - vd_next = mappedcos[i_next]; - - if (origcos) { - co_prev = origcos[i_prev]; - co_curr = origcos[i_curr]; - co_next = origcos[i_next]; - } - else { - co_prev = l_iter->prev->v->co; - co_curr = l_iter->v->co; - co_next = l_iter->next->v->co; - } - - set_crazy_vertex_quat(quats[i_curr], co_curr, co_next, co_prev, vd_curr, vd_next, vd_prev); - - BM_elem_flag_enable(l_iter->v, BM_ELEM_TAG); - } - } while ((l_iter = l_iter->next) != l_first); - } -} - -void BKE_crazyspace_set_quats_mesh(Mesh *me, - float (*origcos)[3], - float (*mappedcos)[3], - float (*quats)[4]) -{ - BLI_bitmap *vert_tag = BLI_BITMAP_NEW(me->totvert, __func__); - - /* first store two sets of tangent vectors in vertices, we derive it just from the face-edges */ - MVert *mvert = me->mvert; - MPoly *mp = me->mpoly; - MLoop *mloop = me->mloop; - - for (int i = 0; i < me->totpoly; i++, mp++) { - MLoop *ml_next = &mloop[mp->loopstart]; - MLoop *ml_curr = &ml_next[mp->totloop - 1]; - MLoop *ml_prev = &ml_next[mp->totloop - 2]; - - for (int j = 0; j < mp->totloop; j++) { - if (!BLI_BITMAP_TEST(vert_tag, ml_curr->v)) { - const float *co_prev, *co_curr, *co_next; /* orig */ - const float *vd_prev, *vd_curr, *vd_next; /* deform */ - - /* retrieve mapped coordinates */ - vd_prev = mappedcos[ml_prev->v]; - vd_curr = mappedcos[ml_curr->v]; - vd_next = mappedcos[ml_next->v]; - - if (origcos) { - co_prev = origcos[ml_prev->v]; - co_curr = origcos[ml_curr->v]; - co_next = origcos[ml_next->v]; - } - else { - co_prev = mvert[ml_prev->v].co; - co_curr = mvert[ml_curr->v].co; - co_next = mvert[ml_next->v].co; - } - - set_crazy_vertex_quat( - quats[ml_curr->v], co_curr, co_next, co_prev, vd_curr, vd_next, vd_prev); - - BLI_BITMAP_ENABLE(vert_tag, ml_curr->v); - } - - ml_prev = ml_curr; - ml_curr = ml_next; - ml_next++; - } - } - - MEM_freeN(vert_tag); -} - -int BKE_crazyspace_get_first_deform_matrices_editbmesh(struct Depsgraph *depsgraph, - Scene *scene, - Object *ob, - BMEditMesh *em, - float (**deformmats)[3][3], - float (**deformcos)[3]) -{ - ModifierData *md; - Mesh *me_input = ob->data; - Mesh *me = NULL; - int i, a, modifiers_left_num = 0, verts_num = 0; - int cageIndex = BKE_modifiers_get_cage_index(scene, ob, NULL, 1); - float(*defmats)[3][3] = NULL, (*deformedVerts)[3] = NULL; - VirtualModifierData virtualModifierData; - ModifierEvalContext mectx = {depsgraph, ob, 0}; - - BKE_modifiers_clear_errors(ob); - - md = BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData); - - /* compute the deformation matrices and coordinates for the first - * modifiers with on cage editing that are enabled and support computing - * deform matrices */ - for (i = 0; md && i <= cageIndex; i++, md = md->next) { - const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); - - if (!editbmesh_modifier_is_enabled(scene, ob, md, me != NULL)) { - continue; - } - - if (mti->type == eModifierTypeType_OnlyDeform && mti->deformMatricesEM) { - if (!defmats) { - const int required_mode = eModifierMode_Realtime | eModifierMode_Editmode; - CustomData_MeshMasks cd_mask_extra = CD_MASK_BAREMESH; - CDMaskLink *datamasks = BKE_modifier_calc_data_masks( - scene, ob, md, &cd_mask_extra, required_mode, NULL, NULL); - cd_mask_extra = datamasks->mask; - BLI_linklist_free((LinkNode *)datamasks, NULL); - - me = BKE_mesh_wrapper_from_editmesh_with_coords(em, &cd_mask_extra, NULL, me_input); - deformedVerts = editbmesh_vert_coords_alloc(em, &verts_num); - defmats = MEM_mallocN(sizeof(*defmats) * verts_num, "defmats"); - - for (a = 0; a < verts_num; a++) { - unit_m3(defmats[a]); - } - } - mti->deformMatricesEM(md, &mectx, em, me, deformedVerts, defmats, verts_num); - } - else { - break; - } - } - - for (; md && i <= cageIndex; md = md->next, i++) { - if (editbmesh_modifier_is_enabled(scene, ob, md, me != NULL) && - BKE_modifier_is_correctable_deformed(md)) { - modifiers_left_num++; - } - } - - if (me) { - BKE_id_free(NULL, me); - } - - *deformmats = defmats; - *deformcos = deformedVerts; - - return modifiers_left_num; -} - -/** - * Crazy-space evaluation needs to have an object which has all the fields - * evaluated, but the mesh data being at undeformed state. This way it can - * re-apply modifiers and also have proper pointers to key data blocks. - * - * Similar to #BKE_object_eval_reset(), but does not modify the actual evaluated object. - */ -static void crazyspace_init_object_for_eval(struct Depsgraph *depsgraph, - Object *object, - Object *object_crazy) -{ - Object *object_eval = DEG_get_evaluated_object(depsgraph, object); - *object_crazy = *object_eval; - if (object_crazy->runtime.data_orig != NULL) { - object_crazy->data = object_crazy->runtime.data_orig; - } -} - -static void crazyspace_init_verts_and_matrices(const Mesh *mesh, - float (**deformmats)[3][3], - float (**deformcos)[3]) -{ - int verts_num; - *deformcos = BKE_mesh_vert_coords_alloc(mesh, &verts_num); - *deformmats = MEM_callocN(sizeof(**deformmats) * verts_num, "defmats"); - for (int a = 0; a < verts_num; a++) { - unit_m3((*deformmats)[a]); - } - BLI_assert(verts_num == mesh->totvert); -} - -static bool crazyspace_modifier_supports_deform_matrices(ModifierData *md) -{ - if (ELEM(md->type, eModifierType_Subsurf, eModifierType_Multires)) { - return true; - } - const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); - return (mti->type == eModifierTypeType_OnlyDeform); -} - -static bool crazyspace_modifier_supports_deform(ModifierData *md) -{ - const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); - return (mti->type == eModifierTypeType_OnlyDeform); -} - -int BKE_sculpt_get_first_deform_matrices(struct Depsgraph *depsgraph, - Scene *scene, - Object *object, - float (**deformmats)[3][3], - float (**deformcos)[3]) -{ - ModifierData *md; - Mesh *me_eval = NULL; - float(*defmats)[3][3] = NULL, (*deformedVerts)[3] = NULL; - int modifiers_left_num = 0; - VirtualModifierData virtualModifierData; - Object object_eval; - crazyspace_init_object_for_eval(depsgraph, object, &object_eval); - MultiresModifierData *mmd = get_multires_modifier(scene, &object_eval, 0); - const bool is_sculpt_mode = (object->mode & OB_MODE_SCULPT) != 0; - const bool has_multires = mmd != NULL && mmd->sculptlvl > 0; - const ModifierEvalContext mectx = {depsgraph, &object_eval, 0}; - - if (is_sculpt_mode && has_multires) { - *deformmats = NULL; - *deformcos = NULL; - return modifiers_left_num; - } - - md = BKE_modifiers_get_virtual_modifierlist(&object_eval, &virtualModifierData); - - for (; md; md = md->next) { - if (!BKE_modifier_is_enabled(scene, md, eModifierMode_Realtime)) { - continue; - } - - if (crazyspace_modifier_supports_deform_matrices(md)) { - const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); - if (defmats == NULL) { - /* NOTE: Evaluated object is re-set to its original un-deformed state. */ - Mesh *me = object_eval.data; - me_eval = BKE_mesh_copy_for_eval(me, true); - crazyspace_init_verts_and_matrices(me_eval, &defmats, &deformedVerts); - } - - if (mti->deformMatrices) { - mti->deformMatrices(md, &mectx, me_eval, deformedVerts, defmats, me_eval->totvert); - } - else { - /* More complex handling will continue in BKE_crazyspace_build_sculpt. - * Exiting the loop on a non-deform modifier causes issues - T71213. */ - BLI_assert(crazyspace_modifier_supports_deform(md)); - break; - } - } - } - - for (; md; md = md->next) { - if (!BKE_modifier_is_enabled(scene, md, eModifierMode_Realtime)) { - continue; - } - - if (crazyspace_modifier_supports_deform(md)) { - modifiers_left_num++; - } - } - - if (me_eval != NULL) { - BKE_id_free(NULL, me_eval); - } - - *deformmats = defmats; - *deformcos = deformedVerts; - - return modifiers_left_num; -} - -void BKE_crazyspace_build_sculpt(struct Depsgraph *depsgraph, - Scene *scene, - Object *object, - float (**deformmats)[3][3], - float (**deformcos)[3]) -{ - int totleft = BKE_sculpt_get_first_deform_matrices( - depsgraph, scene, object, deformmats, deformcos); - - if (totleft) { - /* There are deformation modifier which doesn't support deformation matrices calculation. - * Need additional crazy-space correction. */ - - Mesh *mesh = (Mesh *)object->data; - Mesh *mesh_eval = NULL; - - if (*deformcos == NULL) { - crazyspace_init_verts_and_matrices(mesh, deformmats, deformcos); - } - - float(*deformedVerts)[3] = *deformcos; - float(*origVerts)[3] = MEM_dupallocN(deformedVerts); - float(*quats)[4]; - int i, deformed = 0; - VirtualModifierData virtualModifierData; - Object object_eval; - crazyspace_init_object_for_eval(depsgraph, object, &object_eval); - ModifierData *md = BKE_modifiers_get_virtual_modifierlist(&object_eval, &virtualModifierData); - const ModifierEvalContext mectx = {depsgraph, &object_eval, 0}; - - for (; md; md = md->next) { - if (!BKE_modifier_is_enabled(scene, md, eModifierMode_Realtime)) { - continue; - } - - if (crazyspace_modifier_supports_deform(md)) { - const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); - - /* skip leading modifiers which have been already - * handled in sculpt_get_first_deform_matrices */ - if (mti->deformMatrices && !deformed) { - continue; - } - - if (mesh_eval == NULL) { - mesh_eval = BKE_mesh_copy_for_eval(mesh, true); - } - - mti->deformVerts(md, &mectx, mesh_eval, deformedVerts, mesh_eval->totvert); - deformed = 1; - } - } - - quats = MEM_mallocN(mesh->totvert * sizeof(*quats), "crazy quats"); - - BKE_crazyspace_set_quats_mesh(mesh, origVerts, deformedVerts, quats); - - for (i = 0; i < mesh->totvert; i++) { - float qmat[3][3], tmat[3][3]; - - quat_to_mat3(qmat, quats[i]); - mul_m3_m3m3(tmat, qmat, (*deformmats)[i]); - copy_m3_m3((*deformmats)[i], tmat); - } - - MEM_freeN(origVerts); - MEM_freeN(quats); - - if (mesh_eval != NULL) { - BKE_id_free(NULL, mesh_eval); - } - } - - if (*deformmats == NULL) { - int a, verts_num; - Mesh *mesh = (Mesh *)object->data; - - *deformcos = BKE_mesh_vert_coords_alloc(mesh, &verts_num); - *deformmats = MEM_callocN(sizeof(*(*deformmats)) * verts_num, "defmats"); - - for (a = 0; a < verts_num; a++) { - unit_m3((*deformmats)[a]); - } - } -} - -/* -------------------------------------------------------------------- */ -/** \name Crazyspace API - * \{ */ - -void BKE_crazyspace_api_eval(Depsgraph *depsgraph, - Scene *scene, - Object *object, - struct ReportList *reports) -{ - if (object->runtime.crazyspace_deform_imats != NULL || - object->runtime.crazyspace_deform_cos != NULL) { - return; - } - - if (object->type != OB_MESH) { - BKE_report(reports, - RPT_ERROR, - "Crazyspace transformation is only available for Mesh type of objects"); - return; - } - - const Mesh *mesh = (const Mesh *)object->data; - object->runtime.crazyspace_verts_num = mesh->totvert; - BKE_crazyspace_build_sculpt(depsgraph, - scene, - object, - &object->runtime.crazyspace_deform_imats, - &object->runtime.crazyspace_deform_cos); -} - -void BKE_crazyspace_api_displacement_to_deformed(struct Object *object, - struct ReportList *reports, - int vertex_index, - float displacement[3], - float r_displacement_deformed[3]) -{ - if (vertex_index < 0 || vertex_index >= object->runtime.crazyspace_verts_num) { - BKE_reportf(reports, - RPT_ERROR, - "Invalid vertex index %d (expected to be within 0 to %d range)", - vertex_index, - object->runtime.crazyspace_verts_num); - return; - } - - mul_v3_m3v3(r_displacement_deformed, - object->runtime.crazyspace_deform_imats[vertex_index], - displacement); -} - -void BKE_crazyspace_api_displacement_to_original(struct Object *object, - struct ReportList *reports, - int vertex_index, - float displacement_deformed[3], - float r_displacement[3]) -{ - if (vertex_index < 0 || vertex_index >= object->runtime.crazyspace_verts_num) { - BKE_reportf(reports, - RPT_ERROR, - "Invalid vertex index %d (expected to be within 0 to %d range))", - vertex_index, - object->runtime.crazyspace_verts_num); - return; - } - - float mat[3][3]; - if (!invert_m3_m3(mat, object->runtime.crazyspace_deform_imats[vertex_index])) { - copy_v3_v3(r_displacement, displacement_deformed); - return; - } - - mul_v3_m3v3(r_displacement, mat, displacement_deformed); -} - -void BKE_crazyspace_api_eval_clear(Object *object) -{ - MEM_SAFE_FREE(object->runtime.crazyspace_deform_imats); - MEM_SAFE_FREE(object->runtime.crazyspace_deform_cos); -} - -/** \} */ diff --git a/source/blender/blenkernel/intern/crazyspace.cc b/source/blender/blenkernel/intern/crazyspace.cc new file mode 100644 index 00000000000..c3db3095343 --- /dev/null +++ b/source/blender/blenkernel/intern/crazyspace.cc @@ -0,0 +1,588 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2005 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup bke + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BLI_bitmap.h" +#include "BLI_linklist.h" +#include "BLI_utildefines.h" + +#include "BKE_DerivedMesh.h" +#include "BKE_crazyspace.h" +#include "BKE_editmesh.h" +#include "BKE_lib_id.h" +#include "BKE_mesh.h" +#include "BKE_mesh_wrapper.h" +#include "BKE_modifier.h" +#include "BKE_multires.h" +#include "BKE_report.h" + +#include "DEG_depsgraph_query.h" + +BLI_INLINE void tan_calc_quat_v3(float r_quat[4], + const float co_1[3], + const float co_2[3], + const float co_3[3]) +{ + float vec_u[3], vec_v[3]; + float nor[3]; + + sub_v3_v3v3(vec_u, co_1, co_2); + sub_v3_v3v3(vec_v, co_1, co_3); + + cross_v3_v3v3(nor, vec_u, vec_v); + + if (normalize_v3(nor) > FLT_EPSILON) { + const float zero_vec[3] = {0.0f}; + tri_to_quat_ex(r_quat, zero_vec, vec_u, vec_v, nor); + } + else { + unit_qt(r_quat); + } +} + +static void set_crazy_vertex_quat(float r_quat[4], + const float co_1[3], + const float co_2[3], + const float co_3[3], + const float vd_1[3], + const float vd_2[3], + const float vd_3[3]) +{ + float q1[4], q2[4]; + + tan_calc_quat_v3(q1, co_1, co_2, co_3); + tan_calc_quat_v3(q2, vd_1, vd_2, vd_3); + + sub_qt_qtqt(r_quat, q2, q1); +} + +static bool modifiers_disable_subsurf_temporary(struct Scene *scene, Object *ob) +{ + bool disabled = false; + int cageIndex = BKE_modifiers_get_cage_index(scene, ob, nullptr, 1); + + ModifierData *md = static_cast(ob->modifiers.first); + for (int i = 0; md && i <= cageIndex; i++, md = md->next) { + if (md->type == eModifierType_Subsurf) { + md->mode ^= eModifierMode_DisableTemporary; + disabled = true; + } + } + + return disabled; +} + +float (*BKE_crazyspace_get_mapped_editverts(struct Depsgraph *depsgraph, Object *obedit))[3] +{ + Scene *scene = DEG_get_input_scene(depsgraph); + Scene *scene_eval = DEG_get_evaluated_scene(depsgraph); + Object *obedit_eval = DEG_get_evaluated_object(depsgraph, obedit); + Mesh *mesh_eval = static_cast(obedit_eval->data); + BMEditMesh *editmesh_eval = mesh_eval->edit_mesh; + + /* disable subsurf temporal, get mapped cos, and enable it */ + if (modifiers_disable_subsurf_temporary(scene_eval, obedit_eval)) { + /* need to make new derivemesh */ + makeDerivedMesh(depsgraph, scene_eval, obedit_eval, &CD_MASK_BAREMESH); + } + + /* now get the cage */ + Mesh *mesh_eval_cage = editbmesh_get_eval_cage_from_orig( + depsgraph, scene, obedit, &CD_MASK_BAREMESH); + + const int nverts = editmesh_eval->bm->totvert; + float(*vertexcos)[3] = static_cast( + MEM_mallocN(sizeof(*vertexcos) * nverts, "vertexcos map")); + mesh_get_mapped_verts_coords(mesh_eval_cage, vertexcos, nverts); + + /* set back the flag, no new cage needs to be built, transform does it */ + modifiers_disable_subsurf_temporary(scene_eval, obedit_eval); + + return vertexcos; +} + +void BKE_crazyspace_set_quats_editmesh(BMEditMesh *em, + float (*origcos)[3], + float (*mappedcos)[3], + float (*quats)[4], + const bool use_select) +{ + BMFace *f; + BMIter iter; + int index; + + { + BMVert *v; + BM_ITER_MESH_INDEX (v, &iter, em->bm, BM_VERTS_OF_MESH, index) { + BM_elem_flag_disable(v, BM_ELEM_TAG); + BM_elem_index_set(v, index); /* set_inline */ + } + em->bm->elem_index_dirty &= ~BM_VERT; + } + + BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { + BMLoop *l_iter, *l_first; + + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (BM_elem_flag_test(l_iter->v, BM_ELEM_HIDDEN) || + BM_elem_flag_test(l_iter->v, BM_ELEM_TAG) || + (use_select && !BM_elem_flag_test(l_iter->v, BM_ELEM_SELECT))) { + continue; + } + + if (!BM_elem_flag_test(l_iter->v, BM_ELEM_TAG)) { + const float *co_prev, *co_curr, *co_next; /* orig */ + const float *vd_prev, *vd_curr, *vd_next; /* deform */ + + const int i_prev = BM_elem_index_get(l_iter->prev->v); + const int i_curr = BM_elem_index_get(l_iter->v); + const int i_next = BM_elem_index_get(l_iter->next->v); + + /* retrieve mapped coordinates */ + vd_prev = mappedcos[i_prev]; + vd_curr = mappedcos[i_curr]; + vd_next = mappedcos[i_next]; + + if (origcos) { + co_prev = origcos[i_prev]; + co_curr = origcos[i_curr]; + co_next = origcos[i_next]; + } + else { + co_prev = l_iter->prev->v->co; + co_curr = l_iter->v->co; + co_next = l_iter->next->v->co; + } + + set_crazy_vertex_quat(quats[i_curr], co_curr, co_next, co_prev, vd_curr, vd_next, vd_prev); + + BM_elem_flag_enable(l_iter->v, BM_ELEM_TAG); + } + } while ((l_iter = l_iter->next) != l_first); + } +} + +void BKE_crazyspace_set_quats_mesh(Mesh *me, + float (*origcos)[3], + float (*mappedcos)[3], + float (*quats)[4]) +{ + BLI_bitmap *vert_tag = BLI_BITMAP_NEW(me->totvert, __func__); + + /* first store two sets of tangent vectors in vertices, we derive it just from the face-edges */ + MVert *mvert = me->mvert; + MPoly *mp = me->mpoly; + MLoop *mloop = me->mloop; + + for (int i = 0; i < me->totpoly; i++, mp++) { + MLoop *ml_next = &mloop[mp->loopstart]; + MLoop *ml_curr = &ml_next[mp->totloop - 1]; + MLoop *ml_prev = &ml_next[mp->totloop - 2]; + + for (int j = 0; j < mp->totloop; j++) { + if (!BLI_BITMAP_TEST(vert_tag, ml_curr->v)) { + const float *co_prev, *co_curr, *co_next; /* orig */ + const float *vd_prev, *vd_curr, *vd_next; /* deform */ + + /* retrieve mapped coordinates */ + vd_prev = mappedcos[ml_prev->v]; + vd_curr = mappedcos[ml_curr->v]; + vd_next = mappedcos[ml_next->v]; + + if (origcos) { + co_prev = origcos[ml_prev->v]; + co_curr = origcos[ml_curr->v]; + co_next = origcos[ml_next->v]; + } + else { + co_prev = mvert[ml_prev->v].co; + co_curr = mvert[ml_curr->v].co; + co_next = mvert[ml_next->v].co; + } + + set_crazy_vertex_quat( + quats[ml_curr->v], co_curr, co_next, co_prev, vd_curr, vd_next, vd_prev); + + BLI_BITMAP_ENABLE(vert_tag, ml_curr->v); + } + + ml_prev = ml_curr; + ml_curr = ml_next; + ml_next++; + } + } + + MEM_freeN(vert_tag); +} + +int BKE_crazyspace_get_first_deform_matrices_editbmesh(struct Depsgraph *depsgraph, + Scene *scene, + Object *ob, + BMEditMesh *em, + float (**deformmats)[3][3], + float (**deformcos)[3]) +{ + ModifierData *md; + Mesh *me_input = static_cast(ob->data); + Mesh *me = nullptr; + int i, a, modifiers_left_num = 0, verts_num = 0; + int cageIndex = BKE_modifiers_get_cage_index(scene, ob, nullptr, 1); + float(*defmats)[3][3] = nullptr, (*deformedVerts)[3] = nullptr; + VirtualModifierData virtualModifierData; + ModifierEvalContext mectx = {depsgraph, ob, ModifierApplyFlag(0)}; + + BKE_modifiers_clear_errors(ob); + + md = BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData); + + /* compute the deformation matrices and coordinates for the first + * modifiers with on cage editing that are enabled and support computing + * deform matrices */ + for (i = 0; md && i <= cageIndex; i++, md = md->next) { + const ModifierTypeInfo *mti = BKE_modifier_get_info(static_cast(md->type)); + + if (!editbmesh_modifier_is_enabled(scene, ob, md, me != nullptr)) { + continue; + } + + if (mti->type == eModifierTypeType_OnlyDeform && mti->deformMatricesEM) { + if (!defmats) { + const int required_mode = eModifierMode_Realtime | eModifierMode_Editmode; + CustomData_MeshMasks cd_mask_extra = CD_MASK_BAREMESH; + CDMaskLink *datamasks = BKE_modifier_calc_data_masks( + scene, ob, md, &cd_mask_extra, required_mode, nullptr, nullptr); + cd_mask_extra = datamasks->mask; + BLI_linklist_free((LinkNode *)datamasks, nullptr); + + me = BKE_mesh_wrapper_from_editmesh_with_coords(em, &cd_mask_extra, nullptr, me_input); + deformedVerts = editbmesh_vert_coords_alloc(em, &verts_num); + defmats = static_cast( + MEM_mallocN(sizeof(*defmats) * verts_num, "defmats")); + + for (a = 0; a < verts_num; a++) { + unit_m3(defmats[a]); + } + } + mti->deformMatricesEM(md, &mectx, em, me, deformedVerts, defmats, verts_num); + } + else { + break; + } + } + + for (; md && i <= cageIndex; md = md->next, i++) { + if (editbmesh_modifier_is_enabled(scene, ob, md, me != nullptr) && + BKE_modifier_is_correctable_deformed(md)) { + modifiers_left_num++; + } + } + + if (me) { + BKE_id_free(nullptr, me); + } + + *deformmats = defmats; + *deformcos = deformedVerts; + + return modifiers_left_num; +} + +/** + * Crazy-space evaluation needs to have an object which has all the fields + * evaluated, but the mesh data being at undeformed state. This way it can + * re-apply modifiers and also have proper pointers to key data blocks. + * + * Similar to #BKE_object_eval_reset(), but does not modify the actual evaluated object. + */ +static void crazyspace_init_object_for_eval(struct Depsgraph *depsgraph, + Object *object, + Object *object_crazy) +{ + Object *object_eval = DEG_get_evaluated_object(depsgraph, object); + *object_crazy = blender::dna::shallow_copy(*object_eval); + if (object_crazy->runtime.data_orig != nullptr) { + object_crazy->data = object_crazy->runtime.data_orig; + } +} + +static void crazyspace_init_verts_and_matrices(const Mesh *mesh, + float (**deformmats)[3][3], + float (**deformcos)[3]) +{ + int verts_num; + *deformcos = BKE_mesh_vert_coords_alloc(mesh, &verts_num); + *deformmats = static_cast( + MEM_callocN(sizeof(**deformmats) * verts_num, "defmats")); + for (int a = 0; a < verts_num; a++) { + unit_m3((*deformmats)[a]); + } + BLI_assert(verts_num == mesh->totvert); +} + +static bool crazyspace_modifier_supports_deform_matrices(ModifierData *md) +{ + if (ELEM(md->type, eModifierType_Subsurf, eModifierType_Multires)) { + return true; + } + const ModifierTypeInfo *mti = BKE_modifier_get_info(static_cast(md->type)); + return (mti->type == eModifierTypeType_OnlyDeform); +} + +static bool crazyspace_modifier_supports_deform(ModifierData *md) +{ + const ModifierTypeInfo *mti = BKE_modifier_get_info(static_cast(md->type)); + return (mti->type == eModifierTypeType_OnlyDeform); +} + +int BKE_sculpt_get_first_deform_matrices(struct Depsgraph *depsgraph, + Scene *scene, + Object *object, + float (**deformmats)[3][3], + float (**deformcos)[3]) +{ + ModifierData *md; + Mesh *me_eval = nullptr; + float(*defmats)[3][3] = nullptr, (*deformedVerts)[3] = nullptr; + int modifiers_left_num = 0; + VirtualModifierData virtualModifierData; + Object object_eval; + crazyspace_init_object_for_eval(depsgraph, object, &object_eval); + MultiresModifierData *mmd = get_multires_modifier(scene, &object_eval, 0); + const bool is_sculpt_mode = (object->mode & OB_MODE_SCULPT) != 0; + const bool has_multires = mmd != nullptr && mmd->sculptlvl > 0; + const ModifierEvalContext mectx = {depsgraph, &object_eval, ModifierApplyFlag(0)}; + + if (is_sculpt_mode && has_multires) { + *deformmats = nullptr; + *deformcos = nullptr; + return modifiers_left_num; + } + + md = BKE_modifiers_get_virtual_modifierlist(&object_eval, &virtualModifierData); + + for (; md; md = md->next) { + if (!BKE_modifier_is_enabled(scene, md, eModifierMode_Realtime)) { + continue; + } + + if (crazyspace_modifier_supports_deform_matrices(md)) { + const ModifierTypeInfo *mti = BKE_modifier_get_info(static_cast(md->type)); + if (defmats == nullptr) { + /* NOTE: Evaluated object is re-set to its original un-deformed state. */ + Mesh *me = static_cast(object_eval.data); + me_eval = BKE_mesh_copy_for_eval(me, true); + crazyspace_init_verts_and_matrices(me_eval, &defmats, &deformedVerts); + } + + if (mti->deformMatrices) { + mti->deformMatrices(md, &mectx, me_eval, deformedVerts, defmats, me_eval->totvert); + } + else { + /* More complex handling will continue in BKE_crazyspace_build_sculpt. + * Exiting the loop on a non-deform modifier causes issues - T71213. */ + BLI_assert(crazyspace_modifier_supports_deform(md)); + break; + } + } + } + + for (; md; md = md->next) { + if (!BKE_modifier_is_enabled(scene, md, eModifierMode_Realtime)) { + continue; + } + + if (crazyspace_modifier_supports_deform(md)) { + modifiers_left_num++; + } + } + + if (me_eval != nullptr) { + BKE_id_free(nullptr, me_eval); + } + + *deformmats = defmats; + *deformcos = deformedVerts; + + return modifiers_left_num; +} + +void BKE_crazyspace_build_sculpt(struct Depsgraph *depsgraph, + Scene *scene, + Object *object, + float (**deformmats)[3][3], + float (**deformcos)[3]) +{ + int totleft = BKE_sculpt_get_first_deform_matrices( + depsgraph, scene, object, deformmats, deformcos); + + if (totleft) { + /* There are deformation modifier which doesn't support deformation matrices calculation. + * Need additional crazy-space correction. */ + + Mesh *mesh = (Mesh *)object->data; + Mesh *mesh_eval = nullptr; + + if (*deformcos == nullptr) { + crazyspace_init_verts_and_matrices(mesh, deformmats, deformcos); + } + + float(*deformedVerts)[3] = *deformcos; + float(*origVerts)[3] = static_cast(MEM_dupallocN(deformedVerts)); + float(*quats)[4]; + int i, deformed = 0; + VirtualModifierData virtualModifierData; + Object object_eval; + crazyspace_init_object_for_eval(depsgraph, object, &object_eval); + ModifierData *md = BKE_modifiers_get_virtual_modifierlist(&object_eval, &virtualModifierData); + const ModifierEvalContext mectx = {depsgraph, &object_eval, ModifierApplyFlag(0)}; + + for (; md; md = md->next) { + if (!BKE_modifier_is_enabled(scene, md, eModifierMode_Realtime)) { + continue; + } + + if (crazyspace_modifier_supports_deform(md)) { + const ModifierTypeInfo *mti = BKE_modifier_get_info(static_cast(md->type)); + + /* skip leading modifiers which have been already + * handled in sculpt_get_first_deform_matrices */ + if (mti->deformMatrices && !deformed) { + continue; + } + + if (mesh_eval == nullptr) { + mesh_eval = BKE_mesh_copy_for_eval(mesh, true); + } + + mti->deformVerts(md, &mectx, mesh_eval, deformedVerts, mesh_eval->totvert); + deformed = 1; + } + } + + quats = static_cast(MEM_mallocN(mesh->totvert * sizeof(*quats), "crazy quats")); + + BKE_crazyspace_set_quats_mesh(mesh, origVerts, deformedVerts, quats); + + for (i = 0; i < mesh->totvert; i++) { + float qmat[3][3], tmat[3][3]; + + quat_to_mat3(qmat, quats[i]); + mul_m3_m3m3(tmat, qmat, (*deformmats)[i]); + copy_m3_m3((*deformmats)[i], tmat); + } + + MEM_freeN(origVerts); + MEM_freeN(quats); + + if (mesh_eval != nullptr) { + BKE_id_free(nullptr, mesh_eval); + } + } + + if (*deformmats == nullptr) { + int a, verts_num; + Mesh *mesh = (Mesh *)object->data; + + *deformcos = BKE_mesh_vert_coords_alloc(mesh, &verts_num); + *deformmats = static_cast( + MEM_callocN(sizeof(*(*deformmats)) * verts_num, "defmats")); + + for (a = 0; a < verts_num; a++) { + unit_m3((*deformmats)[a]); + } + } +} + +/* -------------------------------------------------------------------- */ +/** \name Crazyspace API + * \{ */ + +void BKE_crazyspace_api_eval(Depsgraph *depsgraph, + Scene *scene, + Object *object, + struct ReportList *reports) +{ + if (object->runtime.crazyspace_deform_imats != nullptr || + object->runtime.crazyspace_deform_cos != nullptr) { + return; + } + + if (object->type != OB_MESH) { + BKE_report(reports, + RPT_ERROR, + "Crazyspace transformation is only available for Mesh type of objects"); + return; + } + + const Mesh *mesh = (const Mesh *)object->data; + object->runtime.crazyspace_verts_num = mesh->totvert; + BKE_crazyspace_build_sculpt(depsgraph, + scene, + object, + &object->runtime.crazyspace_deform_imats, + &object->runtime.crazyspace_deform_cos); +} + +void BKE_crazyspace_api_displacement_to_deformed(struct Object *object, + struct ReportList *reports, + int vertex_index, + float displacement[3], + float r_displacement_deformed[3]) +{ + if (vertex_index < 0 || vertex_index >= object->runtime.crazyspace_verts_num) { + BKE_reportf(reports, + RPT_ERROR, + "Invalid vertex index %d (expected to be within 0 to %d range)", + vertex_index, + object->runtime.crazyspace_verts_num); + return; + } + + mul_v3_m3v3(r_displacement_deformed, + object->runtime.crazyspace_deform_imats[vertex_index], + displacement); +} + +void BKE_crazyspace_api_displacement_to_original(struct Object *object, + struct ReportList *reports, + int vertex_index, + float displacement_deformed[3], + float r_displacement[3]) +{ + if (vertex_index < 0 || vertex_index >= object->runtime.crazyspace_verts_num) { + BKE_reportf(reports, + RPT_ERROR, + "Invalid vertex index %d (expected to be within 0 to %d range))", + vertex_index, + object->runtime.crazyspace_verts_num); + return; + } + + float mat[3][3]; + if (!invert_m3_m3(mat, object->runtime.crazyspace_deform_imats[vertex_index])) { + copy_v3_v3(r_displacement, displacement_deformed); + return; + } + + mul_v3_m3v3(r_displacement, mat, displacement_deformed); +} + +void BKE_crazyspace_api_eval_clear(Object *object) +{ + MEM_SAFE_FREE(object->runtime.crazyspace_deform_imats); + MEM_SAFE_FREE(object->runtime.crazyspace_deform_cos); +} + +/** \} */ -- cgit v1.2.3 From e0d4aede4da8a1f670f53fe597140111b9726234 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 22 Jul 2022 16:49:37 +1000 Subject: BMesh: move bmesh_mesh to C++ This allows parts of the code to be threaded more easily. --- source/blender/bmesh/CMakeLists.txt | 2 +- source/blender/bmesh/intern/bmesh_mesh.c | 1354 ----------------------------- source/blender/bmesh/intern/bmesh_mesh.cc | 1350 ++++++++++++++++++++++++++++ 3 files changed, 1351 insertions(+), 1355 deletions(-) delete mode 100644 source/blender/bmesh/intern/bmesh_mesh.c create mode 100644 source/blender/bmesh/intern/bmesh_mesh.cc diff --git a/source/blender/bmesh/CMakeLists.txt b/source/blender/bmesh/CMakeLists.txt index 31492cd5c13..0d1eeab8eec 100644 --- a/source/blender/bmesh/CMakeLists.txt +++ b/source/blender/bmesh/CMakeLists.txt @@ -80,7 +80,7 @@ set(SRC intern/bmesh_log.h intern/bmesh_marking.c intern/bmesh_marking.h - intern/bmesh_mesh.c + intern/bmesh_mesh.cc intern/bmesh_mesh.h intern/bmesh_mesh_convert.cc intern/bmesh_mesh_convert.h diff --git a/source/blender/bmesh/intern/bmesh_mesh.c b/source/blender/bmesh/intern/bmesh_mesh.c deleted file mode 100644 index 7dba854b9ef..00000000000 --- a/source/blender/bmesh/intern/bmesh_mesh.c +++ /dev/null @@ -1,1354 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup bmesh - * - * BM mesh level functions. - */ - -#include "MEM_guardedalloc.h" - -#include "DNA_listBase.h" -#include "DNA_scene_types.h" - -#include "BLI_listbase.h" -#include "BLI_math.h" -#include "BLI_utildefines.h" - -#include "BKE_customdata.h" -#include "BKE_mesh.h" - -#include "bmesh.h" - -const BMAllocTemplate bm_mesh_allocsize_default = {512, 1024, 2048, 512}; -const BMAllocTemplate bm_mesh_chunksize_default = {512, 1024, 2048, 512}; - -static void bm_mempool_init_ex(const BMAllocTemplate *allocsize, - const bool use_toolflags, - BLI_mempool **r_vpool, - BLI_mempool **r_epool, - BLI_mempool **r_lpool, - BLI_mempool **r_fpool) -{ - size_t vert_size, edge_size, loop_size, face_size; - - if (use_toolflags == true) { - vert_size = sizeof(BMVert_OFlag); - edge_size = sizeof(BMEdge_OFlag); - loop_size = sizeof(BMLoop); - face_size = sizeof(BMFace_OFlag); - } - else { - vert_size = sizeof(BMVert); - edge_size = sizeof(BMEdge); - loop_size = sizeof(BMLoop); - face_size = sizeof(BMFace); - } - - if (r_vpool) { - *r_vpool = BLI_mempool_create( - vert_size, allocsize->totvert, bm_mesh_chunksize_default.totvert, BLI_MEMPOOL_ALLOW_ITER); - } - if (r_epool) { - *r_epool = BLI_mempool_create( - edge_size, allocsize->totedge, bm_mesh_chunksize_default.totedge, BLI_MEMPOOL_ALLOW_ITER); - } - if (r_lpool) { - *r_lpool = BLI_mempool_create( - loop_size, allocsize->totloop, bm_mesh_chunksize_default.totloop, BLI_MEMPOOL_NOP); - } - if (r_fpool) { - *r_fpool = BLI_mempool_create( - face_size, allocsize->totface, bm_mesh_chunksize_default.totface, BLI_MEMPOOL_ALLOW_ITER); - } -} - -static void bm_mempool_init(BMesh *bm, const BMAllocTemplate *allocsize, const bool use_toolflags) -{ - bm_mempool_init_ex(allocsize, use_toolflags, &bm->vpool, &bm->epool, &bm->lpool, &bm->fpool); - -#ifdef USE_BMESH_HOLES - bm->looplistpool = BLI_mempool_create(sizeof(BMLoopList), 512, 512, BLI_MEMPOOL_NOP); -#endif -} - -void BM_mesh_elem_toolflags_ensure(BMesh *bm) -{ - BLI_assert(bm->use_toolflags); - - if (bm->vtoolflagpool && bm->etoolflagpool && bm->ftoolflagpool) { - return; - } - - bm->vtoolflagpool = BLI_mempool_create(sizeof(BMFlagLayer), bm->totvert, 512, BLI_MEMPOOL_NOP); - bm->etoolflagpool = BLI_mempool_create(sizeof(BMFlagLayer), bm->totedge, 512, BLI_MEMPOOL_NOP); - bm->ftoolflagpool = BLI_mempool_create(sizeof(BMFlagLayer), bm->totface, 512, BLI_MEMPOOL_NOP); - - BMIter iter; - BMVert_OFlag *v_olfag; - BLI_mempool *toolflagpool = bm->vtoolflagpool; - BM_ITER_MESH (v_olfag, &iter, bm, BM_VERTS_OF_MESH) { - v_olfag->oflags = BLI_mempool_calloc(toolflagpool); - } - - BMEdge_OFlag *e_olfag; - toolflagpool = bm->etoolflagpool; - BM_ITER_MESH (e_olfag, &iter, bm, BM_EDGES_OF_MESH) { - e_olfag->oflags = BLI_mempool_calloc(toolflagpool); - } - - BMFace_OFlag *f_olfag; - toolflagpool = bm->ftoolflagpool; - BM_ITER_MESH (f_olfag, &iter, bm, BM_FACES_OF_MESH) { - f_olfag->oflags = BLI_mempool_calloc(toolflagpool); - } - - bm->totflags = 1; -} - -void BM_mesh_elem_toolflags_clear(BMesh *bm) -{ - if (bm->vtoolflagpool) { - BLI_mempool_destroy(bm->vtoolflagpool); - bm->vtoolflagpool = NULL; - } - if (bm->etoolflagpool) { - BLI_mempool_destroy(bm->etoolflagpool); - bm->etoolflagpool = NULL; - } - if (bm->ftoolflagpool) { - BLI_mempool_destroy(bm->ftoolflagpool); - bm->ftoolflagpool = NULL; - } -} - -BMesh *BM_mesh_create(const BMAllocTemplate *allocsize, const struct BMeshCreateParams *params) -{ - /* allocate the structure */ - BMesh *bm = MEM_callocN(sizeof(BMesh), __func__); - - /* allocate the memory pools for the mesh elements */ - bm_mempool_init(bm, allocsize, params->use_toolflags); - - /* allocate one flag pool that we don't get rid of. */ - bm->use_toolflags = params->use_toolflags; - bm->toolflag_index = 0; - bm->totflags = 0; - - CustomData_reset(&bm->vdata); - CustomData_reset(&bm->edata); - CustomData_reset(&bm->ldata); - CustomData_reset(&bm->pdata); - - return bm; -} - -void BM_mesh_data_free(BMesh *bm) -{ - BMVert *v; - BMEdge *e; - BMLoop *l; - BMFace *f; - - BMIter iter; - BMIter itersub; - - const bool is_ldata_free = CustomData_bmesh_has_free(&bm->ldata); - const bool is_pdata_free = CustomData_bmesh_has_free(&bm->pdata); - - /* Check if we have to call free, if not we can avoid a lot of looping */ - if (CustomData_bmesh_has_free(&(bm->vdata))) { - BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { - CustomData_bmesh_free_block(&(bm->vdata), &(v->head.data)); - } - } - if (CustomData_bmesh_has_free(&(bm->edata))) { - BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { - CustomData_bmesh_free_block(&(bm->edata), &(e->head.data)); - } - } - - if (is_ldata_free || is_pdata_free) { - BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { - if (is_pdata_free) { - CustomData_bmesh_free_block(&(bm->pdata), &(f->head.data)); - } - if (is_ldata_free) { - BM_ITER_ELEM (l, &itersub, f, BM_LOOPS_OF_FACE) { - CustomData_bmesh_free_block(&(bm->ldata), &(l->head.data)); - } - } - } - } - - /* Free custom data pools, This should probably go in CustomData_free? */ - if (bm->vdata.totlayer) { - BLI_mempool_destroy(bm->vdata.pool); - } - if (bm->edata.totlayer) { - BLI_mempool_destroy(bm->edata.pool); - } - if (bm->ldata.totlayer) { - BLI_mempool_destroy(bm->ldata.pool); - } - if (bm->pdata.totlayer) { - BLI_mempool_destroy(bm->pdata.pool); - } - - /* free custom data */ - CustomData_free(&bm->vdata, 0); - CustomData_free(&bm->edata, 0); - CustomData_free(&bm->ldata, 0); - CustomData_free(&bm->pdata, 0); - - /* destroy element pools */ - BLI_mempool_destroy(bm->vpool); - BLI_mempool_destroy(bm->epool); - BLI_mempool_destroy(bm->lpool); - BLI_mempool_destroy(bm->fpool); - - if (bm->vtable) { - MEM_freeN(bm->vtable); - } - if (bm->etable) { - MEM_freeN(bm->etable); - } - if (bm->ftable) { - MEM_freeN(bm->ftable); - } - - /* destroy flag pool */ - BM_mesh_elem_toolflags_clear(bm); - -#ifdef USE_BMESH_HOLES - BLI_mempool_destroy(bm->looplistpool); -#endif - - BLI_freelistN(&bm->selected); - - if (bm->lnor_spacearr) { - BKE_lnor_spacearr_free(bm->lnor_spacearr); - MEM_freeN(bm->lnor_spacearr); - } - - BMO_error_clear(bm); -} - -void BM_mesh_clear(BMesh *bm) -{ - const bool use_toolflags = bm->use_toolflags; - - /* free old mesh */ - BM_mesh_data_free(bm); - memset(bm, 0, sizeof(BMesh)); - - /* allocate the memory pools for the mesh elements */ - bm_mempool_init(bm, &bm_mesh_allocsize_default, use_toolflags); - - bm->use_toolflags = use_toolflags; - bm->toolflag_index = 0; - bm->totflags = 0; - - CustomData_reset(&bm->vdata); - CustomData_reset(&bm->edata); - CustomData_reset(&bm->ldata); - CustomData_reset(&bm->pdata); -} - -void BM_mesh_free(BMesh *bm) -{ - BM_mesh_data_free(bm); - - if (bm->py_handle) { - /* keep this out of 'BM_mesh_data_free' because we want python - * to be able to clear the mesh and maintain access. */ - bpy_bm_generic_invalidate(bm->py_handle); - bm->py_handle = NULL; - } - - MEM_freeN(bm); -} - -void bmesh_edit_begin(BMesh *UNUSED(bm), BMOpTypeFlag UNUSED(type_flag)) -{ - /* Most operators seem to be using BMO_OPTYPE_FLAG_UNTAN_MULTIRES to change the MDisps to - * absolute space during mesh edits. With this enabled, changes to the topology - * (loop cuts, edge subdivides, etc) are not reflected in the higher levels of - * the mesh at all, which doesn't seem right. Turning off completely for now, - * until this is shown to be better for certain types of mesh edits. */ -#ifdef BMOP_UNTAN_MULTIRES_ENABLED - /* switch multires data out of tangent space */ - if ((type_flag & BMO_OPTYPE_FLAG_UNTAN_MULTIRES) && - CustomData_has_layer(&bm->ldata, CD_MDISPS)) { - bmesh_mdisps_space_set(bm, MULTIRES_SPACE_TANGENT, MULTIRES_SPACE_ABSOLUTE); - - /* ensure correct normals, if possible */ - bmesh_rationalize_normals(bm, 0); - BM_mesh_normals_update(bm); - } -#endif -} - -void bmesh_edit_end(BMesh *bm, BMOpTypeFlag type_flag) -{ - ListBase select_history; - - /* BMO_OPTYPE_FLAG_UNTAN_MULTIRES disabled for now, see comment above in bmesh_edit_begin. */ -#ifdef BMOP_UNTAN_MULTIRES_ENABLED - /* switch multires data into tangent space */ - if ((flag & BMO_OPTYPE_FLAG_UNTAN_MULTIRES) && CustomData_has_layer(&bm->ldata, CD_MDISPS)) { - /* set normals to their previous winding */ - bmesh_rationalize_normals(bm, 1); - bmesh_mdisps_space_set(bm, MULTIRES_SPACE_ABSOLUTE, MULTIRES_SPACE_TANGENT); - } - else if (flag & BMO_OP_FLAG_RATIONALIZE_NORMALS) { - bmesh_rationalize_normals(bm, 1); - } -#endif - - /* compute normals, clear temp flags and flush selections */ - if (type_flag & BMO_OPTYPE_FLAG_NORMALS_CALC) { - bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL; - BM_mesh_normals_update(bm); - } - - if ((type_flag & BMO_OPTYPE_FLAG_SELECT_VALIDATE) == 0) { - select_history = bm->selected; - BLI_listbase_clear(&bm->selected); - } - - if (type_flag & BMO_OPTYPE_FLAG_SELECT_FLUSH) { - BM_mesh_select_mode_flush(bm); - } - - if ((type_flag & BMO_OPTYPE_FLAG_SELECT_VALIDATE) == 0) { - bm->selected = select_history; - } - if (type_flag & BMO_OPTYPE_FLAG_INVALIDATE_CLNOR_ALL) { - bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL; - } -} - -void BM_mesh_elem_index_ensure_ex(BMesh *bm, const char htype, int elem_offset[4]) -{ - -#ifdef DEBUG - BM_ELEM_INDEX_VALIDATE(bm, "Should Never Fail!", __func__); -#endif - - if (elem_offset == NULL) { - /* Simple case. */ - const char htype_needed = bm->elem_index_dirty & htype; - if (htype_needed == 0) { - goto finally; - } - } - - if (htype & BM_VERT) { - if ((bm->elem_index_dirty & BM_VERT) || (elem_offset && elem_offset[0])) { - BMIter iter; - BMElem *ele; - - int index = elem_offset ? elem_offset[0] : 0; - BM_ITER_MESH (ele, &iter, bm, BM_VERTS_OF_MESH) { - BM_elem_index_set(ele, index++); /* set_ok */ - } - BLI_assert(elem_offset || index == bm->totvert); - } - else { - // printf("%s: skipping vert index calc!\n", __func__); - } - } - - if (htype & BM_EDGE) { - if ((bm->elem_index_dirty & BM_EDGE) || (elem_offset && elem_offset[1])) { - BMIter iter; - BMElem *ele; - - int index = elem_offset ? elem_offset[1] : 0; - BM_ITER_MESH (ele, &iter, bm, BM_EDGES_OF_MESH) { - BM_elem_index_set(ele, index++); /* set_ok */ - } - BLI_assert(elem_offset || index == bm->totedge); - } - else { - // printf("%s: skipping edge index calc!\n", __func__); - } - } - - if (htype & (BM_FACE | BM_LOOP)) { - if ((bm->elem_index_dirty & (BM_FACE | BM_LOOP)) || - (elem_offset && (elem_offset[2] || elem_offset[3]))) { - BMIter iter; - BMElem *ele; - - const bool update_face = (htype & BM_FACE) && (bm->elem_index_dirty & BM_FACE); - const bool update_loop = (htype & BM_LOOP) && (bm->elem_index_dirty & BM_LOOP); - - int index_loop = elem_offset ? elem_offset[2] : 0; - int index = elem_offset ? elem_offset[3] : 0; - - BM_ITER_MESH (ele, &iter, bm, BM_FACES_OF_MESH) { - if (update_face) { - BM_elem_index_set(ele, index++); /* set_ok */ - } - - if (update_loop) { - BMLoop *l_iter, *l_first; - - l_iter = l_first = BM_FACE_FIRST_LOOP((BMFace *)ele); - do { - BM_elem_index_set(l_iter, index_loop++); /* set_ok */ - } while ((l_iter = l_iter->next) != l_first); - } - } - - BLI_assert(elem_offset || !update_face || index == bm->totface); - if (update_loop) { - BLI_assert(elem_offset || !update_loop || index_loop == bm->totloop); - } - } - else { - // printf("%s: skipping face/loop index calc!\n", __func__); - } - } - -finally: - bm->elem_index_dirty &= ~htype; - if (elem_offset) { - if (htype & BM_VERT) { - elem_offset[0] += bm->totvert; - if (elem_offset[0] != bm->totvert) { - bm->elem_index_dirty |= BM_VERT; - } - } - if (htype & BM_EDGE) { - elem_offset[1] += bm->totedge; - if (elem_offset[1] != bm->totedge) { - bm->elem_index_dirty |= BM_EDGE; - } - } - if (htype & BM_LOOP) { - elem_offset[2] += bm->totloop; - if (elem_offset[2] != bm->totloop) { - bm->elem_index_dirty |= BM_LOOP; - } - } - if (htype & BM_FACE) { - elem_offset[3] += bm->totface; - if (elem_offset[3] != bm->totface) { - bm->elem_index_dirty |= BM_FACE; - } - } - } -} - -void BM_mesh_elem_index_ensure(BMesh *bm, const char htype) -{ - BM_mesh_elem_index_ensure_ex(bm, htype, NULL); -} - -void BM_mesh_elem_index_validate( - BMesh *bm, const char *location, const char *func, const char *msg_a, const char *msg_b) -{ - const char iter_types[3] = {BM_VERTS_OF_MESH, BM_EDGES_OF_MESH, BM_FACES_OF_MESH}; - - const char flag_types[3] = {BM_VERT, BM_EDGE, BM_FACE}; - const char *type_names[3] = {"vert", "edge", "face"}; - - BMIter iter; - BMElem *ele; - int i; - bool is_any_error = 0; - - for (i = 0; i < 3; i++) { - const bool is_dirty = (flag_types[i] & bm->elem_index_dirty) != 0; - int index = 0; - bool is_error = false; - int err_val = 0; - int err_idx = 0; - - BM_ITER_MESH (ele, &iter, bm, iter_types[i]) { - if (!is_dirty) { - if (BM_elem_index_get(ele) != index) { - err_val = BM_elem_index_get(ele); - err_idx = index; - is_error = true; - break; - } - } - index++; - } - - if ((is_error == true) && (is_dirty == false)) { - is_any_error = true; - fprintf(stderr, - "Invalid Index: at %s, %s, %s[%d] invalid index %d, '%s', '%s'\n", - location, - func, - type_names[i], - err_idx, - err_val, - msg_a, - msg_b); - } - else if ((is_error == false) && (is_dirty == true)) { - -#if 0 /* mostly annoying */ - - /* dirty may have been incorrectly set */ - fprintf(stderr, - "Invalid Dirty: at %s, %s (%s), dirty flag was set but all index values are " - "correct, '%s', '%s'\n", - location, - func, - type_names[i], - msg_a, - msg_b); -#endif - } - } - -#if 0 /* mostly annoying, even in debug mode */ -# ifdef DEBUG - if (is_any_error == 0) { - fprintf(stderr, "Valid Index Success: at %s, %s, '%s', '%s'\n", location, func, msg_a, msg_b); - } -# endif -#endif - (void)is_any_error; /* shut up the compiler */ -} - -/* debug check only - no need to optimize */ -#ifndef NDEBUG -bool BM_mesh_elem_table_check(BMesh *bm) -{ - BMIter iter; - BMElem *ele; - int i; - - if (bm->vtable && ((bm->elem_table_dirty & BM_VERT) == 0)) { - BM_ITER_MESH_INDEX (ele, &iter, bm, BM_VERTS_OF_MESH, i) { - if (ele != (BMElem *)bm->vtable[i]) { - return false; - } - } - } - - if (bm->etable && ((bm->elem_table_dirty & BM_EDGE) == 0)) { - BM_ITER_MESH_INDEX (ele, &iter, bm, BM_EDGES_OF_MESH, i) { - if (ele != (BMElem *)bm->etable[i]) { - return false; - } - } - } - - if (bm->ftable && ((bm->elem_table_dirty & BM_FACE) == 0)) { - BM_ITER_MESH_INDEX (ele, &iter, bm, BM_FACES_OF_MESH, i) { - if (ele != (BMElem *)bm->ftable[i]) { - return false; - } - } - } - - return true; -} -#endif - -void BM_mesh_elem_table_ensure(BMesh *bm, const char htype) -{ - /* assume if the array is non-null then its valid and no need to recalc */ - const char htype_needed = - (((bm->vtable && ((bm->elem_table_dirty & BM_VERT) == 0)) ? 0 : BM_VERT) | - ((bm->etable && ((bm->elem_table_dirty & BM_EDGE) == 0)) ? 0 : BM_EDGE) | - ((bm->ftable && ((bm->elem_table_dirty & BM_FACE) == 0)) ? 0 : BM_FACE)) & - htype; - - BLI_assert((htype & ~BM_ALL_NOLOOP) == 0); - - /* in debug mode double check we didn't need to recalculate */ - BLI_assert(BM_mesh_elem_table_check(bm) == true); - - if (htype_needed == 0) { - goto finally; - } - - if (htype_needed & BM_VERT) { - if (bm->vtable && bm->totvert <= bm->vtable_tot && bm->totvert * 2 >= bm->vtable_tot) { - /* pass (re-use the array) */ - } - else { - if (bm->vtable) { - MEM_freeN(bm->vtable); - } - bm->vtable = MEM_mallocN(sizeof(void **) * bm->totvert, "bm->vtable"); - bm->vtable_tot = bm->totvert; - } - } - if (htype_needed & BM_EDGE) { - if (bm->etable && bm->totedge <= bm->etable_tot && bm->totedge * 2 >= bm->etable_tot) { - /* pass (re-use the array) */ - } - else { - if (bm->etable) { - MEM_freeN(bm->etable); - } - bm->etable = MEM_mallocN(sizeof(void **) * bm->totedge, "bm->etable"); - bm->etable_tot = bm->totedge; - } - } - if (htype_needed & BM_FACE) { - if (bm->ftable && bm->totface <= bm->ftable_tot && bm->totface * 2 >= bm->ftable_tot) { - /* pass (re-use the array) */ - } - else { - if (bm->ftable) { - MEM_freeN(bm->ftable); - } - bm->ftable = MEM_mallocN(sizeof(void **) * bm->totface, "bm->ftable"); - bm->ftable_tot = bm->totface; - } - } - - if (htype_needed & BM_VERT) { - BM_iter_as_array(bm, BM_VERTS_OF_MESH, NULL, (void **)bm->vtable, bm->totvert); - } - - if (htype_needed & BM_EDGE) { - BM_iter_as_array(bm, BM_EDGES_OF_MESH, NULL, (void **)bm->etable, bm->totedge); - } - - if (htype_needed & BM_FACE) { - BM_iter_as_array(bm, BM_FACES_OF_MESH, NULL, (void **)bm->ftable, bm->totface); - } - -finally: - /* Only clear dirty flags when all the pointers and data are actually valid. - * This prevents possible threading issues when dirty flag check failed but - * data wasn't ready still. - */ - bm->elem_table_dirty &= ~htype_needed; -} - -void BM_mesh_elem_table_init(BMesh *bm, const char htype) -{ - BLI_assert((htype & ~BM_ALL_NOLOOP) == 0); - - /* force recalc */ - BM_mesh_elem_table_free(bm, BM_ALL_NOLOOP); - BM_mesh_elem_table_ensure(bm, htype); -} - -void BM_mesh_elem_table_free(BMesh *bm, const char htype) -{ - if (htype & BM_VERT) { - MEM_SAFE_FREE(bm->vtable); - } - - if (htype & BM_EDGE) { - MEM_SAFE_FREE(bm->etable); - } - - if (htype & BM_FACE) { - MEM_SAFE_FREE(bm->ftable); - } -} - -BMVert *BM_vert_at_index_find(BMesh *bm, const int index) -{ - return BLI_mempool_findelem(bm->vpool, index); -} - -BMEdge *BM_edge_at_index_find(BMesh *bm, const int index) -{ - return BLI_mempool_findelem(bm->epool, index); -} - -BMFace *BM_face_at_index_find(BMesh *bm, const int index) -{ - return BLI_mempool_findelem(bm->fpool, index); -} - -BMLoop *BM_loop_at_index_find(BMesh *bm, const int index) -{ - BMIter iter; - BMFace *f; - int i = index; - BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { - if (i < f->len) { - BMLoop *l_first, *l_iter; - l_iter = l_first = BM_FACE_FIRST_LOOP(f); - do { - if (i == 0) { - return l_iter; - } - i -= 1; - } while ((l_iter = l_iter->next) != l_first); - } - i -= f->len; - } - return NULL; -} - -BMVert *BM_vert_at_index_find_or_table(BMesh *bm, const int index) -{ - if ((bm->elem_table_dirty & BM_VERT) == 0) { - return (index < bm->totvert) ? bm->vtable[index] : NULL; - } - return BM_vert_at_index_find(bm, index); -} - -BMEdge *BM_edge_at_index_find_or_table(BMesh *bm, const int index) -{ - if ((bm->elem_table_dirty & BM_EDGE) == 0) { - return (index < bm->totedge) ? bm->etable[index] : NULL; - } - return BM_edge_at_index_find(bm, index); -} - -BMFace *BM_face_at_index_find_or_table(BMesh *bm, const int index) -{ - if ((bm->elem_table_dirty & BM_FACE) == 0) { - return (index < bm->totface) ? bm->ftable[index] : NULL; - } - return BM_face_at_index_find(bm, index); -} - -int BM_mesh_elem_count(BMesh *bm, const char htype) -{ - BLI_assert((htype & ~BM_ALL_NOLOOP) == 0); - - switch (htype) { - case BM_VERT: - return bm->totvert; - case BM_EDGE: - return bm->totedge; - case BM_FACE: - return bm->totface; - default: { - BLI_assert(0); - return 0; - } - } -} - -void BM_mesh_remap(BMesh *bm, const uint *vert_idx, const uint *edge_idx, const uint *face_idx) -{ - /* Mapping old to new pointers. */ - GHash *vptr_map = NULL, *eptr_map = NULL, *fptr_map = NULL; - BMIter iter, iterl; - BMVert *ve; - BMEdge *ed; - BMFace *fa; - BMLoop *lo; - - if (!(vert_idx || edge_idx || face_idx)) { - return; - } - - BM_mesh_elem_table_ensure( - bm, (vert_idx ? BM_VERT : 0) | (edge_idx ? BM_EDGE : 0) | (face_idx ? BM_FACE : 0)); - - /* Remap Verts */ - if (vert_idx) { - BMVert **verts_pool, *verts_copy, **vep; - int i, totvert = bm->totvert; - const uint *new_idx; - /* Special case: Python uses custom data layers to hold PyObject references. - * These have to be kept in place, else the PyObjects we point to, won't point back to us. */ - const int cd_vert_pyptr = CustomData_get_offset(&bm->vdata, CD_BM_ELEM_PYPTR); - - /* Init the old-to-new vert pointers mapping */ - vptr_map = BLI_ghash_ptr_new_ex("BM_mesh_remap vert pointers mapping", bm->totvert); - - /* Make a copy of all vertices. */ - verts_pool = bm->vtable; - verts_copy = MEM_mallocN(sizeof(BMVert) * totvert, "BM_mesh_remap verts copy"); - void **pyptrs = (cd_vert_pyptr != -1) ? MEM_mallocN(sizeof(void *) * totvert, __func__) : NULL; - for (i = totvert, ve = verts_copy + totvert - 1, vep = verts_pool + totvert - 1; i--; - ve--, vep--) { - *ve = **vep; - // printf("*vep: %p, verts_pool[%d]: %p\n", *vep, i, verts_pool[i]); - if (cd_vert_pyptr != -1) { - void **pyptr = BM_ELEM_CD_GET_VOID_P(((BMElem *)ve), cd_vert_pyptr); - pyptrs[i] = *pyptr; - } - } - - /* Copy back verts to their new place, and update old2new pointers mapping. */ - new_idx = vert_idx + totvert - 1; - ve = verts_copy + totvert - 1; - vep = verts_pool + totvert - 1; /* old, org pointer */ - for (i = totvert; i--; new_idx--, ve--, vep--) { - BMVert *new_vep = verts_pool[*new_idx]; - *new_vep = *ve; -#if 0 - printf( - "mapping vert from %d to %d (%p/%p to %p)\n", i, *new_idx, *vep, verts_pool[i], new_vep); -#endif - BLI_ghash_insert(vptr_map, *vep, new_vep); - if (cd_vert_pyptr != -1) { - void **pyptr = BM_ELEM_CD_GET_VOID_P(((BMElem *)new_vep), cd_vert_pyptr); - *pyptr = pyptrs[*new_idx]; - } - } - bm->elem_index_dirty |= BM_VERT; - bm->elem_table_dirty |= BM_VERT; - - MEM_freeN(verts_copy); - if (pyptrs) { - MEM_freeN(pyptrs); - } - } - - /* Remap Edges */ - if (edge_idx) { - BMEdge **edges_pool, *edges_copy, **edp; - int i, totedge = bm->totedge; - const uint *new_idx; - /* Special case: Python uses custom data layers to hold PyObject references. - * These have to be kept in place, else the PyObjects we point to, won't point back to us. */ - const int cd_edge_pyptr = CustomData_get_offset(&bm->edata, CD_BM_ELEM_PYPTR); - - /* Init the old-to-new vert pointers mapping */ - eptr_map = BLI_ghash_ptr_new_ex("BM_mesh_remap edge pointers mapping", bm->totedge); - - /* Make a copy of all vertices. */ - edges_pool = bm->etable; - edges_copy = MEM_mallocN(sizeof(BMEdge) * totedge, "BM_mesh_remap edges copy"); - void **pyptrs = (cd_edge_pyptr != -1) ? MEM_mallocN(sizeof(void *) * totedge, __func__) : NULL; - for (i = totedge, ed = edges_copy + totedge - 1, edp = edges_pool + totedge - 1; i--; - ed--, edp--) { - *ed = **edp; - if (cd_edge_pyptr != -1) { - void **pyptr = BM_ELEM_CD_GET_VOID_P(((BMElem *)ed), cd_edge_pyptr); - pyptrs[i] = *pyptr; - } - } - - /* Copy back verts to their new place, and update old2new pointers mapping. */ - new_idx = edge_idx + totedge - 1; - ed = edges_copy + totedge - 1; - edp = edges_pool + totedge - 1; /* old, org pointer */ - for (i = totedge; i--; new_idx--, ed--, edp--) { - BMEdge *new_edp = edges_pool[*new_idx]; - *new_edp = *ed; - BLI_ghash_insert(eptr_map, *edp, new_edp); -#if 0 - printf( - "mapping edge from %d to %d (%p/%p to %p)\n", i, *new_idx, *edp, edges_pool[i], new_edp); -#endif - if (cd_edge_pyptr != -1) { - void **pyptr = BM_ELEM_CD_GET_VOID_P(((BMElem *)new_edp), cd_edge_pyptr); - *pyptr = pyptrs[*new_idx]; - } - } - bm->elem_index_dirty |= BM_EDGE; - bm->elem_table_dirty |= BM_EDGE; - - MEM_freeN(edges_copy); - if (pyptrs) { - MEM_freeN(pyptrs); - } - } - - /* Remap Faces */ - if (face_idx) { - BMFace **faces_pool, *faces_copy, **fap; - int i, totface = bm->totface; - const uint *new_idx; - /* Special case: Python uses custom data layers to hold PyObject references. - * These have to be kept in place, else the PyObjects we point to, won't point back to us. */ - const int cd_poly_pyptr = CustomData_get_offset(&bm->pdata, CD_BM_ELEM_PYPTR); - - /* Init the old-to-new vert pointers mapping */ - fptr_map = BLI_ghash_ptr_new_ex("BM_mesh_remap face pointers mapping", bm->totface); - - /* Make a copy of all vertices. */ - faces_pool = bm->ftable; - faces_copy = MEM_mallocN(sizeof(BMFace) * totface, "BM_mesh_remap faces copy"); - void **pyptrs = (cd_poly_pyptr != -1) ? MEM_mallocN(sizeof(void *) * totface, __func__) : NULL; - for (i = totface, fa = faces_copy + totface - 1, fap = faces_pool + totface - 1; i--; - fa--, fap--) { - *fa = **fap; - if (cd_poly_pyptr != -1) { - void **pyptr = BM_ELEM_CD_GET_VOID_P(((BMElem *)fa), cd_poly_pyptr); - pyptrs[i] = *pyptr; - } - } - - /* Copy back verts to their new place, and update old2new pointers mapping. */ - new_idx = face_idx + totface - 1; - fa = faces_copy + totface - 1; - fap = faces_pool + totface - 1; /* old, org pointer */ - for (i = totface; i--; new_idx--, fa--, fap--) { - BMFace *new_fap = faces_pool[*new_idx]; - *new_fap = *fa; - BLI_ghash_insert(fptr_map, *fap, new_fap); - if (cd_poly_pyptr != -1) { - void **pyptr = BM_ELEM_CD_GET_VOID_P(((BMElem *)new_fap), cd_poly_pyptr); - *pyptr = pyptrs[*new_idx]; - } - } - - bm->elem_index_dirty |= BM_FACE | BM_LOOP; - bm->elem_table_dirty |= BM_FACE; - - MEM_freeN(faces_copy); - if (pyptrs) { - MEM_freeN(pyptrs); - } - } - - /* And now, fix all vertices/edges/faces/loops pointers! */ - /* Verts' pointers, only edge pointers... */ - if (eptr_map) { - BM_ITER_MESH (ve, &iter, bm, BM_VERTS_OF_MESH) { - // printf("Vert e: %p -> %p\n", ve->e, BLI_ghash_lookup(eptr_map, ve->e)); - if (ve->e) { - ve->e = BLI_ghash_lookup(eptr_map, ve->e); - BLI_assert(ve->e); - } - } - } - - /* Edges' pointers, only vert pointers (as we don't mess with loops!), - * and - ack! - edge pointers, - * as we have to handle disk-links. */ - if (vptr_map || eptr_map) { - BM_ITER_MESH (ed, &iter, bm, BM_EDGES_OF_MESH) { - if (vptr_map) { -#if 0 - printf("Edge v1: %p -> %p\n", ed->v1, BLI_ghash_lookup(vptr_map, ed->v1)); - printf("Edge v2: %p -> %p\n", ed->v2, BLI_ghash_lookup(vptr_map, ed->v2)); -#endif - ed->v1 = BLI_ghash_lookup(vptr_map, ed->v1); - ed->v2 = BLI_ghash_lookup(vptr_map, ed->v2); - BLI_assert(ed->v1); - BLI_assert(ed->v2); - } - if (eptr_map) { -#if 0 - printf("Edge v1_disk_link prev: %p -> %p\n", - ed->v1_disk_link.prev, - BLI_ghash_lookup(eptr_map, ed->v1_disk_link.prev)); - printf("Edge v1_disk_link next: %p -> %p\n", - ed->v1_disk_link.next, - BLI_ghash_lookup(eptr_map, ed->v1_disk_link.next)); - printf("Edge v2_disk_link prev: %p -> %p\n", - ed->v2_disk_link.prev, - BLI_ghash_lookup(eptr_map, ed->v2_disk_link.prev)); - printf("Edge v2_disk_link next: %p -> %p\n", - ed->v2_disk_link.next, - BLI_ghash_lookup(eptr_map, ed->v2_disk_link.next)); -#endif - ed->v1_disk_link.prev = BLI_ghash_lookup(eptr_map, ed->v1_disk_link.prev); - ed->v1_disk_link.next = BLI_ghash_lookup(eptr_map, ed->v1_disk_link.next); - ed->v2_disk_link.prev = BLI_ghash_lookup(eptr_map, ed->v2_disk_link.prev); - ed->v2_disk_link.next = BLI_ghash_lookup(eptr_map, ed->v2_disk_link.next); - BLI_assert(ed->v1_disk_link.prev); - BLI_assert(ed->v1_disk_link.next); - BLI_assert(ed->v2_disk_link.prev); - BLI_assert(ed->v2_disk_link.next); - } - } - } - - /* Faces' pointers (loops, in fact), always needed... */ - BM_ITER_MESH (fa, &iter, bm, BM_FACES_OF_MESH) { - BM_ITER_ELEM (lo, &iterl, fa, BM_LOOPS_OF_FACE) { - if (vptr_map) { - // printf("Loop v: %p -> %p\n", lo->v, BLI_ghash_lookup(vptr_map, lo->v)); - lo->v = BLI_ghash_lookup(vptr_map, lo->v); - BLI_assert(lo->v); - } - if (eptr_map) { - // printf("Loop e: %p -> %p\n", lo->e, BLI_ghash_lookup(eptr_map, lo->e)); - lo->e = BLI_ghash_lookup(eptr_map, lo->e); - BLI_assert(lo->e); - } - if (fptr_map) { - // printf("Loop f: %p -> %p\n", lo->f, BLI_ghash_lookup(fptr_map, lo->f)); - lo->f = BLI_ghash_lookup(fptr_map, lo->f); - BLI_assert(lo->f); - } - } - } - - /* Selection history */ - { - BMEditSelection *ese; - for (ese = bm->selected.first; ese; ese = ese->next) { - switch (ese->htype) { - case BM_VERT: - if (vptr_map) { - ese->ele = BLI_ghash_lookup(vptr_map, ese->ele); - BLI_assert(ese->ele); - } - break; - case BM_EDGE: - if (eptr_map) { - ese->ele = BLI_ghash_lookup(eptr_map, ese->ele); - BLI_assert(ese->ele); - } - break; - case BM_FACE: - if (fptr_map) { - ese->ele = BLI_ghash_lookup(fptr_map, ese->ele); - BLI_assert(ese->ele); - } - break; - } - } - } - - if (fptr_map) { - if (bm->act_face) { - bm->act_face = BLI_ghash_lookup(fptr_map, bm->act_face); - BLI_assert(bm->act_face); - } - } - - if (vptr_map) { - BLI_ghash_free(vptr_map, NULL, NULL); - } - if (eptr_map) { - BLI_ghash_free(eptr_map, NULL, NULL); - } - if (fptr_map) { - BLI_ghash_free(fptr_map, NULL, NULL); - } -} - -void BM_mesh_rebuild(BMesh *bm, - const struct BMeshCreateParams *params, - BLI_mempool *vpool_dst, - BLI_mempool *epool_dst, - BLI_mempool *lpool_dst, - BLI_mempool *fpool_dst) -{ - const char remap = (vpool_dst ? BM_VERT : 0) | (epool_dst ? BM_EDGE : 0) | - (lpool_dst ? BM_LOOP : 0) | (fpool_dst ? BM_FACE : 0); - - BMVert **vtable_dst = (remap & BM_VERT) ? MEM_mallocN(bm->totvert * sizeof(BMVert *), __func__) : - NULL; - BMEdge **etable_dst = (remap & BM_EDGE) ? MEM_mallocN(bm->totedge * sizeof(BMEdge *), __func__) : - NULL; - BMLoop **ltable_dst = (remap & BM_LOOP) ? MEM_mallocN(bm->totloop * sizeof(BMLoop *), __func__) : - NULL; - BMFace **ftable_dst = (remap & BM_FACE) ? MEM_mallocN(bm->totface * sizeof(BMFace *), __func__) : - NULL; - - const bool use_toolflags = params->use_toolflags; - - if (remap & BM_VERT) { - BMIter iter; - int index; - BMVert *v_src; - BM_ITER_MESH_INDEX (v_src, &iter, bm, BM_VERTS_OF_MESH, index) { - BMVert *v_dst = BLI_mempool_alloc(vpool_dst); - memcpy(v_dst, v_src, sizeof(BMVert)); - if (use_toolflags) { - ((BMVert_OFlag *)v_dst)->oflags = bm->vtoolflagpool ? - BLI_mempool_calloc(bm->vtoolflagpool) : - NULL; - } - - vtable_dst[index] = v_dst; - BM_elem_index_set(v_src, index); /* set_ok */ - } - } - - if (remap & BM_EDGE) { - BMIter iter; - int index; - BMEdge *e_src; - BM_ITER_MESH_INDEX (e_src, &iter, bm, BM_EDGES_OF_MESH, index) { - BMEdge *e_dst = BLI_mempool_alloc(epool_dst); - memcpy(e_dst, e_src, sizeof(BMEdge)); - if (use_toolflags) { - ((BMEdge_OFlag *)e_dst)->oflags = bm->etoolflagpool ? - BLI_mempool_calloc(bm->etoolflagpool) : - NULL; - } - - etable_dst[index] = e_dst; - BM_elem_index_set(e_src, index); /* set_ok */ - } - } - - if (remap & (BM_LOOP | BM_FACE)) { - BMIter iter; - int index, index_loop = 0; - BMFace *f_src; - BM_ITER_MESH_INDEX (f_src, &iter, bm, BM_FACES_OF_MESH, index) { - - if (remap & BM_FACE) { - BMFace *f_dst = BLI_mempool_alloc(fpool_dst); - memcpy(f_dst, f_src, sizeof(BMFace)); - if (use_toolflags) { - ((BMFace_OFlag *)f_dst)->oflags = bm->ftoolflagpool ? - BLI_mempool_calloc(bm->ftoolflagpool) : - NULL; - } - - ftable_dst[index] = f_dst; - BM_elem_index_set(f_src, index); /* set_ok */ - } - - /* handle loops */ - if (remap & BM_LOOP) { - BMLoop *l_iter_src, *l_first_src; - l_iter_src = l_first_src = BM_FACE_FIRST_LOOP((BMFace *)f_src); - do { - BMLoop *l_dst = BLI_mempool_alloc(lpool_dst); - memcpy(l_dst, l_iter_src, sizeof(BMLoop)); - ltable_dst[index_loop] = l_dst; - BM_elem_index_set(l_iter_src, index_loop++); /* set_ok */ - } while ((l_iter_src = l_iter_src->next) != l_first_src); - } - } - } - -#define MAP_VERT(ele) vtable_dst[BM_elem_index_get(ele)] -#define MAP_EDGE(ele) etable_dst[BM_elem_index_get(ele)] -#define MAP_LOOP(ele) ltable_dst[BM_elem_index_get(ele)] -#define MAP_FACE(ele) ftable_dst[BM_elem_index_get(ele)] - -#define REMAP_VERT(ele) \ - { \ - if (remap & BM_VERT) { \ - ele = MAP_VERT(ele); \ - } \ - } \ - ((void)0) -#define REMAP_EDGE(ele) \ - { \ - if (remap & BM_EDGE) { \ - ele = MAP_EDGE(ele); \ - } \ - } \ - ((void)0) -#define REMAP_LOOP(ele) \ - { \ - if (remap & BM_LOOP) { \ - ele = MAP_LOOP(ele); \ - } \ - } \ - ((void)0) -#define REMAP_FACE(ele) \ - { \ - if (remap & BM_FACE) { \ - ele = MAP_FACE(ele); \ - } \ - } \ - ((void)0) - - /* verts */ - { - for (int i = 0; i < bm->totvert; i++) { - BMVert *v = vtable_dst[i]; - if (v->e) { - REMAP_EDGE(v->e); - } - } - } - - /* edges */ - { - for (int i = 0; i < bm->totedge; i++) { - BMEdge *e = etable_dst[i]; - REMAP_VERT(e->v1); - REMAP_VERT(e->v2); - REMAP_EDGE(e->v1_disk_link.next); - REMAP_EDGE(e->v1_disk_link.prev); - REMAP_EDGE(e->v2_disk_link.next); - REMAP_EDGE(e->v2_disk_link.prev); - if (e->l) { - REMAP_LOOP(e->l); - } - } - } - - /* faces */ - { - for (int i = 0; i < bm->totface; i++) { - BMFace *f = ftable_dst[i]; - REMAP_LOOP(f->l_first); - - { - BMLoop *l_iter, *l_first; - l_iter = l_first = BM_FACE_FIRST_LOOP((BMFace *)f); - do { - REMAP_VERT(l_iter->v); - REMAP_EDGE(l_iter->e); - REMAP_FACE(l_iter->f); - - REMAP_LOOP(l_iter->radial_next); - REMAP_LOOP(l_iter->radial_prev); - REMAP_LOOP(l_iter->next); - REMAP_LOOP(l_iter->prev); - } while ((l_iter = l_iter->next) != l_first); - } - } - } - - LISTBASE_FOREACH (BMEditSelection *, ese, &bm->selected) { - switch (ese->htype) { - case BM_VERT: - if (remap & BM_VERT) { - ese->ele = (BMElem *)MAP_VERT(ese->ele); - } - break; - case BM_EDGE: - if (remap & BM_EDGE) { - ese->ele = (BMElem *)MAP_EDGE(ese->ele); - } - break; - case BM_FACE: - if (remap & BM_FACE) { - ese->ele = (BMElem *)MAP_FACE(ese->ele); - } - break; - } - } - - if (bm->act_face) { - REMAP_FACE(bm->act_face); - } - -#undef MAP_VERT -#undef MAP_EDGE -#undef MAP_LOOP -#undef MAP_EDGE - -#undef REMAP_VERT -#undef REMAP_EDGE -#undef REMAP_LOOP -#undef REMAP_EDGE - - /* Cleanup, re-use local tables if the current mesh had tables allocated. - * could use irrespective but it may use more memory than the caller wants - * (and not be needed). */ - if (remap & BM_VERT) { - if (bm->vtable) { - SWAP(BMVert **, vtable_dst, bm->vtable); - bm->vtable_tot = bm->totvert; - bm->elem_table_dirty &= ~BM_VERT; - } - MEM_freeN(vtable_dst); - BLI_mempool_destroy(bm->vpool); - bm->vpool = vpool_dst; - } - - if (remap & BM_EDGE) { - if (bm->etable) { - SWAP(BMEdge **, etable_dst, bm->etable); - bm->etable_tot = bm->totedge; - bm->elem_table_dirty &= ~BM_EDGE; - } - MEM_freeN(etable_dst); - BLI_mempool_destroy(bm->epool); - bm->epool = epool_dst; - } - - if (remap & BM_LOOP) { - /* no loop table */ - MEM_freeN(ltable_dst); - BLI_mempool_destroy(bm->lpool); - bm->lpool = lpool_dst; - } - - if (remap & BM_FACE) { - if (bm->ftable) { - SWAP(BMFace **, ftable_dst, bm->ftable); - bm->ftable_tot = bm->totface; - bm->elem_table_dirty &= ~BM_FACE; - } - MEM_freeN(ftable_dst); - BLI_mempool_destroy(bm->fpool); - bm->fpool = fpool_dst; - } -} - -void BM_mesh_toolflags_set(BMesh *bm, bool use_toolflags) -{ - if (bm->use_toolflags == use_toolflags) { - return; - } - - const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_BM(bm); - - BLI_mempool *vpool_dst = NULL; - BLI_mempool *epool_dst = NULL; - BLI_mempool *fpool_dst = NULL; - - bm_mempool_init_ex(&allocsize, use_toolflags, &vpool_dst, &epool_dst, NULL, &fpool_dst); - - if (use_toolflags == false) { - BLI_mempool_destroy(bm->vtoolflagpool); - BLI_mempool_destroy(bm->etoolflagpool); - BLI_mempool_destroy(bm->ftoolflagpool); - - bm->vtoolflagpool = NULL; - bm->etoolflagpool = NULL; - bm->ftoolflagpool = NULL; - } - - BM_mesh_rebuild(bm, - &((struct BMeshCreateParams){ - .use_toolflags = use_toolflags, - }), - vpool_dst, - epool_dst, - NULL, - fpool_dst); - - bm->use_toolflags = use_toolflags; -} - -/* -------------------------------------------------------------------- */ -/** \name BMesh Coordinate Access - * \{ */ - -void BM_mesh_vert_coords_get(BMesh *bm, float (*vert_coords)[3]) -{ - BMIter iter; - BMVert *v; - int i; - BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) { - copy_v3_v3(vert_coords[i], v->co); - } -} - -float (*BM_mesh_vert_coords_alloc(BMesh *bm, int *r_vert_len))[3] -{ - float(*vert_coords)[3] = MEM_mallocN(bm->totvert * sizeof(*vert_coords), __func__); - BM_mesh_vert_coords_get(bm, vert_coords); - *r_vert_len = bm->totvert; - return vert_coords; -} - -void BM_mesh_vert_coords_apply(BMesh *bm, const float (*vert_coords)[3]) -{ - BMIter iter; - BMVert *v; - int i; - BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) { - copy_v3_v3(v->co, vert_coords[i]); - } -} - -void BM_mesh_vert_coords_apply_with_mat4(BMesh *bm, - const float (*vert_coords)[3], - const float mat[4][4]) -{ - BMIter iter; - BMVert *v; - int i; - BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) { - mul_v3_m4v3(v->co, mat, vert_coords[i]); - } -} - -/** \} */ diff --git a/source/blender/bmesh/intern/bmesh_mesh.cc b/source/blender/bmesh/intern/bmesh_mesh.cc new file mode 100644 index 00000000000..c16d874e3ec --- /dev/null +++ b/source/blender/bmesh/intern/bmesh_mesh.cc @@ -0,0 +1,1350 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup bmesh + * + * BM mesh level functions. + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_listBase.h" +#include "DNA_scene_types.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_utildefines.h" + +#include "BKE_customdata.h" +#include "BKE_mesh.h" + +#include "bmesh.h" + +const BMAllocTemplate bm_mesh_allocsize_default = {512, 1024, 2048, 512}; +const BMAllocTemplate bm_mesh_chunksize_default = {512, 1024, 2048, 512}; + +static void bm_mempool_init_ex(const BMAllocTemplate *allocsize, + const bool use_toolflags, + BLI_mempool **r_vpool, + BLI_mempool **r_epool, + BLI_mempool **r_lpool, + BLI_mempool **r_fpool) +{ + size_t vert_size, edge_size, loop_size, face_size; + + if (use_toolflags == true) { + vert_size = sizeof(BMVert_OFlag); + edge_size = sizeof(BMEdge_OFlag); + loop_size = sizeof(BMLoop); + face_size = sizeof(BMFace_OFlag); + } + else { + vert_size = sizeof(BMVert); + edge_size = sizeof(BMEdge); + loop_size = sizeof(BMLoop); + face_size = sizeof(BMFace); + } + + if (r_vpool) { + *r_vpool = BLI_mempool_create( + vert_size, allocsize->totvert, bm_mesh_chunksize_default.totvert, BLI_MEMPOOL_ALLOW_ITER); + } + if (r_epool) { + *r_epool = BLI_mempool_create( + edge_size, allocsize->totedge, bm_mesh_chunksize_default.totedge, BLI_MEMPOOL_ALLOW_ITER); + } + if (r_lpool) { + *r_lpool = BLI_mempool_create( + loop_size, allocsize->totloop, bm_mesh_chunksize_default.totloop, BLI_MEMPOOL_NOP); + } + if (r_fpool) { + *r_fpool = BLI_mempool_create( + face_size, allocsize->totface, bm_mesh_chunksize_default.totface, BLI_MEMPOOL_ALLOW_ITER); + } +} + +static void bm_mempool_init(BMesh *bm, const BMAllocTemplate *allocsize, const bool use_toolflags) +{ + bm_mempool_init_ex(allocsize, use_toolflags, &bm->vpool, &bm->epool, &bm->lpool, &bm->fpool); + +#ifdef USE_BMESH_HOLES + bm->looplistpool = BLI_mempool_create(sizeof(BMLoopList), 512, 512, BLI_MEMPOOL_NOP); +#endif +} + +void BM_mesh_elem_toolflags_ensure(BMesh *bm) +{ + BLI_assert(bm->use_toolflags); + + if (bm->vtoolflagpool && bm->etoolflagpool && bm->ftoolflagpool) { + return; + } + + bm->vtoolflagpool = BLI_mempool_create(sizeof(BMFlagLayer), bm->totvert, 512, BLI_MEMPOOL_NOP); + bm->etoolflagpool = BLI_mempool_create(sizeof(BMFlagLayer), bm->totedge, 512, BLI_MEMPOOL_NOP); + bm->ftoolflagpool = BLI_mempool_create(sizeof(BMFlagLayer), bm->totface, 512, BLI_MEMPOOL_NOP); + + BMIter iter; + BMVert_OFlag *v_olfag; + BLI_mempool *toolflagpool = bm->vtoolflagpool; + BM_ITER_MESH (v_olfag, &iter, bm, BM_VERTS_OF_MESH) { + v_olfag->oflags = (BMFlagLayer *)BLI_mempool_calloc(toolflagpool); + } + + BMEdge_OFlag *e_olfag; + toolflagpool = bm->etoolflagpool; + BM_ITER_MESH (e_olfag, &iter, bm, BM_EDGES_OF_MESH) { + e_olfag->oflags = (BMFlagLayer *)BLI_mempool_calloc(toolflagpool); + } + + BMFace_OFlag *f_olfag; + toolflagpool = bm->ftoolflagpool; + BM_ITER_MESH (f_olfag, &iter, bm, BM_FACES_OF_MESH) { + f_olfag->oflags = (BMFlagLayer *)BLI_mempool_calloc(toolflagpool); + } + + bm->totflags = 1; +} + +void BM_mesh_elem_toolflags_clear(BMesh *bm) +{ + if (bm->vtoolflagpool) { + BLI_mempool_destroy(bm->vtoolflagpool); + bm->vtoolflagpool = nullptr; + } + if (bm->etoolflagpool) { + BLI_mempool_destroy(bm->etoolflagpool); + bm->etoolflagpool = nullptr; + } + if (bm->ftoolflagpool) { + BLI_mempool_destroy(bm->ftoolflagpool); + bm->ftoolflagpool = nullptr; + } +} + +BMesh *BM_mesh_create(const BMAllocTemplate *allocsize, const struct BMeshCreateParams *params) +{ + /* allocate the structure */ + BMesh *bm = (BMesh *)MEM_callocN(sizeof(BMesh), __func__); + + /* allocate the memory pools for the mesh elements */ + bm_mempool_init(bm, allocsize, params->use_toolflags); + + /* allocate one flag pool that we don't get rid of. */ + bm->use_toolflags = params->use_toolflags; + bm->toolflag_index = 0; + bm->totflags = 0; + + CustomData_reset(&bm->vdata); + CustomData_reset(&bm->edata); + CustomData_reset(&bm->ldata); + CustomData_reset(&bm->pdata); + + return bm; +} + +void BM_mesh_data_free(BMesh *bm) +{ + BMVert *v; + BMEdge *e; + BMLoop *l; + BMFace *f; + + BMIter iter; + BMIter itersub; + + const bool is_ldata_free = CustomData_bmesh_has_free(&bm->ldata); + const bool is_pdata_free = CustomData_bmesh_has_free(&bm->pdata); + + /* Check if we have to call free, if not we can avoid a lot of looping */ + if (CustomData_bmesh_has_free(&(bm->vdata))) { + BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { + CustomData_bmesh_free_block(&(bm->vdata), &(v->head.data)); + } + } + if (CustomData_bmesh_has_free(&(bm->edata))) { + BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { + CustomData_bmesh_free_block(&(bm->edata), &(e->head.data)); + } + } + + if (is_ldata_free || is_pdata_free) { + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (is_pdata_free) { + CustomData_bmesh_free_block(&(bm->pdata), &(f->head.data)); + } + if (is_ldata_free) { + BM_ITER_ELEM (l, &itersub, f, BM_LOOPS_OF_FACE) { + CustomData_bmesh_free_block(&(bm->ldata), &(l->head.data)); + } + } + } + } + + /* Free custom data pools, This should probably go in CustomData_free? */ + if (bm->vdata.totlayer) { + BLI_mempool_destroy(bm->vdata.pool); + } + if (bm->edata.totlayer) { + BLI_mempool_destroy(bm->edata.pool); + } + if (bm->ldata.totlayer) { + BLI_mempool_destroy(bm->ldata.pool); + } + if (bm->pdata.totlayer) { + BLI_mempool_destroy(bm->pdata.pool); + } + + /* free custom data */ + CustomData_free(&bm->vdata, 0); + CustomData_free(&bm->edata, 0); + CustomData_free(&bm->ldata, 0); + CustomData_free(&bm->pdata, 0); + + /* destroy element pools */ + BLI_mempool_destroy(bm->vpool); + BLI_mempool_destroy(bm->epool); + BLI_mempool_destroy(bm->lpool); + BLI_mempool_destroy(bm->fpool); + + if (bm->vtable) { + MEM_freeN(bm->vtable); + } + if (bm->etable) { + MEM_freeN(bm->etable); + } + if (bm->ftable) { + MEM_freeN(bm->ftable); + } + + /* destroy flag pool */ + BM_mesh_elem_toolflags_clear(bm); + +#ifdef USE_BMESH_HOLES + BLI_mempool_destroy(bm->looplistpool); +#endif + + BLI_freelistN(&bm->selected); + + if (bm->lnor_spacearr) { + BKE_lnor_spacearr_free(bm->lnor_spacearr); + MEM_freeN(bm->lnor_spacearr); + } + + BMO_error_clear(bm); +} + +void BM_mesh_clear(BMesh *bm) +{ + const bool use_toolflags = bm->use_toolflags; + + /* free old mesh */ + BM_mesh_data_free(bm); + memset(bm, 0, sizeof(BMesh)); + + /* allocate the memory pools for the mesh elements */ + bm_mempool_init(bm, &bm_mesh_allocsize_default, use_toolflags); + + bm->use_toolflags = use_toolflags; + bm->toolflag_index = 0; + bm->totflags = 0; + + CustomData_reset(&bm->vdata); + CustomData_reset(&bm->edata); + CustomData_reset(&bm->ldata); + CustomData_reset(&bm->pdata); +} + +void BM_mesh_free(BMesh *bm) +{ + BM_mesh_data_free(bm); + + if (bm->py_handle) { + /* keep this out of 'BM_mesh_data_free' because we want python + * to be able to clear the mesh and maintain access. */ + bpy_bm_generic_invalidate((BPy_BMGeneric *)bm->py_handle); + bm->py_handle = nullptr; + } + + MEM_freeN(bm); +} + +void bmesh_edit_begin(BMesh *UNUSED(bm), BMOpTypeFlag UNUSED(type_flag)) +{ + /* Most operators seem to be using BMO_OPTYPE_FLAG_UNTAN_MULTIRES to change the MDisps to + * absolute space during mesh edits. With this enabled, changes to the topology + * (loop cuts, edge subdivides, etc) are not reflected in the higher levels of + * the mesh at all, which doesn't seem right. Turning off completely for now, + * until this is shown to be better for certain types of mesh edits. */ +#ifdef BMOP_UNTAN_MULTIRES_ENABLED + /* switch multires data out of tangent space */ + if ((type_flag & BMO_OPTYPE_FLAG_UNTAN_MULTIRES) && + CustomData_has_layer(&bm->ldata, CD_MDISPS)) { + bmesh_mdisps_space_set(bm, MULTIRES_SPACE_TANGENT, MULTIRES_SPACE_ABSOLUTE); + + /* ensure correct normals, if possible */ + bmesh_rationalize_normals(bm, 0); + BM_mesh_normals_update(bm); + } +#endif +} + +void bmesh_edit_end(BMesh *bm, BMOpTypeFlag type_flag) +{ + ListBase select_history; + + /* BMO_OPTYPE_FLAG_UNTAN_MULTIRES disabled for now, see comment above in bmesh_edit_begin. */ +#ifdef BMOP_UNTAN_MULTIRES_ENABLED + /* switch multires data into tangent space */ + if ((flag & BMO_OPTYPE_FLAG_UNTAN_MULTIRES) && CustomData_has_layer(&bm->ldata, CD_MDISPS)) { + /* set normals to their previous winding */ + bmesh_rationalize_normals(bm, 1); + bmesh_mdisps_space_set(bm, MULTIRES_SPACE_ABSOLUTE, MULTIRES_SPACE_TANGENT); + } + else if (flag & BMO_OP_FLAG_RATIONALIZE_NORMALS) { + bmesh_rationalize_normals(bm, 1); + } +#endif + + /* compute normals, clear temp flags and flush selections */ + if (type_flag & BMO_OPTYPE_FLAG_NORMALS_CALC) { + bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL; + BM_mesh_normals_update(bm); + } + + if ((type_flag & BMO_OPTYPE_FLAG_SELECT_VALIDATE) == 0) { + select_history = bm->selected; + BLI_listbase_clear(&bm->selected); + } + + if (type_flag & BMO_OPTYPE_FLAG_SELECT_FLUSH) { + BM_mesh_select_mode_flush(bm); + } + + if ((type_flag & BMO_OPTYPE_FLAG_SELECT_VALIDATE) == 0) { + bm->selected = select_history; + } + if (type_flag & BMO_OPTYPE_FLAG_INVALIDATE_CLNOR_ALL) { + bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL; + } +} + +void BM_mesh_elem_index_ensure_ex(BMesh *bm, const char htype, int elem_offset[4]) +{ + +#ifdef DEBUG + BM_ELEM_INDEX_VALIDATE(bm, "Should Never Fail!", __func__); +#endif + + if (elem_offset == nullptr) { + /* Simple case. */ + const char htype_needed = bm->elem_index_dirty & htype; + if (htype_needed == 0) { + goto finally; + } + } + + if (htype & BM_VERT) { + if ((bm->elem_index_dirty & BM_VERT) || (elem_offset && elem_offset[0])) { + BMIter iter; + BMElem *ele; + + int index = elem_offset ? elem_offset[0] : 0; + BM_ITER_MESH (ele, &iter, bm, BM_VERTS_OF_MESH) { + BM_elem_index_set(ele, index++); /* set_ok */ + } + BLI_assert(elem_offset || index == bm->totvert); + } + else { + // printf("%s: skipping vert index calc!\n", __func__); + } + } + + if (htype & BM_EDGE) { + if ((bm->elem_index_dirty & BM_EDGE) || (elem_offset && elem_offset[1])) { + BMIter iter; + BMElem *ele; + + int index = elem_offset ? elem_offset[1] : 0; + BM_ITER_MESH (ele, &iter, bm, BM_EDGES_OF_MESH) { + BM_elem_index_set(ele, index++); /* set_ok */ + } + BLI_assert(elem_offset || index == bm->totedge); + } + else { + // printf("%s: skipping edge index calc!\n", __func__); + } + } + + if (htype & (BM_FACE | BM_LOOP)) { + if ((bm->elem_index_dirty & (BM_FACE | BM_LOOP)) || + (elem_offset && (elem_offset[2] || elem_offset[3]))) { + BMIter iter; + BMElem *ele; + + const bool update_face = (htype & BM_FACE) && (bm->elem_index_dirty & BM_FACE); + const bool update_loop = (htype & BM_LOOP) && (bm->elem_index_dirty & BM_LOOP); + + int index_loop = elem_offset ? elem_offset[2] : 0; + int index = elem_offset ? elem_offset[3] : 0; + + BM_ITER_MESH (ele, &iter, bm, BM_FACES_OF_MESH) { + if (update_face) { + BM_elem_index_set(ele, index++); /* set_ok */ + } + + if (update_loop) { + BMLoop *l_iter, *l_first; + + l_iter = l_first = BM_FACE_FIRST_LOOP((BMFace *)ele); + do { + BM_elem_index_set(l_iter, index_loop++); /* set_ok */ + } while ((l_iter = l_iter->next) != l_first); + } + } + + BLI_assert(elem_offset || !update_face || index == bm->totface); + if (update_loop) { + BLI_assert(elem_offset || !update_loop || index_loop == bm->totloop); + } + } + else { + // printf("%s: skipping face/loop index calc!\n", __func__); + } + } + +finally: + bm->elem_index_dirty &= ~htype; + if (elem_offset) { + if (htype & BM_VERT) { + elem_offset[0] += bm->totvert; + if (elem_offset[0] != bm->totvert) { + bm->elem_index_dirty |= BM_VERT; + } + } + if (htype & BM_EDGE) { + elem_offset[1] += bm->totedge; + if (elem_offset[1] != bm->totedge) { + bm->elem_index_dirty |= BM_EDGE; + } + } + if (htype & BM_LOOP) { + elem_offset[2] += bm->totloop; + if (elem_offset[2] != bm->totloop) { + bm->elem_index_dirty |= BM_LOOP; + } + } + if (htype & BM_FACE) { + elem_offset[3] += bm->totface; + if (elem_offset[3] != bm->totface) { + bm->elem_index_dirty |= BM_FACE; + } + } + } +} + +void BM_mesh_elem_index_ensure(BMesh *bm, const char htype) +{ + BM_mesh_elem_index_ensure_ex(bm, htype, nullptr); +} + +void BM_mesh_elem_index_validate( + BMesh *bm, const char *location, const char *func, const char *msg_a, const char *msg_b) +{ + const char iter_types[3] = {BM_VERTS_OF_MESH, BM_EDGES_OF_MESH, BM_FACES_OF_MESH}; + + const char flag_types[3] = {BM_VERT, BM_EDGE, BM_FACE}; + const char *type_names[3] = {"vert", "edge", "face"}; + + BMIter iter; + BMElem *ele; + int i; + bool is_any_error = false; + + for (i = 0; i < 3; i++) { + const bool is_dirty = (flag_types[i] & bm->elem_index_dirty) != 0; + int index = 0; + bool is_error = false; + int err_val = 0; + int err_idx = 0; + + BM_ITER_MESH (ele, &iter, bm, iter_types[i]) { + if (!is_dirty) { + if (BM_elem_index_get(ele) != index) { + err_val = BM_elem_index_get(ele); + err_idx = index; + is_error = true; + break; + } + } + index++; + } + + if ((is_error == true) && (is_dirty == false)) { + is_any_error = true; + fprintf(stderr, + "Invalid Index: at %s, %s, %s[%d] invalid index %d, '%s', '%s'\n", + location, + func, + type_names[i], + err_idx, + err_val, + msg_a, + msg_b); + } + else if ((is_error == false) && (is_dirty == true)) { + +#if 0 /* mostly annoying */ + + /* dirty may have been incorrectly set */ + fprintf(stderr, + "Invalid Dirty: at %s, %s (%s), dirty flag was set but all index values are " + "correct, '%s', '%s'\n", + location, + func, + type_names[i], + msg_a, + msg_b); +#endif + } + } + +#if 0 /* mostly annoying, even in debug mode */ +# ifdef DEBUG + if (is_any_error == 0) { + fprintf(stderr, "Valid Index Success: at %s, %s, '%s', '%s'\n", location, func, msg_a, msg_b); + } +# endif +#endif + (void)is_any_error; /* shut up the compiler */ +} + +/* debug check only - no need to optimize */ +#ifndef NDEBUG +bool BM_mesh_elem_table_check(BMesh *bm) +{ + BMIter iter; + BMElem *ele; + int i; + + if (bm->vtable && ((bm->elem_table_dirty & BM_VERT) == 0)) { + BM_ITER_MESH_INDEX (ele, &iter, bm, BM_VERTS_OF_MESH, i) { + if (ele != (BMElem *)bm->vtable[i]) { + return false; + } + } + } + + if (bm->etable && ((bm->elem_table_dirty & BM_EDGE) == 0)) { + BM_ITER_MESH_INDEX (ele, &iter, bm, BM_EDGES_OF_MESH, i) { + if (ele != (BMElem *)bm->etable[i]) { + return false; + } + } + } + + if (bm->ftable && ((bm->elem_table_dirty & BM_FACE) == 0)) { + BM_ITER_MESH_INDEX (ele, &iter, bm, BM_FACES_OF_MESH, i) { + if (ele != (BMElem *)bm->ftable[i]) { + return false; + } + } + } + + return true; +} +#endif + +void BM_mesh_elem_table_ensure(BMesh *bm, const char htype) +{ + /* assume if the array is non-null then its valid and no need to recalc */ + const char htype_needed = + (((bm->vtable && ((bm->elem_table_dirty & BM_VERT) == 0)) ? 0 : BM_VERT) | + ((bm->etable && ((bm->elem_table_dirty & BM_EDGE) == 0)) ? 0 : BM_EDGE) | + ((bm->ftable && ((bm->elem_table_dirty & BM_FACE) == 0)) ? 0 : BM_FACE)) & + htype; + + BLI_assert((htype & ~BM_ALL_NOLOOP) == 0); + + /* in debug mode double check we didn't need to recalculate */ + BLI_assert(BM_mesh_elem_table_check(bm) == true); + + if (htype_needed == 0) { + goto finally; + } + + if (htype_needed & BM_VERT) { + if (bm->vtable && bm->totvert <= bm->vtable_tot && bm->totvert * 2 >= bm->vtable_tot) { + /* pass (re-use the array) */ + } + else { + if (bm->vtable) { + MEM_freeN(bm->vtable); + } + bm->vtable = (BMVert **)MEM_mallocN(sizeof(void **) * bm->totvert, "bm->vtable"); + bm->vtable_tot = bm->totvert; + } + BM_iter_as_array(bm, BM_VERTS_OF_MESH, nullptr, (void **)bm->vtable, bm->totvert); + } + if (htype_needed & BM_EDGE) { + if (bm->etable && bm->totedge <= bm->etable_tot && bm->totedge * 2 >= bm->etable_tot) { + /* pass (re-use the array) */ + } + else { + if (bm->etable) { + MEM_freeN(bm->etable); + } + bm->etable = (BMEdge **)MEM_mallocN(sizeof(void **) * bm->totedge, "bm->etable"); + bm->etable_tot = bm->totedge; + } + BM_iter_as_array(bm, BM_EDGES_OF_MESH, nullptr, (void **)bm->etable, bm->totedge); + } + if (htype_needed & BM_FACE) { + if (bm->ftable && bm->totface <= bm->ftable_tot && bm->totface * 2 >= bm->ftable_tot) { + /* pass (re-use the array) */ + } + else { + if (bm->ftable) { + MEM_freeN(bm->ftable); + } + bm->ftable = (BMFace **)MEM_mallocN(sizeof(void **) * bm->totface, "bm->ftable"); + bm->ftable_tot = bm->totface; + } + BM_iter_as_array(bm, BM_FACES_OF_MESH, nullptr, (void **)bm->ftable, bm->totface); + } + +finally: + /* Only clear dirty flags when all the pointers and data are actually valid. + * This prevents possible threading issues when dirty flag check failed but + * data wasn't ready still. + */ + bm->elem_table_dirty &= ~htype_needed; +} + +void BM_mesh_elem_table_init(BMesh *bm, const char htype) +{ + BLI_assert((htype & ~BM_ALL_NOLOOP) == 0); + + /* force recalc */ + BM_mesh_elem_table_free(bm, BM_ALL_NOLOOP); + BM_mesh_elem_table_ensure(bm, htype); +} + +void BM_mesh_elem_table_free(BMesh *bm, const char htype) +{ + if (htype & BM_VERT) { + MEM_SAFE_FREE(bm->vtable); + } + + if (htype & BM_EDGE) { + MEM_SAFE_FREE(bm->etable); + } + + if (htype & BM_FACE) { + MEM_SAFE_FREE(bm->ftable); + } +} + +BMVert *BM_vert_at_index_find(BMesh *bm, const int index) +{ + return (BMVert *)BLI_mempool_findelem(bm->vpool, index); +} + +BMEdge *BM_edge_at_index_find(BMesh *bm, const int index) +{ + return (BMEdge *)BLI_mempool_findelem(bm->epool, index); +} + +BMFace *BM_face_at_index_find(BMesh *bm, const int index) +{ + return (BMFace *)BLI_mempool_findelem(bm->fpool, index); +} + +BMLoop *BM_loop_at_index_find(BMesh *bm, const int index) +{ + BMIter iter; + BMFace *f; + int i = index; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (i < f->len) { + BMLoop *l_first, *l_iter; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (i == 0) { + return l_iter; + } + i -= 1; + } while ((l_iter = l_iter->next) != l_first); + } + i -= f->len; + } + return nullptr; +} + +BMVert *BM_vert_at_index_find_or_table(BMesh *bm, const int index) +{ + if ((bm->elem_table_dirty & BM_VERT) == 0) { + return (index < bm->totvert) ? bm->vtable[index] : nullptr; + } + return BM_vert_at_index_find(bm, index); +} + +BMEdge *BM_edge_at_index_find_or_table(BMesh *bm, const int index) +{ + if ((bm->elem_table_dirty & BM_EDGE) == 0) { + return (index < bm->totedge) ? bm->etable[index] : nullptr; + } + return BM_edge_at_index_find(bm, index); +} + +BMFace *BM_face_at_index_find_or_table(BMesh *bm, const int index) +{ + if ((bm->elem_table_dirty & BM_FACE) == 0) { + return (index < bm->totface) ? bm->ftable[index] : nullptr; + } + return BM_face_at_index_find(bm, index); +} + +int BM_mesh_elem_count(BMesh *bm, const char htype) +{ + BLI_assert((htype & ~BM_ALL_NOLOOP) == 0); + + switch (htype) { + case BM_VERT: + return bm->totvert; + case BM_EDGE: + return bm->totedge; + case BM_FACE: + return bm->totface; + default: { + BLI_assert(0); + return 0; + } + } +} + +void BM_mesh_remap(BMesh *bm, const uint *vert_idx, const uint *edge_idx, const uint *face_idx) +{ + /* Mapping old to new pointers. */ + GHash *vptr_map = nullptr, *eptr_map = nullptr, *fptr_map = nullptr; + BMIter iter, iterl; + BMVert *ve; + BMEdge *ed; + BMFace *fa; + BMLoop *lo; + + if (!(vert_idx || edge_idx || face_idx)) { + return; + } + + BM_mesh_elem_table_ensure( + bm, (vert_idx ? BM_VERT : 0) | (edge_idx ? BM_EDGE : 0) | (face_idx ? BM_FACE : 0)); + + /* Remap Verts */ + if (vert_idx) { + BMVert **verts_pool, *verts_copy, **vep; + int i, totvert = bm->totvert; + const uint *new_idx; + /* Special case: Python uses custom data layers to hold PyObject references. + * These have to be kept in place, else the PyObjects we point to, won't point back to us. */ + const int cd_vert_pyptr = CustomData_get_offset(&bm->vdata, CD_BM_ELEM_PYPTR); + + /* Init the old-to-new vert pointers mapping */ + vptr_map = BLI_ghash_ptr_new_ex("BM_mesh_remap vert pointers mapping", bm->totvert); + + /* Make a copy of all vertices. */ + verts_pool = bm->vtable; + verts_copy = (BMVert *)MEM_mallocN(sizeof(BMVert) * totvert, "BM_mesh_remap verts copy"); + void **pyptrs = (cd_vert_pyptr != -1) ? + (void **)MEM_mallocN(sizeof(void *) * totvert, __func__) : + nullptr; + for (i = totvert, ve = verts_copy + totvert - 1, vep = verts_pool + totvert - 1; i--; + ve--, vep--) { + *ve = **vep; + // printf("*vep: %p, verts_pool[%d]: %p\n", *vep, i, verts_pool[i]); + if (cd_vert_pyptr != -1) { + void **pyptr = (void **)BM_ELEM_CD_GET_VOID_P(((BMElem *)ve), cd_vert_pyptr); + pyptrs[i] = *pyptr; + } + } + + /* Copy back verts to their new place, and update old2new pointers mapping. */ + new_idx = vert_idx + totvert - 1; + ve = verts_copy + totvert - 1; + vep = verts_pool + totvert - 1; /* old, org pointer */ + for (i = totvert; i--; new_idx--, ve--, vep--) { + BMVert *new_vep = verts_pool[*new_idx]; + *new_vep = *ve; +#if 0 + printf( + "mapping vert from %d to %d (%p/%p to %p)\n", i, *new_idx, *vep, verts_pool[i], new_vep); +#endif + BLI_ghash_insert(vptr_map, *vep, new_vep); + if (cd_vert_pyptr != -1) { + void **pyptr = (void **)BM_ELEM_CD_GET_VOID_P(((BMElem *)new_vep), cd_vert_pyptr); + *pyptr = pyptrs[*new_idx]; + } + } + bm->elem_index_dirty |= BM_VERT; + bm->elem_table_dirty |= BM_VERT; + + MEM_freeN(verts_copy); + if (pyptrs) { + MEM_freeN(pyptrs); + } + } + + /* Remap Edges */ + if (edge_idx) { + BMEdge **edges_pool, *edges_copy, **edp; + int i, totedge = bm->totedge; + const uint *new_idx; + /* Special case: Python uses custom data layers to hold PyObject references. + * These have to be kept in place, else the PyObjects we point to, won't point back to us. */ + const int cd_edge_pyptr = CustomData_get_offset(&bm->edata, CD_BM_ELEM_PYPTR); + + /* Init the old-to-new vert pointers mapping */ + eptr_map = BLI_ghash_ptr_new_ex("BM_mesh_remap edge pointers mapping", bm->totedge); + + /* Make a copy of all vertices. */ + edges_pool = bm->etable; + edges_copy = (BMEdge *)MEM_mallocN(sizeof(BMEdge) * totedge, "BM_mesh_remap edges copy"); + void **pyptrs = (cd_edge_pyptr != -1) ? + (void **)MEM_mallocN(sizeof(void *) * totedge, __func__) : + nullptr; + for (i = totedge, ed = edges_copy + totedge - 1, edp = edges_pool + totedge - 1; i--; + ed--, edp--) { + *ed = **edp; + if (cd_edge_pyptr != -1) { + void **pyptr = (void **)BM_ELEM_CD_GET_VOID_P(((BMElem *)ed), cd_edge_pyptr); + pyptrs[i] = *pyptr; + } + } + + /* Copy back verts to their new place, and update old2new pointers mapping. */ + new_idx = edge_idx + totedge - 1; + ed = edges_copy + totedge - 1; + edp = edges_pool + totedge - 1; /* old, org pointer */ + for (i = totedge; i--; new_idx--, ed--, edp--) { + BMEdge *new_edp = edges_pool[*new_idx]; + *new_edp = *ed; + BLI_ghash_insert(eptr_map, *edp, new_edp); +#if 0 + printf( + "mapping edge from %d to %d (%p/%p to %p)\n", i, *new_idx, *edp, edges_pool[i], new_edp); +#endif + if (cd_edge_pyptr != -1) { + void **pyptr = (void **)BM_ELEM_CD_GET_VOID_P(((BMElem *)new_edp), cd_edge_pyptr); + *pyptr = pyptrs[*new_idx]; + } + } + bm->elem_index_dirty |= BM_EDGE; + bm->elem_table_dirty |= BM_EDGE; + + MEM_freeN(edges_copy); + if (pyptrs) { + MEM_freeN(pyptrs); + } + } + + /* Remap Faces */ + if (face_idx) { + BMFace **faces_pool, *faces_copy, **fap; + int i, totface = bm->totface; + const uint *new_idx; + /* Special case: Python uses custom data layers to hold PyObject references. + * These have to be kept in place, else the PyObjects we point to, won't point back to us. */ + const int cd_poly_pyptr = CustomData_get_offset(&bm->pdata, CD_BM_ELEM_PYPTR); + + /* Init the old-to-new vert pointers mapping */ + fptr_map = BLI_ghash_ptr_new_ex("BM_mesh_remap face pointers mapping", bm->totface); + + /* Make a copy of all vertices. */ + faces_pool = bm->ftable; + faces_copy = (BMFace *)MEM_mallocN(sizeof(BMFace) * totface, "BM_mesh_remap faces copy"); + void **pyptrs = (cd_poly_pyptr != -1) ? + (void **)MEM_mallocN(sizeof(void *) * totface, __func__) : + nullptr; + for (i = totface, fa = faces_copy + totface - 1, fap = faces_pool + totface - 1; i--; + fa--, fap--) { + *fa = **fap; + if (cd_poly_pyptr != -1) { + void **pyptr = (void **)BM_ELEM_CD_GET_VOID_P(((BMElem *)fa), cd_poly_pyptr); + pyptrs[i] = *pyptr; + } + } + + /* Copy back verts to their new place, and update old2new pointers mapping. */ + new_idx = face_idx + totface - 1; + fa = faces_copy + totface - 1; + fap = faces_pool + totface - 1; /* old, org pointer */ + for (i = totface; i--; new_idx--, fa--, fap--) { + BMFace *new_fap = faces_pool[*new_idx]; + *new_fap = *fa; + BLI_ghash_insert(fptr_map, *fap, new_fap); + if (cd_poly_pyptr != -1) { + void **pyptr = (void **)BM_ELEM_CD_GET_VOID_P(((BMElem *)new_fap), cd_poly_pyptr); + *pyptr = pyptrs[*new_idx]; + } + } + + bm->elem_index_dirty |= BM_FACE | BM_LOOP; + bm->elem_table_dirty |= BM_FACE; + + MEM_freeN(faces_copy); + if (pyptrs) { + MEM_freeN(pyptrs); + } + } + + /* And now, fix all vertices/edges/faces/loops pointers! */ + /* Verts' pointers, only edge pointers... */ + if (eptr_map) { + BM_ITER_MESH (ve, &iter, bm, BM_VERTS_OF_MESH) { + // printf("Vert e: %p -> %p\n", ve->e, BLI_ghash_lookup(eptr_map, ve->e)); + if (ve->e) { + ve->e = (BMEdge *)BLI_ghash_lookup(eptr_map, ve->e); + BLI_assert(ve->e); + } + } + } + + /* Edges' pointers, only vert pointers (as we don't mess with loops!), + * and - ack! - edge pointers, + * as we have to handle disk-links. */ + if (vptr_map || eptr_map) { + BM_ITER_MESH (ed, &iter, bm, BM_EDGES_OF_MESH) { + if (vptr_map) { +#if 0 + printf("Edge v1: %p -> %p\n", ed->v1, BLI_ghash_lookup(vptr_map, ed->v1)); + printf("Edge v2: %p -> %p\n", ed->v2, BLI_ghash_lookup(vptr_map, ed->v2)); +#endif + ed->v1 = (BMVert *)BLI_ghash_lookup(vptr_map, ed->v1); + ed->v2 = (BMVert *)BLI_ghash_lookup(vptr_map, ed->v2); + BLI_assert(ed->v1); + BLI_assert(ed->v2); + } + if (eptr_map) { +#if 0 + printf("Edge v1_disk_link prev: %p -> %p\n", + ed->v1_disk_link.prev, + BLI_ghash_lookup(eptr_map, ed->v1_disk_link.prev)); + printf("Edge v1_disk_link next: %p -> %p\n", + ed->v1_disk_link.next, + BLI_ghash_lookup(eptr_map, ed->v1_disk_link.next)); + printf("Edge v2_disk_link prev: %p -> %p\n", + ed->v2_disk_link.prev, + BLI_ghash_lookup(eptr_map, ed->v2_disk_link.prev)); + printf("Edge v2_disk_link next: %p -> %p\n", + ed->v2_disk_link.next, + BLI_ghash_lookup(eptr_map, ed->v2_disk_link.next)); +#endif + ed->v1_disk_link.prev = (BMEdge *)BLI_ghash_lookup(eptr_map, ed->v1_disk_link.prev); + ed->v1_disk_link.next = (BMEdge *)BLI_ghash_lookup(eptr_map, ed->v1_disk_link.next); + ed->v2_disk_link.prev = (BMEdge *)BLI_ghash_lookup(eptr_map, ed->v2_disk_link.prev); + ed->v2_disk_link.next = (BMEdge *)BLI_ghash_lookup(eptr_map, ed->v2_disk_link.next); + BLI_assert(ed->v1_disk_link.prev); + BLI_assert(ed->v1_disk_link.next); + BLI_assert(ed->v2_disk_link.prev); + BLI_assert(ed->v2_disk_link.next); + } + } + } + + /* Faces' pointers (loops, in fact), always needed... */ + BM_ITER_MESH (fa, &iter, bm, BM_FACES_OF_MESH) { + BM_ITER_ELEM (lo, &iterl, fa, BM_LOOPS_OF_FACE) { + if (vptr_map) { + // printf("Loop v: %p -> %p\n", lo->v, BLI_ghash_lookup(vptr_map, lo->v)); + lo->v = (BMVert *)BLI_ghash_lookup(vptr_map, lo->v); + BLI_assert(lo->v); + } + if (eptr_map) { + // printf("Loop e: %p -> %p\n", lo->e, BLI_ghash_lookup(eptr_map, lo->e)); + lo->e = (BMEdge *)BLI_ghash_lookup(eptr_map, lo->e); + BLI_assert(lo->e); + } + if (fptr_map) { + // printf("Loop f: %p -> %p\n", lo->f, BLI_ghash_lookup(fptr_map, lo->f)); + lo->f = (BMFace *)BLI_ghash_lookup(fptr_map, lo->f); + BLI_assert(lo->f); + } + } + } + + /* Selection history */ + { + BMEditSelection *ese; + for (ese = (BMEditSelection *)bm->selected.first; ese; ese = ese->next) { + switch (ese->htype) { + case BM_VERT: + if (vptr_map) { + ese->ele = (BMElem *)BLI_ghash_lookup(vptr_map, ese->ele); + BLI_assert(ese->ele); + } + break; + case BM_EDGE: + if (eptr_map) { + ese->ele = (BMElem *)BLI_ghash_lookup(eptr_map, ese->ele); + BLI_assert(ese->ele); + } + break; + case BM_FACE: + if (fptr_map) { + ese->ele = (BMElem *)BLI_ghash_lookup(fptr_map, ese->ele); + BLI_assert(ese->ele); + } + break; + } + } + } + + if (fptr_map) { + if (bm->act_face) { + bm->act_face = (BMFace *)BLI_ghash_lookup(fptr_map, bm->act_face); + BLI_assert(bm->act_face); + } + } + + if (vptr_map) { + BLI_ghash_free(vptr_map, nullptr, nullptr); + } + if (eptr_map) { + BLI_ghash_free(eptr_map, nullptr, nullptr); + } + if (fptr_map) { + BLI_ghash_free(fptr_map, nullptr, nullptr); + } +} + +void BM_mesh_rebuild(BMesh *bm, + const struct BMeshCreateParams *params, + BLI_mempool *vpool_dst, + BLI_mempool *epool_dst, + BLI_mempool *lpool_dst, + BLI_mempool *fpool_dst) +{ + const char remap = (vpool_dst ? BM_VERT : 0) | (epool_dst ? BM_EDGE : 0) | + (lpool_dst ? BM_LOOP : 0) | (fpool_dst ? BM_FACE : 0); + + BMVert **vtable_dst = (remap & BM_VERT) ? + (BMVert **)MEM_mallocN(bm->totvert * sizeof(BMVert *), __func__) : + nullptr; + BMEdge **etable_dst = (remap & BM_EDGE) ? + (BMEdge **)MEM_mallocN(bm->totedge * sizeof(BMEdge *), __func__) : + nullptr; + BMLoop **ltable_dst = (remap & BM_LOOP) ? + (BMLoop **)MEM_mallocN(bm->totloop * sizeof(BMLoop *), __func__) : + nullptr; + BMFace **ftable_dst = (remap & BM_FACE) ? + (BMFace **)MEM_mallocN(bm->totface * sizeof(BMFace *), __func__) : + nullptr; + + const bool use_toolflags = params->use_toolflags; + + if (remap & BM_VERT) { + BMIter iter; + int index; + BMVert *v_src; + BM_ITER_MESH_INDEX (v_src, &iter, bm, BM_VERTS_OF_MESH, index) { + BMVert *v_dst = (BMVert *)BLI_mempool_alloc(vpool_dst); + memcpy(v_dst, v_src, sizeof(BMVert)); + if (use_toolflags) { + ((BMVert_OFlag *)v_dst)->oflags = bm->vtoolflagpool ? (BMFlagLayer *)BLI_mempool_calloc( + bm->vtoolflagpool) : + nullptr; + } + + vtable_dst[index] = v_dst; + BM_elem_index_set(v_src, index); /* set_ok */ + } + } + + if (remap & BM_EDGE) { + BMIter iter; + int index; + BMEdge *e_src; + BM_ITER_MESH_INDEX (e_src, &iter, bm, BM_EDGES_OF_MESH, index) { + BMEdge *e_dst = (BMEdge *)BLI_mempool_alloc(epool_dst); + memcpy(e_dst, e_src, sizeof(BMEdge)); + if (use_toolflags) { + ((BMEdge_OFlag *)e_dst)->oflags = bm->etoolflagpool ? (BMFlagLayer *)BLI_mempool_calloc( + bm->etoolflagpool) : + nullptr; + } + + etable_dst[index] = e_dst; + BM_elem_index_set(e_src, index); /* set_ok */ + } + } + + if (remap & (BM_LOOP | BM_FACE)) { + BMIter iter; + int index, index_loop = 0; + BMFace *f_src; + BM_ITER_MESH_INDEX (f_src, &iter, bm, BM_FACES_OF_MESH, index) { + + if (remap & BM_FACE) { + BMFace *f_dst = (BMFace *)BLI_mempool_alloc(fpool_dst); + memcpy(f_dst, f_src, sizeof(BMFace)); + if (use_toolflags) { + ((BMFace_OFlag *)f_dst)->oflags = bm->ftoolflagpool ? (BMFlagLayer *)BLI_mempool_calloc( + bm->ftoolflagpool) : + nullptr; + } + + ftable_dst[index] = f_dst; + BM_elem_index_set(f_src, index); /* set_ok */ + } + + /* handle loops */ + if (remap & BM_LOOP) { + BMLoop *l_iter_src, *l_first_src; + l_iter_src = l_first_src = BM_FACE_FIRST_LOOP((BMFace *)f_src); + do { + BMLoop *l_dst = (BMLoop *)BLI_mempool_alloc(lpool_dst); + memcpy(l_dst, l_iter_src, sizeof(BMLoop)); + ltable_dst[index_loop] = l_dst; + BM_elem_index_set(l_iter_src, index_loop++); /* set_ok */ + } while ((l_iter_src = l_iter_src->next) != l_first_src); + } + } + } + +#define MAP_VERT(ele) vtable_dst[BM_elem_index_get(ele)] +#define MAP_EDGE(ele) etable_dst[BM_elem_index_get(ele)] +#define MAP_LOOP(ele) ltable_dst[BM_elem_index_get(ele)] +#define MAP_FACE(ele) ftable_dst[BM_elem_index_get(ele)] + +#define REMAP_VERT(ele) \ + { \ + if (remap & BM_VERT) { \ + ele = MAP_VERT(ele); \ + } \ + } \ + ((void)0) +#define REMAP_EDGE(ele) \ + { \ + if (remap & BM_EDGE) { \ + ele = MAP_EDGE(ele); \ + } \ + } \ + ((void)0) +#define REMAP_LOOP(ele) \ + { \ + if (remap & BM_LOOP) { \ + ele = MAP_LOOP(ele); \ + } \ + } \ + ((void)0) +#define REMAP_FACE(ele) \ + { \ + if (remap & BM_FACE) { \ + ele = MAP_FACE(ele); \ + } \ + } \ + ((void)0) + + /* verts */ + { + for (int i = 0; i < bm->totvert; i++) { + BMVert *v = vtable_dst[i]; + if (v->e) { + REMAP_EDGE(v->e); + } + } + } + + /* edges */ + { + for (int i = 0; i < bm->totedge; i++) { + BMEdge *e = etable_dst[i]; + REMAP_VERT(e->v1); + REMAP_VERT(e->v2); + REMAP_EDGE(e->v1_disk_link.next); + REMAP_EDGE(e->v1_disk_link.prev); + REMAP_EDGE(e->v2_disk_link.next); + REMAP_EDGE(e->v2_disk_link.prev); + if (e->l) { + REMAP_LOOP(e->l); + } + } + } + + /* faces */ + { + for (int i = 0; i < bm->totface; i++) { + BMFace *f = ftable_dst[i]; + REMAP_LOOP(f->l_first); + + { + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP((BMFace *)f); + do { + REMAP_VERT(l_iter->v); + REMAP_EDGE(l_iter->e); + REMAP_FACE(l_iter->f); + + REMAP_LOOP(l_iter->radial_next); + REMAP_LOOP(l_iter->radial_prev); + REMAP_LOOP(l_iter->next); + REMAP_LOOP(l_iter->prev); + } while ((l_iter = l_iter->next) != l_first); + } + } + } + + LISTBASE_FOREACH (BMEditSelection *, ese, &bm->selected) { + switch (ese->htype) { + case BM_VERT: + if (remap & BM_VERT) { + ese->ele = (BMElem *)MAP_VERT(ese->ele); + } + break; + case BM_EDGE: + if (remap & BM_EDGE) { + ese->ele = (BMElem *)MAP_EDGE(ese->ele); + } + break; + case BM_FACE: + if (remap & BM_FACE) { + ese->ele = (BMElem *)MAP_FACE(ese->ele); + } + break; + } + } + + if (bm->act_face) { + REMAP_FACE(bm->act_face); + } + +#undef MAP_VERT +#undef MAP_EDGE +#undef MAP_LOOP +#undef MAP_EDGE + +#undef REMAP_VERT +#undef REMAP_EDGE +#undef REMAP_LOOP +#undef REMAP_EDGE + + /* Cleanup, re-use local tables if the current mesh had tables allocated. + * could use irrespective but it may use more memory than the caller wants + * (and not be needed). */ + if (remap & BM_VERT) { + if (bm->vtable) { + SWAP(BMVert **, vtable_dst, bm->vtable); + bm->vtable_tot = bm->totvert; + bm->elem_table_dirty &= ~BM_VERT; + } + MEM_freeN(vtable_dst); + BLI_mempool_destroy(bm->vpool); + bm->vpool = vpool_dst; + } + + if (remap & BM_EDGE) { + if (bm->etable) { + SWAP(BMEdge **, etable_dst, bm->etable); + bm->etable_tot = bm->totedge; + bm->elem_table_dirty &= ~BM_EDGE; + } + MEM_freeN(etable_dst); + BLI_mempool_destroy(bm->epool); + bm->epool = epool_dst; + } + + if (remap & BM_LOOP) { + /* no loop table */ + MEM_freeN(ltable_dst); + BLI_mempool_destroy(bm->lpool); + bm->lpool = lpool_dst; + } + + if (remap & BM_FACE) { + if (bm->ftable) { + SWAP(BMFace **, ftable_dst, bm->ftable); + bm->ftable_tot = bm->totface; + bm->elem_table_dirty &= ~BM_FACE; + } + MEM_freeN(ftable_dst); + BLI_mempool_destroy(bm->fpool); + bm->fpool = fpool_dst; + } +} + +void BM_mesh_toolflags_set(BMesh *bm, bool use_toolflags) +{ + if (bm->use_toolflags == use_toolflags) { + return; + } + + const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_BM(bm); + + BLI_mempool *vpool_dst = nullptr; + BLI_mempool *epool_dst = nullptr; + BLI_mempool *fpool_dst = nullptr; + + bm_mempool_init_ex(&allocsize, use_toolflags, &vpool_dst, &epool_dst, nullptr, &fpool_dst); + + if (use_toolflags == false) { + BLI_mempool_destroy(bm->vtoolflagpool); + BLI_mempool_destroy(bm->etoolflagpool); + BLI_mempool_destroy(bm->ftoolflagpool); + + bm->vtoolflagpool = nullptr; + bm->etoolflagpool = nullptr; + bm->ftoolflagpool = nullptr; + } + struct BMeshCreateParams params = {}; + params.use_toolflags = use_toolflags; + + BM_mesh_rebuild(bm, ¶ms, vpool_dst, epool_dst, nullptr, fpool_dst); + + bm->use_toolflags = use_toolflags; +} + +/* -------------------------------------------------------------------- */ +/** \name BMesh Coordinate Access + * \{ */ + +void BM_mesh_vert_coords_get(BMesh *bm, float (*vert_coords)[3]) +{ + BMIter iter; + BMVert *v; + int i; + BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) { + copy_v3_v3(vert_coords[i], v->co); + } +} + +float (*BM_mesh_vert_coords_alloc(BMesh *bm, int *r_vert_len))[3] +{ + float(*vert_coords)[3] = (float(*)[3])MEM_mallocN(bm->totvert * sizeof(*vert_coords), __func__); + BM_mesh_vert_coords_get(bm, vert_coords); + *r_vert_len = bm->totvert; + return vert_coords; +} + +void BM_mesh_vert_coords_apply(BMesh *bm, const float (*vert_coords)[3]) +{ + BMIter iter; + BMVert *v; + int i; + BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) { + copy_v3_v3(v->co, vert_coords[i]); + } +} + +void BM_mesh_vert_coords_apply_with_mat4(BMesh *bm, + const float (*vert_coords)[3], + const float mat[4][4]) +{ + BMIter iter; + BMVert *v; + int i; + BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) { + mul_v3_m4v3(v->co, mat, vert_coords[i]); + } +} + +/** \} */ -- cgit v1.2.3 From 003dfae270781bfa642a2c5804fa190edd4dfca4 Mon Sep 17 00:00:00 2001 From: Xavier Hallade Date: Fri, 22 Jul 2022 13:00:46 +0200 Subject: Cycles: enable oneAPI in Linux release builds 0f50ae131f54d51f778424d4c9655128cafbbefc didn't do it reliably since it was deactivated explicitly a bit above. --- build_files/cmake/config/blender_release.cmake | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/build_files/cmake/config/blender_release.cmake b/build_files/cmake/config/blender_release.cmake index b4609426069..2567e0b444a 100644 --- a/build_files/cmake/config/blender_release.cmake +++ b/build_files/cmake/config/blender_release.cmake @@ -78,11 +78,6 @@ if(UNIX AND NOT APPLE) set(WITH_PULSEAUDIO ON CACHE BOOL "" FORCE) set(WITH_X11_XINPUT ON CACHE BOOL "" FORCE) set(WITH_X11_XF86VMODE ON CACHE BOOL "" FORCE) - - # Disable oneAPI on Linux for the time being. - # The AoT compilation takes too long to be used officially in the buildbot CI/CD and the JIT - # compilation has ABI compatibility issues when running builds made on centOS on Ubuntu. - set(WITH_CYCLES_DEVICE_ONEAPI OFF CACHE BOOL "" FORCE) endif() if(NOT APPLE) set(WITH_XR_OPENXR ON CACHE BOOL "" FORCE) @@ -93,6 +88,6 @@ if(NOT APPLE) set(WITH_CYCLES_HIP_BINARIES ON CACHE BOOL "" FORCE) set(WITH_CYCLES_DEVICE_ONEAPI ON CACHE BOOL "" FORCE) - # Disable AoT kernels compilations until buildbot can deliver them in a reasonabel time. + # Disable AoT kernels compilations until buildbot can deliver them in a reasonable time. set(WITH_CYCLES_ONEAPI_BINARIES OFF CACHE BOOL "" FORCE) endif() -- cgit v1.2.3 From 185eeeaaac01bf24930f7e8b25c96f9fa9503073 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 22 Jul 2022 22:13:10 +1000 Subject: GHOST/Wayland: Fix mouse wheel events for Sway Compositor (use seat v5) Bump the requested seat version to v5, use discreet scroll callback. Tested with gnome, river & sway. --- intern/ghost/intern/GHOST_SystemWayland.cpp | 35 ++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 07202ff972d..96f11aa2aa5 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -1580,13 +1580,38 @@ static void pointer_handle_button(void *data, } } -static void pointer_handle_axis(void *data, +static void pointer_handle_axis(void * /*data*/, struct wl_pointer * /*wl_pointer*/, const uint32_t /*time*/, const uint32_t axis, const wl_fixed_t value) { CLOG_INFO(LOG, 2, "axis (axis=%u, value=%d)", axis, value); +} + +static void pointer_handle_frame(void * /*data*/, struct wl_pointer * /*wl_pointer*/) +{ + CLOG_INFO(LOG, 2, "frame"); +} +static void pointer_handle_axis_source(void * /*data*/, + struct wl_pointer * /*wl_pointer*/, + uint32_t axis_source) +{ + CLOG_INFO(LOG, 2, "axis_source (axis_source=%u)", axis_source); +} +static void pointer_handle_axis_stop(void * /*data*/, + struct wl_pointer * /*wl_pointer*/, + uint32_t /*time*/, + uint32_t axis) +{ + CLOG_INFO(LOG, 2, "axis_stop (axis=%u)", axis); +} +static void pointer_handle_axis_discrete(void *data, + struct wl_pointer * /*wl_pointer*/, + uint32_t axis, + int32_t discrete) +{ + CLOG_INFO(LOG, 2, "axis_discrete (axis=%u, discrete=%d)", axis, discrete); input_t *input = static_cast(data); if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) { @@ -1596,7 +1621,7 @@ static void pointer_handle_axis(void *data, if (wl_surface *focus_surface = input->pointer.wl_surface) { GHOST_WindowWayland *win = ghost_wl_surface_user_data(focus_surface); input->system->pushEvent(new GHOST_EventWheel( - input->system->getMilliSeconds(), win, std::signbit(value) ? +1 : -1)); + input->system->getMilliSeconds(), win, std::signbit(discrete) ? +1 : -1)); } } @@ -1606,6 +1631,10 @@ static const struct wl_pointer_listener pointer_listener = { pointer_handle_motion, pointer_handle_button, pointer_handle_axis, + pointer_handle_frame, + pointer_handle_axis_source, + pointer_handle_axis_stop, + pointer_handle_axis_discrete, }; #undef LOG @@ -2744,7 +2773,7 @@ static void global_handle_add(void *data, input->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); input->data_source = new data_source_t; input->wl_seat = static_cast( - wl_registry_bind(wl_registry, name, &wl_seat_interface, 4)); + wl_registry_bind(wl_registry, name, &wl_seat_interface, 5)); display->inputs.push_back(input); wl_seat_add_listener(input->wl_seat, &seat_listener, input); } -- cgit v1.2.3 From 98bf714b37c1f1b05a162b6ffdaca367b312de1f Mon Sep 17 00:00:00 2001 From: Germano Cavalcante Date: Thu, 21 Jul 2022 23:44:39 -0300 Subject: Refactor: arrange transform convert functions in 'TransConvertTypeInfo' Simplify the transform code by bundling the TransData creation, Data recalculation, and special updates into a single struct. So similar functions and parameters can be accessed without special type checks. Differential Revision: https://developer.blender.org/D15494 --- source/blender/editors/transform/transform.c | 5 +- source/blender/editors/transform/transform.h | 34 +- .../blender/editors/transform/transform_convert.c | 613 ++++++--------------- .../blender/editors/transform/transform_convert.h | 135 ++--- .../editors/transform/transform_convert_action.c | 13 +- .../editors/transform/transform_convert_armature.c | 24 +- .../editors/transform/transform_convert_cursor.c | 33 +- .../editors/transform/transform_convert_curve.c | 11 +- .../editors/transform/transform_convert_gpencil.c | 11 +- .../editors/transform/transform_convert_graph.c | 22 +- .../editors/transform/transform_convert_lattice.c | 11 +- .../editors/transform/transform_convert_mask.c | 13 +- .../editors/transform/transform_convert_mball.c | 11 +- .../editors/transform/transform_convert_mesh.c | 11 +- .../transform/transform_convert_mesh_edge.c | 11 +- .../transform/transform_convert_mesh_skin.c | 11 +- .../editors/transform/transform_convert_mesh_uv.c | 11 +- .../transform/transform_convert_mesh_vert_cdata.c | 11 +- .../editors/transform/transform_convert_nla.c | 13 +- .../editors/transform/transform_convert_node.c | 13 +- .../editors/transform/transform_convert_object.c | 13 +- .../transform/transform_convert_object_texspace.c | 11 +- .../transform/transform_convert_paintcurve.c | 11 +- .../editors/transform/transform_convert_particle.c | 11 +- .../editors/transform/transform_convert_sculpt.c | 13 +- .../transform/transform_convert_sequencer.c | 13 +- .../transform/transform_convert_sequencer_image.c | 13 +- .../editors/transform/transform_convert_tracking.c | 13 +- source/blender/editors/transform/transform_mode.c | 8 +- source/blender/editors/transform/transform_snap.c | 4 +- .../editors/transform/transform_snap_sequencer.c | 5 +- 31 files changed, 493 insertions(+), 639 deletions(-) diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c index 6e47b30ae9d..8dcbf07b776 100644 --- a/source/blender/editors/transform/transform.c +++ b/source/blender/editors/transform/transform.c @@ -525,7 +525,8 @@ static void viewRedrawPost(bContext *C, TransInfo *t) UVCALC_TRANSFORM_CORRECT_SLIDE : UVCALC_TRANSFORM_CORRECT; - if ((t->data_type == TC_MESH_VERTS) && (t->settings->uvcalc_flag & uvcalc_correct_flag)) { + if ((t->data_type == &TransConvertType_Mesh) && + (t->settings->uvcalc_flag & uvcalc_correct_flag)) { WM_event_add_notifier(C, NC_GEOM | ND_DATA, NULL); } @@ -847,7 +848,7 @@ static bool transform_event_modal_constraint(TransInfo *t, short modal_type) return false; } - if (t->data_type == TC_SEQ_IMAGE_DATA) { + if (t->data_type == &TransConvertType_SequencerImage) { /* Setup the 2d msg string so it writes out the transform space. */ msg_2d = msg_3d; diff --git a/source/blender/editors/transform/transform.h b/source/blender/editors/transform/transform.h index b01affc7307..fc59787e1ec 100644 --- a/source/blender/editors/transform/transform.h +++ b/source/blender/editors/transform/transform.h @@ -38,6 +38,7 @@ struct ReportList; struct Scene; struct ScrArea; struct SnapObjectContext; +struct TransConvertTypeInfo; struct TransDataContainer; struct TransInfo; struct TransSnap; @@ -204,37 +205,6 @@ typedef enum { HLP_TRACKBALL = 6, } eTHelpline; -typedef enum { - TC_NONE = 0, - TC_ACTION_DATA, - TC_POSE, - TC_ARMATURE_VERTS, - TC_CURSOR_IMAGE, - TC_CURSOR_SEQUENCER, - TC_CURSOR_VIEW3D, - TC_CURVE_VERTS, - TC_GRAPH_EDIT_DATA, - TC_GPENCIL, - TC_LATTICE_VERTS, - TC_MASKING_DATA, - TC_MBALL_VERTS, - TC_MESH_VERTS, - TC_MESH_EDGES, - TC_MESH_SKIN, - TC_MESH_UV, - TC_MESH_VERT_CDATA, - TC_NLA_DATA, - TC_NODE_DATA, - TC_OBJECT, - TC_OBJECT_TEXSPACE, - TC_PAINT_CURVE_VERTS, - TC_PARTICLE_VERTS, - TC_SCULPT, - TC_SEQ_DATA, - TC_SEQ_IMAGE_DATA, - TC_TRACKING_DATA, -} eTConvertType; - /** \} */ /* -------------------------------------------------------------------- */ @@ -519,7 +489,7 @@ typedef struct TransInfo { int data_len_all; /** TODO: It should be a member of #TransDataContainer. */ - eTConvertType data_type; + struct TransConvertTypeInfo *data_type; /** Current context/options for transform. */ eTContext options; diff --git a/source/blender/editors/transform/transform_convert.c b/source/blender/editors/transform/transform_convert.c index b0148ce508c..e6cf2dd47d9 100644 --- a/source/blender/editors/transform/transform_convert.c +++ b/source/blender/editors/transform/transform_convert.c @@ -776,63 +776,12 @@ void special_aftertrans_update(bContext *C, TransInfo *t) return; } - BLI_assert(CTX_data_main(t->context) == CTX_data_main(C)); - switch (t->data_type) { - case TC_ACTION_DATA: - special_aftertrans_update__actedit(C, t); - break; - case TC_POSE: - special_aftertrans_update__pose(C, t); - break; - case TC_GRAPH_EDIT_DATA: - special_aftertrans_update__graph(C, t); - break; - case TC_MASKING_DATA: - special_aftertrans_update__mask(C, t); - break; - case TC_MESH_VERTS: - case TC_MESH_EDGES: - special_aftertrans_update__mesh(C, t); - break; - case TC_NLA_DATA: - special_aftertrans_update__nla(C, t); - break; - case TC_NODE_DATA: - special_aftertrans_update__node(C, t); - break; - case TC_OBJECT: - special_aftertrans_update__object(C, t); - break; - case TC_SCULPT: - special_aftertrans_update__sculpt(C, t); - break; - case TC_SEQ_DATA: - special_aftertrans_update__sequencer(C, t); - break; - case TC_SEQ_IMAGE_DATA: - special_aftertrans_update__sequencer_image(C, t); - break; - case TC_TRACKING_DATA: - special_aftertrans_update__movieclip(C, t); - break; - case TC_ARMATURE_VERTS: - case TC_CURSOR_IMAGE: - case TC_CURSOR_SEQUENCER: - case TC_CURSOR_VIEW3D: - case TC_CURVE_VERTS: - case TC_GPENCIL: - case TC_LATTICE_VERTS: - case TC_MBALL_VERTS: - case TC_MESH_VERT_CDATA: - case TC_MESH_UV: - case TC_MESH_SKIN: - case TC_OBJECT_TEXSPACE: - case TC_PAINT_CURVE_VERTS: - case TC_PARTICLE_VERTS: - case TC_NONE: - default: - break; + if (!t->data_type || !t->data_type->special_aftertrans_update) { + return; } + + BLI_assert(CTX_data_main(t->context) == CTX_data_main(C)); + t->data_type->special_aftertrans_update(C, t); } int special_transform_moving(TransInfo *t) @@ -893,55 +842,44 @@ static int countAndCleanTransDataContainer(TransInfo *t) static void init_proportional_edit(TransInfo *t) { - eTConvertType convert_type = t->data_type; - switch (convert_type) { - case TC_ACTION_DATA: - case TC_CURVE_VERTS: - case TC_GRAPH_EDIT_DATA: - case TC_GPENCIL: - case TC_LATTICE_VERTS: - case TC_MASKING_DATA: - case TC_MBALL_VERTS: - case TC_MESH_VERTS: - case TC_MESH_EDGES: - case TC_MESH_SKIN: - case TC_MESH_UV: - case TC_MESH_VERT_CDATA: - case TC_NODE_DATA: - case TC_OBJECT: - case TC_PARTICLE_VERTS: - break; - case TC_POSE: /* Disable PET, its not usable in pose mode yet T32444. */ - case TC_ARMATURE_VERTS: - case TC_CURSOR_IMAGE: - case TC_CURSOR_SEQUENCER: - case TC_CURSOR_VIEW3D: - case TC_NLA_DATA: - case TC_OBJECT_TEXSPACE: - case TC_PAINT_CURVE_VERTS: - case TC_SCULPT: - case TC_SEQ_DATA: - case TC_SEQ_IMAGE_DATA: - case TC_TRACKING_DATA: - case TC_NONE: - default: - t->options |= CTX_NO_PET; - t->flag &= ~T_PROP_EDIT_ALL; - return; + /* NOTE: PET is not usable in pose mode yet T32444. */ + if (!ELEM(t->data_type, + &TransConvertType_Action, + &TransConvertType_Curve, + &TransConvertType_Graph, + &TransConvertType_GPencil, + &TransConvertType_Lattice, + &TransConvertType_Mask, + &TransConvertType_MBall, + &TransConvertType_Mesh, + &TransConvertType_MeshEdge, + &TransConvertType_MeshSkin, + &TransConvertType_MeshUV, + &TransConvertType_MeshVertCData, + &TransConvertType_Node, + &TransConvertType_Object, + &TransConvertType_Particle)) { + /* Disable PET */ + t->options |= CTX_NO_PET; + t->flag &= ~T_PROP_EDIT_ALL; + return; } if (t->data_len_all && (t->flag & T_PROP_EDIT)) { - if (convert_type == TC_OBJECT) { + if (t->data_type == &TransConvertType_Object) { /* Selected objects are already first, no need to presort. */ } else { sort_trans_data_selected_first(t); } - if (ELEM(convert_type, TC_ACTION_DATA, TC_GRAPH_EDIT_DATA)) { + if (ELEM(t->data_type, &TransConvertType_Action, &TransConvertType_Graph)) { /* Distance has already been set. */ } - else if (ELEM(convert_type, TC_MESH_VERTS, TC_MESH_SKIN, TC_MESH_VERT_CDATA)) { + else if (ELEM(t->data_type, + &TransConvertType_Mesh, + &TransConvertType_MeshSkin, + &TransConvertType_MeshVertCData)) { if (t->flag & T_PROP_CONNECTED) { /* Already calculated by transform_convert_mesh_connectivity_distance. */ } @@ -949,10 +887,10 @@ static void init_proportional_edit(TransInfo *t) set_prop_dist(t, false); } } - else if (convert_type == TC_MESH_UV && t->flag & T_PROP_CONNECTED) { + else if (t->data_type == &TransConvertType_MeshUV && t->flag & T_PROP_CONNECTED) { /* Already calculated by uv_set_connectivity_distance. */ } - else if (convert_type == TC_CURVE_VERTS) { + else if (t->data_type == &TransConvertType_Curve) { BLI_assert(t->obedit_type == OB_CURVES_LEGACY); set_prop_dist(t, false); } @@ -975,45 +913,26 @@ static void init_TransDataContainers(TransInfo *t, Object **objects, uint objects_len) { - switch (t->data_type) { - case TC_POSE: - case TC_ARMATURE_VERTS: - case TC_CURVE_VERTS: - case TC_GPENCIL: - case TC_LATTICE_VERTS: - case TC_MBALL_VERTS: - case TC_MESH_VERTS: - case TC_MESH_EDGES: - case TC_MESH_SKIN: - case TC_MESH_UV: - case TC_MESH_VERT_CDATA: - break; - case TC_ACTION_DATA: - case TC_GRAPH_EDIT_DATA: - case TC_CURSOR_IMAGE: - case TC_CURSOR_SEQUENCER: - case TC_CURSOR_VIEW3D: - case TC_MASKING_DATA: - case TC_NLA_DATA: - case TC_NODE_DATA: - case TC_OBJECT: - case TC_OBJECT_TEXSPACE: - case TC_PAINT_CURVE_VERTS: - case TC_PARTICLE_VERTS: - case TC_SCULPT: - case TC_SEQ_DATA: - case TC_SEQ_IMAGE_DATA: - case TC_TRACKING_DATA: - case TC_NONE: - default: - /* Does not support Multi object editing. */ - return; + if (!ELEM(t->data_type, + &TransConvertType_Pose, + &TransConvertType_EditArmature, + &TransConvertType_Curve, + &TransConvertType_GPencil, + &TransConvertType_Lattice, + &TransConvertType_MBall, + &TransConvertType_Mesh, + &TransConvertType_MeshEdge, + &TransConvertType_MeshSkin, + &TransConvertType_MeshUV, + &TransConvertType_MeshVertCData)) { + /* Does not support Multi object editing. */ + return; } const eObjectMode object_mode = obact ? obact->mode : OB_MODE_OBJECT; const short object_type = obact ? obact->type : -1; - if ((object_mode & OB_MODE_EDIT) || (t->data_type == TC_GPENCIL) || + if ((object_mode & OB_MODE_EDIT) || (t->data_type == &TransConvertType_GPencil) || ((object_mode & OB_MODE_POSE) && (object_type == OB_ARMATURE))) { if (t->data_container) { MEM_freeN(t->data_container); @@ -1055,7 +974,7 @@ static void init_TransDataContainers(TransInfo *t, tc->poseobj = objects[i]; tc->use_local_mat = true; } - else if (t->data_type == TC_GPENCIL) { + else if (t->data_type == &TransConvertType_GPencil) { tc->use_local_mat = true; } @@ -1078,177 +997,131 @@ static void init_TransDataContainers(TransInfo *t, } } -static eTFlag flags_from_data_type(eTConvertType data_type) -{ - switch (data_type) { - case TC_ACTION_DATA: - case TC_GRAPH_EDIT_DATA: - case TC_MASKING_DATA: - case TC_NLA_DATA: - case TC_NODE_DATA: - case TC_PAINT_CURVE_VERTS: - case TC_SEQ_DATA: - case TC_SEQ_IMAGE_DATA: - case TC_TRACKING_DATA: - return T_POINTS | T_2D_EDIT; - case TC_ARMATURE_VERTS: - case TC_CURVE_VERTS: - case TC_GPENCIL: - case TC_LATTICE_VERTS: - case TC_MBALL_VERTS: - case TC_MESH_VERTS: - case TC_MESH_SKIN: - case TC_MESH_VERT_CDATA: - return T_EDIT | T_POINTS; - case TC_MESH_EDGES: - return T_EDIT; - case TC_MESH_UV: - return T_EDIT | T_POINTS | T_2D_EDIT; - case TC_CURSOR_IMAGE: - case TC_CURSOR_SEQUENCER: - return T_2D_EDIT; - case TC_PARTICLE_VERTS: - return T_POINTS; - case TC_POSE: - case TC_CURSOR_VIEW3D: - case TC_OBJECT: - case TC_OBJECT_TEXSPACE: - case TC_SCULPT: - case TC_NONE: - default: - break; - } - return 0; -} - -static eTConvertType convert_type_get(const TransInfo *t, Object **r_obj_armature) +static TransConvertTypeInfo *convert_type_get(const TransInfo *t, Object **r_obj_armature) { ViewLayer *view_layer = t->view_layer; Object *ob = OBACT(view_layer); - eTConvertType convert_type = TC_NONE; /* if tests must match recalcData for correct updates */ if (t->options & CTX_CURSOR) { if (t->spacetype == SPACE_IMAGE) { - convert_type = TC_CURSOR_IMAGE; + return &TransConvertType_CursorImage; } - else if (t->spacetype == SPACE_SEQ) { - convert_type = TC_CURSOR_SEQUENCER; - } - else { - convert_type = TC_CURSOR_VIEW3D; + + if (t->spacetype == SPACE_SEQ) { + return &TransConvertType_CursorSequencer; } + + return &TransConvertType_Cursor3D; } - else if (!(t->options & CTX_PAINT_CURVE) && (t->spacetype == SPACE_VIEW3D) && ob && - (ob->mode == OB_MODE_SCULPT) && ob->sculpt) { - convert_type = TC_SCULPT; + if (!(t->options & CTX_PAINT_CURVE) && (t->spacetype == SPACE_VIEW3D) && ob && + (ob->mode == OB_MODE_SCULPT) && ob->sculpt) { + return &TransConvertType_Sculpt; } - else if (t->options & CTX_TEXTURE_SPACE) { - convert_type = TC_OBJECT_TEXSPACE; + if (t->options & CTX_TEXTURE_SPACE) { + return &TransConvertType_ObjectTexSpace; } - else if (t->options & CTX_EDGE_DATA) { - convert_type = TC_MESH_EDGES; + if (t->options & CTX_EDGE_DATA) { + return &TransConvertType_MeshEdge; } - else if (t->options & CTX_GPENCIL_STROKES) { - convert_type = TC_GPENCIL; + if (t->options & CTX_GPENCIL_STROKES) { + return &TransConvertType_GPencil; } - else if (t->spacetype == SPACE_IMAGE) { + if (t->spacetype == SPACE_IMAGE) { if (t->options & CTX_MASK) { - convert_type = TC_MASKING_DATA; + return &TransConvertType_Mask; } - else if (t->options & CTX_PAINT_CURVE) { + if (t->options & CTX_PAINT_CURVE) { if (!ELEM(t->mode, TFM_SHEAR, TFM_SHRINKFATTEN)) { - convert_type = TC_PAINT_CURVE_VERTS; + return &TransConvertType_PaintCurve; } } else if (t->obedit_type == OB_MESH) { - convert_type = TC_MESH_UV; + return &TransConvertType_MeshUV; } + return NULL; } - else if (t->spacetype == SPACE_ACTION) { - convert_type = TC_ACTION_DATA; + if (t->spacetype == SPACE_ACTION) { + return &TransConvertType_Action; } - else if (t->spacetype == SPACE_NLA) { - convert_type = TC_NLA_DATA; + if (t->spacetype == SPACE_NLA) { + return &TransConvertType_NLA; } - else if (t->spacetype == SPACE_SEQ) { + if (t->spacetype == SPACE_SEQ) { if (t->options & CTX_SEQUENCER_IMAGE) { - convert_type = TC_SEQ_IMAGE_DATA; - } - else { - convert_type = TC_SEQ_DATA; + return &TransConvertType_SequencerImage; } + return &TransConvertType_Sequencer; } - else if (t->spacetype == SPACE_GRAPH) { - convert_type = TC_GRAPH_EDIT_DATA; + if (t->spacetype == SPACE_GRAPH) { + return &TransConvertType_Graph; } - else if (t->spacetype == SPACE_NODE) { - convert_type = TC_NODE_DATA; + if (t->spacetype == SPACE_NODE) { + return &TransConvertType_Graph; } - else if (t->spacetype == SPACE_CLIP) { + if (t->spacetype == SPACE_CLIP) { if (t->options & CTX_MOVIECLIP) { - convert_type = TC_TRACKING_DATA; + return &TransConvertType_Tracking; } - else if (t->options & CTX_MASK) { - convert_type = TC_MASKING_DATA; + if (t->options & CTX_MASK) { + return &TransConvertType_Mask; } + return NULL; } - else if (t->obedit_type != -1) { + if (t->obedit_type != -1) { if (t->obedit_type == OB_MESH) { if (t->mode == TFM_SKIN_RESIZE) { - convert_type = TC_MESH_SKIN; + return &TransConvertType_MeshSkin; } - else if (ELEM(t->mode, TFM_BWEIGHT, TFM_VERT_CREASE)) { - convert_type = TC_MESH_VERT_CDATA; - } - else { - convert_type = TC_MESH_VERTS; + if (ELEM(t->mode, TFM_BWEIGHT, TFM_VERT_CREASE)) { + return &TransConvertType_MeshVertCData; } + return &TransConvertType_Mesh; } - else if (ELEM(t->obedit_type, OB_CURVES_LEGACY, OB_SURF)) { - convert_type = TC_CURVE_VERTS; + if (ELEM(t->obedit_type, OB_CURVES_LEGACY, OB_SURF)) { + return &TransConvertType_Curve; } - else if (t->obedit_type == OB_LATTICE) { - convert_type = TC_LATTICE_VERTS; + if (t->obedit_type == OB_LATTICE) { + return &TransConvertType_Lattice; } - else if (t->obedit_type == OB_MBALL) { - convert_type = TC_MBALL_VERTS; + if (t->obedit_type == OB_MBALL) { + return &TransConvertType_MBall; } - else if (t->obedit_type == OB_ARMATURE) { - convert_type = TC_ARMATURE_VERTS; + if (t->obedit_type == OB_ARMATURE) { + return &TransConvertType_EditArmature; } + return NULL; } - else if (ob && (ob->mode & OB_MODE_POSE)) { - convert_type = TC_POSE; + if (ob && (ob->mode & OB_MODE_POSE)) { + return &TransConvertType_Pose; } - else if (ob && (ob->mode & OB_MODE_ALL_WEIGHT_PAINT) && !(t->options & CTX_PAINT_CURVE)) { + if (ob && (ob->mode & OB_MODE_ALL_WEIGHT_PAINT) && !(t->options & CTX_PAINT_CURVE)) { Object *ob_armature = transform_object_deform_pose_armature_get(t, ob); if (ob_armature) { *r_obj_armature = ob_armature; - convert_type = TC_POSE; + return &TransConvertType_Pose; } + return NULL; } - else if (ob && (ob->mode & OB_MODE_PARTICLE_EDIT) && - PE_start_edit(PE_get_current(t->depsgraph, t->scene, ob))) { - convert_type = TC_PARTICLE_VERTS; + if (ob && (ob->mode & OB_MODE_PARTICLE_EDIT) && + PE_start_edit(PE_get_current(t->depsgraph, t->scene, ob))) { + return &TransConvertType_Particle; } - else if (ob && (ob->mode & OB_MODE_ALL_PAINT)) { + if (ob && (ob->mode & OB_MODE_ALL_PAINT)) { if ((t->options & CTX_PAINT_CURVE) && !ELEM(t->mode, TFM_SHEAR, TFM_SHRINKFATTEN)) { - convert_type = TC_PAINT_CURVE_VERTS; + return &TransConvertType_PaintCurve; } + return NULL; } - else if ((ob) && (ELEM(ob->mode, - OB_MODE_PAINT_GPENCIL, - OB_MODE_SCULPT_GPENCIL, - OB_MODE_WEIGHT_GPENCIL, - OB_MODE_VERTEX_GPENCIL))) { + if ((ob) && (ELEM(ob->mode, + OB_MODE_PAINT_GPENCIL, + OB_MODE_SCULPT_GPENCIL, + OB_MODE_WEIGHT_GPENCIL, + OB_MODE_VERTEX_GPENCIL))) { /* In grease pencil all transformations must be canceled if not Object or Edit. */ + return NULL; } - else { - convert_type = TC_OBJECT; - } - - return convert_type; + return &TransConvertType_Object; } void createTransData(bContext *C, TransInfo *t) @@ -1257,7 +1130,14 @@ void createTransData(bContext *C, TransInfo *t) Object *ob_armature = NULL; t->data_type = convert_type_get(t, &ob_armature); - t->flag |= flags_from_data_type(t->data_type); + if (t->data_type == NULL) { + printf("edit type not implemented!\n"); + BLI_assert(t->data_len_all == -1); + t->data_len_all = 0; + return; + } + + t->flag |= t->data_type->flags; if (ob_armature) { init_TransDataContainers(t, ob_armature, &ob_armature, 1); @@ -1268,125 +1148,46 @@ void createTransData(bContext *C, TransInfo *t) init_TransDataContainers(t, ob, NULL, 0); } - switch (t->data_type) { - case TC_ACTION_DATA: - createTransActionData(C, t); - break; - case TC_POSE: - t->options |= CTX_POSE_BONE; + if (t->data_type == &TransConvertType_Object) { + t->options |= CTX_OBJECT; - /* XXX active-layer checking isn't done - * as that should probably be checked through context instead. */ - createTransPose(t); - break; - case TC_ARMATURE_VERTS: - createTransArmatureVerts(t); - break; - case TC_CURSOR_IMAGE: - createTransCursor_image(t); - break; - case TC_CURSOR_SEQUENCER: - createTransCursor_sequencer(t); - break; - case TC_CURSOR_VIEW3D: - createTransCursor_view3d(t); - break; - case TC_CURVE_VERTS: - createTransCurveVerts(t); - break; - case TC_GRAPH_EDIT_DATA: - createTransGraphEditData(C, t); - break; - case TC_GPENCIL: - createTransGPencil(C, t); - break; - case TC_LATTICE_VERTS: - createTransLatticeVerts(t); - break; - case TC_MASKING_DATA: - createTransMaskingData(C, t); - break; - case TC_MBALL_VERTS: - createTransMBallVerts(t); - break; - case TC_MESH_VERTS: - createTransEditVerts(t); - break; - case TC_MESH_EDGES: - createTransEdge(t); - break; - case TC_MESH_SKIN: - createTransMeshSkin(t); - break; - case TC_MESH_UV: - createTransUVs(C, t); - break; - case TC_NLA_DATA: - createTransNlaData(C, t); - break; - case TC_MESH_VERT_CDATA: - createTransMeshVertCData(t); - break; - case TC_NODE_DATA: - createTransNodeData(t); - break; - case TC_OBJECT: - t->options |= CTX_OBJECT; - - /* Needed for correct Object.obmat after duplication, see: T62135. */ - BKE_scene_graph_evaluated_ensure(t->depsgraph, CTX_data_main(t->context)); - - if ((t->settings->transform_flag & SCE_XFORM_DATA_ORIGIN) != 0) { - t->options |= CTX_OBMODE_XFORM_OBDATA; - } - if ((t->settings->transform_flag & SCE_XFORM_SKIP_CHILDREN) != 0) { - t->options |= CTX_OBMODE_XFORM_SKIP_CHILDREN; - } - createTransObject(C, t); - /* Check if we're transforming the camera from the camera */ - if ((t->spacetype == SPACE_VIEW3D) && (t->region->regiontype == RGN_TYPE_WINDOW)) { - View3D *v3d = t->view; - RegionView3D *rv3d = t->region->regiondata; - if ((rv3d->persp == RV3D_CAMOB) && v3d->camera) { - /* we could have a flag to easily check an object is being transformed */ - if (v3d->camera->id.tag & LIB_TAG_DOIT) { - t->options |= CTX_CAMERA; - } - } - else if (v3d->ob_center && v3d->ob_center->id.tag & LIB_TAG_DOIT) { + /* Needed for correct Object.obmat after duplication, see: T62135. */ + BKE_scene_graph_evaluated_ensure(t->depsgraph, CTX_data_main(t->context)); + + if ((t->settings->transform_flag & SCE_XFORM_DATA_ORIGIN) != 0) { + t->options |= CTX_OBMODE_XFORM_OBDATA; + } + if ((t->settings->transform_flag & SCE_XFORM_SKIP_CHILDREN) != 0) { + t->options |= CTX_OBMODE_XFORM_SKIP_CHILDREN; + } + TransConvertType_Object.createTransData(C, t); + /* Check if we're transforming the camera from the camera */ + if ((t->spacetype == SPACE_VIEW3D) && (t->region->regiontype == RGN_TYPE_WINDOW)) { + View3D *v3d = t->view; + RegionView3D *rv3d = t->region->regiondata; + if ((rv3d->persp == RV3D_CAMOB) && v3d->camera) { + /* we could have a flag to easily check an object is being transformed */ + if (v3d->camera->id.tag & LIB_TAG_DOIT) { t->options |= CTX_CAMERA; } } - break; - case TC_OBJECT_TEXSPACE: - createTransTexspace(t); - break; - case TC_PAINT_CURVE_VERTS: - createTransPaintCurveVerts(C, t); - break; - case TC_PARTICLE_VERTS: - createTransParticleVerts(t); - break; - case TC_SCULPT: - createTransSculpt(C, t); - break; - case TC_SEQ_DATA: - t->num.flag |= NUM_NO_FRACTION; /* sequencer has no use for floating point transform. */ - createTransSeqData(t); - break; - case TC_SEQ_IMAGE_DATA: + else if (v3d->ob_center && v3d->ob_center->id.tag & LIB_TAG_DOIT) { + t->options |= CTX_CAMERA; + } + } + } + else { + if (t->data_type == &TransConvertType_Pose) { + t->options |= CTX_POSE_BONE; + } + else if (t->data_type == &TransConvertType_Sequencer) { + /* Sequencer has no use for floating point transform. */ + t->num.flag |= NUM_NO_FRACTION; + } + else if (t->data_type == &TransConvertType_SequencerImage) { t->obedit_type = -1; - createTransSeqImageData(t); - break; - case TC_TRACKING_DATA: - createTransTrackingData(C, t); - break; - case TC_NONE: - default: - printf("edit type not implemented!\n"); - BLI_assert(t->data_len_all == -1); - t->data_len_all = 0; - return; + } + t->data_type->createTransData(C, t); } countAndCleanTransDataContainer(t); @@ -1590,92 +1391,10 @@ void transform_convert_flush_handle2D(TransData *td, TransData2D *td2d, const fl void recalcData(TransInfo *t) { - switch (t->data_type) { - case TC_ACTION_DATA: - recalcData_actedit(t); - break; - case TC_POSE: - recalcData_pose(t); - break; - case TC_ARMATURE_VERTS: - recalcData_edit_armature(t); - break; - case TC_CURVE_VERTS: - recalcData_curve(t); - break; - case TC_CURSOR_IMAGE: - recalcData_cursor_image(t); - break; - case TC_CURSOR_SEQUENCER: - recalcData_cursor_sequencer(t); - break; - case TC_CURSOR_VIEW3D: - recalcData_cursor_view3d(t); - break; - case TC_GRAPH_EDIT_DATA: - recalcData_graphedit(t); - break; - case TC_GPENCIL: - recalcData_gpencil_strokes(t); - break; - case TC_MASKING_DATA: - recalcData_mask_common(t); - break; - case TC_MESH_VERTS: - recalcData_mesh(t); - break; - case TC_MESH_EDGES: - recalcData_mesh_edge(t); - break; - case TC_MESH_SKIN: - recalcData_mesh_skin(t); - break; - case TC_MESH_UV: - recalcData_uv(t); - break; - case TC_MESH_VERT_CDATA: - recalcData_mesh_cdata(t); - break; - case TC_NLA_DATA: - recalcData_nla(t); - break; - case TC_NODE_DATA: - flushTransNodes(t); - break; - case TC_OBJECT: - recalcData_objects(t); - break; - case TC_OBJECT_TEXSPACE: - recalcData_texspace(t); - break; - case TC_PAINT_CURVE_VERTS: - flushTransPaintCurve(t); - break; - case TC_SCULPT: - recalcData_sculpt(t); - break; - case TC_SEQ_DATA: - recalcData_sequencer(t); - break; - case TC_SEQ_IMAGE_DATA: - recalcData_sequencer_image(t); - break; - case TC_TRACKING_DATA: - recalcData_tracking(t); - break; - case TC_MBALL_VERTS: - recalcData_mball(t); - break; - case TC_LATTICE_VERTS: - recalcData_lattice(t); - break; - case TC_PARTICLE_VERTS: - recalcData_particles(t); - break; - case TC_NONE: - default: - break; + if (!t->data_type || !t->data_type->recalcData) { + return; } + t->data_type->recalcData(t); } /** \} */ diff --git a/source/blender/editors/transform/transform_convert.h b/source/blender/editors/transform/transform_convert.h index 1b7c33e443c..f32bff6dcff 100644 --- a/source/blender/editors/transform/transform_convert.h +++ b/source/blender/editors/transform/transform_convert.h @@ -21,6 +21,25 @@ struct TransDataCurveHandleFlags; struct TransInfo; struct bContext; +typedef struct TransConvertTypeInfo { + int flags; /* eTFlag */ + + /** + * Allocate and initialize `t->data`. + */ + void (*createTransData)(bContext *C, TransInfo *t); + + /** + * Force recalculation of data during transformation. + */ + void (*recalcData)(TransInfo *t); + + /** + * Called when the operation is finished. + */ + void (*special_aftertrans_update)(bContext *C, TransInfo *t); +} TransConvertTypeInfo; + /* transform_convert.c */ /** @@ -98,82 +117,53 @@ void animrecord_check_state(TransInfo *t, struct ID *id); /* transform_convert_action.c */ -void createTransActionData(bContext *C, TransInfo *t); -/* helper for recalcData() - for Action Editor transforms */ -void recalcData_actedit(TransInfo *t); -void special_aftertrans_update__actedit(bContext *C, TransInfo *t); +extern TransConvertTypeInfo TransConvertType_Action; /* transform_convert_armature.c */ +extern TransConvertTypeInfo TransConvertType_EditArmature; +extern TransConvertTypeInfo TransConvertType_Pose; + /** * Sets transform flags in the bones. * Returns total number of bones with #BONE_TRANSFORM. */ void transform_convert_pose_transflags_update(Object *ob, int mode, short around); -/** - * When objects array is NULL, use 't->data_container' as is. - */ -void createTransPose(TransInfo *t); -void createTransArmatureVerts(TransInfo *t); -void recalcData_edit_armature(TransInfo *t); -void recalcData_pose(TransInfo *t); -void special_aftertrans_update__pose(bContext *C, TransInfo *t); - /* transform_convert_cursor.c */ -void createTransCursor_image(TransInfo *t); -void createTransCursor_sequencer(TransInfo *t); -void createTransCursor_view3d(TransInfo *t); -void recalcData_cursor_image(TransInfo *t); -void recalcData_cursor_sequencer(TransInfo *t); -void recalcData_cursor_view3d(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_CursorImage; +extern TransConvertTypeInfo TransConvertType_CursorSequencer; +extern TransConvertTypeInfo TransConvertType_Cursor3D; /* transform_convert_curve.c */ -void createTransCurveVerts(TransInfo *t); -void recalcData_curve(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_Curve; /* transform_convert_graph.c */ -/** - * It is important to note that this doesn't always act on the selection (like it's usually done), - * it acts on a subset of it. E.g. the selection code may leave a hint that we just dragged on a - * left or right handle (SIPO_RUNTIME_FLAG_TWEAK_HANDLES_LEFT/RIGHT) and then we only transform the - * selected left or right handles accordingly. - * The points to be transformed are tagged with BEZT_FLAG_TEMP_TAG; some lower level curve - * functions may need to be made aware of this. It's ugly that these act based on selection state - * anyway. - */ -void createTransGraphEditData(bContext *C, TransInfo *t); -/* helper for recalcData() - for Graph Editor transforms */ -void recalcData_graphedit(TransInfo *t); -void special_aftertrans_update__graph(bContext *C, TransInfo *t); +extern TransConvertTypeInfo TransConvertType_Graph; /* transform_convert_gpencil.c */ -void createTransGPencil(bContext *C, TransInfo *t); -/* force recalculation of triangles during transformation */ -void recalcData_gpencil_strokes(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_GPencil; /* transform_convert_lattice.c */ -void createTransLatticeVerts(TransInfo *t); -void recalcData_lattice(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_Lattice; /* transform_convert_mask.c */ -void createTransMaskingData(bContext *C, TransInfo *t); -void recalcData_mask_common(TransInfo *t); -void special_aftertrans_update__mask(bContext *C, TransInfo *t); +extern TransConvertTypeInfo TransConvertType_Mask; /* transform_convert_mball.c */ -void createTransMBallVerts(TransInfo *t); -void recalcData_mball(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_MBall; /* transform_convert_mesh.c */ +extern TransConvertTypeInfo TransConvertType_Mesh; + struct TransIslandData { float (*center)[3]; float (*axismtx)[3][3]; @@ -232,89 +222,60 @@ void transform_convert_mesh_crazyspace_transdata_set(const float mtx[3][3], struct TransData *r_td); void transform_convert_mesh_crazyspace_free(struct TransMeshDataCrazySpace *r_crazyspace_data); -void createTransEditVerts(TransInfo *t); -void recalcData_mesh(TransInfo *t); void special_aftertrans_update__mesh(bContext *C, TransInfo *t); /* transform_convert_mesh_edge.c */ -void createTransEdge(TransInfo *t); -void recalcData_mesh_edge(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_MeshEdge; /* transform_convert_mesh_skin.c */ -void createTransMeshSkin(TransInfo *t); -void recalcData_mesh_skin(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_MeshSkin; /* transform_convert_mesh_uv.c */ -void createTransUVs(bContext *C, TransInfo *t); -/* helper for recalcData() - for Image Editor transforms */ -void recalcData_uv(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_MeshUV; /* transform_convert_mesh_vert_cdata.c */ -void createTransMeshVertCData(TransInfo *t); -void recalcData_mesh_cdata(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_MeshVertCData; /* transform_convert_nla.c */ -void createTransNlaData(bContext *C, TransInfo *t); -/* helper for recalcData() - for NLA Editor transforms */ -void recalcData_nla(TransInfo *t); -void special_aftertrans_update__nla(bContext *C, TransInfo *t); +extern TransConvertTypeInfo TransConvertType_NLA; /* transform_convert_node.c */ -void createTransNodeData(TransInfo *t); -void flushTransNodes(TransInfo *t); -void special_aftertrans_update__node(bContext *C, TransInfo *t); +extern TransConvertTypeInfo TransConvertType_Node; /* transform_convert_object.c */ -void createTransObject(bContext *C, TransInfo *t); -/* helper for recalcData() - for object transforms, typically in the 3D view */ -void recalcData_objects(TransInfo *t); -void special_aftertrans_update__object(bContext *C, TransInfo *t); +extern TransConvertTypeInfo TransConvertType_Object; /* transform_convert_object_texspace.c */ -void createTransTexspace(TransInfo *t); -/* helper for recalcData() - for object transforms, typically in the 3D view */ -void recalcData_texspace(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_ObjectTexSpace; /* transform_convert_paintcurve.c */ -void createTransPaintCurveVerts(bContext *C, TransInfo *t); -void flushTransPaintCurve(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_PaintCurve; /* transform_convert_particle.c */ -void createTransParticleVerts(TransInfo *t); -void recalcData_particles(TransInfo *t); +extern TransConvertTypeInfo TransConvertType_Particle; /* transform_convert_sculpt.c */ -void createTransSculpt(bContext *C, TransInfo *t); -void recalcData_sculpt(TransInfo *t); -void special_aftertrans_update__sculpt(bContext *C, TransInfo *t); +extern TransConvertTypeInfo TransConvertType_Sculpt; /* transform_convert_sequencer.c */ -void createTransSeqData(TransInfo *t); -/* helper for recalcData() - for sequencer transforms */ -void recalcData_sequencer(TransInfo *t); -void special_aftertrans_update__sequencer(bContext *C, TransInfo *t); +extern TransConvertTypeInfo TransConvertType_Sequencer; /* transform_convert_sequencer_image.c */ -void createTransSeqImageData(TransInfo *t); -void recalcData_sequencer_image(TransInfo *t); -void special_aftertrans_update__sequencer_image(bContext *C, TransInfo *t); +extern TransConvertTypeInfo TransConvertType_SequencerImage; /* transform_convert_tracking.c */ -void createTransTrackingData(bContext *C, TransInfo *t); -/* helper for recalcData() - for Movie Clip transforms */ -void recalcData_tracking(TransInfo *t); -void special_aftertrans_update__movieclip(bContext *C, TransInfo *t); +extern TransConvertTypeInfo TransConvertType_Tracking; diff --git a/source/blender/editors/transform/transform_convert_action.c b/source/blender/editors/transform/transform_convert_action.c index 71a78321e12..41635522d26 100644 --- a/source/blender/editors/transform/transform_convert_action.c +++ b/source/blender/editors/transform/transform_convert_action.c @@ -289,7 +289,7 @@ static int MaskLayerToTransData(TransData *td, return count; } -void createTransActionData(bContext *C, TransInfo *t) +static void createTransActionData(bContext *C, TransInfo *t) { Scene *scene = t->scene; TransData *td = NULL; @@ -565,7 +565,7 @@ static void flushTransIntFrameActionData(TransInfo *t) } } -void recalcData_actedit(TransInfo *t) +static void recalcData_actedit(TransInfo *t) { ViewLayer *view_layer = t->view_layer; SpaceAction *saction = (SpaceAction *)t->area->spacedata.first; @@ -759,7 +759,7 @@ static void posttrans_action_clean(bAnimContext *ac, bAction *act) ANIM_animdata_freelist(&anim_data); } -void special_aftertrans_update__actedit(bContext *C, TransInfo *t) +static void special_aftertrans_update__actedit(bContext *C, TransInfo *t) { SpaceAction *saction = (SpaceAction *)t->area->spacedata.first; bAnimContext ac; @@ -926,3 +926,10 @@ void special_aftertrans_update__actedit(bContext *C, TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_Action = { + /* flags */ (T_POINTS | T_2D_EDIT), + /* createTransData */ createTransActionData, + /* recalcData */ recalcData_actedit, + /* special_aftertrans_update */ special_aftertrans_update__actedit, +}; diff --git a/source/blender/editors/transform/transform_convert_armature.c b/source/blender/editors/transform/transform_convert_armature.c index 1613218ca29..97d9ab2964a 100644 --- a/source/blender/editors/transform/transform_convert_armature.c +++ b/source/blender/editors/transform/transform_convert_armature.c @@ -701,7 +701,7 @@ static void add_pose_transdata(TransInfo *t, bPoseChannel *pchan, Object *ob, Tr td->con = pchan->constraints.first; } -void createTransPose(TransInfo *t) +static void createTransPose(bContext *UNUSED(C), TransInfo *t) { Main *bmain = CTX_data_main(t->context); @@ -879,7 +879,7 @@ void createTransPose(TransInfo *t) } } -void createTransArmatureVerts(TransInfo *t) +static void createTransArmatureVerts(bContext *UNUSED(C), TransInfo *t) { t->data_len_all = 0; @@ -1189,7 +1189,7 @@ static void restoreBones(TransDataContainer *tc) } } -void recalcData_edit_armature(TransInfo *t) +static void recalcData_edit_armature(TransInfo *t) { if (t->state != TRANS_CANCEL) { applySnappingIndividual(t); @@ -1412,7 +1412,7 @@ static void restoreMirrorPoseBones(TransDataContainer *tc) } } -void recalcData_pose(TransInfo *t) +static void recalcData_pose(TransInfo *t) { if (t->mode == TFM_BONESIZE) { /* Handle the exception where for TFM_BONESIZE in edit mode we pretend to be @@ -1684,7 +1684,7 @@ static void pose_grab_with_ik_clear(Main *bmain, Object *ob) } } -void special_aftertrans_update__pose(bContext *C, TransInfo *t) +static void special_aftertrans_update__pose(bContext *C, TransInfo *t) { Object *ob; @@ -1768,3 +1768,17 @@ void special_aftertrans_update__pose(bContext *C, TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_EditArmature = { + /* flags */ (T_EDIT | T_POINTS), + /* createTransData */ createTransArmatureVerts, + /* recalcData */ recalcData_edit_armature, + /* special_aftertrans_update */ NULL, +}; + +TransConvertTypeInfo TransConvertType_Pose = { + /* flags */ 0, + /* createTransData */ createTransPose, + /* recalcData */ recalcData_pose, + /* special_aftertrans_update */ special_aftertrans_update__pose, +}; diff --git a/source/blender/editors/transform/transform_convert_cursor.c b/source/blender/editors/transform/transform_convert_cursor.c index 5e6eee192f2..0bf6f06a9f2 100644 --- a/source/blender/editors/transform/transform_convert_cursor.c +++ b/source/blender/editors/transform/transform_convert_cursor.c @@ -82,13 +82,13 @@ static void recalcData_cursor_2D_impl(TransInfo *t) /** \name Image Cursor * \{ */ -void createTransCursor_image(TransInfo *t) +static void createTransCursor_image(bContext *UNUSED(C), TransInfo *t) { SpaceImage *sima = t->area->spacedata.first; createTransCursor_2D_impl(t, sima->cursor); } -void recalcData_cursor_image(TransInfo *t) +static void recalcData_cursor_image(TransInfo *t) { recalcData_cursor_2D_impl(t); } @@ -99,7 +99,7 @@ void recalcData_cursor_image(TransInfo *t) /** \name Sequencer Cursor * \{ */ -void createTransCursor_sequencer(TransInfo *t) +static void createTransCursor_sequencer(bContext *UNUSED(C), TransInfo *t) { SpaceSeq *sseq = t->area->spacedata.first; if (sseq->mainb != SEQ_DRAW_IMG_IMBUF) { @@ -108,7 +108,7 @@ void createTransCursor_sequencer(TransInfo *t) createTransCursor_2D_impl(t, sseq->cursor); } -void recalcData_cursor_sequencer(TransInfo *t) +static void recalcData_cursor_sequencer(TransInfo *t) { recalcData_cursor_2D_impl(t); } @@ -119,7 +119,7 @@ void recalcData_cursor_sequencer(TransInfo *t) /** \name View 3D Cursor * \{ */ -void createTransCursor_view3d(TransInfo *t) +static void createTransCursor_view3d(bContext *UNUSED(C), TransInfo *t) { TransData *td; @@ -178,9 +178,30 @@ void createTransCursor_view3d(TransInfo *t) td->ext->rotOrder = cursor->rotation_mode; } -void recalcData_cursor_view3d(TransInfo *t) +static void recalcData_cursor_view3d(TransInfo *t) { DEG_id_tag_update(&t->scene->id, ID_RECALC_COPY_ON_WRITE); } /** \} */ + +TransConvertTypeInfo TransConvertType_CursorImage = { + /* flags */ T_2D_EDIT, + /* createTransData */ createTransCursor_image, + /* recalcData */ recalcData_cursor_image, + /* special_aftertrans_update */ NULL, +}; + +TransConvertTypeInfo TransConvertType_CursorSequencer = { + /* flags */ T_2D_EDIT, + /* createTransData */ createTransCursor_sequencer, + /* recalcData */ recalcData_cursor_sequencer, + /* special_aftertrans_update */ NULL, +}; + +TransConvertTypeInfo TransConvertType_Cursor3D = { + /* flags */ 0, + /* createTransData */ createTransCursor_view3d, + /* recalcData */ recalcData_cursor_view3d, + /* special_aftertrans_update */ NULL, +}; diff --git a/source/blender/editors/transform/transform_convert_curve.c b/source/blender/editors/transform/transform_convert_curve.c index b9581aa1e31..404b1293208 100644 --- a/source/blender/editors/transform/transform_convert_curve.c +++ b/source/blender/editors/transform/transform_convert_curve.c @@ -62,7 +62,7 @@ static int bezt_select_to_transform_triple_flag(const BezTriple *bezt, const boo return flag; } -void createTransCurveVerts(TransInfo *t) +static void createTransCurveVerts(bContext *UNUSED(C), TransInfo *t) { #define SEL_F1 (1 << 0) @@ -415,7 +415,7 @@ void createTransCurveVerts(TransInfo *t) #undef SEL_F3 } -void recalcData_curve(TransInfo *t) +static void recalcData_curve(TransInfo *t) { if (t->state != TRANS_CANCEL) { applySnappingIndividual(t); @@ -446,3 +446,10 @@ void recalcData_curve(TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_Curve = { + /* flags */ (T_EDIT | T_POINTS), + /* createTransData */ createTransCurveVerts, + /* recalcData */ recalcData_curve, + /* special_aftertrans_update */ NULL, +}; diff --git a/source/blender/editors/transform/transform_convert_gpencil.c b/source/blender/editors/transform/transform_convert_gpencil.c index 4bd02b0a45b..0029eaefaba 100644 --- a/source/blender/editors/transform/transform_convert_gpencil.c +++ b/source/blender/editors/transform/transform_convert_gpencil.c @@ -672,7 +672,7 @@ static void createTransGPencil_strokes(bContext *C, } } -void createTransGPencil(bContext *C, TransInfo *t) +static void createTransGPencil(bContext *C, TransInfo *t) { if (t->data_container_len == 0) { return; @@ -737,7 +737,7 @@ void createTransGPencil(bContext *C, TransInfo *t) } } -void recalcData_gpencil_strokes(TransInfo *t) +static void recalcData_gpencil_strokes(TransInfo *t) { TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); GHash *strokes = BLI_ghash_ptr_new(__func__); @@ -762,3 +762,10 @@ void recalcData_gpencil_strokes(TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_GPencil = { + /* flags */ (T_EDIT | T_POINTS), + /* createTransData */ createTransGPencil, + /* recalcData */ recalcData_gpencil_strokes, + /* special_aftertrans_update */ NULL, +}; diff --git a/source/blender/editors/transform/transform_convert_graph.c b/source/blender/editors/transform/transform_convert_graph.c index b57f5b78939..aca2439d5fb 100644 --- a/source/blender/editors/transform/transform_convert_graph.c +++ b/source/blender/editors/transform/transform_convert_graph.c @@ -199,7 +199,16 @@ static void graph_key_shortest_dist( } } -void createTransGraphEditData(bContext *C, TransInfo *t) +/** + * It is important to note that this doesn't always act on the selection (like it's usually done), + * it acts on a subset of it. E.g. the selection code may leave a hint that we just dragged on a + * left or right handle (SIPO_RUNTIME_FLAG_TWEAK_HANDLES_LEFT/RIGHT) and then we only transform the + * selected left or right handles accordingly. + * The points to be transformed are tagged with BEZT_FLAG_TEMP_TAG; some lower level curve + * functions may need to be made aware of this. It's ugly that these act based on selection state + * anyway. + */ +static void createTransGraphEditData(bContext *C, TransInfo *t) { SpaceGraph *sipo = (SpaceGraph *)t->area->spacedata.first; Scene *scene = t->scene; @@ -886,7 +895,7 @@ static void remake_graph_transdata(TransInfo *t, ListBase *anim_data) } } -void recalcData_graphedit(TransInfo *t) +static void recalcData_graphedit(TransInfo *t) { SpaceGraph *sipo = (SpaceGraph *)t->area->spacedata.first; ViewLayer *view_layer = t->view_layer; @@ -960,7 +969,7 @@ void recalcData_graphedit(TransInfo *t) /** \name Special After Transform Graph * \{ */ -void special_aftertrans_update__graph(bContext *C, TransInfo *t) +static void special_aftertrans_update__graph(bContext *C, TransInfo *t) { SpaceGraph *sipo = (SpaceGraph *)t->area->spacedata.first; bAnimContext ac; @@ -1021,3 +1030,10 @@ void special_aftertrans_update__graph(bContext *C, TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_Graph = { + /* flags */ (T_POINTS | T_2D_EDIT), + /* createTransData */ createTransGraphEditData, + /* recalcData */ recalcData_graphedit, + /* special_aftertrans_update */ special_aftertrans_update__graph, +}; diff --git a/source/blender/editors/transform/transform_convert_lattice.c b/source/blender/editors/transform/transform_convert_lattice.c index d3c34697237..b77538dc249 100644 --- a/source/blender/editors/transform/transform_convert_lattice.c +++ b/source/blender/editors/transform/transform_convert_lattice.c @@ -25,7 +25,7 @@ /** \name Curve/Surfaces Transform Creation * \{ */ -void createTransLatticeVerts(TransInfo *t) +static void createTransLatticeVerts(bContext *UNUSED(C), TransInfo *t) { FOREACH_TRANS_DATA_CONTAINER (t, tc) { @@ -98,7 +98,7 @@ void createTransLatticeVerts(TransInfo *t) } } -void recalcData_lattice(TransInfo *t) +static void recalcData_lattice(TransInfo *t) { if (t->state != TRANS_CANCEL) { applySnappingIndividual(t); @@ -114,3 +114,10 @@ void recalcData_lattice(TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_Lattice = { + /* flags */ (T_EDIT | T_POINTS), + /* createTransData */ createTransLatticeVerts, + /* recalcData */ recalcData_lattice, + /* special_aftertrans_update */ NULL, +}; diff --git a/source/blender/editors/transform/transform_convert_mask.c b/source/blender/editors/transform/transform_convert_mask.c index f035cfc7aa9..2dca59a5da1 100644 --- a/source/blender/editors/transform/transform_convert_mask.c +++ b/source/blender/editors/transform/transform_convert_mask.c @@ -245,7 +245,7 @@ static void MaskPointToTransData(Scene *scene, } } -void createTransMaskingData(bContext *C, TransInfo *t) +static void createTransMaskingData(bContext *C, TransInfo *t) { Scene *scene = CTX_data_scene(C); Mask *mask = CTX_data_edit_mask(C); @@ -416,7 +416,7 @@ static void flushTransMasking(TransInfo *t) } } -void recalcData_mask_common(TransInfo *t) +static void recalcData_mask_common(TransInfo *t) { Mask *mask = CTX_data_edit_mask(t->context); @@ -431,7 +431,7 @@ void recalcData_mask_common(TransInfo *t) /** \name Special After Transform Mask * \{ */ -void special_aftertrans_update__mask(bContext *C, TransInfo *t) +static void special_aftertrans_update__mask(bContext *C, TransInfo *t) { Mask *mask = NULL; @@ -463,3 +463,10 @@ void special_aftertrans_update__mask(bContext *C, TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_Mask = { + /* flags */ (T_POINTS | T_2D_EDIT), + /* createTransData */ createTransMaskingData, + /* recalcData */ recalcData_mask_common, + /* special_aftertrans_update */ special_aftertrans_update__mask, +}; diff --git a/source/blender/editors/transform/transform_convert_mball.c b/source/blender/editors/transform/transform_convert_mball.c index 5b2a1f8336d..7ae93524d0b 100644 --- a/source/blender/editors/transform/transform_convert_mball.c +++ b/source/blender/editors/transform/transform_convert_mball.c @@ -22,7 +22,7 @@ /** \name Meta Elements Transform Creation * \{ */ -void createTransMBallVerts(TransInfo *t) +static void createTransMBallVerts(bContext *UNUSED(C), TransInfo *t) { FOREACH_TRANS_DATA_CONTAINER (t, tc) { MetaBall *mb = (MetaBall *)tc->obedit->data; @@ -119,7 +119,7 @@ void createTransMBallVerts(TransInfo *t) /** \name Recalc Meta Ball * \{ */ -void recalcData_mball(TransInfo *t) +static void recalcData_mball(TransInfo *t) { if (t->state != TRANS_CANCEL) { applySnappingIndividual(t); @@ -132,3 +132,10 @@ void recalcData_mball(TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_MBall = { + /* flags */ (T_EDIT | T_POINTS), + /* createTransData */ createTransMBallVerts, + /* recalcData */ recalcData_mball, + /* special_aftertrans_update */ NULL, +}; diff --git a/source/blender/editors/transform/transform_convert_mesh.c b/source/blender/editors/transform/transform_convert_mesh.c index de736fad94e..b5a85f57b84 100644 --- a/source/blender/editors/transform/transform_convert_mesh.c +++ b/source/blender/editors/transform/transform_convert_mesh.c @@ -1454,7 +1454,7 @@ static void VertsToTransData(TransInfo *t, } } -void createTransEditVerts(TransInfo *t) +static void createTransEditVerts(bContext *UNUSED(C), TransInfo *t) { FOREACH_TRANS_DATA_CONTAINER (t, tc) { TransDataExtension *tx = NULL; @@ -2029,7 +2029,7 @@ static void tc_mesh_transdata_mirror_apply(TransDataContainer *tc) } } -void recalcData_mesh(TransInfo *t) +static void recalcData_mesh(TransInfo *t) { bool is_canceling = t->state == TRANS_CANCEL; /* Apply corrections. */ @@ -2129,3 +2129,10 @@ void special_aftertrans_update__mesh(bContext *UNUSED(C), TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_Mesh = { + /* flags */ (T_EDIT | T_POINTS), + /* createTransData */ createTransEditVerts, + /* recalcData */ recalcData_mesh, + /* special_aftertrans_update */ special_aftertrans_update__mesh, +}; diff --git a/source/blender/editors/transform/transform_convert_mesh_edge.c b/source/blender/editors/transform/transform_convert_mesh_edge.c index 4a8ddb78587..becf3c7ce5a 100644 --- a/source/blender/editors/transform/transform_convert_mesh_edge.c +++ b/source/blender/editors/transform/transform_convert_mesh_edge.c @@ -23,7 +23,7 @@ /** \name Edge (for crease) Transform Creation * \{ */ -void createTransEdge(TransInfo *t) +static void createTransEdge(bContext *UNUSED(C), TransInfo *t) { FOREACH_TRANS_DATA_CONTAINER (t, tc) { @@ -108,7 +108,7 @@ void createTransEdge(TransInfo *t) } } -void recalcData_mesh_edge(TransInfo *t) +static void recalcData_mesh_edge(TransInfo *t) { FOREACH_TRANS_DATA_CONTAINER (t, tc) { DEG_id_tag_update(tc->obedit->data, ID_RECALC_GEOMETRY); @@ -116,3 +116,10 @@ void recalcData_mesh_edge(TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_MeshEdge = { + /* flags */ T_EDIT, + /* createTransData */ createTransEdge, + /* recalcData */ recalcData_mesh_edge, + /* special_aftertrans_update */ special_aftertrans_update__mesh, +}; diff --git a/source/blender/editors/transform/transform_convert_mesh_skin.c b/source/blender/editors/transform/transform_convert_mesh_skin.c index fdc2ba59035..376e559181e 100644 --- a/source/blender/editors/transform/transform_convert_mesh_skin.c +++ b/source/blender/editors/transform/transform_convert_mesh_skin.c @@ -66,7 +66,7 @@ static void tc_mesh_skin_transdata_create(TransDataBasic *td, td->extra = eve; } -void createTransMeshSkin(TransInfo *t) +static void createTransMeshSkin(bContext *UNUSED(C), TransInfo *t) { BLI_assert(t->mode == TFM_SKIN_RESIZE); FOREACH_TRANS_DATA_CONTAINER (t, tc) { @@ -271,7 +271,7 @@ static void tc_mesh_skin_apply_to_mirror(TransInfo *t) } } -void recalcData_mesh_skin(TransInfo *t) +static void recalcData_mesh_skin(TransInfo *t) { bool is_canceling = t->state == TRANS_CANCEL; /* mirror modifier clipping? */ @@ -289,3 +289,10 @@ void recalcData_mesh_skin(TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_MeshSkin = { + /* flags */ (T_EDIT | T_POINTS), + /* createTransData */ createTransMeshSkin, + /* recalcData */ recalcData_mesh_skin, + /* special_aftertrans_update */ NULL, +}; diff --git a/source/blender/editors/transform/transform_convert_mesh_uv.c b/source/blender/editors/transform/transform_convert_mesh_uv.c index bb550909407..d95bc7b976f 100644 --- a/source/blender/editors/transform/transform_convert_mesh_uv.c +++ b/source/blender/editors/transform/transform_convert_mesh_uv.c @@ -234,7 +234,7 @@ static void uv_set_connectivity_distance(BMesh *bm, float *dists, const float as MEM_freeN(dists_prev); } -void createTransUVs(bContext *C, TransInfo *t) +static void createTransUVs(bContext *C, TransInfo *t) { SpaceImage *sima = CTX_wm_space_image(C); Scene *scene = t->scene; @@ -446,7 +446,7 @@ static void flushTransUVs(TransInfo *t) } } -void recalcData_uv(TransInfo *t) +static void recalcData_uv(TransInfo *t) { SpaceImage *sima = t->area->spacedata.first; @@ -463,3 +463,10 @@ void recalcData_uv(TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_MeshUV = { + /* flags */ (T_EDIT | T_POINTS | T_2D_EDIT), + /* createTransData */ createTransUVs, + /* recalcData */ recalcData_uv, + /* special_aftertrans_update */ NULL, +}; diff --git a/source/blender/editors/transform/transform_convert_mesh_vert_cdata.c b/source/blender/editors/transform/transform_convert_mesh_vert_cdata.c index 104b45de138..f05688f3325 100644 --- a/source/blender/editors/transform/transform_convert_mesh_vert_cdata.c +++ b/source/blender/editors/transform/transform_convert_mesh_vert_cdata.c @@ -60,7 +60,7 @@ static void tc_mesh_cdata_transdata_create(TransDataBasic *td, td->extra = eve; } -void createTransMeshVertCData(TransInfo *t) +static void createTransMeshVertCData(bContext *UNUSED(C), TransInfo *t) { BLI_assert(ELEM(t->mode, TFM_BWEIGHT, TFM_VERT_CREASE)); FOREACH_TRANS_DATA_CONTAINER (t, tc) { @@ -269,7 +269,7 @@ static void tc_mesh_cdata_apply_to_mirror(TransInfo *t) } } -void recalcData_mesh_cdata(TransInfo *t) +static void recalcData_mesh_cdata(TransInfo *t) { bool is_canceling = t->state == TRANS_CANCEL; /* mirror modifier clipping? */ @@ -287,3 +287,10 @@ void recalcData_mesh_cdata(TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_MeshVertCData = { + /* flags */ (T_EDIT | T_POINTS), + /* createTransData */ createTransMeshVertCData, + /* recalcData */ recalcData_mesh_cdata, + /* special_aftertrans_update */ NULL, +}; diff --git a/source/blender/editors/transform/transform_convert_nla.c b/source/blender/editors/transform/transform_convert_nla.c index 32f70fc010b..cfa933d1600 100644 --- a/source/blender/editors/transform/transform_convert_nla.c +++ b/source/blender/editors/transform/transform_convert_nla.c @@ -59,7 +59,7 @@ typedef struct TransDataNla { /** \name NLA Transform Creation * \{ */ -void createTransNlaData(bContext *C, TransInfo *t) +static void createTransNlaData(bContext *C, TransInfo *t) { Scene *scene = t->scene; SpaceNla *snla = NULL; @@ -249,7 +249,7 @@ void createTransNlaData(bContext *C, TransInfo *t) ANIM_animdata_freelist(&anim_data); } -void recalcData_nla(TransInfo *t) +static void recalcData_nla(TransInfo *t) { SpaceNla *snla = (SpaceNla *)t->area->spacedata.first; @@ -464,7 +464,7 @@ void recalcData_nla(TransInfo *t) /** \name Special After Transform NLA * \{ */ -void special_aftertrans_update__nla(bContext *C, TransInfo *UNUSED(t)) +static void special_aftertrans_update__nla(bContext *C, TransInfo *UNUSED(t)) { bAnimContext ac; @@ -506,3 +506,10 @@ void special_aftertrans_update__nla(bContext *C, TransInfo *UNUSED(t)) } /** \} */ + +TransConvertTypeInfo TransConvertType_NLA = { + /* flags */ (T_POINTS | T_2D_EDIT), + /* createTransData */ createTransNlaData, + /* recalcData */ recalcData_nla, + /* special_aftertrans_update */ special_aftertrans_update__nla, +}; diff --git a/source/blender/editors/transform/transform_convert_node.c b/source/blender/editors/transform/transform_convert_node.c index 8281052c314..e18f75b71ae 100644 --- a/source/blender/editors/transform/transform_convert_node.c +++ b/source/blender/editors/transform/transform_convert_node.c @@ -89,7 +89,7 @@ static bool is_node_parent_select(bNode *node) return false; } -void createTransNodeData(TransInfo *t) +static void createTransNodeData(bContext *UNUSED(C), TransInfo *t) { const float dpi_fac = UI_DPI_FAC; SpaceNode *snode = t->area->spacedata.first; @@ -150,7 +150,7 @@ void createTransNodeData(TransInfo *t) /** \name Node Transform Creation * \{ */ -void flushTransNodes(TransInfo *t) +static void flushTransNodes(TransInfo *t) { const float dpi_fac = UI_DPI_FAC; @@ -220,7 +220,7 @@ void flushTransNodes(TransInfo *t) /** \name Special After Transform Node * \{ */ -void special_aftertrans_update__node(bContext *C, TransInfo *t) +static void special_aftertrans_update__node(bContext *C, TransInfo *t) { struct Main *bmain = CTX_data_main(C); const bool canceled = (t->state == TRANS_CANCEL); @@ -249,3 +249,10 @@ void special_aftertrans_update__node(bContext *C, TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_Node = { + /* flags */ (T_POINTS | T_2D_EDIT), + /* createTransData */ createTransNodeData, + /* recalcData */ flushTransNodes, + /* special_aftertrans_update */ special_aftertrans_update__node, +}; diff --git a/source/blender/editors/transform/transform_convert_object.c b/source/blender/editors/transform/transform_convert_object.c index 26d57ae1e58..be5f59c61c0 100644 --- a/source/blender/editors/transform/transform_convert_object.c +++ b/source/blender/editors/transform/transform_convert_object.c @@ -480,7 +480,7 @@ static void clear_trans_object_base_flags(TransInfo *t) } } -void createTransObject(bContext *C, TransInfo *t) +static void createTransObject(bContext *C, TransInfo *t) { Main *bmain = CTX_data_main(C); TransData *td = NULL; @@ -860,7 +860,7 @@ static bool motionpath_need_update_object(Scene *scene, Object *ob) /** \name Recalc Data object * \{ */ -void recalcData_objects(TransInfo *t) +static void recalcData_objects(TransInfo *t) { bool motionpath_update = false; @@ -918,7 +918,7 @@ void recalcData_objects(TransInfo *t) /** \name Special After Transform Object * \{ */ -void special_aftertrans_update__object(bContext *C, TransInfo *t) +static void special_aftertrans_update__object(bContext *C, TransInfo *t) { BLI_assert(t->options & CTX_OBJECT); @@ -991,3 +991,10 @@ void special_aftertrans_update__object(bContext *C, TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_Object = { + /* flags */ 0, + /* createTransData */ createTransObject, + /* recalcData */ recalcData_objects, + /* special_aftertrans_update */ special_aftertrans_update__object, +}; diff --git a/source/blender/editors/transform/transform_convert_object_texspace.c b/source/blender/editors/transform/transform_convert_object_texspace.c index e5a66f8a1d2..39bf22a9af9 100644 --- a/source/blender/editors/transform/transform_convert_object_texspace.c +++ b/source/blender/editors/transform/transform_convert_object_texspace.c @@ -29,7 +29,7 @@ * * \{ */ -void createTransTexspace(TransInfo *t) +static void createTransTexspace(bContext *UNUSED(C), TransInfo *t) { ViewLayer *view_layer = t->view_layer; TransData *td; @@ -86,7 +86,7 @@ void createTransTexspace(TransInfo *t) /** \name Recalc Data object * \{ */ -void recalcData_texspace(TransInfo *t) +static void recalcData_texspace(TransInfo *t) { if (t->state != TRANS_CANCEL) { @@ -106,3 +106,10 @@ void recalcData_texspace(TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_ObjectTexSpace = { + /* flags */ 0, + /* createTransData */ createTransTexspace, + /* recalcData */ recalcData_texspace, + /* special_aftertrans_update */ NULL, +}; diff --git a/source/blender/editors/transform/transform_convert_paintcurve.c b/source/blender/editors/transform/transform_convert_paintcurve.c index b2566016496..61b1cb9493b 100644 --- a/source/blender/editors/transform/transform_convert_paintcurve.c +++ b/source/blender/editors/transform/transform_convert_paintcurve.c @@ -108,7 +108,7 @@ static void PaintCurvePointToTransData(PaintCurvePoint *pcp, } } -void createTransPaintCurveVerts(bContext *C, TransInfo *t) +static void createTransPaintCurveVerts(bContext *C, TransInfo *t) { Paint *paint = BKE_paint_get_active_from_context(C); PaintCurve *pc; @@ -188,7 +188,7 @@ void createTransPaintCurveVerts(bContext *C, TransInfo *t) /** \name Paint Curve Transform Flush * \{ */ -void flushTransPaintCurve(TransInfo *t) +static void flushTransPaintCurve(TransInfo *t) { int i; @@ -204,3 +204,10 @@ void flushTransPaintCurve(TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_PaintCurve = { + /* flags */ (T_POINTS | T_2D_EDIT), + /* createTransData */ createTransPaintCurveVerts, + /* recalcData */ flushTransPaintCurve, + /* special_aftertrans_update */ NULL, +}; diff --git a/source/blender/editors/transform/transform_convert_particle.c b/source/blender/editors/transform/transform_convert_particle.c index 31e73288ad0..41f37b34af0 100644 --- a/source/blender/editors/transform/transform_convert_particle.c +++ b/source/blender/editors/transform/transform_convert_particle.c @@ -28,7 +28,7 @@ /** \name Particle Edit Transform Creation * \{ */ -void createTransParticleVerts(TransInfo *t) +static void createTransParticleVerts(bContext *UNUSED(C), TransInfo *t) { FOREACH_TRANS_DATA_CONTAINER (t, tc) { @@ -235,7 +235,7 @@ static void flushTransParticles(TransInfo *t) /** \name Recalc Transform Particles Data * \{ */ -void recalcData_particles(TransInfo *t) +static void recalcData_particles(TransInfo *t) { if (t->state != TRANS_CANCEL) { applySnappingIndividual(t); @@ -244,3 +244,10 @@ void recalcData_particles(TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_Particle = { + /* flags */ T_POINTS, + /* createTransData */ createTransParticleVerts, + /* recalcData */ recalcData_particles, + /* special_aftertrans_update */ NULL, +}; diff --git a/source/blender/editors/transform/transform_convert_sculpt.c b/source/blender/editors/transform/transform_convert_sculpt.c index 5bf6bfa8644..95958b816ab 100644 --- a/source/blender/editors/transform/transform_convert_sculpt.c +++ b/source/blender/editors/transform/transform_convert_sculpt.c @@ -23,7 +23,7 @@ /** \name Sculpt Transform Creation * \{ */ -void createTransSculpt(bContext *C, TransInfo *t) +static void createTransSculpt(bContext *C, TransInfo *t) { TransData *td; @@ -94,13 +94,13 @@ void createTransSculpt(bContext *C, TransInfo *t) /** \name Recalc Data object * \{ */ -void recalcData_sculpt(TransInfo *t) +static void recalcData_sculpt(TransInfo *t) { Object *ob = OBACT(t->view_layer); ED_sculpt_update_modal_transform(t->context, ob); } -void special_aftertrans_update__sculpt(bContext *C, TransInfo *t) +static void special_aftertrans_update__sculpt(bContext *C, TransInfo *t) { Scene *scene = t->scene; if (!BKE_id_is_editable(CTX_data_main(C), &scene->id)) { @@ -114,3 +114,10 @@ void special_aftertrans_update__sculpt(bContext *C, TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_Sculpt = { + /* flags */ 0, + /* createTransData */ createTransSculpt, + /* recalcData */ recalcData_sculpt, + /* special_aftertrans_update */ special_aftertrans_update__sculpt, +}; diff --git a/source/blender/editors/transform/transform_convert_sequencer.c b/source/blender/editors/transform/transform_convert_sequencer.c index dcc1739606f..eefc9d0cc2a 100644 --- a/source/blender/editors/transform/transform_convert_sequencer.c +++ b/source/blender/editors/transform/transform_convert_sequencer.c @@ -465,7 +465,7 @@ static SeqCollection *query_time_dependent_strips_strips(TransInfo *t) return dependent; } -void createTransSeqData(TransInfo *t) +static void createTransSeqData(bContext *UNUSED(C), TransInfo *t) { Scene *scene = t->scene; Editing *ed = SEQ_editing_get(t->scene); @@ -659,7 +659,7 @@ static void flushTransSeq(TransInfo *t) SEQ_collection_free(transformed_strips); } -void recalcData_sequencer(TransInfo *t) +static void recalcData_sequencer(TransInfo *t) { TransData *td; int a; @@ -689,7 +689,7 @@ void recalcData_sequencer(TransInfo *t) /** \name Special After Transform Sequencer * \{ */ -void special_aftertrans_update__sequencer(bContext *UNUSED(C), TransInfo *t) +static void special_aftertrans_update__sequencer(bContext *UNUSED(C), TransInfo *t) { if (t->state == TRANS_CANCEL) { return; @@ -734,3 +734,10 @@ void transform_convert_sequencer_channel_clamp(TransInfo *t, float r_val[2]) } /** \} */ + +TransConvertTypeInfo TransConvertType_Sequencer = { + /* flags */ (T_POINTS | T_2D_EDIT), + /* createTransData */ createTransSeqData, + /* recalcData */ recalcData_sequencer, + /* special_aftertrans_update */ special_aftertrans_update__sequencer, +}; diff --git a/source/blender/editors/transform/transform_convert_sequencer_image.c b/source/blender/editors/transform/transform_convert_sequencer_image.c index 8be454b540c..3d0c3ddaa4c 100644 --- a/source/blender/editors/transform/transform_convert_sequencer_image.c +++ b/source/blender/editors/transform/transform_convert_sequencer_image.c @@ -105,7 +105,7 @@ static void freeSeqData(TransInfo *UNUSED(t), MEM_freeN(td->extra); } -void createTransSeqImageData(TransInfo *t) +static void createTransSeqImageData(bContext *UNUSED(C), TransInfo *t) { Editing *ed = SEQ_editing_get(t->scene); const SpaceSeq *sseq = t->area->spacedata.first; @@ -191,7 +191,7 @@ static bool autokeyframe_sequencer_image(bContext *C, return changed; } -void recalcData_sequencer_image(TransInfo *t) +static void recalcData_sequencer_image(TransInfo *t) { TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); TransData *td = NULL; @@ -247,7 +247,7 @@ void recalcData_sequencer_image(TransInfo *t) } } -void special_aftertrans_update__sequencer_image(bContext *UNUSED(C), TransInfo *t) +static void special_aftertrans_update__sequencer_image(bContext *UNUSED(C), TransInfo *t) { TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); @@ -271,3 +271,10 @@ void special_aftertrans_update__sequencer_image(bContext *UNUSED(C), TransInfo * } } } + +TransConvertTypeInfo TransConvertType_SequencerImage = { + /* flags */ (T_POINTS | T_2D_EDIT), + /* createTransData */ createTransSeqImageData, + /* recalcData */ recalcData_sequencer_image, + /* special_aftertrans_update */ special_aftertrans_update__sequencer_image, +}; diff --git a/source/blender/editors/transform/transform_convert_tracking.c b/source/blender/editors/transform/transform_convert_tracking.c index d447cd71a40..c0c660289a5 100644 --- a/source/blender/editors/transform/transform_convert_tracking.c +++ b/source/blender/editors/transform/transform_convert_tracking.c @@ -518,7 +518,7 @@ static void createTransTrackingCurvesData(bContext *C, TransInfo *t) } } -void createTransTrackingData(bContext *C, TransInfo *t) +static void createTransTrackingData(bContext *C, TransInfo *t) { ARegion *region = CTX_wm_region(C); SpaceClip *sc = CTX_wm_space_clip(C); @@ -694,7 +694,7 @@ static void flushTransTracking(TransInfo *t) } } -void recalcData_tracking(TransInfo *t) +static void recalcData_tracking(TransInfo *t) { SpaceClip *sc = t->area->spacedata.first; @@ -747,7 +747,7 @@ void recalcData_tracking(TransInfo *t) /** \name Special After Transform Tracking * \{ */ -void special_aftertrans_update__movieclip(bContext *C, TransInfo *t) +static void special_aftertrans_update__movieclip(bContext *C, TransInfo *t) { SpaceClip *sc = t->area->spacedata.first; MovieClip *clip = ED_space_clip_get_clip(sc); @@ -790,3 +790,10 @@ void special_aftertrans_update__movieclip(bContext *C, TransInfo *t) } /** \} */ + +TransConvertTypeInfo TransConvertType_Tracking = { + /* flags */ (T_POINTS | T_2D_EDIT), + /* createTransData */ createTransTrackingData, + /* recalcData */ recalcData_tracking, + /* special_aftertrans_update */ special_aftertrans_update__movieclip, +}; diff --git a/source/blender/editors/transform/transform_mode.c b/source/blender/editors/transform/transform_mode.c index 356828f513f..d8da7a11d28 100644 --- a/source/blender/editors/transform/transform_mode.c +++ b/source/blender/editors/transform/transform_mode.c @@ -944,7 +944,11 @@ void ElementResize(const TransInfo *t, if (td->ext && td->ext->size) { float fsize[3]; - if (ELEM(t->data_type, TC_SCULPT, TC_OBJECT, TC_OBJECT_TEXSPACE, TC_POSE)) { + if (ELEM(t->data_type, + &TransConvertType_Sculpt, + &TransConvertType_Object, + &TransConvertType_ObjectTexSpace, + &TransConvertType_Pose)) { float obsizemat[3][3]; /* Reorient the size mat to fit the oriented object. */ mul_m3_m3m3(obsizemat, tmat, td->axismtx); @@ -1204,7 +1208,7 @@ void transform_mode_init(TransInfo *t, wmOperator *op, const int mode) break; } - if (t->data_type == TC_MESH_VERTS) { + if (t->data_type == &TransConvertType_Mesh) { /* Init Custom Data correction. * Ideally this should be called when creating the TransData. */ transform_convert_mesh_customdatacorrect_init(t); diff --git a/source/blender/editors/transform/transform_snap.c b/source/blender/editors/transform/transform_snap.c index 22d062a71dc..9a563aaf473 100644 --- a/source/blender/editors/transform/transform_snap.c +++ b/source/blender/editors/transform/transform_snap.c @@ -813,7 +813,7 @@ static void initSnappingMode(TransInfo *t) t->tsnap.use_backface_culling = snap_use_backface_culling(t); t->tsnap.object_context = ED_transform_snap_object_context_create(t->scene, 0); - if (t->data_type == TC_MESH_VERTS) { + if (t->data_type == &TransConvertType_Mesh) { /* Ignore elements being transformed. */ ED_transform_snap_object_context_set_editmesh_callbacks( t->tsnap.object_context, @@ -1252,7 +1252,7 @@ static void snap_target_grid_ensure(TransInfo *t) { /* Only need to calculate once. */ if ((t->tsnap.status & TARGET_GRID_INIT) == 0) { - if (t->data_type == TC_CURSOR_VIEW3D) { + if (t->data_type == &TransConvertType_Cursor3D) { /* Use a fallback when transforming the cursor. * In this case the center is _not_ derived from the cursor which is being transformed. */ copy_v3_v3(t->tsnap.snapTargetGrid, TRANS_DATA_CONTAINER_FIRST_SINGLE(t)->data->iloc); diff --git a/source/blender/editors/transform/transform_snap_sequencer.c b/source/blender/editors/transform/transform_snap_sequencer.c index 5ac526b1e91..06d9bb05206 100644 --- a/source/blender/editors/transform/transform_snap_sequencer.c +++ b/source/blender/editors/transform/transform_snap_sequencer.c @@ -28,6 +28,7 @@ #include "SEQ_time.h" #include "transform.h" +#include "transform_convert.h" #include "transform_snap.h" typedef struct TransSeqSnapData { @@ -244,7 +245,7 @@ static int seq_snap_threshold_get_frame_distance(const TransInfo *t) TransSeqSnapData *transform_snap_sequencer_data_alloc(const TransInfo *t) { - if (t->data_type == TC_SEQ_IMAGE_DATA) { + if (t->data_type == &TransConvertType_SequencerImage) { return NULL; } @@ -375,7 +376,7 @@ bool ED_transform_snap_sequencer_to_closest_strip_calc(Scene *scene, t.scene = scene; t.region = region; t.values[0] = 0; - t.data_type = TC_SEQ_DATA; + t.data_type = &TransConvertType_Sequencer; t.tsnap.mode = SEQ_tool_settings_snap_mode_get(scene); *r_snap_distance = transform_snap_sequencer_to_closest_strip_ex(&t, frame_1, frame_2); -- cgit v1.2.3 From 1f94b56d774440d08eb92f2a7a47b9a6a7aa7b84 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Fri, 22 Jul 2022 15:39:41 +0200 Subject: Curves: support sculpting on deformed curves Previously, curves sculpt tools only worked on original data. This was very limiting, because one could effectively only sculpt the curves when all procedural effects were turned off. This patch adds support for curves sculpting while looking the result of procedural effects (like deformation based on the surface mesh). This functionality is also known as "crazy space" support in Blender. For more details see D15407. Differential Revision: https://developer.blender.org/D15407 --- source/blender/blenkernel/BKE_crazyspace.hh | 53 +++ source/blender/blenkernel/BKE_curves.hh | 33 ++ source/blender/blenkernel/BKE_geometry_set.h | 3 +- source/blender/blenkernel/BKE_geometry_set.hh | 51 ++- source/blender/blenkernel/BKE_mesh_sample.hh | 11 + source/blender/blenkernel/CMakeLists.txt | 2 + source/blender/blenkernel/intern/crazyspace.cc | 64 +++ source/blender/blenkernel/intern/curves.cc | 24 ++ .../intern/geometry_component_edit_data.cc | 58 +++ source/blender/blenkernel/intern/geometry_set.cc | 35 ++ source/blender/blenlib/BLI_kdopbvh.h | 23 + .../editors/sculpt_paint/curves_sculpt_add.cc | 259 ++++++------ .../editors/sculpt_paint/curves_sculpt_brush.cc | 44 +- .../editors/sculpt_paint/curves_sculpt_comb.cc | 107 +++-- .../editors/sculpt_paint/curves_sculpt_delete.cc | 39 +- .../editors/sculpt_paint/curves_sculpt_density.cc | 408 ++++++++++-------- .../sculpt_paint/curves_sculpt_grow_shrink.cc | 14 +- .../editors/sculpt_paint/curves_sculpt_intern.hh | 10 +- .../editors/sculpt_paint/curves_sculpt_ops.cc | 51 ++- .../editors/sculpt_paint/curves_sculpt_pinch.cc | 46 +- .../editors/sculpt_paint/curves_sculpt_puff.cc | 39 +- .../sculpt_paint/curves_sculpt_selection_paint.cc | 25 +- .../editors/sculpt_paint/curves_sculpt_slide.cc | 467 ++++++++++++++------- .../editors/sculpt_paint/curves_sculpt_smooth.cc | 106 ++--- .../sculpt_paint/curves_sculpt_snake_hook.cc | 46 +- source/blender/editors/space_node/node_draw.cc | 16 + source/blender/geometry/GEO_add_curves_on_mesh.hh | 20 +- .../blender/geometry/intern/add_curves_on_mesh.cc | 104 ++--- .../blender/geometry/intern/realize_instances.cc | 17 +- source/blender/nodes/NOD_geometry_exec.hh | 4 + .../blender/nodes/NOD_geometry_nodes_eval_log.hh | 5 + .../nodes/geometry/nodes/node_geo_bounding_box.cc | 4 +- .../nodes/geometry/nodes/node_geo_convex_hull.cc | 2 +- .../geometry/nodes/node_geo_curve_resample.cc | 2 + .../nodes/geometry/nodes/node_geo_curve_to_mesh.cc | 5 +- .../geometry/nodes/node_geo_curve_to_points.cc | 8 +- .../nodes/geometry/nodes/node_geo_curve_trim.cc | 1 + .../nodes/node_geo_deform_curves_on_surface.cc | 88 +++- .../nodes/node_geo_distribute_points_on_faces.cc | 2 +- .../geometry/nodes/node_geo_duplicate_elements.cc | 13 +- .../geometry/nodes/node_geo_instance_on_points.cc | 2 +- .../geometry/nodes/node_geo_instances_to_points.cc | 2 +- .../nodes/geometry/nodes/node_geo_mesh_to_curve.cc | 6 +- .../geometry/nodes/node_geo_mesh_to_points.cc | 6 +- .../geometry/nodes/node_geo_mesh_to_volume.cc | 2 +- .../geometry/nodes/node_geo_points_to_vertices.cc | 6 +- .../geometry/nodes/node_geo_points_to_volume.cc | 2 +- .../nodes/geometry/nodes/node_geo_transform.cc | 34 ++ .../geometry/nodes/node_geo_volume_to_mesh.cc | 2 +- .../nodes/intern/geometry_nodes_eval_log.cc | 12 + source/blender/nodes/intern/node_geometry_exec.cc | 17 + 51 files changed, 1644 insertions(+), 756 deletions(-) create mode 100644 source/blender/blenkernel/BKE_crazyspace.hh create mode 100644 source/blender/blenkernel/intern/geometry_component_edit_data.cc diff --git a/source/blender/blenkernel/BKE_crazyspace.hh b/source/blender/blenkernel/BKE_crazyspace.hh new file mode 100644 index 00000000000..adebf0b7884 --- /dev/null +++ b/source/blender/blenkernel/BKE_crazyspace.hh @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup bke + */ + +#pragma once + +#include "BLI_float3x3.hh" +#include "BLI_math_vec_types.hh" +#include "BLI_span.hh" + +struct Depsgraph; +struct Object; + +namespace blender::bke::crazyspace { + +/** + * Contains information about how points have been deformed during evaluation. + * This allows mapping edits on evaluated data back to original data in some cases. + */ +struct GeometryDeformation { + /** + * Positions of the deformed points. This may also point to the original position if no + * deformation data is available. + */ + Span positions; + /** + * Matrices that transform point translations on original data into corresponding translations in + * evaluated data. This may be empty if not available. + */ + Span deform_mats; + + float3 translation_from_deformed_to_original(const int position_i, + const float3 &translation) const + { + if (this->deform_mats.is_empty()) { + return translation; + } + const float3x3 &deform_mat = this->deform_mats[position_i]; + return deform_mat.inverted() * translation; + } +}; + +/** + * During evaluation of the object, deformation data may have been generated for this object. This + * function either retrieves the deformation data from the evaluated object, or falls back to + * returning the original data. + */ +GeometryDeformation get_evaluated_curves_deformation(const Depsgraph &depsgraph, + const Object &ob_orig); + +} // namespace blender::bke::crazyspace diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh index 68c90a45031..568899721a9 100644 --- a/source/blender/blenkernel/BKE_curves.hh +++ b/source/blender/blenkernel/BKE_curves.hh @@ -11,6 +11,7 @@ #include +#include "BLI_float3x3.hh" #include "BLI_float4x4.hh" #include "BLI_generic_virtual_array.hh" #include "BLI_index_mask.hh" @@ -414,6 +415,38 @@ class CurvesGeometry : public ::CurvesGeometry { } }; +/** + * Used to propagate deformation data through modifier evaluation so that sculpt tools can work on + * evaluated data. + */ +class CurvesEditHints { + public: + /** + * Original data that the edit hints below are meant to be used for. + */ + const Curves &curves_id_orig; + /** + * Evaluated positions for the points in #curves_orig. If this is empty, the positions from the + * evaluated #Curves should be used if possible. + */ + std::optional> positions; + /** + * Matrices which transform point movement vectors from original data to corresponding movements + * of evaluated data. + */ + std::optional> deform_mats; + + CurvesEditHints(const Curves &curves_id_orig) : curves_id_orig(curves_id_orig) + { + } + + /** + * The edit hints have to correspond to the original curves, i.e. the number of deformed points + * is the same as the number of original points. + */ + bool is_valid() const; +}; + namespace curves { /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/BKE_geometry_set.h b/source/blender/blenkernel/BKE_geometry_set.h index a28e9e6bdf6..97e69f3fe1f 100644 --- a/source/blender/blenkernel/BKE_geometry_set.h +++ b/source/blender/blenkernel/BKE_geometry_set.h @@ -23,9 +23,10 @@ typedef enum GeometryComponentType { GEO_COMPONENT_TYPE_INSTANCES = 2, GEO_COMPONENT_TYPE_VOLUME = 3, GEO_COMPONENT_TYPE_CURVE = 4, + GEO_COMPONENT_TYPE_EDIT = 5, } GeometryComponentType; -#define GEO_COMPONENT_TYPE_ENUM_SIZE 5 +#define GEO_COMPONENT_TYPE_ENUM_SIZE 6 void BKE_geometry_set_free(struct GeometrySet *geometry_set); diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index 4108e2f7e2e..be2ec3e3dca 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -43,7 +43,8 @@ enum class GeometryOwnershipType { namespace blender::bke { class ComponentAttributeProviders; -} +class CurvesEditHints; +} // namespace blender::bke class GeometryComponent; @@ -168,6 +169,12 @@ struct GeometrySet { * Remove all geometry components with types that are not in the provided list. */ void keep_only(const blender::Span component_types); + /** + * Keeps the provided geometry types, but also instances and edit data. + * Instances must not be removed while using #modify_geometry_sets. + */ + void keep_only_during_modify(const blender::Span component_types); + void remove_geometry_during_modify(); void add(const GeometryComponent &component); @@ -287,6 +294,10 @@ struct GeometrySet { * Returns a read-only curves data-block or null. */ const Curves *get_curves_for_read() const; + /** + * Returns read-only curve edit hints or null. + */ + const blender::bke::CurvesEditHints *get_curve_edit_hints_for_read() const; /** * Returns a mutable mesh or null. No ownership is transferred. @@ -304,6 +315,10 @@ struct GeometrySet { * Returns a mutable curves data-block or null. No ownership is transferred. */ Curves *get_curves_for_write(); + /** + * Returns mutable curve edit hints or null. + */ + blender::bke::CurvesEditHints *get_curve_edit_hints_for_write(); /* Utility methods for replacement. */ /** @@ -825,3 +840,37 @@ class VolumeComponent : public GeometryComponent { static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_VOLUME; }; + +/** + * When the original data is in some edit mode, we want to propagate some additional information + * through object evaluation. This information can be used by edit modes to support working on + * evaluated data. + * + * This component is added at the beginning of modifier evaluation. + */ +class GeometryComponentEditData final : public GeometryComponent { + public: + /** + * Information about how original curves are manipulated during evaluation. This data is used so + * that curve sculpt tools can work on evaluated data. It is not stored in #CurveComponent + * because the data remains valid even when there is no actual curves geometry anymore, for + * example, when the curves have been converted to a mesh. + */ + std::unique_ptr curves_edit_hints_; + + GeometryComponentEditData(); + + GeometryComponent *copy() const final; + bool owns_direct_data() const final; + void ensure_owns_direct_data() final; + + /** + * The first node that does topology changing operations on curves should store the curve point + * positions it retrieved as input. Without this, information about the deformed positions is + * lost, which would make curves sculpt mode fall back to using original curve positions instead + * of deformed ones. + */ + static void remember_deformed_curve_positions_if_necessary(GeometrySet &geometry); + + static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_EDIT; +}; diff --git a/source/blender/blenkernel/BKE_mesh_sample.hh b/source/blender/blenkernel/BKE_mesh_sample.hh index 356709d8942..0b7d1a1835f 100644 --- a/source/blender/blenkernel/BKE_mesh_sample.hh +++ b/source/blender/blenkernel/BKE_mesh_sample.hh @@ -137,4 +137,15 @@ float3 compute_bary_coord_in_triangle(const Mesh &mesh, const MLoopTri &looptri, const float3 &position); +template +inline T sample_corner_attrribute_with_bary_coords(const float3 &bary_weights, + const MLoopTri &looptri, + const Span corner_attribute) +{ + return attribute_math::mix3(bary_weights, + corner_attribute[looptri.tri[0]], + corner_attribute[looptri.tri[1]], + corner_attribute[looptri.tri[2]]); +} + } // namespace blender::bke::mesh_surface_sample diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index df4b70d4fe6..2ac42037198 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -139,6 +139,7 @@ set(SRC intern/freestyle.c intern/geometry_component_curve.cc intern/geometry_component_curves.cc + intern/geometry_component_edit_data.cc intern/geometry_component_instances.cc intern/geometry_component_mesh.cc intern/geometry_component_pointcloud.cc @@ -353,6 +354,7 @@ set(SRC BKE_constraint.h BKE_context.h BKE_crazyspace.h + BKE_crazyspace.hh BKE_cryptomatte.h BKE_cryptomatte.hh BKE_curve.h diff --git a/source/blender/blenkernel/intern/crazyspace.cc b/source/blender/blenkernel/intern/crazyspace.cc index c3db3095343..978606ef1fa 100644 --- a/source/blender/blenkernel/intern/crazyspace.cc +++ b/source/blender/blenkernel/intern/crazyspace.cc @@ -19,7 +19,10 @@ #include "BKE_DerivedMesh.h" #include "BKE_crazyspace.h" +#include "BKE_crazyspace.hh" +#include "BKE_curves.hh" #include "BKE_editmesh.h" +#include "BKE_geometry_set.hh" #include "BKE_lib_id.h" #include "BKE_mesh.h" #include "BKE_mesh_wrapper.h" @@ -586,3 +589,64 @@ void BKE_crazyspace_api_eval_clear(Object *object) } /** \} */ + +namespace blender::bke::crazyspace { + +GeometryDeformation get_evaluated_curves_deformation(const Depsgraph &depsgraph, + const Object &ob_orig) +{ + BLI_assert(ob_orig.type == OB_CURVES); + const Curves &curves_id_orig = *static_cast(ob_orig.data); + const CurvesGeometry &curves_orig = CurvesGeometry::wrap(curves_id_orig.geometry); + const int points_num = curves_orig.points_num(); + + GeometryDeformation deformation; + /* Use the undeformed positions by default. */ + deformation.positions = curves_orig.positions(); + + const Object *ob_eval = DEG_get_evaluated_object(&depsgraph, const_cast(&ob_orig)); + if (ob_eval == nullptr) { + return deformation; + } + const GeometrySet *geometry_eval = ob_eval->runtime.geometry_set_eval; + if (geometry_eval == nullptr) { + return deformation; + } + + /* If available, use deformation information generated during evaluation. */ + const GeometryComponentEditData *edit_component_eval = + geometry_eval->get_component_for_read(); + bool uses_extra_positions = false; + if (edit_component_eval != nullptr) { + const CurvesEditHints *edit_hints = edit_component_eval->curves_edit_hints_.get(); + if (edit_hints != nullptr && &edit_hints->curves_id_orig == &curves_id_orig) { + if (edit_hints->positions.has_value()) { + BLI_assert(edit_hints->positions->size() == points_num); + deformation.positions = *edit_hints->positions; + uses_extra_positions = true; + } + if (edit_hints->deform_mats.has_value()) { + BLI_assert(edit_hints->deform_mats->size() == points_num); + deformation.deform_mats = *edit_hints->deform_mats; + } + } + } + + /* Use the positions of the evaluated curves directly, if the number of points matches. */ + if (!uses_extra_positions) { + const CurveComponent *curves_component_eval = + geometry_eval->get_component_for_read(); + if (curves_component_eval != nullptr) { + const Curves *curves_id_eval = curves_component_eval->get_for_read(); + if (curves_id_eval != nullptr) { + const CurvesGeometry &curves_eval = CurvesGeometry::wrap(curves_id_eval->geometry); + if (curves_eval.points_num() == points_num) { + deformation.positions = curves_eval.positions(); + } + } + } + } + return deformation; +} + +} // namespace blender::bke::crazyspace diff --git a/source/blender/blenkernel/intern/curves.cc b/source/blender/blenkernel/intern/curves.cc index 5684a2e5b07..6554f42d3dd 100644 --- a/source/blender/blenkernel/intern/curves.cc +++ b/source/blender/blenkernel/intern/curves.cc @@ -319,6 +319,14 @@ void BKE_curves_data_update(struct Depsgraph *depsgraph, struct Scene *scene, Ob Curves *curves = static_cast(object->data); GeometrySet geometry_set = GeometrySet::create_with_curves(curves, GeometryOwnershipType::ReadOnly); + if (object->mode == OB_MODE_SCULPT_CURVES) { + /* Try to propagate deformation data through modifier evaluation, so that sculpt mode can work + * on evaluated curves. */ + GeometryComponentEditData &edit_component = + geometry_set.get_component_for_write(); + edit_component.curves_edit_hints_ = std::make_unique( + *static_cast(DEG_get_original_object(object)->data)); + } curves_evaluate_modifiers(depsgraph, scene, object, geometry_set); /* Assign evaluated object. */ @@ -409,4 +417,20 @@ CurvesSurfaceTransforms::CurvesSurfaceTransforms(const Object &curves_ob, const } } +bool CurvesEditHints::is_valid() const +{ + const int point_num = this->curves_id_orig.geometry.point_num; + if (this->positions.has_value()) { + if (this->positions->size() != point_num) { + return false; + } + } + if (this->deform_mats.has_value()) { + if (this->deform_mats->size() != point_num) { + return false; + } + } + return true; +} + } // namespace blender::bke diff --git a/source/blender/blenkernel/intern/geometry_component_edit_data.cc b/source/blender/blenkernel/intern/geometry_component_edit_data.cc new file mode 100644 index 00000000000..2c00de3254f --- /dev/null +++ b/source/blender/blenkernel/intern/geometry_component_edit_data.cc @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_curves.hh" +#include "BKE_geometry_set.hh" + +using namespace blender; +using namespace blender::bke; + +GeometryComponentEditData::GeometryComponentEditData() : GeometryComponent(GEO_COMPONENT_TYPE_EDIT) +{ +} + +GeometryComponent *GeometryComponentEditData::copy() const +{ + GeometryComponentEditData *new_component = new GeometryComponentEditData(); + if (curves_edit_hints_) { + new_component->curves_edit_hints_ = std::make_unique(*curves_edit_hints_); + } + return new_component; +} + +bool GeometryComponentEditData::owns_direct_data() const +{ + return true; +} + +void GeometryComponentEditData::ensure_owns_direct_data() +{ + /* Nothing to do. */ +} + +void GeometryComponentEditData::remember_deformed_curve_positions_if_necessary( + GeometrySet &geometry) +{ + /* This component should be created at the start of object evaluation if it's necessary. */ + if (!geometry.has()) { + return; + } + GeometryComponentEditData &edit_component = + geometry.get_component_for_write(); + if (!edit_component.curves_edit_hints_) { + return; + } + if (edit_component.curves_edit_hints_->positions.has_value()) { + return; + } + const Curves *curves_id = geometry.get_curves_for_read(); + if (curves_id == nullptr) { + return; + } + const bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id->geometry); + const int points_num = curves.points_num(); + if (points_num != edit_component.curves_edit_hints_->curves_id_orig.geometry.point_num) { + return; + } + edit_component.curves_edit_hints_->positions.emplace(points_num); + edit_component.curves_edit_hints_->positions->as_mutable_span().copy_from(curves.positions()); +} diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc index c6fe8eebc7f..1a0ce4f0893 100644 --- a/source/blender/blenkernel/intern/geometry_set.cc +++ b/source/blender/blenkernel/intern/geometry_set.cc @@ -53,6 +53,8 @@ GeometryComponent *GeometryComponent::create(GeometryComponentType component_typ return new VolumeComponent(); case GEO_COMPONENT_TYPE_CURVE: return new CurveComponent(); + case GEO_COMPONENT_TYPE_EDIT: + return new GeometryComponentEditData(); } BLI_assert_unreachable(); return nullptr; @@ -175,6 +177,20 @@ void GeometrySet::keep_only(const blender::Span component } } +void GeometrySet::keep_only_during_modify( + const blender::Span component_types) +{ + Vector extended_types = component_types; + extended_types.append_non_duplicates(GEO_COMPONENT_TYPE_INSTANCES); + extended_types.append_non_duplicates(GEO_COMPONENT_TYPE_EDIT); + this->keep_only(extended_types); +} + +void GeometrySet::remove_geometry_during_modify() +{ + this->keep_only_during_modify({}); +} + void GeometrySet::add(const GeometryComponent &component) { BLI_assert(!components_[component.type()]); @@ -290,6 +306,13 @@ const Curves *GeometrySet::get_curves_for_read() const return (component == nullptr) ? nullptr : component->get_for_read(); } +const blender::bke::CurvesEditHints *GeometrySet::get_curve_edit_hints_for_read() const +{ + const GeometryComponentEditData *component = + this->get_component_for_read(); + return (component == nullptr) ? nullptr : component->curves_edit_hints_.get(); +} + bool GeometrySet::has_pointcloud() const { const PointCloudComponent *component = this->get_component_for_read(); @@ -453,6 +476,16 @@ Curves *GeometrySet::get_curves_for_write() return component == nullptr ? nullptr : component->get_for_write(); } +blender::bke::CurvesEditHints *GeometrySet::get_curve_edit_hints_for_write() +{ + if (!this->has()) { + return nullptr; + } + GeometryComponentEditData &component = + this->get_component_for_write(); + return component.curves_edit_hints_.get(); +} + void GeometrySet::attribute_foreach(const Span component_types, const bool include_instances, const AttributeForeachCallback callback) const @@ -679,6 +712,8 @@ bool BKE_object_has_geometry_set_instances(const Object *ob) case GEO_COMPONENT_TYPE_CURVE: is_instance = !ELEM(ob->type, OB_CURVES_LEGACY, OB_FONT); break; + case GEO_COMPONENT_TYPE_EDIT: + break; } if (is_instance) { return true; diff --git a/source/blender/blenlib/BLI_kdopbvh.h b/source/blender/blenlib/BLI_kdopbvh.h index 8642d728909..17759b6a8ac 100644 --- a/source/blender/blenlib/BLI_kdopbvh.h +++ b/source/blender/blenlib/BLI_kdopbvh.h @@ -332,6 +332,29 @@ extern const float bvhtree_kdop_axes[13][3]; namespace blender { +using BVHTree_RayCastCallback_CPP = + FunctionRef; + +inline void BLI_bvhtree_ray_cast_all_cpp(BVHTree &tree, + const float3 co, + const float3 dir, + float radius, + float hit_dist, + BVHTree_RayCastCallback_CPP fn) +{ + BLI_bvhtree_ray_cast_all( + &tree, + co, + dir, + radius, + hit_dist, + [](void *userdata, int index, const BVHTreeRay *ray, BVHTreeRayHit *hit) { + BVHTree_RayCastCallback_CPP fn = *static_cast(userdata); + fn(index, *ray, *hit); + }, + &fn); +} + using BVHTree_RangeQuery_CPP = FunctionRef; inline void BLI_bvhtree_range_query_cpp(BVHTree &tree, diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc index 26145a386f5..2757701675b 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc @@ -23,6 +23,8 @@ #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" #include "BKE_mesh_sample.hh" +#include "BKE_modifier.h" +#include "BKE_object.h" #include "BKE_paint.h" #include "BKE_report.h" @@ -42,6 +44,8 @@ #include "WM_api.h" +#include "DEG_depsgraph_query.h" + /** * The code below uses a suffix naming convention to indicate the coordinate space: * cu: Local space of the curves object that is being edited. @@ -80,13 +84,15 @@ struct AddOperationExecutor { AddOperation *self_ = nullptr; CurvesSculptCommonContext ctx_; - Object *object_ = nullptr; - Curves *curves_id_ = nullptr; - CurvesGeometry *curves_ = nullptr; + Object *curves_ob_orig_ = nullptr; + Curves *curves_id_orig_ = nullptr; + CurvesGeometry *curves_orig_ = nullptr; - Object *surface_ob_ = nullptr; - Mesh *surface_ = nullptr; - Span surface_looptris_; + Object *surface_ob_eval_ = nullptr; + Mesh *surface_eval_ = nullptr; + Span surface_looptris_eval_; + VArraySpan surface_uv_map_eval_; + BVHTreeFromMesh surface_bvh_eval_; const CurvesSculpt *curves_sculpt_ = nullptr; const Brush *brush_ = nullptr; @@ -99,14 +105,6 @@ struct AddOperationExecutor { CurvesSurfaceTransforms transforms_; - BVHTreeFromMesh surface_bvh_; - - struct AddedPoints { - Vector positions_cu; - Vector bary_coords; - Vector looptri_indices; - }; - AddOperationExecutor(const bContext &C) : ctx_(C) { } @@ -114,19 +112,26 @@ struct AddOperationExecutor { void execute(AddOperation &self, const bContext &C, const StrokeExtension &stroke_extension) { self_ = &self; - object_ = CTX_data_active_object(&C); + curves_ob_orig_ = CTX_data_active_object(&C); - curves_id_ = static_cast(object_->data); - curves_ = &CurvesGeometry::wrap(curves_id_->geometry); + curves_id_orig_ = static_cast(curves_ob_orig_->data); + curves_orig_ = &CurvesGeometry::wrap(curves_id_orig_->geometry); - if (curves_id_->surface == nullptr || curves_id_->surface->type != OB_MESH) { + if (curves_id_orig_->surface == nullptr || curves_id_orig_->surface->type != OB_MESH) { + report_missing_surface(stroke_extension.reports); return; } - transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface); + transforms_ = CurvesSurfaceTransforms(*curves_ob_orig_, curves_id_orig_->surface); - surface_ob_ = curves_id_->surface; - surface_ = static_cast(surface_ob_->data); + Object &surface_ob_orig = *curves_id_orig_->surface; + Mesh &surface_orig = *static_cast(surface_ob_orig.data); + + surface_ob_eval_ = DEG_get_evaluated_object(ctx_.depsgraph, &surface_ob_orig); + if (surface_ob_eval_ == nullptr) { + return; + } + surface_eval_ = BKE_object_get_evaluated_mesh(surface_ob_eval_); curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt; brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint); @@ -143,56 +148,70 @@ struct AddOperationExecutor { return; } + /* Find UV map. */ + VArraySpan surface_uv_map; + if (curves_id_orig_->surface_uv_map != nullptr) { + surface_uv_map = bke::mesh_attributes(surface_orig) + .lookup(curves_id_orig_->surface_uv_map, ATTR_DOMAIN_CORNER); + surface_uv_map_eval_ = bke::mesh_attributes(*surface_eval_) + .lookup(curves_id_orig_->surface_uv_map, + ATTR_DOMAIN_CORNER); + } + + if (surface_uv_map.is_empty()) { + report_missing_uv_map_on_original_surface(stroke_extension.reports); + return; + } + if (surface_uv_map_eval_.is_empty()) { + report_missing_uv_map_on_evaluated_surface(stroke_extension.reports); + return; + } + const double time = PIL_check_seconds_timer() * 1000000.0; /* Use a pointer cast to avoid overflow warnings. */ RandomNumberGenerator rng{*(uint32_t *)(&time)}; - BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2); - BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); }); + BKE_bvhtree_from_mesh_get(&surface_bvh_eval_, surface_eval_, BVHTREE_FROM_LOOPTRI, 2); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_eval_); }); - surface_looptris_ = {BKE_mesh_runtime_looptri_ensure(surface_), - BKE_mesh_runtime_looptri_len(surface_)}; + surface_looptris_eval_ = {BKE_mesh_runtime_looptri_ensure(surface_eval_), + BKE_mesh_runtime_looptri_len(surface_eval_)}; /* Sample points on the surface using one of multiple strategies. */ - AddedPoints added_points; + Vector sampled_uvs; if (add_amount_ == 1) { - this->sample_in_center_with_symmetry(added_points); + this->sample_in_center_with_symmetry(sampled_uvs); } else if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { - this->sample_projected_with_symmetry(rng, added_points); + this->sample_projected_with_symmetry(rng, sampled_uvs); } else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { - this->sample_spherical_with_symmetry(rng, added_points); + this->sample_spherical_with_symmetry(rng, sampled_uvs); } else { BLI_assert_unreachable(); } - if (added_points.bary_coords.is_empty()) { + if (sampled_uvs.is_empty()) { /* No new points have been added. */ return; } - /* Find UV map. */ - VArraySpan surface_uv_map; - if (curves_id_->surface_uv_map != nullptr) { - const bke::AttributeAccessor surface_attributes = bke::mesh_attributes(*surface_); - surface_uv_map = surface_attributes.lookup(curves_id_->surface_uv_map, - ATTR_DOMAIN_CORNER); - } + const Span surface_looptris_orig = {BKE_mesh_runtime_looptri_ensure(&surface_orig), + BKE_mesh_runtime_looptri_len(&surface_orig)}; /* Find normals. */ - if (!CustomData_has_layer(&surface_->ldata, CD_NORMAL)) { - BKE_mesh_calc_normals_split(surface_); + if (!CustomData_has_layer(&surface_orig.ldata, CD_NORMAL)) { + BKE_mesh_calc_normals_split(&surface_orig); } const Span corner_normals_su = { - reinterpret_cast(CustomData_get_layer(&surface_->ldata, CD_NORMAL)), - surface_->totloop}; + reinterpret_cast(CustomData_get_layer(&surface_orig.ldata, CD_NORMAL)), + surface_orig.totloop}; + + const geometry::ReverseUVSampler reverse_uv_sampler{surface_uv_map, surface_looptris_orig}; geometry::AddCurvesOnMeshInputs add_inputs; - add_inputs.root_positions_cu = added_points.positions_cu; - add_inputs.bary_coords = added_points.bary_coords; - add_inputs.looptri_indices = added_points.looptri_indices; + add_inputs.uvs = sampled_uvs; add_inputs.interpolate_length = brush_settings_->flag & BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_LENGTH; add_inputs.interpolate_shape = brush_settings_->flag & @@ -201,13 +220,10 @@ struct AddOperationExecutor { BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_POINT_COUNT; add_inputs.fallback_curve_length = brush_settings_->curve_length; add_inputs.fallback_point_count = std::max(2, brush_settings_->points_per_curve); - add_inputs.surface = surface_; - add_inputs.surface_bvh = &surface_bvh_; - add_inputs.surface_looptris = surface_looptris_; - add_inputs.surface_uv_map = surface_uv_map; + add_inputs.transforms = &transforms_; + add_inputs.reverse_uv_sampler = &reverse_uv_sampler; + add_inputs.surface = &surface_orig; add_inputs.corner_normals_su = corner_normals_su; - add_inputs.curves_to_surface_mat = transforms_.curves_to_surface; - add_inputs.surface_to_curves_normal_mat = transforms_.surface_to_curves_normal; if (add_inputs.interpolate_length || add_inputs.interpolate_shape || add_inputs.interpolate_point_count) { @@ -215,17 +231,17 @@ struct AddOperationExecutor { add_inputs.old_roots_kdtree = self_->curve_roots_kdtree_; } - geometry::add_curves_on_mesh(*curves_, add_inputs); + geometry::add_curves_on_mesh(*curves_orig_, add_inputs); - DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); - WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); + DEG_id_tag_update(&curves_id_orig_->id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_orig_->id); ED_region_tag_redraw(ctx_.region); } /** * Sample a single point exactly at the mouse position. */ - void sample_in_center_with_symmetry(AddedPoints &r_added_points) + void sample_in_center_with_symmetry(Vector &r_sampled_uvs) { float3 ray_start_wo, ray_end_wo; ED_view3d_win_to_segment_clipped( @@ -234,15 +250,15 @@ struct AddOperationExecutor { const float3 ray_end_cu = transforms_.world_to_curves * ray_end_wo; const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( - eCurvesSymmetryType(curves_id_->symmetry)); + eCurvesSymmetryType(curves_id_orig_->symmetry)); for (const float4x4 &brush_transform : symmetry_brush_transforms) { const float4x4 transform = transforms_.curves_to_surface * brush_transform; - this->sample_in_center(r_added_points, transform * ray_start_cu, transform * ray_end_cu); + this->sample_in_center(r_sampled_uvs, transform * ray_start_cu, transform * ray_end_cu); } } - void sample_in_center(AddedPoints &r_added_points, + void sample_in_center(Vector &r_sampled_uvs, const float3 &ray_start_su, const float3 &ray_end_su) { @@ -251,58 +267,61 @@ struct AddOperationExecutor { BVHTreeRayHit ray_hit; ray_hit.dist = FLT_MAX; ray_hit.index = -1; - BLI_bvhtree_ray_cast(surface_bvh_.tree, + BLI_bvhtree_ray_cast(surface_bvh_eval_.tree, ray_start_su, ray_direction_su, 0.0f, &ray_hit, - surface_bvh_.raycast_callback, - &surface_bvh_); + surface_bvh_eval_.raycast_callback, + &surface_bvh_eval_); if (ray_hit.index == -1) { return; } const int looptri_index = ray_hit.index; + const MLoopTri &looptri = surface_looptris_eval_[looptri_index]; const float3 brush_pos_su = ray_hit.co; const float3 bary_coords = bke::mesh_surface_sample::compute_bary_coord_in_triangle( - *surface_, surface_looptris_[looptri_index], brush_pos_su); - - const float3 brush_pos_cu = transforms_.surface_to_curves * brush_pos_su; + *surface_eval_, looptri, brush_pos_su); - r_added_points.positions_cu.append(brush_pos_cu); - r_added_points.bary_coords.append(bary_coords); - r_added_points.looptri_indices.append(looptri_index); + const float2 uv = bke::mesh_surface_sample::sample_corner_attrribute_with_bary_coords( + bary_coords, looptri, surface_uv_map_eval_); + r_sampled_uvs.append(uv); } /** * Sample points by shooting rays within the brush radius in the 3D view. */ - void sample_projected_with_symmetry(RandomNumberGenerator &rng, AddedPoints &r_added_points) + void sample_projected_with_symmetry(RandomNumberGenerator &rng, Vector &r_sampled_uvs) { const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( - eCurvesSymmetryType(curves_id_->symmetry)); + eCurvesSymmetryType(curves_id_orig_->symmetry)); for (const float4x4 &brush_transform : symmetry_brush_transforms) { - this->sample_projected(rng, r_added_points, brush_transform); + this->sample_projected(rng, r_sampled_uvs, brush_transform); } } void sample_projected(RandomNumberGenerator &rng, - AddedPoints &r_added_points, + Vector &r_sampled_uvs, const float4x4 &brush_transform) { - const int old_amount = r_added_points.bary_coords.size(); + const int old_amount = r_sampled_uvs.size(); const int max_iterations = 100; int current_iteration = 0; - while (r_added_points.bary_coords.size() < old_amount + add_amount_) { + while (r_sampled_uvs.size() < old_amount + add_amount_) { if (current_iteration++ >= max_iterations) { break; } - const int missing_amount = add_amount_ + old_amount - r_added_points.bary_coords.size(); + Vector bary_coords; + Vector looptri_indices; + Vector positions_su; + + const int missing_amount = add_amount_ + old_amount - r_sampled_uvs.size(); const int new_points = bke::mesh_surface_sample::sample_surface_points_projected( rng, - *surface_, - surface_bvh_, + *surface_eval_, + surface_bvh_eval_, brush_pos_re_, brush_radius_re_, [&](const float2 &pos_re, float3 &r_start_su, float3 &r_end_su) { @@ -317,11 +336,14 @@ struct AddOperationExecutor { use_front_face_, add_amount_, missing_amount, - r_added_points.bary_coords, - r_added_points.looptri_indices, - r_added_points.positions_cu); - for (float3 &pos : r_added_points.positions_cu.as_mutable_span().take_back(new_points)) { - pos = transforms_.surface_to_curves * pos; + bary_coords, + looptri_indices, + positions_su); + + for (const int i : IndexRange(new_points)) { + const float2 uv = bke::mesh_surface_sample::sample_corner_attrribute_with_bary_coords( + bary_coords[i], surface_looptris_eval_[looptri_indices[i]], surface_uv_map_eval_); + r_sampled_uvs.append(uv); } } } @@ -329,13 +351,13 @@ struct AddOperationExecutor { /** * Sample points in a 3D sphere around the surface position that the mouse hovers over. */ - void sample_spherical_with_symmetry(RandomNumberGenerator &rng, AddedPoints &r_added_points) + void sample_spherical_with_symmetry(RandomNumberGenerator &rng, Vector &r_sampled_uvs) { const std::optional brush_3d = sample_curves_surface_3d_brush(*ctx_.depsgraph, *ctx_.region, *ctx_.v3d, transforms_, - surface_bvh_, + surface_bvh_eval_, brush_pos_re_, brush_radius_re_); if (!brush_3d.has_value()) { @@ -355,7 +377,7 @@ struct AddOperationExecutor { const float3 view_ray_end_cu = transforms_.world_to_curves * view_ray_end_wo; const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( - eCurvesSymmetryType(curves_id_->symmetry)); + eCurvesSymmetryType(curves_id_orig_->symmetry)); for (const float4x4 &brush_transform : symmetry_brush_transforms) { const float4x4 transform = transforms_.curves_to_surface * brush_transform; @@ -365,13 +387,12 @@ struct AddOperationExecutor { const float brush_radius_su = transform_brush_radius( transform, brush_3d->position_cu, brush_3d->radius_cu); - this->sample_spherical( - rng, r_added_points, brush_pos_su, brush_radius_su, view_direction_su); + this->sample_spherical(rng, r_sampled_uvs, brush_pos_su, brush_radius_su, view_direction_su); } } void sample_spherical(RandomNumberGenerator &rng, - AddedPoints &r_added_points, + Vector &r_sampled_uvs, const float3 &brush_pos_su, const float brush_radius_su, const float3 &view_direction_su) @@ -379,32 +400,32 @@ struct AddOperationExecutor { const float brush_radius_sq_su = pow2f(brush_radius_su); /* Find surface triangles within brush radius. */ - Vector looptri_indices; + Vector selected_looptri_indices; if (use_front_face_) { BLI_bvhtree_range_query_cpp( - *surface_bvh_.tree, + *surface_bvh_eval_.tree, brush_pos_su, brush_radius_su, [&](const int index, const float3 &UNUSED(co), const float UNUSED(dist_sq)) { - const MLoopTri &looptri = surface_looptris_[index]; - const float3 v0_su = surface_->mvert[surface_->mloop[looptri.tri[0]].v].co; - const float3 v1_su = surface_->mvert[surface_->mloop[looptri.tri[1]].v].co; - const float3 v2_su = surface_->mvert[surface_->mloop[looptri.tri[2]].v].co; + const MLoopTri &looptri = surface_looptris_eval_[index]; + const float3 v0_su = surface_eval_->mvert[surface_eval_->mloop[looptri.tri[0]].v].co; + const float3 v1_su = surface_eval_->mvert[surface_eval_->mloop[looptri.tri[1]].v].co; + const float3 v2_su = surface_eval_->mvert[surface_eval_->mloop[looptri.tri[2]].v].co; float3 normal_su; normal_tri_v3(normal_su, v0_su, v1_su, v2_su); if (math::dot(normal_su, view_direction_su) >= 0.0f) { return; } - looptri_indices.append(index); + selected_looptri_indices.append(index); }); } else { BLI_bvhtree_range_query_cpp( - *surface_bvh_.tree, + *surface_bvh_eval_.tree, brush_pos_su, brush_radius_su, [&](const int index, const float3 &UNUSED(co), const float UNUSED(dist_sq)) { - looptri_indices.append(index); + selected_looptri_indices.append(index); }); } @@ -418,42 +439,45 @@ struct AddOperationExecutor { const int max_iterations = 5; int current_iteration = 0; - const int old_amount = r_added_points.bary_coords.size(); - while (r_added_points.bary_coords.size() < old_amount + add_amount_) { + const int old_amount = r_sampled_uvs.size(); + while (r_sampled_uvs.size() < old_amount + add_amount_) { if (current_iteration++ >= max_iterations) { break; } + Vector bary_coords; + Vector looptri_indices; + Vector positions_su; const int new_points = bke::mesh_surface_sample::sample_surface_points_spherical( rng, - *surface_, - looptri_indices, + *surface_eval_, + selected_looptri_indices, brush_pos_su, brush_radius_su, approximate_density_su, - r_added_points.bary_coords, - r_added_points.looptri_indices, - r_added_points.positions_cu); - for (float3 &pos : r_added_points.positions_cu.as_mutable_span().take_back(new_points)) { - pos = transforms_.surface_to_curves * pos; + bary_coords, + looptri_indices, + positions_su); + for (const int i : IndexRange(new_points)) { + const float2 uv = bke::mesh_surface_sample::sample_corner_attrribute_with_bary_coords( + bary_coords[i], surface_looptris_eval_[looptri_indices[i]], surface_uv_map_eval_); + r_sampled_uvs.append(uv); } } /* Remove samples when there are too many. */ - while (r_added_points.bary_coords.size() > old_amount + add_amount_) { + while (r_sampled_uvs.size() > old_amount + add_amount_) { const int index_to_remove = rng.get_int32(add_amount_) + old_amount; - r_added_points.bary_coords.remove_and_reorder(index_to_remove); - r_added_points.looptri_indices.remove_and_reorder(index_to_remove); - r_added_points.positions_cu.remove_and_reorder(index_to_remove); + r_sampled_uvs.remove_and_reorder(index_to_remove); } } void ensure_curve_roots_kdtree() { if (self_->curve_roots_kdtree_ == nullptr) { - self_->curve_roots_kdtree_ = BLI_kdtree_3d_new(curves_->curves_num()); - for (const int curve_i : curves_->curves_range()) { - const int root_point_i = curves_->offsets()[curve_i]; - const float3 &root_pos_cu = curves_->positions()[root_point_i]; + self_->curve_roots_kdtree_ = BLI_kdtree_3d_new(curves_orig_->curves_num()); + for (const int curve_i : curves_orig_->curves_range()) { + const int root_point_i = curves_orig_->offsets()[curve_i]; + const float3 &root_pos_cu = curves_orig_->positions()[root_point_i]; BLI_kdtree_3d_insert(self_->curve_roots_kdtree_, curve_i, root_pos_cu); } BLI_kdtree_3d_balance(self_->curve_roots_kdtree_); @@ -467,17 +491,8 @@ void AddOperation::on_stroke_extended(const bContext &C, const StrokeExtension & executor.execute(*this, C, stroke_extension); } -std::unique_ptr new_add_operation(const bContext &C, - ReportList *reports) +std::unique_ptr new_add_operation() { - const Object &ob_active = *CTX_data_active_object(&C); - BLI_assert(ob_active.type == OB_CURVES); - const Curves &curves_id = *static_cast(ob_active.data); - if (curves_id.surface == nullptr || curves_id.surface->type != OB_MESH) { - BKE_report(reports, RPT_WARNING, "Can not use Add brush when there is no surface mesh"); - return {}; - } - return std::make_unique(); } diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc b/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc index ff27c16dc36..9803c6fdcc6 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc @@ -8,6 +8,9 @@ #include "BKE_bvhutils.h" #include "BKE_context.h" #include "BKE_curves.hh" +#include "BKE_modifier.h" +#include "BKE_object.h" +#include "BKE_report.h" #include "ED_view3d.h" @@ -20,6 +23,10 @@ #include "BLI_length_parameterize.hh" #include "BLI_task.hh" +#include "DEG_depsgraph_query.h" + +#include "BLT_translation.h" + /** * The code below uses a prefix naming convention to indicate the coordinate space: * cu: Local space of the curves object that is being edited. @@ -48,7 +55,8 @@ static std::optional find_curves_brush_position(const CurvesGeometry &cu const float brush_radius_re, const ARegion ®ion, const RegionView3D &rv3d, - const Object &object) + const Object &object, + const Span positions) { /* This value might have to be adjusted based on user feedback. */ const float brush_inner_radius_re = std::min(brush_radius_re, (float)UI_UNIT_X / 3.0f); @@ -88,8 +96,6 @@ static std::optional find_curves_brush_position(const CurvesGeometry &cu } }; - const Span positions = curves.positions(); - BrushPositionCandidate best_candidate = threading::parallel_reduce( curves.curves_range(), 128, @@ -175,20 +181,21 @@ std::optional sample_curves_3d_brush(const Depsgraph &depsgraph, { const Curves &curves_id = *static_cast(curves_object.data); const CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); - const Object *surface_object = curves_id.surface; + Object *surface_object = curves_id.surface; + Object *surface_object_eval = DEG_get_evaluated_object(&depsgraph, surface_object); float3 center_ray_start_wo, center_ray_end_wo; ED_view3d_win_to_segment_clipped( &depsgraph, ®ion, &v3d, brush_pos_re, center_ray_start_wo, center_ray_end_wo, true); /* Shorten ray when the surface object is hit. */ - if (surface_object != nullptr) { + if (surface_object_eval != nullptr) { const float4x4 surface_to_world_mat = surface_object->obmat; const float4x4 world_to_surface_mat = surface_to_world_mat.inverted(); - Mesh &surface = *static_cast(surface_object->data); + Mesh *surface_eval = BKE_object_get_evaluated_mesh(surface_object_eval); BVHTreeFromMesh surface_bvh; - BKE_bvhtree_from_mesh_get(&surface_bvh, &surface, BVHTREE_FROM_LOOPTRI, 2); + BKE_bvhtree_from_mesh_get(&surface_bvh, surface_eval, BVHTREE_FROM_LOOPTRI, 2); BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh); }); const float3 center_ray_start_su = world_to_surface_mat * center_ray_start_wo; @@ -222,6 +229,9 @@ std::optional sample_curves_3d_brush(const Depsgraph &depsgraph, const float3 center_ray_start_cu = world_to_curves_mat * center_ray_start_wo; const float3 center_ray_end_cu = world_to_curves_mat * center_ray_end_wo; + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(depsgraph, curves_object); + const std::optional brush_position_optional_cu = find_curves_brush_position( curves, center_ray_start_cu, @@ -229,7 +239,8 @@ std::optional sample_curves_3d_brush(const Depsgraph &depsgraph, brush_radius_re, region, rv3d, - curves_object); + curves_object, + deformation.positions); if (!brush_position_optional_cu.has_value()) { /* Nothing found. */ return std::nullopt; @@ -380,4 +391,21 @@ CurvesSculptCommonContext::CurvesSculptCommonContext(const bContext &C) this->rv3d = CTX_wm_region_view3d(&C); } +void report_missing_surface(ReportList *reports) +{ + BKE_report(reports, RPT_WARNING, TIP_("Missing surface mesh")); +} + +void report_missing_uv_map_on_original_surface(ReportList *reports) +{ + BKE_report( + reports, RPT_WARNING, TIP_("Missing UV map for attaching curves on original surface")); +} + +void report_missing_uv_map_on_evaluated_surface(ReportList *reports) +{ + BKE_report( + reports, RPT_WARNING, TIP_("Missing UV map for attaching curves on evaluated surface")); +} + } // namespace blender::ed::sculpt_paint diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc b/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc index 449f1786167..52f2ddc6550 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc @@ -13,12 +13,15 @@ #include "PIL_time.h" #include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" #include "BKE_attribute_math.hh" #include "BKE_brush.h" #include "BKE_bvhutils.h" #include "BKE_context.h" +#include "BKE_crazyspace.hh" #include "BKE_curves.hh" +#include "BKE_geometry_set.hh" #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" #include "BKE_paint.h" @@ -88,9 +91,9 @@ struct CombOperationExecutor { eBrushFalloffShape falloff_shape_; - Object *object_ = nullptr; - Curves *curves_id_ = nullptr; - CurvesGeometry *curves_ = nullptr; + Object *curves_ob_orig_ = nullptr; + Curves *curves_id_orig_ = nullptr; + CurvesGeometry *curves_orig_ = nullptr; VArray point_factors_; Vector selected_curve_indices_; @@ -112,7 +115,12 @@ struct CombOperationExecutor { BLI_SCOPED_DEFER([&]() { self_->brush_pos_last_re_ = stroke_extension.mouse_position; }); - object_ = CTX_data_active_object(&C); + curves_ob_orig_ = CTX_data_active_object(&C); + curves_id_orig_ = static_cast(curves_ob_orig_->data); + curves_orig_ = &CurvesGeometry::wrap(curves_id_orig_->geometry); + if (curves_orig_->curves_num() == 0) { + return; + } curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt; brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint); @@ -122,16 +130,10 @@ struct CombOperationExecutor { falloff_shape_ = static_cast(brush_->falloff_shape); - curves_id_ = static_cast(object_->data); - curves_ = &CurvesGeometry::wrap(curves_id_->geometry); - if (curves_->curves_num() == 0) { - return; - } - - transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface); + transforms_ = CurvesSurfaceTransforms(*curves_ob_orig_, curves_id_orig_->surface); - point_factors_ = get_point_selection(*curves_id_); - curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_); + point_factors_ = get_point_selection(*curves_id_orig_); + curve_selection_ = retrieve_selected_curves(*curves_id_orig_, selected_curve_indices_); brush_pos_prev_re_ = self_->brush_pos_last_re_; brush_pos_re_ = stroke_extension.mouse_position; @@ -160,9 +162,9 @@ struct CombOperationExecutor { this->restore_segment_lengths(changed_curves); - curves_->tag_positions_changed(); - DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); - WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); + curves_orig_->tag_positions_changed(); + DEG_id_tag_update(&curves_id_orig_->id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_orig_->id); ED_region_tag_redraw(ctx_.region); } @@ -172,7 +174,7 @@ struct CombOperationExecutor { void comb_projected_with_symmetry(EnumerableThreadSpecific> &r_changed_curves) { const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( - eCurvesSymmetryType(curves_id_->symmetry)); + eCurvesSymmetryType(curves_id_orig_->symmetry)); for (const float4x4 &brush_transform : symmetry_brush_transforms) { this->comb_projected(r_changed_curves, brush_transform); } @@ -183,10 +185,12 @@ struct CombOperationExecutor { { const float4x4 brush_transform_inv = brush_transform.inverted(); - MutableSpan positions_cu = curves_->positions_for_write(); + MutableSpan positions_cu_orig = curves_orig_->positions_for_write(); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *curves_ob_orig_); float4x4 projection; - ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); + ED_view3d_ob_project_mat_get(ctx_.rv3d, curves_ob_orig_, projection.values); const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_; const float brush_radius_sq_re = pow2f(brush_radius_re); @@ -195,16 +199,18 @@ struct CombOperationExecutor { Vector &local_changed_curves = r_changed_curves.local(); for (const int curve_i : curve_selection_.slice(range)) { bool curve_changed = false; - const IndexRange points = curves_->points_for_curve(curve_i); + const IndexRange points = curves_orig_->points_for_curve(curve_i); for (const int point_i : points.drop_front(1)) { - const float3 old_pos_cu = brush_transform_inv * positions_cu[point_i]; + const float3 old_pos_cu = deformation.positions[point_i]; + const float3 old_symm_pos_cu = brush_transform_inv * old_pos_cu; /* Find the position of the point in screen space. */ - float2 old_pos_re; - ED_view3d_project_float_v2_m4(ctx_.region, old_pos_cu, old_pos_re, projection.values); + float2 old_symm_pos_re; + ED_view3d_project_float_v2_m4( + ctx_.region, old_symm_pos_cu, old_symm_pos_re, projection.values); const float distance_to_brush_sq_re = dist_squared_to_line_segment_v2( - old_pos_re, brush_pos_prev_re_, brush_pos_re_); + old_symm_pos_re, brush_pos_prev_re_, brush_pos_re_); if (distance_to_brush_sq_re > brush_radius_sq_re) { /* Ignore the point because it's too far away. */ continue; @@ -219,16 +225,20 @@ struct CombOperationExecutor { /* Offset the old point position in screen space and transform it back into 3D space. */ - const float2 new_position_re = old_pos_re + brush_pos_diff_re_ * weight; - float3 new_position_wo; + const float2 new_symm_pos_re = old_symm_pos_re + brush_pos_diff_re_ * weight; + float3 new_symm_pos_wo; ED_view3d_win_to_3d(ctx_.v3d, ctx_.region, - transforms_.curves_to_world * old_pos_cu, - new_position_re, - new_position_wo); - const float3 new_position_cu = brush_transform * - (transforms_.world_to_curves * new_position_wo); - positions_cu[point_i] = new_position_cu; + transforms_.curves_to_world * old_symm_pos_cu, + new_symm_pos_re, + new_symm_pos_wo); + const float3 new_pos_cu = brush_transform * + (transforms_.world_to_curves * new_symm_pos_wo); + + const float3 translation_eval = new_pos_cu - old_pos_cu; + const float3 translation_orig = deformation.translation_from_deformed_to_original( + point_i, translation_eval); + positions_cu_orig[point_i] += translation_orig; curve_changed = true; } @@ -245,7 +255,7 @@ struct CombOperationExecutor { void comb_spherical_with_symmetry(EnumerableThreadSpecific> &r_changed_curves) { float4x4 projection; - ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); + ED_view3d_ob_project_mat_get(ctx_.rv3d, curves_ob_orig_, projection.values); float3 brush_start_wo, brush_end_wo; ED_view3d_win_to_3d(ctx_.v3d, @@ -264,7 +274,7 @@ struct CombOperationExecutor { const float brush_radius_cu = self_->brush_3d_.radius_cu * brush_radius_factor_; const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( - eCurvesSymmetryType(curves_id_->symmetry)); + eCurvesSymmetryType(curves_id_orig_->symmetry)); for (const float4x4 &brush_transform : symmetry_brush_transforms) { this->comb_spherical(r_changed_curves, brush_transform * brush_start_cu, @@ -278,17 +288,20 @@ struct CombOperationExecutor { const float3 &brush_end_cu, const float brush_radius_cu) { - MutableSpan positions_cu = curves_->positions_for_write(); + MutableSpan positions_cu = curves_orig_->positions_for_write(); const float brush_radius_sq_cu = pow2f(brush_radius_cu); const float3 brush_diff_cu = brush_end_cu - brush_start_cu; + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *curves_ob_orig_); + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { Vector &local_changed_curves = r_changed_curves.local(); for (const int curve_i : curve_selection_.slice(range)) { bool curve_changed = false; - const IndexRange points = curves_->points_for_curve(curve_i); + const IndexRange points = curves_orig_->points_for_curve(curve_i); for (const int point_i : points.drop_front(1)) { - const float3 pos_old_cu = positions_cu[point_i]; + const float3 pos_old_cu = deformation.positions[point_i]; /* Compute distance to the brush. */ const float distance_to_brush_sq_cu = dist_squared_to_line_segment_v3( @@ -306,8 +319,12 @@ struct CombOperationExecutor { /* Combine the falloff and brush strength. */ const float weight = brush_strength_ * radius_falloff * point_factors_[point_i]; + const float3 translation_eval_cu = weight * brush_diff_cu; + const float3 translation_orig_cu = deformation.translation_from_deformed_to_original( + point_i, translation_eval_cu); + /* Update the point position. */ - positions_cu[point_i] = pos_old_cu + weight * brush_diff_cu; + positions_cu[point_i] += translation_orig_cu; curve_changed = true; } if (curve_changed) { @@ -326,7 +343,7 @@ struct CombOperationExecutor { *ctx_.region, *ctx_.v3d, *ctx_.rv3d, - *object_, + *curves_ob_orig_, brush_pos_re_, brush_radius_base_re_); if (brush_3d.has_value()) { @@ -340,11 +357,11 @@ struct CombOperationExecutor { */ void initialize_segment_lengths() { - const Span positions_cu = curves_->positions(); - self_->segment_lengths_cu_.reinitialize(curves_->points_num()); - threading::parallel_for(curves_->curves_range(), 128, [&](const IndexRange range) { + const Span positions_cu = curves_orig_->positions(); + self_->segment_lengths_cu_.reinitialize(curves_orig_->points_num()); + threading::parallel_for(curves_orig_->curves_range(), 128, [&](const IndexRange range) { for (const int curve_i : range) { - const IndexRange points = curves_->points_for_curve(curve_i); + const IndexRange points = curves_orig_->points_for_curve(curve_i); for (const int point_i : points.drop_back(1)) { const float3 &p1_cu = positions_cu[point_i]; const float3 &p2_cu = positions_cu[point_i + 1]; @@ -361,12 +378,12 @@ struct CombOperationExecutor { void restore_segment_lengths(EnumerableThreadSpecific> &changed_curves) { const Span expected_lengths_cu = self_->segment_lengths_cu_; - MutableSpan positions_cu = curves_->positions_for_write(); + MutableSpan positions_cu = curves_orig_->positions_for_write(); threading::parallel_for_each(changed_curves, [&](const Vector &changed_curves) { threading::parallel_for(changed_curves.index_range(), 256, [&](const IndexRange range) { for (const int curve_i : changed_curves.as_span().slice(range)) { - const IndexRange points = curves_->points_for_curve(curve_i); + const IndexRange points = curves_orig_->points_for_curve(curve_i); for (const int segment_i : points.drop_back(1)) { const float3 &p1_cu = positions_cu[segment_i]; float3 &p2_cu = positions_cu[segment_i + 1]; diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc b/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc index 777ebd16110..a44499ce133 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc @@ -51,6 +51,12 @@ using blender::bke::CurvesGeometry; class DeleteOperation : public CurvesSculptStrokeOperation { private: CurvesBrush3D brush_3d_; + /** + * Need to store those in case the brush is evaluated more than once before the curves are + * evaluated again. This can happen when the mouse is moved quickly and the brush spacing is + * small. + */ + Vector deformed_positions_; friend struct DeleteOperationExecutor; @@ -109,6 +115,9 @@ struct DeleteOperationExecutor { if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { this->initialize_spherical_brush_reference_point(); } + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); + self_->deformed_positions_ = deformation.positions; } Array curves_to_delete(curves_->curves_num(), false); @@ -123,12 +132,22 @@ struct DeleteOperationExecutor { } Vector indices; - const IndexMask mask = index_mask_ops::find_indices_based_on_predicate( + const IndexMask mask_to_delete = index_mask_ops::find_indices_based_on_predicate( curves_->curves_range(), 4096, indices, [&](const int curve_i) { return curves_to_delete[curve_i]; }); - curves_->remove_curves(mask); + /* Remove deleted curves from the stored deformed positions. */ + const Vector ranges_to_keep = mask_to_delete.extract_ranges_invert( + curves_->curves_range()); + Vector new_deformed_positions; + for (const IndexRange curves_range : ranges_to_keep) { + new_deformed_positions.extend( + self_->deformed_positions_.as_span().slice(curves_->points_for_curves(curves_range))); + } + self_->deformed_positions_ = std::move(new_deformed_positions); + + curves_->remove_curves(mask_to_delete); DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); @@ -151,8 +170,6 @@ struct DeleteOperationExecutor { float4x4 projection; ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); - Span positions_cu = curves_->positions(); - const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_; const float brush_radius_sq_re = pow2f(brush_radius_re); @@ -160,7 +177,7 @@ struct DeleteOperationExecutor { for (const int curve_i : curve_selection_.slice(range)) { const IndexRange points = curves_->points_for_curve(curve_i); if (points.size() == 1) { - const float3 pos_cu = brush_transform_inv * positions_cu[points.first()]; + const float3 pos_cu = brush_transform_inv * self_->deformed_positions_[points.first()]; float2 pos_re; ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values); @@ -171,8 +188,8 @@ struct DeleteOperationExecutor { } for (const int segment_i : points.drop_back(1)) { - const float3 pos1_cu = brush_transform_inv * positions_cu[segment_i]; - const float3 pos2_cu = brush_transform_inv * positions_cu[segment_i + 1]; + const float3 pos1_cu = brush_transform_inv * self_->deformed_positions_[segment_i]; + const float3 pos2_cu = brush_transform_inv * self_->deformed_positions_[segment_i + 1]; float2 pos1_re, pos2_re; ED_view3d_project_float_v2_m4(ctx_.region, pos1_cu, pos1_re, projection.values); @@ -212,8 +229,6 @@ struct DeleteOperationExecutor { void delete_spherical(const float3 &brush_cu, MutableSpan curves_to_delete) { - Span positions_cu = curves_->positions(); - const float brush_radius_cu = self_->brush_3d_.radius_cu * brush_radius_factor_; const float brush_radius_sq_cu = pow2f(brush_radius_cu); @@ -222,7 +237,7 @@ struct DeleteOperationExecutor { const IndexRange points = curves_->points_for_curve(curve_i); if (points.size() == 1) { - const float3 &pos_cu = positions_cu[points.first()]; + const float3 &pos_cu = self_->deformed_positions_[points.first()]; const float distance_sq_cu = math::distance_squared(pos_cu, brush_cu); if (distance_sq_cu < brush_radius_sq_cu) { curves_to_delete[curve_i] = true; @@ -231,8 +246,8 @@ struct DeleteOperationExecutor { } for (const int segment_i : points.drop_back(1)) { - const float3 &pos1_cu = positions_cu[segment_i]; - const float3 &pos2_cu = positions_cu[segment_i + 1]; + const float3 &pos1_cu = self_->deformed_positions_[segment_i]; + const float3 &pos2_cu = self_->deformed_positions_[segment_i + 1]; const float distance_sq_cu = dist_squared_to_line_segment_v3(brush_cu, pos1_cu, pos2_cu); if (distance_sq_cu > brush_radius_sq_cu) { diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc index e211a568705..b07f5c74055 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc @@ -2,6 +2,7 @@ #include +#include "BKE_attribute_math.hh" #include "BKE_brush.h" #include "BKE_bvhutils.h" #include "BKE_context.h" @@ -9,11 +10,15 @@ #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" #include "BKE_mesh_sample.hh" +#include "BKE_modifier.h" +#include "BKE_object.h" +#include "BKE_report.h" #include "ED_screen.h" #include "ED_view3d.h" #include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" #include "BLI_index_mask_ops.hh" #include "BLI_kdtree.h" @@ -36,7 +41,11 @@ namespace blender::ed::sculpt_paint { class DensityAddOperation : public CurvesSculptStrokeOperation { private: /** Used when some data should be interpolated from existing curves. */ - KDTree_3d *curve_roots_kdtree_ = nullptr; + KDTree_3d *original_curve_roots_kdtree_ = nullptr; + /** Contains curve roots of all curves that existed before the brush started. */ + KDTree_3d *deformed_curve_roots_kdtree_ = nullptr; + /** Root positions of curves that have been added in the current brush stroke. */ + Vector new_deformed_root_positions_; int original_curve_num_ = 0; friend struct DensityAddOperationExecutor; @@ -44,8 +53,11 @@ class DensityAddOperation : public CurvesSculptStrokeOperation { public: ~DensityAddOperation() override { - if (curve_roots_kdtree_ != nullptr) { - BLI_kdtree_3d_free(curve_roots_kdtree_); + if (original_curve_roots_kdtree_ != nullptr) { + BLI_kdtree_3d_free(original_curve_roots_kdtree_); + } + if (deformed_curve_roots_kdtree_ != nullptr) { + BLI_kdtree_3d_free(deformed_curve_roots_kdtree_); } } @@ -56,14 +68,18 @@ struct DensityAddOperationExecutor { DensityAddOperation *self_ = nullptr; CurvesSculptCommonContext ctx_; - Object *object_ = nullptr; - Curves *curves_id_ = nullptr; - CurvesGeometry *curves_ = nullptr; + Object *curves_ob_orig_ = nullptr; + Curves *curves_id_orig_ = nullptr; + CurvesGeometry *curves_orig_ = nullptr; + + Object *surface_ob_orig_ = nullptr; + Mesh *surface_orig_ = nullptr; - Object *surface_ob_ = nullptr; - Mesh *surface_ = nullptr; - Span surface_looptris_; - Span corner_normals_su_; + Object *surface_ob_eval_ = nullptr; + Mesh *surface_eval_ = nullptr; + Span surface_looptris_eval_; + VArraySpan surface_uv_map_eval_; + BVHTreeFromMesh surface_bvh_eval_; const CurvesSculpt *curves_sculpt_ = nullptr; const Brush *brush_ = nullptr; @@ -75,8 +91,6 @@ struct DensityAddOperationExecutor { CurvesSurfaceTransforms transforms_; - BVHTreeFromMesh surface_bvh_; - DensityAddOperationExecutor(const bContext &C) : ctx_(C) { } @@ -86,32 +100,51 @@ struct DensityAddOperationExecutor { const StrokeExtension &stroke_extension) { self_ = &self; - object_ = CTX_data_active_object(&C); - curves_id_ = static_cast(object_->data); - curves_ = &CurvesGeometry::wrap(curves_id_->geometry); + curves_ob_orig_ = CTX_data_active_object(&C); + curves_id_orig_ = static_cast(curves_ob_orig_->data); + curves_orig_ = &CurvesGeometry::wrap(curves_id_orig_->geometry); if (stroke_extension.is_first) { - self_->original_curve_num_ = curves_->curves_num(); + self_->original_curve_num_ = curves_orig_->curves_num(); } - if (curves_id_->surface == nullptr || curves_id_->surface->type != OB_MESH) { + if (curves_id_orig_->surface == nullptr || curves_id_orig_->surface->type != OB_MESH) { + report_missing_surface(stroke_extension.reports); return; } - surface_ob_ = curves_id_->surface; - surface_ = static_cast(surface_ob_->data); - - surface_looptris_ = {BKE_mesh_runtime_looptri_ensure(surface_), - BKE_mesh_runtime_looptri_len(surface_)}; + surface_ob_orig_ = curves_id_orig_->surface; + surface_orig_ = static_cast(surface_ob_orig_->data); - transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface); + surface_ob_eval_ = DEG_get_evaluated_object(ctx_.depsgraph, surface_ob_orig_); + if (surface_ob_eval_ == nullptr) { + return; + } + surface_eval_ = BKE_object_get_evaluated_mesh(surface_ob_eval_); - if (!CustomData_has_layer(&surface_->ldata, CD_NORMAL)) { - BKE_mesh_calc_normals_split(surface_); + BKE_bvhtree_from_mesh_get(&surface_bvh_eval_, surface_eval_, BVHTREE_FROM_LOOPTRI, 2); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_eval_); }); + surface_looptris_eval_ = {BKE_mesh_runtime_looptri_ensure(surface_eval_), + BKE_mesh_runtime_looptri_len(surface_eval_)}; + /* Find UV map. */ + VArraySpan surface_uv_map; + if (curves_id_orig_->surface_uv_map != nullptr) { + surface_uv_map = bke::mesh_attributes(*surface_orig_) + .lookup(curves_id_orig_->surface_uv_map, ATTR_DOMAIN_CORNER); + surface_uv_map_eval_ = bke::mesh_attributes(*surface_eval_) + .lookup(curves_id_orig_->surface_uv_map, + ATTR_DOMAIN_CORNER); + } + if (surface_uv_map.is_empty()) { + report_missing_uv_map_on_original_surface(stroke_extension.reports); + return; + } + if (surface_uv_map_eval_.is_empty()) { + report_missing_uv_map_on_evaluated_surface(stroke_extension.reports); + return; } - corner_normals_su_ = { - reinterpret_cast(CustomData_get_layer(&surface_->ldata, CD_NORMAL)), - surface_->totloop}; + + transforms_ = CurvesSurfaceTransforms(*curves_ob_orig_, curves_id_orig_->surface); curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt; brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint); @@ -123,23 +156,17 @@ struct DensityAddOperationExecutor { const eBrushFalloffShape falloff_shape = static_cast( brush_->falloff_shape); - BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2); - BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); }); - - Vector new_bary_coords; - Vector new_looptri_indices; Vector new_positions_cu; + Vector new_uvs; const double time = PIL_check_seconds_timer() * 1000000.0; RandomNumberGenerator rng{*(uint32_t *)(&time)}; /* Find potential new curve root points. */ if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { - this->sample_projected_with_symmetry( - rng, new_bary_coords, new_looptri_indices, new_positions_cu); + this->sample_projected_with_symmetry(rng, new_uvs, new_positions_cu); } else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { - this->sample_spherical_with_symmetry( - rng, new_bary_coords, new_looptri_indices, new_positions_cu); + this->sample_spherical_with_symmetry(rng, new_uvs, new_positions_cu); } else { BLI_assert_unreachable(); @@ -148,9 +175,11 @@ struct DensityAddOperationExecutor { pos = transforms_.surface_to_curves * pos; } - this->ensure_curve_roots_kdtree(); + if (stroke_extension.is_first) { + this->prepare_curve_roots_kdtrees(); + } - const int already_added_curves = curves_->curves_num() - self_->original_curve_num_; + const int already_added_curves = self_->new_deformed_root_positions_.size(); KDTree_3d *new_roots_kdtree = BLI_kdtree_3d_new(already_added_curves + new_positions_cu.size()); BLI_SCOPED_DEFER([&]() { BLI_kdtree_3d_free(new_roots_kdtree); }); @@ -161,15 +190,12 @@ struct DensityAddOperationExecutor { threading::parallel_invoke( /* Build kdtree from root points created by the current stroke. */ [&]() { - const Span positions_cu = curves_->positions(); - for (const int curve_i : curves_->curves_range().take_back(already_added_curves)) { - const float3 &root_pos_cu = positions_cu[curves_->offsets()[curve_i]]; - BLI_kdtree_3d_insert(new_roots_kdtree, curve_i, root_pos_cu); + for (const int i : IndexRange(already_added_curves)) { + BLI_kdtree_3d_insert(new_roots_kdtree, -1, self_->new_deformed_root_positions_[i]); } for (const int new_i : new_positions_cu.index_range()) { - const int index_in_kdtree = curves_->curves_num() + new_i; const float3 &root_pos_cu = new_positions_cu[new_i]; - BLI_kdtree_3d_insert(new_roots_kdtree, index_in_kdtree, root_pos_cu); + BLI_kdtree_3d_insert(new_roots_kdtree, new_i, root_pos_cu); } BLI_kdtree_3d_balance(new_roots_kdtree); }, @@ -183,7 +209,7 @@ struct DensityAddOperationExecutor { KDTreeNearest_3d nearest; nearest.dist = FLT_MAX; BLI_kdtree_3d_find_nearest( - self_->curve_roots_kdtree_, new_root_pos_cu, &nearest); + self_->deformed_curve_roots_kdtree_, new_root_pos_cu, &nearest); if (nearest.dist < brush_settings_->minimum_distance) { new_curve_skipped[new_i] = true; } @@ -201,12 +227,11 @@ struct DensityAddOperationExecutor { new_roots_kdtree, root_pos_cu, brush_settings_->minimum_distance, - [&](const int other_i, const float *UNUSED(co), float UNUSED(dist_sq)) { - if (other_i < curves_->curves_num()) { + [&](const int other_new_i, const float *UNUSED(co), float UNUSED(dist_sq)) { + if (other_new_i == -1) { new_curve_skipped[new_i] = true; return false; } - const int other_new_i = other_i - curves_->curves_num(); if (new_i == other_new_i) { return true; } @@ -219,31 +244,25 @@ struct DensityAddOperationExecutor { for (int64_t i = new_positions_cu.size() - 1; i >= 0; i--) { if (new_curve_skipped[i]) { new_positions_cu.remove_and_reorder(i); - new_bary_coords.remove_and_reorder(i); - new_looptri_indices.remove_and_reorder(i); + new_uvs.remove_and_reorder(i); } } - - /* Find UV map. */ - VArraySpan surface_uv_map; - if (curves_id_->surface_uv_map != nullptr) { - bke::AttributeAccessor surface_attributes = bke::mesh_attributes(*surface_); - surface_uv_map = surface_attributes.lookup(curves_id_->surface_uv_map, - ATTR_DOMAIN_CORNER); - } + self_->new_deformed_root_positions_.extend(new_positions_cu); /* Find normals. */ - if (!CustomData_has_layer(&surface_->ldata, CD_NORMAL)) { - BKE_mesh_calc_normals_split(surface_); + if (!CustomData_has_layer(&surface_orig_->ldata, CD_NORMAL)) { + BKE_mesh_calc_normals_split(surface_orig_); } const Span corner_normals_su = { - reinterpret_cast(CustomData_get_layer(&surface_->ldata, CD_NORMAL)), - surface_->totloop}; + reinterpret_cast(CustomData_get_layer(&surface_orig_->ldata, CD_NORMAL)), + surface_orig_->totloop}; + + const Span surface_looptris_orig = {BKE_mesh_runtime_looptri_ensure(surface_orig_), + BKE_mesh_runtime_looptri_len(surface_orig_)}; + const geometry::ReverseUVSampler reverse_uv_sampler{surface_uv_map, surface_looptris_orig}; geometry::AddCurvesOnMeshInputs add_inputs; - add_inputs.root_positions_cu = new_positions_cu; - add_inputs.bary_coords = new_bary_coords; - add_inputs.looptri_indices = new_looptri_indices; + add_inputs.uvs = new_uvs; add_inputs.interpolate_length = brush_settings_->flag & BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_LENGTH; add_inputs.interpolate_shape = brush_settings_->flag & @@ -252,53 +271,67 @@ struct DensityAddOperationExecutor { BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_POINT_COUNT; add_inputs.fallback_curve_length = brush_settings_->curve_length; add_inputs.fallback_point_count = std::max(2, brush_settings_->points_per_curve); - add_inputs.surface = surface_; - add_inputs.surface_bvh = &surface_bvh_; - add_inputs.surface_looptris = surface_looptris_; - add_inputs.surface_uv_map = surface_uv_map; + add_inputs.transforms = &transforms_; + add_inputs.surface = surface_orig_; add_inputs.corner_normals_su = corner_normals_su; - add_inputs.curves_to_surface_mat = transforms_.curves_to_surface; - add_inputs.surface_to_curves_normal_mat = transforms_.surface_to_curves_normal; - add_inputs.old_roots_kdtree = self_->curve_roots_kdtree_; + add_inputs.reverse_uv_sampler = &reverse_uv_sampler; + add_inputs.old_roots_kdtree = self_->original_curve_roots_kdtree_; - geometry::add_curves_on_mesh(*curves_, add_inputs); + geometry::add_curves_on_mesh(*curves_orig_, add_inputs); - DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); - WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); + DEG_id_tag_update(&curves_id_orig_->id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_orig_->id); ED_region_tag_redraw(ctx_.region); } - void ensure_curve_roots_kdtree() + void prepare_curve_roots_kdtrees() { - if (self_->curve_roots_kdtree_ == nullptr) { - self_->curve_roots_kdtree_ = BLI_kdtree_3d_new(curves_->curves_num()); - for (const int curve_i : curves_->curves_range()) { - const int root_point_i = curves_->offsets()[curve_i]; - const float3 &root_pos_cu = curves_->positions()[root_point_i]; - BLI_kdtree_3d_insert(self_->curve_roots_kdtree_, curve_i, root_pos_cu); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *curves_ob_orig_); + const Span curve_offsets = curves_orig_->offsets(); + const Span original_positions = curves_orig_->positions(); + const Span deformed_positions = deformation.positions; + BLI_assert(original_positions.size() == deformed_positions.size()); + + auto roots_kdtree_from_positions = [&](const Span positions) { + KDTree_3d *kdtree = BLI_kdtree_3d_new(curves_orig_->curves_num()); + for (const int curve_i : curves_orig_->curves_range()) { + const int root_point_i = curve_offsets[curve_i]; + BLI_kdtree_3d_insert(kdtree, curve_i, positions[root_point_i]); } - BLI_kdtree_3d_balance(self_->curve_roots_kdtree_); - } + BLI_kdtree_3d_balance(kdtree); + return kdtree; + }; + + threading::parallel_invoke( + [&]() { + self_->original_curve_roots_kdtree_ = roots_kdtree_from_positions(original_positions); + }, + [&]() { + self_->deformed_curve_roots_kdtree_ = roots_kdtree_from_positions(deformed_positions); + }); } void sample_projected_with_symmetry(RandomNumberGenerator &rng, - Vector &r_bary_coords, - Vector &r_looptri_indices, + Vector &r_uvs, Vector &r_positions_su) { float4x4 projection; - ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); + ED_view3d_ob_project_mat_get(ctx_.rv3d, curves_ob_orig_, projection.values); const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( - eCurvesSymmetryType(curves_id_->symmetry)); + eCurvesSymmetryType(curves_id_orig_->symmetry)); for (const float4x4 &brush_transform : symmetry_brush_transforms) { const float4x4 brush_transform_inv = brush_transform.inverted(); const float4x4 transform = transforms_.curves_to_surface * brush_transform * transforms_.world_to_curves; + Vector positions_su; + Vector bary_coords; + Vector looptri_indices; const int new_points = bke::mesh_surface_sample::sample_surface_points_projected( rng, - *surface_, - surface_bvh_, + *surface_eval_, + surface_bvh_eval_, brush_pos_re_, brush_radius_re_, [&](const float2 &pos_re, float3 &r_start_su, float3 &r_end_su) { @@ -311,14 +344,13 @@ struct DensityAddOperationExecutor { true, brush_settings_->density_add_attempts, brush_settings_->density_add_attempts, - r_bary_coords, - r_looptri_indices, - r_positions_su); + bary_coords, + looptri_indices, + positions_su); /* Remove some sampled points randomly based on the brush falloff and strength. */ - const int old_points = r_bary_coords.size() - new_points; - for (int i = r_bary_coords.size() - 1; i >= old_points; i--) { - const float3 pos_su = r_positions_su[i]; + for (int i = new_points - 1; i >= 0; i--) { + const float3 pos_su = positions_su[i]; const float3 pos_cu = brush_transform_inv * transforms_.surface_to_curves * pos_su; float2 pos_re; ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values); @@ -327,24 +359,30 @@ struct DensityAddOperationExecutor { brush_, dist_to_brush_re, brush_radius_re_); const float weight = brush_strength_ * radius_falloff; if (rng.get_float() > weight) { - r_bary_coords.remove_and_reorder(i); - r_looptri_indices.remove_and_reorder(i); - r_positions_su.remove_and_reorder(i); + bary_coords.remove_and_reorder(i); + looptri_indices.remove_and_reorder(i); + positions_su.remove_and_reorder(i); } } + + for (const int i : bary_coords.index_range()) { + const float2 uv = bke::mesh_surface_sample::sample_corner_attrribute_with_bary_coords( + bary_coords[i], surface_looptris_eval_[looptri_indices[i]], surface_uv_map_eval_); + r_uvs.append(uv); + } + r_positions_su.extend(positions_su); } } void sample_spherical_with_symmetry(RandomNumberGenerator &rng, - Vector &r_bary_coords, - Vector &r_looptri_indices, + Vector &r_uvs, Vector &r_positions_su) { const std::optional brush_3d = sample_curves_surface_3d_brush(*ctx_.depsgraph, *ctx_.region, *ctx_.v3d, transforms_, - surface_bvh_, + surface_bvh_eval_, brush_pos_re_, brush_radius_re_); if (!brush_3d.has_value()) { @@ -352,7 +390,7 @@ struct DensityAddOperationExecutor { } const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( - eCurvesSymmetryType(curves_id_->symmetry)); + eCurvesSymmetryType(curves_id_orig_->symmetry)); for (const float4x4 &brush_transform : symmetry_brush_transforms) { const float3 brush_pos_cu = brush_transform * brush_3d->position_cu; const float3 brush_pos_su = transforms_.curves_to_surface * brush_pos_cu; @@ -360,45 +398,54 @@ struct DensityAddOperationExecutor { transforms_.curves_to_surface, brush_pos_cu, brush_3d->radius_cu); const float brush_radius_sq_su = pow2f(brush_radius_su); - Vector looptri_indices; + Vector selected_looptri_indices; BLI_bvhtree_range_query_cpp( - *surface_bvh_.tree, + *surface_bvh_eval_.tree, brush_pos_su, brush_radius_su, [&](const int index, const float3 &UNUSED(co), const float UNUSED(dist_sq)) { - looptri_indices.append(index); + selected_looptri_indices.append(index); }); const float brush_plane_area_su = M_PI * brush_radius_sq_su; const float approximate_density_su = brush_settings_->density_add_attempts / brush_plane_area_su; + Vector positions_su; + Vector bary_coords; + Vector looptri_indices; const int new_points = bke::mesh_surface_sample::sample_surface_points_spherical( rng, - *surface_, - looptri_indices, + *surface_eval_, + selected_looptri_indices, brush_pos_su, brush_radius_su, approximate_density_su, - r_bary_coords, - r_looptri_indices, - r_positions_su); + bary_coords, + looptri_indices, + positions_su); /* Remove some sampled points randomly based on the brush falloff and strength. */ - const int old_points = r_bary_coords.size() - new_points; - for (int i = r_bary_coords.size() - 1; i >= old_points; i--) { - const float3 pos_su = r_positions_su[i]; + for (int i = new_points - 1; i >= 0; i--) { + const float3 pos_su = positions_su[i]; const float3 pos_cu = transforms_.surface_to_curves * pos_su; const float dist_to_brush_cu = math::distance(pos_cu, brush_pos_cu); const float radius_falloff = BKE_brush_curve_strength( brush_, dist_to_brush_cu, brush_3d->radius_cu); const float weight = brush_strength_ * radius_falloff; if (rng.get_float() > weight) { - r_bary_coords.remove_and_reorder(i); - r_looptri_indices.remove_and_reorder(i); - r_positions_su.remove_and_reorder(i); + bary_coords.remove_and_reorder(i); + looptri_indices.remove_and_reorder(i); + positions_su.remove_and_reorder(i); } } + + for (const int i : bary_coords.index_range()) { + const float2 uv = bke::mesh_surface_sample::sample_corner_attrribute_with_bary_coords( + bary_coords[i], surface_looptris_eval_[looptri_indices[i]], surface_uv_map_eval_); + r_uvs.append(uv); + } + r_positions_su.extend(positions_su); } } }; @@ -414,6 +461,13 @@ class DensitySubtractOperation : public CurvesSculptStrokeOperation { private: friend struct DensitySubtractOperationExecutor; + /** + * Deformed root positions of curves that still exist. This has to be stored in case the brush is + * executed more than once before the curves are evaluated again. This can happen when the mouse + * is moved quickly and the brush spacing is small. + */ + Vector deformed_root_positions_; + public: void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override; }; @@ -433,8 +487,12 @@ struct DensitySubtractOperationExecutor { Vector selected_curve_indices_; IndexMask curve_selection_; - Object *surface_ob_ = nullptr; - Mesh *surface_ = nullptr; + Object *surface_ob_orig_ = nullptr; + Mesh *surface_orig_ = nullptr; + + Object *surface_ob_eval_ = nullptr; + Mesh *surface_eval_ = nullptr; + BVHTreeFromMesh surface_bvh_eval_; const CurvesSculpt *curves_sculpt_ = nullptr; const Brush *brush_ = nullptr; @@ -446,7 +504,6 @@ struct DensitySubtractOperationExecutor { float minimum_distance_; CurvesSurfaceTransforms transforms_; - BVHTreeFromMesh surface_bvh_; KDTree_3d *root_points_kdtree_; @@ -468,11 +525,20 @@ struct DensitySubtractOperationExecutor { return; } - surface_ob_ = curves_id_->surface; - if (surface_ob_ == nullptr) { + surface_ob_orig_ = curves_id_->surface; + if (surface_ob_orig_ == nullptr) { + return; + } + surface_orig_ = static_cast(surface_ob_orig_->data); + + surface_ob_eval_ = DEG_get_evaluated_object(ctx_.depsgraph, surface_ob_orig_); + if (surface_ob_eval_ == nullptr) { return; } - surface_ = static_cast(surface_ob_->data); + surface_eval_ = BKE_object_get_evaluated_mesh(surface_ob_eval_); + + BKE_bvhtree_from_mesh_get(&surface_bvh_eval_, surface_eval_, BVHTREE_FROM_LOOPTRI, 2); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_eval_); }); curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt; brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint); @@ -488,16 +554,20 @@ struct DensitySubtractOperationExecutor { transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface); const eBrushFalloffShape falloff_shape = static_cast( brush_->falloff_shape); - BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2); - BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); }); - const Span positions_cu = curves_->positions(); + if (stroke_extension.is_first) { + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); + for (const int curve_i : curves_->curves_range()) { + const int first_point_i = curves_->offsets()[curve_i]; + self_->deformed_root_positions_.append(deformation.positions[first_point_i]); + } + } root_points_kdtree_ = BLI_kdtree_3d_new(curve_selection_.size()); BLI_SCOPED_DEFER([&]() { BLI_kdtree_3d_free(root_points_kdtree_); }); for (const int curve_i : curve_selection_) { - const int first_point_i = curves_->offsets()[curve_i]; - const float3 &pos_cu = positions_cu[first_point_i]; + const float3 &pos_cu = self_->deformed_root_positions_[curve_i]; BLI_kdtree_3d_insert(root_points_kdtree_, curve_i, pos_cu); } BLI_kdtree_3d_balance(root_points_kdtree_); @@ -515,12 +585,23 @@ struct DensitySubtractOperationExecutor { } Vector indices; - const IndexMask mask = index_mask_ops::find_indices_based_on_predicate( + const IndexMask mask_to_delete = index_mask_ops::find_indices_based_on_predicate( curves_->curves_range(), 4096, indices, [&](const int curve_i) { return curves_to_delete[curve_i]; }); - curves_->remove_curves(mask); + /* Remove deleted curves fromt he stored deformed root positions. */ + const Vector ranges_to_keep = mask_to_delete.extract_ranges_invert( + curves_->curves_range()); + BLI_assert(curves_->curves_num() == self_->deformed_root_positions_.size()); + Vector new_deformed_positions; + for (const IndexRange range : ranges_to_keep) { + new_deformed_positions.extend(self_->deformed_root_positions_.as_span().slice(range)); + } + self_->deformed_root_positions_ = std::move(new_deformed_positions); + + curves_->remove_curves(mask_to_delete); + BLI_assert(curves_->curves_num() == self_->deformed_root_positions_.size()); DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); @@ -539,15 +620,12 @@ struct DensitySubtractOperationExecutor { void reduce_density_projected(const float4x4 &brush_transform, MutableSpan curves_to_delete) { - const Span positions_cu = curves_->positions(); const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_; const float brush_radius_sq_re = pow2f(brush_radius_re); float4x4 projection; ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); - const Span offsets = curves_->offsets(); - /* Randomly select the curves that are allowed to be removed, based on the brush radius and * strength. */ Array allow_remove_curve(curves_->curves_num(), false); @@ -559,8 +637,7 @@ struct DensitySubtractOperationExecutor { allow_remove_curve[curve_i] = true; continue; } - const int first_point_i = offsets[curve_i]; - const float3 pos_cu = brush_transform * positions_cu[first_point_i]; + const float3 pos_cu = brush_transform * self_->deformed_root_positions_[curve_i]; float2 pos_re; ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values); @@ -586,8 +663,7 @@ struct DensitySubtractOperationExecutor { if (!allow_remove_curve[curve_i]) { continue; } - const int first_point_i = offsets[curve_i]; - const float3 orig_pos_cu = positions_cu[first_point_i]; + const float3 orig_pos_cu = self_->deformed_root_positions_[curve_i]; const float3 pos_cu = brush_transform * orig_pos_cu; float2 pos_re; ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values); @@ -618,7 +694,7 @@ struct DensitySubtractOperationExecutor { *ctx_.region, *ctx_.v3d, transforms_, - surface_bvh_, + surface_bvh_eval_, brush_pos_re_, brush_radius_re); if (!brush_3d.has_value()) { @@ -638,8 +714,6 @@ struct DensitySubtractOperationExecutor { MutableSpan curves_to_delete) { const float brush_radius_sq_cu = pow2f(brush_radius_cu); - const Span positions_cu = curves_->positions(); - const Span offsets = curves_->offsets(); /* Randomly select the curves that are allowed to be removed, based on the brush radius and * strength. */ @@ -652,8 +726,7 @@ struct DensitySubtractOperationExecutor { allow_remove_curve[curve_i] = true; continue; } - const int first_point_i = offsets[curve_i]; - const float3 pos_cu = positions_cu[first_point_i]; + const float3 pos_cu = self_->deformed_root_positions_[curve_i]; const float dist_to_brush_sq_cu = math::distance_squared(brush_pos_cu, pos_cu); if (dist_to_brush_sq_cu > brush_radius_sq_cu) { @@ -677,8 +750,7 @@ struct DensitySubtractOperationExecutor { if (!allow_remove_curve[curve_i]) { continue; } - const int first_point_i = offsets[curve_i]; - const float3 &pos_cu = positions_cu[first_point_i]; + const float3 &pos_cu = self_->deformed_root_positions_[curve_i]; const float dist_to_brush_sq_cu = math::distance_squared(pos_cu, brush_pos_cu); if (dist_to_brush_sq_cu > brush_radius_sq_cu) { continue; @@ -717,6 +789,10 @@ static bool use_add_density_mode(const BrushStrokeMode brush_mode, { const Scene &scene = *CTX_data_scene(&C); const Brush &brush = *BKE_paint_brush_for_read(&scene.toolsettings->curves_sculpt->paint); + const Depsgraph &depsgraph = *CTX_data_depsgraph_on_load(&C); + const ARegion ®ion = *CTX_wm_region(&C); + const View3D &v3d = *CTX_wm_view3d(&C); + const eBrushCurvesSculptDensityMode density_mode = static_cast( brush.curves_sculpt_settings->density_mode); const bool use_invert = brush_mode == BRUSH_STROKE_INVERT; @@ -728,26 +804,29 @@ static bool use_add_density_mode(const BrushStrokeMode brush_mode, return use_invert; } - const Object &curves_ob = *CTX_data_active_object(&C); - const Curves &curves_id = *static_cast(curves_ob.data); - const CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); - if (curves_id.surface == nullptr) { - /* The brush won't do anything in this case anyway. */ + const Object &curves_ob_orig = *CTX_data_active_object(&C); + const Curves &curves_id_orig = *static_cast(curves_ob_orig.data); + Object *surface_ob_orig = curves_id_orig.surface; + if (surface_ob_orig == nullptr) { return true; } + Object *surface_ob_eval = DEG_get_evaluated_object(&depsgraph, surface_ob_orig); + if (surface_ob_eval == nullptr) { + return true; + } + const CurvesGeometry &curves = CurvesGeometry::wrap(curves_id_orig.geometry); if (curves.curves_num() <= 1) { return true; } + const Mesh *surface_mesh_eval = BKE_object_get_evaluated_mesh(surface_ob_eval); + if (surface_mesh_eval == nullptr) { + return true; + } - const CurvesSurfaceTransforms transforms(curves_ob, curves_id.surface); - BVHTreeFromMesh surface_bvh; - BKE_bvhtree_from_mesh_get( - &surface_bvh, static_cast(curves_id.surface->data), BVHTREE_FROM_LOOPTRI, 2); - BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh); }); - - const Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(&C); - const ARegion ®ion = *CTX_wm_region(&C); - const View3D &v3d = *CTX_wm_view3d(&C); + const CurvesSurfaceTransforms transforms(curves_ob_orig, curves_id_orig.surface); + BVHTreeFromMesh surface_bvh_eval; + BKE_bvhtree_from_mesh_get(&surface_bvh_eval, surface_mesh_eval, BVHTREE_FROM_LOOPTRI, 2); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_eval); }); const float2 brush_pos_re = stroke_start.mouse_position; /* Reduce radius so that only an inner circle is used to determine the existing density. */ @@ -755,7 +834,7 @@ static bool use_add_density_mode(const BrushStrokeMode brush_mode, /* Find the surface point under the brush. */ const std::optional brush_3d = sample_curves_surface_3d_brush( - depsgraph, region, v3d, transforms, surface_bvh, brush_pos_re, brush_radius_re); + depsgraph, region, v3d, transforms, surface_bvh_eval, brush_pos_re, brush_radius_re); if (!brush_3d.has_value()) { return true; } @@ -764,8 +843,9 @@ static bool use_add_density_mode(const BrushStrokeMode brush_mode, const float brush_radius_cu = brush_3d->radius_cu; const float brush_radius_sq_cu = pow2f(brush_radius_cu); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(depsgraph, curves_ob_orig); const Span offsets = curves.offsets(); - const Span positions_cu = curves.positions(); /* Compute distance from brush to curve roots. */ Array> distances_sq_to_brush(curves.curves_num()); @@ -774,7 +854,7 @@ static bool use_add_density_mode(const BrushStrokeMode brush_mode, int &valid_curve_count = valid_curve_count_by_thread.local(); for (const int curve_i : range) { const int root_point_i = offsets[curve_i]; - const float3 &root_pos_cu = positions_cu[root_point_i]; + const float3 &root_pos_cu = deformation.positions[root_point_i]; const float dist_sq_cu = math::distance_squared(root_pos_cu, brush_pos_cu); if (dist_sq_cu < brush_radius_sq_cu) { distances_sq_to_brush[curve_i] = {math::distance_squared(root_pos_cu, brush_pos_cu), @@ -799,9 +879,9 @@ static bool use_add_density_mode(const BrushStrokeMode brush_mode, * center. */ float min_dist_sq_cu = FLT_MAX; for (const int i : IndexRange(check_curve_count)) { - const float3 &pos_i = positions_cu[offsets[distances_sq_to_brush[i].second]]; + const float3 &pos_i = deformation.positions[offsets[distances_sq_to_brush[i].second]]; for (int j = i + 1; j < check_curve_count; j++) { - const float3 &pos_j = positions_cu[offsets[distances_sq_to_brush[j].second]]; + const float3 &pos_j = deformation.positions[offsets[distances_sq_to_brush[j].second]]; const float dist_sq_cu = math::distance_squared(pos_i, pos_j); math::min_inplace(min_dist_sq_cu, dist_sq_cu); } diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc b/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc index 8ef18ba7da7..1ee43d98e6f 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc @@ -342,7 +342,8 @@ struct CurvesEffectOperationExecutor { void gather_influences_projected( threading::EnumerableThreadSpecific &influences_for_thread) { - const Span positions_cu = curves_->positions(); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); float4x4 projection; ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); @@ -368,8 +369,8 @@ struct CurvesEffectOperationExecutor { float max_move_distance_cu = 0.0f; for (const float4x4 &brush_transform_inv : symmetry_brush_transforms_inv) { for (const int segment_i : points.drop_back(1)) { - const float3 p1_cu = brush_transform_inv * positions_cu[segment_i]; - const float3 p2_cu = brush_transform_inv * positions_cu[segment_i + 1]; + const float3 p1_cu = brush_transform_inv * deformation.positions[segment_i]; + const float3 p2_cu = brush_transform_inv * deformation.positions[segment_i + 1]; float2 p1_re, p2_re; ED_view3d_project_float_v2_m4(ctx_.region, p1_cu, p1_re, projection.values); @@ -430,7 +431,8 @@ struct CurvesEffectOperationExecutor { void gather_influences_spherical( threading::EnumerableThreadSpecific &influences_for_thread) { - const Span positions_cu = curves_->positions(); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); float3 brush_pos_start_wo, brush_pos_end_wo; ED_view3d_win_to_3d(ctx_.v3d, @@ -468,8 +470,8 @@ struct CurvesEffectOperationExecutor { const float3 brush_pos_end_transformed_cu = brush_transform * brush_pos_end_cu; for (const int segment_i : points.drop_back(1)) { - const float3 &p1_cu = positions_cu[segment_i]; - const float3 &p2_cu = positions_cu[segment_i + 1]; + const float3 &p1_cu = deformation.positions[segment_i]; + const float3 &p2_cu = deformation.positions[segment_i + 1]; float3 closest_on_segment_cu; float3 closest_on_brush_cu; diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh index 61aa7d201b1..4bb00a7d621 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh +++ b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh @@ -12,6 +12,7 @@ #include "BLI_virtual_array.hh" #include "BKE_attribute.h" +#include "BKE_crazyspace.hh" #include "BKE_curves.hh" #include "ED_curves_sculpt.h" @@ -24,6 +25,7 @@ struct Object; struct Brush; struct Scene; struct BVHTreeFromMesh; +struct ReportList; namespace blender::ed::sculpt_paint { @@ -34,6 +36,7 @@ struct StrokeExtension { bool is_first; float2 mouse_position; float pressure; + ReportList *reports = nullptr; }; float brush_radius_factor(const Brush &brush, const StrokeExtension &stroke_extension); @@ -55,8 +58,7 @@ class CurvesSculptStrokeOperation { virtual void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) = 0; }; -std::unique_ptr new_add_operation(const bContext &C, - ReportList *reports); +std::unique_ptr new_add_operation(); std::unique_ptr new_comb_operation(); std::unique_ptr new_delete_operation(); std::unique_ptr new_snake_hook_operation(); @@ -126,4 +128,8 @@ float transform_brush_radius(const float4x4 &transform, const float3 &brush_position, const float old_radius); +void report_missing_surface(ReportList *reports); +void report_missing_uv_map_on_original_surface(ReportList *reports); +void report_missing_uv_map_on_evaluated_surface(ReportList *reports); + } // namespace blender::ed::sculpt_paint diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc index 47e0fe3a61a..9271309d922 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc @@ -9,6 +9,8 @@ #include "BKE_bvhutils.h" #include "BKE_context.h" #include "BKE_curves.hh" +#include "BKE_modifier.h" +#include "BKE_object.h" #include "BKE_paint.h" #include "WM_api.h" @@ -24,6 +26,7 @@ #include "ED_view3d.h" #include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" #include "DNA_brush_types.h" #include "DNA_curves_types.h" @@ -122,7 +125,7 @@ static std::unique_ptr start_brush_operation( case CURVES_SCULPT_TOOL_SNAKE_HOOK: return new_snake_hook_operation(); case CURVES_SCULPT_TOOL_ADD: - return new_add_operation(C, op.reports); + return new_add_operation(); case CURVES_SCULPT_TOOL_GROW_SHRINK: return new_grow_shrink_operation(mode, C); case CURVES_SCULPT_TOOL_SELECTION_PAINT: @@ -176,6 +179,7 @@ static void stroke_update_step(bContext *C, StrokeExtension stroke_extension; RNA_float_get_array(stroke_element, "mouse", stroke_extension.mouse_position); stroke_extension.pressure = RNA_float_get(stroke_element, "pressure"); + stroke_extension.reports = op->reports; if (!op_data->operation) { stroke_extension.is_first = true; @@ -1129,14 +1133,21 @@ static int min_distance_edit_invoke(bContext *C, wmOperator *op, const wmEvent * View3D *v3d = CTX_wm_view3d(C); Scene *scene = CTX_data_scene(C); - Object &curves_ob = *CTX_data_active_object(C); - Curves &curves_id = *static_cast(curves_ob.data); - Object &surface_ob = *curves_id.surface; - Mesh &surface_me = *static_cast(surface_ob.data); + Object &curves_ob_orig = *CTX_data_active_object(C); + Curves &curves_id_orig = *static_cast(curves_ob_orig.data); + Object &surface_ob_orig = *curves_id_orig.surface; + Object *surface_ob_eval = DEG_get_evaluated_object(depsgraph, &surface_ob_orig); + if (surface_ob_eval == nullptr) { + return OPERATOR_CANCELLED; + } + Mesh *surface_me_eval = BKE_object_get_evaluated_mesh(surface_ob_eval); + if (surface_me_eval == nullptr) { + return OPERATOR_CANCELLED; + } - BVHTreeFromMesh surface_bvh; - BKE_bvhtree_from_mesh_get(&surface_bvh, &surface_me, BVHTREE_FROM_LOOPTRI, 2); - BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh); }); + BVHTreeFromMesh surface_bvh_eval; + BKE_bvhtree_from_mesh_get(&surface_bvh_eval, surface_me_eval, BVHTREE_FROM_LOOPTRI, 2); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_eval); }); const int2 mouse_pos_int_re{event->mval}; const float2 mouse_pos_re{mouse_pos_int_re}; @@ -1145,23 +1156,22 @@ static int min_distance_edit_invoke(bContext *C, wmOperator *op, const wmEvent * ED_view3d_win_to_segment_clipped( depsgraph, region, v3d, mouse_pos_re, ray_start_wo, ray_end_wo, true); - const float4x4 surface_to_world_mat = surface_ob.obmat; - const float4x4 world_to_surface_mat = surface_to_world_mat.inverted(); + const CurvesSurfaceTransforms transforms{curves_ob_orig, &surface_ob_orig}; - const float3 ray_start_su = world_to_surface_mat * ray_start_wo; - const float3 ray_end_su = world_to_surface_mat * ray_end_wo; + const float3 ray_start_su = transforms.world_to_surface * ray_start_wo; + const float3 ray_end_su = transforms.world_to_surface * ray_end_wo; const float3 ray_direction_su = math::normalize(ray_end_su - ray_start_su); BVHTreeRayHit ray_hit; ray_hit.dist = FLT_MAX; ray_hit.index = -1; - BLI_bvhtree_ray_cast(surface_bvh.tree, + BLI_bvhtree_ray_cast(surface_bvh_eval.tree, ray_start_su, ray_direction_su, 0.0f, &ray_hit, - surface_bvh.raycast_callback, - &surface_bvh); + surface_bvh_eval.raycast_callback, + &surface_bvh_eval); if (ray_hit.index == -1) { WM_report(RPT_ERROR, "Cursor must be over the surface mesh"); return OPERATOR_CANCELLED; @@ -1169,16 +1179,13 @@ static int min_distance_edit_invoke(bContext *C, wmOperator *op, const wmEvent * const float3 hit_pos_su = ray_hit.co; const float3 hit_normal_su = ray_hit.no; - const float4x4 curves_to_world_mat = curves_ob.obmat; - const float4x4 world_to_curves_mat = curves_to_world_mat.inverted(); - const float4x4 surface_to_curves_mat = world_to_curves_mat * surface_to_world_mat; - const float4x4 surface_to_curves_normal_mat = surface_to_curves_mat.inverted().transposed(); - const float3 hit_pos_cu = surface_to_curves_mat * hit_pos_su; - const float3 hit_normal_cu = math::normalize(surface_to_curves_normal_mat * hit_normal_su); + const float3 hit_pos_cu = transforms.surface_to_curves * hit_pos_su; + const float3 hit_normal_cu = math::normalize(transforms.surface_to_curves_normal * + hit_normal_su); MinDistanceEditData *op_data = MEM_new(__func__); - op_data->curves_to_world_mat = curves_to_world_mat; + op_data->curves_to_world_mat = transforms.curves_to_world; op_data->normal_cu = hit_normal_cu; op_data->pos_cu = hit_pos_cu; op_data->initial_mouse = event->xy; diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc b/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc index 689b7d22e5e..3e43b1a6361 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc @@ -157,6 +157,9 @@ struct PinchOperationExecutor { { const float4x4 brush_transform_inv = brush_transform.inverted(); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); + float4x4 projection; ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); MutableSpan positions_cu = curves_->positions_for_write(); @@ -167,11 +170,13 @@ struct PinchOperationExecutor { for (const int curve_i : curve_selection_.slice(range)) { const IndexRange points = curves_->points_for_curve(curve_i); for (const int point_i : points.drop_front(1)) { - const float3 old_pos_cu = brush_transform_inv * positions_cu[point_i]; - float2 old_pos_re; - ED_view3d_project_float_v2_m4(ctx_.region, old_pos_cu, old_pos_re, projection.values); + const float3 old_pos_cu = deformation.positions[point_i]; + const float3 old_symm_pos_cu = brush_transform_inv * old_pos_cu; + float2 old_symm_pos_re; + ED_view3d_project_float_v2_m4( + ctx_.region, old_symm_pos_cu, old_symm_pos_re, projection.values); - const float dist_to_brush_sq_re = math::distance_squared(old_pos_re, brush_pos_re_); + const float dist_to_brush_sq_re = math::distance_squared(old_symm_pos_re, brush_pos_re_); if (dist_to_brush_sq_re > brush_radius_sq_re) { continue; } @@ -182,14 +187,21 @@ struct PinchOperationExecutor { const float weight = invert_factor_ * 0.1f * brush_strength_ * radius_falloff * point_factors_[point_i]; - const float2 new_pos_re = math::interpolate(old_pos_re, brush_pos_re_, weight); - - const float3 old_pos_wo = transforms_.curves_to_world * old_pos_cu; - float3 new_pos_wo; - ED_view3d_win_to_3d(ctx_.v3d, ctx_.region, old_pos_wo, new_pos_re, new_pos_wo); - - const float3 new_pos_cu = transforms_.world_to_curves * new_pos_wo; - positions_cu[point_i] = brush_transform * new_pos_cu; + const float2 new_symm_pos_re = math::interpolate(old_symm_pos_re, brush_pos_re_, weight); + + float3 new_symm_pos_wo; + ED_view3d_win_to_3d(ctx_.v3d, + ctx_.region, + transforms_.curves_to_world * old_symm_pos_cu, + new_symm_pos_re, + new_symm_pos_wo); + + const float3 new_pos_cu = brush_transform * transforms_.world_to_curves * + new_symm_pos_wo; + const float3 translation_eval = new_pos_cu - old_pos_cu; + const float3 translation_orig = deformation.translation_from_deformed_to_original( + point_i, translation_eval); + positions_cu[point_i] += translation_orig; r_changed_curves[curve_i] = true; } } @@ -221,11 +233,14 @@ struct PinchOperationExecutor { MutableSpan positions_cu = curves_->positions_for_write(); const float brush_radius_sq_cu = pow2f(brush_radius_cu); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { for (const int curve_i : curve_selection_.slice(range)) { const IndexRange points = curves_->points_for_curve(curve_i); for (const int point_i : points.drop_front(1)) { - const float3 old_pos_cu = positions_cu[point_i]; + const float3 old_pos_cu = deformation.positions[point_i]; const float dist_to_brush_sq_cu = math::distance_squared(old_pos_cu, brush_pos_cu); if (dist_to_brush_sq_cu > brush_radius_sq_cu) { @@ -239,7 +254,10 @@ struct PinchOperationExecutor { point_factors_[point_i]; const float3 new_pos_cu = math::interpolate(old_pos_cu, brush_pos_cu, weight); - positions_cu[point_i] = new_pos_cu; + const float3 translation_eval = new_pos_cu - old_pos_cu; + const float3 translation_orig = deformation.translation_from_deformed_to_original( + point_i, translation_eval); + positions_cu[point_i] += translation_orig; r_changed_curves[curve_i] = true; } diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc b/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc index 83cfda6dc00..139e0d67e89 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc @@ -4,6 +4,7 @@ #include "BKE_brush.h" #include "BKE_bvhutils.h" #include "BKE_context.h" +#include "BKE_crazyspace.hh" #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" @@ -20,6 +21,8 @@ #include "BLI_length_parameterize.hh" +#include "GEO_add_curves_on_mesh.hh" + #include "curves_sculpt_intern.hh" namespace blender::ed::sculpt_paint { @@ -38,23 +41,6 @@ class PuffOperation : public CurvesSculptStrokeOperation { void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override; }; -static float3 compute_surface_point_normal(const MLoopTri &looptri, - const float3 &bary_coord, - const Span corner_normals) -{ - const int l0 = looptri.tri[0]; - const int l1 = looptri.tri[1]; - const int l2 = looptri.tri[2]; - - const float3 &l0_normal = corner_normals[l0]; - const float3 &l1_normal = corner_normals[l1]; - const float3 &l2_normal = corner_normals[l2]; - - const float3 normal = math::normalize( - attribute_math::mix3(bary_coord, l0_normal, l1_normal, l2_normal)); - return normal; -} - /** * Utility class that actually executes the update when the stroke is updated. That's useful * because it avoids passing a very large number of parameters between functions. @@ -183,8 +169,6 @@ struct PuffOperationExecutor { void find_curve_weights_projected(const float4x4 &brush_transform, MutableSpan r_curve_weights) { - Span positions_cu = curves_->positions(); - const float4x4 brush_transform_inv = brush_transform.inverted(); float4x4 projection; @@ -193,15 +177,18 @@ struct PuffOperationExecutor { const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_; const float brush_radius_sq_re = pow2f(brush_radius_re); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { for (const int curve_selection_i : range) { const int curve_i = curve_selection_[curve_selection_i]; const IndexRange points = curves_->points_for_curve(curve_i); - const float3 first_pos_cu = brush_transform_inv * positions_cu[points[0]]; + const float3 first_pos_cu = brush_transform_inv * deformation.positions[points[0]]; float2 prev_pos_re; ED_view3d_project_float_v2_m4(ctx_.region, first_pos_cu, prev_pos_re, projection.values); for (const int point_i : points.drop_front(1)) { - const float3 pos_cu = brush_transform_inv * positions_cu[point_i]; + const float3 pos_cu = brush_transform_inv * deformation.positions[point_i]; float2 pos_re; ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values); BLI_SCOPED_DEFER([&]() { prev_pos_re = pos_re; }); @@ -248,16 +235,18 @@ struct PuffOperationExecutor { const float brush_radius_cu, MutableSpan r_curve_weights) { - const Span positions_cu = curves_->positions(); const float brush_radius_sq_cu = pow2f(brush_radius_cu); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { for (const int curve_selection_i : range) { const int curve_i = curve_selection_[curve_selection_i]; const IndexRange points = curves_->points_for_curve(curve_i); for (const int point_i : points.drop_front(1)) { - const float3 &prev_pos_cu = positions_cu[point_i - 1]; - const float3 &pos_cu = positions_cu[point_i]; + const float3 &prev_pos_cu = deformation.positions[point_i - 1]; + const float3 &pos_cu = deformation.positions[point_i]; const float dist_to_brush_sq_cu = dist_squared_to_line_segment_v3( brush_pos_cu, prev_pos_cu, pos_cu); if (dist_to_brush_sq_cu > brush_radius_sq_cu) { @@ -305,7 +294,7 @@ struct PuffOperationExecutor { const float3 &v2_su = surface_->mvert[surface_->mloop[looptri.tri[2]].v].co; float3 bary_coords; interp_weights_tri_v3(bary_coords, v0_su, v1_su, v2_su, closest_pos_su); - const float3 normal_su = compute_surface_point_normal( + const float3 normal_su = geometry::compute_surface_point_normal( looptri, bary_coords, corner_normals_su_); const float3 normal_cu = math::normalize(transforms_.surface_to_curves_normal * normal_su); diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc b/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc index 399d2c73ec3..cc5a5e7ae8a 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc @@ -161,14 +161,15 @@ struct SelectionPaintOperationExecutor { float4x4 projection; ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); - Span positions_cu = curves_->positions(); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_; const float brush_radius_sq_re = pow2f(brush_radius_re); threading::parallel_for(curves_->points_range(), 1024, [&](const IndexRange point_range) { for (const int point_i : point_range) { - const float3 pos_cu = brush_transform_inv * positions_cu[point_i]; + const float3 pos_cu = brush_transform_inv * deformation.positions[point_i]; /* Find the position of the point in screen space. */ float2 pos_re; @@ -215,14 +216,15 @@ struct SelectionPaintOperationExecutor { void paint_point_selection_spherical(MutableSpan selection, const float3 &brush_cu) { - Span positions_cu = curves_->positions(); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); const float brush_radius_cu = self_->brush_3d_.radius_cu; const float brush_radius_sq_cu = pow2f(brush_radius_cu); threading::parallel_for(curves_->points_range(), 1024, [&](const IndexRange point_range) { for (const int i : point_range) { - const float3 pos_old_cu = positions_cu[i]; + const float3 pos_old_cu = deformation.positions[i]; /* Compute distance to the brush. */ const float distance_to_brush_sq_cu = math::distance_squared(pos_old_cu, brush_cu); @@ -256,9 +258,11 @@ struct SelectionPaintOperationExecutor { void paint_curve_selection_projected(const float4x4 &brush_transform, MutableSpan selection) { - const Span positions_cu = curves_->positions(); const float4x4 brush_transform_inv = brush_transform.inverted(); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); + float4x4 projection; ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); @@ -274,8 +278,8 @@ struct SelectionPaintOperationExecutor { [&](const IndexRange segment_range, const float init) { float max_weight = init; for (const int segment_i : segment_range) { - const float3 pos1_cu = brush_transform_inv * positions_cu[segment_i]; - const float3 pos2_cu = brush_transform_inv * positions_cu[segment_i + 1]; + const float3 pos1_cu = brush_transform_inv * deformation.positions[segment_i]; + const float3 pos2_cu = brush_transform_inv * deformation.positions[segment_i + 1]; float2 pos1_re; float2 pos2_re; @@ -323,7 +327,8 @@ struct SelectionPaintOperationExecutor { void paint_curve_selection_spherical(MutableSpan selection, const float3 &brush_cu) { - const Span positions_cu = curves_->positions(); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); const float brush_radius_cu = self_->brush_3d_.radius_cu; const float brush_radius_sq_cu = pow2f(brush_radius_cu); @@ -337,8 +342,8 @@ struct SelectionPaintOperationExecutor { [&](const IndexRange segment_range, const float init) { float max_weight = init; for (const int segment_i : segment_range) { - const float3 &pos1_cu = positions_cu[segment_i]; - const float3 &pos2_cu = positions_cu[segment_i + 1]; + const float3 &pos1_cu = deformation.positions[segment_i]; + const float3 &pos2_cu = deformation.positions[segment_i + 1]; const float distance_sq_cu = dist_squared_to_line_segment_v3( brush_cu, pos1_cu, pos2_cu); diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc b/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc index aabe6fd93e4..443fbcb883c 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc @@ -4,6 +4,7 @@ #include "curves_sculpt_intern.hh" +#include "BLI_float3x3.hh" #include "BLI_float4x4.hh" #include "BLI_vector.hh" @@ -20,14 +21,16 @@ #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" #include "BKE_mesh_sample.hh" +#include "BKE_modifier.h" +#include "BKE_object.h" #include "BKE_paint.h" +#include "BKE_report.h" #include "DNA_brush_enums.h" #include "DNA_brush_types.h" #include "DNA_curves_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" -#include "DNA_object_types.h" #include "DNA_screen_types.h" #include "DNA_space_types.h" @@ -38,13 +41,27 @@ #include "WM_api.h" +#include "DEG_depsgraph_query.h" + +#include "GEO_add_curves_on_mesh.hh" +#include "GEO_reverse_uv_sampler.hh" + +#include "BLT_translation.h" + namespace blender::ed::sculpt_paint { +using geometry::ReverseUVSampler; + struct SlideCurveInfo { /** Index of the curve to slide. */ int curve_i; /** A weight based on the initial distance to the brush. */ float radius_falloff; + /** + * Normal of the surface where the curve was attached. This is used to rotate the curve if it is + * moved to a place with a different normal. + */ + float3 initial_normal_cu; }; struct SlideInfo { @@ -55,10 +72,13 @@ struct SlideInfo { class SlideOperation : public CurvesSculptStrokeOperation { private: - /** Last mouse position. */ - float2 brush_pos_last_re_; + float2 initial_brush_pos_re_; /** Information about which curves to slide. This is initialized when the brush starts. */ Vector slide_info_; + /** Positions of all curve points at the start of sliding. */ + Array initial_positions_cu_; + /** Deformed positions of all curve points at the start of sliding. */ + Array initial_deformed_positions_cu_; friend struct SlideOperationExecutor; @@ -80,27 +100,31 @@ struct SlideOperationExecutor { float brush_radius_factor_; float brush_strength_; - Object *object_ = nullptr; - Curves *curves_id_ = nullptr; - CurvesGeometry *curves_ = nullptr; + Object *curves_ob_orig_ = nullptr; + Curves *curves_id_orig_ = nullptr; + CurvesGeometry *curves_orig_ = nullptr; - Object *surface_ob_ = nullptr; - Mesh *surface_ = nullptr; - Span surface_looptris_; - VArraySpan surface_uv_map_; + Object *surface_ob_orig_ = nullptr; + Mesh *surface_orig_ = nullptr; + Span surface_looptris_orig_; + VArraySpan surface_uv_map_orig_; + Span corner_normals_orig_su_; + + Object *surface_ob_eval_ = nullptr; + Mesh *surface_eval_ = nullptr; + Span surface_looptris_eval_; + VArraySpan surface_uv_map_eval_; + BVHTreeFromMesh surface_bvh_eval_; VArray curve_factors_; - VArray point_factors_; Vector selected_curve_indices_; IndexMask curve_selection_; - float2 brush_pos_prev_re_; float2 brush_pos_re_; - float2 brush_pos_diff_re_; CurvesSurfaceTransforms transforms_; - BVHTreeFromMesh surface_bvh_; + std::atomic found_invalid_uv_mapping_{false}; SlideOperationExecutor(const bContext &C) : ctx_(C) { @@ -111,15 +135,21 @@ struct SlideOperationExecutor { UNUSED_VARS(C, stroke_extension); self_ = &self; - object_ = CTX_data_active_object(&C); - curves_id_ = static_cast(object_->data); - curves_ = &CurvesGeometry::wrap(curves_id_->geometry); - if (curves_id_->surface == nullptr || curves_id_->surface->type != OB_MESH) { + curves_ob_orig_ = CTX_data_active_object(&C); + curves_id_orig_ = static_cast(curves_ob_orig_->data); + curves_orig_ = &CurvesGeometry::wrap(curves_id_orig_->geometry); + if (curves_id_orig_->surface == nullptr || curves_id_orig_->surface->type != OB_MESH) { + report_missing_surface(stroke_extension.reports); + return; + } + if (curves_orig_->curves_num() == 0) { return; } - if (curves_->curves_num() == 0) { + if (curves_id_orig_->surface_uv_map == nullptr) { + report_missing_uv_map_on_original_surface(stroke_extension.reports); return; } + const StringRefNull uv_map_name = curves_id_orig_->surface_uv_map; curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt; brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint); @@ -127,169 +157,312 @@ struct SlideOperationExecutor { brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension); brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension); - curve_factors_ = get_curves_selection(*curves_id_); - point_factors_ = get_point_selection(*curves_id_); - curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_); + curve_factors_ = get_curves_selection(*curves_id_orig_); + curve_selection_ = retrieve_selected_curves(*curves_id_orig_, selected_curve_indices_); - brush_pos_prev_re_ = self_->brush_pos_last_re_; brush_pos_re_ = stroke_extension.mouse_position; - brush_pos_diff_re_ = brush_pos_re_ - brush_pos_prev_re_; - BLI_SCOPED_DEFER([&]() { self_->brush_pos_last_re_ = brush_pos_re_; }); - - transforms_ = CurvesSurfaceTransforms(*object_, curves_id_->surface); - surface_ob_ = curves_id_->surface; - surface_ = static_cast(surface_ob_->data); + transforms_ = CurvesSurfaceTransforms(*curves_ob_orig_, curves_id_orig_->surface); - BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2); - BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); }); - - surface_looptris_ = {BKE_mesh_runtime_looptri_ensure(surface_), - BKE_mesh_runtime_looptri_len(surface_)}; + surface_ob_orig_ = curves_id_orig_->surface; + surface_orig_ = static_cast(surface_ob_orig_->data); + surface_looptris_orig_ = {BKE_mesh_runtime_looptri_ensure(surface_orig_), + BKE_mesh_runtime_looptri_len(surface_orig_)}; + surface_uv_map_orig_ = + bke::mesh_attributes(*surface_orig_).lookup(uv_map_name, ATTR_DOMAIN_CORNER); + if (surface_uv_map_orig_.is_empty()) { + report_missing_uv_map_on_original_surface(stroke_extension.reports); + return; + } + if (!CustomData_has_layer(&surface_orig_->ldata, CD_NORMAL)) { + BKE_mesh_calc_normals_split(surface_orig_); + } + corner_normals_orig_su_ = { + reinterpret_cast(CustomData_get_layer(&surface_orig_->ldata, CD_NORMAL)), + surface_orig_->totloop}; - if (curves_id_->surface_uv_map != nullptr) { - const bke::AttributeAccessor surface_attributes = bke::mesh_attributes(*surface_); - surface_uv_map_ = surface_attributes.lookup(curves_id_->surface_uv_map, - ATTR_DOMAIN_CORNER); + surface_ob_eval_ = DEG_get_evaluated_object(ctx_.depsgraph, surface_ob_orig_); + if (surface_ob_eval_ == nullptr) { + return; + } + surface_eval_ = BKE_object_get_evaluated_mesh(surface_ob_eval_); + if (surface_eval_ == nullptr) { + return; } + surface_looptris_eval_ = {BKE_mesh_runtime_looptri_ensure(surface_eval_), + BKE_mesh_runtime_looptri_len(surface_eval_)}; + surface_uv_map_eval_ = + bke::mesh_attributes(*surface_eval_).lookup(uv_map_name, ATTR_DOMAIN_CORNER); + if (surface_uv_map_eval_.is_empty()) { + report_missing_uv_map_on_evaluated_surface(stroke_extension.reports); + return; + } + BKE_bvhtree_from_mesh_get(&surface_bvh_eval_, surface_eval_, BVHTREE_FROM_LOOPTRI, 2); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_eval_); }); if (stroke_extension.is_first) { - const Vector brush_transforms = get_symmetry_brush_transforms( - eCurvesSymmetryType(curves_id_->symmetry)); - for (const float4x4 &brush_transform : brush_transforms) { - this->detect_curves_to_slide(brush_transform); - } + self_->initial_brush_pos_re_ = brush_pos_re_; + /* Remember original and deformed positions of all points. Otherwise this information is lost + * when sliding starts, but it's still used. */ + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *curves_ob_orig_); + self_->initial_positions_cu_ = curves_orig_->positions(); + self_->initial_deformed_positions_cu_ = deformation.positions; + + /* First find all curves to slide. When the mouse moves, only those curves will be moved. */ + this->find_curves_to_slide_with_symmetry(); return; } - this->slide_projected(); + this->slide_with_symmetry(); - curves_->tag_positions_changed(); - DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); - WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); + if (found_invalid_uv_mapping_) { + BKE_report( + stroke_extension.reports, RPT_WARNING, TIP_("UV map or surface attachment is invalid")); + } + + curves_orig_->tag_positions_changed(); + DEG_id_tag_update(&curves_id_orig_->id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_orig_->id); ED_region_tag_redraw(ctx_.region); } - void detect_curves_to_slide(const float4x4 &brush_transform) + void find_curves_to_slide_with_symmetry() { - const float4x4 brush_transform_inv = brush_transform.inverted(); - + const Vector brush_transforms = get_symmetry_brush_transforms( + eCurvesSymmetryType(curves_id_orig_->symmetry)); const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_; - const float brush_radius_sq_re = pow2f(brush_radius_re); - - const Span positions_cu = curves_->positions(); - - float4x4 projection; - ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); + const std::optional brush_3d = sample_curves_surface_3d_brush(*ctx_.depsgraph, + *ctx_.region, + *ctx_.v3d, + transforms_, + surface_bvh_eval_, + brush_pos_re_, + brush_radius_re); + if (!brush_3d.has_value()) { + return; + } + const ReverseUVSampler reverse_uv_sampler_orig{surface_uv_map_orig_, surface_looptris_orig_}; + for (const float4x4 &brush_transform : brush_transforms) { + self_->slide_info_.append_as(); + SlideInfo &slide_info = self_->slide_info_.last(); + slide_info.brush_transform = brush_transform; + this->find_curves_to_slide(brush_transform * brush_3d->position_cu, + brush_3d->radius_cu, + reverse_uv_sampler_orig, + slide_info.curves_to_slide); + } + } - self_->slide_info_.append({brush_transform}); - Vector &curves_to_slide = self_->slide_info_.last().curves_to_slide; + void find_curves_to_slide(const float3 &brush_pos_cu, + const float brush_radius_cu, + const ReverseUVSampler &reverse_uv_sampler_orig, + Vector &r_curves_to_slide) + { + const Span surface_uv_coords = curves_orig_->surface_uv_coords(); + const float brush_radius_sq_cu = pow2f(brush_radius_cu); - /* Find curves in brush radius that should be moved. */ + const Span offsets = curves_orig_->offsets(); for (const int curve_i : curve_selection_) { - const int first_point_i = curves_->offsets()[curve_i]; - const float3 &first_pos_cu = brush_transform_inv * positions_cu[first_point_i]; - - float2 first_pos_re; - ED_view3d_project_float_v2_m4(ctx_.region, first_pos_cu, first_pos_re, projection.values); - - const float dist_to_brush_sq_re = math::distance_squared(first_pos_re, brush_pos_re_); - if (dist_to_brush_sq_re > brush_radius_sq_re) { + const int first_point_i = offsets[curve_i]; + const float3 old_pos_cu = self_->initial_deformed_positions_cu_[first_point_i]; + const float dist_to_brush_sq_cu = math::distance_squared(old_pos_cu, brush_pos_cu); + if (dist_to_brush_sq_cu > brush_radius_sq_cu) { + /* Root point is too far away from curve center. */ continue; } - const float dist_to_brush_re = std::sqrt(dist_to_brush_sq_re); + const float dist_to_brush_cu = std::sqrt(dist_to_brush_sq_cu); const float radius_falloff = BKE_brush_curve_strength( - brush_, dist_to_brush_re, brush_radius_re); - curves_to_slide.append({curve_i, radius_falloff}); + brush_, dist_to_brush_cu, brush_radius_cu); + + const float2 uv = surface_uv_coords[curve_i]; + ReverseUVSampler::Result result = reverse_uv_sampler_orig.sample(uv); + if (result.type != ReverseUVSampler::ResultType::Ok) { + /* The curve does not have a valid surface attachment. */ + found_invalid_uv_mapping_.store(true); + continue; + } + /* Compute the normal at the initial surface position. */ + const float3 normal_cu = math::normalize( + transforms_.surface_to_curves_normal * + geometry::compute_surface_point_normal( + *result.looptri, result.bary_weights, corner_normals_orig_su_)); + + r_curves_to_slide.append({curve_i, radius_falloff, normal_cu}); } } - void slide_projected() + void slide_with_symmetry() { - MutableSpan positions_cu = curves_->positions_for_write(); - - MutableSpan surface_uv_coords; - if (!surface_uv_map_.is_empty()) { - surface_uv_coords = curves_->surface_uv_coords_for_write(); + const ReverseUVSampler reverse_uv_sampler_orig{surface_uv_map_orig_, surface_looptris_orig_}; + for (const SlideInfo &slide_info : self_->slide_info_) { + this->slide(slide_info.curves_to_slide, reverse_uv_sampler_orig, slide_info.brush_transform); } + } - float4x4 projection; - ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); + void slide(const Span slide_curves, + const ReverseUVSampler &reverse_uv_sampler_orig, + const float4x4 &brush_transform) + { + const float4x4 brush_transform_inv = brush_transform.inverted(); - for (const SlideInfo &slide_info : self_->slide_info_) { - const float4x4 &brush_transform = slide_info.brush_transform; - const float4x4 brush_transform_inv = brush_transform.inverted(); - const Span curves_to_slide = slide_info.curves_to_slide; - - threading::parallel_for(curves_to_slide.index_range(), 256, [&](const IndexRange range) { - for (const SlideCurveInfo &curve_slide_info : curves_to_slide.slice(range)) { - const int curve_i = curve_slide_info.curve_i; - const IndexRange points = curves_->points_for_curve(curve_i); - const int first_point_i = points.first(); - const float3 old_first_pos_cu = brush_transform_inv * positions_cu[first_point_i]; - - float2 old_first_pos_re; - ED_view3d_project_float_v2_m4( - ctx_.region, old_first_pos_cu, old_first_pos_re, projection.values); - const float first_point_weight = brush_strength_ * curve_slide_info.radius_falloff; - - /* Slide root position in region space and then project it back onto the surface. */ - const float2 new_first_pos_re = old_first_pos_re + - first_point_weight * brush_pos_diff_re_; - - float3 ray_start_wo, ray_end_wo; - ED_view3d_win_to_segment_clipped(ctx_.depsgraph, - ctx_.region, - ctx_.v3d, - new_first_pos_re, - ray_start_wo, - ray_end_wo, - true); - const float3 ray_start_su = transforms_.world_to_surface * ray_start_wo; - const float3 ray_end_su = transforms_.world_to_surface * ray_end_wo; - - const float3 ray_direction_su = math::normalize(ray_end_su - ray_start_su); - BVHTreeRayHit hit; - hit.dist = FLT_MAX; - hit.index = -1; - BLI_bvhtree_ray_cast(surface_bvh_.tree, - ray_start_su, - ray_direction_su, - 0.0f, - &hit, - surface_bvh_.raycast_callback, - &surface_bvh_); - if (hit.index == -1) { - continue; - } + const Span verts_orig_su{surface_orig_->mvert, surface_orig_->totvert}; + const Span loops_orig{surface_orig_->mloop, surface_orig_->totloop}; - const int looptri_index = hit.index; - const float3 attached_pos_su = hit.co; + MutableSpan positions_orig_cu = curves_orig_->positions_for_write(); + MutableSpan surface_uv_coords = curves_orig_->surface_uv_coords_for_write(); - const float3 attached_pos_cu = transforms_.surface_to_curves * attached_pos_su; - const float3 pos_offset_cu = brush_transform * (attached_pos_cu - old_first_pos_cu); + float4x4 projection; + ED_view3d_ob_project_mat_get(ctx_.rv3d, curves_ob_orig_, projection.values); + + const float2 brush_pos_diff_re = brush_pos_re_ - self_->initial_brush_pos_re_; + + /* The brush transformation has to be applied in curves space. */ + const float4x4 world_to_surface_with_symmetry_mat = transforms_.curves_to_surface * + brush_transform * + transforms_.world_to_curves; + + threading::parallel_for(slide_curves.index_range(), 256, [&](const IndexRange range) { + for (const SlideCurveInfo &slide_curve_info : slide_curves.slice(range)) { + const int curve_i = slide_curve_info.curve_i; + const IndexRange points = curves_orig_->points_for_curve(curve_i); + const int first_point_i = points[0]; + + const float3 old_first_pos_eval_cu = self_->initial_deformed_positions_cu_[first_point_i]; + const float3 old_first_symm_pos_eval_cu = brush_transform_inv * old_first_pos_eval_cu; + const float3 old_first_pos_eval_su = transforms_.curves_to_surface * old_first_pos_eval_cu; + + float2 old_first_symm_pos_eval_re; + ED_view3d_project_float_v2_m4(ctx_.region, + old_first_symm_pos_eval_cu, + old_first_symm_pos_eval_re, + projection.values); + + const float radius_falloff = slide_curve_info.radius_falloff; + const float curve_weight = brush_strength_ * radius_falloff * curve_factors_[curve_i]; + const float2 new_first_symm_pos_eval_re = old_first_symm_pos_eval_re + + curve_weight * brush_pos_diff_re; + + /* Compute the ray that will be used to find the new position on the surface. */ + float3 ray_start_wo, ray_end_wo; + ED_view3d_win_to_segment_clipped(ctx_.depsgraph, + ctx_.region, + ctx_.v3d, + new_first_symm_pos_eval_re, + ray_start_wo, + ray_end_wo, + true); + const float3 ray_start_su = world_to_surface_with_symmetry_mat * ray_start_wo; + const float3 ray_end_su = world_to_surface_with_symmetry_mat * ray_end_wo; + const float3 ray_direction_su = math::normalize(ray_end_su - ray_start_su); + + /* Find the ray hit that is closest to the initial curve root position. */ + int looptri_index_eval; + float3 hit_pos_eval_su; + if (!this->find_closest_ray_hit(ray_start_su, + ray_direction_su, + old_first_pos_eval_su, + looptri_index_eval, + hit_pos_eval_su)) { + continue; + } - /* Update positions. The first point doesn't have an additional weight here, because then - * it wouldn't be attached to the surface anymore. */ - positions_cu[first_point_i] += pos_offset_cu; - for (const int point_i : points.drop_front(1)) { - const float weight = point_factors_[point_i]; - positions_cu[point_i] += weight * pos_offset_cu; - } + /* Compute the uv of the new surface position on the evaluated mesh. */ + const MLoopTri &looptri_eval = surface_looptris_eval_[looptri_index_eval]; + const float3 bary_weights_eval = bke::mesh_surface_sample::compute_bary_coord_in_triangle( + *surface_eval_, looptri_eval, hit_pos_eval_su); + const float2 uv = attribute_math::mix3(bary_weights_eval, + surface_uv_map_eval_[looptri_eval.tri[0]], + surface_uv_map_eval_[looptri_eval.tri[1]], + surface_uv_map_eval_[looptri_eval.tri[2]]); + + /* Try to find the same uv on the original surface. */ + const ReverseUVSampler::Result result = reverse_uv_sampler_orig.sample(uv); + if (result.type != ReverseUVSampler::ResultType::Ok) { + found_invalid_uv_mapping_.store(true); + continue; + } + const MLoopTri &looptri_orig = *result.looptri; + const float3 &bary_weights_orig = result.bary_weights; + + /* Gather old and new surface normal. */ + const float3 &initial_normal_cu = slide_curve_info.initial_normal_cu; + const float3 new_normal_cu = math::normalize( + transforms_.surface_to_curves_normal * + geometry::compute_surface_point_normal( + *result.looptri, result.bary_weights, corner_normals_orig_su_)); + + /* Gather old and new surface position. */ + const float3 old_first_pos_orig_cu = self_->initial_positions_cu_[first_point_i]; + const float3 new_first_pos_orig_cu = + transforms_.surface_to_curves * + attribute_math::mix3(bary_weights_orig, + verts_orig_su[loops_orig[looptri_orig.tri[0]].v].co, + verts_orig_su[loops_orig[looptri_orig.tri[1]].v].co, + verts_orig_su[loops_orig[looptri_orig.tri[2]].v].co); + + /* Actually transform curve points. */ + const float4x4 slide_transform = this->get_slide_transform( + old_first_pos_orig_cu, new_first_pos_orig_cu, initial_normal_cu, new_normal_cu); + for (const int point_i : points) { + positions_orig_cu[point_i] = slide_transform * self_->initial_positions_cu_[point_i]; + } + surface_uv_coords[curve_i] = uv; + } + }); + } - /* Update surface attachment information if necessary. */ - if (!surface_uv_map_.is_empty()) { - const MLoopTri &looptri = surface_looptris_[looptri_index]; - const float3 bary_coord = bke::mesh_surface_sample::compute_bary_coord_in_triangle( - *surface_, looptri, attached_pos_su); - const float2 &uv0 = surface_uv_map_[looptri.tri[0]]; - const float2 &uv1 = surface_uv_map_[looptri.tri[1]]; - const float2 &uv2 = surface_uv_map_[looptri.tri[2]]; - const float2 uv = attribute_math::mix3(bary_coord, uv0, uv1, uv2); - surface_uv_coords[curve_i] = uv; + bool find_closest_ray_hit(const float3 &ray_start_su, + const float3 &ray_direction_su, + const float3 &point_su, + int &r_looptri_index, + float3 &r_hit_pos) + { + float best_dist_sq_su = FLT_MAX; + int best_looptri_index_eval; + float3 best_hit_pos_su; + BLI_bvhtree_ray_cast_all_cpp( + *surface_bvh_eval_.tree, + ray_start_su, + ray_direction_su, + 0.0f, + FLT_MAX, + [&](const int looptri_index, const BVHTreeRay &ray, BVHTreeRayHit &hit) { + surface_bvh_eval_.raycast_callback(&surface_bvh_eval_, looptri_index, &ray, &hit); + if (hit.index < 0) { + return; } - } - }); + const float3 &hit_pos_su = hit.co; + const float dist_sq_su = math::distance_squared(hit_pos_su, point_su); + if (dist_sq_su < best_dist_sq_su) { + best_dist_sq_su = dist_sq_su; + best_hit_pos_su = hit_pos_su; + best_looptri_index_eval = hit.index; + } + }); + + if (best_dist_sq_su == FLT_MAX) { + return false; } + r_looptri_index = best_looptri_index_eval; + r_hit_pos = best_hit_pos_su; + return true; + } + + float4x4 get_slide_transform(const float3 &old_root_pos, + const float3 &new_root_pos, + const float3 &old_normal, + const float3 &new_normal) + { + float3x3 rotation_3x3; + rotation_between_vecs_to_mat3(rotation_3x3.values, old_normal, new_normal); + float4x4 rotation_4x4; + copy_m4_m3(rotation_4x4.values, rotation_3x3.values); + + float4x4 transform = float4x4::identity(); + sub_v3_v3(transform.values[3], old_root_pos); + mul_m4_m4_pre(transform.values, rotation_4x4.values); + add_v3_v3(transform.values[3], new_root_pos); + return transform; } }; diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_smooth.cc b/source/blender/editors/sculpt_paint/curves_sculpt_smooth.cc index f874a9fc255..37a7f1c83ed 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_smooth.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_smooth.cc @@ -2,6 +2,7 @@ #include "BKE_brush.h" #include "BKE_context.h" +#include "BKE_crazyspace.hh" #include "ED_screen.h" #include "ED_view3d.h" @@ -95,60 +96,56 @@ struct SmoothOperationExecutor { } } + Array point_smooth_factors(curves_->points_num(), 0.0f); + if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { - this->smooth_projected_with_symmetry(); + this->find_projected_smooth_factors_with_symmetry(point_smooth_factors); } else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { - this->smooth_spherical_with_symmetry(); + this->find_spherical_smooth_factors_with_symmetry(point_smooth_factors); } else { BLI_assert_unreachable(); } + this->smooth(point_smooth_factors); curves_->tag_positions_changed(); DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id); ED_region_tag_redraw(ctx_.region); } - void smooth_projected_with_symmetry() + void find_projected_smooth_factors_with_symmetry(MutableSpan r_point_smooth_factors) { const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( eCurvesSymmetryType(curves_id_->symmetry)); for (const float4x4 &brush_transform : symmetry_brush_transforms) { - this->smooth_projected(brush_transform); + this->find_projected_smooth_factors(brush_transform, r_point_smooth_factors); } } - void smooth_projected(const float4x4 &brush_transform) + void find_projected_smooth_factors(const float4x4 &brush_transform, + MutableSpan r_point_smooth_factors) { const float4x4 brush_transform_inv = brush_transform.inverted(); - MutableSpan positions_cu = curves_->positions_for_write(); const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_; const float brush_radius_sq_re = pow2f(brush_radius_re); float4x4 projection; ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { - Vector old_curve_positions_re; for (const int curve_i : curve_selection_.slice(range)) { const IndexRange points = curves_->points_for_curve(curve_i); - - /* Find position of control points in screen space. */ - old_curve_positions_re.clear(); - old_curve_positions_re.reserve(points.size()); for (const int point_i : points) { - const float3 &pos_cu = brush_transform_inv * positions_cu[point_i]; + const float3 &pos_cu = brush_transform_inv * deformation.positions[point_i]; float2 pos_re; ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values); - old_curve_positions_re.append_unchecked(pos_re); - } - for (const int i : IndexRange(points.size()).drop_front(1).drop_back(1)) { - const int point_i = points[i]; - const float2 &old_pos_re = old_curve_positions_re[i]; - const float dist_to_brush_sq_re = math::distance_squared(old_pos_re, brush_pos_re_); + const float dist_to_brush_sq_re = math::distance_squared(pos_re, brush_pos_re_); if (dist_to_brush_sq_re > brush_radius_sq_re) { continue; } @@ -161,27 +158,13 @@ struct SmoothOperationExecutor { const float weight_factor = 0.1f; const float weight = weight_factor * brush_strength_ * radius_falloff * point_factors_[point_i]; - - /* Move points towards the middle of their neighbors. */ - const float2 &old_pos_prev_re = old_curve_positions_re[i - 1]; - const float2 &old_pos_next_re = old_curve_positions_re[i + 1]; - const float2 goal_pos_re = math::midpoint(old_pos_prev_re, old_pos_next_re); - const float2 new_pos_re = math::interpolate(old_pos_re, goal_pos_re, weight); - const float3 old_pos_cu = brush_transform_inv * positions_cu[point_i]; - float3 new_pos_wo; - ED_view3d_win_to_3d(ctx_.v3d, - ctx_.region, - transforms_.curves_to_world * old_pos_cu, - new_pos_re, - new_pos_wo); - const float3 new_pos_cu = brush_transform * (transforms_.world_to_curves * new_pos_wo); - positions_cu[point_i] = new_pos_cu; + math::max_inplace(r_point_smooth_factors[point_i], weight); } } }); } - void smooth_spherical_with_symmetry() + void find_spherical_smooth_factors_with_symmetry(MutableSpan r_point_smooth_factors) { float4x4 projection; ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values); @@ -198,27 +181,25 @@ struct SmoothOperationExecutor { const Vector symmetry_brush_transforms = get_symmetry_brush_transforms( eCurvesSymmetryType(curves_id_->symmetry)); for (const float4x4 &brush_transform : symmetry_brush_transforms) { - this->smooth_spherical(brush_transform * brush_pos_cu, brush_radius_cu); + this->find_spherical_smooth_factors( + brush_transform * brush_pos_cu, brush_radius_cu, r_point_smooth_factors); } } - void smooth_spherical(const float3 &brush_pos_cu, const float brush_radius_cu) + void find_spherical_smooth_factors(const float3 &brush_pos_cu, + const float brush_radius_cu, + MutableSpan r_point_smooth_factors) { - MutableSpan positions_cu = curves_->positions_for_write(); const float brush_radius_sq_cu = pow2f(brush_radius_cu); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { - Vector old_curve_positions_cu; for (const int curve_i : curve_selection_.slice(range)) { const IndexRange points = curves_->points_for_curve(curve_i); - /* Remember original positions so that we don't smooth based on already smoothed points - * below. */ - old_curve_positions_cu.clear(); - old_curve_positions_cu.extend(positions_cu.slice(points)); - for (const int i : IndexRange(points.size()).drop_front(1).drop_back(1)) { - const int point_i = points[i]; - const float3 &old_pos_cu = old_curve_positions_cu[i]; - const float dist_to_brush_sq_cu = math::distance_squared(old_pos_cu, brush_pos_cu); + for (const int point_i : points) { + const float3 &pos_cu = deformation.positions[point_i]; + const float dist_to_brush_sq_cu = math::distance_squared(pos_cu, brush_pos_cu); if (dist_to_brush_sq_cu > brush_radius_sq_cu) { continue; } @@ -231,13 +212,34 @@ struct SmoothOperationExecutor { const float weight_factor = 0.1f; const float weight = weight_factor * brush_strength_ * radius_falloff * point_factors_[point_i]; + math::max_inplace(r_point_smooth_factors[point_i], weight); + } + } + }); + } - /* Move points towards the middle of their neighbors. */ - const float3 &old_pos_prev_cu = old_curve_positions_cu[i - 1]; - const float3 &old_pos_next_cu = old_curve_positions_cu[i + 1]; - const float3 goal_pos_cu = math::midpoint(old_pos_prev_cu, old_pos_next_cu); - const float3 new_pos_cu = math::interpolate(old_pos_cu, goal_pos_cu, weight); - positions_cu[point_i] = new_pos_cu; + void smooth(const Span point_smooth_factors) + { + MutableSpan positions = curves_->positions_for_write(); + threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) { + Vector old_positions; + for (const int curve_i : curve_selection_.slice(range)) { + const IndexRange points = curves_->points_for_curve(curve_i); + old_positions.clear(); + old_positions.extend(positions.slice(points)); + for (const int i : IndexRange(points.size()).drop_front(1).drop_back(1)) { + const int point_i = points[i]; + const float smooth_factor = point_smooth_factors[point_i]; + if (smooth_factor == 0.0f) { + continue; + } + /* Move towards the middle of the neighboring points. */ + const float3 old_pos = old_positions[i]; + const float3 &prev_pos = old_positions[i - 1]; + const float3 &next_pos = old_positions[i + 1]; + const float3 goal_pos = math::midpoint(prev_pos, next_pos); + const float3 new_pos = math::interpolate(old_pos, goal_pos, smooth_factor); + positions[point_i] = new_pos; } } }); diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc b/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc index ec0e8ff45e5..54b81fa221d 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc @@ -176,6 +176,8 @@ struct SnakeHookOperatorExecutor { void projected_snake_hook(const float4x4 &brush_transform) { const float4x4 brush_transform_inv = brush_transform.inverted(); + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); MutableSpan positions_cu = curves_->positions_for_write(); @@ -189,12 +191,14 @@ struct SnakeHookOperatorExecutor { for (const int curve_i : curves_range) { const IndexRange points = curves_->points_for_curve(curve_i); const int last_point_i = points.last(); - const float3 old_pos_cu = brush_transform_inv * positions_cu[last_point_i]; + const float3 old_pos_cu = deformation.positions[last_point_i]; + const float3 old_symm_pos_cu = brush_transform_inv * old_pos_cu; - float2 old_pos_re; - ED_view3d_project_float_v2_m4(ctx_.region, old_pos_cu, old_pos_re, projection.values); + float2 old_symm_pos_re; + ED_view3d_project_float_v2_m4( + ctx_.region, old_symm_pos_cu, old_symm_pos_re, projection.values); - const float distance_to_brush_sq_re = math::distance_squared(old_pos_re, + const float distance_to_brush_sq_re = math::distance_squared(old_symm_pos_re, brush_pos_prev_re_); if (distance_to_brush_sq_re > brush_radius_sq_re) { continue; @@ -204,17 +208,21 @@ struct SnakeHookOperatorExecutor { brush_, std::sqrt(distance_to_brush_sq_re), brush_radius_re); const float weight = brush_strength_ * radius_falloff * curve_factors_[curve_i]; - const float2 new_position_re = old_pos_re + brush_pos_diff_re_ * weight; - float3 new_position_wo; + const float2 new_symm_pos_re = old_symm_pos_re + brush_pos_diff_re_ * weight; + float3 new_symm_pos_wo; ED_view3d_win_to_3d(ctx_.v3d, ctx_.region, - transforms_.curves_to_world * old_pos_cu, - new_position_re, - new_position_wo); - const float3 new_position_cu = brush_transform * - (transforms_.world_to_curves * new_position_wo); - - move_last_point_and_resample(positions_cu.slice(points), new_position_cu); + transforms_.curves_to_world * old_symm_pos_cu, + new_symm_pos_re, + new_symm_pos_wo); + const float3 new_pos_cu = brush_transform * + (transforms_.world_to_curves * new_symm_pos_wo); + const float3 translation_eval = new_pos_cu - old_pos_cu; + const float3 translation_orig = deformation.translation_from_deformed_to_original( + last_point_i, translation_eval); + + move_last_point_and_resample(positions_cu.slice(points), + positions_cu[last_point_i] + translation_orig); } }); } @@ -252,6 +260,9 @@ struct SnakeHookOperatorExecutor { const float3 &brush_end_cu, const float brush_radius_cu) { + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(*ctx_.depsgraph, *object_); + MutableSpan positions_cu = curves_->positions_for_write(); const float3 brush_diff_cu = brush_end_cu - brush_start_cu; const float brush_radius_sq_cu = pow2f(brush_radius_cu); @@ -260,7 +271,7 @@ struct SnakeHookOperatorExecutor { for (const int curve_i : curves_range) { const IndexRange points = curves_->points_for_curve(curve_i); const int last_point_i = points.last(); - const float3 old_pos_cu = positions_cu[last_point_i]; + const float3 old_pos_cu = deformation.positions[last_point_i]; const float distance_to_brush_sq_cu = dist_squared_to_line_segment_v3( old_pos_cu, brush_start_cu, brush_end_cu); @@ -274,9 +285,12 @@ struct SnakeHookOperatorExecutor { brush_, distance_to_brush_cu, brush_radius_cu); const float weight = brush_strength_ * radius_falloff * curve_factors_[curve_i]; - const float3 new_pos_cu = old_pos_cu + weight * brush_diff_cu; + const float3 translation_eval = weight * brush_diff_cu; + const float3 translation_orig = deformation.translation_from_deformed_to_original( + last_point_i, translation_eval); - move_last_point_and_resample(positions_cu.slice(points), new_pos_cu); + move_last_point_and_resample(positions_cu.slice(points), + positions_cu[last_point_i] + translation_orig); } }); } diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index b879219e39c..9b8009cc7c0 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -937,6 +937,19 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo ss << TIP_("\u2022 Volume") << line_end; break; } + case GEO_COMPONENT_TYPE_EDIT: { + if (value_log.edit_data_info.has_value()) { + const geo_log::GeometryValueLog::EditDataInfo &edit_info = *value_log.edit_data_info; + char line[256]; + BLI_snprintf(line, + sizeof(line), + TIP_("\u2022 Edit Curves: %s, %s"), + edit_info.has_deformed_positions ? TIP_("positions") : TIP_("no positions"), + edit_info.has_deform_matrices ? TIP_("matrices") : TIP_("no matrices")); + ss << line << line_end; + } + break; + } } } @@ -975,6 +988,9 @@ static void create_inspection_string_for_geometry(const geo_log::GeometryValueLo ss << TIP_("Volume"); break; } + case GEO_COMPONENT_TYPE_EDIT: { + break; + } } ss << ((type == supported_types.last()) ? "" : ", "); } diff --git a/source/blender/geometry/GEO_add_curves_on_mesh.hh b/source/blender/geometry/GEO_add_curves_on_mesh.hh index cf60a8e8ace..68c8dc5b76b 100644 --- a/source/blender/geometry/GEO_add_curves_on_mesh.hh +++ b/source/blender/geometry/GEO_add_curves_on_mesh.hh @@ -13,13 +13,13 @@ #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" +#include "GEO_reverse_uv_sampler.hh" + namespace blender::geometry { struct AddCurvesOnMeshInputs { - /** Information about the root points where new curves should be generated. */ - Span root_positions_cu; - Span bary_coords; - Span looptri_indices; + /** UV Coordinates at which the new curves should be added. */ + Span uvs; /** Determines shape of new curves. */ bool interpolate_length = false; @@ -30,14 +30,10 @@ struct AddCurvesOnMeshInputs { /** Information about the surface that the new curves are attached to. */ const Mesh *surface = nullptr; - BVHTreeFromMesh *surface_bvh = nullptr; - Span surface_looptris; - Span surface_uv_map; + const ReverseUVSampler *reverse_uv_sampler = nullptr; Span corner_normals_su; - /** Transformation matrices. */ - float4x4 curves_to_surface_mat; - float4x4 surface_to_curves_normal_mat; + bke::CurvesSurfaceTransforms *transforms = nullptr; /** * KD-Tree that contains the root points of existing curves. This is only necessary when @@ -51,4 +47,8 @@ struct AddCurvesOnMeshInputs { */ void add_curves_on_mesh(bke::CurvesGeometry &curves, const AddCurvesOnMeshInputs &inputs); +float3 compute_surface_point_normal(const MLoopTri &looptri, + const float3 &bary_coord, + const Span corner_normals); + } // namespace blender::geometry diff --git a/source/blender/geometry/intern/add_curves_on_mesh.cc b/source/blender/geometry/intern/add_curves_on_mesh.cc index e54e2bdd3b0..aa04cedb5c5 100644 --- a/source/blender/geometry/intern/add_curves_on_mesh.cc +++ b/source/blender/geometry/intern/add_curves_on_mesh.cc @@ -6,6 +6,7 @@ #include "BKE_mesh_sample.hh" #include "GEO_add_curves_on_mesh.hh" +#include "GEO_reverse_uv_sampler.hh" /** * The code below uses a suffix naming convention to indicate the coordinate space: @@ -27,9 +28,9 @@ struct NeighborCurve { static constexpr int max_neighbors = 5; using NeighborCurves = Vector; -static float3 compute_surface_point_normal(const MLoopTri &looptri, - const float3 &bary_coord, - const Span corner_normals) +float3 compute_surface_point_normal(const MLoopTri &looptri, + const float3 &bary_coord, + const Span corner_normals) { const int l0 = looptri.tri[0]; const int l1 = looptri.tri[1]; @@ -136,16 +137,15 @@ static void interpolate_position_with_interpolation(CurvesGeometry &curves, const int old_curves_num, const Span new_lengths_cu, const Span new_normals_su, - const float4x4 &surface_to_curves_normal_mat, - const float4x4 &curves_to_surface_mat, - const BVHTreeFromMesh &surface_bvh, - const Span surface_looptris, - const Mesh &surface, + const bke::CurvesSurfaceTransforms &transforms, + const ReverseUVSampler &reverse_uv_sampler, const Span corner_normals_su) { MutableSpan positions_cu = curves.positions_for_write(); const int added_curves_num = root_positions_cu.size(); + const Span uv_coords = curves.surface_uv_coords(); + threading::parallel_for(IndexRange(added_curves_num), 256, [&](const IndexRange range) { for (const int added_curve_i : range) { const NeighborCurves &neighbors = neighbors_per_curve[added_curve_i]; @@ -154,7 +154,7 @@ static void interpolate_position_with_interpolation(CurvesGeometry &curves, const float length_cu = new_lengths_cu[added_curve_i]; const float3 &normal_su = new_normals_su[added_curve_i]; - const float3 normal_cu = math::normalize(surface_to_curves_normal_mat * normal_su); + const float3 normal_cu = math::normalize(transforms.surface_to_curves_normal * normal_su); const float3 &root_cu = root_positions_cu[added_curve_i]; @@ -169,26 +169,15 @@ static void interpolate_position_with_interpolation(CurvesGeometry &curves, for (const NeighborCurve &neighbor : neighbors) { const int neighbor_curve_i = neighbor.index; - const float3 &neighbor_first_pos_cu = positions_cu[curves.offsets()[neighbor_curve_i]]; - const float3 neighbor_first_pos_su = curves_to_surface_mat * neighbor_first_pos_cu; - - BVHTreeNearest nearest; - nearest.dist_sq = FLT_MAX; - BLI_bvhtree_find_nearest(surface_bvh.tree, - neighbor_first_pos_su, - &nearest, - surface_bvh.nearest_callback, - const_cast(&surface_bvh)); - const int neighbor_looptri_index = nearest.index; - const MLoopTri &neighbor_looptri = surface_looptris[neighbor_looptri_index]; - - const float3 neighbor_bary_coord = - bke::mesh_surface_sample::compute_bary_coord_in_triangle( - surface, neighbor_looptri, nearest.co); + const float2 neighbor_uv = uv_coords[neighbor_curve_i]; + const ReverseUVSampler::Result result = reverse_uv_sampler.sample(neighbor_uv); + if (result.type != ReverseUVSampler::ResultType::Ok) { + continue; + } const float3 neighbor_normal_su = compute_surface_point_normal( - surface_looptris[neighbor_looptri_index], neighbor_bary_coord, corner_normals_su); - const float3 neighbor_normal_cu = math::normalize(surface_to_curves_normal_mat * + *result.looptri, result.bary_weights, corner_normals_su); + const float3 neighbor_normal_cu = math::normalize(transforms.surface_to_curves_normal * neighbor_normal_su); /* The rotation matrix used to transform relative coordinates of the neighbor curve @@ -245,13 +234,37 @@ void add_curves_on_mesh(CurvesGeometry &curves, const AddCurvesOnMeshInputs &inp const bool use_interpolation = inputs.interpolate_length || inputs.interpolate_point_count || inputs.interpolate_shape; + Vector root_positions_cu; + Vector bary_coords; + Vector looptris; + Vector used_uvs; + + /* Find faces that the passed in uvs belong to. */ + for (const int i : inputs.uvs.index_range()) { + const float2 &uv = inputs.uvs[i]; + const ReverseUVSampler::Result result = inputs.reverse_uv_sampler->sample(uv); + if (result.type != ReverseUVSampler::ResultType::Ok) { + continue; + } + const MLoopTri &looptri = *result.looptri; + bary_coords.append(result.bary_weights); + looptris.append(&looptri); + const float3 root_position_su = attribute_math::mix3( + result.bary_weights, + inputs.surface->mvert[inputs.surface->mloop[looptri.tri[0]].v].co, + inputs.surface->mvert[inputs.surface->mloop[looptri.tri[1]].v].co, + inputs.surface->mvert[inputs.surface->mloop[looptri.tri[2]].v].co); + root_positions_cu.append(inputs.transforms->surface_to_curves * root_position_su); + used_uvs.append(uv); + } + Array neighbors_per_curve; if (use_interpolation) { BLI_assert(inputs.old_roots_kdtree != nullptr); - neighbors_per_curve = find_curve_neighbors(inputs.root_positions_cu, *inputs.old_roots_kdtree); + neighbors_per_curve = find_curve_neighbors(root_positions_cu, *inputs.old_roots_kdtree); } - const int added_curves_num = inputs.root_positions_cu.size(); + const int added_curves_num = root_positions_cu.size(); const int old_points_num = curves.points_num(); const int old_curves_num = curves.curves_num(); const int new_curves_num = old_curves_num + added_curves_num; @@ -280,6 +293,10 @@ void add_curves_on_mesh(CurvesGeometry &curves, const AddCurvesOnMeshInputs &inp curves.resize(new_points_num, new_curves_num); MutableSpan positions_cu = curves.positions_for_write(); + /* Initialize attachment information. */ + MutableSpan surface_uv_coords = curves.surface_uv_coords_for_write(); + surface_uv_coords.take_back(added_curves_num).copy_from(used_uvs); + /* Determine length of new curves. */ Array new_lengths_cu(added_curves_num); if (inputs.interpolate_length) { @@ -306,25 +323,11 @@ void add_curves_on_mesh(CurvesGeometry &curves, const AddCurvesOnMeshInputs &inp Array new_normals_su(added_curves_num); threading::parallel_for(IndexRange(added_curves_num), 256, [&](const IndexRange range) { for (const int i : range) { - const int looptri_index = inputs.looptri_indices[i]; - const float3 &bary_coord = inputs.bary_coords[i]; new_normals_su[i] = compute_surface_point_normal( - inputs.surface_looptris[looptri_index], bary_coord, inputs.corner_normals_su); + *looptris[i], bary_coords[i], inputs.corner_normals_su); } }); - /* Propagate attachment information. */ - if (!inputs.surface_uv_map.is_empty()) { - MutableSpan surface_uv_coords = curves.surface_uv_coords_for_write(); - bke::mesh_surface_sample::sample_corner_attribute( - *inputs.surface, - inputs.looptri_indices, - inputs.bary_coords, - GVArray::ForSpan(inputs.surface_uv_map), - IndexRange(added_curves_num), - surface_uv_coords.take_back(added_curves_num)); - } - /* Update selection arrays when available. */ const VArray points_selection = curves.selection_point_float(); if (points_selection.is_span()) { @@ -340,25 +343,22 @@ void add_curves_on_mesh(CurvesGeometry &curves, const AddCurvesOnMeshInputs &inp /* Initialize position attribute. */ if (inputs.interpolate_shape) { interpolate_position_with_interpolation(curves, - inputs.root_positions_cu, + root_positions_cu, neighbors_per_curve, old_curves_num, new_lengths_cu, new_normals_su, - inputs.surface_to_curves_normal_mat, - inputs.curves_to_surface_mat, - *inputs.surface_bvh, - inputs.surface_looptris, - *inputs.surface, + *inputs.transforms, + *inputs.reverse_uv_sampler, inputs.corner_normals_su); } else { interpolate_position_without_interpolation(curves, old_curves_num, - inputs.root_positions_cu, + root_positions_cu, new_lengths_cu, new_normals_su, - inputs.surface_to_curves_normal_mat); + inputs.transforms->surface_to_curves_normal); } /* Set curve types. */ diff --git a/source/blender/geometry/intern/realize_instances.cc b/source/blender/geometry/intern/realize_instances.cc index 0544f304283..4b3b184536b 100644 --- a/source/blender/geometry/intern/realize_instances.cc +++ b/source/blender/geometry/intern/realize_instances.cc @@ -204,7 +204,8 @@ struct GatherTasks { /* Volumes only have very simple support currently. Only the first found volume is put into the * output. */ - UserCounter first_volume; + UserCounter first_volume; + UserCounter first_edit_data; }; /** Current offsets while during the gather operation. */ @@ -582,7 +583,16 @@ static void gather_realize_tasks_recursive(GatherTasksInfo &gather_info, const VolumeComponent *volume_component = static_cast(component); if (!gather_info.r_tasks.first_volume) { volume_component->user_add(); - gather_info.r_tasks.first_volume = const_cast(volume_component); + gather_info.r_tasks.first_volume = volume_component; + } + break; + } + case GEO_COMPONENT_TYPE_EDIT: { + const GeometryComponentEditData *edit_component = + static_cast(component); + if (!gather_info.r_tasks.first_edit_data) { + edit_component->user_add(); + gather_info.r_tasks.first_edit_data = edit_component; } break; } @@ -1405,6 +1415,9 @@ GeometrySet realize_instances(GeometrySet geometry_set, const RealizeInstancesOp if (gather_info.r_tasks.first_volume) { new_geometry_set.add(*gather_info.r_tasks.first_volume); } + if (gather_info.r_tasks.first_edit_data) { + new_geometry_set.add(*gather_info.r_tasks.first_edit_data); + } return new_geometry_set; } diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh index 0d5ba6cf5db..c5bc42b059d 100644 --- a/source/blender/nodes/NOD_geometry_exec.hh +++ b/source/blender/nodes/NOD_geometry_exec.hh @@ -163,6 +163,7 @@ class GeoNodeExecParams { } void check_input_geometry_set(StringRef identifier, const GeometrySet &geometry_set) const; + void check_output_geometry_set(const GeometrySet &geometry_set) const; /** * Get input as vector for multi input socket with the given identifier. @@ -231,6 +232,9 @@ class GeoNodeExecParams { #ifdef DEBUG this->check_output_access(identifier, type); #endif + if constexpr (std::is_same_v) { + this->check_output_geometry_set(value); + } GMutablePointer gvalue = provider_->alloc_output_value(type); new (gvalue.get()) StoredT(std::forward(value)); provider_->set_output(identifier, gvalue); diff --git a/source/blender/nodes/NOD_geometry_nodes_eval_log.hh b/source/blender/nodes/NOD_geometry_nodes_eval_log.hh index 05c97c3903d..46ba72d14d8 100644 --- a/source/blender/nodes/NOD_geometry_nodes_eval_log.hh +++ b/source/blender/nodes/NOD_geometry_nodes_eval_log.hh @@ -115,11 +115,16 @@ class GeometryValueLog : public ValueLog { struct InstancesInfo { int instances_num; }; + struct EditDataInfo { + bool has_deformed_positions; + bool has_deform_matrices; + }; std::optional mesh_info; std::optional curve_info; std::optional pointcloud_info; std::optional instances_info; + std::optional edit_data_info; GeometryValueLog(const GeometrySet &geometry_set, bool log_full_geometry = false); diff --git a/source/blender/nodes/geometry/nodes/node_geo_bounding_box.cc b/source/blender/nodes/geometry/nodes/node_geo_bounding_box.cc index 00b10cc8a2f..75c198f7bdd 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_bounding_box.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_bounding_box.cc @@ -50,7 +50,7 @@ static void node_geo_exec(GeoNodeExecParams params) } if (sub_min == float3(FLT_MAX)) { - sub_geometry.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + sub_geometry.remove_geometry_during_modify(); } else { const float3 scale = sub_max - sub_min; @@ -58,7 +58,7 @@ static void node_geo_exec(GeoNodeExecParams params) Mesh *mesh = geometry::create_cuboid_mesh(scale, 2, 2, 2, "uv_map"); transform_mesh(*mesh, center, float3(0), float3(1)); sub_geometry.replace_mesh(mesh); - sub_geometry.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES}); + sub_geometry.keep_only_during_modify({GEO_COMPONENT_TYPE_MESH}); } }); diff --git a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc index 7c26ffc2099..03892501c89 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc @@ -224,7 +224,7 @@ static void node_geo_exec(GeoNodeExecParams params) geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { Mesh *mesh = compute_hull(geometry_set); geometry_set.replace_mesh(mesh); - geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES}); + geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_MESH}); }); params.set_output("Convex Hull", std::move(geometry_set)); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc index 2815dd5b2e8..37fc6823b9a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc @@ -60,6 +60,8 @@ static void node_geo_exec(GeoNodeExecParams params) const Field selection = params.extract_input>("Selection"); + GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set); + switch (mode) { case GEO_NODE_CURVE_RESAMPLE_COUNT: { Field count = params.extract_input>("Count"); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc index 903a5e7c1d7..bc319ce905a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc @@ -28,9 +28,10 @@ static void geometry_set_curve_to_mesh(GeometrySet &geometry_set, const bool fill_caps) { const Curves &curves = *geometry_set.get_curves_for_read(); - const Curves *profile_curves = profile_set.get_curves_for_read(); + GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set); + if (profile_curves == nullptr) { Mesh *mesh = bke::curve_to_wire_mesh(bke::CurvesGeometry::wrap(curves.geometry)); geometry_set.replace_mesh(mesh); @@ -55,7 +56,7 @@ static void node_geo_exec(GeoNodeExecParams params) has_curves = true; geometry_set_curve_to_mesh(geometry_set, profile_set, fill_caps); } - geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES}); + geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_MESH}); }); params.set_output("Mesh", std::move(curve_set)); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc index 6c4fb2e0855..fd75855bddb 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc @@ -316,9 +316,11 @@ static void node_geo_exec(GeoNodeExecParams params) attribute_outputs.normal_id = StrongAnonymousAttributeID("Normal"); attribute_outputs.rotation_id = StrongAnonymousAttributeID("Rotation"); + GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set); + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { if (!geometry_set.has_curves()) { - geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + geometry_set.remove_geometry_during_modify(); return; } const std::unique_ptr curve = curves_to_curve_eval( @@ -329,7 +331,7 @@ static void node_geo_exec(GeoNodeExecParams params) const Array offsets = calculate_spline_point_offsets(params, mode, *curve, splines); const int total_num = offsets.last(); if (total_num == 0) { - geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + geometry_set.remove_geometry_during_modify(); return; } @@ -355,7 +357,7 @@ static void node_geo_exec(GeoNodeExecParams params) point_attributes.tangents, point_attributes.normals, point_attributes.rotations); } - geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES, GEO_COMPONENT_TYPE_POINT_CLOUD}); + geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_POINT_CLOUD}); }); params.set_output("Points", std::move(geometry_set)); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc index 51994cb8a41..0932de237a9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc @@ -579,6 +579,7 @@ static void node_geo_exec(GeoNodeExecParams params) const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)storage.mode; GeometrySet geometry_set = params.extract_input("Curve"); + GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set); if (mode == GEO_NODE_CURVE_SAMPLE_FACTOR) { Field start_field = params.extract_input>("Start"); diff --git a/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc index bd08abbd070..8b653296e12 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc @@ -41,7 +41,7 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output(N_("Curves")); } -static void deform_curves(CurvesGeometry &curves, +static void deform_curves(const CurvesGeometry &curves, const Mesh &surface_mesh_old, const Mesh &surface_mesh_new, const Span curve_attachment_uvs, @@ -51,6 +51,8 @@ static void deform_curves(CurvesGeometry &curves, const Span corner_normals_new, const Span rest_positions, const float4x4 &surface_to_curves, + MutableSpan r_positions, + MutableSpan r_rotations, std::atomic &r_invalid_uv_count) { /* Find attachment points on old and new mesh. */ @@ -61,8 +63,6 @@ static void deform_curves(CurvesGeometry &curves, [&]() { reverse_uv_sampler_old.sample_many(curve_attachment_uvs, surface_samples_old); }, [&]() { reverse_uv_sampler_new.sample_many(curve_attachment_uvs, surface_samples_new); }); - MutableSpan positions = curves.positions_for_write(); - const float4x4 curves_to_surface = surface_to_curves.inverted(); threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange range) { @@ -190,9 +190,15 @@ static void deform_curves(CurvesGeometry &curves, /* Actually transform all points. */ const IndexRange points = curves.points_for_curve(curve_i); for (const int point_i : points) { - const float3 old_point_pos = positions[point_i]; + const float3 old_point_pos = r_positions[point_i]; const float3 new_point_pos = curve_transform * old_point_pos; - positions[point_i] = new_point_pos; + r_positions[point_i] = new_point_pos; + } + + if (!r_rotations.is_empty()) { + for (const int point_i : points) { + r_rotations[point_i] = rotation * r_rotations[point_i]; + } } } }); @@ -320,17 +326,67 @@ static void node_geo_exec(GeoNodeExecParams params) const bke::CurvesSurfaceTransforms transforms{*self_ob_eval, surface_ob_eval}; - deform_curves(curves, - *surface_mesh_orig, - *surface_mesh_eval, - surface_uv_coords, - reverse_uv_sampler_orig, - reverse_uv_sampler_eval, - corner_normals_orig, - corner_normals_eval, - rest_positions, - transforms.surface_to_curves, - invalid_uv_count); + bke::CurvesEditHints *edit_hints = curves_geometry.get_curve_edit_hints_for_write(); + MutableSpan edit_hint_positions; + MutableSpan edit_hint_rotations; + if (edit_hints != nullptr) { + if (edit_hints->positions.has_value()) { + edit_hint_positions = *edit_hints->positions; + } + if (!edit_hints->deform_mats.has_value()) { + edit_hints->deform_mats.emplace(edit_hints->curves_id_orig.geometry.point_num, + float3x3::identity()); + edit_hints->deform_mats->fill(float3x3::identity()); + } + edit_hint_rotations = *edit_hints->deform_mats; + } + + if (edit_hint_positions.is_empty()) { + deform_curves(curves, + *surface_mesh_orig, + *surface_mesh_eval, + surface_uv_coords, + reverse_uv_sampler_orig, + reverse_uv_sampler_eval, + corner_normals_orig, + corner_normals_eval, + rest_positions, + transforms.surface_to_curves, + curves.positions_for_write(), + edit_hint_rotations, + invalid_uv_count); + } + else { + /* First deform the actual curves in the input geometry. */ + deform_curves(curves, + *surface_mesh_orig, + *surface_mesh_eval, + surface_uv_coords, + reverse_uv_sampler_orig, + reverse_uv_sampler_eval, + corner_normals_orig, + corner_normals_eval, + rest_positions, + transforms.surface_to_curves, + curves.positions_for_write(), + {}, + invalid_uv_count); + /* Then also deform edit curve information for use in sculpt mode. */ + const CurvesGeometry &curves_orig = CurvesGeometry::wrap(edit_hints->curves_id_orig.geometry); + deform_curves(curves_orig, + *surface_mesh_orig, + *surface_mesh_eval, + surface_uv_coords, + reverse_uv_sampler_orig, + reverse_uv_sampler_eval, + corner_normals_orig, + corner_normals_eval, + rest_positions, + transforms.surface_to_curves, + edit_hint_positions, + edit_hint_rotations, + invalid_uv_count); + } curves.tag_positions_changed(); diff --git a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc index 44793926bbd..cfb9cbf7e24 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc @@ -550,7 +550,7 @@ static void node_geo_exec(GeoNodeExecParams params) geometry_set, selection_field, method, seed, attribute_outputs, params); /* Keep instances because the original geometry set may contain instances that are processed as * well. */ - geometry_set.keep_only({GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_INSTANCES}); + geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_POINT_CLOUD}); }); params.set_output("Points", std::move(geometry_set)); diff --git a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc index 679a8ba4f8c..c6b0fb4c068 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc @@ -328,10 +328,11 @@ static void duplicate_curves(GeometrySet &geometry_set, const IndexAttributes &attribute_outputs) { if (!geometry_set.has_curves()) { - geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + geometry_set.remove_geometry_during_modify(); return; } - geometry_set.keep_only({GEO_COMPONENT_TYPE_CURVE, GEO_COMPONENT_TYPE_INSTANCES}); + geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_CURVE}); + GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set); const CurveComponent &src_component = *geometry_set.get_component_for_read(); const Curves &curves_id = *src_component.get_for_read(); @@ -516,10 +517,10 @@ static void duplicate_faces(GeometrySet &geometry_set, const IndexAttributes &attribute_outputs) { if (!geometry_set.has_mesh()) { - geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + geometry_set.remove_geometry_during_modify(); return; } - geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES}); + geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_MESH}); const MeshComponent &src_component = *geometry_set.get_component_for_read(); const Mesh &mesh = *src_component.get_for_read(); @@ -720,7 +721,7 @@ static void duplicate_edges(GeometrySet &geometry_set, const IndexAttributes &attribute_outputs) { if (!geometry_set.has_mesh()) { - geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + geometry_set.remove_geometry_during_modify(); return; }; const MeshComponent &src_component = *geometry_set.get_component_for_read(); @@ -1032,7 +1033,7 @@ static void duplicate_points(GeometrySet &geometry_set, } } component_types.append(GEO_COMPONENT_TYPE_INSTANCES); - geometry_set.keep_only(component_types); + geometry_set.keep_only_during_modify(component_types); } /** \} */ diff --git a/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc b/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc index 3ce26a086e2..119d895fead 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc @@ -213,7 +213,7 @@ static void node_geo_exec(GeoNodeExecParams params) } } - geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + geometry_set.remove_geometry_during_modify(); }); /* Unused references may have been added above. Remove those now so that other nodes don't diff --git a/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc index f14329c96da..5e0789e557b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc @@ -94,7 +94,7 @@ static void node_geo_exec(GeoNodeExecParams params) params.extract_input>("Position"), params.extract_input>("Radius"), params.extract_input>("Selection")); - geometry_set.keep_only({GEO_COMPONENT_TYPE_POINT_CLOUD}); + geometry_set.keep_only({GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_EDIT}); params.set_output("Points", std::move(geometry_set)); } else { diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc index 4a79ec159f4..40169def51e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc @@ -20,7 +20,7 @@ static void node_geo_exec(GeoNodeExecParams params) geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { const Mesh *mesh = geometry_set.get_mesh_for_read(); if (mesh == nullptr) { - geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + geometry_set.remove_geometry_during_modify(); return; } @@ -31,13 +31,13 @@ static void node_geo_exec(GeoNodeExecParams params) evaluator.evaluate(); const IndexMask selection = evaluator.get_evaluated_as_mask(0); if (selection.size() == 0) { - geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + geometry_set.remove_geometry_during_modify(); return; } bke::CurvesGeometry curves = geometry::mesh_to_curve_convert(*mesh, selection); geometry_set.replace_curves(bke::curves_new_nomain(std::move(curves))); - geometry_set.keep_only({GEO_COMPONENT_TYPE_CURVE, GEO_COMPONENT_TYPE_INSTANCES}); + geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_CURVE}); }); params.set_output("Curve", std::move(geometry_set)); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc index 8e621d3ed97..d3d1312be6d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc @@ -62,13 +62,13 @@ static void geometry_set_mesh_to_points(GeometrySet &geometry_set, { const MeshComponent *mesh_component = geometry_set.get_component_for_read(); if (mesh_component == nullptr) { - geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + geometry_set.remove_geometry_during_modify(); return; } GeometryComponentFieldContext field_context{*mesh_component, domain}; const int domain_num = mesh_component->attribute_domain_size(domain); if (domain_num == 0) { - geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + geometry_set.remove_geometry_during_modify(); return; } fn::FieldEvaluator evaluator{field_context, domain_num}; @@ -115,7 +115,7 @@ static void geometry_set_mesh_to_points(GeometrySet &geometry_set, } } - geometry_set.keep_only({GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_INSTANCES}); + geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_POINT_CLOUD}); } static void node_geo_exec(GeoNodeExecParams params) diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_volume.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_volume.cc index 5890e070b2f..92814a8bc5e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_volume.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_volume.cc @@ -154,7 +154,7 @@ static void node_geo_exec(GeoNodeExecParams params) if (geometry_set.has_mesh()) { Volume *volume = create_volume_from_mesh(*geometry_set.get_mesh_for_read(), params); geometry_set.replace_volume(volume); - geometry_set.keep_only({GEO_COMPONENT_TYPE_VOLUME, GEO_COMPONENT_TYPE_INSTANCES}); + geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_VOLUME}); } }); params.set_output("Volume", std::move(geometry_set)); diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc index 74fff8efeee..ed7ef9b7c71 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc @@ -25,14 +25,14 @@ static void geometry_set_points_to_vertices(GeometrySet &geometry_set, const PointCloudComponent *point_component = geometry_set.get_component_for_read(); if (point_component == nullptr) { - geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + geometry_set.remove_geometry_during_modify(); return; } GeometryComponentFieldContext field_context{*point_component, ATTR_DOMAIN_POINT}; const int domain_num = point_component->attribute_domain_size(ATTR_DOMAIN_POINT); if (domain_num == 0) { - geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + geometry_set.remove_geometry_during_modify(); return; } @@ -63,7 +63,7 @@ static void geometry_set_points_to_vertices(GeometrySet &geometry_set, } } - geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES}); + geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_MESH}); } static void node_geo_exec(GeoNodeExecParams params) diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc index 28a01a5cbce..4a3048e5f4a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc @@ -224,7 +224,7 @@ static void initialize_volume_component_from_points(GeoNodeExecParams ¶ms, new_grid->transform().postScale(voxel_size); BKE_volume_grid_add_vdb(*volume, "density", std::move(new_grid)); - r_geometry_set.keep_only({GEO_COMPONENT_TYPE_VOLUME, GEO_COMPONENT_TYPE_INSTANCES}); + r_geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_VOLUME}); r_geometry_set.replace_volume(volume); } #endif diff --git a/source/blender/nodes/geometry/nodes/node_geo_transform.cc b/source/blender/nodes/geometry/nodes/node_geo_transform.cc index 8e65e73d1e2..945d5fbdcac 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_transform.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_transform.cc @@ -123,6 +123,34 @@ static void translate_volume(Volume &volume, const float3 translation, const Dep transform_volume(volume, float4x4::from_location(translation), depsgraph); } +static void transform_curve_edit_hints(bke::CurvesEditHints &edit_hints, const float4x4 &transform) +{ + if (edit_hints.positions.has_value()) { + for (float3 &pos : *edit_hints.positions) { + pos = transform * pos; + } + } + float3x3 deform_mat; + copy_m3_m4(deform_mat.values, transform.values); + if (edit_hints.deform_mats.has_value()) { + for (float3x3 &mat : *edit_hints.deform_mats) { + mat = deform_mat * mat; + } + } + else { + edit_hints.deform_mats.emplace(edit_hints.curves_id_orig.geometry.point_num, deform_mat); + } +} + +static void translate_curve_edit_hints(bke::CurvesEditHints &edit_hints, const float3 &translation) +{ + if (edit_hints.positions.has_value()) { + for (float3 &pos : *edit_hints.positions) { + pos += translation; + } + } +} + static void translate_geometry_set(GeometrySet &geometry, const float3 translation, const Depsgraph &depsgraph) @@ -142,6 +170,9 @@ static void translate_geometry_set(GeometrySet &geometry, if (geometry.has_instances()) { translate_instances(geometry.get_component_for_write(), translation); } + if (bke::CurvesEditHints *curve_edit_hints = geometry.get_curve_edit_hints_for_write()) { + translate_curve_edit_hints(*curve_edit_hints, translation); + } } void transform_geometry_set(GeometrySet &geometry, @@ -163,6 +194,9 @@ void transform_geometry_set(GeometrySet &geometry, if (geometry.has_instances()) { transform_instances(geometry.get_component_for_write(), transform); } + if (bke::CurvesEditHints *curve_edit_hints = geometry.get_curve_edit_hints_for_write()) { + transform_curve_edit_hints(*curve_edit_hints, transform); + } } void transform_mesh(Mesh &mesh, diff --git a/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc index e1d1c67b8c8..91429560ac8 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc @@ -193,7 +193,7 @@ static void node_geo_exec(GeoNodeExecParams params) geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { Mesh *mesh = create_mesh_from_volume(geometry_set, params); geometry_set.replace_mesh(mesh); - geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES}); + geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_MESH}); }); #else params.error_message_add(NodeWarningType::Error, diff --git a/source/blender/nodes/intern/geometry_nodes_eval_log.cc b/source/blender/nodes/intern/geometry_nodes_eval_log.cc index cf7cbbdc4bf..55930dcb1ee 100644 --- a/source/blender/nodes/intern/geometry_nodes_eval_log.cc +++ b/source/blender/nodes/intern/geometry_nodes_eval_log.cc @@ -2,6 +2,7 @@ #include "NOD_geometry_nodes_eval_log.hh" +#include "BKE_curves.hh" #include "BKE_geometry_set_instances.hh" #include "DNA_modifier_types.h" @@ -264,6 +265,17 @@ GeometryValueLog::GeometryValueLog(const GeometrySet &geometry_set, bool log_ful info.instances_num = instances_component.instances_num(); break; } + case GEO_COMPONENT_TYPE_EDIT: { + const GeometryComponentEditData &edit_component = *( + const GeometryComponentEditData *)component; + if (const bke::CurvesEditHints *curve_edit_hints = + edit_component.curves_edit_hints_.get()) { + EditDataInfo &info = this->edit_data_info.emplace(); + info.has_deform_matrices = curve_edit_hints->deform_mats.has_value(); + info.has_deformed_positions = curve_edit_hints->positions.has_value(); + } + break; + } case GEO_COMPONENT_TYPE_VOLUME: { break; } diff --git a/source/blender/nodes/intern/node_geometry_exec.cc b/source/blender/nodes/intern/node_geometry_exec.cc index 56e9c9f0496..c6ebc22c43c 100644 --- a/source/blender/nodes/intern/node_geometry_exec.cc +++ b/source/blender/nodes/intern/node_geometry_exec.cc @@ -4,6 +4,7 @@ #include "DEG_depsgraph_query.h" +#include "BKE_curves.hh" #include "BKE_type_conversions.hh" #include "NOD_geometry_exec.hh" @@ -94,11 +95,27 @@ void GeoNodeExecParams::check_input_geometry_set(StringRef identifier, message += TIP_("Curve"); break; } + case GEO_COMPONENT_TYPE_EDIT: { + continue; + } } this->error_message_add(NodeWarningType::Info, std::move(message)); } } +void GeoNodeExecParams::check_output_geometry_set(const GeometrySet &geometry_set) const +{ + UNUSED_VARS_NDEBUG(geometry_set); +#ifdef DEBUG + if (const bke::CurvesEditHints *curve_edit_hints = + geometry_set.get_curve_edit_hints_for_read()) { + /* If this is not valid, it's likely that the number of stored deformed points does not match + * the number of points in the original data. */ + BLI_assert(curve_edit_hints->is_valid()); + } +#endif +} + const bNodeSocket *GeoNodeExecParams::find_available_socket(const StringRef name) const { for (const InputSocketRef *socket : provider_->dnode->inputs()) { -- cgit v1.2.3 From 6bcda04d1f1cc396dcc188678997105b09231bde Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 22 Jul 2022 09:59:28 -0500 Subject: Geometry Nodes: Port sample curves node to new data-block Use the newer more generic sampling and interpolation functions developed recently (ab444a80a280) instead of the `CurveEval` type. Functions are split up a bit more internally, to allow a separate mode for supplying the curve index directly in the future (T92474). In one basic test, the performance seems mostly unchanged from 3.1. Differential Revision: https://developer.blender.org/D14621 --- source/blender/blenlib/BLI_length_parameterize.hh | 43 ++- source/blender/functions/FN_field.hh | 11 + .../nodes/geometry/nodes/node_geo_curve_sample.cc | 318 +++++++++++++-------- 3 files changed, 242 insertions(+), 130 deletions(-) diff --git a/source/blender/blenlib/BLI_length_parameterize.hh b/source/blender/blenlib/BLI_length_parameterize.hh index 1b494c021a3..d81bcbe1e7a 100644 --- a/source/blender/blenlib/BLI_length_parameterize.hh +++ b/source/blender/blenlib/BLI_length_parameterize.hh @@ -6,6 +6,7 @@ * \ingroup bli */ +#include "BLI_index_mask.hh" #include "BLI_math_base.hh" #include "BLI_math_color.hh" #include "BLI_math_vector.hh" @@ -41,26 +42,38 @@ void accumulate_lengths(const Span values, const bool cyclic, MutableSpan -inline void interpolate(const Span src, - const Span indices, - const Span factors, - MutableSpan dst) +inline void interpolate_to_masked(const Span src, + const Span indices, + const Span factors, + const IndexMask dst_mask, + MutableSpan dst) { BLI_assert(indices.size() == factors.size()); - BLI_assert(indices.size() == dst.size()); + BLI_assert(indices.size() == dst_mask.size()); const int last_src_index = src.size() - 1; - for (const int i : dst.index_range()) { - const int prev_index = indices[i]; - const float factor = factors[i]; - const bool is_cyclic_case = prev_index == last_src_index; - if (is_cyclic_case) { - dst[i] = math::interpolate(src.last(), src.first(), factor); + dst_mask.to_best_mask_type([&](auto dst_mask) { + for (const int i : IndexRange(dst_mask.size())) { + const int prev_index = indices[i]; + const float factor = factors[i]; + const bool is_cyclic_case = prev_index == last_src_index; + if (is_cyclic_case) { + dst[dst_mask[i]] = math::interpolate(src.last(), src.first(), factor); + } + else { + dst[dst_mask[i]] = math::interpolate(src[prev_index], src[prev_index + 1], factor); + } } - else { - dst[i] = math::interpolate(src[prev_index], src[prev_index + 1], factor); - } - } + }); +} + +template +inline void interpolate(const Span src, + const Span indices, + const Span factors, + MutableSpan dst) +{ + interpolate_to_masked(src, indices, factors, dst.index_range(), dst); } /** diff --git a/source/blender/functions/FN_field.hh b/source/blender/functions/FN_field.hh index a8136d06c5f..bc42cab8db5 100644 --- a/source/blender/functions/FN_field.hh +++ b/source/blender/functions/FN_field.hh @@ -221,6 +221,17 @@ class FieldOperation : public FieldNode { const MultiFunction &multi_function() const; const CPPType &output_cpp_type(int output_index) const override; + + static std::shared_ptr Create(std::shared_ptr function, + Vector inputs = {}) + { + return std::make_shared(FieldOperation(std::move(function), inputs)); + } + static std::shared_ptr Create(const MultiFunction &function, + Vector inputs = {}) + { + return std::make_shared(FieldOperation(function, inputs)); + } }; class FieldContext; diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc index 01cf1d8db52..e80b600a740 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc @@ -1,8 +1,9 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include "BLI_task.hh" +#include "BLI_devirtualize_parameters.hh" +#include "BLI_length_parameterize.hh" -#include "BKE_spline.hh" +#include "BKE_curves.hh" #include "UI_interface.h" #include "UI_resources.h" @@ -58,34 +59,103 @@ static void node_update(bNodeTree *ntree, bNode *node) nodeSetSocketAvailability(ntree, length, mode == GEO_NODE_CURVE_SAMPLE_LENGTH); } -template static T sample_with_lookup(const Spline::LookupResult lookup, Span data) +static void sample_indices_and_lengths(const Span accumulated_lengths, + const Span sample_lengths, + const IndexMask mask, + MutableSpan r_segment_indices, + MutableSpan r_length_in_segment) { - return attribute_math::mix2( - lookup.factor, data[lookup.evaluated_index], data[lookup.next_evaluated_index]); + const float total_length = accumulated_lengths.last(); + length_parameterize::SampleSegmentHint hint; + + mask.to_best_mask_type([&](const auto mask) { + for (const int64_t i : mask) { + int segment_i; + float factor_in_segment; + length_parameterize::sample_at_length(accumulated_lengths, + std::clamp(sample_lengths[i], 0.0f, total_length), + segment_i, + factor_in_segment, + &hint); + const float segment_start = segment_i == 0 ? 0.0f : accumulated_lengths[segment_i - 1]; + const float segment_end = accumulated_lengths[segment_i]; + const float segment_length = segment_end - segment_start; + + r_segment_indices[i] = segment_i; + r_length_in_segment[i] = factor_in_segment * segment_length; + } + }); +} + +static void sample_indices_and_factors_to_compressed(const Span accumulated_lengths, + const Span sample_lengths, + const IndexMask mask, + MutableSpan r_segment_indices, + MutableSpan r_factor_in_segment) +{ + const float total_length = accumulated_lengths.last(); + length_parameterize::SampleSegmentHint hint; + + mask.to_best_mask_type([&](const auto mask) { + for (const int64_t i : IndexRange(mask.size())) { + const float length = sample_lengths[mask[i]]; + length_parameterize::sample_at_length(accumulated_lengths, + std::clamp(length, 0.0f, total_length), + r_segment_indices[i], + r_factor_in_segment[i], + &hint); + } + }); } +/** + * Given an array of accumulated lengths, find the segment indices that + * sample lengths lie on, and how far along the segment they are. + */ +class SampleFloatSegmentsFunction : public fn::MultiFunction { + private: + Array accumulated_lengths_; + + public: + SampleFloatSegmentsFunction(Array accumulated_lengths) + : accumulated_lengths_(std::move(accumulated_lengths)) + { + static fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static fn::MFSignature create_signature() + { + fn::MFSignatureBuilder signature{"Sample Curve Index"}; + signature.single_input("Length"); + + signature.single_output("Curve Index"); + signature.single_output("Length in Curve"); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + const VArraySpan lengths = params.readonly_single_input(0, "Length"); + MutableSpan indices = params.uninitialized_single_output(1, "Curve Index"); + MutableSpan lengths_in_segments = params.uninitialized_single_output( + 2, "Length in Curve"); + + sample_indices_and_lengths(accumulated_lengths_, lengths, mask, indices, lengths_in_segments); + } +}; + class SampleCurveFunction : public fn::MultiFunction { private: /** - * The function holds a geometry set instead of a curve or a curve component in order to - * maintain a reference to the geometry while the field tree is being built, so that the - * curve is not freed before the function can execute. + * The function holds a geometry set instead of curves or a curve component reference in order + * to maintain a ownership of the geometry while the field tree is being built and used, so + * that the curve is not freed before the function can execute. */ GeometrySet geometry_set_; - /** - * To support factor inputs, the node adds another field operation before this one to multiply by - * the curve's total length. Since that must calculate the spline lengths anyway, store them to - * reuse the calculation. - */ - Array spline_lengths_; - /** The last member of #spline_lengths_, extracted for convenience. */ - const float total_length_; public: - SampleCurveFunction(GeometrySet geometry_set, Array spline_lengths) - : geometry_set_(std::move(geometry_set)), - spline_lengths_(std::move(spline_lengths)), - total_length_(spline_lengths_.last()) + SampleCurveFunction(GeometrySet geometry_set) : geometry_set_(std::move(geometry_set)) { static fn::MFSignature signature = create_signature(); this->set_signature(&signature); @@ -93,7 +163,8 @@ class SampleCurveFunction : public fn::MultiFunction { static fn::MFSignature create_signature() { - blender::fn::MFSignatureBuilder signature{"Curve Sample"}; + blender::fn::MFSignatureBuilder signature{"Sample Curve"}; + signature.single_input("Curve Index"); signature.single_input("Length"); signature.single_output("Position"); signature.single_output("Tangent"); @@ -104,11 +175,11 @@ class SampleCurveFunction : public fn::MultiFunction { void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override { MutableSpan sampled_positions = params.uninitialized_single_output_if_required( - 1, "Position"); + 2, "Position"); MutableSpan sampled_tangents = params.uninitialized_single_output_if_required( - 2, "Tangent"); + 3, "Tangent"); MutableSpan sampled_normals = params.uninitialized_single_output_if_required( - 3, "Normal"); + 4, "Normal"); auto return_default = [&]() { if (!sampled_positions.is_empty()) { @@ -126,61 +197,78 @@ class SampleCurveFunction : public fn::MultiFunction { return return_default(); } - const CurveComponent *curve_component = geometry_set_.get_component_for_read(); - const std::unique_ptr curve = curves_to_curve_eval( - *curve_component->get_for_read()); - Span splines = curve->splines(); - if (splines.is_empty()) { + const Curves &curves_id = *geometry_set_.get_curves_for_read(); + const bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry); + if (curves.points_num() == 0) { return return_default(); } - - const VArray &lengths_varray = params.readonly_single_input(0, "Length"); - const VArraySpan lengths{lengths_varray}; -#ifdef DEBUG - for (const float length : lengths) { - /* Lengths must be in range of the curve's total length. This is ensured in - * #get_length_input_field by adding another multi-function before this one - * to clamp the lengths. */ - BLI_assert(length >= 0.0f && length <= total_length_); - } -#endif - - Array spline_indices(mask.min_array_size()); - for (const int i : mask) { - const float *offset = std::lower_bound( - spline_lengths_.begin(), spline_lengths_.end(), lengths[i]); - const int index = offset - spline_lengths_.data() - 1; - spline_indices[i] = std::max(index, 0); + Span evaluated_positions = curves.evaluated_positions(); + Span evaluated_tangents; + Span evaluated_normals; + if (!sampled_tangents.is_empty()) { + evaluated_tangents = curves.evaluated_tangents(); } - - /* Storing lookups in an array is unnecessary but will simplify custom attribute transfer. */ - Array lookups(mask.min_array_size()); - for (const int i : mask) { - const float length_in_spline = lengths[i] - spline_lengths_[spline_indices[i]]; - lookups[i] = splines[spline_indices[i]]->lookup_evaluated_length(length_in_spline); + if (!sampled_normals.is_empty()) { + evaluated_normals = curves.evaluated_normals(); } - if (!sampled_positions.is_empty()) { - for (const int i : mask) { - const Spline::LookupResult &lookup = lookups[i]; - const Span evaluated_positions = splines[spline_indices[i]]->evaluated_positions(); - sampled_positions[i] = sample_with_lookup(lookup, evaluated_positions); + const VArray curve_indices = params.readonly_single_input(0, "Curve Index"); + const VArraySpan lengths = params.readonly_single_input(1, "Length"); + const VArray cyclic = curves.cyclic(); + + Array indices; + Array factors; + + auto sample_curve = [&](const int curve_i, const IndexMask mask) { + /* Store the sampled indices and factors in arrays the size of the mask. + * Then, during interpolation, move the results back to the masked indices. */ + indices.reinitialize(mask.size()); + factors.reinitialize(mask.size()); + sample_indices_and_factors_to_compressed( + curves.evaluated_lengths_for_curve(curve_i, cyclic[curve_i]), + lengths, + mask, + indices, + factors); + + const IndexRange evaluated_points = curves.evaluated_points_for_curve(curve_i); + if (!sampled_positions.is_empty()) { + length_parameterize::interpolate_to_masked( + evaluated_positions.slice(evaluated_points), + indices, + factors, + mask, + sampled_positions); } - } - - if (!sampled_tangents.is_empty()) { - for (const int i : mask) { - const Spline::LookupResult &lookup = lookups[i]; - const Span evaluated_tangents = splines[spline_indices[i]]->evaluated_tangents(); - sampled_tangents[i] = math::normalize(sample_with_lookup(lookup, evaluated_tangents)); + if (!sampled_tangents.is_empty()) { + length_parameterize::interpolate_to_masked( + evaluated_tangents.slice(evaluated_points), indices, factors, mask, sampled_tangents); + for (const int64_t i : mask) { + sampled_tangents[i] = math::normalize(sampled_tangents[i]); + } } - } + if (!sampled_normals.is_empty()) { + length_parameterize::interpolate_to_masked( + evaluated_normals.slice(evaluated_points), indices, factors, mask, sampled_normals); + for (const int64_t i : mask) { + sampled_normals[i] = math::normalize(sampled_normals[i]); + } + } + }; - if (!sampled_normals.is_empty()) { - for (const int i : mask) { - const Spline::LookupResult &lookup = lookups[i]; - const Span evaluated_normals = splines[spline_indices[i]]->evaluated_normals(); - sampled_normals[i] = math::normalize(sample_with_lookup(lookup, evaluated_normals)); + if (curve_indices.is_single()) { + sample_curve(curve_indices.get_internal_single(), mask); + } + else { + MultiValueMap indices_per_curve; + devirtualize_varray(curve_indices, [&](const auto curve_indices) { + for (const int64_t i : mask) { + indices_per_curve.add(curve_indices[i], i); + } + }); + + for (const int curve_i : indices_per_curve.keys()) { + sample_curve(curve_i, IndexMask(indices_per_curve.lookup(curve_i))); } } } @@ -188,82 +276,82 @@ class SampleCurveFunction : public fn::MultiFunction { /** * Pre-process the lengths or factors used for the sampling, turning factors into lengths, and - * clamping between zero and the total length of the curve. Do this as a separate operation in the + * clamping between zero and the total length of the curves. Do this as a separate operation in the * field tree to make the sampling simpler, and to let the evaluator optimize better. * * \todo Use a mutable single input instead when they are supported. */ -static Field get_length_input_field(const GeoNodeExecParams ¶ms, - const float curve_total_length) +static Field get_length_input_field(GeoNodeExecParams params, + const GeometryNodeCurveSampleMode mode, + const float curves_total_length) { - const NodeGeometryCurveSample &storage = node_storage(params.node()); - const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)storage.mode; - if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { - /* Just make sure the length is in bounds of the curve. */ - Field length_field = params.get_input>("Length"); - auto clamp_fn = std::make_unique>( - __func__, - [curve_total_length](float length) { - return std::clamp(length, 0.0f, curve_total_length); - }, - fn::CustomMF_presets::AllSpanOrSingle()); - auto clamp_op = std::make_shared( - FieldOperation(std::move(clamp_fn), {std::move(length_field)})); - - return Field(std::move(clamp_op), 0); + return params.extract_input>("Length"); } - /* Convert the factor to a length and clamp it to the bounds of the curve. */ + /* Convert the factor to a length. */ Field factor_field = params.get_input>("Factor"); auto clamp_fn = std::make_unique>( __func__, - [curve_total_length](float factor) { - const float length = factor * curve_total_length; - return std::clamp(length, 0.0f, curve_total_length); - }, + [curves_total_length](float factor) { return factor * curves_total_length; }, fn::CustomMF_presets::AllSpanOrSingle()); - auto process_op = std::make_shared( - FieldOperation(std::move(clamp_fn), {std::move(factor_field)})); - return Field(std::move(process_op), 0); + return Field(FieldOperation::Create(std::move(clamp_fn), {std::move(factor_field)}), 0); } -static void node_geo_exec(GeoNodeExecParams params) +static Array curve_accumulated_lengths(const bke::CurvesGeometry &curves) { - GeometrySet geometry_set = params.extract_input("Curve"); - - const CurveComponent *component = geometry_set.get_component_for_read(); - if (component == nullptr) { - params.set_default_remaining_outputs(); - return; + curves.ensure_evaluated_lengths(); + + Array curve_lengths(curves.curves_num()); + const VArray cyclic = curves.cyclic(); + float length = 0.0f; + for (const int i : curves.curves_range()) { + length += curves.evaluated_length_total_for_curve(i, cyclic[i]); + curve_lengths[i] = length; } + return curve_lengths; +} - if (!component->has_curves()) { +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input("Curve"); + if (!geometry_set.has_curves()) { params.set_default_remaining_outputs(); return; } - const std::unique_ptr curve = curves_to_curve_eval(*component->get_for_read()); - - if (curve->splines().is_empty()) { + const Curves &curves_id = *geometry_set.get_curves_for_read(); + const bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry); + if (curves.points_num() == 0) { params.set_default_remaining_outputs(); return; } - Array spline_lengths = curve->accumulated_spline_lengths(); - const float total_length = spline_lengths.last(); + Array curve_lengths = curve_accumulated_lengths(curves); + const float total_length = curve_lengths.last(); if (total_length == 0.0f) { params.set_default_remaining_outputs(); return; } - Field length_field = get_length_input_field(params, total_length); + const NodeGeometryCurveSample &storage = node_storage(params.node()); + const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)storage.mode; + Field length_field = get_length_input_field(params, mode, total_length); + + auto sample_fn = std::make_unique(std::move(geometry_set)); - auto sample_fn = std::make_unique(std::move(geometry_set), - std::move(spline_lengths)); - auto sample_op = std::make_shared( - FieldOperation(std::move(sample_fn), {length_field})); + std::shared_ptr sample_op; + if (curves.curves_num() == 1) { + sample_op = FieldOperation::Create(std::move(sample_fn), + {fn::make_constant_field(0), std::move(length_field)}); + } + else { + auto index_fn = std::make_unique(std::move(curve_lengths)); + auto index_op = FieldOperation::Create(std::move(index_fn), {std::move(length_field)}); + sample_op = FieldOperation::Create(std::move(sample_fn), + {Field(index_op, 0), Field(index_op, 1)}); + } params.set_output("Position", Field(sample_op, 0)); params.set_output("Tangent", Field(sample_op, 1)); -- cgit v1.2.3 From e4eaf424b9ce4800e64d35ddfebe28f986b14647 Mon Sep 17 00:00:00 2001 From: Germano Cavalcante Date: Fri, 22 Jul 2022 12:17:22 -0300 Subject: Fix nodes not transforming Error in {rB98bf714b37c1} --- source/blender/editors/transform/transform_convert.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/editors/transform/transform_convert.c b/source/blender/editors/transform/transform_convert.c index e6cf2dd47d9..d1c2af75274 100644 --- a/source/blender/editors/transform/transform_convert.c +++ b/source/blender/editors/transform/transform_convert.c @@ -1057,7 +1057,7 @@ static TransConvertTypeInfo *convert_type_get(const TransInfo *t, Object **r_obj return &TransConvertType_Graph; } if (t->spacetype == SPACE_NODE) { - return &TransConvertType_Graph; + return &TransConvertType_Node; } if (t->spacetype == SPACE_CLIP) { if (t->options & CTX_MOVIECLIP) { -- cgit v1.2.3 From c40971d79a887820d621705b29f65f833d9b9f52 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 22 Jul 2022 10:31:10 -0500 Subject: Fix T99873: Store named attribute node cannot write to vertex groups Since fd5e5dac8946b13599, the node would remove the attribute before adding it again, which lost the vertex group status of an attribute, meaning they were written as arbitrary attributes. Now, the node first tries to write to attributes with the same domain and data-type, which covers the vertex group case. Then it falls back to removing the attribute and adding it again. Even that can fail though, so I added an error message to make that a bit clearer. Differential Revision: https://developer.blender.org/D15514 --- .../nodes/node_geo_store_named_attribute.cc | 61 +++++++++++++++------- 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc index 69a4fad10e2..2a784430683 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc @@ -1,8 +1,12 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ +#include + #include "UI_interface.h" #include "UI_resources.h" +#include "RNA_enum_types.h" + #include "NOD_socket_search_link.hh" #include "BKE_type_conversions.hh" @@ -85,7 +89,8 @@ static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) static void try_capture_field_on_geometry(GeometryComponent &component, const StringRef name, const eAttrDomain domain, - const GField &field) + const GField &field, + std::atomic &failure) { const int domain_size = component.attribute_domain_size(domain); if (domain_size == 0) { @@ -100,7 +105,7 @@ static void try_capture_field_on_geometry(GeometryComponent &component, const eCustomDataType data_type = bke::cpp_type_to_custom_data_type(type); /* Could avoid allocating a new buffer if: - * - We are writing to an attribute that exists already. + * - We are writing to an attribute that exists already with the correct domain and type. * - The field does not depend on that attribute (we can't easily check for that yet). */ void *buffer = MEM_mallocN(type.size() * domain_size, __func__); @@ -108,25 +113,27 @@ static void try_capture_field_on_geometry(GeometryComponent &component, evaluator.add_with_destination(field, GMutableSpan{type, buffer, domain_size}); evaluator.evaluate(); - attributes.remove(name); - if (attributes.contains(name)) { - GAttributeWriter write_attribute = attributes.lookup_for_write(name); - if (write_attribute && write_attribute.domain == domain && - write_attribute.varray.type() == type) { - write_attribute.varray.set_all(buffer); - write_attribute.finish(); - } - else { - /* Cannot change type of built-in attribute. */ + if (GAttributeWriter attribute = attributes.lookup_for_write(name)) { + if (attribute.domain == domain && attribute.varray.type() == type) { + attribute.varray.set_all(buffer); + attribute.finish(); + type.destruct_n(buffer, domain_size); + MEM_freeN(buffer); + return; } - type.destruct_n(buffer, domain_size); - MEM_freeN(buffer); } - else { - if (!attributes.add(name, domain, data_type, bke::AttributeInitMove{buffer})) { - MEM_freeN(buffer); + + if (attributes.remove(name)) { + if (attributes.add(name, domain, data_type, bke::AttributeInitMove{buffer})) { + return; } } + + /* If the name corresponds to a builtin attribute, removing the attribute might fail if + * it's required, and adding the attribute might fail if the domain or type is incorrect. */ + type.destruct_n(buffer, domain_size); + MEM_freeN(buffer); + failure = true; } static void node_geo_exec(GeoNodeExecParams params) @@ -177,12 +184,14 @@ static void node_geo_exec(GeoNodeExecParams params) break; } + std::atomic failure = false; + /* Run on the instances component separately to only affect the top level of instances. */ if (domain == ATTR_DOMAIN_INSTANCE) { if (geometry_set.has_instances()) { GeometryComponent &component = geometry_set.get_component_for_write( GEO_COMPONENT_TYPE_INSTANCES); - try_capture_field_on_geometry(component, name, domain, field); + try_capture_field_on_geometry(component, name, domain, field, failure); } } else { @@ -191,12 +200,26 @@ static void node_geo_exec(GeoNodeExecParams params) {GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE}) { if (geometry_set.has(type)) { GeometryComponent &component = geometry_set.get_component_for_write(type); - try_capture_field_on_geometry(component, name, domain, field); + try_capture_field_on_geometry(component, name, domain, field, failure); } } }); } + if (failure) { + const char *domain_name = nullptr; + RNA_enum_name_from_value(rna_enum_attribute_domain_items, domain, &domain_name); + const char *type_name = nullptr; + RNA_enum_name_from_value(rna_enum_attribute_type_items, data_type, &type_name); + char *message = BLI_sprintfN( + TIP_("Failed to write to attribute \"%s\" with domain \"%s\" and type \"%s\""), + name.c_str(), + TIP_(domain_name), + TIP_(type_name)); + params.error_message_add(NodeWarningType::Warning, message); + MEM_freeN(message); + } + params.set_output("Geometry", std::move(geometry_set)); } -- cgit v1.2.3 From 98395e0bdfc849e2d2770052c6e8651a42500608 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 22 Jul 2022 10:49:09 -0500 Subject: Cleanup: Use r_ prefix for boolean return parameters Also rearrange some lines to simplify logic. --- source/blender/io/alembic/intern/abc_reader_object.cc | 6 +++--- source/blender/modifiers/intern/MOD_nodes.cc | 6 +++--- .../nodes/geometry/nodes/node_geo_store_named_attribute.cc | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/source/blender/io/alembic/intern/abc_reader_object.cc b/source/blender/io/alembic/intern/abc_reader_object.cc index a698eeca8f1..db056c0eef6 100644 --- a/source/blender/io/alembic/intern/abc_reader_object.cc +++ b/source/blender/io/alembic/intern/abc_reader_object.cc @@ -218,12 +218,12 @@ Alembic::AbcGeom::IXform AbcObjectReader::xform() void AbcObjectReader::read_matrix(float r_mat[4][4] /* local matrix */, const chrono_t time, const float scale, - bool &is_constant) + bool &r_is_constant) { IXform ixform = xform(); if (!ixform) { unit_m4(r_mat); - is_constant = true; + r_is_constant = true; return; } @@ -254,7 +254,7 @@ void AbcObjectReader::read_matrix(float r_mat[4][4] /* local matrix */, mul_m4_m4m4(r_mat, scale_mat, r_mat); } - is_constant = schema.isConstant(); + r_is_constant = schema.isConstant(); } void AbcObjectReader::addCacheModifier() diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 01e4d5ff6b3..223e4b757b7 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -200,7 +200,7 @@ static bool node_needs_own_transform_relation(const bNode &node) static void process_nodes_for_depsgraph(const bNodeTree &tree, Set &ids, - bool &needs_own_transform_relation) + bool &r_needs_own_transform_relation) { Set handled_groups; @@ -211,10 +211,10 @@ static void process_nodes_for_depsgraph(const bNodeTree &tree, if (ELEM(node->type, NODE_GROUP, NODE_CUSTOM_GROUP)) { const bNodeTree *group = (bNodeTree *)node->id; if (group != nullptr && handled_groups.add(group)) { - process_nodes_for_depsgraph(*group, ids, needs_own_transform_relation); + process_nodes_for_depsgraph(*group, ids, r_needs_own_transform_relation); } } - needs_own_transform_relation |= node_needs_own_transform_relation(*node); + r_needs_own_transform_relation |= node_needs_own_transform_relation(*node); } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc index 2a784430683..dbd68f4c783 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc @@ -90,13 +90,13 @@ static void try_capture_field_on_geometry(GeometryComponent &component, const StringRef name, const eAttrDomain domain, const GField &field, - std::atomic &failure) + std::atomic &r_failure) { - const int domain_size = component.attribute_domain_size(domain); + MutableAttributeAccessor attributes = *component.attributes_for_write(); + const int domain_size = attributes.domain_size(domain); if (domain_size == 0) { return; } - MutableAttributeAccessor attributes = *component.attributes_for_write(); GeometryComponentFieldContext field_context{component, domain}; const IndexMask mask{IndexMask(domain_size)}; @@ -133,7 +133,7 @@ static void try_capture_field_on_geometry(GeometryComponent &component, * it's required, and adding the attribute might fail if the domain or type is incorrect. */ type.destruct_n(buffer, domain_size); MEM_freeN(buffer); - failure = true; + r_failure = true; } static void node_geo_exec(GeoNodeExecParams params) -- cgit v1.2.3 From 35843ddcd80e5957656b157650da950f228d2808 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 22 Jul 2022 11:36:55 -0500 Subject: Fix T99835: Incorrect title case for two node names --- source/blender/nodes/NOD_static_types.h | 4 ++-- source/blender/nodes/function/nodes/node_fn_align_euler_to_vector.cc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 71d8f014399..d743c341885 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -260,7 +260,7 @@ DefNode(TextureNode, TEX_NODE_PROC+TEX_NOISE, 0, "TEX_NO DefNode(TextureNode, TEX_NODE_PROC+TEX_STUCCI, 0, "TEX_STUCCI", TexStucci, "Stucci", "" ) DefNode(TextureNode, TEX_NODE_PROC+TEX_DISTNOISE, 0, "TEX_DISTNOISE", TexDistNoise, "Distorted Noise", "" ) -DefNode(FunctionNode, FN_NODE_ALIGN_EULER_TO_VECTOR, def_fn_align_euler_to_vector, "ALIGN_EULER_TO_VECTOR", AlignEulerToVector, "Align Euler To Vector", "") +DefNode(FunctionNode, FN_NODE_ALIGN_EULER_TO_VECTOR, def_fn_align_euler_to_vector, "ALIGN_EULER_TO_VECTOR", AlignEulerToVector, "Align Euler to Vector", "") DefNode(FunctionNode, FN_NODE_BOOLEAN_MATH, def_boolean_math, "BOOLEAN_MATH", BooleanMath, "Boolean Math", "") DefNode(FunctionNode, FN_NODE_COMBINE_COLOR, def_fn_combsep_color, "COMBINE_COLOR", CombineColor, "Combine Color", "") DefNode(FunctionNode, FN_NODE_COMPARE, def_compare, "COMPARE", Compare, "Compare", "") @@ -358,7 +358,7 @@ DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_LINE, def_geo_mesh_line, "MESH_PRI DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_UV_SPHERE, 0, "MESH_PRIMITIVE_UV_SPHERE", MeshUVSphere, "UV Sphere", "") DefNode(GeometryNode, GEO_NODE_MESH_TO_CURVE, 0, "MESH_TO_CURVE", MeshToCurve, "Mesh to Curve", "") DefNode(GeometryNode, GEO_NODE_MESH_TO_POINTS, def_geo_mesh_to_points, "MESH_TO_POINTS", MeshToPoints, "Mesh to Points", "") -DefNode(GeometryNode, GEO_NODE_MESH_TO_VOLUME, def_geo_mesh_to_volume, "MESH_TO_VOLUME", MeshToVolume, "Mesh To Volume", "") +DefNode(GeometryNode, GEO_NODE_MESH_TO_VOLUME, def_geo_mesh_to_volume, "MESH_TO_VOLUME", MeshToVolume, "Mesh to Volume", "") DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, def_geo_object_info, "OBJECT_INFO", ObjectInfo, "Object Info", "") DefNode(GeometryNode, GEO_NODE_POINTS, 0, "POINTS", Points, "Points", "") DefNode(GeometryNode, GEO_NODE_POINTS_TO_VERTICES, 0, "POINTS_TO_VERTICES", PointsToVertices, "Points to Vertices", "") diff --git a/source/blender/nodes/function/nodes/node_fn_align_euler_to_vector.cc b/source/blender/nodes/function/nodes/node_fn_align_euler_to_vector.cc index 2cb455832e5..7d08d57c503 100644 --- a/source/blender/nodes/function/nodes/node_fn_align_euler_to_vector.cc +++ b/source/blender/nodes/function/nodes/node_fn_align_euler_to_vector.cc @@ -150,7 +150,7 @@ class MF_AlignEulerToVector : public fn::MultiFunction { static fn::MFSignature create_signature() { - fn::MFSignatureBuilder signature{"Align Euler To Vector"}; + fn::MFSignatureBuilder signature{"Align Euler to Vector"}; signature.single_input("Rotation"); signature.single_input("Factor"); signature.single_input("Vector"); -- cgit v1.2.3 From 676a2f690c3e3fffe2e515208d4d308e0a96e8e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Fri, 22 Jul 2022 20:32:17 +0200 Subject: EEVEE-Next: Fix render not working The swaps during accumulation were ignored because of the way the `SwapChain<>` implementation works. Using external references and updating them fixes the issue. --- source/blender/draw/engines/eevee_next/eevee_film.cc | 20 ++++++++++++++++---- source/blender/draw/engines/eevee_next/eevee_film.hh | 6 ++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/eevee_film.cc b/source/blender/draw/engines/eevee_next/eevee_film.cc index d3b09beedaa..486413a0834 100644 --- a/source/blender/draw/engines/eevee_next/eevee_film.cc +++ b/source/blender/draw/engines/eevee_next/eevee_film.cc @@ -393,10 +393,10 @@ void Film::sync() /* NOTE(@fclem): 16 is the max number of sampled texture in many implementations. * If we need more, we need to pack more of the similar passes in the same textures as arrays or * use image binding instead. */ - DRW_shgroup_uniform_image_ref(grp, "in_weight_img", &weight_tx_.current()); - DRW_shgroup_uniform_image_ref(grp, "out_weight_img", &weight_tx_.next()); - DRW_shgroup_uniform_texture_ref_ex(grp, "in_combined_tx", &combined_tx_.current(), filter); - DRW_shgroup_uniform_image_ref(grp, "out_combined_img", &combined_tx_.next()); + DRW_shgroup_uniform_image_ref(grp, "in_weight_img", &weight_src_tx_); + DRW_shgroup_uniform_image_ref(grp, "out_weight_img", &weight_dst_tx_); + DRW_shgroup_uniform_texture_ref_ex(grp, "in_combined_tx", &combined_src_tx_, filter); + DRW_shgroup_uniform_image_ref(grp, "out_combined_img", &combined_dst_tx_); DRW_shgroup_uniform_image_ref(grp, "depth_img", &depth_tx_); DRW_shgroup_uniform_image_ref(grp, "color_accum_img", &color_accum_tx_); DRW_shgroup_uniform_image_ref(grp, "value_accum_img", &value_accum_tx_); @@ -541,6 +541,12 @@ void Film::accumulate(const DRWView *view) update_sample_table(); + /* Need to update the static references as there could have change from a previous swap. */ + weight_src_tx_ = weight_tx_.current(); + weight_dst_tx_ = weight_tx_.next(); + combined_src_tx_ = combined_tx_.current(); + combined_dst_tx_ = combined_tx_.next(); + data_.display_only = false; data_.push_update(); @@ -567,6 +573,12 @@ void Film::display() GPU_framebuffer_bind(dfbl->default_fb); GPU_framebuffer_viewport_set(dfbl->default_fb, UNPACK2(data_.offset), UNPACK2(data_.extent)); + /* Need to update the static references as there could have change from a previous swap. */ + weight_src_tx_ = weight_tx_.current(); + weight_dst_tx_ = weight_tx_.next(); + combined_src_tx_ = combined_tx_.current(); + combined_dst_tx_ = combined_tx_.next(); + data_.display_only = true; data_.push_update(); diff --git a/source/blender/draw/engines/eevee_next/eevee_film.hh b/source/blender/draw/engines/eevee_next/eevee_film.hh index 1be95b3ac6b..4ccd5684396 100644 --- a/source/blender/draw/engines/eevee_next/eevee_film.hh +++ b/source/blender/draw/engines/eevee_next/eevee_film.hh @@ -45,8 +45,14 @@ class Film { Texture depth_tx_; /** Combined "Color" buffer. Double buffered to allow re-projection. */ SwapChain combined_tx_; + /** Static reference as SwapChain does not actually move the objects when swapping. */ + GPUTexture *combined_src_tx_ = nullptr; + GPUTexture *combined_dst_tx_ = nullptr; /** Weight buffers. Double buffered to allow updating it during accumulation. */ SwapChain weight_tx_; + /** Static reference as SwapChain does not actually move the objects when swapping. */ + GPUTexture *weight_src_tx_ = nullptr; + GPUTexture *weight_dst_tx_ = nullptr; /** Extent used by the render buffers when rendering the main views. */ int2 render_extent_ = int2(-1); /** User setting to disable reprojection. Useful for debugging or have a more precise render. */ -- cgit v1.2.3 From 7d8b651268df74157f89858eda9ddce9fd452138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Fri, 22 Jul 2022 21:03:06 +0200 Subject: EEVEE-Next: Add exposure awareness to denoising This uses the exposure to get a better approximation of the perceptual brighness of a sample before accumulating it. Note that we do not modify exposure of the image. Only the samples weights are computed differently. --- source/blender/draw/engines/eevee_next/eevee_film.cc | 11 ++++++----- source/blender/draw/engines/eevee_next/eevee_shader_shared.hh | 2 +- .../draw/engines/eevee_next/shaders/eevee_film_lib.glsl | 5 +---- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/eevee_film.cc b/source/blender/draw/engines/eevee_next/eevee_film.cc index 486413a0834..42c61ac90a8 100644 --- a/source/blender/draw/engines/eevee_next/eevee_film.cc +++ b/source/blender/draw/engines/eevee_next/eevee_film.cc @@ -164,6 +164,8 @@ inline bool operator!=(const FilmData &a, const FilmData &b) void Film::init(const int2 &extent, const rcti *output_rect) { Sampling &sampling = inst_.sampling; + Scene &scene = *inst_.scene; + SceneEEVEE &scene_eevee = scene.eevee; init_aovs(); @@ -230,9 +232,8 @@ void Film::init(const int2 &extent, const rcti *output_rect) data.offset = int2(output_rect->xmin, output_rect->ymin); data.extent_inv = 1.0f / float2(data.extent); /* Disable filtering if sample count is 1. */ - data.filter_size = (sampling.sample_count() == 1) ? - 0.0f : - clamp_f(inst_.scene->r.gauss, 0.0f, 100.0f); + data.filter_size = (sampling.sample_count() == 1) ? 0.0f : + clamp_f(scene.r.gauss, 0.0f, 100.0f); /* TODO(fclem): parameter hidden in experimental. * We need to figure out LOD bias first in order to preserve texture crispiness. */ data.scaling_factor = 1; @@ -254,7 +255,7 @@ void Film::init(const int2 &extent, const rcti *output_rect) EEVEE_RENDER_PASS_MIST | EEVEE_RENDER_PASS_SHADOW | EEVEE_RENDER_PASS_AO; - data_.exposure = 1.0f /* TODO */; + data_.exposure_scale = pow2f(scene.view_settings.exposure); data_.has_data = (enabled_passes_ & data_passes) != 0; data_.any_render_pass_1 = (enabled_passes_ & color_passes_1) != 0; data_.any_render_pass_2 = (enabled_passes_ & color_passes_2) != 0; @@ -346,7 +347,7 @@ void Film::init(const int2 &extent, const rcti *output_rect) } } - force_disable_reprojection_ = (inst_.scene->eevee.flag & SCE_EEVEE_TAA_REPROJECTION) == 0; + force_disable_reprojection_ = (scene_eevee.flag & SCE_EEVEE_TAA_REPROJECTION) == 0; } void Film::sync() diff --git a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh index 9cf7f75b2c3..819f49756d7 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh +++ b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh @@ -202,7 +202,7 @@ struct FilmData { /** Settings to render mist pass */ float mist_scale, mist_bias, mist_exponent; /** Scene exposure used for better noise reduction. */ - float exposure; + float exposure_scale; /** Scaling factor for scaled resolution rendering. */ int scaling_factor; /** Film pixel filter radius. */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl index 451b8e8fca7..efd3d2dfe35 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl @@ -48,8 +48,6 @@ vec3 film_scene_linear_from_YCoCg(vec3 ycocg_color) vec4 film_texelfetch_as_YCoCg_opacity(sampler2D tx, ivec2 texel) { vec4 color = texelFetch(combined_tx, texel, 0); - /* Can we assume safe color from earlier pass? */ - // color = safe_color(color); /* Convert transmittance to opacity. */ color.a = saturate(1.0 - color.a); /* Transform to YCoCg for accumulation. */ @@ -62,8 +60,7 @@ float film_luma_weight(float luma) { /* Slide 20 of "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014. */ /* To preserve more details in dark areas, we use a bigger bias. */ - /* TODO(fclem): exposure weighting. */ - return 1.0 / (4.0 + luma); + return 1.0 / (4.0 + luma * film_buf.exposure_scale); } /* -------------------------------------------------------------------- */ -- cgit v1.2.3 From 80b2fc59d11d5814afe7e219c535525a8d494c17 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 22 Jul 2022 15:49:53 -0500 Subject: Fix T99873: Use evaluated vertex groups in armature modifier Geometry nodes has added the ability to modify mesh vertex groups during evaluation (see 3b6ee8cee7080af2). However, the armature modifier always uses the vertex groups from the original object. This is wrong for the modifier stack, where each modifier is meant to use the output of the previous. This commit makes the armature modifier use the evaluated vertex groups if they are available. Otherwise it uses the originals like before. Differential Revision: https://developer.blender.org/D15515 --- source/blender/blenkernel/intern/armature_deform.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/source/blender/blenkernel/intern/armature_deform.c b/source/blender/blenkernel/intern/armature_deform.c index 8532c7d1c15..cc1bfcc087c 100644 --- a/source/blender/blenkernel/intern/armature_deform.c +++ b/source/blender/blenkernel/intern/armature_deform.c @@ -483,14 +483,11 @@ static void armature_deform_coords_impl(const Object *ob_arm, } if (BKE_object_supports_vertex_groups(ob_target)) { - /* get the def_nr for the overall armature vertex group if present */ - armature_def_nr = BKE_object_defgroup_name_index(ob_target, defgrp_name); - - defbase_len = BKE_object_defgroup_count(ob_target); - + const ID *target_data_id = NULL; if (ob_target->type == OB_MESH) { + target_data_id = me_target == NULL ? (const ID *)ob_target->data : &me_target->id; if (em_target == NULL) { - const Mesh *me = ob_target->data; + const Mesh *me = (const Mesh *)target_data_id; dverts = me->dvert; if (dverts) { dverts_len = me->totvert; @@ -499,18 +496,25 @@ static void armature_deform_coords_impl(const Object *ob_arm, } else if (ob_target->type == OB_LATTICE) { const Lattice *lt = ob_target->data; + target_data_id = (const ID *)ob_target->data; dverts = lt->dvert; if (dverts) { dverts_len = lt->pntsu * lt->pntsv * lt->pntsw; } } else if (ob_target->type == OB_GPENCIL) { + target_data_id = (const ID *)ob_target->data; dverts = gps_target->dvert; if (dverts) { dverts_len = gps_target->totpoints; } } + /* Collect the vertex group names from the evaluated data. */ + armature_def_nr = BKE_id_defgroup_name_index(target_data_id, defgrp_name); + const ListBase *defbase = BKE_id_defgroup_list_get(target_data_id); + defbase_len = BLI_listbase_count(defbase); + /* get a vertex-deform-index to posechannel array */ if (deformflag & ARM_DEF_VGROUP) { /* if we have a Mesh, only use dverts if it has them */ @@ -531,7 +535,6 @@ static void armature_deform_coords_impl(const Object *ob_arm, * * - Check whether keeping this consistent across frames gives speedup. */ - const ListBase *defbase = BKE_object_defgroup_list(ob_target); int i; LISTBASE_FOREACH_INDEX (bDeformGroup *, dg, defbase, i) { pchan_from_defbase[i] = BKE_pose_channel_find_name(ob_arm->pose, dg->name); -- cgit v1.2.3 From 82467e5dcf985123a2b47692b133d50b08d6fa6b Mon Sep 17 00:00:00 2001 From: Chris Blackbourn Date: Sat, 23 Jul 2022 09:08:59 +1200 Subject: Cleanup: Typo with uv sphere normal creation Regression from 087f27a52f78 --- source/blender/bmesh/operators/bmo_primitive.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/blender/bmesh/operators/bmo_primitive.c b/source/blender/bmesh/operators/bmo_primitive.c index 2ed0964d735..c60ffbedb94 100644 --- a/source/blender/bmesh/operators/bmo_primitive.c +++ b/source/blender/bmesh/operators/bmo_primitive.c @@ -856,9 +856,9 @@ void bmo_create_uvsphere_exec(BMesh *bm, BMOperator *op) for (a = 0; a <= tot; a++) { /* Going in this direction, then edge extruding, makes normals face outward */ float sin_phi, cos_phi; - sin_cos_from_fraction(a, tot, &sin_phi, &cos_phi); + sin_cos_from_fraction(a, 2 * tot, &sin_phi, &cos_phi); - vec[0] = 0.0; + vec[0] = 0.0f; vec[1] = rad * sin_phi; vec[2] = rad * cos_phi; eve = BM_vert_create(bm, vec, NULL, BM_CREATE_NOP); @@ -1391,7 +1391,7 @@ void bmo_create_cone_exec(BMesh *bm, BMOperator *op) BMFace **side_faces = MEM_mallocN(sizeof(*side_faces) * side_faces_len, __func__); for (int i = 0; i < segs; i++) { - /* Calculate with doubles for higher precision, see: T87779. */ + /* Calculate with higher precision, see: T87779. */ float sin_phi, cos_phi; sin_cos_from_fraction(i, segs, &sin_phi, &cos_phi); -- cgit v1.2.3 From fc8b9efb24768f33a95fe6d9938820cd45beb361 Mon Sep 17 00:00:00 2001 From: Aaron Carlisle Date: Fri, 22 Jul 2022 21:00:34 -0400 Subject: Update RNA to User manual mappings --- release/scripts/modules/rna_manual_reference.py | 57 +++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/release/scripts/modules/rna_manual_reference.py b/release/scripts/modules/rna_manual_reference.py index cfce84f84e3..0d75b190d3f 100644 --- a/release/scripts/modules/rna_manual_reference.py +++ b/release/scripts/modules/rna_manual_reference.py @@ -62,6 +62,7 @@ url_manual_mapping = ( ("bpy.types.lineartgpencilmodifier.use_image_boundary_trimming*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-image-boundary-trimming"), ("bpy.types.rigidbodyconstraint.use_override_solver_iterations*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-use-override-solver-iterations"), ("bpy.types.toolsettings.use_transform_correct_face_attributes*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-transform-correct-face-attributes"), + ("bpy.types.brushcurvessculptsettings.interpolate_point_count*", "sculpt_paint/curves_sculpting/tools/add_curves.html#bpy-types-brushcurvessculptsettings-interpolate-point-count"), ("bpy.types.cyclesrendersettings.adaptive_scrambling_distance*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-adaptive-scrambling-distance"), ("bpy.types.cyclesrendersettings.preview_adaptive_min_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-preview-adaptive-min-samples"), ("bpy.types.lineartgpencilmodifier.use_face_mark_keep_contour*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-face-mark-keep-contour"), @@ -83,6 +84,7 @@ url_manual_mapping = ( ("bpy.types.spacespreadsheet.display_context_path_collapsed*", "editors/spreadsheet.html#bpy-types-spacespreadsheet-display-context-path-collapsed"), ("bpy.types.toolsettings.annotation_stroke_placement_view2d*", "interface/annotate_tool.html#bpy-types-toolsettings-annotation-stroke-placement-view2d"), ("bpy.types.toolsettings.annotation_stroke_placement_view3d*", "interface/annotate_tool.html#bpy-types-toolsettings-annotation-stroke-placement-view3d"), + ("bpy.types.brushcurvessculptsettings.density_add_attempts*", "sculpt_paint/curves_sculpting/tools/density_curves.html#bpy-types-brushcurvessculptsettings-density-add-attempts"), ("bpy.types.brushgpencilsettings.use_random_press_strength*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-random-press-strength"), ("bpy.types.fluiddomainsettings.use_collision_border_front*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-front"), ("bpy.types.fluiddomainsettings.use_collision_border_right*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-right"), @@ -103,6 +105,7 @@ url_manual_mapping = ( ("bpy.types.fluiddomainsettings.use_collision_border_left*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-left"), ("bpy.types.lineartgpencilmodifier.use_intersection_match*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-intersection-match"), ("bpy.types.rendersettings_simplify_gpencil_view_modifier*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-view-modifier"), + ("bpy.types.brushcurvessculptsettings.interpolate_length*", "sculpt_paint/curves_sculpting/tools/add_curves.html#bpy-types-brushcurvessculptsettings-interpolate-length"), ("bpy.types.brushgpencilsettings.eraser_thickness_factor*", "grease_pencil/modes/draw/tools/erase.html#bpy-types-brushgpencilsettings-eraser-thickness-factor"), ("bpy.types.brushgpencilsettings.use_random_press_radius*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-random-press-radius"), ("bpy.types.brushgpencilsettings.use_settings_stabilizer*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-settings-stabilizer"), @@ -122,6 +125,7 @@ url_manual_mapping = ( ("bpy.types.toolsettings.use_transform_pivot_point_align*", "scene_layout/object/tools/tool_settings.html#bpy-types-toolsettings-use-transform-pivot-point-align"), ("bpy.types.animvizmotionpaths.show_keyframe_action_all*", "animation/motion_paths.html#bpy-types-animvizmotionpaths-show-keyframe-action-all"), ("bpy.types.brush.show_multiplane_scrape_planes_preview*", "sculpt_paint/sculpting/tools/multiplane_scrape.html#bpy-types-brush-show-multiplane-scrape-planes-preview"), + ("bpy.types.brushcurvessculptsettings.interpolate_shape*", "sculpt_paint/curves_sculpting/tools/add_curves.html#bpy-types-brushcurvessculptsettings-interpolate-shape"), ("bpy.types.brushgpencilsettings.eraser_strength_factor*", "grease_pencil/modes/draw/tools/erase.html#bpy-types-brushgpencilsettings-eraser-strength-factor"), ("bpy.types.cyclesmaterialsettings.volume_interpolation*", "render/cycles/material_settings.html#bpy-types-cyclesmaterialsettings-volume-interpolation"), ("bpy.types.cyclesrendersettings.denoising_input_passes*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-denoising-input-passes"), @@ -135,6 +139,8 @@ url_manual_mapping = ( ("bpy.types.sequencertoolsettings.snap_to_current_frame*", "video_editing/edit/montage/editing.html#bpy-types-sequencertoolsettings-snap-to-current-frame"), ("bpy.ops.object.geometry_nodes_input_attribute_toggle*", "modeling/modifiers/generate/geometry_nodes.html#bpy-ops-object-geometry-nodes-input-attribute-toggle"), ("bpy.types.animvizmotionpaths.show_keyframe_highlight*", "animation/motion_paths.html#bpy-types-animvizmotionpaths-show-keyframe-highlight"), + ("bpy.types.brushcurvessculptsettings.minimum_distance*", "sculpt_paint/curves_sculpting/tools/density_curves.html#bpy-types-brushcurvessculptsettings-minimum-distance"), + ("bpy.types.brushcurvessculptsettings.points_per_curve*", "sculpt_paint/curves_sculpting/tools/add_curves.html#bpy-types-brushcurvessculptsettings-points-per-curve"), ("bpy.types.brushgpencilsettings.pen_subdivision_steps*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-pen-subdivision-steps"), ("bpy.types.brushgpencilsettings.use_strength_pressure*", "grease_pencil/modes/draw/tools/erase.html#bpy-types-brushgpencilsettings-use-strength-pressure"), ("bpy.types.brushgpencilsettings.use_stroke_random_hue*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-stroke-random-hue"), @@ -193,6 +199,7 @@ url_manual_mapping = ( ("bpy.types.animvizmotionpaths.show_keyframe_numbers*", "animation/motion_paths.html#bpy-types-animvizmotionpaths-show-keyframe-numbers"), ("bpy.types.brush.cloth_constraint_softbody_strength*", "sculpt_paint/sculpting/tools/cloth.html#bpy-types-brush-cloth-constraint-softbody-strength"), ("bpy.types.brush.elastic_deform_volume_preservation*", "sculpt_paint/sculpting/tools/elastic_deform.html#bpy-types-brush-elastic-deform-volume-preservation"), + ("bpy.types.brushcurvessculptsettings.minimum_length*", "sculpt_paint/curves_sculpting/tools/grow_shrink_curves.html#bpy-types-brushcurvessculptsettings-minimum-length"), ("bpy.types.brushgpencilsettings.fill_simplify_level*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-fill-simplify-level"), ("bpy.types.brushgpencilsettings.random_value_factor*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-random-value-factor"), ("bpy.types.brushgpencilsettings.use_jitter_pressure*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-jitter-pressure"), @@ -224,6 +231,7 @@ url_manual_mapping = ( ("bpy.types.bakesettings.use_pass_ambient_occlusion*", "render/cycles/baking.html#bpy-types-bakesettings-use-pass-ambient-occlusion"), ("bpy.types.brush.surface_smooth_shape_preservation*", "sculpt_paint/sculpting/tools/smooth.html#bpy-types-brush-surface-smooth-shape-preservation"), ("bpy.types.brush.use_cloth_pin_simulation_boundary*", "sculpt_paint/sculpting/tools/cloth.html#bpy-types-brush-use-cloth-pin-simulation-boundary"), + ("bpy.types.brushcurvessculptsettings.scale_uniform*", "sculpt_paint/curves_sculpting/tools/grow_shrink_curves.html#bpy-types-brushcurvessculptsettings-scale-uniform"), ("bpy.types.brushgpencilsettings.show_fill_boundary*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-show-fill-boundary"), ("bpy.types.brushgpencilsettings.use_default_eraser*", "grease_pencil/modes/draw/tools/erase.html#bpy-types-brushgpencilsettings-use-default-eraser"), ("bpy.types.colormanagedsequencercolorspacesettings*", "render/color_management.html#bpy-types-colormanagedsequencercolorspacesettings"), @@ -267,6 +275,8 @@ url_manual_mapping = ( ("bpy.ops.gpencil.vertex_color_brightness_contrast*", "grease_pencil/modes/vertex_paint/editing.html#bpy-ops-gpencil-vertex-color-brightness-contrast"), ("bpy.ops.view3d.edit_mesh_extrude_individual_move*", "modeling/meshes/editing/face/extrude_faces.html#bpy-ops-view3d-edit-mesh-extrude-individual-move"), ("bpy.ops.view3d.edit_mesh_extrude_manifold_normal*", "modeling/meshes/tools/extrude_manifold.html#bpy-ops-view3d-edit-mesh-extrude-manifold-normal"), + ("bpy.types.brushcurvessculptsettings.curve_length*", "sculpt_paint/curves_sculpting/tools/add_curves.html#bpy-types-brushcurvessculptsettings-curve-length"), + ("bpy.types.brushcurvessculptsettings.density_mode*", "sculpt_paint/curves_sculpting/tools/density_curves.html#bpy-types-brushcurvessculptsettings-density-mode"), ("bpy.types.brushgpencilsettings.pen_smooth_factor*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-pen-smooth-factor"), ("bpy.types.brushgpencilsettings.random_hue_factor*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-random-hue-factor"), ("bpy.types.cyclescurverendersettings.subdivisions*", "render/cycles/render_settings/hair.html#bpy-types-cyclescurverendersettings-subdivisions"), @@ -368,6 +378,7 @@ url_manual_mapping = ( ("bpy.types.viewlayer.use_pass_cryptomatte_object*", "render/layers/passes.html#bpy-types-viewlayer-use-pass-cryptomatte-object"), ("bpy.ops.armature.rigify_apply_selection_colors*", "addons/rigging/rigify/metarigs.html#bpy-ops-armature-rigify-apply-selection-colors"), ("bpy.ops.ed.lib_id_generate_preview_from_object*", "editors/asset_browser.html#bpy-ops-ed-lib-id-generate-preview-from-object"), + ("bpy.types.brushcurvessculptsettings.add_amount*", "sculpt_paint/curves_sculpting/tools/add_curves.html#bpy-types-brushcurvessculptsettings-add-amount"), ("bpy.types.brushgpencilsettings.fill_layer_mode*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-fill-layer-mode"), ("bpy.types.brushgpencilsettings.random_strength*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-random-strength"), ("bpy.types.brushgpencilsettings.simplify_factor*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-simplify-factor"), @@ -426,6 +437,7 @@ url_manual_mapping = ( ("bpy.types.cyclesworldsettings.volume_sampling*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-volume-sampling"), ("bpy.types.editbone.bbone_handle_use_scale_end*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-handle-use-scale-end"), ("bpy.types.ffmpegsettings.constant_rate_factor*", "render/output/properties/output.html#bpy-types-ffmpegsettings-constant-rate-factor"), + ("bpy.types.fieldsettings.use_guide_path_weight*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-use-guide-path-weight"), ("bpy.types.fluiddomainsettings.adapt_threshold*", "physics/fluid/type/domain/gas/adaptive_domain.html#bpy-types-fluiddomainsettings-adapt-threshold"), ("bpy.types.fluiddomainsettings.cache_directory*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-cache-directory"), ("bpy.types.fluiddomainsettings.cache_frame_end*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-cache-frame-end"), @@ -478,6 +490,8 @@ url_manual_mapping = ( ("bpy.types.cyclesrendersettings.use_denoising*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-use-denoising"), ("bpy.types.editbone.bbone_custom_handle_start*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-custom-handle-start"), ("bpy.types.editbone.bbone_handle_use_ease_end*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-handle-use-ease-end"), + ("bpy.types.fieldsettings.guide_kink_amplitude*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-guide-kink-amplitude"), + ("bpy.types.fieldsettings.guide_kink_frequency*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-guide-kink-frequency"), ("bpy.types.fluiddomainsettings.additional_res*", "physics/fluid/type/domain/gas/adaptive_domain.html#bpy-types-fluiddomainsettings-additional-res"), ("bpy.types.fluiddomainsettings.dissolve_speed*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-dissolve-speed"), ("bpy.types.fluiddomainsettings.effector_group*", "physics/fluid/type/domain/collections.html#bpy-types-fluiddomainsettings-effector-group"), @@ -591,6 +605,8 @@ url_manual_mapping = ( ("bpy.types.cyclesrendersettings.use_fast_gi*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-use-fast-gi"), ("bpy.types.editbone.bbone_custom_handle_end*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-custom-handle-end"), ("bpy.types.editbone.bbone_handle_type_start*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-handle-type-start"), + ("bpy.types.fieldsettings.guide_clump_amount*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-guide-clump-amount"), + ("bpy.types.fieldsettings.use_guide_path_add*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-use-guide-path-add"), ("bpy.types.fileselectparams.recursion_level*", "editors/file_browser.html#bpy-types-fileselectparams-recursion-level"), ("bpy.types.fluiddomainsettings.adapt_margin*", "physics/fluid/type/domain/gas/adaptive_domain.html#bpy-types-fluiddomainsettings-adapt-margin"), ("bpy.types.fluiddomainsettings.burning_rate*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-burning-rate"), @@ -633,6 +649,7 @@ url_manual_mapping = ( ("bpy.types.viewlayer.pass_cryptomatte_depth*", "render/layers/passes.html#bpy-types-viewlayer-pass-cryptomatte-depth"), ("bpy.ops.clip.stabilize_2d_rotation_select*", "movie_clip/tracking/clip/selecting.html#bpy-ops-clip-stabilize-2d-rotation-select"), ("bpy.ops.constraint.disable_keep_transform*", "animation/constraints/interface/common.html#bpy-ops-constraint-disable-keep-transform"), + ("bpy.ops.curves.convert_to_particle_system*", "sculpt_paint/curves_sculpting/introduction.html#bpy-ops-curves-convert-to-particle-system"), ("bpy.ops.gpencil.stroke_reset_vertex_color*", "grease_pencil/modes/vertex_paint/editing.html#bpy-ops-gpencil-stroke-reset-vertex-color"), ("bpy.ops.object.modifier_apply_as_shapekey*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-apply-as-shapekey"), ("bpy.ops.object.vertex_group_normalize_all*", "sculpt_paint/weight_paint/editing.html#bpy-ops-object-vertex-group-normalize-all"), @@ -656,6 +673,9 @@ url_manual_mapping = ( ("bpy.types.cyclesvisibilitysettings.camera*", "render/cycles/world_settings.html#bpy-types-cyclesvisibilitysettings-camera"), ("bpy.types.cyclesworldsettings.max_bounces*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-max-bounces"), ("bpy.types.ffmpegsettings.use_max_b_frames*", "render/output/properties/output.html#bpy-types-ffmpegsettings-use-max-b-frames"), + ("bpy.types.fieldsettings.apply_to_location*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-apply-to-location"), + ("bpy.types.fieldsettings.apply_to_rotation*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-apply-to-rotation"), + ("bpy.types.fieldsettings.guide_clump_shape*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-guide-clump-shape"), ("bpy.types.fluiddomainsettings.domain_type*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-domain-type"), ("bpy.types.fluiddomainsettings.flame_smoke*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-flame-smoke"), ("bpy.types.fluiddomainsettings.fluid_group*", "physics/fluid/type/domain/collections.html#bpy-types-fluiddomainsettings-fluid-group"), @@ -733,6 +753,9 @@ url_manual_mapping = ( ("bpy.types.cyclescamerasettings.longitude*", "render/cycles/object_settings/cameras.html#bpy-types-cyclescamerasettings-longitude"), ("bpy.types.editbone.bbone_handle_type_end*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-handle-type-end"), ("bpy.types.editbone.use_endroll_as_inroll*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-use-endroll-as-inroll"), + ("bpy.types.fieldsettings.guide_kink_shape*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-guide-kink-shape"), + ("bpy.types.fieldsettings.use_max_distance*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-use-max-distance"), + ("bpy.types.fieldsettings.use_min_distance*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-use-min-distance"), ("bpy.types.fileselectparams.filter_search*", "editors/file_browser.html#bpy-types-fileselectparams-filter-search"), ("bpy.types.fluiddomainsettings.cache_type*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-cache-type"), ("bpy.types.fluiddomainsettings.flip_ratio*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-flip-ratio"), @@ -802,6 +825,7 @@ url_manual_mapping = ( ("bpy.ops.poselib.restore_previous_action*", "animation/armatures/posing/editing/pose_library.html#bpy-ops-poselib-restore-previous-action"), ("bpy.ops.preferences.reset_default_theme*", "editors/preferences/themes.html#bpy-ops-preferences-reset-default-theme"), ("bpy.ops.scene.view_layer_add_lightgroup*", "render/layers/passes.html#bpy-ops-scene-view-layer-add-lightgroup"), + ("bpy.ops.sculpt_curves.min_distance_edit*", "sculpt_paint/curves_sculpting/tools/density_curves.html#bpy-ops-sculpt-curves-min-distance-edit"), ("bpy.ops.sequencer.strip_transform_clear*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-strip-transform-clear"), ("bpy.ops.spreadsheet.add_row_filter_rule*", "editors/spreadsheet.html#bpy-ops-spreadsheet-add-row-filter-rule"), ("bpy.types.animdata.action_extrapolation*", "editors/nla/sidebar.html#bpy-types-animdata-action-extrapolation"), @@ -820,6 +844,8 @@ url_manual_mapping = ( ("bpy.types.cyclesrendersettings.denoiser*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-denoiser"), ("bpy.types.editbone.use_inherit_rotation*", "animation/armatures/bones/properties/relations.html#bpy-types-editbone-use-inherit-rotation"), ("bpy.types.ffmpegsettings.audio_channels*", "render/output/properties/output.html#bpy-types-ffmpegsettings-audio-channels"), + ("bpy.types.fieldsettings.guide_kink_axis*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-guide-kink-axis"), + ("bpy.types.fieldsettings.guide_kink_type*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-guide-kink-type"), ("bpy.types.fileselectparams.display_size*", "editors/file_browser.html#bpy-types-fileselectparams-display-size"), ("bpy.types.fileselectparams.display_type*", "editors/file_browser.html#bpy-types-fileselectparams-display-type"), ("bpy.types.fluiddomainsettings.use_guide*", "physics/fluid/type/domain/guides.html#bpy-types-fluiddomainsettings-use-guide"), @@ -898,6 +924,7 @@ url_manual_mapping = ( ("bpy.types.ffmpegsettings.ffmpeg_preset*", "render/output/properties/output.html#bpy-types-ffmpegsettings-ffmpeg-preset"), ("bpy.types.ffmpegsettings.use_autosplit*", "render/output/properties/output.html#bpy-types-ffmpegsettings-use-autosplit"), ("bpy.types.ffmpegsettings.video_bitrate*", "render/output/properties/output.html#bpy-types-ffmpegsettings-video-bitrate"), + ("bpy.types.fieldsettings.use_absorption*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-use-absorption"), ("bpy.types.fileselectparams.show_hidden*", "editors/file_browser.html#bpy-types-fileselectparams-show-hidden"), ("bpy.types.fileselectparams.sort_method*", "editors/file_browser.html#bpy-types-fileselectparams-sort-method"), ("bpy.types.fluiddomainsettings.clipping*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-clipping"), @@ -946,6 +973,7 @@ url_manual_mapping = ( ("bpy.types.volumedisplay.wireframe_type*", "modeling/volumes/properties.html#bpy-types-volumedisplay-wireframe-type"), ("bpy.ops.anim.channels_editable_toggle*", "editors/graph_editor/channels.html#bpy-ops-anim-channels-editable-toggle"), ("bpy.ops.curve.normals_make_consistent*", "modeling/curves/editing/control_points.html#bpy-ops-curve-normals-make-consistent"), + ("bpy.ops.curves.snap_curves_to_surface*", "sculpt_paint/curves_sculpting/introduction.html#bpy-ops-curves-snap-curves-to-surface"), ("bpy.ops.ed.lib_id_load_custom_preview*", "editors/asset_browser.html#bpy-ops-ed-lib-id-load-custom-preview"), ("bpy.ops.gpencil.frame_clean_duplicate*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-frame-clean-duplicate"), ("bpy.ops.gpencil.stroke_simplify_fixed*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-simplify-fixed"), @@ -982,6 +1010,8 @@ url_manual_mapping = ( ("bpy.types.editbone.use_local_location*", "animation/armatures/bones/properties/relations.html#bpy-types-editbone-use-local-location"), ("bpy.types.ffmpegsettings.audio_volume*", "render/output/properties/output.html#bpy-types-ffmpegsettings-audio-volume"), ("bpy.types.ffmpegsettings.max_b_frames*", "render/output/properties/output.html#bpy-types-ffmpegsettings-max-b-frames"), + ("bpy.types.fieldsettings.falloff_power*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-falloff-power"), + ("bpy.types.fieldsettings.guide_minimum*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-guide-minimum"), ("bpy.types.fileselectparams.use_filter*", "editors/file_browser.html#bpy-types-fileselectparams-use-filter"), ("bpy.types.fluiddomainsettings.gravity*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-gravity"), ("bpy.types.fluidflowsettings.flow_type*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-flow-type"), @@ -1068,6 +1098,9 @@ url_manual_mapping = ( ("bpy.types.compositornodekeyingscreen*", "compositing/types/matte/keying_screen.html#bpy-types-compositornodekeyingscreen"), ("bpy.types.dynamicpaintcanvassettings*", "physics/dynamic_paint/canvas.html#bpy-types-dynamicpaintcanvassettings"), ("bpy.types.ffmpegsettings.audio_codec*", "render/output/properties/output.html#bpy-types-ffmpegsettings-audio-codec"), + ("bpy.types.fieldsettings.distance_max*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-distance-max"), + ("bpy.types.fieldsettings.distance_min*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-distance-min"), + ("bpy.types.fieldsettings.falloff_type*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-falloff-type"), ("bpy.types.fileselectparams.directory*", "editors/file_browser.html#bpy-types-fileselectparams-directory"), ("bpy.types.fluidflowsettings.use_flow*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-use-flow"), ("bpy.types.fmodifierfunctiongenerator*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierfunctiongenerator"), @@ -1133,6 +1166,7 @@ url_manual_mapping = ( ("bpy.ops.pose.visual_transform_apply*", "animation/armatures/posing/editing/apply.html#bpy-ops-pose-visual-transform-apply"), ("bpy.ops.poselib.convert_old_poselib*", "animation/armatures/posing/editing/pose_library.html#bpy-ops-poselib-convert-old-poselib"), ("bpy.ops.render.shutter_curve_preset*", "render/cycles/render_settings/motion_blur.html#bpy-ops-render-shutter-curve-preset"), + ("bpy.ops.sculpt_curves.select_random*", "sculpt_paint/curves_sculpting/introduction.html#bpy-ops-sculpt-curves-select-random"), ("bpy.ops.sequencer.view_ghost_border*", "editors/video_sequencer/preview/sidebar.html#bpy-ops-sequencer-view-ghost-border"), ("bpy.ops.ui.override_type_set_button*", "files/linked_libraries/library_overrides.html#bpy-ops-ui-override-type-set-button"), ("bpy.types.animdata.action_influence*", "editors/nla/sidebar.html#bpy-types-animdata-action-influence"), @@ -1144,6 +1178,7 @@ url_manual_mapping = ( ("bpy.types.brush.use_cloth_collision*", "sculpt_paint/sculpting/tools/cloth.html#bpy-types-brush-use-cloth-collision"), ("bpy.types.brush.use_grab_silhouette*", "sculpt_paint/sculpting/tools/grab.html#bpy-types-brush-use-grab-silhouette"), ("bpy.types.brush.use_primary_overlay*", "sculpt_paint/brush/cursor.html#bpy-types-brush-use-primary-overlay"), + ("bpy.types.brushcurvessculptsettings*", "sculpt_paint/curves_sculpting/index.html#bpy-types-brushcurvessculptsettings"), ("bpy.types.brushtextureslot.map_mode*", "sculpt_paint/brush/texture.html#bpy-types-brushtextureslot-map-mode"), ("bpy.types.camera.passepartout_alpha*", "render/cameras.html#bpy-types-camera-passepartout-alpha"), ("bpy.types.colorrampelement.position*", "interface/controls/templates/color_ramp.html#bpy-types-colorrampelement-position"), @@ -1158,6 +1193,8 @@ url_manual_mapping = ( ("bpy.types.editbone.use_scale_easing*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-use-scale-easing"), ("bpy.types.ffmpegsettings.buffersize*", "render/output/properties/output.html#bpy-types-ffmpegsettings-buffersize"), ("bpy.types.ffmpegsettings.packetsize*", "render/output/properties/output.html#bpy-types-ffmpegsettings-packetsize"), + ("bpy.types.fieldsettings.wind_factor*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-wind-factor"), + ("bpy.types.fieldsettings.z_direction*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-z-direction"), ("bpy.types.fileselectparams.filename*", "editors/file_browser.html#bpy-types-fileselectparams-filename"), ("bpy.types.fluiddomainsettings.alpha*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-alpha"), ("bpy.types.fluidflowsettings.density*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-density"), @@ -1261,6 +1298,7 @@ url_manual_mapping = ( ("bpy.types.copytransformsconstraint*", "animation/constraints/transform/copy_transforms.html#bpy-types-copytransformsconstraint"), ("bpy.types.correctivesmoothmodifier*", "modeling/modifiers/deform/corrective_smooth.html#bpy-types-correctivesmoothmodifier"), ("bpy.types.curve.bevel_factor_start*", "modeling/curves/properties/geometry.html#bpy-types-curve-bevel-factor-start"), + ("bpy.types.fieldsettings.guide_free*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-guide-free"), ("bpy.types.fluiddomainsettings.beta*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-beta"), ("bpy.types.fluidmodifier.fluid_type*", "physics/fluid/type/index.html#bpy-types-fluidmodifier-fluid-type"), ("bpy.types.freestylelineset.exclude*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-exclude"), @@ -1295,6 +1333,7 @@ url_manual_mapping = ( ("bpy.types.particleinstancemodifier*", "modeling/modifiers/physics/particle_instance.html#bpy-types-particleinstancemodifier"), ("bpy.types.rendersettings.hair_type*", "render/eevee/render_settings/hair.html#bpy-types-rendersettings-hair-type"), ("bpy.types.rendersettings.tile_size*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-tile-size"), + ("bpy.types.scenedisplay.viewport_aa*", "render/workbench/sampling.html#bpy-types-scenedisplay-viewport-aa"), ("bpy.types.sequencertimelineoverlay*", "editors/video_sequencer/sequencer/display.html#bpy-types-sequencertimelineoverlay"), ("bpy.types.sequencetransform.filter*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-types-sequencetransform-filter"), ("bpy.types.sequencetransform.offset*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-types-sequencetransform-offset"), @@ -1337,6 +1376,7 @@ url_manual_mapping = ( ("bpy.ops.preferences.theme_install*", "editors/preferences/themes.html#bpy-ops-preferences-theme-install"), ("bpy.ops.render.play_rendered_anim*", "render/output/animation_player.html#bpy-ops-render-play-rendered-anim"), ("bpy.ops.sculpt.set_pivot_position*", "sculpt_paint/sculpting/editing/sculpt.html#bpy-ops-sculpt-set-pivot-position"), + ("bpy.ops.sculpt_curves.select_grow*", "sculpt_paint/curves_sculpting/introduction.html#bpy-ops-sculpt-curves-select-grow"), ("bpy.ops.sequencer.image_strip_add*", "video_editing/edit/montage/strips/image.html#bpy-ops-sequencer-image-strip-add"), ("bpy.ops.sequencer.movie_strip_add*", "video_editing/edit/montage/strips/movie.html#bpy-ops-sequencer-movie-strip-add"), ("bpy.ops.sequencer.reassign_inputs*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-reassign-inputs"), @@ -1460,10 +1500,13 @@ url_manual_mapping = ( ("bpy.ops.rigidbody.mass_calculate*", "scene_layout/object/editing/rigid_body.html#bpy-ops-rigidbody-mass-calculate"), ("bpy.ops.screen.spacedata_cleanup*", "advanced/operators.html#bpy-ops-screen-spacedata-cleanup"), ("bpy.ops.sculpt.detail_flood_fill*", "sculpt_paint/sculpting/tool_settings/dyntopo.html#bpy-ops-sculpt-detail-flood-fill"), + ("bpy.ops.sculpt_curves.select_all*", "sculpt_paint/curves_sculpting/introduction.html#bpy-ops-sculpt-curves-select-all"), + ("bpy.ops.sculpt_curves.select_end*", "sculpt_paint/curves_sculpting/introduction.html#bpy-ops-sculpt-curves-select-end"), ("bpy.ops.sequencer.duplicate_move*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-duplicate-move"), ("bpy.ops.sequencer.select_grouped*", "video_editing/edit/montage/selecting.html#bpy-ops-sequencer-select-grouped"), ("bpy.ops.sequencer.select_handles*", "video_editing/edit/montage/selecting.html#bpy-ops-sequencer-select-handles"), ("bpy.ops.uv.average_islands_scale*", "modeling/meshes/uv/editing.html#bpy-ops-uv-average-islands-scale"), + ("bpy.types.action.use_frame_range*", "animation/actions.html#bpy-types-action-use-frame-range"), ("bpy.types.armature.axes_position*", "animation/armatures/properties/display.html#bpy-types-armature-axes-position"), ("bpy.types.armature.pose_position*", "animation/armatures/properties/skeleton.html#bpy-types-armature-pose-position"), ("bpy.types.bakesettings.use_clear*", "render/cycles/baking.html#bpy-types-bakesettings-use-clear"), @@ -1504,6 +1547,7 @@ url_manual_mapping = ( ("bpy.types.ffmpegsettings.maxrate*", "render/output/properties/output.html#bpy-types-ffmpegsettings-maxrate"), ("bpy.types.ffmpegsettings.minrate*", "render/output/properties/output.html#bpy-types-ffmpegsettings-minrate"), ("bpy.types.ffmpegsettings.muxrate*", "render/output/properties/output.html#bpy-types-ffmpegsettings-muxrate"), + ("bpy.types.fieldsettings.strength*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-strength"), ("bpy.types.freestylelinestyle.gap*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-gap"), ("bpy.types.freestylesettings.mode*", "render/freestyle/view_layer/freestyle.html#bpy-types-freestylesettings-mode"), ("bpy.types.functionnodefloattoint*", "modeling/geometry_nodes/utilities/float_to_integer.html#bpy-types-functionnodefloattoint"), @@ -1534,6 +1578,7 @@ url_manual_mapping = ( ("bpy.types.opacitygpencilmodifier*", "grease_pencil/modifiers/color/opacity.html#bpy-types-opacitygpencilmodifier"), ("bpy.types.particlesystemmodifier*", "physics/particles/index.html#bpy-types-particlesystemmodifier"), ("bpy.types.rendersettings.threads*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-threads"), + ("bpy.types.scenedisplay.render_aa*", "render/workbench/sampling.html#bpy-types-scenedisplay-render-aa"), ("bpy.types.sceneeevee.motion_blur*", "render/eevee/render_settings/motion_blur.html#bpy-types-sceneeevee-motion-blur"), ("bpy.types.sceneeevee.taa_samples*", "render/eevee/render_settings/sampling.html#bpy-types-sceneeevee-taa-samples"), ("bpy.types.sculpt.radial_symmetry*", "sculpt_paint/sculpting/tool_settings/symmetry.html#bpy-types-sculpt-radial-symmetry"), @@ -1632,6 +1677,7 @@ url_manual_mapping = ( ("bpy.types.compositornodevecblur*", "compositing/types/filter/vector_blur.html#bpy-types-compositornodevecblur"), ("bpy.types.curve.use_fill_deform*", "modeling/curves/properties/shape.html#bpy-types-curve-use-fill-deform"), ("bpy.types.curve.use_path_follow*", "modeling/curves/properties/path_animation.html#bpy-types-curve-use-path-follow"), + ("bpy.types.curves.surface_uv_map*", "sculpt_paint/curves_sculpting/introduction.html#bpy-types-curves-surface-uv-map"), ("bpy.types.dampedtrackconstraint*", "animation/constraints/tracking/damped_track.html#bpy-types-dampedtrackconstraint"), ("bpy.types.distortednoisetexture*", "render/materials/legacy_textures/types/distorted_noise.html#bpy-types-distortednoisetexture"), ("bpy.types.dopesheet.filter_text*", "editors/graph_editor/channels.html#bpy-types-dopesheet-filter-text"), @@ -1871,6 +1917,8 @@ url_manual_mapping = ( ("bpy.types.curve.use_map_taper*", "modeling/curves/properties/geometry.html#bpy-types-curve-use-map-taper"), ("bpy.types.cyclesworldsettings*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings"), ("bpy.types.dashgpencilmodifier*", "grease_pencil/modifiers/generate/dash.html#bpy-types-dashgpencilmodifier"), + ("bpy.types.fieldsettings.noise*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-noise"), + ("bpy.types.fieldsettings.shape*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-shape"), ("bpy.types.fluiddomainsettings*", "physics/fluid/type/domain/index.html#bpy-types-fluiddomainsettings"), ("bpy.types.functionnodecompare*", "modeling/geometry_nodes/utilities/compare.html#bpy-types-functionnodecompare"), ("bpy.types.geometrynodeinputid*", "modeling/geometry_nodes/input/id.html#bpy-types-geometrynodeinputid"), @@ -1997,6 +2045,9 @@ url_manual_mapping = ( ("bpy.types.curve.twist_smooth*", "modeling/curves/properties/shape.html#bpy-types-curve-twist-smooth"), ("bpy.types.curvepaintsettings*", "modeling/curves/tools/draw.html#bpy-types-curvepaintsettings"), ("bpy.types.fcurve.array_index*", "editors/graph_editor/fcurves/properties.html#bpy-types-fcurve-array-index"), + ("bpy.types.fieldsettings.flow*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-flow"), + ("bpy.types.fieldsettings.seed*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-seed"), + ("bpy.types.fieldsettings.size*", "physics/forces/force_fields/types/turbulence.html#bpy-types-fieldsettings-size"), ("bpy.types.fileselectidfilter*", "editors/file_browser.html#bpy-types-fileselectidfilter"), ("bpy.types.fmodifiergenerator*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifiergenerator"), ("bpy.types.freestylelinestyle*", "render/freestyle/view_layer/line_style/index.html#bpy-types-freestylelinestyle"), @@ -2105,6 +2156,7 @@ url_manual_mapping = ( ("bpy.ops.uv.minimize_stretch*", "modeling/meshes/uv/editing.html#bpy-ops-uv-minimize-stretch"), ("bpy.ops.uv.select_edge_ring*", "editors/uv/selecting.html#bpy-ops-uv-select-edge-ring"), ("bpy.ops.wm.save_as_mainfile*", "files/blend/open_save.html#bpy-ops-wm-save-as-mainfile"), + ("bpy.types.action.use_cyclic*", "animation/actions.html#bpy-types-action-use-cyclic"), ("bpy.types.alphaoversequence*", "video_editing/edit/montage/strips/effects/alpha_over_under_overdrop.html#bpy-types-alphaoversequence"), ("bpy.types.armatureeditbones*", "animation/armatures/bones/editing/index.html#bpy-types-armatureeditbones"), ("bpy.types.brush.pose_offset*", "sculpt_paint/sculpting/tools/pose.html#bpy-types-brush-pose-offset"), @@ -2213,6 +2265,7 @@ url_manual_mapping = ( ("bpy.ops.sculpt.mask_filter*", "sculpt_paint/sculpting/editing/mask.html#bpy-ops-sculpt-mask-filter"), ("bpy.ops.transform.tosphere*", "modeling/meshes/editing/mesh/transform/to_sphere.html#bpy-ops-transform-tosphere"), ("bpy.ops.view3d.clip_border*", "editors/3dview/navigate/regions.html#bpy-ops-view3d-clip-border"), + ("bpy.ops.view3d.toggle_xray*", "modeling/meshes/selecting/introduction.html#bpy-ops-view3d-toggle-xray"), ("bpy.ops.view3d.zoom_border*", "editors/3dview/navigate/navigation.html#bpy-ops-view3d-zoom-border"), ("bpy.ops.wm.previews_ensure*", "files/blend/previews.html#bpy-ops-wm-previews-ensure"), ("bpy.ops.wm.properties_edit*", "files/data_blocks.html#bpy-ops-wm-properties-edit"), @@ -2422,6 +2475,7 @@ url_manual_mapping = ( ("bpy.ops.wm.save_mainfile*", "files/blend/open_save.html#bpy-ops-wm-save-mainfile"), ("bpy.types.bone.show_wire*", "animation/armatures/bones/properties/display.html#bpy-types-bone-show-wire"), ("bpy.types.brush.hardness*", "sculpt_paint/sculpting/tool_settings/brush_settings.html#bpy-types-brush-hardness"), + ("bpy.types.curves.surface*", "sculpt_paint/curves_sculpting/introduction.html#bpy-types-curves-surface"), ("bpy.types.curvesmodifier*", "editors/video_sequencer/sequencer/sidebar/modifiers.html#bpy-types-curvesmodifier"), ("bpy.types.ffmpegsettings*", "render/output/properties/output.html#bpy-types-ffmpegsettings"), ("bpy.types.fmodifiernoise*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifiernoise"), @@ -2658,6 +2712,7 @@ url_manual_mapping = ( ("bpy.ops.screen.header*", "interface/window_system/regions.html#bpy-ops-screen-header"), ("bpy.ops.script.reload*", "advanced/operators.html#bpy-ops-script-reload"), ("bpy.ops.sculpt.expand*", "sculpt_paint/sculpting/editing/mask.html#bpy-ops-sculpt-expand"), + ("bpy.ops.sculpt_curves*", "sculpt_paint/curves_sculpting/index.html#bpy-ops-sculpt-curves"), ("bpy.ops.view3d.select*", "editors/3dview/selecting.html#bpy-ops-view3d-select"), ("bpy.ops.wm.debug_menu*", "advanced/operators.html#bpy-ops-wm-debug-menu"), ("bpy.ops.wm.obj_export*", "files/import_export/obj.html#bpy-ops-wm-obj-export"), @@ -2834,6 +2889,7 @@ url_manual_mapping = ( ("bpy.ops.sequencer*", "video_editing/index.html#bpy-ops-sequencer"), ("bpy.ops.text.find*", "editors/text_editor.html#bpy-ops-text-find"), ("bpy.ops.transform*", "scene_layout/object/editing/transform/index.html#bpy-ops-transform"), + ("bpy.ops.uv.reveal*", "modeling/meshes/uv/editing.html#bpy-ops-uv-reveal"), ("bpy.ops.uv.select*", "editors/uv/selecting.html#bpy-ops-uv-select"), ("bpy.ops.uv.stitch*", "modeling/meshes/uv/editing.html#bpy-ops-uv-stitch"), ("bpy.ops.uv.unwrap*", "modeling/meshes/editing/uv.html#bpy-ops-uv-unwrap"), @@ -2880,6 +2936,7 @@ url_manual_mapping = ( ("bpy.ops.ptcache*", "physics/baking.html#bpy-ops-ptcache"), ("bpy.ops.surface*", "modeling/surfaces/index.html#bpy-ops-surface"), ("bpy.ops.texture*", "render/materials/legacy_textures/index.html#bpy-ops-texture"), + ("bpy.ops.uv.hide*", "modeling/meshes/uv/editing.html#bpy-ops-uv-hide"), ("bpy.ops.uv.weld*", "modeling/meshes/uv/editing.html#bpy-ops-uv-weld"), ("bpy.ops.wm.link*", "files/linked_libraries/link_append.html#bpy-ops-wm-link"), ("bpy.ops.wm.tool*", "interface/tool_system.html#bpy-ops-wm-tool"), -- cgit v1.2.3 From 5da807e00fb75de31442f872f4997ba361be00f8 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Sat, 23 Jul 2022 12:14:45 +0200 Subject: Fix: Store Named Attribute node not working when attribute did not exist --- .../nodes/geometry/nodes/node_geo_store_named_attribute.cc | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc index dbd68f4c783..1d3beb8be96 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc @@ -122,11 +122,9 @@ static void try_capture_field_on_geometry(GeometryComponent &component, return; } } - - if (attributes.remove(name)) { - if (attributes.add(name, domain, data_type, bke::AttributeInitMove{buffer})) { - return; - } + attributes.remove(name); + if (attributes.add(name, domain, data_type, bke::AttributeInitMove{buffer})) { + return; } /* If the name corresponds to a builtin attribute, removing the attribute might fail if -- cgit v1.2.3 From beb746135dbe0c5ca21c2334e7dc475f201ad71e Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Sat, 23 Jul 2022 13:30:15 +0200 Subject: Fix T99830: missing update after reordering node group sockets --- source/blender/nodes/intern/node_common.cc | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/source/blender/nodes/intern/node_common.cc b/source/blender/nodes/intern/node_common.cc index b7c5f9570e4..6402ec3f3d6 100644 --- a/source/blender/nodes/intern/node_common.cc +++ b/source/blender/nodes/intern/node_common.cc @@ -37,6 +37,7 @@ using blender::MultiValueMap; using blender::Set; using blender::Stack; using blender::StringRef; +using blender::Vector; /* -------------------------------------------------------------------- */ /** \name Node Group @@ -160,6 +161,7 @@ static void group_verify_socket_list(bNodeTree &node_tree, const bool ensure_extend_socket_exists) { ListBase old_sockets = verify_lb; + Vector ordered_old_sockets = old_sockets; BLI_listbase_clear(&verify_lb); LISTBASE_FOREACH (const bNodeSocket *, interface_socket, &interface_sockets) { @@ -193,6 +195,19 @@ static void group_verify_socket_list(bNodeTree &node_tree, LISTBASE_FOREACH_MUTABLE (bNodeSocket *, unused_socket, &old_sockets) { nodeRemoveSocket(&node_tree, &node, unused_socket); } + + { + /* Check if new sockets match the old sockets. */ + int index; + LISTBASE_FOREACH_INDEX (bNodeSocket *, new_socket, &verify_lb, index) { + if (index < ordered_old_sockets.size()) { + if (ordered_old_sockets[index] != new_socket) { + BKE_ntree_update_tag_interface(&node_tree); + break; + } + } + } + } } void node_group_update(struct bNodeTree *ntree, struct bNode *node) -- cgit v1.2.3 From 092732d1136cf4bed04f5dcb117e7f4a0df5fc0c Mon Sep 17 00:00:00 2001 From: Aras Pranckevicius Date: Sat, 23 Jul 2022 15:16:14 +0300 Subject: IO: speed up import of large amounts of objects in USD/OBJ by pre-sorting objects by name Previously, when creating "very large" (tens-hundreds of thousands) amounts of objects, the Blender code that was ensuring name uniqueness was the bottleneck. That got recently addressed (D14162), however now sorting of IDs by their names is the remaining bottleneck. Name sorting code in Blender is optimized for the pattern where names are inserted in already sorted order (i.e. objects expect to get added near the end of the list). By doing this pre-sorting of objects intended to get created by an importer (USD and OBJ, in this patch), this sorting bottleneck can be largely removed, especially with very high object counts. Windows, Ryzen 5950X, import times: - OBJ, splash screen scene (26k objects): 22.0s -> 20.7s - USD, Disney Moana scene (250k objects): 585s -> 82.2s (10 minutes -> 1.5 minutes) Reviewed By: Michael Kowalski, Howard Trickey Differential Revision: https://developer.blender.org/D15506 --- source/blender/io/usd/intern/usd_capi_import.cc | 24 +- source/blender/io/usd/intern/usd_reader_stage.cc | 15 +- source/blender/io/usd/intern/usd_reader_stage.h | 2 + .../io/wavefront_obj/importer/obj_importer.cc | 10 + .../io/wavefront_obj/tests/obj_importer_tests.cc | 298 ++++++++++----------- 5 files changed, 196 insertions(+), 153 deletions(-) diff --git a/source/blender/io/usd/intern/usd_capi_import.cc b/source/blender/io/usd/intern/usd_capi_import.cc index 13ae6f4d4c0..03af3aed2d0 100644 --- a/source/blender/io/usd/intern/usd_capi_import.cc +++ b/source/blender/io/usd/intern/usd_capi_import.cc @@ -218,6 +218,7 @@ static void import_startjob(void *customdata, short *stop, short *do_update, flo data->scene->r.efra = stage->GetEndTimeCode(); } + *data->do_update = true; *data->progress = 0.15f; USDStageReader *archive = new USDStageReader(stage, data->params, data->settings); @@ -226,13 +227,32 @@ static void import_startjob(void *customdata, short *stop, short *do_update, flo archive->collect_readers(data->bmain); + *data->do_update = true; *data->progress = 0.2f; const float size = static_cast(archive->readers().size()); size_t i = 0; - /* Setup parenthood */ + /* Sort readers by name: when creating a lot of objects in Blender, + * it is much faster if the order is sorted by name. */ + archive->sort_readers(); + *data->do_update = true; + *data->progress = 0.25f; + + /* Create blender objects. */ + for (USDPrimReader *reader : archive->readers()) { + if (!reader) { + continue; + } + reader->create_object(data->bmain, 0.0); + if ((++i & 1023) == 0) { + *data->do_update = true; + *data->progress = 0.25f + 0.25f * (i / size); + } + } + /* Setup parenthood and read actual object data. */ + i = 0; for (USDPrimReader *reader : archive->readers()) { if (!reader) { @@ -252,7 +272,7 @@ static void import_startjob(void *customdata, short *stop, short *do_update, flo ob->parent = parent->object(); } - *data->progress = 0.2f + 0.8f * (++i / size); + *data->progress = 0.5f + 0.5f * (++i / size); *data->do_update = true; if (G.is_break) { diff --git a/source/blender/io/usd/intern/usd_reader_stage.cc b/source/blender/io/usd/intern/usd_reader_stage.cc index 583c58a1356..df75be849e2 100644 --- a/source/blender/io/usd/intern/usd_reader_stage.cc +++ b/source/blender/io/usd/intern/usd_reader_stage.cc @@ -28,6 +28,9 @@ #include +#include "BLI_sort.hh" +#include "BLI_string.h" + namespace blender::io::usd { USDStageReader::USDStageReader(pxr::UsdStageRefPtr stage, @@ -252,8 +255,6 @@ USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim & return nullptr; } - reader->create_object(bmain, 0.0); - readers_.push_back(reader); reader->incref(); @@ -310,4 +311,14 @@ void USDStageReader::clear_readers() readers_.clear(); } +void USDStageReader::sort_readers() +{ + blender::parallel_sort( + readers_.begin(), readers_.end(), [](const USDPrimReader *a, const USDPrimReader *b) { + const char *na = a ? a->name().c_str() : ""; + const char *nb = b ? b->name().c_str() : ""; + return BLI_strcasecmp(na, nb) < 0; + }); +} + } // Namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_stage.h b/source/blender/io/usd/intern/usd_reader_stage.h index 0ed964c7679..5f4a343f874 100644 --- a/source/blender/io/usd/intern/usd_reader_stage.h +++ b/source/blender/io/usd/intern/usd_reader_stage.h @@ -63,6 +63,8 @@ class USDStageReader { return readers_; }; + void sort_readers(); + private: USDPrimReader *collect_readers(Main *bmain, const pxr::UsdPrim &prim); diff --git a/source/blender/io/wavefront_obj/importer/obj_importer.cc b/source/blender/io/wavefront_obj/importer/obj_importer.cc index f2051d195c8..bb32776d2be 100644 --- a/source/blender/io/wavefront_obj/importer/obj_importer.cc +++ b/source/blender/io/wavefront_obj/importer/obj_importer.cc @@ -9,6 +9,7 @@ #include "BLI_map.hh" #include "BLI_math_vec_types.hh" #include "BLI_set.hh" +#include "BLI_sort.hh" #include "BLI_string_ref.hh" #include "BKE_layer.h" @@ -44,6 +45,15 @@ static void geometry_to_blender_objects(Main *bmain, /* Don't do collection syncs for each object, will do once after the loop. */ BKE_layer_collection_resync_forbid(); + /* Sort objects by name: creating many objects is much faster if the creation + * order is sorted by name. */ + blender::parallel_sort( + all_geometries.begin(), all_geometries.end(), [](const auto &a, const auto &b) { + const char *na = a ? a->geometry_name_.c_str() : ""; + const char *nb = b ? b->geometry_name_.c_str() : ""; + return BLI_strcasecmp(na, nb) < 0; + }); + /* Create all the objects. */ Vector objects; objects.reserve(all_geometries.size()); diff --git a/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc b/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc index 02565556c37..c59269f5a7d 100644 --- a/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc @@ -226,7 +226,9 @@ TEST_F(obj_importer_test, import_nurbs_curves) { Expectation expect[] = { {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, + {"OBCurveDeg3", OB_CURVES_LEGACY, 4, 0, 3, 0, float3(10, -2, 0), float3(6, -2, 0)}, {"OBnurbs_curves", OB_CURVES_LEGACY, 4, 0, 4, 0, float3(2, -2, 0), float3(-2, -2, 0)}, + {"OBNurbsCurveCyclic", OB_CURVES_LEGACY, 7, 0, 4, 1, float3(-2, -2, 0), float3(-6, 2, 0)}, {"OBNurbsCurveDiffWeights", OB_CURVES_LEGACY, 4, @@ -235,7 +237,6 @@ TEST_F(obj_importer_test, import_nurbs_curves) 0, float3(6, -2, 0), float3(2, -2, 0)}, - {"OBNurbsCurveCyclic", OB_CURVES_LEGACY, 7, 0, 4, 1, float3(-2, -2, 0), float3(-6, 2, 0)}, {"OBNurbsCurveEndpoint", OB_CURVES_LEGACY, 4, @@ -244,7 +245,6 @@ TEST_F(obj_importer_test, import_nurbs_curves) 0, float3(-6, -2, 0), float3(-10, -2, 0)}, - {"OBCurveDeg3", OB_CURVES_LEGACY, 4, 0, 3, 0, float3(10, -2, 0), float3(6, -2, 0)}, }; import_and_check("nurbs_curves.obj", expect, std::size(expect), 0); } @@ -269,7 +269,8 @@ TEST_F(obj_importer_test, import_nurbs_manual) { Expectation expect[] = { {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, - {"OBCurve_Uniform_Parm", OB_CURVES_LEGACY, 5, 0, 4, 0, float3(-2, 0, 2), float3(-2, 0, 2)}, + {"OBCurve_Cyclic", OB_CURVES_LEGACY, 7, 0, 4, 1, float3(-2, 0, 2), float3(2, 0, -2)}, + {"OBCurve_Endpoints", OB_CURVES_LEGACY, 5, 1, 4, 0, float3(-2, 0, 2), float3(-2, 0, 2)}, {"OBCurve_NonUniform_Parm", OB_CURVES_LEGACY, 5, @@ -278,8 +279,7 @@ TEST_F(obj_importer_test, import_nurbs_manual) 0, float3(-2, 0, 2), float3(-2, 0, 2)}, - {"OBCurve_Endpoints", OB_CURVES_LEGACY, 5, 1, 4, 0, float3(-2, 0, 2), float3(-2, 0, 2)}, - {"OBCurve_Cyclic", OB_CURVES_LEGACY, 7, 0, 4, 1, float3(-2, 0, 2), float3(2, 0, -2)}, + {"OBCurve_Uniform_Parm", OB_CURVES_LEGACY, 5, 0, 4, 0, float3(-2, 0, 2), float3(-2, 0, 2)}, }; import_and_check("nurbs_manual.obj", expect, std::size(expect), 0); } @@ -313,23 +313,14 @@ TEST_F(obj_importer_test, import_faces_invalid_or_with_holes) { Expectation expect[] = { {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, - {"OBFaceWithHole_BecomesTwoFacesFormingAHole", + {"OBFaceAllVerts_BecomesOneOverlappingFaceUsingAllVerts", OB_MESH, 8, - 10, - 2, - 12, - float3(-2, 0, -2), - float3(1, 0, -1)}, - {"OBFaceQuadDupSomeVerts_BecomesOneQuadUsing4Verts", - OB_MESH, - 4, - 4, + 8, 1, - 4, - float3(3, 0, -2), - float3(7, 0, -2)}, - {"OBFaceTriDupVert_Becomes1Tri", OB_MESH, 3, 3, 1, 3, float3(-2, 0, 3), float3(2, 0, 7)}, + 8, + float3(8, 0, -2), + float3(11, 0, -1)}, {"OBFaceAllVertsDup_BecomesOneOverlappingFaceUsingAllVerts", OB_MESH, 8, @@ -338,15 +329,24 @@ TEST_F(obj_importer_test, import_faces_invalid_or_with_holes) 8, float3(3, 0, 3), float3(6, 0, 4)}, - {"OBFaceAllVerts_BecomesOneOverlappingFaceUsingAllVerts", + {"OBFaceJustTwoVerts_IsSkipped", OB_MESH, 2, 0, 0, 0, float3(8, 0, 3), float3(8, 0, 7)}, + {"OBFaceQuadDupSomeVerts_BecomesOneQuadUsing4Verts", OB_MESH, - 8, - 8, + 4, + 4, 1, + 4, + float3(3, 0, -2), + float3(7, 0, -2)}, + {"OBFaceTriDupVert_Becomes1Tri", OB_MESH, 3, 3, 1, 3, float3(-2, 0, 3), float3(2, 0, 7)}, + {"OBFaceWithHole_BecomesTwoFacesFormingAHole", + OB_MESH, 8, - float3(8, 0, -2), - float3(11, 0, -1)}, - {"OBFaceJustTwoVerts_IsSkipped", OB_MESH, 2, 0, 0, 0, float3(8, 0, 3), float3(8, 0, 7)}, + 10, + 2, + 12, + float3(-2, 0, -2), + float3(1, 0, -1)}, }; import_and_check("faces_invalid_or_with_holes.obj", expect, std::size(expect), 0); } @@ -392,6 +392,63 @@ TEST_F(obj_importer_test, import_all_objects) Expectation expect[] = { {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, /* .obj file has empty EmptyText and EmptyMesh objects; these are ignored and skipped */ + {"OBBezierCurve", OB_MESH, 13, 12, 0, 0, float3(-1, -2, 0), float3(1, -2, 0)}, + {"OBBlankCube", OB_MESH, 8, 13, 7, 26, float3(1, 1, -1), float3(-1, 1, 1), float3(0, 0, 1)}, + {"OBMaterialCube", + OB_MESH, + 8, + 13, + 7, + 26, + float3(28, 1, -1), + float3(26, 1, 1), + float3(-1, 0, 0)}, + {"OBNurbsCircle", + OB_MESH, + 96, + 96, + 0, + 0, + float3(3.292893f, -2.707107f, 0), + float3(3.369084f, -2.77607f, 0)}, + {"OBNurbsCircle.001", OB_MESH, 4, 4, 0, 0, float3(2, -3, 0), float3(3, -2, 0)}, + {"OBParticleCube", + OB_MESH, + 8, + 13, + 7, + 26, + float3(22, 1, -1), + float3(20, 1, 1), + float3(0, 0, 1)}, + {"OBShapeKeyCube", + OB_MESH, + 8, + 13, + 7, + 26, + float3(19, 1, -1), + float3(17, 1, 1), + float3(-0.4082f, -0.4082f, 0.8165f)}, + {"OBSmoothCube", + OB_MESH, + 8, + 13, + 7, + 26, + float3(4, 1, -1), + float3(2, 1, 1), + float3(0.5774f, 0.5773f, 0.5774f)}, + {"OBSurface", + OB_MESH, + 256, + 480, + 224, + 896, + float3(7.292893f, -2.707107f, -1), + float3(7.525872f, -2.883338f, 1), + float3(-0.7071f, -0.7071f, 0), + float2(0, 0.142857f)}, {"OBSurfPatch", OB_MESH, 256, @@ -412,24 +469,16 @@ TEST_F(obj_importer_test, import_all_objects) float3(11, -2, 1), float3(-0.0541f, -0.0541f, -0.9971f), float2(0, 1)}, - {"OBSmoothCube", - OB_MESH, - 8, - 13, - 7, - 26, - float3(4, 1, -1), - float3(2, 1, 1), - float3(0.5774f, 0.5773f, 0.5774f)}, - {"OBMaterialCube", + {"OBSurfTorus.001", OB_MESH, - 8, - 13, - 7, - 26, - float3(28, 1, -1), - float3(26, 1, 1), - float3(-1, 0, 0)}, + 1024, + 2048, + 1024, + 4096, + float3(5.34467f, -2.65533f, -0.176777f), + float3(5.232792f, -2.411795f, -0.220835f), + float3(-0.5042f, -0.5042f, -0.7011f), + float2(0, 1)}, {"OBTaperCube", OB_MESH, 106, @@ -439,24 +488,26 @@ TEST_F(obj_importer_test, import_all_objects) float3(24.444445f, 0.502543f, -0.753814f), float3(23.790743f, 0.460522f, -0.766546f), float3(-0.0546f, 0.1716f, 0.9837f)}, - {"OBParticleCube", + {"OBText", OB_MESH, - 8, - 13, - 7, - 26, - float3(22, 1, -1), - float3(20, 1, 1), - float3(0, 0, 1)}, - {"OBShapeKeyCube", + 177, + 345, + 171, + 513, + float3(1.75f, -9.458f, 0), + float3(0.587f, -9.406f, 0), + float3(0, 0, 1), + float2(0.017544f, 0)}, + {"OBUVCube", OB_MESH, 8, 13, 7, 26, - float3(19, 1, -1), - float3(17, 1, 1), - float3(-0.4082f, -0.4082f, 0.8165f)}, + float3(7, 1, -1), + float3(5, 1, 1), + float3(0, 0, 1), + float2(0.654526f, 0.579873f)}, {"OBUVImageCube", OB_MESH, 8, @@ -467,15 +518,6 @@ TEST_F(obj_importer_test, import_all_objects) float3(8, 1, 1), float3(0, 0, 1), float2(0.654526f, 0.579873f)}, - {"OBVGroupCube", - OB_MESH, - 8, - 13, - 7, - 26, - float3(16, 1, -1), - float3(14, 1, 1), - float3(0, 0, 1)}, {"OBVColCube", OB_MESH, 8, @@ -487,57 +529,15 @@ TEST_F(obj_importer_test, import_all_objects) float3(0, 0, 1), float2(0, 0), float4(0.0f, 0.002125f, 1.0f, 1.0f)}, - {"OBUVCube", + {"OBVGroupCube", OB_MESH, 8, 13, 7, 26, - float3(7, 1, -1), - float3(5, 1, 1), - float3(0, 0, 1), - float2(0.654526f, 0.579873f)}, - {"OBNurbsCircle.001", OB_MESH, 4, 4, 0, 0, float3(2, -3, 0), float3(3, -2, 0)}, - {"OBSurface", - OB_MESH, - 256, - 480, - 224, - 896, - float3(7.292893f, -2.707107f, -1), - float3(7.525872f, -2.883338f, 1), - float3(-0.7071f, -0.7071f, 0), - float2(0, 0.142857f)}, - {"OBText", - OB_MESH, - 177, - 345, - 171, - 513, - float3(1.75f, -9.458f, 0), - float3(0.587f, -9.406f, 0), - float3(0, 0, 1), - float2(0.017544f, 0)}, - {"OBSurfTorus.001", - OB_MESH, - 1024, - 2048, - 1024, - 4096, - float3(5.34467f, -2.65533f, -0.176777f), - float3(5.232792f, -2.411795f, -0.220835f), - float3(-0.5042f, -0.5042f, -0.7011f), - float2(0, 1)}, - {"OBNurbsCircle", - OB_MESH, - 96, - 96, - 0, - 0, - float3(3.292893f, -2.707107f, 0), - float3(3.369084f, -2.77607f, 0)}, - {"OBBezierCurve", OB_MESH, 13, 12, 0, 0, float3(-1, -2, 0), float3(1, -2, 0)}, - {"OBBlankCube", OB_MESH, 8, 13, 7, 26, float3(1, 1, -1), float3(-1, 1, 1), float3(0, 0, 1)}, + float3(16, 1, -1), + float3(14, 1, 1), + float3(0, 0, 1)}, }; import_and_check("all_objects.obj", expect, std::size(expect), 7); } @@ -546,28 +546,6 @@ TEST_F(obj_importer_test, import_cubes_vertex_colors) { Expectation expect[] = { {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, - {"OBCubeVertexByte", - OB_MESH, - 8, - 12, - 6, - 24, - float3(1.0f, 1.0f, -1.0f), - float3(-1.0f, -1.0f, 1.0f), - float3(0, 0, 0), - float2(0, 0), - float4(0.846873f, 0.027321f, 0.982123f, 1.0f)}, - {"OBCubeVertexFloat", - OB_MESH, - 8, - 12, - 6, - 24, - float3(3.392028f, 1.0f, -1.0f), - float3(1.392028f, -1.0f, 1.0f), - float3(0, 0, 0), - float2(0, 0), - float4(49.99467f, 0.027321f, 0.982123f, 1.0f)}, {"OBCubeCornerByte", OB_MESH, 8, @@ -609,6 +587,28 @@ TEST_F(obj_importer_test, import_cubes_vertex_colors) 24, float3(-4.550208f, -1.0f, -1.918042f), float3(-2.550208f, 1.0f, -3.918042f)}, + {"OBCubeVertexByte", + OB_MESH, + 8, + 12, + 6, + 24, + float3(1.0f, 1.0f, -1.0f), + float3(-1.0f, -1.0f, 1.0f), + float3(0, 0, 0), + float2(0, 0), + float4(0.846873f, 0.027321f, 0.982123f, 1.0f)}, + {"OBCubeVertexFloat", + OB_MESH, + 8, + 12, + 6, + 24, + float3(3.392028f, 1.0f, -1.0f), + float3(1.392028f, -1.0f, 1.0f), + float3(0, 0, 0), + float2(0, 0), + float4(49.99467f, 0.027321f, 0.982123f, 1.0f)}, }; import_and_check("cubes_vertex_colors.obj", expect, std::size(expect), 0); } @@ -617,38 +617,28 @@ TEST_F(obj_importer_test, import_cubes_vertex_colors_mrgb) { Expectation expect[] = { {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, - {"OBCubeXYZRGB", + {"OBCubeMRGB", OB_MESH, 8, 12, 6, 24, - float3(1, 1, -1), - float3(-1, -1, 1), + float3(4, 1, -1), + float3(2, -1, 1), float3(0, 0, 0), float2(0, 0), - float4(0.6038f, 0.3185f, 0.1329f, 1.0f)}, - {"OBCubeMRGB", + float4(0.8714f, 0.6308f, 0.5271f, 1.0f)}, + {"OBCubeXYZRGB", OB_MESH, 8, 12, 6, 24, - float3(4, 1, -1), - float3(2, -1, 1), + float3(1, 1, -1), + float3(-1, -1, 1), float3(0, 0, 0), float2(0, 0), - float4(0.8714f, 0.6308f, 0.5271f, 1.0f)}, - { - "OBTriNoColors", - OB_MESH, - 3, - 3, - 1, - 3, - float3(8, 1, -1), - float3(6, 0, -1), - }, + float4(0.6038f, 0.3185f, 0.1329f, 1.0f)}, {"OBTriMRGB", OB_MESH, 3, @@ -660,6 +650,16 @@ TEST_F(obj_importer_test, import_cubes_vertex_colors_mrgb) float3(0, 0, 0), float2(0, 0), float4(1.0f, 0.0f, 0.0f, 1.0f)}, + { + "OBTriNoColors", + OB_MESH, + 3, + 3, + 1, + 3, + float3(8, 1, -1), + float3(6, 0, -1), + }, }; import_and_check("cubes_vertex_colors_mrgb.obj", expect, std::size(expect), 0); } -- cgit v1.2.3 From d53ea1d0af58cfdcd230e75ba4ac58453bdbd808 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Sat, 23 Jul 2022 14:37:39 +0200 Subject: Fix T99905: wrong toposort when the node tree is cyclic --- source/blender/nodes/intern/node_tree_ref.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/source/blender/nodes/intern/node_tree_ref.cc b/source/blender/nodes/intern/node_tree_ref.cc index 64a8690a869..05e7fe33776 100644 --- a/source/blender/nodes/intern/node_tree_ref.cc +++ b/source/blender/nodes/intern/node_tree_ref.cc @@ -522,6 +522,7 @@ static void toposort_from_start_node(const NodeTreeRef::ToposortDirection direct /* Do a depth-first search to sort nodes topologically. */ Stack nodes_to_check; nodes_to_check.push({&start_node}); + node_states[start_node.id()].is_in_stack = true; while (!nodes_to_check.is_empty()) { Item &item = nodes_to_check.peek(); const NodeRef &node = *item.node; -- cgit v1.2.3 From 7c6d546f3a72569e15285bc046345b7f60df2aaa Mon Sep 17 00:00:00 2001 From: Howard Trickey Date: Sat, 23 Jul 2022 12:15:59 -0400 Subject: Fix an assert trip in boolean tickled by D11272 example. The face merging code in exact boolean made an assumption that the tesselated original face was manifold except at the boundaries. This should be true but sometimes (e.g., if the input faces have self-intersection, as happens in the example), it is not. This commit makes face merging tolerant of such a situation. It might leave some stray edges from triangulation, but it should only happen if the input is malformed. Note: the input may be malformed if there were previous booleans in the stack, since snapping the exact result to float coordinates is not guaranteed to leave the mesh without defects. This is the second try at this commit. The previous one had a typo in it -- luckily, the tests caught the problem. --- source/blender/blenlib/intern/mesh_boolean.cc | 32 +++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/source/blender/blenlib/intern/mesh_boolean.cc b/source/blender/blenlib/intern/mesh_boolean.cc index 700c126ca4c..d4586f95fe0 100644 --- a/source/blender/blenlib/intern/mesh_boolean.cc +++ b/source/blender/blenlib/intern/mesh_boolean.cc @@ -2966,6 +2966,11 @@ static std::ostream &operator<<(std::ostream &os, const FaceMergeState &fms) * \a tris all have the same original face. * Find the 2d edge/triangle topology for these triangles, but only the ones facing in the * norm direction, and whether each edge is dissolvable or not. + * If we did the initial triangulation properly, and any Delaunay triangulations of interections + * properly, then each triangle edge should have at most one neighbor. + * However, there can be anonalies. For example, if an input face is self-intersecting, we fall + * back on the floating poing polyfill triangulation, which, after which all bets are off. + * Hence, try to be tolerant of such unexpected topology. */ static void init_face_merge_state(FaceMergeState *fms, const Vector &tris, @@ -3053,16 +3058,35 @@ static void init_face_merge_state(FaceMergeState *fms, std::cout << "me.v1 == mf.vert[i] so set edge[" << me_index << "].left_face = " << f << "\n"; } - BLI_assert(me.left_face == -1); - fms->edge[me_index].left_face = f; + if (me.left_face != -1) { + /* Unexpected in the normal case: this means more than one triangle shares this + * edge in the same orientation. But be tolerant of this case. By making this + * edge not dissolvable, we'll avoid future problems due to this non-manifold topology. + */ + if (dbg_level > 1) { + std::cout << "me.left_face was already occupied, so triangulation wasn't good\n"; + } + me.dissolvable = false; + } + else { + fms->edge[me_index].left_face = f; + } } else { if (dbg_level > 1) { std::cout << "me.v1 != mf.vert[i] so set edge[" << me_index << "].right_face = " << f << "\n"; } - BLI_assert(me.right_face == -1); - fms->edge[me_index].right_face = f; + if (me.right_face != -1) { + /* Unexpected, analogous to the me.left_face != -1 case above. */ + if (dbg_level > 1) { + std::cout << "me.right_face was already occupied, so triangulation wasn't good\n"; + } + me.dissolvable = false; + } + else { + fms->edge[me_index].right_face = f; + } } fms->face[f].edge.append(me_index); } -- cgit v1.2.3 From 3ea2b4ac310af64572045f59debd550e7f9671cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Sat, 23 Jul 2022 22:54:34 +0200 Subject: EEVEE-Next: Film: Fix incorrect anti-aliasing There was a confusion about what space the offset was in. --- source/blender/draw/engines/eevee_next/eevee_film.cc | 4 ++-- source/blender/draw/engines/eevee_next/eevee_view.cc | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/eevee_film.cc b/source/blender/draw/engines/eevee_next/eevee_film.cc index 42c61ac90a8..a111b1db4a3 100644 --- a/source/blender/draw/engines/eevee_next/eevee_film.cc +++ b/source/blender/draw/engines/eevee_next/eevee_film.cc @@ -441,8 +441,8 @@ float2 Film::pixel_jitter_get() const jitter = Sampling::sample_disk(jitter) * data_.filter_size; } else { - /* Jitter the size of a whole pixel. */ - jitter = jitter * 2.0f - 1.0f; + /* Jitter the size of a whole pixel. [-0.5..0.5] */ + jitter -= 0.5f; } /* TODO(fclem): Mixed-resolution rendering: We need to offset to each of the target pixel covered * by a render pixel, ideally, by choosing one randomly using another sampling dimension, or by diff --git a/source/blender/draw/engines/eevee_next/eevee_view.cc b/source/blender/draw/engines/eevee_next/eevee_view.cc index 1a222dc4ebd..8052ea76def 100644 --- a/source/blender/draw/engines/eevee_next/eevee_view.cc +++ b/source/blender/draw/engines/eevee_next/eevee_view.cc @@ -181,6 +181,8 @@ void ShadingView::update_view() /* Anti-Aliasing / Super-Sampling jitter. */ float2 jitter = inst_.film.pixel_jitter_get() / float2(extent_); + /* Transform to NDC space. */ + jitter *= 2.0f; window_translate_m4(winmat.ptr(), winmat.ptr(), UNPACK2(jitter)); DRW_view_update_sub(sub_view_, viewmat.ptr(), winmat.ptr()); -- cgit v1.2.3 From 0c3851d31fa89642797f00da179ca91702c28697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Sat, 23 Jul 2022 22:56:01 +0200 Subject: EEVEE-Next: Film: Rename filter_size for clarity and add box filter ... ... as a debug option. --- .../blender/draw/engines/eevee_next/eevee_film.cc | 34 +++++++++++----------- .../blender/draw/engines/eevee_next/eevee_film.hh | 2 ++ .../draw/engines/eevee_next/eevee_shader_shared.hh | 8 ++--- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/eevee_film.cc b/source/blender/draw/engines/eevee_next/eevee_film.cc index a111b1db4a3..b8408e55cb3 100644 --- a/source/blender/draw/engines/eevee_next/eevee_film.cc +++ b/source/blender/draw/engines/eevee_next/eevee_film.cc @@ -146,8 +146,8 @@ void Film::sync_mist() inline bool operator==(const FilmData &a, const FilmData &b) { - return (a.extent == b.extent) && (a.offset == b.offset) && (a.filter_size == b.filter_size) && - (a.scaling_factor == b.scaling_factor); + return (a.extent == b.extent) && (a.offset == b.offset) && + (a.filter_radius == b.filter_radius) && (a.scaling_factor == b.scaling_factor); } inline bool operator!=(const FilmData &a, const FilmData &b) @@ -232,8 +232,8 @@ void Film::init(const int2 &extent, const rcti *output_rect) data.offset = int2(output_rect->xmin, output_rect->ymin); data.extent_inv = 1.0f / float2(data.extent); /* Disable filtering if sample count is 1. */ - data.filter_size = (sampling.sample_count() == 1) ? 0.0f : - clamp_f(scene.r.gauss, 0.0f, 100.0f); + data.filter_radius = (sampling.sample_count() == 1) ? 0.0f : + clamp_f(scene.r.gauss, 0.0f, 100.0f); /* TODO(fclem): parameter hidden in experimental. * We need to figure out LOD bias first in order to preserve texture crispiness. */ data.scaling_factor = 1; @@ -433,12 +433,12 @@ float2 Film::pixel_jitter_get() const { float2 jitter = inst_.sampling.rng_2d_get(SAMPLING_FILTER_U); - if (data_.filter_size < M_SQRT1_2 && !inst_.camera.is_panoramic()) { + if (!use_box_filter && data_.filter_radius < M_SQRT1_2 && !inst_.camera.is_panoramic()) { /* For filter size less than a pixel, change sampling strategy and use a uniform disk * distribution covering the filter shape. This avoids putting samples in areas without any * weights. */ /* TODO(fclem): Importance sampling could be a better option here. */ - jitter = Sampling::sample_disk(jitter) * data_.filter_size; + jitter = Sampling::sample_disk(jitter) * data_.filter_radius; } else { /* Jitter the size of a whole pixel. [-0.5..0.5] */ @@ -459,36 +459,36 @@ void Film::update_sample_table() { data_.subpixel_offset = pixel_jitter_get(); - int filter_size_ceil = ceilf(data_.filter_size); - float filter_size_sqr = square_f(data_.filter_size); + int filter_radius_ceil = ceilf(data_.filter_radius); + float filter_radius_sqr = square_f(data_.filter_radius); data_.samples_len = 0; - if (data_.filter_size < 0.01f) { - /* Disable filtering. */ + if (use_box_filter || data_.filter_radius < 0.01f) { + /* Disable gather filtering. */ data_.samples[0].texel = int2(0, 0); data_.samples[0].weight = 1.0f; data_.samples_weight_total = 1.0f; data_.samples_len = 1; } /* NOTE: Threshold determined by hand until we don't hit the assert bellow. */ - else if (data_.filter_size < 2.20f) { + else if (data_.filter_radius < 2.20f) { /* Small filter Size. */ int closest_index = 0; float closest_distance = FLT_MAX; data_.samples_weight_total = 0.0f; /* TODO(fclem): For optimization, could try Z-tile ordering. */ - for (int y = -filter_size_ceil; y <= filter_size_ceil; y++) { - for (int x = -filter_size_ceil; x <= filter_size_ceil; x++) { + for (int y = -filter_radius_ceil; y <= filter_radius_ceil; y++) { + for (int x = -filter_radius_ceil; x <= filter_radius_ceil; x++) { float2 pixel_offset = float2(x, y) - data_.subpixel_offset; float distance_sqr = math::length_squared(pixel_offset); - if (distance_sqr < filter_size_sqr) { + if (distance_sqr < filter_radius_sqr) { if (data_.samples_len >= FILM_PRECOMP_SAMPLE_MAX) { BLI_assert_msg(0, "Precomputed sample table is too small."); break; } FilmSample &sample = data_.samples[data_.samples_len]; sample.texel = int2(x, y); - sample.weight = film_filter_weight(data_.filter_size, distance_sqr); + sample.weight = film_filter_weight(data_.filter_radius, distance_sqr); data_.samples_weight_total += sample.weight; if (distance_sqr < closest_distance) { @@ -521,11 +521,11 @@ void Film::update_sample_table() * neighbor filtering not converging rapidly. */ random_2d.x = (random_2d.x + i) / float(FILM_PRECOMP_SAMPLE_MAX); - float2 pixel_offset = math::floor(Sampling::sample_spiral(random_2d) * data_.filter_size); + float2 pixel_offset = math::floor(Sampling::sample_spiral(random_2d) * data_.filter_radius); sample.texel = int2(pixel_offset); float distance_sqr = math::length_squared(pixel_offset - data_.subpixel_offset); - sample.weight = film_filter_weight(data_.filter_size, distance_sqr); + sample.weight = film_filter_weight(data_.filter_radius, distance_sqr); data_.samples_weight_total += sample.weight; i++; } diff --git a/source/blender/draw/engines/eevee_next/eevee_film.hh b/source/blender/draw/engines/eevee_next/eevee_film.hh index 4ccd5684396..e2d5956710d 100644 --- a/source/blender/draw/engines/eevee_next/eevee_film.hh +++ b/source/blender/draw/engines/eevee_next/eevee_film.hh @@ -34,6 +34,8 @@ class Film { public: /** Stores indirection table of AOVs based on their name hash and their type. */ AOVsInfoDataBuf aovs_info; + /** For debugging purpose but could be a user option in the future. */ + static constexpr bool use_box_filter = false; private: Instance &inst_; diff --git a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh index 819f49756d7..cc991efeee2 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh +++ b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh @@ -206,7 +206,7 @@ struct FilmData { /** Scaling factor for scaled resolution rendering. */ int scaling_factor; /** Film pixel filter radius. */ - float filter_size; + float filter_radius; /** Precomputed samples. First in the table is the closest one. The rest is unordered. */ int samples_len; /** Sum of the weights of all samples in the sample table. */ @@ -215,17 +215,17 @@ struct FilmData { }; BLI_STATIC_ASSERT_ALIGN(FilmData, 16) -static inline float film_filter_weight(float filter_size, float sample_distance_sqr) +static inline float film_filter_weight(float filter_radius, float sample_distance_sqr) { #if 1 /* Faster */ /* Gaussian fitted to Blackman-Harris. */ - float r = sample_distance_sqr / (filter_size * filter_size); + float r = sample_distance_sqr / (filter_radius * filter_radius); const float sigma = 0.284; const float fac = -0.5 / (sigma * sigma); float weight = expf(fac * r); #else /* Blackman-Harris filter. */ - float r = M_2PI * saturate(0.5 + sqrtf(sample_distance_sqr) / (2.0 * filter_size)); + float r = M_2PI * saturate(0.5 + sqrtf(sample_distance_sqr) / (2.0 * filter_radius)); float weight = 0.35875 - 0.48829 * cosf(r) + 0.14128 * cosf(2.0 * r) - 0.01168 * cosf(3.0 * r); #endif return weight; -- cgit v1.2.3 From c94c0d988a5640ab7c6fb815462792dcf8dd863b Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Sat, 23 Jul 2022 19:59:59 -0500 Subject: Fix: Removing attributes from UI invalidates caches Use the new attribute API to implement the attribute remove function used by RNA, except for BMesh attributes. Currently, removing curve attributes from the panel in the property editor does not mark the relevant caches dirty (for example, the cache of curve type counts), because that behavior is implemented with the new attribute API. Also, eventually we want to merge the two APIs, and removing an attribute is the first function that can be partially implemented with the new API. Differential Revision: https://developer.blender.org/D15495 --- source/blender/blenkernel/intern/attribute.cc | 43 ++++++++++++++++++---- .../blender/blenkernel/intern/attribute_access.cc | 13 ++++++- .../blenkernel/intern/attribute_access_intern.hh | 8 ++-- 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/source/blender/blenkernel/intern/attribute.cc b/source/blender/blenkernel/intern/attribute.cc index 030e4941874..b277fc39caf 100644 --- a/source/blender/blenkernel/intern/attribute.cc +++ b/source/blender/blenkernel/intern/attribute.cc @@ -8,6 +8,7 @@ */ #include +#include #include "MEM_guardedalloc.h" @@ -24,7 +25,7 @@ #include "BKE_attribute.h" #include "BKE_attribute.hh" -#include "BKE_curves.h" +#include "BKE_curves.hh" #include "BKE_customdata.h" #include "BKE_editmesh.h" #include "BKE_pointcloud.h" @@ -89,6 +90,36 @@ static void get_domains(const ID *id, DomainInfo info[ATTR_DOMAIN_NUM]) } } +namespace blender::bke { + +static std::optional get_attribute_accessor_for_write( + ID &id) +{ + switch (GS(id.name)) { + case ID_ME: { + Mesh &mesh = reinterpret_cast(id); + /* The attribute API isn't implemented for BMesh, so edit mode meshes are not supported. */ + BLI_assert(mesh.edit_mesh == nullptr); + return mesh_attributes_for_write(mesh); + } + case ID_PT: { + PointCloud &pointcloud = reinterpret_cast(id); + return pointcloud_attributes_for_write(pointcloud); + } + case ID_CV: { + Curves &curves_id = reinterpret_cast(id); + CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); + return curves.attributes_for_write(); + } + default: { + BLI_assert_unreachable(); + return {}; + } + } +} + +} // namespace blender::bke + bool BKE_id_attributes_supported(const ID *id) { DomainInfo info[ATTR_DOMAIN_NUM]; @@ -242,6 +273,7 @@ CustomDataLayer *BKE_id_attribute_duplicate(ID *id, const char *name, ReportList bool BKE_id_attribute_remove(ID *id, const char *name, ReportList *reports) { + using namespace blender::bke; if (BKE_id_attribute_required(id, name)) { BKE_report(reports, RPT_ERROR, "Attribute is required and can't be removed"); return false; @@ -266,12 +298,9 @@ bool BKE_id_attribute_remove(ID *id, const char *name, ReportList *reports) ATTR_FALLTHROUGH; } default: - for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) { - if (CustomData *data = info[domain].customdata) { - if (CustomData_free_layer_named(data, name, info[domain].length)) { - return true; - } - } + if (std::optional attributes = get_attribute_accessor_for_write( + *id)) { + return attributes->remove(name); } return false; } diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index 19faddc5727..8d21c6fe792 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -319,8 +319,8 @@ GAttributeWriter BuiltinCustomDataLayerProvider::try_get_for_write(void *owner) } std::function tag_modified_fn; - if (update_on_write_ != nullptr) { - tag_modified_fn = [owner, update = update_on_write_]() { update(owner); }; + if (update_on_change_ != nullptr) { + tag_modified_fn = [owner, update = update_on_change_]() { update(owner); }; } return {as_write_attribute_(data, element_num), domain_, std::move(tag_modified_fn)}; @@ -336,12 +336,19 @@ bool BuiltinCustomDataLayerProvider::try_delete(void *owner) const return {}; } + auto update = [&]() { + if (update_on_change_ != nullptr) { + update_on_change_(owner); + } + }; + const int element_num = custom_data_access_.get_element_num(owner); if (stored_as_named_attribute_) { if (CustomData_free_layer_named(custom_data, name_.c_str(), element_num)) { if (custom_data_access_.update_custom_data_pointers) { custom_data_access_.update_custom_data_pointers(owner); } + update(); return true; } return false; @@ -352,8 +359,10 @@ bool BuiltinCustomDataLayerProvider::try_delete(void *owner) const if (custom_data_access_.update_custom_data_pointers) { custom_data_access_.update_custom_data_pointers(owner); } + update(); return true; } + return false; } diff --git a/source/blender/blenkernel/intern/attribute_access_intern.hh b/source/blender/blenkernel/intern/attribute_access_intern.hh index 17432fa2726..1a2607d9403 100644 --- a/source/blender/blenkernel/intern/attribute_access_intern.hh +++ b/source/blender/blenkernel/intern/attribute_access_intern.hh @@ -226,12 +226,12 @@ class BuiltinCustomDataLayerProvider final : public BuiltinAttributeProvider { using AsReadAttribute = GVArray (*)(const void *data, int element_num); using AsWriteAttribute = GVMutableArray (*)(void *data, int element_num); using UpdateOnRead = void (*)(const void *owner); - using UpdateOnWrite = void (*)(void *owner); + using UpdateOnChange = void (*)(void *owner); const eCustomDataType stored_type_; const CustomDataAccessInfo custom_data_access_; const AsReadAttribute as_read_attribute_; const AsWriteAttribute as_write_attribute_; - const UpdateOnWrite update_on_write_; + const UpdateOnChange update_on_change_; bool stored_as_named_attribute_; public: @@ -245,14 +245,14 @@ class BuiltinCustomDataLayerProvider final : public BuiltinAttributeProvider { const CustomDataAccessInfo custom_data_access, const AsReadAttribute as_read_attribute, const AsWriteAttribute as_write_attribute, - const UpdateOnWrite update_on_write) + const UpdateOnChange update_on_write) : BuiltinAttributeProvider( std::move(attribute_name), domain, attribute_type, creatable, writable, deletable), stored_type_(stored_type), custom_data_access_(custom_data_access), as_read_attribute_(as_read_attribute), as_write_attribute_(as_write_attribute), - update_on_write_(update_on_write), + update_on_change_(update_on_write), stored_as_named_attribute_(data_type_ == stored_type_) { } -- cgit v1.2.3 From f1f2c26223efd4ff0a99c5b7510da917fc606205 Mon Sep 17 00:00:00 2001 From: Chris Blackbourn Date: Sun, 24 Jul 2022 13:47:32 +1200 Subject: Cleanup: Simplify uv sculpt tool No functional changes. --- source/blender/editors/sculpt_paint/sculpt_uv.c | 38 +++++++++---------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/source/blender/editors/sculpt_paint/sculpt_uv.c b/source/blender/editors/sculpt_paint/sculpt_uv.c index 5c2dff7b252..dfa85e8e56d 100644 --- a/source/blender/editors/sculpt_paint/sculpt_uv.c +++ b/source/blender/editors/sculpt_paint/sculpt_uv.c @@ -211,7 +211,7 @@ static void HC_relaxation_iteration_uv(BMEditMesh *em, } } - MEM_freeN(tmp_uvdata); + MEM_SAFE_FREE(tmp_uvdata); } static void laplacian_relaxation_iteration_uv(BMEditMesh *em, @@ -240,7 +240,7 @@ static void laplacian_relaxation_iteration_uv(BMEditMesh *em, add_v2_v2(tmp_uvdata[tmpedge->uv1].sum_co, sculptdata->uv[tmpedge->uv2].uv); } - /* Original Lacplacian algorithm included removal of normal component of translation. + /* Original Laplacian algorithm included removal of normal component of translation. * here it is not needed since we translate along the UV plane always. */ for (i = 0; i < sculptdata->totalUniqueUvs; i++) { copy_v2_v2(tmp_uvdata[i].p, tmp_uvdata[i].sum_co); @@ -283,7 +283,7 @@ static void laplacian_relaxation_iteration_uv(BMEditMesh *em, } } - MEM_freeN(tmp_uvdata); + MEM_SAFE_FREE(tmp_uvdata); } static void uv_sculpt_stroke_apply(bContext *C, @@ -417,20 +417,14 @@ static void uv_sculpt_stroke_exit(bContext *C, wmOperator *op) if (data->elementMap) { BM_uv_element_map_free(data->elementMap); } - if (data->uv) { - MEM_freeN(data->uv); - } - if (data->uvedges) { - MEM_freeN(data->uvedges); - } + MEM_SAFE_FREE(data->uv); + MEM_SAFE_FREE(data->uvedges); if (data->initial_stroke) { - if (data->initial_stroke->initialSelection) { - MEM_freeN(data->initial_stroke->initialSelection); - } - MEM_freeN(data->initial_stroke); + MEM_SAFE_FREE(data->initial_stroke->initialSelection); + MEM_SAFE_FREE(data->initial_stroke); } - MEM_freeN(data); + MEM_SAFE_FREE(data); op->customdata = NULL; } @@ -489,7 +483,7 @@ static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wm bool do_island_optimization = !(ts->uv_sculpt_settings & UV_SCULPT_ALL_ISLANDS); int island_index = 0; - /* Holds, for each UvElement in elementMap, a pointer to its unique UV. */ + /* Holds, for each UvElement in elementMap, an index of its unique UV. */ int *uniqueUv; data->tool = (RNA_enum_get(op->ptr, "mode") == BRUSH_STROKE_SMOOTH) ? UV_SCULPT_TOOL_RELAX : @@ -540,12 +534,8 @@ static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wm /* we have at most totalUVs edges */ edges = MEM_mallocN(sizeof(*edges) * data->elementMap->totalUVs, "uv_brush_all_edges"); if (!data->uv || !uniqueUv || !edgeHash || !edges) { - if (edges) { - MEM_freeN(edges); - } - if (uniqueUv) { - MEM_freeN(uniqueUv); - } + MEM_SAFE_FREE(edges); + MEM_SAFE_FREE(uniqueUv); if (edgeHash) { BLI_ghash_free(edgeHash, NULL, NULL); } @@ -625,14 +615,14 @@ static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wm } } - MEM_freeN(uniqueUv); + MEM_SAFE_FREE(uniqueUv); /* Allocate connectivity data, we allocate edges once */ data->uvedges = MEM_mallocN(sizeof(*data->uvedges) * BLI_ghash_len(edgeHash), "uv_brush_edge_connectivity_data"); if (!data->uvedges) { BLI_ghash_free(edgeHash, NULL, NULL); - MEM_freeN(edges); + MEM_SAFE_FREE(edges); uv_sculpt_stroke_exit(C, op); return NULL; } @@ -646,7 +636,7 @@ static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wm /* cleanup temporary stuff */ BLI_ghash_free(edgeHash, NULL, NULL); - MEM_freeN(edges); + MEM_SAFE_FREE(edges); /* transfer boundary edge property to UV's */ if (ts->uv_sculpt_settings & UV_SCULPT_LOCK_BORDERS) { -- cgit v1.2.3 From 0fcc04e7bfe1edf390bec06f488aa9a4ee220983 Mon Sep 17 00:00:00 2001 From: Chris Blackbourn Date: Sun, 24 Jul 2022 14:48:30 +1200 Subject: Cleanup: Fix off-by-half-errors with udim search --- source/blender/blenkernel/intern/image.cc | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/source/blender/blenkernel/intern/image.cc b/source/blender/blenkernel/intern/image.cc index 7fa3a5ad14d..c2b8ec95a46 100644 --- a/source/blender/blenkernel/intern/image.cc +++ b/source/blender/blenkernel/intern/image.cc @@ -867,28 +867,28 @@ int BKE_image_find_nearest_tile_with_offset(const Image *image, const float co[2], float r_uv_offset[2]) { - const float co_floor[2] = {floorf(co[0]), floorf(co[1])}; - /* Distance to the closest UDIM tile. */ + /* Distance squared to the closest UDIM tile. */ float dist_best_sq = FLT_MAX; float uv_offset_best[2] = {0, 0}; int tile_number_best = -1; + const float co_offset[2] = {co[0] - 0.5f, co[1] - 0.5f}; + LISTBASE_FOREACH (const ImageTile *, tile, &image->tiles) { float uv_offset[2]; BKE_image_get_tile_uv(image, tile->tile_number, uv_offset); - if (equals_v2v2(co_floor, uv_offset)) { - copy_v2_v2(r_uv_offset, uv_offset); - return tile->tile_number; - } - - /* Distance between co[2] and UDIM tile. */ - const float dist_sq = len_squared_v2v2(uv_offset, co); + /* Distance squared between co[2] and center of UDIM tile. */ + const float dist_sq = len_squared_v2v2(uv_offset, co_offset); if (dist_sq < dist_best_sq) { dist_best_sq = dist_sq; tile_number_best = tile->tile_number; copy_v2_v2(uv_offset_best, uv_offset); + + if (dist_best_sq < 0.5f * 0.5f) { + break; /* No other tile can be closer. */ + } } } if (tile_number_best != -1) { -- cgit v1.2.3 From 364babab652bf80d6beea419a74242611a4e2393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Sun, 24 Jul 2022 09:18:56 +0200 Subject: EEVEE-Next: Fix background velocity --- .../draw/engines/eevee_next/shaders/eevee_velocity_lib.glsl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_velocity_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_velocity_lib.glsl index fb9c9faaca2..c21456b7a5c 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_velocity_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_velocity_lib.glsl @@ -41,8 +41,16 @@ vec4 velocity_background(vec3 vV) { /* Only transform direction to avoid loosing precision. */ vec3 V = transform_direction(camera_curr.viewinv, vV); + /* NOTE: We don't use the drw_view.winmat to avoid adding the TAA jitter to the velocity. */ + vec2 prev_uv = project_point(camera_prev.winmat, V).xy; + vec2 curr_uv = project_point(camera_curr.winmat, V).xy; + vec2 next_uv = project_point(camera_next.winmat, V).xy; - return velocity_surface(V, V, V); + vec4 motion = vec4(prev_uv - curr_uv, curr_uv - next_uv); + /* Convert NDC velocity to UV velocity */ + motion *= 0.5; + + return motion; } /** -- cgit v1.2.3 From bd9bb56f181de64779539db833217cb6a04d855b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Sun, 24 Jul 2022 10:33:41 +0200 Subject: EEVEE-Next: Fix Alt+B render borders A few offsets were missing. Reminder that this does not change the actual render resolution but it reduces the VRAM consumption of accumulation buffers. --- source/blender/draw/engines/eevee_next/eevee_film.cc | 8 +++++++- source/blender/draw/engines/eevee_next/eevee_film.hh | 4 +--- .../blender/draw/engines/eevee_next/eevee_shader_shared.hh | 3 +++ .../draw/engines/eevee_next/shaders/eevee_film_frag.glsl | 2 +- .../draw/engines/eevee_next/shaders/eevee_film_lib.glsl | 14 ++++++++------ 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/eevee_film.cc b/source/blender/draw/engines/eevee_next/eevee_film.cc index b8408e55cb3..ae84ea249b7 100644 --- a/source/blender/draw/engines/eevee_next/eevee_film.cc +++ b/source/blender/draw/engines/eevee_next/eevee_film.cc @@ -311,7 +311,7 @@ void Film::init(const int2 &extent, const rcti *output_rect) { /* TODO(@fclem): Over-scans. */ - render_extent_ = math::divide_ceil(extent, int2(data_.scaling_factor)); + data_.render_extent = math::divide_ceil(extent, int2(data_.scaling_factor)); int2 weight_extent = inst_.camera.is_panoramic() ? data_.extent : int2(data_.scaling_factor); eGPUTextureFormat color_format = GPU_RGBA16F; @@ -536,7 +536,13 @@ void Film::accumulate(const DRWView *view) { if (inst_.is_viewport()) { DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get(); + DefaultTextureList *dtxl = DRW_viewport_texture_list_get(); GPU_framebuffer_bind(dfbl->default_fb); + /* Clear when using render borders. */ + if (data_.extent != int2(GPU_texture_width(dtxl->color), GPU_texture_height(dtxl->color))) { + float4 clear_color = {0.0f, 0.0f, 0.0f, 0.0f}; + GPU_framebuffer_clear_color(dfbl->default_fb, clear_color); + } GPU_framebuffer_viewport_set(dfbl->default_fb, UNPACK2(data_.offset), UNPACK2(data_.extent)); } diff --git a/source/blender/draw/engines/eevee_next/eevee_film.hh b/source/blender/draw/engines/eevee_next/eevee_film.hh index e2d5956710d..26e27c447bc 100644 --- a/source/blender/draw/engines/eevee_next/eevee_film.hh +++ b/source/blender/draw/engines/eevee_next/eevee_film.hh @@ -55,8 +55,6 @@ class Film { /** Static reference as SwapChain does not actually move the objects when swapping. */ GPUTexture *weight_src_tx_ = nullptr; GPUTexture *weight_dst_tx_ = nullptr; - /** Extent used by the render buffers when rendering the main views. */ - int2 render_extent_ = int2(-1); /** User setting to disable reprojection. Useful for debugging or have a more precise render. */ bool force_disable_reprojection_ = false; @@ -86,7 +84,7 @@ class Film { int2 render_extent_get() const { - return render_extent_; + return data_.render_extent; } float2 pixel_jitter_get() const; diff --git a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh index cc991efeee2..d703f000ab6 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh +++ b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh @@ -154,6 +154,8 @@ struct FilmData { int2 extent; /** Offset of the film in the full-res frame, in pixels. */ int2 offset; + /** Extent used by the render buffers when rendering the main views. */ + int2 render_extent; /** Sub-pixel offset applied to the window matrix. * NOTE: In final film pixel unit. * NOTE: Positive values makes the view translate in the negative axes direction. @@ -172,6 +174,7 @@ struct FilmData { /** Is true if accumulation of filtered passes is needed. */ bool1 any_render_pass_1; bool1 any_render_pass_2; + float _pad0, _pad1; /** Output counts per type. */ int color_len, value_len; /** Index in color_accum_img or value_accum_img of each pass. -1 if pass is not enabled. */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_film_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_film_frag.glsl index 454c835673b..5867330f151 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_film_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_film_frag.glsl @@ -4,7 +4,7 @@ void main() { - ivec2 texel_film = ivec2(gl_FragCoord.xy); + ivec2 texel_film = ivec2(gl_FragCoord.xy) - film_buf.offset; float out_depth; if (film_buf.display_only) { diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl index efd3d2dfe35..c0d19ca4451 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl @@ -80,9 +80,9 @@ FilmSample film_sample_get(int sample_n, ivec2 texel_film) # endif FilmSample film_sample = film_buf.samples[sample_n]; - film_sample.texel += texel_film; + film_sample.texel += texel_film + film_buf.offset; /* Use extend on borders. */ - film_sample.texel = clamp(film_sample.texel, ivec2(0, 0), film_buf.extent - 1); + film_sample.texel = clamp(film_sample.texel, ivec2(0, 0), film_buf.render_extent - 1); /* TODO(fclem): Panoramic projection will need to compute the sample weight in the shader * instead of precomputing it on CPU. */ @@ -440,7 +440,7 @@ void film_store_combined( /* Interactive accumulation. Do reprojection and Temporal Anti-Aliasing. */ /* Reproject by finding where this pixel was in the previous frame. */ - vec2 motion = film_pixel_history_motion_vector(dst.texel); + vec2 motion = film_pixel_history_motion_vector(src_texel); vec2 history_texel = vec2(dst.texel) + motion; float velocity = length(motion); @@ -592,11 +592,13 @@ void film_process_data(ivec2 texel_film, out vec4 out_color, out float out_depth float weight_accum = 0.0; vec4 combined_accum = vec4(0.0); - for (int i = 0; i < film_buf.samples_len; i++) { - FilmSample src = film_sample_get(i, texel_film); + FilmSample src; + for (int i = film_buf.samples_len - 1; i >= 0; i--) { + src = film_sample_get(i, texel_film); film_sample_accum_combined(src, combined_accum, weight_accum); } - film_store_combined(dst, texel_film, combined_accum, weight_accum, out_color); + /* NOTE: src.texel is center texel in incomming data buffer. */ + film_store_combined(dst, src.texel, combined_accum, weight_accum, out_color); } if (film_buf.has_data) { -- cgit v1.2.3 From 8ac5b1fdb39010ba65c5da010f6ebcc71479a7ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Sun, 24 Jul 2022 14:46:44 +0200 Subject: EEVEE-Next: Make Anti-Flicker more strong This might make the image a bit blurier but it reduces the flickering of shiny surfaces during animation. This uses the technique described in "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014 (Slide 45): Reduce the exponential factor when the history is close the bounding box border. --- .../engines/eevee_next/shaders/eevee_film_lib.glsl | 29 +++++++++++++++------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl index c0d19ca4451..62c3d89cfc4 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl @@ -381,12 +381,9 @@ float film_aabb_clipping_dist_alpha(float origin, float direction, float aabb_mi } /* Modulate the history color to avoid ghosting artifact. */ -vec4 film_amend_combined_history(vec4 color_history, vec4 src_color, ivec2 src_texel) +vec4 film_amend_combined_history( + vec4 min_color, vec4 max_color, vec4 color_history, vec4 src_color, ivec2 src_texel) { - /* Get local color bounding box of source neighboorhood. */ - vec4 min_color, max_color; - film_combined_neighbor_boundbox(src_texel, min_color, max_color); - /* Clip instead of clamping to avoid color accumulating in the AABB corners. */ vec4 clip_dir = src_color - color_history; @@ -402,6 +399,8 @@ vec4 film_amend_combined_history(vec4 color_history, vec4 src_color, ivec2 src_t float film_history_blend_factor(float velocity, vec2 texel, + float luma_min, + float luma_max, float luma_incoming, float luma_history) { @@ -409,8 +408,15 @@ float film_history_blend_factor(float velocity, float blend = 0.05; /* Blend less history if the pixel has substential velocity. */ blend = mix(blend, 0.20, saturate(velocity * 0.02)); - /* Weight by luma. */ - blend = max(blend, saturate(0.01 * luma_history / abs(luma_history - luma_incoming))); + /** + * "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014 (Slide 43) + * Bias towards history if incomming pixel is near clamping. Reduces flicker. + */ + float distance_to_luma_clip = min_v2(vec2(luma_history - luma_min, luma_max - luma_history)); + /* Divide by bbox size to get a factor. 2 factor to compensate the line above. */ + distance_to_luma_clip *= 2.0 * safe_rcp(luma_max - luma_min); + /* Linearly blend when history gets bellow to 25% of the bbox size. */ + blend *= saturate(distance_to_luma_clip * 4.0 + 0.1); /* Discard out of view history. */ if (any(lessThan(texel, vec2(0))) || any(greaterThanEqual(texel, film_buf.extent))) { blend = 1.0; @@ -451,9 +457,14 @@ void film_store_combined( color_dst = film_sample_catmull_rom(in_combined_tx, history_texel); color_dst.rgb = film_YCoCg_from_scene_linear(color_dst.rgb); - float blend = film_history_blend_factor(velocity, history_texel, color_src.x, color_dst.x); + /* Get local color bounding box of source neighboorhood. */ + vec4 min_color, max_color; + film_combined_neighbor_boundbox(src_texel, min_color, max_color); + + float blend = film_history_blend_factor( + velocity, history_texel, min_color.x, max_color.x, color_src.x, color_dst.x); - color_dst = film_amend_combined_history(color_dst, color_src, src_texel); + color_dst = film_amend_combined_history(min_color, max_color, color_dst, color_src, src_texel); /* Luma weighted blend to avoid flickering. */ weight_dst = film_luma_weight(color_dst.x) * (1.0 - blend); -- cgit v1.2.3 From 68101fea687f12a7646230f509078452ccfccb2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Sun, 24 Jul 2022 15:33:28 +0200 Subject: EEVEE-Next: Add back background opacity toggle --- release/scripts/startup/bl_ui/properties_render.py | 1 + source/blender/draw/engines/eevee_next/eevee_film.cc | 8 +++++++- source/blender/draw/engines/eevee_next/eevee_film.hh | 5 +++++ source/blender/draw/engines/eevee_next/eevee_pipeline.cc | 1 + source/blender/draw/engines/eevee_next/eevee_shader_shared.hh | 4 +++- .../draw/engines/eevee_next/shaders/eevee_surf_world_frag.glsl | 3 +++ .../draw/engines/eevee_next/shaders/infos/eevee_material_info.hh | 1 + 7 files changed, 21 insertions(+), 2 deletions(-) diff --git a/release/scripts/startup/bl_ui/properties_render.py b/release/scripts/startup/bl_ui/properties_render.py index f217df9b599..2f138ea26bd 100644 --- a/release/scripts/startup/bl_ui/properties_render.py +++ b/release/scripts/startup/bl_ui/properties_render.py @@ -525,6 +525,7 @@ class RENDER_PT_eevee_next_film(RenderButtonsPanel, Panel): col = layout.column() col.prop(rd, "filter_size") + col.prop(rd, "film_transparent", text="Transparent") def draw_curves_settings(self, context): diff --git a/source/blender/draw/engines/eevee_next/eevee_film.cc b/source/blender/draw/engines/eevee_next/eevee_film.cc index ae84ea249b7..cdd753b988d 100644 --- a/source/blender/draw/engines/eevee_next/eevee_film.cc +++ b/source/blender/draw/engines/eevee_next/eevee_film.cc @@ -147,7 +147,8 @@ void Film::sync_mist() inline bool operator==(const FilmData &a, const FilmData &b) { return (a.extent == b.extent) && (a.offset == b.offset) && - (a.filter_radius == b.filter_radius) && (a.scaling_factor == b.scaling_factor); + (a.filter_radius == b.filter_radius) && (a.scaling_factor == b.scaling_factor) && + (a.background_opacity == b.background_opacity); } inline bool operator!=(const FilmData &a, const FilmData &b) @@ -238,6 +239,11 @@ void Film::init(const int2 &extent, const rcti *output_rect) * We need to figure out LOD bias first in order to preserve texture crispiness. */ data.scaling_factor = 1; + data.background_opacity = (scene.r.alphamode == R_ALPHAPREMUL) ? 0.0f : 1.0f; + if (inst_.is_viewport() && false /* TODO(fclem): StudioLight */) { + data.background_opacity = inst_.v3d->shading.studiolight_background; + } + FilmData &data_prev_ = data_; if (assign_if_different(data_prev_, data)) { sampling.reset(); diff --git a/source/blender/draw/engines/eevee_next/eevee_film.hh b/source/blender/draw/engines/eevee_next/eevee_film.hh index 26e27c447bc..124fd79a1c0 100644 --- a/source/blender/draw/engines/eevee_next/eevee_film.hh +++ b/source/blender/draw/engines/eevee_next/eevee_film.hh @@ -89,6 +89,11 @@ class Film { float2 pixel_jitter_get() const; + float background_opacity_get() const + { + return data_.background_opacity; + } + eViewLayerEEVEEPassType enabled_passes_get() const; static bool pass_is_value(eViewLayerEEVEEPassType pass_type) diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc index 7b3cfbf5899..214fe9c7153 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc @@ -36,6 +36,7 @@ void WorldPipeline::sync(GPUMaterial *gpumat) DRWShadingGroup *grp = DRW_shgroup_material_create(gpumat, world_ps_); DRW_shgroup_uniform_texture(grp, "utility_tx", inst_.pipelines.utility_tx); DRW_shgroup_call_obmat(grp, DRW_cache_fullscreen_quad_get(), camera_mat.ptr()); + DRW_shgroup_uniform_float_copy(grp, "world_opacity_fade", inst_.film.background_opacity_get()); /* AOVs. */ DRW_shgroup_uniform_image_ref(grp, "aov_color_img", &rbufs.aov_color_tx); DRW_shgroup_uniform_image_ref(grp, "aov_value_img", &rbufs.aov_value_tx); diff --git a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh index d703f000ab6..3c10f633740 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh +++ b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh @@ -174,7 +174,9 @@ struct FilmData { /** Is true if accumulation of filtered passes is needed. */ bool1 any_render_pass_1; bool1 any_render_pass_2; - float _pad0, _pad1; + /** Controlled by user in lookdev mode or by render settings. */ + float background_opacity; + float _pad0; /** Output counts per type. */ int color_len, value_len; /** Index in color_accum_img or value_accum_img of each pass. -1 if pass is not enabled. */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_world_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_world_frag.glsl index b32c3c1c4eb..ed75282a550 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_world_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_world_frag.glsl @@ -34,4 +34,7 @@ void main() out_background.rgb = safe_color(g_emission) * (1.0 - g_holdout); out_background.a = saturate(avg(g_transmittance)) * g_holdout; + + /* World opacity. */ + out_background = mix(vec4(0.0, 0.0, 0.0, 1.0), out_background, world_opacity_fade); } diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh index 950164f5b86..2368061402c 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh @@ -140,6 +140,7 @@ GPU_SHADER_CREATE_INFO(eevee_surf_world) .image_out(3, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_specular_light_img") .image_out(4, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_specular_color_img") .image_out(5, Qualifier::READ_WRITE, GPU_RGBA16F, "rp_emission_img") + .push_constant(Type::FLOAT, "world_opacity_fade") .fragment_out(0, Type::VEC4, "out_background") .fragment_source("eevee_surf_world_frag.glsl") .additional_info("eevee_aov_out" -- cgit v1.2.3 From a5bcb4c1484860844ed3dbbfe0c6bfb47c2427a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Sun, 24 Jul 2022 15:37:13 +0200 Subject: EEVEE-Next: Make animated viewport non jittered when disabling denoising --- source/blender/draw/engines/eevee_next/eevee_sampling.cc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/source/blender/draw/engines/eevee_next/eevee_sampling.cc b/source/blender/draw/engines/eevee_next/eevee_sampling.cc index ef2469647ef..aa62c3dd38a 100644 --- a/source/blender/draw/engines/eevee_next/eevee_sampling.cc +++ b/source/blender/draw/engines/eevee_next/eevee_sampling.cc @@ -60,8 +60,15 @@ void Sampling::end_sync() } if (inst_.is_viewport()) { + interactive_mode_ = viewport_sample_ < interactive_mode_threshold; - if (interactive_mode_) { + + bool interactive_mode_disabled = (inst_.scene->eevee.flag & SCE_EEVEE_TAA_REPROJECTION) == 0; + if (interactive_mode_disabled) { + interactive_mode_ = false; + sample_ = viewport_sample_; + } + else if (interactive_mode_) { int interactive_sample_count = min_ii(interactive_sample_max_, sample_count_); if (viewport_sample_ < interactive_sample_count) { -- cgit v1.2.3 From b1c49b3b2a429a27e8a82e4ed8034a01583333c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Sun, 24 Jul 2022 17:54:09 +0200 Subject: EEVEE-Next: Fix depth accumulation and stability in viewport The display depth is used to composite Gpencil and Overlays. For it to be stable we bias it using the dFdx gradient functions. This makes overlays like edit mode not flicker. The previous approach to save the 1st center sample does not work anymore since we jitter the projection matrix in a looping pattern when scene is updated. So the center depth is only (almost) valid 1/8th of the times. The biasing technique, even if not perfect, does the job of being stable. This has a few cons: - it makes the geometry below the ground plane unlike workbench engine. - it makes overlays render over geometry at larger depth discontinuities. --- source/blender/draw/engines/eevee_next/eevee_instance.hh | 2 +- .../draw/engines/eevee_next/shaders/eevee_film_frag.glsl | 2 ++ .../draw/engines/eevee_next/shaders/eevee_film_lib.glsl | 16 +++++++++++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/eevee_instance.hh b/source/blender/draw/engines/eevee_next/eevee_instance.hh index f47d4f20363..1efda769648 100644 --- a/source/blender/draw/engines/eevee_next/eevee_instance.hh +++ b/source/blender/draw/engines/eevee_next/eevee_instance.hh @@ -111,7 +111,7 @@ class Instance { bool overlays_enabled() const { - return (!v3d) || ((v3d->flag & V3D_HIDE_OVERLAYS) == 0); + return v3d && ((v3d->flag2 & V3D_HIDE_OVERLAYS) == 0); } bool use_scene_lights() const diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_film_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_film_frag.glsl index 5867330f151..26040234fd0 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_film_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_film_frag.glsl @@ -26,4 +26,6 @@ void main() } gl_FragDepth = get_depth_from_view_z(-out_depth); + + gl_FragDepth = film_display_depth_ammend(texel_film, gl_FragDepth); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl index 62c3d89cfc4..b286836e8df 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl @@ -192,7 +192,7 @@ float film_distance_load(ivec2 texel) /* Repeat texture coordinates as the weight can be optimized to a small portion of the film. */ texel = texel % imageSize(in_weight_img).xy; - if (film_buf.use_history == false) { + if (!film_buf.use_history || film_buf.use_reprojection) { return 1.0e16; } return imageLoad(in_weight_img, ivec3(texel, WEIGHT_lAYER_DISTANCE)).x; @@ -577,6 +577,20 @@ void film_store_weight(ivec2 texel, float value) imageStore(out_weight_img, ivec3(texel, WEIGHT_lAYER_ACCUMULATION), vec4(value)); } +float film_display_depth_ammend(ivec2 texel, float depth) +{ + /* This effectively offsets the depth of the whole 2x2 region to the lowest value of the region + * twice. One for X and one for Y direction. */ + /* TODO(fclem): This could be improved as it gives flickering result at depth discontinuity. + * But this is the quickest stable result I could come with for now. */ +#ifdef GPU_FRAGMENT_SHADER + depth += fwidth(depth); +#endif + /* Small offset to avoid depth test lessEqual failing because of all the conversions loss. */ + depth += 2.4e-7 * 4.0; + return saturate(depth); +} + /** \} */ /** NOTE: out_depth is scene linear depth from the camera origin. */ -- cgit v1.2.3 From ad632a13d98e9d29c418a4acd5ec2848d6b71d7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Sun, 24 Jul 2022 19:06:12 +0200 Subject: EEVEE-Next: Decorelate Large filter spiral sampling This avoids correlation artifacts with the jitter pattern itself. Also try to reduce the visible spiral pattern. --- source/blender/draw/engines/eevee_next/eevee_film.cc | 2 +- source/blender/draw/engines/eevee_next/eevee_sampling.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/eevee_film.cc b/source/blender/draw/engines/eevee_next/eevee_film.cc index cdd753b988d..18e0452da25 100644 --- a/source/blender/draw/engines/eevee_next/eevee_film.cc +++ b/source/blender/draw/engines/eevee_next/eevee_film.cc @@ -522,7 +522,7 @@ void Film::update_sample_table() int i = 0; for (FilmSample &sample : sample_table) { /* TODO(fclem): Own RNG. */ - float2 random_2d = inst_.sampling.rng_2d_get(SAMPLING_FILTER_U); + float2 random_2d = inst_.sampling.rng_2d_get(SAMPLING_SSS_U); /* This randomization makes sure we converge to the right result but also makes nearest * neighbor filtering not converging rapidly. */ random_2d.x = (random_2d.x + i) / float(FILM_PRECOMP_SAMPLE_MAX); diff --git a/source/blender/draw/engines/eevee_next/eevee_sampling.cc b/source/blender/draw/engines/eevee_next/eevee_sampling.cc index aa62c3dd38a..1d320c75f16 100644 --- a/source/blender/draw/engines/eevee_next/eevee_sampling.cc +++ b/source/blender/draw/engines/eevee_next/eevee_sampling.cc @@ -177,7 +177,7 @@ float2 Sampling::sample_disk(const float2 &rand) float2 Sampling::sample_spiral(const float2 &rand) { /* Fibonacci spiral. */ - float omega = M_PI * (1.0f + sqrtf(5.0f)) * rand.x; + float omega = 4.0f * M_PI * (1.0f + sqrtf(5.0f)) * rand.x; float r = sqrtf(rand.x); /* Random rotation. */ omega += rand.y * 2.0f * M_PI; -- cgit v1.2.3 From 31365c6b9e4cd99a79fe64ebaf016c3d7e0e0c4f Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Sun, 24 Jul 2022 12:46:08 -0500 Subject: Attributes: Use new API for C-API functions Use the C++ API to implement more of the existing C functions. This corrects the cases where one tries to add a builtin attribute with the wrong domain or type on curves, though a better warning message would be helpful in the future, and also reduces duplication of the internal logic. Not much more is possible without changing the interface. --- source/blender/blenkernel/intern/attribute.cc | 105 ++++++++++++-------------- 1 file changed, 50 insertions(+), 55 deletions(-) diff --git a/source/blender/blenkernel/intern/attribute.cc b/source/blender/blenkernel/intern/attribute.cc index b277fc39caf..ff40f842349 100644 --- a/source/blender/blenkernel/intern/attribute.cc +++ b/source/blender/blenkernel/intern/attribute.cc @@ -203,6 +203,7 @@ bool BKE_id_attribute_calc_unique_name(ID *id, const char *name, char *outname) CustomDataLayer *BKE_id_attribute_new( ID *id, const char *name, const int type, const eAttrDomain domain, ReportList *reports) { + using namespace blender::bke; DomainInfo info[ATTR_DOMAIN_NUM]; get_domains(id, info); @@ -215,60 +216,56 @@ CustomDataLayer *BKE_id_attribute_new( char uniquename[MAX_CUSTOMDATA_LAYER_NAME]; BKE_id_attribute_calc_unique_name(id, name, uniquename); - switch (GS(id->name)) { - case ID_ME: { - Mesh *me = (Mesh *)id; - BMEditMesh *em = me->edit_mesh; - if (em != nullptr) { - BM_data_layer_add_named(em->bm, customdata, type, uniquename); - } - else { - CustomData_add_layer_named( - customdata, type, CD_DEFAULT, nullptr, info[domain].length, uniquename); - } - break; - } - default: { - CustomData_add_layer_named( - customdata, type, CD_DEFAULT, nullptr, info[domain].length, uniquename); - break; + if (GS(id->name) == ID_ME) { + Mesh *mesh = reinterpret_cast(id); + if (BMEditMesh *em = mesh->edit_mesh) { + BM_data_layer_add_named(em->bm, customdata, type, uniquename); + const int index = CustomData_get_named_layer_index(customdata, type, uniquename); + return (index == -1) ? nullptr : &(customdata->layers[index]); } } + std::optional attributes = get_attribute_accessor_for_write(*id); + if (!attributes) { + return nullptr; + } + + attributes->add(uniquename, domain, eCustomDataType(type), AttributeInitDefault()); + const int index = CustomData_get_named_layer_index(customdata, type, uniquename); return (index == -1) ? nullptr : &(customdata->layers[index]); } CustomDataLayer *BKE_id_attribute_duplicate(ID *id, const char *name, ReportList *reports) { - const CustomDataLayer *src_layer = BKE_id_attribute_search( - id, name, CD_MASK_PROP_ALL, ATTR_DOMAIN_MASK_ALL); - if (src_layer == nullptr) { - BKE_report(reports, RPT_ERROR, "Attribute is not part of this geometry"); - return nullptr; - } - - const eCustomDataType type = (eCustomDataType)src_layer->type; - const eAttrDomain domain = BKE_id_attribute_domain(id, src_layer); + using namespace blender::bke; + char uniquename[MAX_CUSTOMDATA_LAYER_NAME]; + BKE_id_attribute_calc_unique_name(id, name, uniquename); - /* Make a copy of name in case CustomData API reallocates the layers. */ - const std::string name_copy = name; + if (GS(id->name) == ID_ME) { + Mesh *mesh = reinterpret_cast(id); + if (BMEditMesh *em = mesh->edit_mesh) { + BLI_assert_unreachable(); + UNUSED_VARS(em); + return nullptr; + } + } - DomainInfo info[ATTR_DOMAIN_NUM]; - get_domains(id, info); - CustomData *customdata = info[domain].customdata; + std::optional attributes = get_attribute_accessor_for_write(*id); + if (!attributes) { + return nullptr; + } - CustomDataLayer *new_layer = BKE_id_attribute_new(id, name_copy.c_str(), type, domain, reports); - if (new_layer == nullptr) { + GAttributeReader src = attributes->lookup(name); + if (!src) { + BKE_report(reports, RPT_ERROR, "Attribute is not part of this geometry"); return nullptr; } - const int from_index = CustomData_get_named_layer_index(customdata, type, name_copy.c_str()); - const int to_index = CustomData_get_named_layer_index(customdata, type, new_layer->name); - CustomData_copy_data_layer( - customdata, customdata, from_index, to_index, 0, 0, info[domain].length); + const eCustomDataType type = cpp_type_to_custom_data_type(src.varray.type()); + attributes->add(uniquename, src.domain, type, AttributeInitVArray(src.varray)); - return new_layer; + return BKE_id_attribute_search(id, uniquename, CD_MASK_PROP_ALL, ATTR_DOMAIN_MASK_ALL); } bool BKE_id_attribute_remove(ID *id, const char *name, ReportList *reports) @@ -282,28 +279,26 @@ bool BKE_id_attribute_remove(ID *id, const char *name, ReportList *reports) DomainInfo info[ATTR_DOMAIN_NUM]; get_domains(id, info); - switch (GS(id->name)) { - case ID_ME: { - Mesh *mesh = reinterpret_cast(id); - if (BMEditMesh *em = mesh->edit_mesh) { - for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) { - if (CustomData *data = info[domain].customdata) { - if (BM_data_layer_free_named(em->bm, data, name)) { - return true; - } + if (GS(id->name) == ID_ME) { + Mesh *mesh = reinterpret_cast(id); + if (BMEditMesh *em = mesh->edit_mesh) { + for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) { + if (CustomData *data = info[domain].customdata) { + if (BM_data_layer_free_named(em->bm, data, name)) { + return true; } } - return false; - } - ATTR_FALLTHROUGH; - } - default: - if (std::optional attributes = get_attribute_accessor_for_write( - *id)) { - return attributes->remove(name); } return false; + } } + + std::optional attributes = get_attribute_accessor_for_write(*id); + if (!attributes) { + return false; + } + + return attributes->remove(name); } CustomDataLayer *BKE_id_attribute_find(const ID *id, -- cgit v1.2.3 From d26c29d8e46c284b27cc6d3bcde8fde7ff678ae4 Mon Sep 17 00:00:00 2001 From: Lukas Stockner Date: Sun, 24 Jul 2022 20:32:19 +0200 Subject: Fix T98367: Light group passes do not work when shadow catcher is used --- intern/cycles/session/buffers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intern/cycles/session/buffers.cpp b/intern/cycles/session/buffers.cpp index b74074765fe..e060e0c6829 100644 --- a/intern/cycles/session/buffers.cpp +++ b/intern/cycles/session/buffers.cpp @@ -209,7 +209,7 @@ const BufferPass *BufferParams::get_actual_display_pass(const BufferPass *pass) return nullptr; } - if (pass->type == PASS_COMBINED) { + if (pass->type == PASS_COMBINED && pass->lightgroup.empty()) { const BufferPass *shadow_catcher_matte_pass = find_pass(PASS_SHADOW_CATCHER_MATTE, pass->mode); if (shadow_catcher_matte_pass) { pass = shadow_catcher_matte_pass; -- cgit v1.2.3 From f7d5aaa3656cf5b839d86bc6d5ad57960d540202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dietrich?= Date: Sat, 23 Jul 2022 06:01:34 +0200 Subject: Alembic: speed up edge crease import The Alembic importer uses a linear search over the mesh edges to find the right edge when setting edge creases. Although the complexity is `O(m * n)`, with `m` being the number of creased edges, and `n` being the number of edges, this can lead to a quadratic complexity as `m` approches `n`. This patch uses `EdgeHash` to store and retrieve the edges, which should bring complexity closer to `O(n)`, provided that lookup is `O(1)`. See differential for some timings. In most files, this is expected to give at least a 2-3x speedup for this operation, but can lead orders of magnitude speed increase for dense meshes with a significant number of edge creases. Differential Revision: https://developer.blender.org/D15521 --- .../blender/io/alembic/intern/abc_reader_mesh.cc | 29 ++++++++++------------ 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.cc b/source/blender/io/alembic/intern/abc_reader_mesh.cc index df3559c108c..bacc1f06599 100644 --- a/source/blender/io/alembic/intern/abc_reader_mesh.cc +++ b/source/blender/io/alembic/intern/abc_reader_mesh.cc @@ -20,6 +20,7 @@ #include "DNA_object_types.h" #include "BLI_compiler_compat.h" +#include "BLI_edgehash.h" #include "BLI_index_range.hh" #include "BLI_listbase.h" #include "BLI_math_geom.h" @@ -830,19 +831,6 @@ void AbcMeshReader::readFaceSetsSample(Main *bmain, Mesh *mesh, const ISampleSel /* ************************************************************************** */ -BLI_INLINE MEdge *find_edge(MEdge *edges, int totedge, int v1, int v2) -{ - for (int i = 0, e = totedge; i < e; i++) { - MEdge &edge = edges[i]; - - if (edge.v1 == v1 && edge.v2 == v2) { - return &edge; - } - } - - return nullptr; -} - static void read_subd_sample(const std::string &iobject_full_name, ImportSettings *settings, const ISubDSchema &schema, @@ -927,7 +915,14 @@ static void read_edge_creases(Mesh *mesh, } MEdge *edges = mesh->medge; - int totedge = mesh->totedge; + const int totedge = mesh->totedge; + + EdgeHash *edge_hash = BLI_edgehash_new_ex(__func__, mesh->totedge); + + for (int i = 0; i < totedge; i++) { + MEdge *edge = &edges[i]; + BLI_edgehash_insert(edge_hash, edge->v1, edge->v2, edge); + } for (int i = 0, s = 0, e = indices->size(); i < e; i += 2, s++) { int v1 = (*indices)[i]; @@ -939,9 +934,9 @@ static void read_edge_creases(Mesh *mesh, std::swap(v1, v2); } - MEdge *edge = find_edge(edges, totedge, v1, v2); + MEdge *edge = static_cast(BLI_edgehash_lookup(edge_hash, v1, v2)); if (edge == nullptr) { - edge = find_edge(edges, totedge, v2, v1); + edge = static_cast(BLI_edgehash_lookup(edge_hash, v2, v1)); } if (edge) { @@ -949,6 +944,8 @@ static void read_edge_creases(Mesh *mesh, } } + BLI_edgehash_free(edge_hash, nullptr); + mesh->cd_flag |= ME_CDFLAG_EDGE_CREASE; } -- cgit v1.2.3 From 6db059e3d7c3156943e3f0646612eb1d74409215 Mon Sep 17 00:00:00 2001 From: Lukas Stockner Date: Mon, 16 May 2022 10:52:49 +0200 Subject: Render: Update lightgroup membership in objects and world if lightgroup is renamed As discussed, this only updates objects in and the world of the scene to which the view layer belongs, which also avoids the problem of not having a BMain available. Differential Revision: https://developer.blender.org/D14740 --- source/blender/blenkernel/BKE_layer.h | 3 ++- source/blender/blenkernel/intern/layer.c | 27 ++++++++++++++++++++++++++- source/blender/makesrna/intern/rna_scene.c | 2 +- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/source/blender/blenkernel/BKE_layer.h b/source/blender/blenkernel/BKE_layer.h index 3e4f2fe154e..9a6c3cf2b5f 100644 --- a/source/blender/blenkernel/BKE_layer.h +++ b/source/blender/blenkernel/BKE_layer.h @@ -590,7 +590,8 @@ void BKE_view_layer_set_active_lightgroup(struct ViewLayer *view_layer, struct ViewLayerLightgroup *lightgroup); struct ViewLayer *BKE_view_layer_find_with_lightgroup( struct Scene *scene, struct ViewLayerLightgroup *view_layer_lightgroup); -void BKE_view_layer_rename_lightgroup(ViewLayer *view_layer, +void BKE_view_layer_rename_lightgroup(struct Scene *scene, + ViewLayer *view_layer, ViewLayerLightgroup *lightgroup, const char *name); diff --git a/source/blender/blenkernel/intern/layer.c b/source/blender/blenkernel/intern/layer.c index ac582ff69ca..dabc76f29ca 100644 --- a/source/blender/blenkernel/intern/layer.c +++ b/source/blender/blenkernel/intern/layer.c @@ -39,6 +39,7 @@ #include "DNA_view3d_types.h" #include "DNA_windowmanager_types.h" #include "DNA_workspace_types.h" +#include "DNA_world_types.h" #include "DEG_depsgraph.h" #include "DEG_depsgraph_debug.h" @@ -2588,12 +2589,36 @@ ViewLayer *BKE_view_layer_find_with_lightgroup(struct Scene *scene, return NULL; } -void BKE_view_layer_rename_lightgroup(ViewLayer *view_layer, +void BKE_view_layer_rename_lightgroup(Scene *scene, + ViewLayer *view_layer, ViewLayerLightgroup *lightgroup, const char *name) { + char old_name[64]; + BLI_strncpy_utf8(old_name, lightgroup->name, sizeof(old_name)); BLI_strncpy_utf8(lightgroup->name, name, sizeof(lightgroup->name)); viewlayer_lightgroup_make_name_unique(view_layer, lightgroup); + + if (scene != NULL) { + /* Update objects in the scene to refer to the new name instead. */ + FOREACH_SCENE_OBJECT_BEGIN (scene, ob) { + if (!ID_IS_LINKED(ob) && ob->lightgroup != NULL) { + LightgroupMembership *lgm = ob->lightgroup; + if (STREQ(lgm->name, old_name)) { + BLI_strncpy_utf8(lgm->name, lightgroup->name, sizeof(lgm->name)); + } + } + } + FOREACH_SCENE_OBJECT_END; + + /* Update the scene's world to refer to the new name instead. */ + if (scene->world != NULL && !ID_IS_LINKED(scene->world) && scene->world->lightgroup != NULL) { + LightgroupMembership *lgm = scene->world->lightgroup; + if (STREQ(lgm->name, old_name)) { + BLI_strncpy_utf8(lgm->name, lightgroup->name, sizeof(lgm->name)); + } + } + } } void BKE_lightgroup_membership_get(struct LightgroupMembership *lgm, char *name) diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index daf4c99845d..f24aec3447b 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -2501,7 +2501,7 @@ static void rna_ViewLayerLightgroup_name_set(PointerRNA *ptr, const char *value) Scene *scene = (Scene *)ptr->owner_id; ViewLayer *view_layer = BKE_view_layer_find_with_lightgroup(scene, lightgroup); - BKE_view_layer_rename_lightgroup(view_layer, lightgroup, value); + BKE_view_layer_rename_lightgroup(scene, view_layer, lightgroup, value); } /* Fake value, used internally (not saved to DNA). */ -- cgit v1.2.3 From 7808ee9bd73d7eb286d52ff5a28592dfffbccf40 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Sun, 24 Jul 2022 20:03:16 -0500 Subject: Geometry Nodes: Improve UV Sphere primive performance In a test producing 10 million vertices I observed a 3.6x improvement, from 470ms to 130ms. The largest improvement comes from calculating each mesh array on a separate thread. Besides that, the larger changes come from splitting the filling of corner and face arrays, and precalculating sines and cosines for each ring. Using `parallel_invoke` does gives some overhead. On a small 32x16 input, the time went up from 51us to 74us. It could be disabled for small outputs in the future. The reasoning for this parallelization method instead of more standard data-size-based parallelism is that the latter wouldn't be helpful except for very high resolution. --- .../nodes/node_geo_mesh_primitive_uv_sphere.cc | 141 +++++++++++++-------- 1 file changed, 89 insertions(+), 52 deletions(-) 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 f78752387c6..a46bb40a3eb 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 @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "BLI_task.hh" + #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" @@ -61,15 +63,26 @@ static int sphere_face_total(const int segments, const int rings) * Also calculate vertex normals here, since the calculation is trivial, and it allows avoiding the * calculation later, if it's necessary. The vertex normals are just the normalized positions. */ -static void calculate_sphere_vertex_data(MutableSpan verts, - MutableSpan vert_normals, - const float radius, - const int segments, - const int rings) +BLI_NOINLINE static void calculate_sphere_vertex_data(MutableSpan verts, + MutableSpan vert_normals, + const float radius, + const int segments, + const int rings) { const float delta_theta = M_PI / rings; const float delta_phi = (2.0f * M_PI) / segments; + Array segment_cosines(segments + 1); + for (const int segment : IndexRange(1, segments)) { + const float phi = segment * delta_phi; + segment_cosines[segment] = std::cos(phi); + } + Array segment_sines(segments + 1); + for (const int segment : IndexRange(1, segments)) { + const float phi = segment * delta_phi; + segment_sines[segment] = std::sin(phi); + } + copy_v3_v3(verts[0].co, float3(0.0f, 0.0f, radius)); vert_normals.first() = float3(0.0f, 0.0f, 1.0f); @@ -79,9 +92,8 @@ static void calculate_sphere_vertex_data(MutableSpan verts, const float sin_theta = std::sin(theta); const float z = std::cos(theta); for (const int segment : IndexRange(1, segments)) { - const float phi = segment * delta_phi; - const float x = sin_theta * std::cos(phi); - const float y = sin_theta * std::sin(phi); + const float x = sin_theta * segment_cosines[segment]; + const float y = sin_theta * segment_sines[segment]; copy_v3_v3(verts[vert_index].co, float3(x, y, z) * radius); vert_normals[vert_index] = float3(x, y, z); vert_index++; @@ -92,9 +104,9 @@ static void calculate_sphere_vertex_data(MutableSpan verts, vert_normals.last() = float3(0.0f, 0.0f, -1.0f); } -static void calculate_sphere_edge_indices(MutableSpan edges, - const int segments, - const int rings) +BLI_NOINLINE static void calculate_sphere_edge_indices(MutableSpan edges, + const int segments, + const int rings) { int edge_index = 0; @@ -142,20 +154,46 @@ static void calculate_sphere_edge_indices(MutableSpan edges, } } -static void calculate_sphere_faces(MutableSpan loops, - MutableSpan polys, - const int segments, - const int rings) +BLI_NOINLINE static void calculate_sphere_faces(MutableSpan polys, const int segments) { int loop_index = 0; - int poly_index = 0; /* Add the triangles connected to the top vertex. */ - const int first_vert_ring_index_start = 1; - for (const int segment : IndexRange(segments)) { - MPoly &poly = polys[poly_index++]; + for (MPoly &poly : polys.take_front(segments)) { poly.loopstart = loop_index; poly.totloop = 3; + loop_index += 3; + } + + /* Add the middle quads. */ + for (MPoly &poly : polys.drop_front(segments).drop_back(segments)) { + poly.loopstart = loop_index; + poly.totloop = 4; + loop_index += 4; + } + + /* Add the triangles connected to the bottom vertex. */ + for (MPoly &poly : polys.take_back(segments)) { + poly.loopstart = loop_index; + poly.totloop = 3; + loop_index += 3; + } +} + +BLI_NOINLINE static void calculate_sphere_corners(MutableSpan loops, + const int segments, + const int rings) +{ + int loop_index = 0; + auto segment_next_or_first = [&](const int segment) { + return segment == segments - 1 ? 0 : segment + 1; + }; + + /* Add the triangles connected to the top vertex. */ + const int first_vert_ring_index_start = 1; + for (const int segment : IndexRange(segments)) { + const int segment_next = segment_next_or_first(segment); + MLoop &loop_a = loops[loop_index++]; loop_a.v = 0; loop_a.e = segment; @@ -163,8 +201,8 @@ static void calculate_sphere_faces(MutableSpan loops, loop_b.v = first_vert_ring_index_start + segment; loop_b.e = segments + segment; MLoop &loop_c = loops[loop_index++]; - loop_c.v = first_vert_ring_index_start + (segment + 1) % segments; - loop_c.e = (segment + 1) % segments; + loop_c.v = first_vert_ring_index_start + segment_next; + loop_c.e = segment_next; } int ring_vert_index_start = 1; @@ -175,9 +213,7 @@ static void calculate_sphere_faces(MutableSpan loops, const int ring_vertical_edge_index_start = ring_edge_index_start + segments; for (const int segment : IndexRange(segments)) { - MPoly &poly = polys[poly_index++]; - poly.loopstart = loop_index; - poly.totloop = 4; + const int segment_next = segment_next_or_first(segment); MLoop &loop_a = loops[loop_index++]; loop_a.v = ring_vert_index_start + segment; @@ -186,10 +222,10 @@ static void calculate_sphere_faces(MutableSpan loops, loop_b.v = next_ring_vert_index_start + segment; loop_b.e = next_ring_edge_index_start + segment; MLoop &loop_c = loops[loop_index++]; - loop_c.v = next_ring_vert_index_start + (segment + 1) % segments; - loop_c.e = ring_vertical_edge_index_start + (segment + 1) % segments; + loop_c.v = next_ring_vert_index_start + segment_next; + loop_c.e = ring_vertical_edge_index_start + segment_next; MLoop &loop_d = loops[loop_index++]; - loop_d.v = ring_vert_index_start + (segment + 1) % segments; + loop_d.v = ring_vert_index_start + segment_next; loop_d.e = ring_edge_index_start + segment; } ring_vert_index_start += segments; @@ -202,15 +238,13 @@ static void calculate_sphere_faces(MutableSpan loops, const int last_vert_index = sphere_vert_total(segments, rings) - 1; const int last_vert_ring_start = last_vert_index - segments; for (const int segment : IndexRange(segments)) { - MPoly &poly = polys[poly_index++]; - poly.loopstart = loop_index; - poly.totloop = 3; + const int segment_next = segment_next_or_first(segment); MLoop &loop_a = loops[loop_index++]; loop_a.v = last_vert_index; - loop_a.e = bottom_edge_fan_start + (segment + 1) % segments; + loop_a.e = bottom_edge_fan_start + segment_next; MLoop &loop_b = loops[loop_index++]; - loop_b.v = last_vert_ring_start + (segment + 1) % segments; + loop_b.v = last_vert_ring_start + segment_next; loop_b.e = last_edge_ring_start + segment; MLoop &loop_c = loops[loop_index++]; loop_c.v = last_vert_ring_start + segment; @@ -218,7 +252,7 @@ static void calculate_sphere_faces(MutableSpan loops, } } -static void calculate_sphere_uvs(Mesh *mesh, const float segments, const float rings) +BLI_NOINLINE static void calculate_sphere_uvs(Mesh *mesh, const float segments, const float rings) { MutableAttributeAccessor attributes = bke::mesh_attributes_for_write(*mesh); @@ -229,29 +263,31 @@ static void calculate_sphere_uvs(Mesh *mesh, const float segments, const float r int loop_index = 0; const float dy = 1.0f / rings; + const float segments_inv = 1.0f / segments; + for (const int i_segment : IndexRange(segments)) { const float segment = static_cast(i_segment); - uvs[loop_index++] = float2((segment + 0.5f) / segments, 0.0f); - uvs[loop_index++] = float2(segment / segments, dy); - uvs[loop_index++] = float2((segment + 1.0f) / segments, dy); + uvs[loop_index++] = float2((segment + 0.5f) * segments_inv, 0.0f); + uvs[loop_index++] = float2(segment * segments_inv, dy); + uvs[loop_index++] = float2((segment + 1.0f) * segments_inv, dy); } for (const int i_ring : IndexRange(1, rings - 2)) { const float ring = static_cast(i_ring); for (const int i_segment : IndexRange(segments)) { const float segment = static_cast(i_segment); - uvs[loop_index++] = float2(segment / segments, ring / rings); - uvs[loop_index++] = float2(segment / segments, (ring + 1.0f) / rings); - uvs[loop_index++] = float2((segment + 1.0f) / segments, (ring + 1.0f) / rings); - uvs[loop_index++] = float2((segment + 1.0f) / segments, ring / rings); + uvs[loop_index++] = float2(segment * segments_inv, ring / rings); + uvs[loop_index++] = float2(segment * segments_inv, (ring + 1.0f) / rings); + uvs[loop_index++] = float2((segment + 1.0f) * segments_inv, (ring + 1.0f) / rings); + uvs[loop_index++] = float2((segment + 1.0f) * segments_inv, ring / rings); } } for (const int i_segment : IndexRange(segments)) { const float segment = static_cast(i_segment); - uvs[loop_index++] = float2((segment + 0.5f) / segments, 1.0f); - uvs[loop_index++] = float2((segment + 1.0f) / segments, 1.0f - dy); - uvs[loop_index++] = float2(segment / segments, 1.0f - dy); + uvs[loop_index++] = float2((segment + 0.5f) * segments_inv, 1.0f); + uvs[loop_index++] = float2((segment + 1.0f) * segments_inv, 1.0f - dy); + uvs[loop_index++] = float2(segment * segments_inv, 1.0f - dy); } uv_attribute.finish(); @@ -270,15 +306,16 @@ static Mesh *create_uv_sphere_mesh(const float radius, const int segments, const MutableSpan edges{mesh->medge, mesh->totedge}; MutableSpan polys{mesh->mpoly, mesh->totpoly}; - MutableSpan vert_normals{(float3 *)BKE_mesh_vertex_normals_for_write(mesh), mesh->totvert}; - calculate_sphere_vertex_data(verts, vert_normals, radius, segments, rings); - BKE_mesh_vertex_normals_clear_dirty(mesh); - - calculate_sphere_edge_indices(edges, segments, rings); - - calculate_sphere_faces(loops, polys, segments, rings); - - calculate_sphere_uvs(mesh, segments, rings); + threading::parallel_invoke( + [&]() { + MutableSpan vert_normals{(float3 *)BKE_mesh_vertex_normals_for_write(mesh), mesh->totvert}; + calculate_sphere_vertex_data(verts, vert_normals, radius, segments, rings); + BKE_mesh_vertex_normals_clear_dirty(mesh); + }, + [&]() { calculate_sphere_edge_indices(edges, segments, rings); }, + [&]() { calculate_sphere_faces(polys, segments); }, + [&]() { calculate_sphere_corners(loops, segments, rings); }, + [&]() { calculate_sphere_uvs(mesh, segments, rings); }); return mesh; } -- cgit v1.2.3 From 44258b5ad0737140d7d1026cc540e3f94ea05566 Mon Sep 17 00:00:00 2001 From: Alex Parker Date: Mon, 25 Jul 2022 07:56:17 +0200 Subject: Undo: Improve image undo performance When texture painting a lot of time is spent in ED_image_paint_tile_find. This fixes stores the PaintTiles in a blender::Map making ED_image_paint_tile_find an O(1) rather than O(n) operation. When using threading the locking should happen during read as well, still this gives a boost in performance as the read is now much faster. Reviewed By: jbakker Maniphest Tasks: T99546 Differential Revision: https://developer.blender.org/D15415 --- source/blender/editors/include/ED_paint.h | 7 +- source/blender/editors/sculpt_paint/paint_image.cc | 2 +- .../blender/editors/sculpt_paint/paint_image_2d.c | 2 +- .../editors/sculpt_paint/paint_image_proj.c | 2 +- .../editors/sculpt_paint/sculpt_paint_image.cc | 2 +- source/blender/editors/space_image/CMakeLists.txt | 2 +- source/blender/editors/space_image/image_undo.c | 1093 ------------------- source/blender/editors/space_image/image_undo.cc | 1133 ++++++++++++++++++++ 8 files changed, 1142 insertions(+), 1101 deletions(-) delete mode 100644 source/blender/editors/space_image/image_undo.c create mode 100644 source/blender/editors/space_image/image_undo.cc diff --git a/source/blender/editors/include/ED_paint.h b/source/blender/editors/include/ED_paint.h index ba5834fd508..048424cdee1 100644 --- a/source/blender/editors/include/ED_paint.h +++ b/source/blender/editors/include/ED_paint.h @@ -22,6 +22,7 @@ struct UndoType; struct bContext; struct wmKeyConfig; struct wmOperator; +typedef struct PaintTileMap PaintTileMap; /* paint_ops.c */ @@ -76,7 +77,7 @@ void ED_image_undo_restore(struct UndoStep *us); /** Export for ED_undo_sys. */ void ED_image_undosys_type(struct UndoType *ut); -void *ED_image_paint_tile_find(struct ListBase *paint_tiles, +void *ED_image_paint_tile_find(PaintTileMap *paint_tile_map, struct Image *image, struct ImBuf *ibuf, struct ImageUser *iuser, @@ -84,7 +85,7 @@ void *ED_image_paint_tile_find(struct ListBase *paint_tiles, int y_tile, unsigned short **r_mask, bool validate); -void *ED_image_paint_tile_push(struct ListBase *paint_tiles, +void *ED_image_paint_tile_push(PaintTileMap *paint_tile_map, struct Image *image, struct ImBuf *ibuf, struct ImBuf **tmpibuf, @@ -98,7 +99,7 @@ void *ED_image_paint_tile_push(struct ListBase *paint_tiles, void ED_image_paint_tile_lock_init(void); void ED_image_paint_tile_lock_end(void); -struct ListBase *ED_image_paint_tile_list_get(void); +struct PaintTileMap *ED_image_paint_tile_map_get(void); #define ED_IMAGE_UNDO_TILE_BITS 6 #define ED_IMAGE_UNDO_TILE_SIZE (1 << ED_IMAGE_UNDO_TILE_BITS) diff --git a/source/blender/editors/sculpt_paint/paint_image.cc b/source/blender/editors/sculpt_paint/paint_image.cc index 6ed43fe6403..24290fed323 100644 --- a/source/blender/editors/sculpt_paint/paint_image.cc +++ b/source/blender/editors/sculpt_paint/paint_image.cc @@ -125,7 +125,7 @@ void ED_imapaint_dirty_region( imapaint_region_tiles(ibuf, x, y, w, h, &tilex, &tiley, &tilew, &tileh); - ListBase *undo_tiles = ED_image_paint_tile_list_get(); + PaintTileMap *undo_tiles = ED_image_paint_tile_map_get(); for (ty = tiley; ty <= tileh; ty++) { for (tx = tilex; tx <= tilew; tx++) { diff --git a/source/blender/editors/sculpt_paint/paint_image_2d.c b/source/blender/editors/sculpt_paint/paint_image_2d.c index fae2e6863fa..5df65e596b9 100644 --- a/source/blender/editors/sculpt_paint/paint_image_2d.c +++ b/source/blender/editors/sculpt_paint/paint_image_2d.c @@ -1196,7 +1196,7 @@ static void paint_2d_do_making_brush(ImagePaintState *s, ImBuf tmpbuf; IMB_initImBuf(&tmpbuf, ED_IMAGE_UNDO_TILE_SIZE, ED_IMAGE_UNDO_TILE_SIZE, 32, 0); - ListBase *undo_tiles = ED_image_paint_tile_list_get(); + PaintTileMap *undo_tiles = ED_image_paint_tile_map_get(); for (int ty = tiley; ty <= tileh; ty++) { for (int tx = tilex; tx <= tilew; tx++) { diff --git a/source/blender/editors/sculpt_paint/paint_image_proj.c b/source/blender/editors/sculpt_paint/paint_image_proj.c index 50480b8aef0..9449cc6eb8d 100644 --- a/source/blender/editors/sculpt_paint/paint_image_proj.c +++ b/source/blender/editors/sculpt_paint/paint_image_proj.c @@ -1813,7 +1813,7 @@ static int project_paint_undo_subtiles(const TileInfo *tinf, int tx, int ty) } if (generate_tile) { - ListBase *undo_tiles = ED_image_paint_tile_list_get(); + struct PaintTileMap *undo_tiles = ED_image_paint_tile_map_get(); volatile void *undorect; if (tinf->masked) { undorect = ED_image_paint_tile_push(undo_tiles, diff --git a/source/blender/editors/sculpt_paint/sculpt_paint_image.cc b/source/blender/editors/sculpt_paint/sculpt_paint_image.cc index 975a8f21aaf..f51a603ee5d 100644 --- a/source/blender/editors/sculpt_paint/sculpt_paint_image.cc +++ b/source/blender/editors/sculpt_paint/sculpt_paint_image.cc @@ -383,7 +383,7 @@ static void push_undo(const NodeData &node_data, continue; } int tilex, tiley, tilew, tileh; - ListBase *undo_tiles = ED_image_paint_tile_list_get(); + PaintTileMap *undo_tiles = ED_image_paint_tile_map_get(); undo_region_tiles(&image_buffer, tile_undo.region.xmin, tile_undo.region.ymin, diff --git a/source/blender/editors/space_image/CMakeLists.txt b/source/blender/editors/space_image/CMakeLists.txt index 39fb41245bf..c6a1a6a77b4 100644 --- a/source/blender/editors/space_image/CMakeLists.txt +++ b/source/blender/editors/space_image/CMakeLists.txt @@ -28,7 +28,7 @@ set(SRC image_edit.c image_ops.c image_sequence.c - image_undo.c + image_undo.cc space_image.c image_intern.h diff --git a/source/blender/editors/space_image/image_undo.c b/source/blender/editors/space_image/image_undo.c deleted file mode 100644 index a7a8bde1115..00000000000 --- a/source/blender/editors/space_image/image_undo.c +++ /dev/null @@ -1,1093 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup spimage - * - * Overview - * ======== - * - * - Each undo step is a #ImageUndoStep - * - Each #ImageUndoStep stores a list of #UndoImageHandle - * - Each #UndoImageHandle stores a list of #UndoImageBuf - * (this is the undo systems equivalent of an #ImBuf). - * - Each #UndoImageBuf stores an array of #UndoImageTile - * The tiles are shared between #UndoImageBuf's to avoid duplication. - * - * When the undo system manages an image, there will always be a full copy (as a #UndoImageBuf) - * each new undo step only stores modified tiles. - */ - -#include "CLG_log.h" - -#include "MEM_guardedalloc.h" - -#include "BLI_blenlib.h" -#include "BLI_math.h" -#include "BLI_threads.h" -#include "BLI_utildefines.h" - -#include "DNA_image_types.h" -#include "DNA_object_types.h" -#include "DNA_screen_types.h" -#include "DNA_space_types.h" -#include "DNA_windowmanager_types.h" - -#include "IMB_imbuf.h" -#include "IMB_imbuf_types.h" - -#include "BKE_context.h" -#include "BKE_image.h" -#include "BKE_paint.h" -#include "BKE_undo_system.h" - -#include "DEG_depsgraph.h" - -#include "ED_object.h" -#include "ED_paint.h" -#include "ED_undo.h" -#include "ED_util.h" - -#include "WM_api.h" - -static CLG_LogRef LOG = {"ed.image.undo"}; - -/* -------------------------------------------------------------------- */ -/** \name Thread Locking - * \{ */ - -/* This is a non-global static resource, - * Maybe it should be exposed as part of the - * paint operation, but for now just give a public interface */ -static SpinLock paint_tiles_lock; - -void ED_image_paint_tile_lock_init(void) -{ - BLI_spin_init(&paint_tiles_lock); -} - -void ED_image_paint_tile_lock_end(void) -{ - BLI_spin_end(&paint_tiles_lock); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Paint Tiles - * - * Created on demand while painting, - * use to access the previous state for some paint operations. - * - * These buffers are also used for undo when available. - * - * \{ */ - -static ImBuf *imbuf_alloc_temp_tile(void) -{ - return IMB_allocImBuf( - ED_IMAGE_UNDO_TILE_SIZE, ED_IMAGE_UNDO_TILE_SIZE, 32, IB_rectfloat | IB_rect); -} - -typedef struct PaintTile { - struct PaintTile *next, *prev; - Image *image; - ImBuf *ibuf; - /* For 2D image painting the ImageUser uses most of the values. - * Even though views and passes are stored they are currently not supported for painting. - * For 3D projection painting this only uses a tile & frame number. - * The scene pointer must be cleared (or temporarily set it as needed, but leave cleared). */ - ImageUser iuser; - union { - float *fp; - uint *uint; - void *pt; - } rect; - ushort *mask; - bool valid; - bool use_float; - int x_tile, y_tile; -} PaintTile; - -static void ptile_free(PaintTile *ptile) -{ - if (ptile->rect.pt) { - MEM_freeN(ptile->rect.pt); - } - if (ptile->mask) { - MEM_freeN(ptile->mask); - } - MEM_freeN(ptile); -} - -static void ptile_free_list(ListBase *paint_tiles) -{ - for (PaintTile *ptile = paint_tiles->first, *ptile_next; ptile; ptile = ptile_next) { - ptile_next = ptile->next; - ptile_free(ptile); - } - BLI_listbase_clear(paint_tiles); -} - -static void ptile_invalidate_list(ListBase *paint_tiles) -{ - LISTBASE_FOREACH (PaintTile *, ptile, paint_tiles) { - ptile->valid = false; - } -} - -void *ED_image_paint_tile_find(ListBase *paint_tiles, - Image *image, - ImBuf *ibuf, - ImageUser *iuser, - int x_tile, - int y_tile, - ushort **r_mask, - bool validate) -{ - LISTBASE_FOREACH (PaintTile *, ptile, paint_tiles) { - if (ptile->x_tile == x_tile && ptile->y_tile == y_tile) { - if (ptile->image == image && ptile->ibuf == ibuf && ptile->iuser.tile == iuser->tile) { - if (r_mask) { - /* allocate mask if requested. */ - if (!ptile->mask) { - ptile->mask = MEM_callocN(sizeof(ushort) * square_i(ED_IMAGE_UNDO_TILE_SIZE), - "UndoImageTile.mask"); - } - *r_mask = ptile->mask; - } - if (validate) { - ptile->valid = true; - } - return ptile->rect.pt; - } - } - } - return NULL; -} - -void *ED_image_paint_tile_push(ListBase *paint_tiles, - Image *image, - ImBuf *ibuf, - ImBuf **tmpibuf, - ImageUser *iuser, - int x_tile, - int y_tile, - ushort **r_mask, - bool **r_valid, - bool use_thread_lock, - bool find_prev) -{ - const bool has_float = (ibuf->rect_float != NULL); - - /* check if tile is already pushed */ - - /* in projective painting we keep accounting of tiles, so if we need one pushed, just push! */ - if (find_prev) { - void *data = ED_image_paint_tile_find( - paint_tiles, image, ibuf, iuser, x_tile, y_tile, r_mask, true); - if (data) { - return data; - } - } - - if (*tmpibuf == NULL) { - *tmpibuf = imbuf_alloc_temp_tile(); - } - - PaintTile *ptile = MEM_callocN(sizeof(PaintTile), "PaintTile"); - - ptile->image = image; - ptile->ibuf = ibuf; - ptile->iuser = *iuser; - ptile->iuser.scene = NULL; - - ptile->x_tile = x_tile; - ptile->y_tile = y_tile; - - /* add mask explicitly here */ - if (r_mask) { - *r_mask = ptile->mask = MEM_callocN(sizeof(ushort) * square_i(ED_IMAGE_UNDO_TILE_SIZE), - "PaintTile.mask"); - } - - ptile->rect.pt = MEM_callocN((ibuf->rect_float ? sizeof(float[4]) : sizeof(char[4])) * - square_i(ED_IMAGE_UNDO_TILE_SIZE), - "PaintTile.rect"); - - ptile->use_float = has_float; - ptile->valid = true; - - if (r_valid) { - *r_valid = &ptile->valid; - } - - IMB_rectcpy(*tmpibuf, - ibuf, - 0, - 0, - x_tile * ED_IMAGE_UNDO_TILE_SIZE, - y_tile * ED_IMAGE_UNDO_TILE_SIZE, - ED_IMAGE_UNDO_TILE_SIZE, - ED_IMAGE_UNDO_TILE_SIZE); - - if (has_float) { - SWAP(float *, ptile->rect.fp, (*tmpibuf)->rect_float); - } - else { - SWAP(uint *, ptile->rect.uint, (*tmpibuf)->rect); - } - - if (use_thread_lock) { - BLI_spin_lock(&paint_tiles_lock); - } - BLI_addtail(paint_tiles, ptile); - - if (use_thread_lock) { - BLI_spin_unlock(&paint_tiles_lock); - } - return ptile->rect.pt; -} - -static void ptile_restore_runtime_list(ListBase *paint_tiles) -{ - ImBuf *tmpibuf = imbuf_alloc_temp_tile(); - - LISTBASE_FOREACH (PaintTile *, ptile, paint_tiles) { - Image *image = ptile->image; - ImBuf *ibuf = BKE_image_acquire_ibuf(image, &ptile->iuser, NULL); - const bool has_float = (ibuf->rect_float != NULL); - - if (has_float) { - SWAP(float *, ptile->rect.fp, tmpibuf->rect_float); - } - else { - SWAP(uint *, ptile->rect.uint, tmpibuf->rect); - } - - IMB_rectcpy(ibuf, - tmpibuf, - ptile->x_tile * ED_IMAGE_UNDO_TILE_SIZE, - ptile->y_tile * ED_IMAGE_UNDO_TILE_SIZE, - 0, - 0, - ED_IMAGE_UNDO_TILE_SIZE, - ED_IMAGE_UNDO_TILE_SIZE); - - if (has_float) { - SWAP(float *, ptile->rect.fp, tmpibuf->rect_float); - } - else { - SWAP(uint *, ptile->rect.uint, tmpibuf->rect); - } - - /* Force OpenGL reload (maybe partial update will operate better?) */ - BKE_image_free_gputextures(image); - - if (ibuf->rect_float) { - ibuf->userflags |= IB_RECT_INVALID; /* force recreate of char rect */ - } - if (ibuf->mipmap[0]) { - ibuf->userflags |= IB_MIPMAP_INVALID; /* Force MIP-MAP recreation. */ - } - ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID; - - BKE_image_release_ibuf(image, ibuf, NULL); - } - - IMB_freeImBuf(tmpibuf); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Image Undo Tile - * \{ */ - -static uint index_from_xy(uint tile_x, uint tile_y, const uint tiles_dims[2]) -{ - BLI_assert(tile_x < tiles_dims[0] && tile_y < tiles_dims[1]); - return (tile_y * tiles_dims[0]) + tile_x; -} - -typedef struct UndoImageTile { - union { - float *fp; - uint *uint; - void *pt; - } rect; - int users; -} UndoImageTile; - -static UndoImageTile *utile_alloc(bool has_float) -{ - UndoImageTile *utile = MEM_callocN(sizeof(*utile), "ImageUndoTile"); - if (has_float) { - utile->rect.fp = MEM_mallocN(sizeof(float[4]) * square_i(ED_IMAGE_UNDO_TILE_SIZE), __func__); - } - else { - utile->rect.uint = MEM_mallocN(sizeof(uint) * square_i(ED_IMAGE_UNDO_TILE_SIZE), __func__); - } - return utile; -} - -static void utile_init_from_imbuf( - UndoImageTile *utile, const uint x, const uint y, const ImBuf *ibuf, ImBuf *tmpibuf) -{ - const bool has_float = ibuf->rect_float; - - if (has_float) { - SWAP(float *, utile->rect.fp, tmpibuf->rect_float); - } - else { - SWAP(uint *, utile->rect.uint, tmpibuf->rect); - } - - IMB_rectcpy(tmpibuf, ibuf, 0, 0, x, y, ED_IMAGE_UNDO_TILE_SIZE, ED_IMAGE_UNDO_TILE_SIZE); - - if (has_float) { - SWAP(float *, utile->rect.fp, tmpibuf->rect_float); - } - else { - SWAP(uint *, utile->rect.uint, tmpibuf->rect); - } -} - -static void utile_restore( - const UndoImageTile *utile, const uint x, const uint y, ImBuf *ibuf, ImBuf *tmpibuf) -{ - const bool has_float = ibuf->rect_float; - float *prev_rect_float = tmpibuf->rect_float; - uint *prev_rect = tmpibuf->rect; - - if (has_float) { - tmpibuf->rect_float = utile->rect.fp; - } - else { - tmpibuf->rect = utile->rect.uint; - } - - IMB_rectcpy(ibuf, tmpibuf, x, y, 0, 0, ED_IMAGE_UNDO_TILE_SIZE, ED_IMAGE_UNDO_TILE_SIZE); - - tmpibuf->rect_float = prev_rect_float; - tmpibuf->rect = prev_rect; -} - -static void utile_decref(UndoImageTile *utile) -{ - utile->users -= 1; - BLI_assert(utile->users >= 0); - if (utile->users == 0) { - MEM_freeN(utile->rect.pt); - MEM_freeN(utile); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Image Undo Buffer - * \{ */ - -typedef struct UndoImageBuf { - struct UndoImageBuf *next, *prev; - - /** - * The buffer after the undo step has executed. - */ - struct UndoImageBuf *post; - - char ibuf_name[IMB_FILENAME_SIZE]; - - UndoImageTile **tiles; - - /** Can calculate these from dims, just for convenience. */ - uint tiles_len; - uint tiles_dims[2]; - - uint image_dims[2]; - - /** Store variables from the image. */ - struct { - short source; - bool use_float; - char gen_type; - } image_state; - -} UndoImageBuf; - -static UndoImageBuf *ubuf_from_image_no_tiles(Image *image, const ImBuf *ibuf) -{ - UndoImageBuf *ubuf = MEM_callocN(sizeof(*ubuf), __func__); - - ubuf->image_dims[0] = ibuf->x; - ubuf->image_dims[1] = ibuf->y; - - ubuf->tiles_dims[0] = ED_IMAGE_UNDO_TILE_NUMBER(ubuf->image_dims[0]); - ubuf->tiles_dims[1] = ED_IMAGE_UNDO_TILE_NUMBER(ubuf->image_dims[1]); - - ubuf->tiles_len = ubuf->tiles_dims[0] * ubuf->tiles_dims[1]; - ubuf->tiles = MEM_callocN(sizeof(*ubuf->tiles) * ubuf->tiles_len, __func__); - - BLI_strncpy(ubuf->ibuf_name, ibuf->name, sizeof(ubuf->ibuf_name)); - ubuf->image_state.gen_type = image->gen_type; - ubuf->image_state.source = image->source; - ubuf->image_state.use_float = ibuf->rect_float != NULL; - - return ubuf; -} - -static void ubuf_from_image_all_tiles(UndoImageBuf *ubuf, const ImBuf *ibuf) -{ - ImBuf *tmpibuf = imbuf_alloc_temp_tile(); - - const bool has_float = ibuf->rect_float; - int i = 0; - for (uint y_tile = 0; y_tile < ubuf->tiles_dims[1]; y_tile += 1) { - uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS; - for (uint x_tile = 0; x_tile < ubuf->tiles_dims[0]; x_tile += 1) { - uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS; - - BLI_assert(ubuf->tiles[i] == NULL); - UndoImageTile *utile = utile_alloc(has_float); - utile->users = 1; - utile_init_from_imbuf(utile, x, y, ibuf, tmpibuf); - ubuf->tiles[i] = utile; - - i += 1; - } - } - - BLI_assert(i == ubuf->tiles_len); - - IMB_freeImBuf(tmpibuf); -} - -/** Ensure we can copy the ubuf into the ibuf. */ -static void ubuf_ensure_compat_ibuf(const UndoImageBuf *ubuf, ImBuf *ibuf) -{ - /* We could have both float and rect buffers, - * in this case free the float buffer if it's unused. */ - if ((ibuf->rect_float != NULL) && (ubuf->image_state.use_float == false)) { - imb_freerectfloatImBuf(ibuf); - } - - if (ibuf->x == ubuf->image_dims[0] && ibuf->y == ubuf->image_dims[1] && - (ubuf->image_state.use_float ? (void *)ibuf->rect_float : (void *)ibuf->rect)) { - return; - } - - imb_freerectImbuf_all(ibuf); - IMB_rect_size_set(ibuf, ubuf->image_dims); - - if (ubuf->image_state.use_float) { - imb_addrectfloatImBuf(ibuf); - } - else { - imb_addrectImBuf(ibuf); - } -} - -static void ubuf_free(UndoImageBuf *ubuf) -{ - UndoImageBuf *ubuf_post = ubuf->post; - for (uint i = 0; i < ubuf->tiles_len; i++) { - UndoImageTile *utile = ubuf->tiles[i]; - utile_decref(utile); - } - MEM_freeN(ubuf->tiles); - MEM_freeN(ubuf); - if (ubuf_post) { - ubuf_free(ubuf_post); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Image Undo Handle - * \{ */ - -typedef struct UndoImageHandle { - struct UndoImageHandle *next, *prev; - - /** Each undo handle refers to a single image which may have multiple buffers. */ - UndoRefID_Image image_ref; - - /** Each tile of a tiled image has its own UndoImageHandle. - * The tile number of this IUser is used to distinguish them. - */ - ImageUser iuser; - - /** - * List of #UndoImageBuf's to support multiple buffers per image. - */ - ListBase buffers; - -} UndoImageHandle; - -static void uhandle_restore_list(ListBase *undo_handles, bool use_init) -{ - ImBuf *tmpibuf = imbuf_alloc_temp_tile(); - - LISTBASE_FOREACH (UndoImageHandle *, uh, undo_handles) { - /* Tiles only added to second set of tiles. */ - Image *image = uh->image_ref.ptr; - - ImBuf *ibuf = BKE_image_acquire_ibuf(image, &uh->iuser, NULL); - if (UNLIKELY(ibuf == NULL)) { - CLOG_ERROR(&LOG, "Unable to get buffer for image '%s'", image->id.name + 2); - continue; - } - bool changed = false; - LISTBASE_FOREACH (UndoImageBuf *, ubuf_iter, &uh->buffers) { - UndoImageBuf *ubuf = use_init ? ubuf_iter : ubuf_iter->post; - ubuf_ensure_compat_ibuf(ubuf, ibuf); - - int i = 0; - for (uint y_tile = 0; y_tile < ubuf->tiles_dims[1]; y_tile += 1) { - uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS; - for (uint x_tile = 0; x_tile < ubuf->tiles_dims[0]; x_tile += 1) { - uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS; - utile_restore(ubuf->tiles[i], x, y, ibuf, tmpibuf); - changed = true; - i += 1; - } - } - } - - if (changed) { - BKE_image_mark_dirty(image, ibuf); - /* TODO(jbakker): only mark areas that are actually updated to improve performance. */ - BKE_image_partial_update_mark_full_update(image); - - if (ibuf->rect_float) { - ibuf->userflags |= IB_RECT_INVALID; /* force recreate of char rect */ - } - if (ibuf->mipmap[0]) { - ibuf->userflags |= IB_MIPMAP_INVALID; /* force mip-map recreation. */ - } - ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID; - - DEG_id_tag_update(&image->id, 0); - } - BKE_image_release_ibuf(image, ibuf, NULL); - } - - IMB_freeImBuf(tmpibuf); -} - -static void uhandle_free_list(ListBase *undo_handles) -{ - LISTBASE_FOREACH_MUTABLE (UndoImageHandle *, uh, undo_handles) { - LISTBASE_FOREACH_MUTABLE (UndoImageBuf *, ubuf, &uh->buffers) { - ubuf_free(ubuf); - } - MEM_freeN(uh); - } - BLI_listbase_clear(undo_handles); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Image Undo Internal Utilities - * \{ */ - -/** #UndoImageHandle utilities */ - -static UndoImageBuf *uhandle_lookup_ubuf(UndoImageHandle *uh, - const Image *UNUSED(image), - const char *ibuf_name) -{ - LISTBASE_FOREACH (UndoImageBuf *, ubuf, &uh->buffers) { - if (STREQ(ubuf->ibuf_name, ibuf_name)) { - return ubuf; - } - } - return NULL; -} - -static UndoImageBuf *uhandle_add_ubuf(UndoImageHandle *uh, Image *image, ImBuf *ibuf) -{ - BLI_assert(uhandle_lookup_ubuf(uh, image, ibuf->name) == NULL); - UndoImageBuf *ubuf = ubuf_from_image_no_tiles(image, ibuf); - BLI_addtail(&uh->buffers, ubuf); - - ubuf->post = NULL; - - return ubuf; -} - -static UndoImageBuf *uhandle_ensure_ubuf(UndoImageHandle *uh, Image *image, ImBuf *ibuf) -{ - UndoImageBuf *ubuf = uhandle_lookup_ubuf(uh, image, ibuf->name); - if (ubuf == NULL) { - ubuf = uhandle_add_ubuf(uh, image, ibuf); - } - return ubuf; -} - -static UndoImageHandle *uhandle_lookup_by_name(ListBase *undo_handles, - const Image *image, - int tile_number) -{ - LISTBASE_FOREACH (UndoImageHandle *, uh, undo_handles) { - if (STREQ(image->id.name + 2, uh->image_ref.name + 2) && uh->iuser.tile == tile_number) { - return uh; - } - } - return NULL; -} - -static UndoImageHandle *uhandle_lookup(ListBase *undo_handles, const Image *image, int tile_number) -{ - LISTBASE_FOREACH (UndoImageHandle *, uh, undo_handles) { - if (image == uh->image_ref.ptr && uh->iuser.tile == tile_number) { - return uh; - } - } - return NULL; -} - -static UndoImageHandle *uhandle_add(ListBase *undo_handles, Image *image, ImageUser *iuser) -{ - BLI_assert(uhandle_lookup(undo_handles, image, iuser->tile) == NULL); - UndoImageHandle *uh = MEM_callocN(sizeof(*uh), __func__); - uh->image_ref.ptr = image; - uh->iuser = *iuser; - uh->iuser.scene = NULL; - BLI_addtail(undo_handles, uh); - return uh; -} - -static UndoImageHandle *uhandle_ensure(ListBase *undo_handles, Image *image, ImageUser *iuser) -{ - UndoImageHandle *uh = uhandle_lookup(undo_handles, image, iuser->tile); - if (uh == NULL) { - uh = uhandle_add(undo_handles, image, iuser); - } - return uh; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Implements ED Undo System - * \{ */ - -typedef struct ImageUndoStep { - UndoStep step; - - /** #UndoImageHandle */ - ListBase handles; - - /** - * #PaintTile - * Run-time only data (active during a paint stroke). - */ - ListBase paint_tiles; - - bool is_encode_init; - ePaintMode paint_mode; - -} ImageUndoStep; - -/** - * Find the previous undo buffer from this one. - * \note We could look into undo steps even further back. - */ -static UndoImageBuf *ubuf_lookup_from_reference(ImageUndoStep *us_prev, - const Image *image, - int tile_number, - const UndoImageBuf *ubuf) -{ - /* Use name lookup because the pointer is cleared for previous steps. */ - UndoImageHandle *uh_prev = uhandle_lookup_by_name(&us_prev->handles, image, tile_number); - if (uh_prev != NULL) { - UndoImageBuf *ubuf_reference = uhandle_lookup_ubuf(uh_prev, image, ubuf->ibuf_name); - if (ubuf_reference) { - ubuf_reference = ubuf_reference->post; - if ((ubuf_reference->image_dims[0] == ubuf->image_dims[0]) && - (ubuf_reference->image_dims[1] == ubuf->image_dims[1])) { - return ubuf_reference; - } - } - } - return NULL; -} - -static bool image_undosys_poll(bContext *C) -{ - Object *obact = CTX_data_active_object(C); - - ScrArea *area = CTX_wm_area(C); - if (area && (area->spacetype == SPACE_IMAGE)) { - SpaceImage *sima = (SpaceImage *)area->spacedata.first; - if ((obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) || (sima->mode == SI_MODE_PAINT)) { - return true; - } - } - else { - if (obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) { - return true; - } - } - return false; -} - -static void image_undosys_step_encode_init(struct bContext *UNUSED(C), UndoStep *us_p) -{ - ImageUndoStep *us = (ImageUndoStep *)us_p; - /* dummy, memory is cleared anyway. */ - us->is_encode_init = true; - BLI_listbase_clear(&us->handles); - BLI_listbase_clear(&us->paint_tiles); -} - -static bool image_undosys_step_encode(struct bContext *C, - struct Main *UNUSED(bmain), - UndoStep *us_p) -{ - /* Encoding is done along the way by adding tiles - * to the current 'ImageUndoStep' added by encode_init. - * - * This function ensures there are previous and current states of the image in the undo buffer. - */ - ImageUndoStep *us = (ImageUndoStep *)us_p; - - BLI_assert(us->step.data_size == 0); - - if (us->is_encode_init) { - - ImBuf *tmpibuf = imbuf_alloc_temp_tile(); - - ImageUndoStep *us_reference = (ImageUndoStep *)ED_undo_stack_get()->step_active; - while (us_reference && us_reference->step.type != BKE_UNDOSYS_TYPE_IMAGE) { - us_reference = (ImageUndoStep *)us_reference->step.prev; - } - - /* Initialize undo tiles from ptiles (if they exist). */ - for (PaintTile *ptile = us->paint_tiles.first, *ptile_next; ptile; ptile = ptile_next) { - if (ptile->valid) { - UndoImageHandle *uh = uhandle_ensure(&us->handles, ptile->image, &ptile->iuser); - UndoImageBuf *ubuf_pre = uhandle_ensure_ubuf(uh, ptile->image, ptile->ibuf); - - UndoImageTile *utile = MEM_callocN(sizeof(*utile), "UndoImageTile"); - utile->users = 1; - utile->rect.pt = ptile->rect.pt; - ptile->rect.pt = NULL; - const uint tile_index = index_from_xy(ptile->x_tile, ptile->y_tile, ubuf_pre->tiles_dims); - - BLI_assert(ubuf_pre->tiles[tile_index] == NULL); - ubuf_pre->tiles[tile_index] = utile; - } - ptile_next = ptile->next; - ptile_free(ptile); - } - BLI_listbase_clear(&us->paint_tiles); - - LISTBASE_FOREACH (UndoImageHandle *, uh, &us->handles) { - LISTBASE_FOREACH (UndoImageBuf *, ubuf_pre, &uh->buffers) { - - ImBuf *ibuf = BKE_image_acquire_ibuf(uh->image_ref.ptr, &uh->iuser, NULL); - - const bool has_float = ibuf->rect_float; - - BLI_assert(ubuf_pre->post == NULL); - ubuf_pre->post = ubuf_from_image_no_tiles(uh->image_ref.ptr, ibuf); - UndoImageBuf *ubuf_post = ubuf_pre->post; - - if (ubuf_pre->image_dims[0] != ubuf_post->image_dims[0] || - ubuf_pre->image_dims[1] != ubuf_post->image_dims[1]) { - ubuf_from_image_all_tiles(ubuf_post, ibuf); - } - else { - /* Search for the previous buffer. */ - UndoImageBuf *ubuf_reference = - (us_reference ? ubuf_lookup_from_reference( - us_reference, uh->image_ref.ptr, uh->iuser.tile, ubuf_post) : - NULL); - - int i = 0; - for (uint y_tile = 0; y_tile < ubuf_pre->tiles_dims[1]; y_tile += 1) { - uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS; - for (uint x_tile = 0; x_tile < ubuf_pre->tiles_dims[0]; x_tile += 1) { - uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS; - - if ((ubuf_reference != NULL) && ((ubuf_pre->tiles[i] == NULL) || - /* In this case the paint stroke as has added a tile - * which we have a duplicate reference available. */ - (ubuf_pre->tiles[i]->users == 1))) { - if (ubuf_pre->tiles[i] != NULL) { - /* If we have a reference, re-use this single use tile for the post state. */ - BLI_assert(ubuf_pre->tiles[i]->users == 1); - ubuf_post->tiles[i] = ubuf_pre->tiles[i]; - ubuf_pre->tiles[i] = NULL; - utile_init_from_imbuf(ubuf_post->tiles[i], x, y, ibuf, tmpibuf); - } - else { - BLI_assert(ubuf_post->tiles[i] == NULL); - ubuf_post->tiles[i] = ubuf_reference->tiles[i]; - ubuf_post->tiles[i]->users += 1; - } - BLI_assert(ubuf_pre->tiles[i] == NULL); - ubuf_pre->tiles[i] = ubuf_reference->tiles[i]; - ubuf_pre->tiles[i]->users += 1; - - BLI_assert(ubuf_pre->tiles[i] != NULL); - BLI_assert(ubuf_post->tiles[i] != NULL); - } - else { - UndoImageTile *utile = utile_alloc(has_float); - utile_init_from_imbuf(utile, x, y, ibuf, tmpibuf); - - if (ubuf_pre->tiles[i] != NULL) { - ubuf_post->tiles[i] = utile; - utile->users = 1; - } - else { - ubuf_pre->tiles[i] = utile; - ubuf_post->tiles[i] = utile; - utile->users = 2; - } - } - BLI_assert(ubuf_pre->tiles[i] != NULL); - BLI_assert(ubuf_post->tiles[i] != NULL); - i += 1; - } - } - BLI_assert(i == ubuf_pre->tiles_len); - BLI_assert(i == ubuf_post->tiles_len); - } - BKE_image_release_ibuf(uh->image_ref.ptr, ibuf, NULL); - } - } - - IMB_freeImBuf(tmpibuf); - - /* Useful to debug tiles are stored correctly. */ - if (false) { - uhandle_restore_list(&us->handles, false); - } - } - else { - BLI_assert(C != NULL); - /* Happens when switching modes. */ - ePaintMode paint_mode = BKE_paintmode_get_active_from_context(C); - BLI_assert(ELEM(paint_mode, PAINT_MODE_TEXTURE_2D, PAINT_MODE_TEXTURE_3D)); - us->paint_mode = paint_mode; - } - - us_p->is_applied = true; - - return true; -} - -static void image_undosys_step_decode_undo_impl(ImageUndoStep *us, bool is_final) -{ - BLI_assert(us->step.is_applied == true); - uhandle_restore_list(&us->handles, !is_final); - us->step.is_applied = false; -} - -static void image_undosys_step_decode_redo_impl(ImageUndoStep *us) -{ - BLI_assert(us->step.is_applied == false); - uhandle_restore_list(&us->handles, false); - us->step.is_applied = true; -} - -static void image_undosys_step_decode_undo(ImageUndoStep *us, bool is_final) -{ - /* Walk forward over any applied steps of same type, - * then walk back in the next loop, un-applying them. */ - ImageUndoStep *us_iter = us; - while (us_iter->step.next && (us_iter->step.next->type == us_iter->step.type)) { - if (us_iter->step.next->is_applied == false) { - break; - } - us_iter = (ImageUndoStep *)us_iter->step.next; - } - while (us_iter != us || (!is_final && us_iter == us)) { - BLI_assert(us_iter->step.type == us->step.type); /* Previous loop ensures this. */ - image_undosys_step_decode_undo_impl(us_iter, is_final); - if (us_iter == us) { - break; - } - us_iter = (ImageUndoStep *)us_iter->step.prev; - } -} - -static void image_undosys_step_decode_redo(ImageUndoStep *us) -{ - ImageUndoStep *us_iter = us; - while (us_iter->step.prev && (us_iter->step.prev->type == us_iter->step.type)) { - if (us_iter->step.prev->is_applied == true) { - break; - } - us_iter = (ImageUndoStep *)us_iter->step.prev; - } - while (us_iter && (us_iter->step.is_applied == false)) { - image_undosys_step_decode_redo_impl(us_iter); - if (us_iter == us) { - break; - } - us_iter = (ImageUndoStep *)us_iter->step.next; - } -} - -static void image_undosys_step_decode( - struct bContext *C, struct Main *bmain, UndoStep *us_p, const eUndoStepDir dir, bool is_final) -{ - /* NOTE: behavior for undo/redo closely matches sculpt undo. */ - BLI_assert(dir != STEP_INVALID); - - ImageUndoStep *us = (ImageUndoStep *)us_p; - if (dir == STEP_UNDO) { - image_undosys_step_decode_undo(us, is_final); - } - else if (dir == STEP_REDO) { - image_undosys_step_decode_redo(us); - } - - if (us->paint_mode == PAINT_MODE_TEXTURE_3D) { - ED_object_mode_set_ex(C, OB_MODE_TEXTURE_PAINT, false, NULL); - } - - /* Refresh texture slots. */ - ED_editors_init_for_undo(bmain); -} - -static void image_undosys_step_free(UndoStep *us_p) -{ - ImageUndoStep *us = (ImageUndoStep *)us_p; - uhandle_free_list(&us->handles); - - /* Typically this list will have been cleared. */ - ptile_free_list(&us->paint_tiles); -} - -static void image_undosys_foreach_ID_ref(UndoStep *us_p, - UndoTypeForEachIDRefFn foreach_ID_ref_fn, - void *user_data) -{ - ImageUndoStep *us = (ImageUndoStep *)us_p; - LISTBASE_FOREACH (UndoImageHandle *, uh, &us->handles) { - foreach_ID_ref_fn(user_data, ((UndoRefID *)&uh->image_ref)); - } -} - -void ED_image_undosys_type(UndoType *ut) -{ - ut->name = "Image"; - ut->poll = image_undosys_poll; - ut->step_encode_init = image_undosys_step_encode_init; - ut->step_encode = image_undosys_step_encode; - ut->step_decode = image_undosys_step_decode; - ut->step_free = image_undosys_step_free; - - ut->step_foreach_ID_ref = image_undosys_foreach_ID_ref; - - /* NOTE: this is actually a confusing case, since it expects a valid context, but only in a - * specific case, see `image_undosys_step_encode` code. We cannot specify - * `UNDOTYPE_FLAG_NEED_CONTEXT_FOR_ENCODE` though, as it can be called with a NULL context by - * current code. */ - ut->flags = UNDOTYPE_FLAG_DECODE_ACTIVE_STEP; - - ut->step_size = sizeof(ImageUndoStep); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Utilities - * - * \note image undo exposes #ED_image_undo_push_begin, #ED_image_undo_push_end - * which must be called by the operator directly. - * - * Unlike most other undo stacks this is needed: - * - So we can always access the state before the image was painted onto, - * which is needed if previous undo states aren't image-type. - * - So operators can access the pixel-data before the stroke was applied, at run-time. - * \{ */ - -ListBase *ED_image_paint_tile_list_get(void) -{ - UndoStack *ustack = ED_undo_stack_get(); - UndoStep *us_prev = ustack->step_init; - UndoStep *us_p = BKE_undosys_stack_init_or_active_with_type(ustack, BKE_UNDOSYS_TYPE_IMAGE); - ImageUndoStep *us = (ImageUndoStep *)us_p; - /* We should always have an undo push started when accessing tiles, - * not doing this means we won't have paint_mode correctly set. */ - BLI_assert(us_p == us_prev); - if (us_p != us_prev) { - /* Fallback value until we can be sure this never happens. */ - us->paint_mode = PAINT_MODE_TEXTURE_2D; - } - return &us->paint_tiles; -} - -void ED_image_undo_restore(UndoStep *us) -{ - ListBase *paint_tiles = &((ImageUndoStep *)us)->paint_tiles; - ptile_restore_runtime_list(paint_tiles); - ptile_invalidate_list(paint_tiles); -} - -static ImageUndoStep *image_undo_push_begin(const char *name, int paint_mode) -{ - UndoStack *ustack = ED_undo_stack_get(); - bContext *C = NULL; /* special case, we never read from this. */ - UndoStep *us_p = BKE_undosys_step_push_init_with_type(ustack, C, name, BKE_UNDOSYS_TYPE_IMAGE); - ImageUndoStep *us = (ImageUndoStep *)us_p; - BLI_assert(ELEM(paint_mode, PAINT_MODE_TEXTURE_2D, PAINT_MODE_TEXTURE_3D, PAINT_MODE_SCULPT)); - us->paint_mode = paint_mode; - return us; -} - -void ED_image_undo_push_begin(const char *name, int paint_mode) -{ - image_undo_push_begin(name, paint_mode); -} - -void ED_image_undo_push_begin_with_image(const char *name, - Image *image, - ImBuf *ibuf, - ImageUser *iuser) -{ - ImageUndoStep *us = image_undo_push_begin(name, PAINT_MODE_TEXTURE_2D); - - BLI_assert(BKE_image_get_tile(image, iuser->tile)); - UndoImageHandle *uh = uhandle_ensure(&us->handles, image, iuser); - UndoImageBuf *ubuf_pre = uhandle_ensure_ubuf(uh, image, ibuf); - BLI_assert(ubuf_pre->post == NULL); - - ImageUndoStep *us_reference = (ImageUndoStep *)ED_undo_stack_get()->step_active; - while (us_reference && us_reference->step.type != BKE_UNDOSYS_TYPE_IMAGE) { - us_reference = (ImageUndoStep *)us_reference->step.prev; - } - UndoImageBuf *ubuf_reference = (us_reference ? ubuf_lookup_from_reference( - us_reference, image, iuser->tile, ubuf_pre) : - NULL); - - if (ubuf_reference) { - memcpy(ubuf_pre->tiles, ubuf_reference->tiles, sizeof(*ubuf_pre->tiles) * ubuf_pre->tiles_len); - for (uint i = 0; i < ubuf_pre->tiles_len; i++) { - UndoImageTile *utile = ubuf_pre->tiles[i]; - utile->users += 1; - } - } - else { - ubuf_from_image_all_tiles(ubuf_pre, ibuf); - } -} - -void ED_image_undo_push_end(void) -{ - UndoStack *ustack = ED_undo_stack_get(); - BKE_undosys_step_push(ustack, NULL, NULL); - BKE_undosys_stack_limit_steps_and_memory_defaults(ustack); - WM_file_tag_modified(); -} - -/** \} */ diff --git a/source/blender/editors/space_image/image_undo.cc b/source/blender/editors/space_image/image_undo.cc new file mode 100644 index 00000000000..986265afee0 --- /dev/null +++ b/source/blender/editors/space_image/image_undo.cc @@ -0,0 +1,1133 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup spimage + * + * Overview + * ======== + * + * - Each undo step is a #ImageUndoStep + * - Each #ImageUndoStep stores a list of #UndoImageHandle + * - Each #UndoImageHandle stores a list of #UndoImageBuf + * (this is the undo systems equivalent of an #ImBuf). + * - Each #UndoImageBuf stores an array of #UndoImageTile + * The tiles are shared between #UndoImageBuf's to avoid duplication. + * + * When the undo system manages an image, there will always be a full copy (as a #UndoImageBuf) + * each new undo step only stores modified tiles. + */ + +#include "CLG_log.h" + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" +#include "BLI_threads.h" +#include "BLI_utildefines.h" +#include "BLI_map.hh" + +#include "DNA_image_types.h" +#include "DNA_object_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" +#include "DNA_windowmanager_types.h" + +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" + +#include "BKE_context.h" +#include "BKE_image.h" +#include "BKE_paint.h" +#include "BKE_undo_system.h" + +#include "DEG_depsgraph.h" + +#include "ED_object.h" +#include "ED_paint.h" +#include "ED_undo.h" +#include "ED_util.h" + +#include "WM_api.h" + +static CLG_LogRef LOG = {"ed.image.undo"}; + +/* -------------------------------------------------------------------- */ +/** \name Thread Locking + * \{ */ + +/* This is a non-global static resource, + * Maybe it should be exposed as part of the + * paint operation, but for now just give a public interface */ +static SpinLock paint_tiles_lock; + +void ED_image_paint_tile_lock_init(void) +{ + BLI_spin_init(&paint_tiles_lock); +} + +void ED_image_paint_tile_lock_end(void) +{ + BLI_spin_end(&paint_tiles_lock); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Paint Tiles + * + * Created on demand while painting, + * use to access the previous state for some paint operations. + * + * These buffers are also used for undo when available. + * + * \{ */ + +static ImBuf *imbuf_alloc_temp_tile() +{ + return IMB_allocImBuf( + ED_IMAGE_UNDO_TILE_SIZE, ED_IMAGE_UNDO_TILE_SIZE, 32, IB_rectfloat | IB_rect); +} + +struct PaintTileKey { + int x_tile, y_tile; + Image *image; + ImBuf *ibuf; + /* Copied from iuser.tile in PaintTile. */ + int iuser_tile; + + uint64_t hash() const + { + return blender::get_default_hash_4(x_tile, y_tile, image, ibuf); + } + bool operator==(const PaintTileKey &other) const + { + return x_tile == other.x_tile && y_tile == other.y_tile && image == other.image && + ibuf == other.ibuf && iuser_tile == other.iuser_tile; + } +}; + +struct PaintTile { + Image *image; + ImBuf *ibuf; + /* For 2D image painting the ImageUser uses most of the values. + * Even though views and passes are stored they are currently not supported for painting. + * For 3D projection painting this only uses a tile & frame number. + * The scene pointer must be cleared (or temporarily set it as needed, but leave cleared). */ + ImageUser iuser; + union { + float *fp; + uint32_t *uint; + void *pt; + } rect; + uint16_t *mask; + bool valid; + bool use_float; + int x_tile, y_tile; +}; + +static void ptile_free(PaintTile *ptile) +{ + if (ptile->rect.pt) { + MEM_freeN(ptile->rect.pt); + } + if (ptile->mask) { + MEM_freeN(ptile->mask); + } + MEM_freeN(ptile); +} + +struct PaintTileMap { + blender::Map map; + + ~PaintTileMap() + { + for (PaintTile *ptile : map.values()) { + ptile_free(ptile); + } + } +}; + +static void ptile_invalidate_map(PaintTileMap *paint_tile_map) +{ + for (PaintTile *ptile : paint_tile_map->map.values()) { + ptile->valid = false; + } +} + +void *ED_image_paint_tile_find(PaintTileMap *paint_tile_map, + Image *image, + ImBuf *ibuf, + ImageUser *iuser, + int x_tile, + int y_tile, + ushort **r_mask, + bool validate) +{ + PaintTileKey key; + key.ibuf = ibuf; + key.image = image; + key.iuser_tile = iuser->tile; + key.x_tile = x_tile; + key.y_tile = y_tile; + PaintTile **pptile = paint_tile_map->map.lookup_ptr(key); + if (pptile == nullptr) { + return nullptr; + } + PaintTile *ptile = *pptile; + if (r_mask) { + /* allocate mask if requested. */ + if (!ptile->mask) { + ptile->mask = static_cast(MEM_callocN(sizeof(uint16_t) * square_i(ED_IMAGE_UNDO_TILE_SIZE), + "UndoImageTile.mask")); + } + *r_mask = ptile->mask; + } + if (validate) { + ptile->valid = true; + } + return ptile->rect.pt; +} + +void *ED_image_paint_tile_push(PaintTileMap *paint_tile_map, + Image *image, + ImBuf *ibuf, + ImBuf **tmpibuf, + ImageUser *iuser, + int x_tile, + int y_tile, + ushort **r_mask, + bool **r_valid, + bool use_thread_lock, + bool find_prev) +{ + if (use_thread_lock) { + BLI_spin_lock(&paint_tiles_lock); + } + const bool has_float = (ibuf->rect_float != nullptr); + + /* check if tile is already pushed */ + + /* in projective painting we keep accounting of tiles, so if we need one pushed, just push! */ + if (find_prev) { + void *data = ED_image_paint_tile_find( + paint_tile_map, image, ibuf, iuser, x_tile, y_tile, r_mask, true); + if (data) { + if (use_thread_lock) { + BLI_spin_unlock(&paint_tiles_lock); + } + return data; + } + } + + if (*tmpibuf == nullptr) { + *tmpibuf = imbuf_alloc_temp_tile(); + } + + PaintTile *ptile = static_cast(MEM_callocN(sizeof(PaintTile), "PaintTile")); + + ptile->image = image; + ptile->ibuf = ibuf; + ptile->iuser = *iuser; + ptile->iuser.scene = nullptr; + + ptile->x_tile = x_tile; + ptile->y_tile = y_tile; + + /* add mask explicitly here */ + if (r_mask) { + *r_mask = ptile->mask = static_cast(MEM_callocN(sizeof(uint16_t) * square_i(ED_IMAGE_UNDO_TILE_SIZE), + "PaintTile.mask")); + } + + ptile->rect.pt = MEM_callocN((ibuf->rect_float ? sizeof(float[4]) : sizeof(char[4])) * + square_i(ED_IMAGE_UNDO_TILE_SIZE), + "PaintTile.rect"); + + ptile->use_float = has_float; + ptile->valid = true; + + if (r_valid) { + *r_valid = &ptile->valid; + } + + IMB_rectcpy(*tmpibuf, + ibuf, + 0, + 0, + x_tile * ED_IMAGE_UNDO_TILE_SIZE, + y_tile * ED_IMAGE_UNDO_TILE_SIZE, + ED_IMAGE_UNDO_TILE_SIZE, + ED_IMAGE_UNDO_TILE_SIZE); + + if (has_float) { + SWAP(float *, ptile->rect.fp, (*tmpibuf)->rect_float); + } + else { + SWAP(uint32_t *, ptile->rect.uint, (*tmpibuf)->rect); + } + + PaintTileKey key = {}; + key.ibuf = ibuf; + key.image = image; + key.iuser_tile = iuser->tile; + key.x_tile = x_tile; + key.y_tile = y_tile; + PaintTile *existing_tile = nullptr; + paint_tile_map->map.add_or_modify( + key, + [&](PaintTile **pptile) { *pptile = ptile; }, + [&](PaintTile **pptile) { existing_tile = *pptile; }); + if (existing_tile) { + ptile_free(ptile); + ptile = existing_tile; + } + + if (use_thread_lock) { + BLI_spin_unlock(&paint_tiles_lock); + } + return ptile->rect.pt; +} + +static void ptile_restore_runtime_map(PaintTileMap *paint_tile_map) +{ + ImBuf *tmpibuf = imbuf_alloc_temp_tile(); + + for (PaintTile *ptile : paint_tile_map->map.values()) { + Image *image = ptile->image; + ImBuf *ibuf = BKE_image_acquire_ibuf(image, &ptile->iuser, nullptr); + const bool has_float = (ibuf->rect_float != nullptr); + + if (has_float) { + SWAP(float *, ptile->rect.fp, tmpibuf->rect_float); + } + else { + SWAP(uint32_t *, ptile->rect.uint, tmpibuf->rect); + } + + IMB_rectcpy(ibuf, + tmpibuf, + ptile->x_tile * ED_IMAGE_UNDO_TILE_SIZE, + ptile->y_tile * ED_IMAGE_UNDO_TILE_SIZE, + 0, + 0, + ED_IMAGE_UNDO_TILE_SIZE, + ED_IMAGE_UNDO_TILE_SIZE); + + if (has_float) { + SWAP(float *, ptile->rect.fp, tmpibuf->rect_float); + } + else { + SWAP(uint32_t *, ptile->rect.uint, tmpibuf->rect); + } + + /* Force OpenGL reload (maybe partial update will operate better?) */ + BKE_image_free_gputextures(image); + + if (ibuf->rect_float) { + ibuf->userflags |= IB_RECT_INVALID; /* force recreate of char rect */ + } + if (ibuf->mipmap[0]) { + ibuf->userflags |= IB_MIPMAP_INVALID; /* Force MIP-MAP recreation. */ + } + ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID; + + BKE_image_release_ibuf(image, ibuf, nullptr); + } + + IMB_freeImBuf(tmpibuf); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Image Undo Tile + * \{ */ + +static uint32_t index_from_xy(uint32_t tile_x, uint32_t tile_y, const uint32_t tiles_dims[2]) +{ + BLI_assert(tile_x < tiles_dims[0] && tile_y < tiles_dims[1]); + return (tile_y * tiles_dims[0]) + tile_x; +} + +struct UndoImageTile { + union { + float *fp; + uint32_t *uint_ptr; + void *pt; + } rect; + int users; +}; + +static UndoImageTile *utile_alloc(bool has_float) +{ + UndoImageTile *utile = static_cast(MEM_callocN(sizeof(*utile), "ImageUndoTile")); + if (has_float) { + utile->rect.fp = static_cast(MEM_mallocN(sizeof(float[4]) * square_i(ED_IMAGE_UNDO_TILE_SIZE), __func__)); + } + else { + utile->rect.uint_ptr = static_cast(MEM_mallocN(sizeof(uint32_t) * square_i(ED_IMAGE_UNDO_TILE_SIZE), __func__)); + } + return utile; +} + +static void utile_init_from_imbuf( + UndoImageTile *utile, const uint32_t x, const uint32_t y, const ImBuf *ibuf, ImBuf *tmpibuf) +{ + const bool has_float = ibuf->rect_float; + + if (has_float) { + SWAP(float *, utile->rect.fp, tmpibuf->rect_float); + } + else { + SWAP(uint32_t *, utile->rect.uint_ptr, tmpibuf->rect); + } + + IMB_rectcpy(tmpibuf, ibuf, 0, 0, x, y, ED_IMAGE_UNDO_TILE_SIZE, ED_IMAGE_UNDO_TILE_SIZE); + + if (has_float) { + SWAP(float *, utile->rect.fp, tmpibuf->rect_float); + } + else { + SWAP(uint32_t *, utile->rect.uint_ptr, tmpibuf->rect); + } +} + +static void utile_restore( + const UndoImageTile *utile, const uint x, const uint y, ImBuf *ibuf, ImBuf *tmpibuf) +{ + const bool has_float = ibuf->rect_float; + float *prev_rect_float = tmpibuf->rect_float; + uint32_t *prev_rect = tmpibuf->rect; + + if (has_float) { + tmpibuf->rect_float = utile->rect.fp; + } + else { + tmpibuf->rect = utile->rect.uint_ptr; + } + + IMB_rectcpy(ibuf, tmpibuf, x, y, 0, 0, ED_IMAGE_UNDO_TILE_SIZE, ED_IMAGE_UNDO_TILE_SIZE); + + tmpibuf->rect_float = prev_rect_float; + tmpibuf->rect = prev_rect; +} + +static void utile_decref(UndoImageTile *utile) +{ + utile->users -= 1; + BLI_assert(utile->users >= 0); + if (utile->users == 0) { + MEM_freeN(utile->rect.pt); + MEM_delete(utile); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Image Undo Buffer + * \{ */ + +typedef struct UndoImageBuf { + struct UndoImageBuf *next, *prev; + + /** + * The buffer after the undo step has executed. + */ + struct UndoImageBuf *post; + + char ibuf_name[IMB_FILENAME_SIZE]; + + UndoImageTile **tiles; + + /** Can calculate these from dims, just for convenience. */ + uint32_t tiles_len; + uint32_t tiles_dims[2]; + + uint32_t image_dims[2]; + + /** Store variables from the image. */ + struct { + short source; + bool use_float; + char gen_type; + } image_state; + +} UndoImageBuf; + +static UndoImageBuf *ubuf_from_image_no_tiles(Image *image, const ImBuf *ibuf) +{ + UndoImageBuf *ubuf = static_cast(MEM_callocN(sizeof(*ubuf), __func__)); + + ubuf->image_dims[0] = ibuf->x; + ubuf->image_dims[1] = ibuf->y; + + ubuf->tiles_dims[0] = ED_IMAGE_UNDO_TILE_NUMBER(ubuf->image_dims[0]); + ubuf->tiles_dims[1] = ED_IMAGE_UNDO_TILE_NUMBER(ubuf->image_dims[1]); + + ubuf->tiles_len = ubuf->tiles_dims[0] * ubuf->tiles_dims[1]; + ubuf->tiles = static_cast(MEM_callocN(sizeof(*ubuf->tiles) * ubuf->tiles_len, __func__)); + + BLI_strncpy(ubuf->ibuf_name, ibuf->name, sizeof(ubuf->ibuf_name)); + ubuf->image_state.gen_type = image->gen_type; + ubuf->image_state.source = image->source; + ubuf->image_state.use_float = ibuf->rect_float != nullptr; + + return ubuf; +} + +static void ubuf_from_image_all_tiles(UndoImageBuf *ubuf, const ImBuf *ibuf) +{ + ImBuf *tmpibuf = imbuf_alloc_temp_tile(); + + const bool has_float = ibuf->rect_float; + int i = 0; + for (uint y_tile = 0; y_tile < ubuf->tiles_dims[1]; y_tile += 1) { + uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS; + for (uint x_tile = 0; x_tile < ubuf->tiles_dims[0]; x_tile += 1) { + uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS; + + BLI_assert(ubuf->tiles[i] == nullptr); + UndoImageTile *utile = utile_alloc(has_float); + utile->users = 1; + utile_init_from_imbuf(utile, x, y, ibuf, tmpibuf); + ubuf->tiles[i] = utile; + + i += 1; + } + } + + BLI_assert(i == ubuf->tiles_len); + + IMB_freeImBuf(tmpibuf); +} + +/** Ensure we can copy the ubuf into the ibuf. */ +static void ubuf_ensure_compat_ibuf(const UndoImageBuf *ubuf, ImBuf *ibuf) +{ + /* We could have both float and rect buffers, + * in this case free the float buffer if it's unused. */ + if ((ibuf->rect_float != nullptr) && (ubuf->image_state.use_float == false)) { + imb_freerectfloatImBuf(ibuf); + } + + if (ibuf->x == ubuf->image_dims[0] && ibuf->y == ubuf->image_dims[1] && + (ubuf->image_state.use_float ? (void *)ibuf->rect_float : (void *)ibuf->rect)) { + return; + } + + imb_freerectImbuf_all(ibuf); + IMB_rect_size_set(ibuf, ubuf->image_dims); + + if (ubuf->image_state.use_float) { + imb_addrectfloatImBuf(ibuf); + } + else { + imb_addrectImBuf(ibuf); + } +} + +static void ubuf_free(UndoImageBuf *ubuf) +{ + UndoImageBuf *ubuf_post = ubuf->post; + for (uint i = 0; i < ubuf->tiles_len; i++) { + UndoImageTile *utile = ubuf->tiles[i]; + utile_decref(utile); + } + MEM_freeN(ubuf->tiles); + MEM_freeN(ubuf); + if (ubuf_post) { + ubuf_free(ubuf_post); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Image Undo Handle + * \{ */ + +typedef struct UndoImageHandle { + struct UndoImageHandle *next, *prev; + + /** Each undo handle refers to a single image which may have multiple buffers. */ + UndoRefID_Image image_ref; + + /** Each tile of a tiled image has its own UndoImageHandle. + * The tile number of this IUser is used to distinguish them. + */ + ImageUser iuser; + + /** + * List of #UndoImageBuf's to support multiple buffers per image. + */ + ListBase buffers; + +} UndoImageHandle; + +static void uhandle_restore_list(ListBase *undo_handles, bool use_init) +{ + ImBuf *tmpibuf = imbuf_alloc_temp_tile(); + + LISTBASE_FOREACH (UndoImageHandle *, uh, undo_handles) { + /* Tiles only added to second set of tiles. */ + Image *image = uh->image_ref.ptr; + + ImBuf *ibuf = BKE_image_acquire_ibuf(image, &uh->iuser, nullptr); + if (UNLIKELY(ibuf == nullptr)) { + CLOG_ERROR(&LOG, "Unable to get buffer for image '%s'", image->id.name + 2); + continue; + } + bool changed = false; + LISTBASE_FOREACH (UndoImageBuf *, ubuf_iter, &uh->buffers) { + UndoImageBuf *ubuf = use_init ? ubuf_iter : ubuf_iter->post; + ubuf_ensure_compat_ibuf(ubuf, ibuf); + + int i = 0; + for (uint y_tile = 0; y_tile < ubuf->tiles_dims[1]; y_tile += 1) { + uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS; + for (uint x_tile = 0; x_tile < ubuf->tiles_dims[0]; x_tile += 1) { + uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS; + utile_restore(ubuf->tiles[i], x, y, ibuf, tmpibuf); + changed = true; + i += 1; + } + } + } + + if (changed) { + BKE_image_mark_dirty(image, ibuf); + /* TODO(jbakker): only mark areas that are actually updated to improve performance. */ + BKE_image_partial_update_mark_full_update(image); + + if (ibuf->rect_float) { + ibuf->userflags |= IB_RECT_INVALID; /* force recreate of char rect */ + } + if (ibuf->mipmap[0]) { + ibuf->userflags |= IB_MIPMAP_INVALID; /* force mip-map recreation. */ + } + ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID; + + DEG_id_tag_update(&image->id, 0); + } + BKE_image_release_ibuf(image, ibuf, nullptr); + } + + IMB_freeImBuf(tmpibuf); +} + +static void uhandle_free_list(ListBase *undo_handles) +{ + LISTBASE_FOREACH_MUTABLE (UndoImageHandle *, uh, undo_handles) { + LISTBASE_FOREACH_MUTABLE (UndoImageBuf *, ubuf, &uh->buffers) { + ubuf_free(ubuf); + } + MEM_freeN(uh); + } + BLI_listbase_clear(undo_handles); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Image Undo Internal Utilities + * \{ */ + +/** #UndoImageHandle utilities */ + +static UndoImageBuf *uhandle_lookup_ubuf(UndoImageHandle *uh, + const Image *UNUSED(image), + const char *ibuf_name) +{ + LISTBASE_FOREACH (UndoImageBuf *, ubuf, &uh->buffers) { + if (STREQ(ubuf->ibuf_name, ibuf_name)) { + return ubuf; + } + } + return nullptr; +} + +static UndoImageBuf *uhandle_add_ubuf(UndoImageHandle *uh, Image *image, ImBuf *ibuf) +{ + BLI_assert(uhandle_lookup_ubuf(uh, image, ibuf->name) == nullptr); + UndoImageBuf *ubuf = ubuf_from_image_no_tiles(image, ibuf); + BLI_addtail(&uh->buffers, ubuf); + + ubuf->post = nullptr; + + return ubuf; +} + +static UndoImageBuf *uhandle_ensure_ubuf(UndoImageHandle *uh, Image *image, ImBuf *ibuf) +{ + UndoImageBuf *ubuf = uhandle_lookup_ubuf(uh, image, ibuf->name); + if (ubuf == nullptr) { + ubuf = uhandle_add_ubuf(uh, image, ibuf); + } + return ubuf; +} + +static UndoImageHandle *uhandle_lookup_by_name(ListBase *undo_handles, + const Image *image, + int tile_number) +{ + LISTBASE_FOREACH (UndoImageHandle *, uh, undo_handles) { + if (STREQ(image->id.name + 2, uh->image_ref.name + 2) && uh->iuser.tile == tile_number) { + return uh; + } + } + return nullptr; +} + +static UndoImageHandle *uhandle_lookup(ListBase *undo_handles, const Image *image, int tile_number) +{ + LISTBASE_FOREACH (UndoImageHandle *, uh, undo_handles) { + if (image == uh->image_ref.ptr && uh->iuser.tile == tile_number) { + return uh; + } + } + return nullptr; +} + +static UndoImageHandle *uhandle_add(ListBase *undo_handles, Image *image, ImageUser *iuser) +{ + BLI_assert(uhandle_lookup(undo_handles, image, iuser->tile) == nullptr); + UndoImageHandle *uh = static_cast(MEM_callocN(sizeof(*uh), __func__)); + uh->image_ref.ptr = image; + uh->iuser = *iuser; + uh->iuser.scene = nullptr; + BLI_addtail(undo_handles, uh); + return uh; +} + +static UndoImageHandle *uhandle_ensure(ListBase *undo_handles, Image *image, ImageUser *iuser) +{ + UndoImageHandle *uh = uhandle_lookup(undo_handles, image, iuser->tile); + if (uh == nullptr) { + uh = uhandle_add(undo_handles, image, iuser); + } + return uh; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Implements ED Undo System + * \{ */ + +struct ImageUndoStep { + UndoStep step; + + /** #UndoImageHandle */ + ListBase handles; + + /** + * #PaintTile + * Run-time only data (active during a paint stroke). + */ + PaintTileMap* paint_tile_map; + + bool is_encode_init; + ePaintMode paint_mode; +}; + +/** + * Find the previous undo buffer from this one. + * \note We could look into undo steps even further back. + */ +static UndoImageBuf *ubuf_lookup_from_reference(ImageUndoStep *us_prev, + const Image *image, + int tile_number, + const UndoImageBuf *ubuf) +{ + /* Use name lookup because the pointer is cleared for previous steps. */ + UndoImageHandle *uh_prev = uhandle_lookup_by_name(&us_prev->handles, image, tile_number); + if (uh_prev != nullptr) { + UndoImageBuf *ubuf_reference = uhandle_lookup_ubuf(uh_prev, image, ubuf->ibuf_name); + if (ubuf_reference) { + ubuf_reference = ubuf_reference->post; + if ((ubuf_reference->image_dims[0] == ubuf->image_dims[0]) && + (ubuf_reference->image_dims[1] == ubuf->image_dims[1])) { + return ubuf_reference; + } + } + } + return nullptr; +} + +static bool image_undosys_poll(bContext *C) +{ + Object *obact = CTX_data_active_object(C); + + ScrArea *area = CTX_wm_area(C); + if (area && (area->spacetype == SPACE_IMAGE)) { + SpaceImage *sima = (SpaceImage *)area->spacedata.first; + if ((obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) || (sima->mode == SI_MODE_PAINT)) { + return true; + } + } + else { + if (obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) { + return true; + } + } + return false; +} + +static void image_undosys_step_encode_init(struct bContext *UNUSED(C), UndoStep *us_p) +{ + ImageUndoStep *us = reinterpret_cast(us_p); + /* dummy, memory is cleared anyway. */ + us->is_encode_init = true; + BLI_listbase_clear(&us->handles); + us->paint_tile_map = MEM_new(__func__); +} + +static bool image_undosys_step_encode(struct bContext *C, + struct Main *UNUSED(bmain), + UndoStep *us_p) +{ + /* Encoding is done along the way by adding tiles + * to the current 'ImageUndoStep' added by encode_init. + * + * This function ensures there are previous and current states of the image in the undo buffer. + */ + ImageUndoStep *us = reinterpret_cast(us_p); + + BLI_assert(us->step.data_size == 0); + + if (us->is_encode_init) { + + ImBuf *tmpibuf = imbuf_alloc_temp_tile(); + + ImageUndoStep *us_reference = reinterpret_cast(ED_undo_stack_get()->step_active); + while (us_reference && us_reference->step.type != BKE_UNDOSYS_TYPE_IMAGE) { + us_reference = reinterpret_cast(us_reference->step.prev); + } + + /* Initialize undo tiles from ptiles (if they exist). */ + for (PaintTile *ptile : us->paint_tile_map->map.values()) { + if (ptile->valid) { + UndoImageHandle *uh = uhandle_ensure(&us->handles, ptile->image, &ptile->iuser); + UndoImageBuf *ubuf_pre = uhandle_ensure_ubuf(uh, ptile->image, ptile->ibuf); + + UndoImageTile *utile = static_cast(MEM_callocN(sizeof(*utile), "UndoImageTile")); + utile->users = 1; + utile->rect.pt = ptile->rect.pt; + ptile->rect.pt = nullptr; + const uint tile_index = index_from_xy(ptile->x_tile, ptile->y_tile, ubuf_pre->tiles_dims); + + BLI_assert(ubuf_pre->tiles[tile_index] == nullptr); + ubuf_pre->tiles[tile_index] = utile; + } + ptile_free(ptile); + } + us->paint_tile_map->map.clear(); + + LISTBASE_FOREACH (UndoImageHandle *, uh, &us->handles) { + LISTBASE_FOREACH (UndoImageBuf *, ubuf_pre, &uh->buffers) { + + ImBuf *ibuf = BKE_image_acquire_ibuf(uh->image_ref.ptr, &uh->iuser, nullptr); + + const bool has_float = ibuf->rect_float; + + BLI_assert(ubuf_pre->post == nullptr); + ubuf_pre->post = ubuf_from_image_no_tiles(uh->image_ref.ptr, ibuf); + UndoImageBuf *ubuf_post = ubuf_pre->post; + + if (ubuf_pre->image_dims[0] != ubuf_post->image_dims[0] || + ubuf_pre->image_dims[1] != ubuf_post->image_dims[1]) { + ubuf_from_image_all_tiles(ubuf_post, ibuf); + } + else { + /* Search for the previous buffer. */ + UndoImageBuf *ubuf_reference = + (us_reference ? ubuf_lookup_from_reference( + us_reference, uh->image_ref.ptr, uh->iuser.tile, ubuf_post) : + nullptr); + + int i = 0; + for (uint y_tile = 0; y_tile < ubuf_pre->tiles_dims[1]; y_tile += 1) { + uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS; + for (uint x_tile = 0; x_tile < ubuf_pre->tiles_dims[0]; x_tile += 1) { + uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS; + + if ((ubuf_reference != nullptr) && ((ubuf_pre->tiles[i] == nullptr) || + /* In this case the paint stroke as has added a tile + * which we have a duplicate reference available. */ + (ubuf_pre->tiles[i]->users == 1))) { + if (ubuf_pre->tiles[i] != nullptr) { + /* If we have a reference, re-use this single use tile for the post state. */ + BLI_assert(ubuf_pre->tiles[i]->users == 1); + ubuf_post->tiles[i] = ubuf_pre->tiles[i]; + ubuf_pre->tiles[i] = nullptr; + utile_init_from_imbuf(ubuf_post->tiles[i], x, y, ibuf, tmpibuf); + } + else { + BLI_assert(ubuf_post->tiles[i] == nullptr); + ubuf_post->tiles[i] = ubuf_reference->tiles[i]; + ubuf_post->tiles[i]->users += 1; + } + BLI_assert(ubuf_pre->tiles[i] == nullptr); + ubuf_pre->tiles[i] = ubuf_reference->tiles[i]; + ubuf_pre->tiles[i]->users += 1; + + BLI_assert(ubuf_pre->tiles[i] != nullptr); + BLI_assert(ubuf_post->tiles[i] != nullptr); + } + else { + UndoImageTile *utile = utile_alloc(has_float); + utile_init_from_imbuf(utile, x, y, ibuf, tmpibuf); + + if (ubuf_pre->tiles[i] != nullptr) { + ubuf_post->tiles[i] = utile; + utile->users = 1; + } + else { + ubuf_pre->tiles[i] = utile; + ubuf_post->tiles[i] = utile; + utile->users = 2; + } + } + BLI_assert(ubuf_pre->tiles[i] != nullptr); + BLI_assert(ubuf_post->tiles[i] != nullptr); + i += 1; + } + } + BLI_assert(i == ubuf_pre->tiles_len); + BLI_assert(i == ubuf_post->tiles_len); + } + BKE_image_release_ibuf(uh->image_ref.ptr, ibuf, nullptr); + } + } + + IMB_freeImBuf(tmpibuf); + + /* Useful to debug tiles are stored correctly. */ + if (false) { + uhandle_restore_list(&us->handles, false); + } + } + else { + BLI_assert(C != nullptr); + /* Happens when switching modes. */ + ePaintMode paint_mode = BKE_paintmode_get_active_from_context(C); + BLI_assert(ELEM(paint_mode, PAINT_MODE_TEXTURE_2D, PAINT_MODE_TEXTURE_3D)); + us->paint_mode = paint_mode; + } + + us_p->is_applied = true; + + return true; +} + +static void image_undosys_step_decode_undo_impl(ImageUndoStep *us, bool is_final) +{ + BLI_assert(us->step.is_applied == true); + uhandle_restore_list(&us->handles, !is_final); + us->step.is_applied = false; +} + +static void image_undosys_step_decode_redo_impl(ImageUndoStep *us) +{ + BLI_assert(us->step.is_applied == false); + uhandle_restore_list(&us->handles, false); + us->step.is_applied = true; +} + +static void image_undosys_step_decode_undo(ImageUndoStep *us, bool is_final) +{ + /* Walk forward over any applied steps of same type, + * then walk back in the next loop, un-applying them. */ + ImageUndoStep *us_iter = us; + while (us_iter->step.next && (us_iter->step.next->type == us_iter->step.type)) { + if (us_iter->step.next->is_applied == false) { + break; + } + us_iter = (ImageUndoStep *)us_iter->step.next; + } + while (us_iter != us || (!is_final && us_iter == us)) { + BLI_assert(us_iter->step.type == us->step.type); /* Previous loop ensures this. */ + image_undosys_step_decode_undo_impl(us_iter, is_final); + if (us_iter == us) { + break; + } + us_iter = (ImageUndoStep *)us_iter->step.prev; + } +} + +static void image_undosys_step_decode_redo(ImageUndoStep *us) +{ + ImageUndoStep *us_iter = us; + while (us_iter->step.prev && (us_iter->step.prev->type == us_iter->step.type)) { + if (us_iter->step.prev->is_applied == true) { + break; + } + us_iter = (ImageUndoStep *)us_iter->step.prev; + } + while (us_iter && (us_iter->step.is_applied == false)) { + image_undosys_step_decode_redo_impl(us_iter); + if (us_iter == us) { + break; + } + us_iter = (ImageUndoStep *)us_iter->step.next; + } +} + +static void image_undosys_step_decode( + struct bContext *C, struct Main *bmain, UndoStep *us_p, const eUndoStepDir dir, bool is_final) +{ + /* NOTE: behavior for undo/redo closely matches sculpt undo. */ + BLI_assert(dir != STEP_INVALID); + + ImageUndoStep *us = reinterpret_cast(us_p); + if (dir == STEP_UNDO) { + image_undosys_step_decode_undo(us, is_final); + } + else if (dir == STEP_REDO) { + image_undosys_step_decode_redo(us); + } + + if (us->paint_mode == PAINT_MODE_TEXTURE_3D) { + ED_object_mode_set_ex(C, OB_MODE_TEXTURE_PAINT, false, nullptr); + } + + /* Refresh texture slots. */ + ED_editors_init_for_undo(bmain); +} + +static void image_undosys_step_free(UndoStep *us_p) +{ + ImageUndoStep *us = (ImageUndoStep *)us_p; + uhandle_free_list(&us->handles); + + /* Typically this map will have been cleared. */ + MEM_delete(us->paint_tile_map); + us->paint_tile_map = nullptr; +} + +static void image_undosys_foreach_ID_ref(UndoStep *us_p, + UndoTypeForEachIDRefFn foreach_ID_ref_fn, + void *user_data) +{ + ImageUndoStep *us = reinterpret_cast(us_p); + LISTBASE_FOREACH (UndoImageHandle *, uh, &us->handles) { + foreach_ID_ref_fn(user_data, ((UndoRefID *)&uh->image_ref)); + } +} + +void ED_image_undosys_type(UndoType *ut) +{ + ut->name = "Image"; + ut->poll = image_undosys_poll; + ut->step_encode_init = image_undosys_step_encode_init; + ut->step_encode = image_undosys_step_encode; + ut->step_decode = image_undosys_step_decode; + ut->step_free = image_undosys_step_free; + + ut->step_foreach_ID_ref = image_undosys_foreach_ID_ref; + + /* NOTE: this is actually a confusing case, since it expects a valid context, but only in a + * specific case, see `image_undosys_step_encode` code. We cannot specify + * `UNDOTYPE_FLAG_NEED_CONTEXT_FOR_ENCODE` though, as it can be called with a NULL context by + * current code. */ + ut->flags = UNDOTYPE_FLAG_DECODE_ACTIVE_STEP; + + ut->step_size = sizeof(ImageUndoStep); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Utilities + * + * \note image undo exposes #ED_image_undo_push_begin, #ED_image_undo_push_end + * which must be called by the operator directly. + * + * Unlike most other undo stacks this is needed: + * - So we can always access the state before the image was painted onto, + * which is needed if previous undo states aren't image-type. + * - So operators can access the pixel-data before the stroke was applied, at run-time. + * \{ */ + +PaintTileMap *ED_image_paint_tile_map_get(void) +{ + UndoStack *ustack = ED_undo_stack_get(); + UndoStep *us_prev = ustack->step_init; + UndoStep *us_p = BKE_undosys_stack_init_or_active_with_type(ustack, BKE_UNDOSYS_TYPE_IMAGE); + ImageUndoStep *us = reinterpret_cast(us_p); + /* We should always have an undo push started when accessing tiles, + * not doing this means we won't have paint_mode correctly set. */ + BLI_assert(us_p == us_prev); + if (us_p != us_prev) { + /* Fallback value until we can be sure this never happens. */ + us->paint_mode = PAINT_MODE_TEXTURE_2D; + } + return us->paint_tile_map; +} + +void ED_image_undo_restore(UndoStep *us) +{ + PaintTileMap *paint_tile_map = reinterpret_cast(us)->paint_tile_map; + ptile_restore_runtime_map(paint_tile_map); + ptile_invalidate_map(paint_tile_map); +} + +static ImageUndoStep *image_undo_push_begin(const char *name, int paint_mode) +{ + UndoStack *ustack = ED_undo_stack_get(); + bContext *C = nullptr; /* special case, we never read from this. */ + UndoStep *us_p = BKE_undosys_step_push_init_with_type(ustack, C, name, BKE_UNDOSYS_TYPE_IMAGE); + ImageUndoStep *us = reinterpret_cast(us_p); + BLI_assert(ELEM(paint_mode, PAINT_MODE_TEXTURE_2D, PAINT_MODE_TEXTURE_3D, PAINT_MODE_SCULPT)); + us->paint_mode = (ePaintMode)paint_mode; + return us; +} + +void ED_image_undo_push_begin(const char *name, int paint_mode) +{ + image_undo_push_begin(name, paint_mode); +} + +void ED_image_undo_push_begin_with_image(const char *name, + Image *image, + ImBuf *ibuf, + ImageUser *iuser) +{ + ImageUndoStep *us = image_undo_push_begin(name, PAINT_MODE_TEXTURE_2D); + + BLI_assert(BKE_image_get_tile(image, iuser->tile)); + UndoImageHandle *uh = uhandle_ensure(&us->handles, image, iuser); + UndoImageBuf *ubuf_pre = uhandle_ensure_ubuf(uh, image, ibuf); + BLI_assert(ubuf_pre->post == nullptr); + + ImageUndoStep *us_reference = reinterpret_cast(ED_undo_stack_get()->step_active); + while (us_reference && us_reference->step.type != BKE_UNDOSYS_TYPE_IMAGE) { + us_reference = reinterpret_cast(us_reference->step.prev); + } + UndoImageBuf *ubuf_reference = (us_reference ? ubuf_lookup_from_reference( + us_reference, image, iuser->tile, ubuf_pre) : + nullptr); + + if (ubuf_reference) { + memcpy(ubuf_pre->tiles, ubuf_reference->tiles, sizeof(*ubuf_pre->tiles) * ubuf_pre->tiles_len); + for (uint32_t i = 0; i < ubuf_pre->tiles_len; i++) { + UndoImageTile *utile = ubuf_pre->tiles[i]; + utile->users += 1; + } + } + else { + ubuf_from_image_all_tiles(ubuf_pre, ibuf); + } +} + +void ED_image_undo_push_end(void) +{ + UndoStack *ustack = ED_undo_stack_get(); + BKE_undosys_step_push(ustack, nullptr, nullptr); + BKE_undosys_stack_limit_steps_and_memory_defaults(ustack); + WM_file_tag_modified(); +} + +/** \} */ -- cgit v1.2.3 From cacdea7f4a5b49d4b2ea2190373c822974aad163 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 25 Jul 2022 11:14:42 +0200 Subject: Fix: crash when accessing attributes from multiple threads Calling two non-const methods on a `MutableAttributeAccessor` at the same time in multiple threads is not safe. While I don't know what caused the crash here exactly, I do know that it happens while looking up the attribute for writing, which may modify the unterlying geometry. I couldn't reproduce the bug with a debug build or without threading. --- source/blender/blenkernel/intern/curves_geometry.cc | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc index f5c040a6fee..227ebc73c89 100644 --- a/source/blender/blenkernel/intern/curves_geometry.cc +++ b/source/blender/blenkernel/intern/curves_geometry.cc @@ -1163,8 +1163,8 @@ static CurvesGeometry copy_with_removed_points(const CurvesGeometry &curves, threading::parallel_invoke( /* Initialize curve offsets. */ [&]() { new_curves.offsets_for_write().copy_from(new_curve_offsets); }, - /* Copy over point attributes. */ [&]() { + /* Copy over point attributes. */ for (auto &attribute : bke::retrieve_attributes_for_transfer( curves.attributes(), new_curves.attributes_for_write(), ATTR_DOMAIN_MASK_POINT)) { threading::parallel_for(copy_point_ranges.index_range(), 128, [&](IndexRange range) { @@ -1179,11 +1179,10 @@ static CurvesGeometry copy_with_removed_points(const CurvesGeometry &curves, }); attribute.dst.finish(); } - }, - /* Copy over curve attributes. - * In some cases points are just dissolved, so the the number of - * curves will be the same. That could be optimized in the future. */ - [&]() { + + /* Copy over curve attributes. + * In some cases points are just dissolved, so the the number of + * curves will be the same. That could be optimized in the future. */ for (auto &attribute : bke::retrieve_attributes_for_transfer( curves.attributes(), new_curves.attributes_for_write(), ATTR_DOMAIN_MASK_CURVE)) { if (new_curves.curves_num() == curves.curves_num()) { @@ -1260,8 +1259,8 @@ static CurvesGeometry copy_with_removed_curves(const CurvesGeometry &curves, } }); }, - /* Copy over point attributes. */ [&]() { + /* Copy over point attributes. */ for (auto &attribute : bke::retrieve_attributes_for_transfer( curves.attributes(), new_curves.attributes_for_write(), ATTR_DOMAIN_MASK_POINT)) { threading::parallel_for(old_curve_ranges.index_range(), 128, [&](IndexRange range) { @@ -1275,9 +1274,7 @@ static CurvesGeometry copy_with_removed_curves(const CurvesGeometry &curves, }); attribute.dst.finish(); } - }, - /* Copy over curve attributes. */ - [&]() { + /* Copy over curve attributes. */ for (auto &attribute : bke::retrieve_attributes_for_transfer( curves.attributes(), new_curves.attributes_for_write(), ATTR_DOMAIN_MASK_CURVE)) { threading::parallel_for(old_curve_ranges.index_range(), 128, [&](IndexRange range) { -- cgit v1.2.3 From 72fb92ded8e8e25c870c9bf26414d12cab385c83 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 25 Jul 2022 11:22:14 +0200 Subject: Fix T99961: crash when spreadsheet shows volume grids --- .../editors/space_spreadsheet/spreadsheet_data_source_geometry.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc index 629a0b5802b..fdcf9798b7f 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc @@ -559,7 +559,7 @@ static void add_fields_as_extra_columns(SpaceSpreadsheet *sspreadsheet, std::make_unique(component)); const eAttrDomain domain = (eAttrDomain)sspreadsheet->attribute_domain; - const int domain_num = component.attributes()->domain_size(domain); + const int domain_num = component.attribute_domain_size(domain); for (const auto item : fields_to_show.items()) { const StringRef name = item.key; const GField &field = item.value; -- cgit v1.2.3 From cd9ebc816ece2ba7af95caa38cf3517131a9a361 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Mon, 25 Jul 2022 11:15:24 +0200 Subject: Fix build error with WITH_CYCLES_KERNEL_NATIVE_ONLY on macOS Arm -march=native is not supported for all architectures. --- intern/cycles/CMakeLists.txt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/intern/cycles/CMakeLists.txt b/intern/cycles/CMakeLists.txt index 82fd81be262..89dad8ed36e 100644 --- a/intern/cycles/CMakeLists.txt +++ b/intern/cycles/CMakeLists.txt @@ -36,8 +36,13 @@ if(WITH_CYCLES_NATIVE_ONLY) ) if(NOT MSVC) - string(APPEND CMAKE_CXX_FLAGS " -march=native") - set(CYCLES_KERNEL_FLAGS "-march=native") + ADD_CHECK_CXX_COMPILER_FLAG(CMAKE_CXX_FLAGS _has_march_native "-march=native") + if(_has_march_native) + set(CYCLES_KERNEL_FLAGS "-march=native") + else() + set(CYCLES_KERNEL_FLAGS "") + endif() + unset(_has_march_native) else() if(NOT MSVC_NATIVE_ARCH_FLAGS) TRY_RUN( -- cgit v1.2.3 From 47d1a7484c57b6cc28936c0ef647054be15bc361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Mon, 25 Jul 2022 09:08:54 +0200 Subject: EEVEE-Next: Display compatible properties panels Only a few are kept not available as their features are not yet supported. --- .../scripts/startup/bl_ui/properties_data_bone.py | 2 +- .../startup/bl_ui/properties_data_camera.py | 26 +++--- .../scripts/startup/bl_ui/properties_data_curve.py | 4 +- .../startup/bl_ui/properties_data_curves.py | 8 +- .../startup/bl_ui/properties_data_lattice.py | 2 +- .../startup/bl_ui/properties_data_metaball.py | 4 +- .../startup/bl_ui/properties_data_pointcloud.py | 6 +- .../startup/bl_ui/properties_data_speaker.py | 10 +- .../startup/bl_ui/properties_data_volume.py | 14 +-- .../scripts/startup/bl_ui/properties_material.py | 33 ++++++- release/scripts/startup/bl_ui/properties_output.py | 28 +++--- .../scripts/startup/bl_ui/properties_particle.py | 102 ++++++++++----------- .../startup/bl_ui/properties_physics_cloth.py | 26 +++--- .../startup/bl_ui/properties_physics_common.py | 2 +- .../bl_ui/properties_physics_dynamicpaint.py | 42 ++++----- .../startup/bl_ui/properties_physics_field.py | 20 ++-- .../startup/bl_ui/properties_physics_fluid.py | 44 ++++----- .../startup/bl_ui/properties_physics_rigidbody.py | 16 ++-- .../properties_physics_rigidbody_constraint.py | 26 +++--- .../startup/bl_ui/properties_physics_softbody.py | 30 +++--- release/scripts/startup/bl_ui/properties_render.py | 18 ++-- .../scripts/startup/bl_ui/properties_texture.py | 42 ++++----- .../scripts/startup/bl_ui/properties_view_layer.py | 31 ++++++- 23 files changed, 293 insertions(+), 243 deletions(-) diff --git a/release/scripts/startup/bl_ui/properties_data_bone.py b/release/scripts/startup/bl_ui/properties_data_bone.py index c842af9af88..abbb3a8717f 100644 --- a/release/scripts/startup/bl_ui/properties_data_bone.py +++ b/release/scripts/startup/bl_ui/properties_data_bone.py @@ -442,7 +442,7 @@ class BONE_PT_deform(BoneButtonsPanel, Panel): class BONE_PT_custom_props(BoneButtonsPanel, PropertyPanel, Panel): - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} _property_type = bpy.types.Bone, bpy.types.EditBone, bpy.types.PoseBone @property diff --git a/release/scripts/startup/bl_ui/properties_data_camera.py b/release/scripts/startup/bl_ui/properties_data_camera.py index a9a032ac4a3..0f839eac126 100644 --- a/release/scripts/startup/bl_ui/properties_data_camera.py +++ b/release/scripts/startup/bl_ui/properties_data_camera.py @@ -21,7 +21,7 @@ class CAMERA_PT_presets(PresetPanel, Panel): preset_subdir = "camera" preset_operator = "script.execute_preset" preset_add_operator = "camera.preset_add" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} class SAFE_AREAS_PT_presets(PresetPanel, Panel): @@ -29,13 +29,13 @@ class SAFE_AREAS_PT_presets(PresetPanel, Panel): preset_subdir = "safe_areas" preset_operator = "script.execute_preset" preset_add_operator = "safe_areas.preset_add" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} class DATA_PT_context_camera(CameraButtonsPanel, Panel): bl_label = "" bl_options = {'HIDE_HEADER'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -52,7 +52,7 @@ class DATA_PT_context_camera(CameraButtonsPanel, Panel): class DATA_PT_lens(CameraButtonsPanel, Panel): bl_label = "Lens" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -100,7 +100,7 @@ class DATA_PT_lens(CameraButtonsPanel, Panel): col.prop(ccam, "fisheye_polynomial_k3", text="K3") col.prop(ccam, "fisheye_polynomial_k4", text="K4") - elif engine in {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}: + elif engine in {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'}: if cam.lens_unit == 'MILLIMETERS': col.prop(cam, "lens") elif cam.lens_unit == 'FOV': @@ -122,7 +122,7 @@ class DATA_PT_lens(CameraButtonsPanel, Panel): class DATA_PT_camera_stereoscopy(CameraButtonsPanel, Panel): bl_label = "Stereoscopy" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -171,7 +171,7 @@ class DATA_PT_camera_stereoscopy(CameraButtonsPanel, Panel): class DATA_PT_camera(CameraButtonsPanel, Panel): bl_label = "Camera" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw_header_preset(self, _context): CAMERA_PT_presets.draw_panel_header(self.layout) @@ -252,7 +252,7 @@ class DATA_PT_camera_dof_aperture(CameraButtonsPanel, Panel): class DATA_PT_camera_background_image(CameraButtonsPanel, Panel): bl_label = "Background Images" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw_header(self, context): cam = context.camera @@ -359,7 +359,7 @@ class DATA_PT_camera_background_image(CameraButtonsPanel, Panel): class DATA_PT_camera_display(CameraButtonsPanel, Panel): bl_label = "Viewport Display" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -392,7 +392,7 @@ class DATA_PT_camera_display_composition_guides(CameraButtonsPanel, Panel): bl_label = "Composition Guides" bl_parent_id = "DATA_PT_camera_display" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -419,7 +419,7 @@ class DATA_PT_camera_display_composition_guides(CameraButtonsPanel, Panel): class DATA_PT_camera_safe_areas(CameraButtonsPanel, Panel): bl_label = "Safe Areas" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw_header(self, context): cam = context.camera @@ -449,7 +449,7 @@ class DATA_PT_camera_safe_areas_center_cut(CameraButtonsPanel, Panel): bl_label = "Center-Cut Safe Areas" bl_parent_id = "DATA_PT_camera_safe_areas" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw_header(self, context): cam = context.camera @@ -473,7 +473,7 @@ class DATA_PT_camera_safe_areas_center_cut(CameraButtonsPanel, Panel): class DATA_PT_custom_props_camera(CameraButtonsPanel, PropertyPanel, Panel): - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} _context_path = "object.data" _property_type = bpy.types.Camera diff --git a/release/scripts/startup/bl_ui/properties_data_curve.py b/release/scripts/startup/bl_ui/properties_data_curve.py index 4da7cd0283b..88dd3caaa74 100644 --- a/release/scripts/startup/bl_ui/properties_data_curve.py +++ b/release/scripts/startup/bl_ui/properties_data_curve.py @@ -116,7 +116,7 @@ class DATA_PT_shape_curve(CurveButtonsPanel, Panel): class DATA_PT_curve_texture_space(CurveButtonsPanel, Panel): bl_label = "Texture Space" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -475,7 +475,7 @@ class DATA_PT_text_boxes(CurveButtonsPanelText, Panel): class DATA_PT_custom_props_curve(CurveButtonsPanel, PropertyPanel, Panel): - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} _context_path = "object.data" _property_type = bpy.types.Curve diff --git a/release/scripts/startup/bl_ui/properties_data_curves.py b/release/scripts/startup/bl_ui/properties_data_curves.py index 4a11c5edde6..ff0eabeb7d9 100644 --- a/release/scripts/startup/bl_ui/properties_data_curves.py +++ b/release/scripts/startup/bl_ui/properties_data_curves.py @@ -18,7 +18,7 @@ class DataButtonsPanel: class DATA_PT_context_curves(DataButtonsPanel, Panel): bl_label = "" bl_options = {'HIDE_HEADER'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -35,7 +35,7 @@ class DATA_PT_context_curves(DataButtonsPanel, Panel): class DATA_PT_curves_surface(DataButtonsPanel, Panel): bl_label = "Surface" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -104,7 +104,7 @@ class CURVES_UL_attributes(UIList): class DATA_PT_CURVES_attributes(DataButtonsPanel, Panel): bl_label = "Attributes" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): curves = context.curves @@ -129,7 +129,7 @@ class DATA_PT_CURVES_attributes(DataButtonsPanel, Panel): class DATA_PT_custom_props_curves(DataButtonsPanel, PropertyPanel, Panel): - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} _context_path = "object.data" _property_type = bpy.types.Curves if hasattr(bpy.types, "Curves") else None diff --git a/release/scripts/startup/bl_ui/properties_data_lattice.py b/release/scripts/startup/bl_ui/properties_data_lattice.py index 618f73ecc06..e57b46989fe 100644 --- a/release/scripts/startup/bl_ui/properties_data_lattice.py +++ b/release/scripts/startup/bl_ui/properties_data_lattice.py @@ -64,7 +64,7 @@ class DATA_PT_lattice(DataButtonsPanel, Panel): class DATA_PT_custom_props_lattice(DataButtonsPanel, PropertyPanel, Panel): - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} _context_path = "object.data" _property_type = bpy.types.Lattice diff --git a/release/scripts/startup/bl_ui/properties_data_metaball.py b/release/scripts/startup/bl_ui/properties_data_metaball.py index defb6d55169..eba5676535f 100644 --- a/release/scripts/startup/bl_ui/properties_data_metaball.py +++ b/release/scripts/startup/bl_ui/properties_data_metaball.py @@ -56,7 +56,7 @@ class DATA_PT_metaball(DataButtonsPanel, Panel): class DATA_PT_mball_texture_space(DataButtonsPanel, Panel): bl_label = "Texture Space" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -111,7 +111,7 @@ class DATA_PT_metaball_element(DataButtonsPanel, Panel): class DATA_PT_custom_props_metaball(DataButtonsPanel, PropertyPanel, Panel): - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} _context_path = "object.data" _property_type = bpy.types.MetaBall diff --git a/release/scripts/startup/bl_ui/properties_data_pointcloud.py b/release/scripts/startup/bl_ui/properties_data_pointcloud.py index 8ef6ad63bba..d93adcdcc60 100644 --- a/release/scripts/startup/bl_ui/properties_data_pointcloud.py +++ b/release/scripts/startup/bl_ui/properties_data_pointcloud.py @@ -18,7 +18,7 @@ class DataButtonsPanel: class DATA_PT_context_pointcloud(DataButtonsPanel, Panel): bl_label = "" bl_options = {'HIDE_HEADER'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -89,7 +89,7 @@ class POINTCLOUD_UL_attributes(UIList): class DATA_PT_pointcloud_attributes(DataButtonsPanel, Panel): bl_label = "Attributes" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): pointcloud = context.pointcloud @@ -114,7 +114,7 @@ class DATA_PT_pointcloud_attributes(DataButtonsPanel, Panel): class DATA_PT_custom_props_pointcloud(DataButtonsPanel, PropertyPanel, Panel): - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} _context_path = "object.data" _property_type = bpy.types.PointCloud if hasattr(bpy.types, "PointCloud") else None diff --git a/release/scripts/startup/bl_ui/properties_data_speaker.py b/release/scripts/startup/bl_ui/properties_data_speaker.py index 7934aa0bc92..9bdf0e22c2f 100644 --- a/release/scripts/startup/bl_ui/properties_data_speaker.py +++ b/release/scripts/startup/bl_ui/properties_data_speaker.py @@ -18,7 +18,7 @@ class DataButtonsPanel: class DATA_PT_context_speaker(DataButtonsPanel, Panel): bl_label = "" bl_options = {'HIDE_HEADER'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -35,7 +35,7 @@ class DATA_PT_context_speaker(DataButtonsPanel, Panel): class DATA_PT_speaker(DataButtonsPanel, Panel): bl_label = "Sound" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -57,7 +57,7 @@ class DATA_PT_speaker(DataButtonsPanel, Panel): class DATA_PT_distance(DataButtonsPanel, Panel): bl_label = "Distance" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -81,7 +81,7 @@ class DATA_PT_distance(DataButtonsPanel, Panel): class DATA_PT_cone(DataButtonsPanel, Panel): bl_label = "Cone" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -103,7 +103,7 @@ class DATA_PT_cone(DataButtonsPanel, Panel): class DATA_PT_custom_props_speaker(DataButtonsPanel, PropertyPanel, Panel): - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} _context_path = "object.data" _property_type = bpy.types.Speaker diff --git a/release/scripts/startup/bl_ui/properties_data_volume.py b/release/scripts/startup/bl_ui/properties_data_volume.py index 460fcd7124b..1aa2faad7b8 100644 --- a/release/scripts/startup/bl_ui/properties_data_volume.py +++ b/release/scripts/startup/bl_ui/properties_data_volume.py @@ -18,7 +18,7 @@ class DataButtonsPanel: class DATA_PT_context_volume(DataButtonsPanel, Panel): bl_label = "" bl_options = {'HIDE_HEADER'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -35,7 +35,7 @@ class DATA_PT_context_volume(DataButtonsPanel, Panel): class DATA_PT_volume_file(DataButtonsPanel, Panel): bl_label = "OpenVDB File" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -80,7 +80,7 @@ class VOLUME_UL_grids(UIList): class DATA_PT_volume_grids(DataButtonsPanel, Panel): bl_label = "Grids" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -93,7 +93,7 @@ class DATA_PT_volume_grids(DataButtonsPanel, Panel): class DATA_PT_volume_render(DataButtonsPanel, Panel): bl_label = "Render" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -125,7 +125,7 @@ class DATA_PT_volume_render(DataButtonsPanel, Panel): class DATA_PT_volume_viewport_display(DataButtonsPanel, Panel): bl_label = "Viewport Display" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -149,7 +149,7 @@ class DATA_PT_volume_viewport_display(DataButtonsPanel, Panel): class DATA_PT_volume_viewport_display_slicing(DataButtonsPanel, Panel): bl_label = "" bl_parent_id = 'DATA_PT_volume_viewport_display' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw_header(self, context): layout = self.layout @@ -175,7 +175,7 @@ class DATA_PT_volume_viewport_display_slicing(DataButtonsPanel, Panel): class DATA_PT_custom_props_volume(DataButtonsPanel, PropertyPanel, Panel): - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} _context_path = "object.data" _property_type = bpy.types.Volume diff --git a/release/scripts/startup/bl_ui/properties_material.py b/release/scripts/startup/bl_ui/properties_material.py index ba6ed9ae6a2..afcd1b753d2 100644 --- a/release/scripts/startup/bl_ui/properties_material.py +++ b/release/scripts/startup/bl_ui/properties_material.py @@ -60,7 +60,7 @@ class MATERIAL_PT_preview(MaterialButtonsPanel, Panel): class MATERIAL_PT_custom_props(MaterialButtonsPanel, PropertyPanel, Panel): - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} _context_path = "material" _property_type = bpy.types.Material @@ -69,7 +69,7 @@ class EEVEE_MATERIAL_PT_context_material(MaterialButtonsPanel, Panel): bl_label = "" bl_context = "material" bl_options = {'HIDE_HEADER'} - COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -148,7 +148,7 @@ def panel_node_draw(layout, ntree, _output_type, input_name): class EEVEE_MATERIAL_PT_surface(MaterialButtonsPanel, Panel): bl_label = "Surface" bl_context = "material" - COMPAT_ENGINES = {'BLENDER_EEVEE'} + COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT'} def draw(self, context): layout = self.layout @@ -234,6 +234,32 @@ class EEVEE_MATERIAL_PT_viewport_settings(MaterialButtonsPanel, Panel): draw_material_settings(self, context) +class EEVEE_NEXT_MATERIAL_PT_settings(MaterialButtonsPanel, Panel): + bl_label = "Settings" + bl_context = "material" + COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + mat = context.material + + layout.prop(mat, "use_backface_culling") + layout.prop(mat, "blend_method") + layout.prop(mat, "shadow_method") + + row = layout.row() + row.active = ((mat.blend_method == 'CLIP') or (mat.shadow_method == 'CLIP')) + row.prop(mat, "alpha_threshold") + + if mat.blend_method not in {'OPAQUE', 'CLIP', 'HASHED'}: + layout.prop(mat, "show_transparent_back") + + layout.prop(mat, "pass_index") + + class MATERIAL_PT_viewport(MaterialButtonsPanel, Panel): bl_label = "Viewport Display" bl_context = "material" @@ -302,6 +328,7 @@ classes = ( EEVEE_MATERIAL_PT_surface, EEVEE_MATERIAL_PT_volume, EEVEE_MATERIAL_PT_settings, + EEVEE_NEXT_MATERIAL_PT_settings, MATERIAL_PT_lineart, MATERIAL_PT_viewport, EEVEE_MATERIAL_PT_viewport_settings, diff --git a/release/scripts/startup/bl_ui/properties_output.py b/release/scripts/startup/bl_ui/properties_output.py index b80a49cddbb..bc7e8bb4347 100644 --- a/release/scripts/startup/bl_ui/properties_output.py +++ b/release/scripts/startup/bl_ui/properties_output.py @@ -39,7 +39,7 @@ class RenderOutputButtonsPanel: class RENDER_PT_format(RenderOutputButtonsPanel, Panel): bl_label = "Format" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} _frame_rate_args_prev = None _preset_class = None @@ -117,7 +117,7 @@ class RENDER_PT_format(RenderOutputButtonsPanel, Panel): class RENDER_PT_frame_range(RenderOutputButtonsPanel, Panel): bl_label = "Frame Range" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -136,7 +136,7 @@ class RENDER_PT_time_stretching(RenderOutputButtonsPanel, Panel): bl_label = "Time Stretching" bl_parent_id = "RENDER_PT_frame_range" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -153,7 +153,7 @@ class RENDER_PT_time_stretching(RenderOutputButtonsPanel, Panel): class RENDER_PT_post_processing(RenderOutputButtonsPanel, Panel): bl_label = "Post Processing" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -171,7 +171,7 @@ class RENDER_PT_post_processing(RenderOutputButtonsPanel, Panel): class RENDER_PT_stamp(RenderOutputButtonsPanel, Panel): bl_label = "Metadata" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -205,7 +205,7 @@ class RENDER_PT_stamp_note(RenderOutputButtonsPanel, Panel): bl_label = "Note" bl_parent_id = "RENDER_PT_stamp" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw_header(self, context): rd = context.scene.render @@ -225,7 +225,7 @@ class RENDER_PT_stamp_burn(RenderOutputButtonsPanel, Panel): bl_label = "Burn Into Image" bl_parent_id = "RENDER_PT_stamp" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw_header(self, context): rd = context.scene.render @@ -249,7 +249,7 @@ class RENDER_PT_stamp_burn(RenderOutputButtonsPanel, Panel): class RENDER_PT_output(RenderOutputButtonsPanel, Panel): bl_label = "Output" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -278,7 +278,7 @@ class RENDER_PT_output(RenderOutputButtonsPanel, Panel): class RENDER_PT_output_views(RenderOutputButtonsPanel, Panel): bl_label = "Views" bl_parent_id = "RENDER_PT_output" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -298,7 +298,7 @@ class RENDER_PT_output_color_management(RenderOutputButtonsPanel, Panel): bl_label = "Color Management" bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "RENDER_PT_output" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): scene = context.scene @@ -333,7 +333,7 @@ class RENDER_PT_encoding(RenderOutputButtonsPanel, Panel): bl_label = "Encoding" bl_parent_id = "RENDER_PT_output" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw_header_preset(self, _context): RENDER_PT_ffmpeg_presets.draw_panel_header(self.layout) @@ -358,7 +358,7 @@ class RENDER_PT_encoding(RenderOutputButtonsPanel, Panel): class RENDER_PT_encoding_video(RenderOutputButtonsPanel, Panel): bl_label = "Video" bl_parent_id = "RENDER_PT_encoding" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -422,7 +422,7 @@ class RENDER_PT_encoding_video(RenderOutputButtonsPanel, Panel): class RENDER_PT_encoding_audio(RenderOutputButtonsPanel, Panel): bl_label = "Audio" bl_parent_id = "RENDER_PT_encoding" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -464,7 +464,7 @@ class RENDER_UL_renderviews(UIList): class RENDER_PT_stereoscopy(RenderOutputButtonsPanel, Panel): bl_label = "Stereoscopy" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} bl_options = {'DEFAULT_CLOSED'} def draw_header(self, context): diff --git a/release/scripts/startup/bl_ui/properties_particle.py b/release/scripts/startup/bl_ui/properties_particle.py index ae94accf5c7..cd390eee970 100644 --- a/release/scripts/startup/bl_ui/properties_particle.py +++ b/release/scripts/startup/bl_ui/properties_particle.py @@ -50,7 +50,7 @@ def particle_get_settings(context): class PARTICLE_MT_context_menu(Menu): bl_label = "Particle Specials" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -90,7 +90,7 @@ class PARTICLE_PT_hair_dynamics_presets(PresetPanel, Panel): preset_subdir = "hair_dynamics" preset_operator = "script.execute_preset" preset_add_operator = "particle.hair_dynamics_preset_add" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} class ParticleButtonsPanel: @@ -144,7 +144,7 @@ class PARTICLE_UL_particle_systems(bpy.types.UIList): class PARTICLE_PT_context_particles(ParticleButtonsPanel, Panel): bl_label = "" bl_options = {'HIDE_HEADER'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -237,7 +237,7 @@ class PARTICLE_PT_context_particles(ParticleButtonsPanel, Panel): class PARTICLE_PT_emission(ParticleButtonsPanel, Panel): bl_label = "Emission" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -289,7 +289,7 @@ class PARTICLE_PT_emission_source(ParticleButtonsPanel, Panel): bl_label = "Source" bl_parent_id = "PARTICLE_PT_emission" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -326,7 +326,7 @@ class PARTICLE_PT_emission_source(ParticleButtonsPanel, Panel): class PARTICLE_PT_hair_dynamics(ParticleButtonsPanel, Panel): bl_label = "Hair Dynamics" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -406,7 +406,7 @@ class PARTICLE_PT_hair_dynamics_collision(ParticleButtonsPanel, Panel): bl_label = "Collisions" bl_parent_id = "PARTICLE_PT_hair_dynamics" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -438,7 +438,7 @@ class PARTICLE_PT_hair_dynamics_structure(ParticleButtonsPanel, Panel): bl_label = "Structure" bl_parent_id = "PARTICLE_PT_hair_dynamics" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -469,7 +469,7 @@ class PARTICLE_PT_hair_dynamics_volume(ParticleButtonsPanel, Panel): bl_label = "Volume" bl_parent_id = "PARTICLE_PT_hair_dynamics" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -500,7 +500,7 @@ class PARTICLE_PT_hair_dynamics_volume(ParticleButtonsPanel, Panel): class PARTICLE_PT_cache(ParticleButtonsPanel, Panel): bl_label = "Cache" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -533,7 +533,7 @@ class PARTICLE_PT_cache(ParticleButtonsPanel, Panel): class PARTICLE_PT_velocity(ParticleButtonsPanel, Panel): bl_label = "Velocity" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -582,7 +582,7 @@ class PARTICLE_PT_velocity(ParticleButtonsPanel, Panel): class PARTICLE_PT_rotation(ParticleButtonsPanel, Panel): bl_label = "Rotation" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -637,7 +637,7 @@ class PARTICLE_PT_rotation_angular_velocity(ParticleButtonsPanel, Panel): bl_label = "Angular Velocity" bl_parent_id = "PARTICLE_PT_rotation" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -662,7 +662,7 @@ class PARTICLE_PT_rotation_angular_velocity(ParticleButtonsPanel, Panel): class PARTICLE_PT_physics(ParticleButtonsPanel, Panel): bl_label = "Physics" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -715,7 +715,7 @@ class PARTICLE_PT_physics_fluid_advanced(ParticleButtonsPanel, Panel): bl_label = "Advanced" bl_parent_id = "PARTICLE_PT_physics" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -760,7 +760,7 @@ class PARTICLE_PT_physics_fluid_springs(ParticleButtonsPanel, Panel): bl_label = "Springs" bl_parent_id = "PARTICLE_PT_physics" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -784,7 +784,7 @@ class PARTICLE_PT_physics_fluid_springs_viscoelastic(ParticleButtonsPanel, Panel bl_label = "Viscoelastic Springs" bl_parent_id = "PARTICLE_PT_physics_fluid_springs" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -820,7 +820,7 @@ class PARTICLE_PT_physics_fluid_springs_advanced(ParticleButtonsPanel, Panel): bl_label = "Advanced" bl_parent_id = "PARTICLE_PT_physics_fluid_springs" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -844,7 +844,7 @@ class PARTICLE_PT_physics_boids_movement(ParticleButtonsPanel, Panel): bl_label = "Movement" bl_parent_id = "PARTICLE_PT_physics" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -897,7 +897,7 @@ class PARTICLE_PT_physics_boids_battle(ParticleButtonsPanel, Panel): bl_label = "Battle" bl_parent_id = "PARTICLE_PT_physics" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -924,7 +924,7 @@ class PARTICLE_PT_physics_boids_misc(ParticleButtonsPanel, Panel): bl_label = "Misc" bl_parent_id = "PARTICLE_PT_physics" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -949,7 +949,7 @@ class PARTICLE_PT_physics_relations(ParticleButtonsPanel, Panel): bl_label = "Relations" bl_parent_id = "PARTICLE_PT_physics" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1004,7 +1004,7 @@ class PARTICLE_PT_physics_fluid_interaction(ParticleButtonsPanel, Panel): bl_label = "Fluid Interaction" bl_parent_id = "PARTICLE_PT_physics" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1045,7 +1045,7 @@ class PARTICLE_PT_physics_deflection(ParticleButtonsPanel, Panel): bl_label = "Deflection" bl_parent_id = "PARTICLE_PT_physics" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1071,7 +1071,7 @@ class PARTICLE_PT_physics_deflection(ParticleButtonsPanel, Panel): class PARTICLE_PT_physics_forces(ParticleButtonsPanel, Panel): bl_label = "Forces" bl_parent_id = "PARTICLE_PT_physics" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1098,7 +1098,7 @@ class PARTICLE_PT_physics_integration(ParticleButtonsPanel, Panel): bl_label = "Integration" bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "PARTICLE_PT_physics" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1132,7 +1132,7 @@ class PARTICLE_PT_boidbrain(ParticleButtonsPanel, Panel): bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "PARTICLE_PT_physics" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1230,7 +1230,7 @@ class PARTICLE_PT_boidbrain(ParticleButtonsPanel, Panel): class PARTICLE_PT_render(ParticleButtonsPanel, Panel): bl_label = "Render" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1277,7 +1277,7 @@ class PARTICLE_PT_render_extra(ParticleButtonsPanel, Panel): bl_label = "Extra" bl_parent_id = "PARTICLE_PT_render" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1301,7 +1301,7 @@ class PARTICLE_PT_render_extra(ParticleButtonsPanel, Panel): class PARTICLE_PT_render_path(ParticleButtonsPanel, Panel): bl_label = "Path" bl_parent_id = "PARTICLE_PT_render" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1323,7 +1323,7 @@ class PARTICLE_PT_render_path_timing(ParticleButtonsPanel, Panel): bl_label = "Timing" bl_parent_id = "PARTICLE_PT_render" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1351,7 +1351,7 @@ class PARTICLE_PT_render_path_timing(ParticleButtonsPanel, Panel): class PARTICLE_PT_render_object(ParticleButtonsPanel, Panel): bl_label = "Object" bl_parent_id = "PARTICLE_PT_render" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1376,7 +1376,7 @@ class PARTICLE_PT_render_object(ParticleButtonsPanel, Panel): class PARTICLE_PT_render_collection(ParticleButtonsPanel, Panel): bl_label = "Collection" bl_parent_id = "PARTICLE_PT_render" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1406,7 +1406,7 @@ class PARTICLE_PT_render_collection_use_count(ParticleButtonsPanel, Panel): bl_label = "Use Count" bl_parent_id = "PARTICLE_PT_render_collection" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1454,7 +1454,7 @@ class PARTICLE_PT_render_collection_use_count(ParticleButtonsPanel, Panel): class PARTICLE_PT_draw(ParticleButtonsPanel, Panel): bl_label = "Viewport Display" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1513,7 +1513,7 @@ class PARTICLE_PT_children(ParticleButtonsPanel, Panel): bl_label = "Children" bl_translation_context = i18n_contexts.id_particlesettings bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1566,7 +1566,7 @@ class PARTICLE_PT_children_parting(ParticleButtonsPanel, Panel): bl_label = "Parting" bl_parent_id = "PARTICLE_PT_children" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1597,7 +1597,7 @@ class PARTICLE_PT_children_clumping(ParticleButtonsPanel, Panel): bl_label = "Clumping" bl_parent_id = "PARTICLE_PT_children" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1633,7 +1633,7 @@ class PARTICLE_PT_children_clumping_noise(ParticleButtonsPanel, Panel): bl_label = "Clump Noise" bl_parent_id = "PARTICLE_PT_children_clumping" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw_header(self, context): @@ -1656,7 +1656,7 @@ class PARTICLE_PT_children_roughness(ParticleButtonsPanel, Panel): bl_label = "Roughness" bl_parent_id = "PARTICLE_PT_children" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1697,7 +1697,7 @@ class PARTICLE_PT_children_kink(ParticleButtonsPanel, Panel): bl_label = "Kink" bl_parent_id = "PARTICLE_PT_children" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1747,7 +1747,7 @@ class PARTICLE_PT_children_kink(ParticleButtonsPanel, Panel): class PARTICLE_PT_field_weights(ParticleButtonsPanel, Panel): bl_label = "Field Weights" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1768,7 +1768,7 @@ class PARTICLE_PT_field_weights(ParticleButtonsPanel, Panel): class PARTICLE_PT_force_fields(ParticleButtonsPanel, Panel): bl_label = "Force Field Settings" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -1784,7 +1784,7 @@ class PARTICLE_PT_force_fields(ParticleButtonsPanel, Panel): class PARTICLE_PT_force_fields_type1(ParticleButtonsPanel, Panel): bl_label = "Type 1" bl_parent_id = "PARTICLE_PT_force_fields" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -1801,7 +1801,7 @@ class PARTICLE_PT_force_fields_type1(ParticleButtonsPanel, Panel): class PARTICLE_PT_force_fields_type2(ParticleButtonsPanel, Panel): bl_label = "Type 2" bl_parent_id = "PARTICLE_PT_force_fields" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -1819,7 +1819,7 @@ class PARTICLE_PT_force_fields_type1_falloff(ParticleButtonsPanel, Panel): bl_label = "Falloff" bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "PARTICLE_PT_force_fields_type1" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -1835,7 +1835,7 @@ class PARTICLE_PT_force_fields_type2_falloff(ParticleButtonsPanel, Panel): bl_label = "Falloff" bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "PARTICLE_PT_force_fields_type2" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -1850,7 +1850,7 @@ class PARTICLE_PT_force_fields_type2_falloff(ParticleButtonsPanel, Panel): class PARTICLE_PT_vertexgroups(ParticleButtonsPanel, Panel): bl_label = "Vertex Groups" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1939,7 +1939,7 @@ class PARTICLE_PT_vertexgroups(ParticleButtonsPanel, Panel): class PARTICLE_PT_textures(ParticleButtonsPanel, Panel): bl_label = "Textures" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1971,7 +1971,7 @@ class PARTICLE_PT_textures(ParticleButtonsPanel, Panel): class PARTICLE_PT_hair_shape(ParticleButtonsPanel, Panel): bl_label = "Hair Shape" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1999,7 +1999,7 @@ class PARTICLE_PT_hair_shape(ParticleButtonsPanel, Panel): class PARTICLE_PT_custom_props(ParticleButtonsPanel, PropertyPanel, Panel): - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} _context_path = "particle_system.settings" _property_type = bpy.types.ParticleSettings diff --git a/release/scripts/startup/bl_ui/properties_physics_cloth.py b/release/scripts/startup/bl_ui/properties_physics_cloth.py index dd1a1fb59d6..e1d26fdcd69 100644 --- a/release/scripts/startup/bl_ui/properties_physics_cloth.py +++ b/release/scripts/startup/bl_ui/properties_physics_cloth.py @@ -35,7 +35,7 @@ class PhysicButtonsPanel: class PHYSICS_PT_cloth(PhysicButtonsPanel, Panel): bl_label = "Cloth" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw_header_preset(self, _context): CLOTH_PT_presets.draw_panel_header(self.layout) @@ -60,7 +60,7 @@ class PHYSICS_PT_cloth(PhysicButtonsPanel, Panel): class PHYSICS_PT_cloth_physical_properties(PhysicButtonsPanel, Panel): bl_label = "Physical Properties" bl_parent_id = 'PHYSICS_PT_cloth' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -84,7 +84,7 @@ class PHYSICS_PT_cloth_physical_properties(PhysicButtonsPanel, Panel): class PHYSICS_PT_cloth_stiffness(PhysicButtonsPanel, Panel): bl_label = "Stiffness" bl_parent_id = 'PHYSICS_PT_cloth_physical_properties' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -115,7 +115,7 @@ class PHYSICS_PT_cloth_stiffness(PhysicButtonsPanel, Panel): class PHYSICS_PT_cloth_damping(PhysicButtonsPanel, Panel): bl_label = "Damping" bl_parent_id = 'PHYSICS_PT_cloth_physical_properties' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -146,7 +146,7 @@ class PHYSICS_PT_cloth_damping(PhysicButtonsPanel, Panel): class PHYSICS_PT_cloth_internal_springs(PhysicButtonsPanel, Panel): bl_label = "Internal Springs" bl_parent_id = 'PHYSICS_PT_cloth_physical_properties' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw_header(self, context): cloth = context.cloth.settings @@ -188,7 +188,7 @@ class PHYSICS_PT_cloth_internal_springs(PhysicButtonsPanel, Panel): class PHYSICS_PT_cloth_pressure(PhysicButtonsPanel, Panel): bl_label = "Pressure" bl_parent_id = 'PHYSICS_PT_cloth_physical_properties' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw_header(self, context): cloth = context.cloth.settings @@ -232,7 +232,7 @@ class PHYSICS_PT_cloth_cache(PhysicButtonsPanel, Panel): bl_label = "Cache" bl_parent_id = 'PHYSICS_PT_cloth' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): md = context.cloth @@ -243,7 +243,7 @@ class PHYSICS_PT_cloth_shape(PhysicButtonsPanel, Panel): bl_label = "Shape" bl_parent_id = 'PHYSICS_PT_cloth' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -293,7 +293,7 @@ class PHYSICS_PT_cloth_collision(PhysicButtonsPanel, Panel): bl_label = "Collisions" bl_parent_id = 'PHYSICS_PT_cloth' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -313,7 +313,7 @@ class PHYSICS_PT_cloth_collision(PhysicButtonsPanel, Panel): class PHYSICS_PT_cloth_object_collision(PhysicButtonsPanel, Panel): bl_label = "Object Collisions" bl_parent_id = 'PHYSICS_PT_cloth_collision' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw_header(self, context): cloth = context.cloth.collision_settings @@ -349,7 +349,7 @@ class PHYSICS_PT_cloth_object_collision(PhysicButtonsPanel, Panel): class PHYSICS_PT_cloth_self_collision(PhysicButtonsPanel, Panel): bl_label = "Self Collisions" bl_parent_id = 'PHYSICS_PT_cloth_collision' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw_header(self, context): cloth = context.cloth.collision_settings @@ -386,7 +386,7 @@ class PHYSICS_PT_cloth_property_weights(PhysicButtonsPanel, Panel): bl_label = "Property Weights" bl_parent_id = 'PHYSICS_PT_cloth' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -440,7 +440,7 @@ class PHYSICS_PT_cloth_field_weights(PhysicButtonsPanel, Panel): bl_label = "Field Weights" bl_parent_id = 'PHYSICS_PT_cloth' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): cloth = context.cloth.settings diff --git a/release/scripts/startup/bl_ui/properties_physics_common.py b/release/scripts/startup/bl_ui/properties_physics_common.py index bb9fe6e114a..60f384a3839 100644 --- a/release/scripts/startup/bl_ui/properties_physics_common.py +++ b/release/scripts/startup/bl_ui/properties_physics_common.py @@ -50,7 +50,7 @@ def physics_add_special(layout, data, name, addop, removeop, typeicon): class PHYSICS_PT_add(PhysicButtonsPanel, Panel): bl_label = "" bl_options = {'HIDE_HEADER'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout diff --git a/release/scripts/startup/bl_ui/properties_physics_dynamicpaint.py b/release/scripts/startup/bl_ui/properties_physics_dynamicpaint.py index 5ab96bdd2c5..f71fc56a9f0 100644 --- a/release/scripts/startup/bl_ui/properties_physics_dynamicpaint.py +++ b/release/scripts/startup/bl_ui/properties_physics_dynamicpaint.py @@ -83,7 +83,7 @@ class PhysicButtonsPanel: class PHYSICS_PT_dynamic_paint(PhysicButtonsPanel, Panel): bl_label = "Dynamic Paint" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -104,7 +104,7 @@ class PHYSICS_PT_dynamic_paint(PhysicButtonsPanel, Panel): class PHYSICS_PT_dynamic_paint_settings(PhysicButtonsPanel, Panel): bl_label = "Settings" bl_parent_id = 'PHYSICS_PT_dynamic_paint' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -188,7 +188,7 @@ class PHYSICS_PT_dynamic_paint_settings(PhysicButtonsPanel, Panel): class PHYSICS_PT_dp_surface_canvas(PhysicButtonsPanel, Panel): bl_label = "Surface" bl_parent_id = "PHYSICS_PT_dynamic_paint" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -251,7 +251,7 @@ class PHYSICS_PT_dp_surface_canvas_paint_dry(PhysicButtonsPanel, Panel): bl_label = "Dry" bl_parent_id = "PHYSICS_PT_dp_surface_canvas" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -287,7 +287,7 @@ class PHYSICS_PT_dp_surface_canvas_paint_dissolve(PhysicButtonsPanel, Panel): bl_label = "Dissolve" bl_parent_id = "PHYSICS_PT_dp_surface_canvas" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -324,7 +324,7 @@ class PHYSICS_PT_dp_canvas_output(PhysicButtonsPanel, Panel): bl_label = "Output" bl_parent_id = "PHYSICS_PT_dynamic_paint" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -400,7 +400,7 @@ class PHYSICS_PT_dp_canvas_output_paintmaps(PhysicButtonsPanel, Panel): bl_label = "Paintmaps" bl_parent_id = "PHYSICS_PT_dp_canvas_output" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -430,7 +430,7 @@ class PHYSICS_PT_dp_canvas_output_wetmaps(PhysicButtonsPanel, Panel): bl_label = "Wetmaps" bl_parent_id = "PHYSICS_PT_dp_canvas_output" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -460,7 +460,7 @@ class PHYSICS_PT_dp_canvas_initial_color(PhysicButtonsPanel, Panel): bl_label = "Initial Color" bl_parent_id = "PHYSICS_PT_dynamic_paint" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -500,7 +500,7 @@ class PHYSICS_PT_dp_effects(PhysicButtonsPanel, Panel): bl_label = "Effects" bl_parent_id = 'PHYSICS_PT_dynamic_paint' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -517,7 +517,7 @@ class PHYSICS_PT_dp_effects_spread(PhysicButtonsPanel, Panel): bl_label = "Spread" bl_parent_id = "PHYSICS_PT_dp_effects" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -552,7 +552,7 @@ class PHYSICS_PT_dp_effects_drip(PhysicButtonsPanel, Panel): bl_label = "Drip" bl_parent_id = "PHYSICS_PT_dp_effects" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -588,7 +588,7 @@ class PHYSICS_PT_dp_effects_drip_weights(PhysicButtonsPanel, Panel): bl_label = "Weights" bl_parent_id = "PHYSICS_PT_dp_effects_drip" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -612,7 +612,7 @@ class PHYSICS_PT_dp_effects_shrink(PhysicButtonsPanel, Panel): bl_label = "Shrink" bl_parent_id = "PHYSICS_PT_dp_effects" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -642,7 +642,7 @@ class PHYSICS_PT_dp_cache(PhysicButtonsPanel, Panel): bl_label = "Cache" bl_parent_id = "PHYSICS_PT_dynamic_paint" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -662,7 +662,7 @@ class PHYSICS_PT_dp_cache(PhysicButtonsPanel, Panel): class PHYSICS_PT_dp_brush_source(PhysicButtonsPanel, Panel): bl_label = "Source" bl_parent_id = "PHYSICS_PT_dynamic_paint" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -725,7 +725,7 @@ class PHYSICS_PT_dp_brush_source(PhysicButtonsPanel, Panel): class PHYSICS_PT_dp_brush_source_color_ramp(PhysicButtonsPanel, Panel): bl_label = "Falloff Ramp" bl_parent_id = "PHYSICS_PT_dp_brush_source" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -752,7 +752,7 @@ class PHYSICS_PT_dp_brush_velocity(PhysicButtonsPanel, Panel): bl_label = "Velocity" bl_parent_id = "PHYSICS_PT_dynamic_paint" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -783,7 +783,7 @@ class PHYSICS_PT_dp_brush_velocity_color_ramp(PhysicButtonsPanel, Panel): bl_label = "Ramp" bl_parent_id = "PHYSICS_PT_dp_brush_velocity" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -804,7 +804,7 @@ class PHYSICS_PT_dp_brush_velocity_smudge(PhysicButtonsPanel, Panel): bl_label = "Smudge" bl_parent_id = "PHYSICS_PT_dp_brush_velocity" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -832,7 +832,7 @@ class PHYSICS_PT_dp_brush_wave(PhysicButtonsPanel, Panel): bl_label = "Waves" bl_parent_id = "PHYSICS_PT_dynamic_paint" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): diff --git a/release/scripts/startup/bl_ui/properties_physics_field.py b/release/scripts/startup/bl_ui/properties_physics_field.py index 8a1bd1a0c54..36d5dc7f68d 100644 --- a/release/scripts/startup/bl_ui/properties_physics_field.py +++ b/release/scripts/startup/bl_ui/properties_physics_field.py @@ -27,7 +27,7 @@ class PhysicButtonsPanel: class PHYSICS_PT_field(PhysicButtonsPanel, Panel): bl_label = "Force Fields" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -49,7 +49,7 @@ class PHYSICS_PT_field(PhysicButtonsPanel, Panel): class PHYSICS_PT_field_settings(PhysicButtonsPanel, Panel): bl_label = "Settings" bl_parent_id = 'PHYSICS_PT_field' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -136,7 +136,7 @@ class PHYSICS_PT_field_settings(PhysicButtonsPanel, Panel): class PHYSICS_PT_field_settings_kink(PhysicButtonsPanel, Panel): bl_label = "Kink" bl_parent_id = 'PHYSICS_PT_field_settings' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -170,7 +170,7 @@ class PHYSICS_PT_field_settings_kink(PhysicButtonsPanel, Panel): class PHYSICS_PT_field_settings_texture_select(PhysicButtonsPanel, Panel): bl_label = "Texture" bl_parent_id = 'PHYSICS_PT_field_settings' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -192,7 +192,7 @@ class PHYSICS_PT_field_settings_texture_select(PhysicButtonsPanel, Panel): class PHYSICS_PT_field_falloff(PhysicButtonsPanel, Panel): bl_label = "Falloff" bl_parent_id = "PHYSICS_PT_field" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -217,7 +217,7 @@ class PHYSICS_PT_field_falloff(PhysicButtonsPanel, Panel): class PHYSICS_PT_field_falloff_angular(PhysicButtonsPanel, Panel): bl_label = "Angular" bl_parent_id = "PHYSICS_PT_field_falloff" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -256,7 +256,7 @@ class PHYSICS_PT_field_falloff_angular(PhysicButtonsPanel, Panel): class PHYSICS_PT_field_falloff_radial(PhysicButtonsPanel, Panel): bl_label = "Radial" bl_parent_id = "PHYSICS_PT_field_falloff" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -300,7 +300,7 @@ def collision_warning(layout): class PHYSICS_PT_collision(PhysicButtonsPanel, Panel): bl_label = "Collision" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -331,7 +331,7 @@ class PHYSICS_PT_collision(PhysicButtonsPanel, Panel): class PHYSICS_PT_collision_particle(PhysicButtonsPanel, Panel): bl_label = "Particle" bl_parent_id = "PHYSICS_PT_collision" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -377,7 +377,7 @@ class PHYSICS_PT_collision_particle(PhysicButtonsPanel, Panel): class PHYSICS_PT_collision_softbody(PhysicButtonsPanel, Panel): bl_label = "Softbody & Cloth" bl_parent_id = "PHYSICS_PT_collision" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): diff --git a/release/scripts/startup/bl_ui/properties_physics_fluid.py b/release/scripts/startup/bl_ui/properties_physics_fluid.py index f1162d8935a..3cd4e8d2d0e 100644 --- a/release/scripts/startup/bl_ui/properties_physics_fluid.py +++ b/release/scripts/startup/bl_ui/properties_physics_fluid.py @@ -98,7 +98,7 @@ class PhysicButtonsPanel: class PHYSICS_PT_fluid(PhysicButtonsPanel, Panel): bl_label = "Fluid" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -122,7 +122,7 @@ class PHYSICS_PT_fluid(PhysicButtonsPanel, Panel): class PHYSICS_PT_settings(PhysicButtonsPanel, Panel): bl_label = "Settings" bl_parent_id = 'PHYSICS_PT_fluid' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -285,7 +285,7 @@ class PHYSICS_PT_settings(PhysicButtonsPanel, Panel): class PHYSICS_PT_borders(PhysicButtonsPanel, Panel): bl_label = "Border Collisions" bl_parent_id = 'PHYSICS_PT_settings' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -318,7 +318,7 @@ class PHYSICS_PT_borders(PhysicButtonsPanel, Panel): class PHYSICS_PT_smoke(PhysicButtonsPanel, Panel): bl_label = "Gas" bl_parent_id = 'PHYSICS_PT_fluid' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -351,7 +351,7 @@ class PHYSICS_PT_smoke_dissolve(PhysicButtonsPanel, Panel): bl_label = "Dissolve" bl_parent_id = 'PHYSICS_PT_smoke' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -395,7 +395,7 @@ class PHYSICS_PT_fire(PhysicButtonsPanel, Panel): bl_label = "Fire" bl_parent_id = 'PHYSICS_PT_smoke' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -434,7 +434,7 @@ class PHYSICS_PT_fire(PhysicButtonsPanel, Panel): class PHYSICS_PT_liquid(PhysicButtonsPanel, Panel): bl_label = "Liquid" bl_parent_id = 'PHYSICS_PT_fluid' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -497,7 +497,7 @@ class PHYSICS_PT_flow_source(PhysicButtonsPanel, Panel): bl_label = "Flow Source" bl_parent_id = 'PHYSICS_PT_settings' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -538,7 +538,7 @@ class PHYSICS_PT_flow_source(PhysicButtonsPanel, Panel): class PHYSICS_PT_flow_initial_velocity(PhysicButtonsPanel, Panel): bl_label = "Initial Velocity" bl_parent_id = 'PHYSICS_PT_settings' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -580,7 +580,7 @@ class PHYSICS_PT_flow_texture(PhysicButtonsPanel, Panel): bl_label = "Texture" bl_parent_id = 'PHYSICS_PT_settings' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -631,7 +631,7 @@ class PHYSICS_PT_adaptive_domain(PhysicButtonsPanel, Panel): bl_label = "Adaptive Domain" bl_parent_id = 'PHYSICS_PT_settings' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -683,7 +683,7 @@ class PHYSICS_PT_noise(PhysicButtonsPanel, Panel): bl_label = "Noise" bl_parent_id = 'PHYSICS_PT_smoke' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -763,7 +763,7 @@ class PHYSICS_PT_mesh(PhysicButtonsPanel, Panel): bl_label = "Mesh" bl_parent_id = 'PHYSICS_PT_liquid' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -858,7 +858,7 @@ class PHYSICS_PT_particles(PhysicButtonsPanel, Panel): bl_label = "Particles" bl_parent_id = 'PHYSICS_PT_liquid' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -989,7 +989,7 @@ class PHYSICS_PT_viscosity(PhysicButtonsPanel, Panel): bl_label = "Viscosity" bl_parent_id = 'PHYSICS_PT_liquid' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1029,7 +1029,7 @@ class PHYSICS_PT_diffusion(PhysicButtonsPanel, Panel): bl_label = "Diffusion" bl_parent_id = 'PHYSICS_PT_liquid' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1076,7 +1076,7 @@ class PHYSICS_PT_guide(PhysicButtonsPanel, Panel): bl_label = "Guides" bl_parent_id = 'PHYSICS_PT_fluid' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1142,7 +1142,7 @@ class PHYSICS_PT_collections(PhysicButtonsPanel, Panel): bl_label = "Collections" bl_parent_id = 'PHYSICS_PT_fluid' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1169,7 +1169,7 @@ class PHYSICS_PT_collections(PhysicButtonsPanel, Panel): class PHYSICS_PT_cache(PhysicButtonsPanel, Panel): bl_label = "Cache" bl_parent_id = 'PHYSICS_PT_fluid' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1253,7 +1253,7 @@ class PHYSICS_PT_export(PhysicButtonsPanel, Panel): bl_label = "Advanced" bl_parent_id = 'PHYSICS_PT_cache' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1298,7 +1298,7 @@ class PHYSICS_PT_field_weights(PhysicButtonsPanel, Panel): bl_label = "Field Weights" bl_parent_id = 'PHYSICS_PT_fluid' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -1487,7 +1487,7 @@ class PHYSICS_PT_fluid_domain_render(PhysicButtonsPanel, Panel): bl_label = "Render" bl_parent_id = 'PHYSICS_PT_fluid' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): diff --git a/release/scripts/startup/bl_ui/properties_physics_rigidbody.py b/release/scripts/startup/bl_ui/properties_physics_rigidbody.py index ad8f539d62c..85d1c883b50 100644 --- a/release/scripts/startup/bl_ui/properties_physics_rigidbody.py +++ b/release/scripts/startup/bl_ui/properties_physics_rigidbody.py @@ -19,7 +19,7 @@ class PHYSICS_PT_rigidbody_panel: class PHYSICS_PT_rigid_body(PHYSICS_PT_rigidbody_panel, Panel): bl_label = "Rigid Body" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -54,7 +54,7 @@ class PHYSICS_PT_rigid_body(PHYSICS_PT_rigidbody_panel, Panel): class PHYSICS_PT_rigid_body_settings(PHYSICS_PT_rigidbody_panel, Panel): bl_label = "Settings" bl_parent_id = 'PHYSICS_PT_rigid_body' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -86,7 +86,7 @@ class PHYSICS_PT_rigid_body_settings(PHYSICS_PT_rigidbody_panel, Panel): class PHYSICS_PT_rigid_body_collisions(PHYSICS_PT_rigidbody_panel, Panel): bl_label = "Collisions" bl_parent_id = 'PHYSICS_PT_rigid_body' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -136,7 +136,7 @@ class PHYSICS_PT_rigid_body_collisions_surface(PHYSICS_PT_rigidbody_panel, Panel bl_label = "Surface Response" bl_parent_id = 'PHYSICS_PT_rigid_body_collisions' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -164,7 +164,7 @@ class PHYSICS_PT_rigid_body_collisions_sensitivity(PHYSICS_PT_rigidbody_panel, P bl_label = "Sensitivity" bl_parent_id = 'PHYSICS_PT_rigid_body_collisions' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -201,7 +201,7 @@ class PHYSICS_PT_rigid_body_collisions_collections(PHYSICS_PT_rigidbody_panel, P bl_label = "Collections" bl_parent_id = 'PHYSICS_PT_rigid_body_collisions' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -223,7 +223,7 @@ class PHYSICS_PT_rigid_body_dynamics(PHYSICS_PT_rigidbody_panel, Panel): bl_label = "Dynamics" bl_parent_id = 'PHYSICS_PT_rigid_body' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -256,7 +256,7 @@ class PHYSICS_PT_rigid_body_dynamics_deactivation(PHYSICS_PT_rigidbody_panel, Pa bl_label = "Deactivation" bl_parent_id = 'PHYSICS_PT_rigid_body_dynamics' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): diff --git a/release/scripts/startup/bl_ui/properties_physics_rigidbody_constraint.py b/release/scripts/startup/bl_ui/properties_physics_rigidbody_constraint.py index 5dc98674b99..12b64abec8f 100644 --- a/release/scripts/startup/bl_ui/properties_physics_rigidbody_constraint.py +++ b/release/scripts/startup/bl_ui/properties_physics_rigidbody_constraint.py @@ -13,7 +13,7 @@ class PHYSICS_PT_rigidbody_constraint_panel: class PHYSICS_PT_rigid_body_constraint(PHYSICS_PT_rigidbody_constraint_panel, Panel): bl_label = "Rigid Body Constraint" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -33,7 +33,7 @@ class PHYSICS_PT_rigid_body_constraint(PHYSICS_PT_rigidbody_constraint_panel, Pa class PHYSICS_PT_rigid_body_constraint_settings(PHYSICS_PT_rigidbody_constraint_panel, Panel): bl_label = "Settings" bl_parent_id = 'PHYSICS_PT_rigid_body_constraint' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -64,7 +64,7 @@ class PHYSICS_PT_rigid_body_constraint_settings(PHYSICS_PT_rigidbody_constraint_ class PHYSICS_PT_rigid_body_constraint_objects(PHYSICS_PT_rigidbody_constraint_panel, Panel): bl_label = "Objects" bl_parent_id = 'PHYSICS_PT_rigid_body_constraint' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -85,7 +85,7 @@ class PHYSICS_PT_rigid_body_constraint_objects(PHYSICS_PT_rigidbody_constraint_p class PHYSICS_PT_rigid_body_constraint_override_iterations(PHYSICS_PT_rigidbody_constraint_panel, Panel): bl_label = "Override Iterations" bl_parent_id = 'PHYSICS_PT_rigid_body_constraint' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -111,7 +111,7 @@ class PHYSICS_PT_rigid_body_constraint_override_iterations(PHYSICS_PT_rigidbody_ class PHYSICS_PT_rigid_body_constraint_limits(PHYSICS_PT_rigidbody_constraint_panel, Panel): bl_label = "Limits" bl_parent_id = 'PHYSICS_PT_rigid_body_constraint' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -128,7 +128,7 @@ class PHYSICS_PT_rigid_body_constraint_limits(PHYSICS_PT_rigidbody_constraint_pa class PHYSICS_PT_rigid_body_constraint_limits_linear(PHYSICS_PT_rigidbody_constraint_panel, Panel): bl_label = "Linear" bl_parent_id = 'PHYSICS_PT_rigid_body_constraint_limits' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -185,7 +185,7 @@ class PHYSICS_PT_rigid_body_constraint_limits_linear(PHYSICS_PT_rigidbody_constr class PHYSICS_PT_rigid_body_constraint_limits_angular(PHYSICS_PT_rigidbody_constraint_panel, Panel): bl_label = "Angular" bl_parent_id = 'PHYSICS_PT_rigid_body_constraint_limits' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -251,7 +251,7 @@ class PHYSICS_PT_rigid_body_constraint_limits_angular(PHYSICS_PT_rigidbody_const class PHYSICS_PT_rigid_body_constraint_motor(PHYSICS_PT_rigidbody_constraint_panel, Panel): bl_label = "Motor" bl_parent_id = 'PHYSICS_PT_rigid_body_constraint' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -268,7 +268,7 @@ class PHYSICS_PT_rigid_body_constraint_motor(PHYSICS_PT_rigidbody_constraint_pan class PHYSICS_PT_rigid_body_constraint_motor_angular(PHYSICS_PT_rigidbody_constraint_panel, Panel): bl_label = "Angular" bl_parent_id = 'PHYSICS_PT_rigid_body_constraint_motor' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -304,7 +304,7 @@ class PHYSICS_PT_rigid_body_constraint_motor_angular(PHYSICS_PT_rigidbody_constr class PHYSICS_PT_rigid_body_constraint_motor_linear(PHYSICS_PT_rigidbody_constraint_panel, Panel): bl_label = "Linear" bl_parent_id = 'PHYSICS_PT_rigid_body_constraint_motor' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -340,7 +340,7 @@ class PHYSICS_PT_rigid_body_constraint_motor_linear(PHYSICS_PT_rigidbody_constra class PHYSICS_PT_rigid_body_constraint_springs(PHYSICS_PT_rigidbody_constraint_panel, Panel): bl_label = "Springs" bl_parent_id = 'PHYSICS_PT_rigid_body_constraint' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -364,7 +364,7 @@ class PHYSICS_PT_rigid_body_constraint_springs(PHYSICS_PT_rigidbody_constraint_p class PHYSICS_PT_rigid_body_constraint_springs_angular(PHYSICS_PT_rigidbody_constraint_panel, Panel): bl_label = "Angular" bl_parent_id = 'PHYSICS_PT_rigid_body_constraint_springs' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -412,7 +412,7 @@ class PHYSICS_PT_rigid_body_constraint_springs_angular(PHYSICS_PT_rigidbody_cons class PHYSICS_PT_rigid_body_constraint_springs_linear(PHYSICS_PT_rigidbody_constraint_panel, Panel): bl_label = "Linear" bl_parent_id = 'PHYSICS_PT_rigid_body_constraint_springs' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): diff --git a/release/scripts/startup/bl_ui/properties_physics_softbody.py b/release/scripts/startup/bl_ui/properties_physics_softbody.py index bde7778e54c..14211d35261 100644 --- a/release/scripts/startup/bl_ui/properties_physics_softbody.py +++ b/release/scripts/startup/bl_ui/properties_physics_softbody.py @@ -28,7 +28,7 @@ class PhysicButtonsPanel: class PHYSICS_PT_softbody(PhysicButtonsPanel, Panel): bl_label = "Soft Body" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -44,7 +44,7 @@ class PHYSICS_PT_softbody_object(PhysicButtonsPanel, Panel): bl_label = "Object" bl_parent_id = 'PHYSICS_PT_softbody' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -72,7 +72,7 @@ class PHYSICS_PT_softbody_simulation(PhysicButtonsPanel, Panel): bl_label = "Simulation" bl_parent_id = 'PHYSICS_PT_softbody' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -90,7 +90,7 @@ class PHYSICS_PT_softbody_cache(PhysicButtonsPanel, Panel): bl_label = "Cache" bl_parent_id = 'PHYSICS_PT_softbody' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): md = context.soft_body @@ -101,7 +101,7 @@ class PHYSICS_PT_softbody_goal(PhysicButtonsPanel, Panel): bl_label = "Goal" bl_parent_id = 'PHYSICS_PT_softbody' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw_header(self, context): softbody = context.soft_body.settings @@ -126,7 +126,7 @@ class PHYSICS_PT_softbody_goal_strengths(PhysicButtonsPanel, Panel): bl_label = "Strengths" bl_parent_id = 'PHYSICS_PT_softbody_goal' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -152,7 +152,7 @@ class PHYSICS_PT_softbody_goal_settings(PhysicButtonsPanel, Panel): bl_label = "Settings" bl_parent_id = 'PHYSICS_PT_softbody_goal' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -175,7 +175,7 @@ class PHYSICS_PT_softbody_edge(PhysicButtonsPanel, Panel): bl_label = "Edges" bl_parent_id = 'PHYSICS_PT_softbody' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw_header(self, context): softbody = context.soft_body.settings @@ -222,7 +222,7 @@ class PHYSICS_PT_softbody_edge_aerodynamics(PhysicButtonsPanel, Panel): bl_label = "Aerodynamics" bl_parent_id = 'PHYSICS_PT_softbody_edge' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -245,7 +245,7 @@ class PHYSICS_PT_softbody_edge_stiffness(PhysicButtonsPanel, Panel): bl_label = "Stiffness" bl_parent_id = 'PHYSICS_PT_softbody_edge' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw_header(self, context): softbody = context.soft_body.settings @@ -269,7 +269,7 @@ class PHYSICS_PT_softbody_collision(PhysicButtonsPanel, Panel): bl_label = "Self Collision" bl_parent_id = 'PHYSICS_PT_softbody' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw_header(self, context): softbody = context.soft_body.settings @@ -304,7 +304,7 @@ class PHYSICS_PT_softbody_solver(PhysicButtonsPanel, Panel): bl_label = "Solver" bl_parent_id = 'PHYSICS_PT_softbody' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -329,7 +329,7 @@ class PHYSICS_PT_softbody_solver_diagnostics(PhysicButtonsPanel, Panel): bl_label = "Diagnostics" bl_parent_id = 'PHYSICS_PT_softbody_solver' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -348,7 +348,7 @@ class PHYSICS_PT_softbody_solver_helpers(PhysicButtonsPanel, Panel): bl_label = "Helpers" bl_parent_id = 'PHYSICS_PT_softbody_solver' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -371,7 +371,7 @@ class PHYSICS_PT_softbody_field_weights(PhysicButtonsPanel, Panel): bl_label = "Field Weights" bl_parent_id = 'PHYSICS_PT_softbody' bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): md = context.soft_body diff --git a/release/scripts/startup/bl_ui/properties_render.py b/release/scripts/startup/bl_ui/properties_render.py index 2f138ea26bd..3365a68b9b2 100644 --- a/release/scripts/startup/bl_ui/properties_render.py +++ b/release/scripts/startup/bl_ui/properties_render.py @@ -47,7 +47,7 @@ class RENDER_PT_color_management(RenderButtonsPanel, Panel): bl_label = "Color Management" bl_options = {'DEFAULT_CLOSED'} bl_order = 100 - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -80,7 +80,7 @@ class RENDER_PT_color_management_curves(RenderButtonsPanel, Panel): bl_label = "Use Curves" bl_parent_id = "RENDER_PT_color_management" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw_header(self, context): @@ -543,7 +543,7 @@ def draw_curves_settings(self, context): class RENDER_PT_eevee_hair(RenderButtonsPanel, Panel): bl_label = "Curves" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_EEVEE'} + COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT'} @classmethod def poll(cls, context): @@ -556,7 +556,7 @@ class RENDER_PT_eevee_hair(RenderButtonsPanel, Panel): class RENDER_PT_eevee_performance(RenderButtonsPanel, Panel): bl_label = "Performance" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -577,7 +577,7 @@ class RENDER_PT_gpencil(RenderButtonsPanel, Panel): bl_label = "Grease Pencil" bl_options = {'DEFAULT_CLOSED'} bl_order = 10 - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -665,7 +665,7 @@ class RENDER_PT_opengl_options(RenderButtonsPanel, Panel): class RENDER_PT_simplify(RenderButtonsPanel, Panel): bl_label = "Simplify" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw_header(self, context): rd = context.scene.render @@ -678,7 +678,7 @@ class RENDER_PT_simplify(RenderButtonsPanel, Panel): class RENDER_PT_simplify_viewport(RenderButtonsPanel, Panel): bl_label = "Viewport" bl_parent_id = "RENDER_PT_simplify" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -703,7 +703,7 @@ class RENDER_PT_simplify_viewport(RenderButtonsPanel, Panel): class RENDER_PT_simplify_render(RenderButtonsPanel, Panel): bl_label = "Render" bl_parent_id = "RENDER_PT_simplify" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -725,7 +725,7 @@ class RENDER_PT_simplify_render(RenderButtonsPanel, Panel): class RENDER_PT_simplify_greasepencil(RenderButtonsPanel, Panel, GreasePencilSimplifyPanel): bl_label = "Grease Pencil" bl_parent_id = "RENDER_PT_simplify" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_GAME', 'BLENDER_CLAY', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_GAME', 'BLENDER_CLAY', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} bl_options = {'DEFAULT_CLOSED'} diff --git a/release/scripts/startup/bl_ui/properties_texture.py b/release/scripts/startup/bl_ui/properties_texture.py index 106ea76ccc8..d9c51397d6e 100644 --- a/release/scripts/startup/bl_ui/properties_texture.py +++ b/release/scripts/startup/bl_ui/properties_texture.py @@ -67,7 +67,7 @@ class TextureButtonsPanel: class TEXTURE_PT_preview(TextureButtonsPanel, Panel): bl_label = "Preview" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -96,7 +96,7 @@ class TEXTURE_PT_context(TextureButtonsPanel, Panel): bl_label = "" bl_context = "texture" bl_options = {'HIDE_HEADER'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -135,7 +135,7 @@ class TEXTURE_PT_context(TextureButtonsPanel, Panel): class TEXTURE_PT_node(TextureButtonsPanel, Panel): bl_label = "Node" bl_context = "texture" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -164,7 +164,7 @@ class TextureTypePanel(TextureButtonsPanel): class TEXTURE_PT_clouds(TextureTypePanel, Panel): bl_label = "Clouds" tex_type = 'CLOUDS' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -196,7 +196,7 @@ class TEXTURE_PT_clouds(TextureTypePanel, Panel): class TEXTURE_PT_wood(TextureTypePanel, Panel): bl_label = "Wood" tex_type = 'WOOD' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -233,7 +233,7 @@ class TEXTURE_PT_wood(TextureTypePanel, Panel): class TEXTURE_PT_marble(TextureTypePanel, Panel): bl_label = "Marble" tex_type = 'MARBLE' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -267,7 +267,7 @@ class TEXTURE_PT_marble(TextureTypePanel, Panel): class TEXTURE_PT_magic(TextureTypePanel, Panel): bl_label = "Magic" tex_type = 'MAGIC' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -286,7 +286,7 @@ class TEXTURE_PT_magic(TextureTypePanel, Panel): class TEXTURE_PT_blend(TextureTypePanel, Panel): bl_label = "Blend" tex_type = 'BLEND' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -308,7 +308,7 @@ class TEXTURE_PT_blend(TextureTypePanel, Panel): class TEXTURE_PT_stucci(TextureTypePanel, Panel): bl_label = "Stucci" tex_type = 'STUCCI' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -339,7 +339,7 @@ class TEXTURE_PT_stucci(TextureTypePanel, Panel): class TEXTURE_PT_image(TextureTypePanel, Panel): bl_label = "Image" tex_type = 'IMAGE' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, _context): # TODO: maybe expose the template_ID from the template image here. @@ -351,7 +351,7 @@ class TEXTURE_PT_image_settings(TextureTypePanel, Panel): bl_label = "Settings" bl_parent_id = 'TEXTURE_PT_image' tex_type = 'IMAGE' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -506,7 +506,7 @@ class TEXTURE_PT_image_mapping_crop(TextureTypePanel, Panel): class TEXTURE_PT_musgrave(TextureTypePanel, Panel): bl_label = "Musgrave" tex_type = 'MUSGRAVE' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -551,7 +551,7 @@ class TEXTURE_PT_musgrave(TextureTypePanel, Panel): class TEXTURE_PT_voronoi(TextureTypePanel, Panel): bl_label = "Voronoi" tex_type = 'VORONOI' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -584,7 +584,7 @@ class TEXTURE_PT_voronoi_feature_weights(TextureTypePanel, Panel): bl_label = "Feature Weights" bl_parent_id = "TEXTURE_PT_voronoi" tex_type = 'VORONOI' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -605,7 +605,7 @@ class TEXTURE_PT_voronoi_feature_weights(TextureTypePanel, Panel): class TEXTURE_PT_distortednoise(TextureTypePanel, Panel): bl_label = "Distorted Noise" tex_type = 'DISTORTED_NOISE' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -630,7 +630,7 @@ class TEXTURE_PT_distortednoise(TextureTypePanel, Panel): class TextureSlotPanel(TextureButtonsPanel): - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -642,7 +642,7 @@ class TextureSlotPanel(TextureButtonsPanel): class TEXTURE_PT_mapping(TextureSlotPanel, Panel): bl_label = "Mapping" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -710,7 +710,7 @@ class TEXTURE_PT_mapping(TextureSlotPanel, Panel): class TEXTURE_PT_influence(TextureSlotPanel, Panel): bl_label = "Influence" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} @classmethod def poll(cls, context): @@ -792,7 +792,7 @@ class TextureColorsPoll: class TEXTURE_PT_colors(TextureButtonsPanel, TextureColorsPoll, Panel): bl_label = "Colors" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -821,7 +821,7 @@ class TEXTURE_PT_colors_ramp(TextureButtonsPanel, TextureColorsPoll, Panel): bl_label = "Color Ramp" bl_options = {'DEFAULT_CLOSED'} bl_parent_id = 'TEXTURE_PT_colors' - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw_header(self, context): tex = context.texture @@ -842,7 +842,7 @@ class TEXTURE_PT_colors_ramp(TextureButtonsPanel, TextureColorsPoll, Panel): class TEXTURE_PT_custom_props(TextureButtonsPanel, PropertyPanel, Panel): - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} _context_path = "texture" _property_type = Texture diff --git a/release/scripts/startup/bl_ui/properties_view_layer.py b/release/scripts/startup/bl_ui/properties_view_layer.py index 01ded1ceb9b..01bd0adc8df 100644 --- a/release/scripts/startup/bl_ui/properties_view_layer.py +++ b/release/scripts/startup/bl_ui/properties_view_layer.py @@ -24,7 +24,7 @@ class ViewLayerButtonsPanel: class VIEWLAYER_PT_layer(ViewLayerButtonsPanel, Panel): bl_label = "View Layer" - COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH'} def draw(self, context): layout = self.layout @@ -42,7 +42,7 @@ class VIEWLAYER_PT_layer(ViewLayerButtonsPanel, Panel): class VIEWLAYER_PT_layer_passes(ViewLayerButtonsPanel, Panel): bl_label = "Passes" - COMPAT_ENGINES = {'BLENDER_EEVEE'} + COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT'} def draw(self, context): pass @@ -68,10 +68,32 @@ class VIEWLAYER_PT_eevee_layer_passes_data(ViewLayerButtonsPanel, Panel): col.prop(view_layer, "use_pass_normal") +class VIEWLAYER_PT_eevee_next_layer_passes_data(ViewLayerButtonsPanel, Panel): + bl_label = "Data" + bl_parent_id = "VIEWLAYER_PT_layer_passes" + + COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + view_layer = context.view_layer + + col = layout.column() + col.prop(view_layer, "use_pass_combined") + col.prop(view_layer, "use_pass_z") + col.prop(view_layer, "use_pass_mist") + col.prop(view_layer, "use_pass_normal") + col.prop(view_layer, "use_pass_position") + col.prop(view_layer, "use_pass_vector") + + class VIEWLAYER_PT_eevee_layer_passes_light(ViewLayerButtonsPanel, Panel): bl_label = "Light" bl_parent_id = "VIEWLAYER_PT_layer_passes" - COMPAT_ENGINES = {'BLENDER_EEVEE'} + COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT'} def draw(self, context): layout = self.layout @@ -151,7 +173,7 @@ class ViewLayerAOVPanel(ViewLayerButtonsPanel, Panel): class VIEWLAYER_PT_layer_passes_aov(ViewLayerAOVPanel): bl_parent_id = "VIEWLAYER_PT_layer_passes" - COMPAT_ENGINES = {'BLENDER_EEVEE'} + COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT'} class ViewLayerCryptomattePanel(ViewLayerButtonsPanel, Panel): @@ -228,6 +250,7 @@ classes = ( VIEWLAYER_MT_lightgroup_sync, VIEWLAYER_PT_layer, VIEWLAYER_PT_layer_passes, + VIEWLAYER_PT_eevee_next_layer_passes_data, VIEWLAYER_PT_eevee_layer_passes_data, VIEWLAYER_PT_eevee_layer_passes_light, VIEWLAYER_PT_eevee_layer_passes_effects, -- cgit v1.2.3 From f814871e8170e53b9eec0e166a7363cc7d210d5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Mon, 25 Jul 2022 09:44:15 +0200 Subject: EEVEE-Next: Fix some Material compilation errors --- .../eevee_next/shaders/eevee_attributes_lib.glsl | 42 +-------------------- .../eevee_next/shaders/eevee_nodetree_lib.glsl | 44 ++++++++++++++++++++++ .../eevee_next/shaders/eevee_surf_depth_frag.glsl | 12 ++++++ 3 files changed, 58 insertions(+), 40 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_attributes_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_attributes_lib.glsl index 326481a1db6..974581e674e 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_attributes_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_attributes_lib.glsl @@ -3,6 +3,8 @@ #pragma BLENDER_REQUIRE(common_math_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl) +#defined EEVEE_ATTRIBUTE_LIB + #if defined(MAT_GEOM_MESH) /* -------------------------------------------------------------------- */ @@ -282,43 +284,3 @@ vec3 attr_load_uv(vec3 attr) /** \} */ #endif - -/* -------------------------------------------------------------------- */ -/** \name Volume Attribute post - * - * TODO(@fclem): These implementation details should concern the DRWManager and not be a fix on - * the engine side. But as of now, the engines are responsible for loading the attributes. - * - * \{ */ - -#if defined(MAT_GEOM_VOLUME) - -float attr_load_temperature_post(float attr) -{ - /* Bring the into standard range without having to modify the grid values */ - attr = (attr > 0.01) ? (attr * drw_volume.temperature_mul + drw_volume.temperature_bias) : 0.0; - return attr; -} -vec4 attr_load_color_post(vec4 attr) -{ - /* Density is premultiplied for interpolation, divide it out here. */ - attr.rgb *= safe_rcp(attr.a); - attr.rgb *= drw_volume.color_mul.rgb; - attr.a = 1.0; - return attr; -} - -#else /* Noop for any other surface. */ - -float attr_load_temperature_post(float attr) -{ - return attr; -} -vec4 attr_load_color_post(vec4 attr) -{ - return attr; -} - -#endif - -/** \} */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl index 71921d0477a..c488216eeac 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl @@ -269,6 +269,10 @@ void output_aov(vec4 color, float value, uint hash) # define nodetree_thickness() 0.1 #endif +#ifdef GPU_VERTEX_SHADER +# define closure_to_rgba(a) vec4(0.0) +#endif + /* -------------------------------------------------------------------- */ /** \name Fragment Displacement * @@ -373,3 +377,43 @@ vec3 coordinate_incoming(vec3 P) } /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Volume Attribute post + * + * TODO(@fclem): These implementation details should concern the DRWManager and not be a fix on + * the engine side. But as of now, the engines are responsible for loading the attributes. + * + * \{ */ + +#if defined(MAT_GEOM_VOLUME) + +float attr_load_temperature_post(float attr) +{ + /* Bring the into standard range without having to modify the grid values */ + attr = (attr > 0.01) ? (attr * drw_volume.temperature_mul + drw_volume.temperature_bias) : 0.0; + return attr; +} +vec4 attr_load_color_post(vec4 attr) +{ + /* Density is premultiplied for interpolation, divide it out here. */ + attr.rgb *= safe_rcp(attr.a); + attr.rgb *= drw_volume.color_mul.rgb; + attr.a = 1.0; + return attr; +} + +#else /* Noop for any other surface. */ + +float attr_load_temperature_post(float attr) +{ + return attr; +} +vec4 attr_load_color_post(vec4 attr) +{ + return attr; +} + +#endif + +/** \} */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_depth_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_depth_frag.glsl index f19b6038a6a..34ea288852a 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_depth_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_depth_frag.glsl @@ -10,6 +10,18 @@ #pragma BLENDER_REQUIRE(eevee_surf_lib.glsl) #pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl) +vec4 closure_to_rgba(Closure cl) +{ + vec4 out_color; + out_color.rgb = g_emission; + out_color.a = saturate(1.0 - avg(g_transmittance)); + + /* Reset for the next closure tree. */ + closure_weights_reset(); + + return out_color; +} + /* From the paper "Hashed Alpha Testing" by Chris Wyman and Morgan McGuire. */ float hash(vec2 a) { -- cgit v1.2.3 From c5394f3db83fe189316f420b0613fb00ceefb7ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Mon, 25 Jul 2022 11:25:17 +0200 Subject: EEVEE-Next: Fix float3 passes being incorrect --- source/blender/draw/engines/eevee_next/eevee_film.cc | 11 ++++++++++- source/blender/draw/engines/eevee_next/eevee_film.hh | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/source/blender/draw/engines/eevee_next/eevee_film.cc b/source/blender/draw/engines/eevee_next/eevee_film.cc index 18e0452da25..49f43265aa8 100644 --- a/source/blender/draw/engines/eevee_next/eevee_film.cc +++ b/source/blender/draw/engines/eevee_next/eevee_film.cc @@ -620,7 +620,16 @@ float *Film::read_pass(eViewLayerEEVEEPassType pass_type) GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE); - return (float *)GPU_texture_read(pass_tx, GPU_DATA_FLOAT, 0); + float *result = (float *)GPU_texture_read(pass_tx, GPU_DATA_FLOAT, 0); + + if (pass_is_float3(pass_type)) { + /* Convert result in place as we cannot do this conversion on GPU. */ + for (auto px : IndexRange(accum_tx.width() * accum_tx.height())) { + *(reinterpret_cast(result) + px) = *(reinterpret_cast(result + px * 4)); + } + } + + return result; } /** \} */ diff --git a/source/blender/draw/engines/eevee_next/eevee_film.hh b/source/blender/draw/engines/eevee_next/eevee_film.hh index 124fd79a1c0..1165b9a4c12 100644 --- a/source/blender/draw/engines/eevee_next/eevee_film.hh +++ b/source/blender/draw/engines/eevee_next/eevee_film.hh @@ -109,6 +109,23 @@ class Film { } } + static bool pass_is_float3(eViewLayerEEVEEPassType pass_type) + { + switch (pass_type) { + case EEVEE_RENDER_PASS_NORMAL: + case EEVEE_RENDER_PASS_DIFFUSE_LIGHT: + case EEVEE_RENDER_PASS_DIFFUSE_COLOR: + case EEVEE_RENDER_PASS_SPECULAR_LIGHT: + case EEVEE_RENDER_PASS_SPECULAR_COLOR: + case EEVEE_RENDER_PASS_VOLUME_LIGHT: + case EEVEE_RENDER_PASS_EMIT: + case EEVEE_RENDER_PASS_ENVIRONMENT: + return true; + default: + return false; + } + } + /* Returns layer offset in the accumulation texture. -1 if the pass is not enabled. */ int pass_id_get(eViewLayerEEVEEPassType pass_type) const { -- cgit v1.2.3 From 53113a2e571972e835f43217fea217066892e435 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 25 Jul 2022 11:32:27 +0200 Subject: Geometry: detect when the sample uv is in multiple triangles --- source/blender/geometry/intern/reverse_uv_sampler.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/blender/geometry/intern/reverse_uv_sampler.cc b/source/blender/geometry/intern/reverse_uv_sampler.cc index 54df43db4ea..39fec40333c 100644 --- a/source/blender/geometry/intern/reverse_uv_sampler.cc +++ b/source/blender/geometry/intern/reverse_uv_sampler.cc @@ -67,9 +67,9 @@ ReverseUVSampler::Result ReverseUVSampler::sample(const float2 &query_uv) const const float z_dist = std::max(-bary_weights.z, bary_weights.z - 1.0f); const float dist = MAX3(x_dist, y_dist, z_dist); - if (dist <= 0.0f) { - /* Return early if the uv coordinate is in the triangle. */ - return Result{ResultType::Ok, &looptri, bary_weights}; + if (dist <= 0.0f && best_dist <= 0.0f) { + /* The uv sample is in multiple triangles. */ + return Result{ResultType::Multiple}; } if (dist < best_dist) { -- cgit v1.2.3 From 1c05f30e4dcbf05a7dc1a700cd76dc8c3d70601d Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 25 Jul 2022 11:42:27 +0200 Subject: Curves: add warning when invalid uv map is used when adding curves UV maps that are used for surface attachment must not have overlapping uv islands, because then the same uv coordinate would correspond to multiple surface positions. Ref T99936. --- source/blender/editors/sculpt_paint/curves_sculpt_add.cc | 7 ++++++- source/blender/editors/sculpt_paint/curves_sculpt_brush.cc | 5 +++++ source/blender/editors/sculpt_paint/curves_sculpt_density.cc | 7 ++++++- source/blender/editors/sculpt_paint/curves_sculpt_intern.hh | 1 + source/blender/geometry/GEO_add_curves_on_mesh.hh | 9 ++++++++- source/blender/geometry/intern/add_curves_on_mesh.cc | 8 +++++++- 6 files changed, 33 insertions(+), 4 deletions(-) diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc index 2757701675b..6c693376ad3 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc @@ -231,7 +231,12 @@ struct AddOperationExecutor { add_inputs.old_roots_kdtree = self_->curve_roots_kdtree_; } - geometry::add_curves_on_mesh(*curves_orig_, add_inputs); + const geometry::AddCurvesOnMeshOutputs add_outputs = geometry::add_curves_on_mesh( + *curves_orig_, add_inputs); + + if (add_outputs.uv_error) { + report_invalid_uv_map(stroke_extension.reports); + } DEG_id_tag_update(&curves_id_orig_->id, ID_RECALC_GEOMETRY); WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_orig_->id); diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc b/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc index 9803c6fdcc6..a180a232189 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_brush.cc @@ -408,4 +408,9 @@ void report_missing_uv_map_on_evaluated_surface(ReportList *reports) reports, RPT_WARNING, TIP_("Missing UV map for attaching curves on evaluated surface")); } +void report_invalid_uv_map(ReportList *reports) +{ + BKE_report(reports, RPT_WARNING, TIP_("Invalid UV map: UV islands must not overlap")); +} + } // namespace blender::ed::sculpt_paint diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc index b07f5c74055..6ad3d0e4aad 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc @@ -277,7 +277,12 @@ struct DensityAddOperationExecutor { add_inputs.reverse_uv_sampler = &reverse_uv_sampler; add_inputs.old_roots_kdtree = self_->original_curve_roots_kdtree_; - geometry::add_curves_on_mesh(*curves_orig_, add_inputs); + const geometry::AddCurvesOnMeshOutputs add_outputs = geometry::add_curves_on_mesh( + *curves_orig_, add_inputs); + + if (add_outputs.uv_error) { + report_invalid_uv_map(stroke_extension.reports); + } DEG_id_tag_update(&curves_id_orig_->id, ID_RECALC_GEOMETRY); WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_orig_->id); diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh index 4bb00a7d621..7d40ed80a65 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh +++ b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh @@ -131,5 +131,6 @@ float transform_brush_radius(const float4x4 &transform, void report_missing_surface(ReportList *reports); void report_missing_uv_map_on_original_surface(ReportList *reports); void report_missing_uv_map_on_evaluated_surface(ReportList *reports); +void report_invalid_uv_map(ReportList *reports); } // namespace blender::ed::sculpt_paint diff --git a/source/blender/geometry/GEO_add_curves_on_mesh.hh b/source/blender/geometry/GEO_add_curves_on_mesh.hh index 68c8dc5b76b..2d2ba89a18f 100644 --- a/source/blender/geometry/GEO_add_curves_on_mesh.hh +++ b/source/blender/geometry/GEO_add_curves_on_mesh.hh @@ -40,12 +40,19 @@ struct AddCurvesOnMeshInputs { * interpolation is used. */ KDTree_3d *old_roots_kdtree = nullptr; + + bool r_uv_error = false; +}; + +struct AddCurvesOnMeshOutputs { + bool uv_error = false; }; /** * Generate new curves on a mesh surface with the given inputs. Existing curves stay intact. */ -void add_curves_on_mesh(bke::CurvesGeometry &curves, const AddCurvesOnMeshInputs &inputs); +AddCurvesOnMeshOutputs add_curves_on_mesh(bke::CurvesGeometry &curves, + const AddCurvesOnMeshInputs &inputs); float3 compute_surface_point_normal(const MLoopTri &looptri, const float3 &bary_coord, diff --git a/source/blender/geometry/intern/add_curves_on_mesh.cc b/source/blender/geometry/intern/add_curves_on_mesh.cc index aa04cedb5c5..7184d774a22 100644 --- a/source/blender/geometry/intern/add_curves_on_mesh.cc +++ b/source/blender/geometry/intern/add_curves_on_mesh.cc @@ -229,8 +229,11 @@ static void interpolate_position_with_interpolation(CurvesGeometry &curves, }); } -void add_curves_on_mesh(CurvesGeometry &curves, const AddCurvesOnMeshInputs &inputs) +AddCurvesOnMeshOutputs add_curves_on_mesh(CurvesGeometry &curves, + const AddCurvesOnMeshInputs &inputs) { + AddCurvesOnMeshOutputs outputs; + const bool use_interpolation = inputs.interpolate_length || inputs.interpolate_point_count || inputs.interpolate_shape; @@ -244,6 +247,7 @@ void add_curves_on_mesh(CurvesGeometry &curves, const AddCurvesOnMeshInputs &inp const float2 &uv = inputs.uvs[i]; const ReverseUVSampler::Result result = inputs.reverse_uv_sampler->sample(uv); if (result.type != ReverseUVSampler::ResultType::Ok) { + outputs.uv_error = true; continue; } const MLoopTri &looptri = *result.looptri; @@ -365,6 +369,8 @@ void add_curves_on_mesh(CurvesGeometry &curves, const AddCurvesOnMeshInputs &inp MutableSpan types_span = curves.curve_types_for_write(); types_span.drop_front(old_curves_num).fill(CURVE_TYPE_CATMULL_ROM); curves.update_curve_types(); + + return outputs; } } // namespace blender::geometry -- cgit v1.2.3 From c5afef12240d142bce3a22498e3aee5e4656566d Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Mon, 25 Jul 2022 11:42:10 +0200 Subject: Fix missing disabled hint when dragging from Asset Browser in edit mode When dragging assets into the 3D View while in any other mode than object mode, dropping would be disabled and the cursor would indicate that. However there was supposed to be an "Only supported in object mode" message, that similar operators showed, but got forgotten when this one was introduced. --- source/blender/editors/object/object_add.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc index 9c34e203bee..e9abf8c1441 100644 --- a/source/blender/editors/object/object_add.cc +++ b/source/blender/editors/object/object_add.cc @@ -3993,7 +3993,7 @@ void OBJECT_OT_transform_to_mouse(wmOperatorType *ot) /* api callbacks */ ot->invoke = object_add_drop_xy_generic_invoke; ot->exec = object_transform_to_mouse_exec; - ot->poll = ED_operator_objectmode; + ot->poll = ED_operator_objectmode_poll_msg; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -- cgit v1.2.3 From 6f1cdcba8528753ba6be0869f342b0ba00a0f9db Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 25 Jul 2022 11:54:36 +0200 Subject: Fix T99929: lattice modifier looks up vertex group index in wrong place It looked up the vertex group index based on the object instead of the actual mesh that is currently used. Since geometry nodes, the number and order of attributes can change in arbitrary ways during evaluation. Therefore, this index has to be looked up on the mesh which contains the most up-to-date information. There are probably similar issues in other modifiers. That has to be fixed step by step. Ideally by using the attribute api directly eventually. --- source/blender/blenkernel/intern/lattice_deform.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/blender/blenkernel/intern/lattice_deform.c b/source/blender/blenkernel/intern/lattice_deform.c index 70f8522aab4..40a9d4befdb 100644 --- a/source/blender/blenkernel/intern/lattice_deform.c +++ b/source/blender/blenkernel/intern/lattice_deform.c @@ -348,7 +348,8 @@ static void lattice_deform_coords_impl(const Object *ob_lattice, * We want either a Mesh/Lattice with no derived data, or derived data with deformverts. */ if (defgrp_name && defgrp_name[0] && ob_target && ELEM(ob_target->type, OB_MESH, OB_LATTICE)) { - defgrp_index = BKE_id_defgroup_name_index((ID *)ob_target->data, defgrp_name); + defgrp_index = BKE_id_defgroup_name_index(me_target ? &me_target->id : (ID *)ob_target->data, + defgrp_name); if (defgrp_index != -1) { /* if there's derived data without deformverts, don't use vgroups */ -- cgit v1.2.3 From cf9dd3c0d8304cc8915f8b7d1f703e26e12c45f5 Mon Sep 17 00:00:00 2001 From: Ramil Roosileht Date: Mon, 25 Jul 2022 12:13:27 +0200 Subject: Fix T99036: hex color in "Add Color Attribute" Proposed solution by @scurest The color attribute in the RNA was tagged as COLOR_GAMMA. This change will change it to a regular COLOR. {F13217692} Reviewed By: joeedh, jbakker Maniphest Tasks: T99036 Differential Revision: https://developer.blender.org/D15272 --- source/blender/editors/geometry/geometry_attributes.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/editors/geometry/geometry_attributes.cc b/source/blender/editors/geometry/geometry_attributes.cc index 4cf14334ac7..a6a9b2fcd26 100644 --- a/source/blender/editors/geometry/geometry_attributes.cc +++ b/source/blender/editors/geometry/geometry_attributes.cc @@ -405,7 +405,7 @@ void GEOMETRY_OT_color_attribute_add(wmOperatorType *ot) prop = RNA_def_float_color( ot->srna, "color", 4, nullptr, 0.0f, FLT_MAX, "Color", "Default fill color", 0.0f, 1.0f); - RNA_def_property_subtype(prop, PROP_COLOR_GAMMA); + RNA_def_property_subtype(prop, PROP_COLOR); RNA_def_property_float_array_default(prop, default_color); } -- cgit v1.2.3 From 5feb3541f459d916202b705c2e9c62f5778e182b Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 25 Jul 2022 12:20:37 +0200 Subject: Fix T99889: Fillet Curve node uses wrong radius --- source/blender/geometry/intern/fillet_curves.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/geometry/intern/fillet_curves.cc b/source/blender/geometry/intern/fillet_curves.cc index 2cca91f40ae..1bbbee6edef 100644 --- a/source/blender/geometry/intern/fillet_curves.cc +++ b/source/blender/geometry/intern/fillet_curves.cc @@ -98,7 +98,7 @@ static void calculate_result_offsets(const bke::CurvesGeometry &src_curves, /* Implicitly "deselect" points with zero radius. */ devirtualize_varray(radii, [&](const auto radii) { for (const int i : IndexRange(src_points.size())) { - if (radii[i] == 0.0f) { + if (radii[src_points[i]] == 0.0f) { point_counts[i] = 1; } } -- cgit v1.2.3 From 2c81b4d4cf758ec39ea02f0fe1e427d5188385b7 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 25 Jul 2022 12:27:45 +0200 Subject: Fix T99880: no node timing for frames in node groups --- source/blender/editors/space_node/node_draw.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index 9b8009cc7c0..c74cd58d8fb 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -1713,7 +1713,7 @@ static std::string node_get_execution_time_label(const SpaceNode &snode, const b { int node_count = 0; std::chrono::microseconds exec_time = node_get_execution_time( - *snode.nodetree, node, snode, node_count); + *snode.edittree, node, snode, node_count); if (node_count == 0) { return std::string(""); -- cgit v1.2.3 From b9e66af686f59ea4179f7a26685e6cd273e3403d Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 25 Jul 2022 12:45:04 +0200 Subject: Fix T99851: Subdivide Curve node does not initialize attributes of end point --- source/blender/geometry/intern/subdivide_curves.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/blender/geometry/intern/subdivide_curves.cc b/source/blender/geometry/intern/subdivide_curves.cc index 8a6f3cbd5e3..8057f029e73 100644 --- a/source/blender/geometry/intern/subdivide_curves.cc +++ b/source/blender/geometry/intern/subdivide_curves.cc @@ -91,7 +91,8 @@ static void subdivide_attribute_linear(const bke::CurvesGeometry &src_curves, } }); - const IndexRange dst_last_segment = bke::offsets_to_range(offsets, src_points.size() - 1); + const IndexRange dst_last_segment = dst_points.slice( + bke::offsets_to_range(offsets, src_points.size() - 1)); linear_interpolation(curve_src.last(), curve_src.first(), dst.slice(dst_last_segment)); } }); -- cgit v1.2.3 From 332d547ab7bc99604b31f20cb300a38277629787 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 25 Jul 2022 13:10:34 +0200 Subject: Fix T99850: incorrect tangents on evaluated bezier curves Cyclic curves don't need the tangent correction based on the first and last handle position. --- source/blender/blenkernel/intern/curves_geometry.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc index 227ebc73c89..3050e01e528 100644 --- a/source/blender/blenkernel/intern/curves_geometry.cc +++ b/source/blender/blenkernel/intern/curves_geometry.cc @@ -715,8 +715,9 @@ Span CurvesGeometry::evaluated_tangents() const } }); - /* Correct the first and last tangents of Bezier curves so that they align with the inner - * handles. This is a separate loop to avoid the cost when Bezier type curves are not used. */ + /* Correct the first and last tangents of non-cyclic Bezier curves so that they align with the + * inner handles. This is a separate loop to avoid the cost when Bezier type curves are not + * used. */ Vector bezier_indices; const IndexMask bezier_mask = this->indices_for_curve_type(CURVE_TYPE_BEZIER, bezier_indices); if (!bezier_mask.is_empty()) { @@ -726,6 +727,9 @@ Span CurvesGeometry::evaluated_tangents() const threading::parallel_for(bezier_mask.index_range(), 1024, [&](IndexRange range) { for (const int curve_index : bezier_mask.slice(range)) { + if (cyclic[curve_index]) { + continue; + } const IndexRange points = this->points_for_curve(curve_index); const IndexRange evaluated_points = this->evaluated_points_for_curve(curve_index); -- cgit v1.2.3 From d567785658349504dc98c693c8c46c30e9a60c44 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 25 Jul 2022 13:16:59 +0200 Subject: Fix T99816: renaming attribute works incorrectly This fixes two issues: * There was a crash when the new attribute name was empty. * The attribute name was incremented (e.g. "Attribute.001") when the old and new name were the same. --- source/blender/blenkernel/intern/attribute.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/source/blender/blenkernel/intern/attribute.cc b/source/blender/blenkernel/intern/attribute.cc index ff40f842349..639e190a2c6 100644 --- a/source/blender/blenkernel/intern/attribute.cc +++ b/source/blender/blenkernel/intern/attribute.cc @@ -146,6 +146,13 @@ bool BKE_id_attribute_rename(ID *id, BLI_assert_msg(0, "Required attribute name is not editable"); return false; } + if (STREQ(new_name, "")) { + BKE_report(reports, RPT_ERROR, "Attribute name can not be empty"); + return false; + } + if (STREQ(old_name, new_name)) { + return false; + } CustomDataLayer *layer = BKE_id_attribute_search( id, old_name, CD_MASK_PROP_ALL, ATTR_DOMAIN_MASK_ALL); -- cgit v1.2.3 From 023eb2ea7c16a00272f83d564145e28aeb9ed2b7 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Thu, 21 Jul 2022 15:49:00 +0200 Subject: Cycles: more closely match some math and intersection operations in Embree This helps with debugging, and gives a slightly closer match between CPU and CUDA/HIP/Metal renders when it comes to ray tracing precision. --- intern/cycles/blender/curves.cpp | 2 +- intern/cycles/kernel/geom/object.h | 2 +- intern/cycles/kernel/geom/shader_data.h | 2 +- intern/cycles/util/math.h | 5 +++ intern/cycles/util/math_float3.h | 15 +++++-- intern/cycles/util/math_intersect.h | 35 +++++++-------- intern/cycles/util/transform.cpp | 19 +------- intern/cycles/util/transform.h | 79 ++++++++++++++++++--------------- 8 files changed, 80 insertions(+), 79 deletions(-) diff --git a/intern/cycles/blender/curves.cpp b/intern/cycles/blender/curves.cpp index c4154bce022..04ba7c53878 100644 --- a/intern/cycles/blender/curves.cpp +++ b/intern/cycles/blender/curves.cpp @@ -55,7 +55,7 @@ static bool ObtainCacheParticleData( return false; Transform tfm = get_transform(b_ob->matrix_world()); - Transform itfm = transform_quick_inverse(tfm); + Transform itfm = transform_inverse(tfm); for (BL::Modifier &b_mod : b_ob->modifiers) { if ((b_mod.type() == b_mod.type_PARTICLE_SYSTEM) && diff --git a/intern/cycles/kernel/geom/object.h b/intern/cycles/kernel/geom/object.h index b15f6b5dda5..bef7d710159 100644 --- a/intern/cycles/kernel/geom/object.h +++ b/intern/cycles/kernel/geom/object.h @@ -86,7 +86,7 @@ ccl_device_inline Transform object_fetch_transform_motion_test(KernelGlobals kg, Transform tfm = object_fetch_transform_motion(kg, object, time); if (itfm) - *itfm = transform_quick_inverse(tfm); + *itfm = transform_inverse(tfm); return tfm; } diff --git a/intern/cycles/kernel/geom/shader_data.h b/intern/cycles/kernel/geom/shader_data.h index 99b9289cb4a..5af89b45f20 100644 --- a/intern/cycles/kernel/geom/shader_data.h +++ b/intern/cycles/kernel/geom/shader_data.h @@ -18,7 +18,7 @@ ccl_device void shader_setup_object_transforms(KernelGlobals kg, { if (sd->object_flag & SD_OBJECT_MOTION) { sd->ob_tfm_motion = object_fetch_transform_motion(kg, sd->object, time); - sd->ob_itfm_motion = transform_quick_inverse(sd->ob_tfm_motion); + sd->ob_itfm_motion = transform_inverse(sd->ob_tfm_motion); } } #endif diff --git a/intern/cycles/util/math.h b/intern/cycles/util/math.h index af2f1ea092d..8360ce05a56 100644 --- a/intern/cycles/util/math.h +++ b/intern/cycles/util/math.h @@ -511,6 +511,11 @@ ccl_device_inline float4 float3_to_float4(const float3 a) return make_float4(a.x, a.y, a.z, 1.0f); } +ccl_device_inline float4 float3_to_float4(const float3 a, const float w) +{ + return make_float4(a.x, a.y, a.z, w); +} + ccl_device_inline float inverse_lerp(float a, float b, float x) { return (x - a) / (b - a); diff --git a/intern/cycles/util/math_float3.h b/intern/cycles/util/math_float3.h index c02b4cdbf0d..c408eadf195 100644 --- a/intern/cycles/util/math_float3.h +++ b/intern/cycles/util/math_float3.h @@ -147,8 +147,11 @@ ccl_device_inline float3 operator/(const float f, const float3 &a) ccl_device_inline float3 operator/(const float3 &a, const float f) { - float invf = 1.0f / f; - return a * invf; +# if defined(__KERNEL_SSE__) + return float3(_mm_div_ps(a.m128, _mm_set1_ps(f))); +# else + return make_float3(a.x / f, a.y / f, a.z / f); +# endif } ccl_device_inline float3 operator/(const float3 &a, const float3 &b) @@ -284,8 +287,12 @@ ccl_device_inline float dot_xy(const float3 &a, const float3 &b) ccl_device_inline float3 cross(const float3 &a, const float3 &b) { - float3 r = make_float3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); - return r; +# ifdef __KERNEL_SSE__ + return float3(shuffle<1, 2, 0, 3>( + msub(ssef(a), shuffle<1, 2, 0, 3>(ssef(b)), shuffle<1, 2, 0, 3>(ssef(a)) * ssef(b)))); +# else + return make_float3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); +# endif } ccl_device_inline float3 normalize(const float3 &a) diff --git a/intern/cycles/util/math_intersect.h b/intern/cycles/util/math_intersect.h index c5b1cd51030..3e5891b2507 100644 --- a/intern/cycles/util/math_intersect.h +++ b/intern/cycles/util/math_intersect.h @@ -105,10 +105,10 @@ ccl_device bool ray_disk_intersect(float3 ray_P, return false; } -ccl_device_forceinline bool ray_triangle_intersect(float3 ray_P, - float3 ray_dir, - float ray_tmin, - float ray_tmax, +ccl_device_forceinline bool ray_triangle_intersect(const float3 ray_P, + const float3 ray_D, + const float ray_tmin, + const float ray_tmax, const float3 tri_a, const float3 tri_b, const float3 tri_c, @@ -116,14 +116,13 @@ ccl_device_forceinline bool ray_triangle_intersect(float3 ray_P, ccl_private float *isect_v, ccl_private float *isect_t) { -#define dot3(a, b) dot(a, b) - const float3 P = ray_P; - const float3 dir = ray_dir; + /* This implementation matches the Plücker coordinates triangle intersection + * in Embree. */ /* Calculate vertices relative to ray origin. */ - const float3 v0 = tri_c - P; - const float3 v1 = tri_a - P; - const float3 v2 = tri_b - P; + const float3 v0 = tri_c - ray_P; + const float3 v1 = tri_a - ray_P; + const float3 v2 = tri_b - ray_P; /* Calculate triangle edges. */ const float3 e0 = v2 - v0; @@ -131,29 +130,29 @@ ccl_device_forceinline bool ray_triangle_intersect(float3 ray_P, const float3 e2 = v1 - v2; /* Perform edge tests. */ - const float U = dot(cross(v2 + v0, e0), ray_dir); - const float V = dot(cross(v0 + v1, e1), ray_dir); - const float W = dot(cross(v1 + v2, e2), ray_dir); + const float U = dot(cross(v2 + v0, e0), ray_D); + const float V = dot(cross(v0 + v1, e1), ray_D); + const float W = dot(cross(v1 + v2, e2), ray_D); + const float eps = FLT_EPSILON * fabsf(U + V + W); const float minUVW = min(U, min(V, W)); const float maxUVW = max(U, max(V, W)); - if (minUVW < 0.0f && maxUVW > 0.0f) { + if (!(minUVW >= -eps || maxUVW <= eps)) { return false; } /* Calculate geometry normal and denominator. */ const float3 Ng1 = cross(e1, e0); - // const Vec3vfM Ng1 = stable_triangle_normal(e2,e1,e0); const float3 Ng = Ng1 + Ng1; - const float den = dot3(Ng, dir); + const float den = dot(Ng, ray_D); /* Avoid division by 0. */ if (UNLIKELY(den == 0.0f)) { return false; } /* Perform depth test. */ - const float T = dot3(v0, Ng); + const float T = dot(v0, Ng); const float t = T / den; if (!(t >= ray_tmin && t <= ray_tmax)) { return false; @@ -163,8 +162,6 @@ ccl_device_forceinline bool ray_triangle_intersect(float3 ray_P, *isect_v = V / den; *isect_t = t; return true; - -#undef dot3 } /* Tests for an intersection between a ray and a quad defined by diff --git a/intern/cycles/util/transform.cpp b/intern/cycles/util/transform.cpp index 0bf5de57a20..0b87e88871d 100644 --- a/intern/cycles/util/transform.cpp +++ b/intern/cycles/util/transform.cpp @@ -99,15 +99,7 @@ ProjectionTransform projection_inverse(const ProjectionTransform &tfm) memcpy(M, &tfm, sizeof(M)); if (UNLIKELY(!transform_matrix4_gj_inverse(R, M))) { - /* matrix is degenerate (e.g. 0 scale on some axis), ideally we should - * never be in this situation, but try to invert it anyway with tweak */ - M[0][0] += 1e-8f; - M[1][1] += 1e-8f; - M[2][2] += 1e-8f; - - if (UNLIKELY(!transform_matrix4_gj_inverse(R, M))) { - return projection_identity(); - } + return projection_identity(); } memcpy(&tfmR, R, sizeof(R)); @@ -115,16 +107,9 @@ ProjectionTransform projection_inverse(const ProjectionTransform &tfm) return tfmR; } -Transform transform_inverse(const Transform &tfm) -{ - ProjectionTransform projection(tfm); - return projection_to_transform(projection_inverse(projection)); -} - Transform transform_transposed_inverse(const Transform &tfm) { - ProjectionTransform projection(tfm); - ProjectionTransform iprojection = projection_inverse(projection); + ProjectionTransform iprojection(transform_inverse(tfm)); return projection_to_transform(projection_transpose(iprojection)); } diff --git a/intern/cycles/util/transform.h b/intern/cycles/util/transform.h index a460581d1f3..71164efbac1 100644 --- a/intern/cycles/util/transform.h +++ b/intern/cycles/util/transform.h @@ -63,10 +63,10 @@ ccl_device_inline float3 transform_point(ccl_private const Transform *t, const f _MM_TRANSPOSE4_PS(x, y, z, w); - ssef tmp = shuffle<0>(aa) * x; - tmp = madd(shuffle<1>(aa), y, tmp); + ssef tmp = w; tmp = madd(shuffle<2>(aa), z, tmp); - tmp += w; + tmp = madd(shuffle<1>(aa), y, tmp); + tmp = madd(shuffle<0>(aa), x, tmp); return float3(tmp.m128); #elif defined(__KERNEL_METAL__) @@ -93,9 +93,9 @@ ccl_device_inline float3 transform_direction(ccl_private const Transform *t, con _MM_TRANSPOSE4_PS(x, y, z, w); - ssef tmp = shuffle<0>(aa) * x; + ssef tmp = shuffle<2>(aa) * z; tmp = madd(shuffle<1>(aa), y, tmp); - tmp = madd(shuffle<2>(aa), z, tmp); + tmp = madd(shuffle<0>(aa), x, tmp); return float3(tmp.m128); #elif defined(__KERNEL_METAL__) @@ -312,7 +312,6 @@ ccl_device_inline void transform_set_column(Transform *t, int column, float3 val t->z[column] = value.z; } -Transform transform_inverse(const Transform &a); Transform transform_transposed_inverse(const Transform &a); ccl_device_inline bool transform_uniform_scale(const Transform &tfm, float &scale) @@ -392,39 +391,47 @@ ccl_device_inline float4 quat_interpolate(float4 q1, float4 q2, float t) #endif /* defined(__KERNEL_GPU_RAYTRACING__) */ } -ccl_device_inline Transform transform_quick_inverse(Transform M) +ccl_device_inline Transform transform_inverse(const Transform tfm) { - /* possible optimization: can we avoid doing this altogether and construct - * the inverse matrix directly from negated translation, transposed rotation, - * scale can be inverted but what about shearing? */ - Transform R; - float det = M.x.x * (M.z.z * M.y.y - M.z.y * M.y.z) - M.y.x * (M.z.z * M.x.y - M.z.y * M.x.z) + - M.z.x * (M.y.z * M.x.y - M.y.y * M.x.z); + /* This implementation matches the one in Embree exactly, to ensure consistent + * results with the ray intersection of instances. */ + float3 x = make_float3(tfm.x.x, tfm.y.x, tfm.z.x); + float3 y = make_float3(tfm.x.y, tfm.y.y, tfm.z.y); + float3 z = make_float3(tfm.x.z, tfm.y.z, tfm.z.z); + float3 w = make_float3(tfm.x.w, tfm.y.w, tfm.z.w); + + /* Compute determinant. */ + float det = dot(x, cross(y, z)); + if (det == 0.0f) { - M.x.x += 1e-8f; - M.y.y += 1e-8f; - M.z.z += 1e-8f; - det = M.x.x * (M.z.z * M.y.y - M.z.y * M.y.z) - M.y.x * (M.z.z * M.x.y - M.z.y * M.x.z) + - M.z.x * (M.y.z * M.x.y - M.y.y * M.x.z); + /* Matrix is degenerate (e.g. 0 scale on some axis), ideally we should + * never be in this situation, but try to invert it anyway with tweak. + * + * This logic does not match Embree which would just give an invalid + * matrix. A better solution would be to remove this and ensure any object + * matrix is valid. */ + x.x += 1e-8f; + y.y += 1e-8f; + z.z += 1e-8f; + + det = dot(x, cross(y, z)); + if (det == 0.0f) { + det = FLT_MAX; + } } - det = (det != 0.0f) ? 1.0f / det : 0.0f; - - float3 Rx = det * make_float3(M.z.z * M.y.y - M.z.y * M.y.z, - M.z.y * M.x.z - M.z.z * M.x.y, - M.y.z * M.x.y - M.y.y * M.x.z); - float3 Ry = det * make_float3(M.z.x * M.y.z - M.z.z * M.y.x, - M.z.z * M.x.x - M.z.x * M.x.z, - M.y.x * M.x.z - M.y.z * M.x.x); - float3 Rz = det * make_float3(M.z.y * M.y.x - M.z.x * M.y.y, - M.z.x * M.x.y - M.z.y * M.x.x, - M.y.y * M.x.x - M.y.x * M.x.y); - float3 T = -make_float3(M.x.w, M.y.w, M.z.w); - - R.x = make_float4(Rx.x, Rx.y, Rx.z, dot(Rx, T)); - R.y = make_float4(Ry.x, Ry.y, Ry.z, dot(Ry, T)); - R.z = make_float4(Rz.x, Rz.y, Rz.z, dot(Rz, T)); - - return R; + + /* Divide adjoint matrix by the determinant to compute inverse of 3x3 matrix. */ + const float3 inverse_x = cross(y, z) / det; + const float3 inverse_y = cross(z, x) / det; + const float3 inverse_z = cross(x, y) / det; + + /* Compute translation and fill transform. */ + Transform itfm; + itfm.x = float3_to_float4(inverse_x, -dot(inverse_x, w)); + itfm.y = float3_to_float4(inverse_y, -dot(inverse_y, w)); + itfm.z = float3_to_float4(inverse_z, -dot(inverse_z, w)); + + return itfm; } ccl_device_inline void transform_compose(ccl_private Transform *tfm, -- cgit v1.2.3 From 484ad3165307391aa5c55656b876b3ff7d615e80 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Thu, 21 Jul 2022 16:37:38 +0200 Subject: Cycles: simplify handling of ray distance in GPU rendering All our intersections functions now work with unnormalized ray direction, which means we no longer need to transform ray distance between world and object space, they can all remain in world space. There doesn't seem to be any real performance difference one way or the other, but it does simplify the code. --- intern/cycles/kernel/bvh/bvh.h | 9 +- intern/cycles/kernel/bvh/local.h | 8 +- intern/cycles/kernel/bvh/shadow_all.h | 81 ++++-------------- intern/cycles/kernel/bvh/traversal.h | 19 ++--- intern/cycles/kernel/bvh/volume.h | 20 ++--- intern/cycles/kernel/bvh/volume_all.h | 60 ++----------- intern/cycles/kernel/device/metal/kernel.metal | 96 ++++++--------------- intern/cycles/kernel/device/optix/kernel.cu | 35 +++----- intern/cycles/kernel/geom/curve_intersect.h | 77 ++++++++--------- intern/cycles/kernel/geom/object.h | 111 ++++++------------------- intern/cycles/kernel/geom/point_intersect.h | 30 +++---- 11 files changed, 153 insertions(+), 393 deletions(-) diff --git a/intern/cycles/kernel/bvh/bvh.h b/intern/cycles/kernel/bvh/bvh.h index 9972de86c47..387e74b9885 100644 --- a/intern/cycles/kernel/bvh/bvh.h +++ b/intern/cycles/kernel/bvh/bvh.h @@ -475,12 +475,7 @@ ccl_device_intersect bool scene_intersect_local(KernelGlobals kg, float3 P = ray->P; float3 dir = ray->D; float3 idir = ray->D; - Transform ob_itfm; - rtc_ray.tfar = ray->tmax * - bvh_instance_motion_push(kg, local_object, ray, &P, &dir, &idir, &ob_itfm); - /* bvh_instance_motion_push() returns the inverse transform but - * it's not needed here. */ - (void)ob_itfm; + bvh_instance_motion_push(kg, local_object, ray, &P, &dir, &idir); rtc_ray.org_x = P.x; rtc_ray.org_y = P.y; @@ -488,6 +483,8 @@ ccl_device_intersect bool scene_intersect_local(KernelGlobals kg, rtc_ray.dir_x = dir.x; rtc_ray.dir_y = dir.y; rtc_ray.dir_z = dir.z; + rtc_ray.tnear = ray->tmin; + rtc_ray.tfar = ray->tmax; RTCScene scene = (RTCScene)rtcGetGeometryUserData(geom); kernel_assert(scene); if (scene) { diff --git a/intern/cycles/kernel/bvh/local.h b/intern/cycles/kernel/bvh/local.h index 017a241ef4a..add61adc126 100644 --- a/intern/cycles/kernel/bvh/local.h +++ b/intern/cycles/kernel/bvh/local.h @@ -59,14 +59,10 @@ ccl_device_inline const int object_flag = kernel_data_fetch(object_flag, local_object); if (!(object_flag & SD_OBJECT_TRANSFORM_APPLIED)) { #if BVH_FEATURE(BVH_MOTION) - Transform ob_itfm; - const float t_world_to_instance = bvh_instance_motion_push( - kg, local_object, ray, &P, &dir, &idir, &ob_itfm); + bvh_instance_motion_push(kg, local_object, ray, &P, &dir, &idir); #else - const float t_world_to_instance = bvh_instance_push(kg, local_object, ray, &P, &dir, &idir); + bvh_instance_push(kg, local_object, ray, &P, &dir, &idir); #endif - isect_t *= t_world_to_instance; - tmin *= t_world_to_instance; object = local_object; } diff --git a/intern/cycles/kernel/bvh/shadow_all.h b/intern/cycles/kernel/bvh/shadow_all.h index db3c91569aa..f37af2a1e65 100644 --- a/intern/cycles/kernel/bvh/shadow_all.h +++ b/intern/cycles/kernel/bvh/shadow_all.h @@ -53,23 +53,11 @@ ccl_device_inline int object = OBJECT_NONE; uint num_hits = 0; -#if BVH_FEATURE(BVH_MOTION) - Transform ob_itfm; -#endif - /* Max distance in world space. May be dynamically reduced when max number of * recorded hits is exceeded and we no longer need to find hits beyond the max * distance found. */ - float t_max_world = ray->tmax; - - /* Current maximum distance to the intersection. - * Is calculated as a ray length, transformed to an object space when entering - * instance node. */ - float t_max_current = ray->tmax; - - /* Conversion from world to local space for the current instance if any, 1.0 - * otherwise. */ - float t_world_to_instance = 1.0f; + const float tmax = ray->tmax; + float tmax_hits = tmax; *r_num_recorded_hits = 0; *r_throughput = 1.0f; @@ -90,7 +78,7 @@ ccl_device_inline #endif idir, tmin, - t_max_current, + tmax, node_addr, visibility, dist); @@ -158,16 +146,8 @@ ccl_device_inline switch (type & PRIMITIVE_ALL) { case PRIMITIVE_TRIANGLE: { - hit = triangle_intersect(kg, - &isect, - P, - dir, - tmin, - t_max_current, - visibility, - prim_object, - prim, - prim_addr); + hit = triangle_intersect( + kg, &isect, P, dir, tmin, tmax, visibility, prim_object, prim, prim_addr); break; } #if BVH_FEATURE(BVH_MOTION) @@ -177,7 +157,7 @@ ccl_device_inline P, dir, tmin, - t_max_current, + tmax, ray->time, visibility, prim_object, @@ -200,16 +180,8 @@ ccl_device_inline } const int curve_type = kernel_data_fetch(prim_type, prim_addr); - hit = curve_intersect(kg, - &isect, - P, - dir, - tmin, - t_max_current, - prim_object, - prim, - ray->time, - curve_type); + hit = curve_intersect( + kg, &isect, P, dir, tmin, tmax, prim_object, prim, ray->time, curve_type); break; } @@ -226,16 +198,8 @@ ccl_device_inline } const int point_type = kernel_data_fetch(prim_type, prim_addr); - hit = point_intersect(kg, - &isect, - P, - dir, - tmin, - t_max_current, - prim_object, - prim, - ray->time, - point_type); + hit = point_intersect( + kg, &isect, P, dir, tmin, tmax, prim_object, prim, ray->time, point_type); break; } #endif /* BVH_FEATURE(BVH_POINTCLOUD) */ @@ -247,9 +211,6 @@ ccl_device_inline /* shadow ray early termination */ if (hit) { - /* Convert intersection distance to world space. */ - isect.t /= t_world_to_instance; - /* detect if this surface has a shader with transparent shadows */ /* todo: optimize so primitive visibility flag indicates if * the primitive has a transparent shadow shader? */ @@ -281,7 +242,7 @@ ccl_device_inline if (record_intersection) { /* Test if we need to record this transparent intersection. */ const uint max_record_hits = min(max_hits, INTEGRATOR_SHADOW_ISECT_SIZE); - if (*r_num_recorded_hits < max_record_hits || isect.t < t_max_world) { + if (*r_num_recorded_hits < max_record_hits || isect.t < tmax_hits) { /* If maximum number of hits was reached, replace the intersection with the * highest distance. We want to find the N closest intersections. */ const uint num_recorded_hits = min(*r_num_recorded_hits, max_record_hits); @@ -303,7 +264,7 @@ ccl_device_inline } /* Limit the ray distance and stop counting hits beyond this. */ - t_max_world = max(isect.t, max_t); + tmax_hits = max(isect.t, max_t); } integrator_state_write_shadow_isect(state, &isect, isect_index); @@ -321,16 +282,11 @@ ccl_device_inline object = kernel_data_fetch(prim_object, -prim_addr - 1); #if BVH_FEATURE(BVH_MOTION) - t_world_to_instance = bvh_instance_motion_push( - kg, object, ray, &P, &dir, &idir, &ob_itfm); + bvh_instance_motion_push(kg, object, ray, &P, &dir, &idir); #else - t_world_to_instance = bvh_instance_push(kg, object, ray, &P, &dir, &idir); + bvh_instance_push(kg, object, ray, &P, &dir, &idir); #endif - /* Convert intersection to object space. */ - t_max_current *= t_world_to_instance; - tmin *= t_world_to_instance; - ++stack_ptr; kernel_assert(stack_ptr < BVH_STACK_SIZE); traversal_stack[stack_ptr] = ENTRYPOINT_SENTINEL; @@ -345,17 +301,12 @@ ccl_device_inline /* Instance pop. */ #if BVH_FEATURE(BVH_MOTION) - bvh_instance_motion_pop(kg, object, ray, &P, &dir, &idir, FLT_MAX, &ob_itfm); + bvh_instance_motion_pop(kg, object, ray, &P, &dir, &idir); #else - bvh_instance_pop(kg, object, ray, &P, &dir, &idir, FLT_MAX); + bvh_instance_pop(kg, object, ray, &P, &dir, &idir); #endif - /* Restore world space ray length. */ - tmin = ray->tmin; - t_max_current = ray->tmax; - object = OBJECT_NONE; - t_world_to_instance = 1.0f; node_addr = traversal_stack[stack_ptr]; --stack_ptr; } diff --git a/intern/cycles/kernel/bvh/traversal.h b/intern/cycles/kernel/bvh/traversal.h index 0ff38bf02de..9069d16912b 100644 --- a/intern/cycles/kernel/bvh/traversal.h +++ b/intern/cycles/kernel/bvh/traversal.h @@ -43,13 +43,9 @@ ccl_device_noinline bool BVH_FUNCTION_FULL_NAME(BVH)(KernelGlobals kg, float3 P = ray->P; float3 dir = bvh_clamp_direction(ray->D); float3 idir = bvh_inverse_direction(dir); - float tmin = ray->tmin; + const float tmin = ray->tmin; int object = OBJECT_NONE; -#if BVH_FEATURE(BVH_MOTION) - Transform ob_itfm; -#endif - isect->t = ray->tmax; isect->u = 0.0f; isect->v = 0.0f; @@ -223,15 +219,11 @@ ccl_device_noinline bool BVH_FUNCTION_FULL_NAME(BVH)(KernelGlobals kg, object = kernel_data_fetch(prim_object, -prim_addr - 1); #if BVH_FEATURE(BVH_MOTION) - const float t_world_to_instance = bvh_instance_motion_push( - kg, object, ray, &P, &dir, &idir, &ob_itfm); + bvh_instance_motion_push(kg, object, ray, &P, &dir, &idir); #else - const float t_world_to_instance = bvh_instance_push(kg, object, ray, &P, &dir, &idir); + bvh_instance_push(kg, object, ray, &P, &dir, &idir); #endif - isect->t *= t_world_to_instance; - tmin *= t_world_to_instance; - ++stack_ptr; kernel_assert(stack_ptr < BVH_STACK_SIZE); traversal_stack[stack_ptr] = ENTRYPOINT_SENTINEL; @@ -246,11 +238,10 @@ ccl_device_noinline bool BVH_FUNCTION_FULL_NAME(BVH)(KernelGlobals kg, /* instance pop */ #if BVH_FEATURE(BVH_MOTION) - isect->t = bvh_instance_motion_pop(kg, object, ray, &P, &dir, &idir, isect->t, &ob_itfm); + bvh_instance_motion_pop(kg, object, ray, &P, &dir, &idir); #else - isect->t = bvh_instance_pop(kg, object, ray, &P, &dir, &idir, isect->t); + bvh_instance_pop(kg, object, ray, &P, &dir, &idir); #endif - tmin = ray->tmin; object = OBJECT_NONE; node_addr = traversal_stack[stack_ptr]; diff --git a/intern/cycles/kernel/bvh/volume.h b/intern/cycles/kernel/bvh/volume.h index bd4e508ecac..cc3915b4bf7 100644 --- a/intern/cycles/kernel/bvh/volume.h +++ b/intern/cycles/kernel/bvh/volume.h @@ -46,13 +46,9 @@ ccl_device_inline float3 P = ray->P; float3 dir = bvh_clamp_direction(ray->D); float3 idir = bvh_inverse_direction(dir); - float tmin = ray->tmin; + const float tmin = ray->tmin; int object = OBJECT_NONE; -#if BVH_FEATURE(BVH_MOTION) - Transform ob_itfm; -#endif - isect->t = ray->tmax; isect->u = 0.0f; isect->v = 0.0f; @@ -189,15 +185,11 @@ ccl_device_inline int object_flag = kernel_data_fetch(object_flag, object); if (object_flag & SD_OBJECT_HAS_VOLUME) { #if BVH_FEATURE(BVH_MOTION) - const float t_world_to_instance = bvh_instance_motion_push( - kg, object, ray, &P, &dir, &idir, &ob_itfm); + bvh_instance_motion_push(kg, object, ray, &P, &dir, &idir); #else - const float t_world_to_instance = bvh_instance_push(kg, object, ray, &P, &dir, &idir); + bvh_instance_push(kg, object, ray, &P, &dir, &idir); #endif - isect->t *= t_world_to_instance; - tmin *= t_world_to_instance; - ++stack_ptr; kernel_assert(stack_ptr < BVH_STACK_SIZE); traversal_stack[stack_ptr] = ENTRYPOINT_SENTINEL; @@ -219,13 +211,11 @@ ccl_device_inline /* instance pop */ #if BVH_FEATURE(BVH_MOTION) - isect->t = bvh_instance_motion_pop(kg, object, ray, &P, &dir, &idir, isect->t, &ob_itfm); + bvh_instance_motion_pop(kg, object, ray, &P, &dir, &idir); #else - isect->t = bvh_instance_pop(kg, object, ray, &P, &dir, &idir, isect->t); + bvh_instance_pop(kg, object, ray, &P, &dir, &idir); #endif - tmin = ray->tmin; - object = OBJECT_NONE; node_addr = traversal_stack[stack_ptr]; --stack_ptr; diff --git a/intern/cycles/kernel/bvh/volume_all.h b/intern/cycles/kernel/bvh/volume_all.h index c6eeb07a14d..5cdea3e354c 100644 --- a/intern/cycles/kernel/bvh/volume_all.h +++ b/intern/cycles/kernel/bvh/volume_all.h @@ -47,14 +47,10 @@ ccl_device_inline float3 P = ray->P; float3 dir = bvh_clamp_direction(ray->D); float3 idir = bvh_inverse_direction(dir); - float tmin = ray->tmin; + const float tmin = ray->tmin; int object = OBJECT_NONE; float isect_t = ray->tmax; -#if BVH_FEATURE(BVH_MOTION) - Transform ob_itfm; -#endif - int num_hits_in_instance = 0; uint num_hits = 0; @@ -159,18 +155,6 @@ ccl_device_inline num_hits_in_instance++; isect_array->t = isect_t; if (num_hits == max_hits) { - if (object != OBJECT_NONE) { -#if BVH_FEATURE(BVH_MOTION) - float t_fac = 1.0f / len(transform_direction(&ob_itfm, dir)); -#else - Transform itfm = object_fetch_transform( - kg, object, OBJECT_INVERSE_TRANSFORM); - float t_fac = 1.0f / len(transform_direction(&itfm, dir)); -#endif - for (int i = 0; i < num_hits_in_instance; i++) { - (isect_array - i - 1)->t *= t_fac; - } - } return num_hits; } } @@ -212,18 +196,6 @@ ccl_device_inline num_hits_in_instance++; isect_array->t = isect_t; if (num_hits == max_hits) { - if (object != OBJECT_NONE) { -# if BVH_FEATURE(BVH_MOTION) - float t_fac = 1.0f / len(transform_direction(&ob_itfm, dir)); -# else - Transform itfm = object_fetch_transform( - kg, object, OBJECT_INVERSE_TRANSFORM); - float t_fac = 1.0f / len(transform_direction(&itfm, dir)); -# endif - for (int i = 0; i < num_hits_in_instance; i++) { - (isect_array - i - 1)->t *= t_fac; - } - } return num_hits; } } @@ -242,15 +214,11 @@ ccl_device_inline int object_flag = kernel_data_fetch(object_flag, object); if (object_flag & SD_OBJECT_HAS_VOLUME) { #if BVH_FEATURE(BVH_MOTION) - const float t_world_to_instance = bvh_instance_motion_push( - kg, object, ray, &P, &dir, &idir, &ob_itfm); + bvh_instance_motion_push(kg, object, ray, &P, &dir, &idir); #else - const float t_world_to_instance = bvh_instance_push(kg, object, ray, &P, &dir, &idir); + bvh_instance_push(kg, object, ray, &P, &dir, &idir); #endif - isect_t *= t_world_to_instance; - tmin *= t_world_to_instance; - num_hits_in_instance = 0; isect_array->t = isect_t; @@ -274,29 +242,11 @@ ccl_device_inline kernel_assert(object != OBJECT_NONE); /* Instance pop. */ - if (num_hits_in_instance) { - float t_fac; #if BVH_FEATURE(BVH_MOTION) - bvh_instance_motion_pop_factor(kg, object, ray, &P, &dir, &idir, &t_fac, &ob_itfm); + bvh_instance_motion_pop(kg, object, ray, &P, &dir, &idir); #else - bvh_instance_pop_factor(kg, object, ray, &P, &dir, &idir, &t_fac); + bvh_instance_pop(kg, object, ray, &P, &dir, &idir); #endif - /* Scale isect->t to adjust for instancing. */ - for (int i = 0; i < num_hits_in_instance; i++) { - (isect_array - i - 1)->t *= t_fac; - } - } - else { -#if BVH_FEATURE(BVH_MOTION) - bvh_instance_motion_pop(kg, object, ray, &P, &dir, &idir, FLT_MAX, &ob_itfm); -#else - bvh_instance_pop(kg, object, ray, &P, &dir, &idir, FLT_MAX); -#endif - } - - tmin = ray->tmin; - isect_t = ray->tmax; - isect_array->t = isect_t; object = OBJECT_NONE; node_addr = traversal_stack[stack_ptr]; diff --git a/intern/cycles/kernel/device/metal/kernel.metal b/intern/cycles/kernel/device/metal/kernel.metal index 764c26dbe8f..8c6f2e1df5e 100644 --- a/intern/cycles/kernel/device/metal/kernel.metal +++ b/intern/cycles/kernel/device/metal/kernel.metal @@ -407,8 +407,8 @@ void metalrt_intersection_curve(constant KernelParamsMetal &launch_params_metal, const uint object, const uint prim, const uint type, - const float3 ray_origin, - const float3 ray_direction, + const float3 ray_P, + const float3 ray_D, float time, const float ray_tmin, const float ray_tmax, @@ -421,25 +421,15 @@ void metalrt_intersection_curve(constant KernelParamsMetal &launch_params_metal, } # endif - float3 P = ray_origin; - float3 dir = ray_direction; - - /* The direction is not normalized by default, but the curve intersection routine expects that */ - float len; - dir = normalize_len(dir, &len); - Intersection isect; isect.t = ray_tmax; - /* Transform maximum distance into object space. */ - if (isect.t != FLT_MAX) - isect.t *= len; MetalKernelContext context(launch_params_metal); - if (context.curve_intersect(NULL, &isect, P, dir, ray_tmin, isect.t, object, prim, time, type)) { + if (context.curve_intersect(NULL, &isect, ray_P, ray_D, ray_tmin, isect.t, object, prim, time, type)) { result = metalrt_visibility_test( launch_params_metal, payload, object, prim, isect.u); if (result.accept) { - result.distance = isect.t / len; + result.distance = isect.t; payload.u = isect.u; payload.v = isect.v; payload.prim = prim; @@ -454,8 +444,6 @@ void metalrt_intersection_curve_shadow(constant KernelParamsMetal &launch_params const uint object, const uint prim, const uint type, - const float3 ray_origin, - const float3 ray_direction, float time, const float ray_tmin, const float ray_tmax, @@ -463,28 +451,14 @@ void metalrt_intersection_curve_shadow(constant KernelParamsMetal &launch_params { const uint visibility = payload.visibility; - float3 P = ray_origin; - float3 dir = ray_direction; - - /* The direction is not normalized by default, but the curve intersection routine expects that */ - float len; - dir = normalize_len(dir, &len); - Intersection isect; isect.t = ray_tmax; - /* Transform maximum distance into object space */ - if (isect.t != FLT_MAX) - isect.t *= len; MetalKernelContext context(launch_params_metal); - if (context.curve_intersect(NULL, &isect, P, dir, ray_tmin, isect.t, object, prim, time, type)) { + if (context.curve_intersect(NULL, &isect, ray_P, ray_D, ray_tmin, isect.t, object, prim, time, type)) { result.continue_search = metalrt_shadow_all_hit( launch_params_metal, payload, object, prim, float2(isect.u, isect.v), ray_tmax); result.accept = !result.continue_search; - - if (result.accept) { - result.distance = isect.t / len; - } } } @@ -494,8 +468,8 @@ __intersection__curve_ribbon(constant KernelParamsMetal &launch_params_metal [[b ray_data MetalKernelContext::MetalRTIntersectionPayload &payload [[payload]], const uint object [[user_instance_id]], const uint primitive_id [[primitive_id]], - const float3 ray_origin [[origin]], - const float3 ray_direction [[direction]], + const float3 ray_P [[origin]], + const float3 ray_D [[direction]], const float ray_tmin [[min_distance]], const float ray_tmax [[max_distance]]) { @@ -508,7 +482,7 @@ __intersection__curve_ribbon(constant KernelParamsMetal &launch_params_metal [[b result.distance = ray_tmax; if (segment.type & PRIMITIVE_CURVE_RIBBON) { - metalrt_intersection_curve(launch_params_metal, payload, object, segment.prim, segment.type, ray_origin, ray_direction, + metalrt_intersection_curve(launch_params_metal, payload, object, segment.prim, segment.type, ray_P, ray_D, # if defined(__METALRT_MOTION__) payload.time, # else @@ -526,8 +500,8 @@ __intersection__curve_ribbon_shadow(constant KernelParamsMetal &launch_params_me ray_data MetalKernelContext::MetalRTIntersectionShadowPayload &payload [[payload]], const uint object [[user_instance_id]], const uint primitive_id [[primitive_id]], - const float3 ray_origin [[origin]], - const float3 ray_direction [[direction]], + const float3 ray_P [[origin]], + const float3 ray_D [[direction]], const float ray_tmin [[min_distance]], const float ray_tmax [[max_distance]]) { @@ -540,7 +514,7 @@ __intersection__curve_ribbon_shadow(constant KernelParamsMetal &launch_params_me result.distance = ray_tmax; if (segment.type & PRIMITIVE_CURVE_RIBBON) { - metalrt_intersection_curve_shadow(launch_params_metal, payload, object, segment.prim, segment.type, ray_origin, ray_direction, + metalrt_intersection_curve_shadow(launch_params_metal, payload, object, segment.prim, segment.type, ray_P, ray_D, # if defined(__METALRT_MOTION__) payload.time, # else @@ -558,8 +532,8 @@ __intersection__curve_all(constant KernelParamsMetal &launch_params_metal [[buff ray_data MetalKernelContext::MetalRTIntersectionPayload &payload [[payload]], const uint object [[user_instance_id]], const uint primitive_id [[primitive_id]], - const float3 ray_origin [[origin]], - const float3 ray_direction [[direction]], + const float3 ray_P [[origin]], + const float3 ray_D [[direction]], const float ray_tmin [[min_distance]], const float ray_tmax [[max_distance]]) { @@ -570,7 +544,7 @@ __intersection__curve_all(constant KernelParamsMetal &launch_params_metal [[buff result.accept = false; result.continue_search = true; result.distance = ray_tmax; - metalrt_intersection_curve(launch_params_metal, payload, object, segment.prim, segment.type, ray_origin, ray_direction, + metalrt_intersection_curve(launch_params_metal, payload, object, segment.prim, segment.type, ray_P, ray_D, # if defined(__METALRT_MOTION__) payload.time, # else @@ -587,8 +561,8 @@ __intersection__curve_all_shadow(constant KernelParamsMetal &launch_params_metal ray_data MetalKernelContext::MetalRTIntersectionShadowPayload &payload [[payload]], const uint object [[user_instance_id]], const uint primitive_id [[primitive_id]], - const float3 ray_origin [[origin]], - const float3 ray_direction [[direction]], + const float3 ray_P [[origin]], + const float3 ray_D [[direction]], const float ray_tmin [[min_distance]], const float ray_tmax [[max_distance]]) { @@ -600,7 +574,7 @@ __intersection__curve_all_shadow(constant KernelParamsMetal &launch_params_metal result.continue_search = true; result.distance = ray_tmax; - metalrt_intersection_curve_shadow(launch_params_metal, payload, object, segment.prim, segment.type, ray_origin, ray_direction, + metalrt_intersection_curve_shadow(launch_params_metal, payload, object, segment.prim, segment.type, ray_P, ray_D, # if defined(__METALRT_MOTION__) payload.time, # else @@ -619,8 +593,8 @@ void metalrt_intersection_point(constant KernelParamsMetal &launch_params_metal, const uint object, const uint prim, const uint type, - const float3 ray_origin, - const float3 ray_direction, + const float3 ray_P, + const float3 ray_D, float time, const float ray_tmin, const float ray_tmax, @@ -633,25 +607,15 @@ void metalrt_intersection_point(constant KernelParamsMetal &launch_params_metal, } # endif - float3 P = ray_origin; - float3 dir = ray_direction; - - /* The direction is not normalized by default, but the point intersection routine expects that */ - float len; - dir = normalize_len(dir, &len); - Intersection isect; isect.t = ray_tmax; - /* Transform maximum distance into object space. */ - if (isect.t != FLT_MAX) - isect.t *= len; MetalKernelContext context(launch_params_metal); - if (context.point_intersect(NULL, &isect, P, dir, ray_tmin, isect.t, object, prim, time, type)) { + if (context.point_intersect(NULL, &isect, ray_P, ray_D, ray_tmin, isect.t, object, prim, time, type)) { result = metalrt_visibility_test( launch_params_metal, payload, object, prim, isect.u); if (result.accept) { - result.distance = isect.t / len; + result.distance = isect.t; payload.u = isect.u; payload.v = isect.v; payload.prim = prim; @@ -666,8 +630,8 @@ void metalrt_intersection_point_shadow(constant KernelParamsMetal &launch_params const uint object, const uint prim, const uint type, - const float3 ray_origin, - const float3 ray_direction, + const float3 ray_P, + const float3 ray_D, float time, const float ray_tmin, const float ray_tmax, @@ -675,27 +639,17 @@ void metalrt_intersection_point_shadow(constant KernelParamsMetal &launch_params { const uint visibility = payload.visibility; - float3 P = ray_origin; - float3 dir = ray_direction; - - /* The direction is not normalized by default, but the point intersection routine expects that */ - float len; - dir = normalize_len(dir, &len); - Intersection isect; isect.t = ray_tmax; - /* Transform maximum distance into object space */ - if (isect.t != FLT_MAX) - isect.t *= len; MetalKernelContext context(launch_params_metal); - if (context.point_intersect(NULL, &isect, P, dir, ray_tmin, isect.t, object, prim, time, type)) { + if (context.point_intersect(NULL, &isect, ray_P, ray_D, ray_tmin, isect.t, object, prim, time, type)) { result.continue_search = metalrt_shadow_all_hit( launch_params_metal, payload, object, prim, float2(isect.u, isect.v), ray_tmax); result.accept = !result.continue_search; if (result.accept) { - result.distance = isect.t / len; + result.distance = isect.t; } } } diff --git a/intern/cycles/kernel/device/optix/kernel.cu b/intern/cycles/kernel/device/optix/kernel.cu index 510f7cca5d6..204aa8182a1 100644 --- a/intern/cycles/kernel/device/optix/kernel.cu +++ b/intern/cycles/kernel/device/optix/kernel.cu @@ -410,13 +410,9 @@ ccl_device_inline void optix_intersection_curve(const int prim, const int type) } # endif - float3 P = optixGetObjectRayOrigin(); - float3 dir = optixGetObjectRayDirection(); - float tmin = optixGetRayTmin(); - - /* The direction is not normalized by default, but the curve intersection routine expects that */ - float len; - dir = normalize_len(dir, &len); + const float3 ray_P = optixGetObjectRayOrigin(); + const float3 ray_D = optixGetObjectRayDirection(); + const float ray_tmin = optixGetRayTmin(); # ifdef __OBJECT_MOTION__ const float time = optixGetRayTime(); @@ -426,13 +422,10 @@ ccl_device_inline void optix_intersection_curve(const int prim, const int type) Intersection isect; isect.t = optixGetRayTmax(); - /* Transform maximum distance into object space. */ - if (isect.t != FLT_MAX) - isect.t *= len; - if (curve_intersect(NULL, &isect, P, dir, tmin, isect.t, object, prim, time, type)) { + if (curve_intersect(NULL, &isect, ray_P, ray_D, ray_tmin, isect.t, object, prim, time, type)) { static_assert(PRIMITIVE_ALL < 128, "Values >= 128 are reserved for OptiX internal use"); - optixReportIntersection(isect.t / len, + optixReportIntersection(isect.t, type & PRIMITIVE_ALL, __float_as_int(isect.u), /* Attribute_0 */ __float_as_int(isect.v)); /* Attribute_1 */ @@ -465,13 +458,9 @@ extern "C" __global__ void __intersection__point() } # endif - float3 P = optixGetObjectRayOrigin(); - float3 dir = optixGetObjectRayDirection(); - float tmin = optixGetRayTmin(); - - /* The direction is not normalized by default, the point intersection routine expects that. */ - float len; - dir = normalize_len(dir, &len); + const float3 ray_P = optixGetObjectRayOrigin(); + const float3 ray_D = optixGetObjectRayDirection(); + const float ray_tmin = optixGetRayTmin(); # ifdef __OBJECT_MOTION__ const float time = optixGetRayTime(); @@ -481,14 +470,10 @@ extern "C" __global__ void __intersection__point() Intersection isect; isect.t = optixGetRayTmax(); - /* Transform maximum distance into object space. */ - if (isect.t != FLT_MAX) { - isect.t *= len; - } - if (point_intersect(NULL, &isect, P, dir, tmin, isect.t, object, prim, time, type)) { + if (point_intersect(NULL, &isect, ray_P, ray_D, ray_tmin, isect.t, object, prim, time, type)) { static_assert(PRIMITIVE_ALL < 128, "Values >= 128 are reserved for OptiX internal use"); - optixReportIntersection(isect.t / len, type & PRIMITIVE_ALL); + optixReportIntersection(isect.t, type & PRIMITIVE_ALL); } } #endif diff --git a/intern/cycles/kernel/geom/curve_intersect.h b/intern/cycles/kernel/geom/curve_intersect.h index 9770105dd81..97644aacaa8 100644 --- a/intern/cycles/kernel/geom/curve_intersect.h +++ b/intern/cycles/kernel/geom/curve_intersect.h @@ -72,7 +72,7 @@ ccl_device_inline float sqr_point_to_line_distance(const float3 PmQ0, const floa ccl_device_inline bool cylinder_intersect(const float3 cylinder_start, const float3 cylinder_end, const float cylinder_radius, - const float3 ray_dir, + const float3 ray_D, ccl_private float2 *t_o, ccl_private float *u0_o, ccl_private float3 *Ng0_o, @@ -82,7 +82,7 @@ ccl_device_inline bool cylinder_intersect(const float3 cylinder_start, /* Calculate quadratic equation to solve. */ const float rl = 1.0f / len(cylinder_end - cylinder_start); const float3 P0 = cylinder_start, dP = (cylinder_end - cylinder_start) * rl; - const float3 O = -P0, dO = ray_dir; + const float3 O = -P0, dO = ray_D; const float dOdO = dot(dO, dO); const float OdO = dot(dO, O); @@ -123,7 +123,7 @@ ccl_device_inline bool cylinder_intersect(const float3 cylinder_start, /* Calculates u and Ng for near hit. */ { *u0_o = (t0 * dOz + Oz) * rl; - const float3 Pr = t0 * ray_dir; + const float3 Pr = t0 * ray_D; const float3 Pl = (*u0_o) * (cylinder_end - cylinder_start) + cylinder_start; *Ng0_o = Pr - Pl; } @@ -131,7 +131,7 @@ ccl_device_inline bool cylinder_intersect(const float3 cylinder_start, /* Calculates u and Ng for far hit. */ { *u1_o = (t1 * dOz + Oz) * rl; - const float3 Pr = t1 * ray_dir; + const float3 Pr = t1 * ray_D; const float3 Pl = (*u1_o) * (cylinder_end - cylinder_start) + cylinder_start; *Ng1_o = Pr - Pl; } @@ -141,10 +141,10 @@ ccl_device_inline bool cylinder_intersect(const float3 cylinder_start, return true; } -ccl_device_inline float2 half_plane_intersect(const float3 P, const float3 N, const float3 ray_dir) +ccl_device_inline float2 half_plane_intersect(const float3 P, const float3 N, const float3 ray_D) { const float3 O = -P; - const float3 D = ray_dir; + const float3 D = ray_D; const float ON = dot(O, N); const float DN = dot(D, N); const float min_rcp_input = 1e-18f; @@ -155,7 +155,7 @@ ccl_device_inline float2 half_plane_intersect(const float3 P, const float3 N, co return make_float2(lower, upper); } -ccl_device bool curve_intersect_iterative(const float3 ray_dir, +ccl_device bool curve_intersect_iterative(const float3 ray_D, const float ray_tmin, ccl_private float *ray_tmax, const float dt, @@ -165,7 +165,7 @@ ccl_device bool curve_intersect_iterative(const float3 ray_dir, const bool use_backfacing, ccl_private Intersection *isect) { - const float length_ray_dir = len(ray_dir); + const float length_ray_D = len(ray_D); /* Error of curve evaluations is proportional to largest coordinate. */ const float4 box_min = min(min(curve[0], curve[1]), min(curve[2], curve[3])); @@ -176,9 +176,9 @@ ccl_device bool curve_intersect_iterative(const float3 ray_dir, const float radius_max = box_max.w; for (int i = 0; i < CURVE_NUM_JACOBIAN_ITERATIONS; i++) { - const float3 Q = ray_dir * t; - const float3 dQdt = ray_dir; - const float Q_err = 16.0f * FLT_EPSILON * length_ray_dir * t; + const float3 Q = ray_D * t; + const float3 dQdt = ray_D; + const float Q_err = 16.0f * FLT_EPSILON * length_ray_D * t; const float4 P4 = catmull_rom_basis_eval(curve, u); const float4 dPdu4 = catmull_rom_basis_derivative(curve, u); @@ -233,7 +233,7 @@ ccl_device bool curve_intersect_iterative(const float3 ray_dir, const float3 U = dradiusdu * R + dPdu; const float3 V = cross(dPdu, R); const float3 Ng = cross(V, U); - if (!use_backfacing && dot(ray_dir, Ng) > 0.0f) { + if (!use_backfacing && dot(ray_D, Ng) > 0.0f) { return false; } @@ -249,8 +249,8 @@ ccl_device bool curve_intersect_iterative(const float3 ray_dir, return false; } -ccl_device bool curve_intersect_recursive(const float3 ray_orig, - const float3 ray_dir, +ccl_device bool curve_intersect_recursive(const float3 ray_P, + const float3 ray_D, const float ray_tmin, float ray_tmax, float4 curve[4], @@ -258,8 +258,8 @@ ccl_device bool curve_intersect_recursive(const float3 ray_orig, { /* Move ray closer to make intersection stable. */ const float3 center = float4_to_float3(0.25f * (curve[0] + curve[1] + curve[2] + curve[3])); - const float dt = dot(center - ray_orig, ray_dir) / dot(ray_dir, ray_dir); - const float3 ref = ray_orig + ray_dir * dt; + const float dt = dot(center - ray_P, ray_D) / dot(ray_D, ray_D); + const float3 ref = ray_P + ray_D * dt; const float4 ref4 = make_float4(ref.x, ref.y, ref.z, 0.0f); curve[0] -= ref4; curve[1] -= ref4; @@ -322,7 +322,7 @@ ccl_device bool curve_intersect_recursive(const float3 ray_orig, valid = cylinder_intersect(float4_to_float3(P0), float4_to_float3(P3), r_outer, - ray_dir, + ray_D, &tc_outer, &u_outer0, &Ng_outer0, @@ -335,11 +335,10 @@ ccl_device bool curve_intersect_recursive(const float3 ray_orig, /* Intersect with cap-planes. */ float2 tp = make_float2(ray_tmin - dt, ray_tmax - dt); tp = make_float2(max(tp.x, tc_outer.x), min(tp.y, tc_outer.y)); - const float2 h0 = half_plane_intersect( - float4_to_float3(P0), float4_to_float3(dP0du), ray_dir); + const float2 h0 = half_plane_intersect(float4_to_float3(P0), float4_to_float3(dP0du), ray_D); tp = make_float2(max(tp.x, h0.x), min(tp.y, h0.y)); const float2 h1 = half_plane_intersect( - float4_to_float3(P3), -float4_to_float3(dP3du), ray_dir); + float4_to_float3(P3), -float4_to_float3(dP3du), ray_D); tp = make_float2(max(tp.x, h1.x), min(tp.y, h1.y)); valid = tp.x <= tp.y; if (!valid) { @@ -359,7 +358,7 @@ ccl_device bool curve_intersect_recursive(const float3 ray_orig, const bool valid_inner = cylinder_intersect(float4_to_float3(P0), float4_to_float3(P3), r_inner, - ray_dir, + ray_D, &tc_inner, &u_inner0, &Ng_inner0, @@ -369,9 +368,9 @@ ccl_device bool curve_intersect_recursive(const float3 ray_orig, /* At the unstable area we subdivide deeper. */ # if 0 const bool unstable0 = (!valid_inner) | - (fabsf(dot(normalize(ray_dir), normalize(Ng_inner0))) < 0.3f); + (fabsf(dot(normalize(ray_D), normalize(Ng_inner0))) < 0.3f); const bool unstable1 = (!valid_inner) | - (fabsf(dot(normalize(ray_dir), normalize(Ng_inner1))) < 0.3f); + (fabsf(dot(normalize(ray_D), normalize(Ng_inner1))) < 0.3f); # else /* On the GPU appears to be a little faster if always enabled. */ (void)valid_inner; @@ -396,7 +395,7 @@ ccl_device bool curve_intersect_recursive(const float3 ray_orig, CURVE_NUM_BEZIER_SUBDIVISIONS; if (depth >= termDepth) { found |= curve_intersect_iterative( - ray_dir, ray_tmin, &ray_tmax, dt, curve, u_outer0, tp0.x, use_backfacing, isect); + ray_D, ray_tmin, &ray_tmax, dt, curve, u_outer0, tp0.x, use_backfacing, isect); } else { recurse = true; @@ -409,7 +408,7 @@ ccl_device bool curve_intersect_recursive(const float3 ray_orig, CURVE_NUM_BEZIER_SUBDIVISIONS; if (depth >= termDepth) { found |= curve_intersect_iterative( - ray_dir, ray_tmin, &ray_tmax, dt, curve, u_outer1, tp1.y, use_backfacing, isect); + ray_D, ray_tmin, &ray_tmax, dt, curve, u_outer1, tp1.y, use_backfacing, isect); } else { recurse = true; @@ -519,13 +518,16 @@ ccl_device_inline bool ribbon_intersect_quad(const float ray_tmin, return true; } -ccl_device_inline void ribbon_ray_space(const float3 ray_dir, float3 ray_space[3]) +ccl_device_inline void ribbon_ray_space(const float3 ray_D, + const float ray_D_invlen, + float3 ray_space[3]) { - const float3 dx0 = make_float3(0, ray_dir.z, -ray_dir.y); - const float3 dx1 = make_float3(-ray_dir.z, 0, ray_dir.x); + const float3 D = ray_D * ray_D_invlen; + const float3 dx0 = make_float3(0, D.z, -D.y); + const float3 dx1 = make_float3(-D.z, 0, D.x); ray_space[0] = normalize(dot(dx0, dx0) > dot(dx1, dx1) ? dx0 : dx1); - ray_space[1] = normalize(cross(ray_dir, ray_space[0])); - ray_space[2] = ray_dir; + ray_space[1] = normalize(cross(D, ray_space[0])); + ray_space[2] = D * ray_D_invlen; } ccl_device_inline float4 ribbon_to_ray_space(const float3 ray_space[3], @@ -537,7 +539,7 @@ ccl_device_inline float4 ribbon_to_ray_space(const float3 ray_space[3], } ccl_device_inline bool ribbon_intersect(const float3 ray_org, - const float3 ray_dir, + const float3 ray_D, const float ray_tmin, float ray_tmax, const int N, @@ -545,8 +547,9 @@ ccl_device_inline bool ribbon_intersect(const float3 ray_org, ccl_private Intersection *isect) { /* Transform control points into ray space. */ + const float ray_D_invlen = 1.0f / len(ray_D); float3 ray_space[3]; - ribbon_ray_space(ray_dir, ray_space); + ribbon_ray_space(ray_D, ray_D_invlen, ray_space); curve[0] = ribbon_to_ray_space(ray_space, ray_org, curve[0]); curve[1] = ribbon_to_ray_space(ray_space, ray_org, curve[1]); @@ -594,7 +597,7 @@ ccl_device_inline bool ribbon_intersect(const float3 ray_org, const float avoidance_factor = 2.0f; if (avoidance_factor != 0.0f) { float r = mix(p0.w, p1.w, vu); - valid0 = vt > avoidance_factor * r; + valid0 = vt > avoidance_factor * r * ray_D_invlen; } if (valid0) { @@ -619,8 +622,8 @@ ccl_device_inline bool ribbon_intersect(const float3 ray_org, ccl_device_forceinline bool curve_intersect(KernelGlobals kg, ccl_private Intersection *isect, - const float3 P, - const float3 dir, + const float3 ray_P, + const float3 ray_D, const float tmin, const float tmax, int object, @@ -651,7 +654,7 @@ ccl_device_forceinline bool curve_intersect(KernelGlobals kg, if (type & PRIMITIVE_CURVE_RIBBON) { /* todo: adaptive number of subdivisions could help performance here. */ const int subdivisions = kernel_data.bvh.curve_subdivisions; - if (ribbon_intersect(P, dir, tmin, tmax, subdivisions, curve, isect)) { + if (ribbon_intersect(ray_P, ray_D, tmin, tmax, subdivisions, curve, isect)) { isect->prim = prim; isect->object = object; isect->type = type; @@ -661,7 +664,7 @@ ccl_device_forceinline bool curve_intersect(KernelGlobals kg, return false; } else { - if (curve_intersect_recursive(P, dir, tmin, tmax, curve, isect)) { + if (curve_intersect_recursive(ray_P, ray_D, tmin, tmax, curve, isect)) { isect->prim = prim; isect->object = object; isect->type = type; diff --git a/intern/cycles/kernel/geom/object.h b/intern/cycles/kernel/geom/object.h index bef7d710159..badfd311985 100644 --- a/intern/cycles/kernel/geom/object.h +++ b/intern/cycles/kernel/geom/object.h @@ -488,59 +488,30 @@ ccl_device_inline float3 bvh_inverse_direction(float3 dir) /* Transform ray into object space to enter static object in BVH */ -ccl_device_inline float bvh_instance_push(KernelGlobals kg, - int object, - ccl_private const Ray *ray, - ccl_private float3 *P, - ccl_private float3 *dir, - ccl_private float3 *idir) +ccl_device_inline void bvh_instance_push(KernelGlobals kg, + int object, + ccl_private const Ray *ray, + ccl_private float3 *P, + ccl_private float3 *dir, + ccl_private float3 *idir) { Transform tfm = object_fetch_transform(kg, object, OBJECT_INVERSE_TRANSFORM); *P = transform_point(&tfm, ray->P); - float len; - *dir = bvh_clamp_direction(normalize_len(transform_direction(&tfm, ray->D), &len)); + *dir = bvh_clamp_direction(transform_direction(&tfm, ray->D)); *idir = bvh_inverse_direction(*dir); - - return len; } /* Transform ray to exit static object in BVH. */ -ccl_device_inline float bvh_instance_pop(KernelGlobals kg, - int object, - ccl_private const Ray *ray, - ccl_private float3 *P, - ccl_private float3 *dir, - ccl_private float3 *idir, - float t) +ccl_device_inline void bvh_instance_pop(KernelGlobals kg, + int object, + ccl_private const Ray *ray, + ccl_private float3 *P, + ccl_private float3 *dir, + ccl_private float3 *idir) { - if (t != FLT_MAX) { - Transform tfm = object_fetch_transform(kg, object, OBJECT_INVERSE_TRANSFORM); - t /= len(transform_direction(&tfm, ray->D)); - } - - *P = ray->P; - *dir = bvh_clamp_direction(ray->D); - *idir = bvh_inverse_direction(*dir); - - return t; -} - -/* Same as above, but returns scale factor to apply to multiple intersection distances */ - -ccl_device_inline void bvh_instance_pop_factor(KernelGlobals kg, - int object, - ccl_private const Ray *ray, - ccl_private float3 *P, - ccl_private float3 *dir, - ccl_private float3 *idir, - ccl_private float *t_fac) -{ - Transform tfm = object_fetch_transform(kg, object, OBJECT_INVERSE_TRANSFORM); - *t_fac = 1.0f / len(transform_direction(&tfm, ray->D)); - *P = ray->P; *dir = bvh_clamp_direction(ray->D); *idir = bvh_inverse_direction(*dir); @@ -549,59 +520,31 @@ ccl_device_inline void bvh_instance_pop_factor(KernelGlobals kg, #ifdef __OBJECT_MOTION__ /* Transform ray into object space to enter motion blurred object in BVH */ -ccl_device_inline float bvh_instance_motion_push(KernelGlobals kg, - int object, - ccl_private const Ray *ray, - ccl_private float3 *P, - ccl_private float3 *dir, - ccl_private float3 *idir, - ccl_private Transform *itfm) -{ - object_fetch_transform_motion_test(kg, object, ray->time, itfm); - - *P = transform_point(itfm, ray->P); - - float len; - *dir = bvh_clamp_direction(normalize_len(transform_direction(itfm, ray->D), &len)); - *idir = bvh_inverse_direction(*dir); - - return len; -} - -/* Transform ray to exit motion blurred object in BVH. */ - -ccl_device_inline float bvh_instance_motion_pop(KernelGlobals kg, +ccl_device_inline void bvh_instance_motion_push(KernelGlobals kg, int object, ccl_private const Ray *ray, ccl_private float3 *P, ccl_private float3 *dir, - ccl_private float3 *idir, - float t, - ccl_private Transform *itfm) + ccl_private float3 *idir) { - if (t != FLT_MAX) { - t /= len(transform_direction(itfm, ray->D)); - } + Transform tfm; + object_fetch_transform_motion_test(kg, object, ray->time, &tfm); - *P = ray->P; - *dir = bvh_clamp_direction(ray->D); - *idir = bvh_inverse_direction(*dir); + *P = transform_point(&tfm, ray->P); - return t; + *dir = bvh_clamp_direction(transform_direction(&tfm, ray->D)); + *idir = bvh_inverse_direction(*dir); } -/* Same as above, but returns scale factor to apply to multiple intersection distances */ +/* Transform ray to exit motion blurred object in BVH. */ -ccl_device_inline void bvh_instance_motion_pop_factor(KernelGlobals kg, - int object, - ccl_private const Ray *ray, - ccl_private float3 *P, - ccl_private float3 *dir, - ccl_private float3 *idir, - ccl_private float *t_fac, - ccl_private Transform *itfm) +ccl_device_inline void bvh_instance_motion_pop(KernelGlobals kg, + int object, + ccl_private const Ray *ray, + ccl_private float3 *P, + ccl_private float3 *dir, + ccl_private float3 *idir) { - *t_fac = 1.0f / len(transform_direction(itfm, ray->D)); *P = ray->P; *dir = bvh_clamp_direction(ray->D); *idir = bvh_inverse_direction(*dir); diff --git a/intern/cycles/kernel/geom/point_intersect.h b/intern/cycles/kernel/geom/point_intersect.h index ee5a564947b..15fb814c58d 100644 --- a/intern/cycles/kernel/geom/point_intersect.h +++ b/intern/cycles/kernel/geom/point_intersect.h @@ -10,20 +10,20 @@ CCL_NAMESPACE_BEGIN #ifdef __POINTCLOUD__ ccl_device_forceinline bool point_intersect_test(const float4 point, - const float3 P, - const float3 dir, - const float tmin, - const float tmax, + const float3 ray_P, + const float3 ray_D, + const float ray_tmin, + const float ray_tmax, ccl_private float *t) { const float3 center = float4_to_float3(point); const float radius = point.w; - const float rd2 = 1.0f / dot(dir, dir); + const float rd2 = 1.0f / dot(ray_D, ray_D); - const float3 c0 = center - P; - const float projC0 = dot(c0, dir) * rd2; - const float3 perp = c0 - projC0 * dir; + const float3 c0 = center - ray_P; + const float projC0 = dot(c0, ray_D) * rd2; + const float3 perp = c0 - projC0 * ray_D; const float l2 = dot(perp, perp); const float r2 = radius * radius; if (!(l2 <= r2)) { @@ -32,12 +32,12 @@ ccl_device_forceinline bool point_intersect_test(const float4 point, const float td = sqrt((r2 - l2) * rd2); const float t_front = projC0 - td; - const bool valid_front = (tmin <= t_front) & (t_front <= tmax); + const bool valid_front = (ray_tmin <= t_front) & (t_front <= ray_tmax); /* Always back-face culling for now. */ # if 0 const float t_back = projC0 + td; - const bool valid_back = (tmin <= t_back) & (t_back <= tmax); + const bool valid_back = (ray_tmin <= t_back) & (t_back <= ray_tmax); /* check if there is a first hit */ const bool valid_first = valid_front | valid_back; @@ -58,10 +58,10 @@ ccl_device_forceinline bool point_intersect_test(const float4 point, ccl_device_forceinline bool point_intersect(KernelGlobals kg, ccl_private Intersection *isect, - const float3 P, - const float3 dir, - const float tmin, - const float tmax, + const float3 ray_P, + const float3 ray_D, + const float ray_tmin, + const float ray_tmax, const int object, const int prim, const float time, @@ -70,7 +70,7 @@ ccl_device_forceinline bool point_intersect(KernelGlobals kg, const float4 point = (type & PRIMITIVE_MOTION) ? motion_point(kg, object, prim, time) : kernel_data_fetch(points, prim); - if (!point_intersect_test(point, P, dir, tmin, tmax, &isect->t)) { + if (!point_intersect_test(point, ray_P, ray_D, ray_tmin, ray_tmax, &isect->t)) { return false; } -- cgit v1.2.3 From 881ef0548a32e1c71d4ede79a386cf0d5a6c1bf6 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Mon, 25 Jul 2022 14:50:33 +0200 Subject: Fix wrong Cycles SSS intersection distance after ray distance changes No need anymore to have a difference between CPU/GPU, all distances remain in world space. --- intern/cycles/kernel/integrator/subsurface_disk.h | 11 +---------- intern/cycles/kernel/integrator/subsurface_random_walk.h | 13 ------------- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/intern/cycles/kernel/integrator/subsurface_disk.h b/intern/cycles/kernel/integrator/subsurface_disk.h index 2836934f6dd..60b63c075a0 100644 --- a/intern/cycles/kernel/integrator/subsurface_disk.h +++ b/intern/cycles/kernel/integrator/subsurface_disk.h @@ -126,17 +126,8 @@ ccl_device_inline bool subsurface_disk(KernelGlobals kg, if (!(object_flag & SD_OBJECT_TRANSFORM_APPLIED)) { /* Transform normal to world space. */ Transform itfm; - Transform tfm = object_fetch_transform_motion_test(kg, object, time, &itfm); + object_fetch_transform_motion_test(kg, object, time, &itfm); hit_Ng = normalize(transform_direction_transposed(&itfm, hit_Ng)); - - /* Transform t to world space, except for OptiX and MetalRT where it already is. */ -#ifdef __KERNEL_GPU_RAYTRACING__ - (void)tfm; -#else - float3 D = transform_direction(&itfm, ray.D); - D = normalize(D) * ss_isect.hits[hit].t; - ss_isect.hits[hit].t = len(transform_direction(&tfm, D)); -#endif } /* Quickly retrieve P and Ng without setting up ShaderData. */ diff --git a/intern/cycles/kernel/integrator/subsurface_random_walk.h b/intern/cycles/kernel/integrator/subsurface_random_walk.h index c1691030817..7857673b271 100644 --- a/intern/cycles/kernel/integrator/subsurface_random_walk.h +++ b/intern/cycles/kernel/integrator/subsurface_random_walk.h @@ -205,12 +205,6 @@ ccl_device_inline bool subsurface_random_walk(KernelGlobals kg, ray.self.light_object = OBJECT_NONE; ray.self.light_prim = PRIM_NONE; -#ifndef __KERNEL_GPU_RAYTRACING__ - /* Compute or fetch object transforms. */ - Transform ob_itfm ccl_optional_struct_init; - Transform ob_tfm = object_fetch_transform_motion_test(kg, object, time, &ob_itfm); -#endif - /* Convert subsurface to volume coefficients. * The single-scattering albedo is named alpha to avoid confusion with the surface albedo. */ const float3 albedo = INTEGRATOR_STATE(state, subsurface, albedo); @@ -383,15 +377,8 @@ ccl_device_inline bool subsurface_random_walk(KernelGlobals kg, hit = (ss_isect.num_hits > 0); if (hit) { -#ifdef __KERNEL_GPU_RAYTRACING__ /* t is always in world space with OptiX and MetalRT. */ ray.tmax = ss_isect.hits[0].t; -#else - /* Compute world space distance to surface hit. */ - float3 D = transform_direction(&ob_itfm, ray.D); - D = normalize(D) * ss_isect.hits[0].t; - ray.tmax = len(transform_direction(&ob_tfm, D)); -#endif } if (bounce == 0) { -- cgit v1.2.3 From c6ce70855a13c42a724755f2989dee756519bef0 Mon Sep 17 00:00:00 2001 From: Arye Ramaty Date: Mon, 25 Jul 2022 08:56:00 -0500 Subject: Geometry Nodes: Add node descriptions/tooltips This commit adds tooltips to the geometry nodes add menu. Differential Revision: https://developer.blender.org/D15414 --- source/blender/nodes/NOD_static_types.h | 254 ++++++++++++++++---------------- 1 file changed, 127 insertions(+), 127 deletions(-) diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index d743c341885..db0204c5426 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -261,7 +261,7 @@ DefNode(TextureNode, TEX_NODE_PROC+TEX_STUCCI, 0, "TEX_ST DefNode(TextureNode, TEX_NODE_PROC+TEX_DISTNOISE, 0, "TEX_DISTNOISE", TexDistNoise, "Distorted Noise", "" ) DefNode(FunctionNode, FN_NODE_ALIGN_EULER_TO_VECTOR, def_fn_align_euler_to_vector, "ALIGN_EULER_TO_VECTOR", AlignEulerToVector, "Align Euler to Vector", "") -DefNode(FunctionNode, FN_NODE_BOOLEAN_MATH, def_boolean_math, "BOOLEAN_MATH", BooleanMath, "Boolean Math", "") +DefNode(FunctionNode, FN_NODE_BOOLEAN_MATH, def_boolean_math, "BOOLEAN_MATH", BooleanMath, "Boolean Math", "") DefNode(FunctionNode, FN_NODE_COMBINE_COLOR, def_fn_combsep_color, "COMBINE_COLOR", CombineColor, "Combine Color", "") DefNode(FunctionNode, FN_NODE_COMPARE, def_compare, "COMPARE", Compare, "Compare", "") DefNode(FunctionNode, FN_NODE_FLOAT_TO_INT, def_float_to_int, "FLOAT_TO_INT", FloatToInt, "Float to Integer", "") @@ -279,132 +279,132 @@ DefNode(FunctionNode, FN_NODE_SLICE_STRING, 0, "SLICE_STRING", SliceString, "Sli DefNode(FunctionNode, FN_NODE_STRING_LENGTH, 0, "STRING_LENGTH", StringLength, "String Length", "") DefNode(FunctionNode, FN_NODE_VALUE_TO_STRING, 0, "VALUE_TO_STRING", ValueToString, "Value to String", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_DOMAIN_SIZE, def_geo_attribute_domain_size, "ATTRIBUTE_DOMAIN_SIZE", AttributeDomainSize, "Domain Size", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_STATISTIC, def_geo_attribute_statistic, "ATTRIBUTE_STATISTIC", AttributeStatistic, "Attribute Statistic", "") -DefNode(GeometryNode, GEO_NODE_BOUNDING_BOX, 0, "BOUNDING_BOX", BoundBox, "Bounding Box", "") -DefNode(GeometryNode, GEO_NODE_CAPTURE_ATTRIBUTE, def_geo_attribute_capture, "CAPTURE_ATTRIBUTE", CaptureAttribute, "Capture Attribute", "") -DefNode(GeometryNode, GEO_NODE_COLLECTION_INFO, def_geo_collection_info, "COLLECTION_INFO", CollectionInfo, "Collection Info", "") -DefNode(GeometryNode, GEO_NODE_CONVEX_HULL, 0, "CONVEX_HULL", ConvexHull, "Convex Hull", "") -DefNode(GeometryNode, GEO_NODE_CURVE_ENDPOINT_SELECTION, 0, "CURVE_ENDPOINT_SELECTION", CurveEndpointSelection, "Endpoint Selection", "") -DefNode(GeometryNode, GEO_NODE_CURVE_HANDLE_TYPE_SELECTION, def_geo_curve_handle_type_selection, "CURVE_HANDLE_TYPE_SELECTION", CurveHandleTypeSelection, "Handle Type Selection", "") -DefNode(GeometryNode, GEO_NODE_CURVE_LENGTH, 0, "CURVE_LENGTH", CurveLength, "Curve Length", "") -DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_ARC, def_geo_curve_primitive_arc, "CURVE_PRIMITIVE_ARC", CurveArc, "Arc", "") -DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT, def_geo_curve_primitive_bezier_segment, "CURVE_PRIMITIVE_BEZIER_SEGMENT", CurvePrimitiveBezierSegment, "Bezier Segment", "") -DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_CIRCLE, def_geo_curve_primitive_circle, "CURVE_PRIMITIVE_CIRCLE", CurvePrimitiveCircle, "Curve Circle", "") -DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_LINE, def_geo_curve_primitive_line, "CURVE_PRIMITIVE_LINE", CurvePrimitiveLine, "Curve Line", "") -DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_QUADRATIC_BEZIER, 0, "CURVE_PRIMITIVE_QUADRATIC_BEZIER", CurveQuadraticBezier, "Quadratic Bezier", "") -DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_QUADRILATERAL, def_geo_curve_primitive_quadrilateral, "CURVE_PRIMITIVE_QUADRILATERAL", CurvePrimitiveQuadrilateral, "Quadrilateral", "") -DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_SPIRAL, 0, "CURVE_PRIMITIVE_SPIRAL", CurveSpiral, "Curve Spiral", "") -DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_STAR, 0, "CURVE_PRIMITIVE_STAR", CurveStar, "Star", "") -DefNode(GeometryNode, GEO_NODE_CURVE_SET_HANDLE_TYPE, def_geo_curve_set_handle_type, "CURVE_SET_HANDLES", CurveSetHandles, "Set Handle Type", "") -DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_PARAMETER, 0, "SPLINE_PARAMETER", SplineParameter, "Spline Parameter", "") -DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_TYPE, def_geo_curve_spline_type, "CURVE_SPLINE_TYPE", CurveSplineType, "Set Spline Type", "") -DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "") -DefNode(GeometryNode, GEO_NODE_CURVE_TO_POINTS, def_geo_curve_to_points, "CURVE_TO_POINTS", CurveToPoints, "Curve to Points", "") -DefNode(GeometryNode, GEO_NODE_DEFORM_CURVES_ON_SURFACE, 0, "DEFORM_CURVES_ON_SURFACE", DeformCurvesOnSurface, "Deform Curves on Surface", "") -DefNode(GeometryNode, GEO_NODE_DELETE_GEOMETRY, def_geo_delete_geometry, "DELETE_GEOMETRY", DeleteGeometry, "Delete Geometry", "") -DefNode(GeometryNode, GEO_NODE_DUPLICATE_ELEMENTS, def_geo_duplicate_elements, "DUPLICATE_ELEMENTS", DuplicateElements, "Duplicate Elements", "") -DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_ON_FACES, def_geo_distribute_points_on_faces, "DISTRIBUTE_POINTS_ON_FACES", DistributePointsOnFaces, "Distribute Points on Faces", "") -DefNode(GeometryNode, GEO_NODE_ACCUMULATE_FIELD, def_geo_accumulate_field, "ACCUMULATE_FIELD", AccumulateField, "Accumulate Field", "") -DefNode(GeometryNode, GEO_NODE_DUAL_MESH, 0, "DUAL_MESH", DualMesh, "Dual Mesh", "") -DefNode(GeometryNode, GEO_NODE_EXTRUDE_MESH, def_geo_extrude_mesh, "EXTRUDE_MESH", ExtrudeMesh, "Extrude Mesh", "") -DefNode(GeometryNode, GEO_NODE_FIELD_AT_INDEX, def_geo_field_at_index, "FIELD_AT_INDEX", FieldAtIndex, "Field at Index", "") -DefNode(GeometryNode, GEO_NODE_FIELD_ON_DOMAIN, def_geo_field_on_domain, "FIELD_ON_DOMAIN", FieldOnDomain, "Field on Domain", "") -DefNode(GeometryNode, GEO_NODE_FILL_CURVE, def_geo_curve_fill, "FILL_CURVE", FillCurve, "Fill Curve", "") -DefNode(GeometryNode, GEO_NODE_FILLET_CURVE, def_geo_curve_fillet, "FILLET_CURVE", FilletCurve, "Fillet Curve", "") -DefNode(GeometryNode, GEO_NODE_FLIP_FACES, 0, "FLIP_FACES", FlipFaces, "Flip Faces", "") -DefNode(GeometryNode, GEO_NODE_GEOMETRY_TO_INSTANCE, 0, "GEOMETRY_TO_INSTANCE", GeometryToInstance, "Geometry to Instance", "") -DefNode(GeometryNode, GEO_NODE_IMAGE_TEXTURE, def_geo_image_texture, "IMAGE_TEXTURE", ImageTexture, "Image Texture", "") -DefNode(GeometryNode, GEO_NODE_INPUT_NAMED_ATTRIBUTE, def_geo_input_named_attribute, "INPUT_ATTRIBUTE", InputNamedAttribute, "Named Attribute", "") -DefNode(GeometryNode, GEO_NODE_INPUT_CURVE_HANDLES, 0, "INPUT_CURVE_HANDLES", InputCurveHandlePositions, "Curve Handle Positions", "") -DefNode(GeometryNode, GEO_NODE_INPUT_CURVE_TILT, 0, "INPUT_CURVE_TILT", InputCurveTilt, "Curve Tilt", "") -DefNode(GeometryNode, GEO_NODE_INPUT_ID, 0, "INPUT_ID", InputID, "ID", "") -DefNode(GeometryNode, GEO_NODE_INPUT_INDEX, 0, "INDEX", InputIndex, "Index", "") -DefNode(GeometryNode, GEO_NODE_INPUT_INSTANCE_ROTATION, 0, "INPUT_INSTANCE_ROTATION", InputInstanceRotation, "Instance Rotation", "") -DefNode(GeometryNode, GEO_NODE_INPUT_INSTANCE_SCALE, 0, "INPUT_INSTANCE_SCALE", InputInstanceScale, "Instance Scale", "") -DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL_INDEX, 0, "INPUT_MATERIAL_INDEX", InputMaterialIndex, "Material Index", "") -DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL, def_geo_input_material, "INPUT_MATERIAL", InputMaterial, "Material", "") -DefNode(GeometryNode, GEO_NODE_INPUT_MESH_EDGE_ANGLE, 0, "MESH_EDGE_ANGLE", InputMeshEdgeAngle, "Edge Angle", "") -DefNode(GeometryNode, GEO_NODE_INPUT_MESH_EDGE_NEIGHBORS, 0, "MESH_EDGE_NEIGHBORS", InputMeshEdgeNeighbors, "Edge Neighbors", "") -DefNode(GeometryNode, GEO_NODE_INPUT_MESH_EDGE_VERTICES, 0, "MESH_EDGE_VERTICES", InputMeshEdgeVertices, "Edge Vertices", "") -DefNode(GeometryNode, GEO_NODE_INPUT_MESH_FACE_AREA, 0, "MESH_FACE_AREA", InputMeshFaceArea, "Face Area", "") -DefNode(GeometryNode, GEO_NODE_INPUT_MESH_FACE_IS_PLANAR, 0, "MESH_FACE_IS_PLANAR", InputMeshFaceIsPlanar, "Face is Planar", "") -DefNode(GeometryNode, GEO_NODE_INPUT_MESH_FACE_NEIGHBORS, 0, "MESH_FACE_NEIGHBORS", InputMeshFaceNeighbors, "Face Neighbors", "") -DefNode(GeometryNode, GEO_NODE_INPUT_MESH_ISLAND, 0, "MESH_ISLAND", InputMeshIsland, "Mesh Island", "") -DefNode(GeometryNode, GEO_NODE_INPUT_MESH_VERTEX_NEIGHBORS, 0, "MESH_VERTEX_NEIGHBORS", InputMeshVertexNeighbors, "Vertex Neighbors", "") -DefNode(GeometryNode, GEO_NODE_INPUT_NORMAL, 0, "INPUT_NORMAL", InputNormal, "Normal", "") -DefNode(GeometryNode, GEO_NODE_INPUT_POSITION, 0, "POSITION", InputPosition, "Position", "") -DefNode(GeometryNode, GEO_NODE_INPUT_RADIUS, 0, "INPUT_RADIUS", InputRadius, "Radius", "") -DefNode(GeometryNode, GEO_NODE_INPUT_SCENE_TIME, 0, "INPUT_SCENE_TIME", InputSceneTime, "Scene Time", "") -DefNode(GeometryNode, GEO_NODE_INPUT_SHADE_SMOOTH, 0, "INPUT_SHADE_SMOOTH", InputShadeSmooth, "Is Shade Smooth", "") -DefNode(GeometryNode, GEO_NODE_INPUT_SPLINE_CYCLIC, 0, "INPUT_SPLINE_CYCLIC", InputSplineCyclic, "Is Spline Cyclic", "") -DefNode(GeometryNode, GEO_NODE_INPUT_SPLINE_LENGTH, 0, "SPLINE_LENGTH", SplineLength, "Spline Length", "") -DefNode(GeometryNode, GEO_NODE_INPUT_SPLINE_RESOLUTION, 0, "INPUT_SPLINE_RESOLUTION", InputSplineResolution, "Spline Resolution", "") -DefNode(GeometryNode, GEO_NODE_INPUT_TANGENT, 0, "INPUT_TANGENT", InputTangent, "Curve Tangent", "") -DefNode(GeometryNode, GEO_NODE_INSTANCE_ON_POINTS, 0, "INSTANCE_ON_POINTS", InstanceOnPoints, "Instance on Points", "") -DefNode(GeometryNode, GEO_NODE_INSTANCES_TO_POINTS, 0, "INSTANCES_TO_POINTS", InstancesToPoints, "Instances to Points", "") -DefNode(GeometryNode, GEO_NODE_IS_VIEWPORT, 0, "IS_VIEWPORT", IsViewport, "Is Viewport", "") -DefNode(GeometryNode, GEO_NODE_JOIN_GEOMETRY, 0, "JOIN_GEOMETRY", JoinGeometry, "Join Geometry", "") -DefNode(GeometryNode, GEO_NODE_MATERIAL_SELECTION, 0, "MATERIAL_SELECTION", MaterialSelection, "Material Selection", "") -DefNode(GeometryNode, GEO_NODE_MERGE_BY_DISTANCE, def_geo_merge_by_distance, "MERGE_BY_DISTANCE", MergeByDistance, "Merge by Distance", "") -DefNode(GeometryNode, GEO_NODE_MESH_BOOLEAN, def_geo_boolean, "MESH_BOOLEAN", MeshBoolean, "Mesh Boolean", "") -DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CIRCLE, def_geo_mesh_circle, "MESH_PRIMITIVE_CIRCLE", MeshCircle, "Mesh Circle", "") -DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CONE, def_geo_mesh_cone, "MESH_PRIMITIVE_CONE", MeshCone, "Cone", "") -DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CUBE, 0, "MESH_PRIMITIVE_CUBE", MeshCube, "Cube", "") -DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CYLINDER, def_geo_mesh_cylinder, "MESH_PRIMITIVE_CYLINDER", MeshCylinder, "Cylinder", "") -DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_GRID, 0, "MESH_PRIMITIVE_GRID", MeshGrid, "Grid", "") -DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_ICO_SPHERE, 0, "MESH_PRIMITIVE_ICO_SPHERE", MeshIcoSphere, "Ico Sphere", "") -DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_LINE, def_geo_mesh_line, "MESH_PRIMITIVE_LINE", MeshLine, "Mesh Line", "") -DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_UV_SPHERE, 0, "MESH_PRIMITIVE_UV_SPHERE", MeshUVSphere, "UV Sphere", "") -DefNode(GeometryNode, GEO_NODE_MESH_TO_CURVE, 0, "MESH_TO_CURVE", MeshToCurve, "Mesh to Curve", "") -DefNode(GeometryNode, GEO_NODE_MESH_TO_POINTS, def_geo_mesh_to_points, "MESH_TO_POINTS", MeshToPoints, "Mesh to Points", "") -DefNode(GeometryNode, GEO_NODE_MESH_TO_VOLUME, def_geo_mesh_to_volume, "MESH_TO_VOLUME", MeshToVolume, "Mesh to Volume", "") -DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, def_geo_object_info, "OBJECT_INFO", ObjectInfo, "Object Info", "") -DefNode(GeometryNode, GEO_NODE_POINTS, 0, "POINTS", Points, "Points", "") -DefNode(GeometryNode, GEO_NODE_POINTS_TO_VERTICES, 0, "POINTS_TO_VERTICES", PointsToVertices, "Points to Vertices", "") -DefNode(GeometryNode, GEO_NODE_POINTS_TO_VOLUME, def_geo_points_to_volume, "POINTS_TO_VOLUME", PointsToVolume, "Points to Volume", "") -DefNode(GeometryNode, GEO_NODE_PROXIMITY, def_geo_proximity, "PROXIMITY", Proximity, "Geometry Proximity", "") -DefNode(GeometryNode, GEO_NODE_RAYCAST, def_geo_raycast, "RAYCAST", Raycast, "Raycast", "") -DefNode(GeometryNode, GEO_NODE_REMOVE_ATTRIBUTE, 0, "REMOVE_ATTRIBUTE", RemoveAttribute, "Remove Named Attribute", "") -DefNode(GeometryNode, GEO_NODE_REALIZE_INSTANCES, def_geo_realize_instances, "REALIZE_INSTANCES", RealizeInstances, "Realize Instances", "") -DefNode(GeometryNode, GEO_NODE_REPLACE_MATERIAL, 0, "REPLACE_MATERIAL", ReplaceMaterial, "Replace Material", "") -DefNode(GeometryNode, GEO_NODE_RESAMPLE_CURVE, def_geo_curve_resample, "RESAMPLE_CURVE", ResampleCurve, "Resample Curve", "") -DefNode(GeometryNode, GEO_NODE_REVERSE_CURVE, 0, "REVERSE_CURVE", ReverseCurve, "Reverse Curve", "") -DefNode(GeometryNode, GEO_NODE_ROTATE_INSTANCES, 0, "ROTATE_INSTANCES", RotateInstances, "Rotate Instances", "") -DefNode(GeometryNode, GEO_NODE_SAMPLE_CURVE, def_geo_curve_sample, "SAMPLE_CURVE", SampleCurve, "Sample Curve", "") -DefNode(GeometryNode, GEO_NODE_SCALE_ELEMENTS, def_geo_scale_elements, "SCALE_ELEMENTS", ScaleElements, "Scale Elements", "") -DefNode(GeometryNode, GEO_NODE_SCALE_INSTANCES, 0, "SCALE_INSTANCES", ScaleInstances, "Scale Instances", "") -DefNode(GeometryNode, GEO_NODE_SEPARATE_COMPONENTS, 0, "SEPARATE_COMPONENTS", SeparateComponents, "Separate Components", "") -DefNode(GeometryNode, GEO_NODE_SEPARATE_GEOMETRY, def_geo_separate_geometry, "SEPARATE_GEOMETRY", SeparateGeometry, "Separate Geometry", "") -DefNode(GeometryNode, GEO_NODE_SET_CURVE_HANDLES, def_geo_curve_set_handle_positions, "SET_CURVE_HANDLES", SetCurveHandlePositions, "Set Handle Positions", "") -DefNode(GeometryNode, GEO_NODE_SET_CURVE_RADIUS, 0, "SET_CURVE_RADIUS", SetCurveRadius, "Set Curve Radius", "") -DefNode(GeometryNode, GEO_NODE_SET_CURVE_TILT, 0, "SET_CURVE_TILT", SetCurveTilt, "Set Curve Tilt", "") -DefNode(GeometryNode, GEO_NODE_SET_ID, 0, "SET_ID", SetID, "Set ID", "") -DefNode(GeometryNode, GEO_NODE_SET_MATERIAL_INDEX, 0, "SET_MATERIAL_INDEX", SetMaterialIndex, "Set Material Index", "") -DefNode(GeometryNode, GEO_NODE_SET_MATERIAL, 0, "SET_MATERIAL", SetMaterial, "Set Material", "") -DefNode(GeometryNode, GEO_NODE_SET_POINT_RADIUS, 0, "SET_POINT_RADIUS", SetPointRadius, "Set Point Radius", "") -DefNode(GeometryNode, GEO_NODE_SET_POSITION, 0, "SET_POSITION", SetPosition, "Set Position", "") -DefNode(GeometryNode, GEO_NODE_SET_SHADE_SMOOTH, 0, "SET_SHADE_SMOOTH", SetShadeSmooth, "Set Shade Smooth", "") -DefNode(GeometryNode, GEO_NODE_SET_SPLINE_CYCLIC, 0, "SET_SPLINE_CYCLIC", SetSplineCyclic, "Set Spline Cyclic", "") -DefNode(GeometryNode, GEO_NODE_SET_SPLINE_RESOLUTION, 0, "SET_SPLINE_RESOLUTION", SetSplineResolution, "Set Spline Resolution", "") -DefNode(GeometryNode, GEO_NODE_SPLIT_EDGES, 0, "SPLIT_EDGES", SplitEdges, "Split Edges", "") -DefNode(GeometryNode, GEO_NODE_STORE_NAMED_ATTRIBUTE, def_geo_store_named_attribute, "STORE_NAMED_ATTRIBUTE", StoreNamedAttribute, "Store Named Attribute", "") -DefNode(GeometryNode, GEO_NODE_STRING_JOIN, 0, "STRING_JOIN", StringJoin, "Join Strings", "") -DefNode(GeometryNode, GEO_NODE_STRING_TO_CURVES, def_geo_string_to_curves, "STRING_TO_CURVES", StringToCurves, "String to Curves", "") -DefNode(GeometryNode, GEO_NODE_SUBDIVIDE_CURVE, 0, "SUBDIVIDE_CURVE", SubdivideCurve, "Subdivide Curve", "") -DefNode(GeometryNode, GEO_NODE_SUBDIVIDE_MESH, 0, "SUBDIVIDE_MESH", SubdivideMesh, "Subdivide Mesh", "") -DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, def_geo_subdivision_surface, "SUBDIVISION_SURFACE", SubdivisionSurface, "Subdivision Surface", "") -DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "") -DefNode(GeometryNode, GEO_NODE_TRANSFER_ATTRIBUTE, def_geo_transfer_attribute, "ATTRIBUTE_TRANSFER", AttributeTransfer, "Transfer Attribute", "") -DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "") -DefNode(GeometryNode, GEO_NODE_TRANSLATE_INSTANCES, 0, "TRANSLATE_INSTANCES", TranslateInstances, "Translate Instances", "") -DefNode(GeometryNode, GEO_NODE_TRIANGULATE, def_geo_triangulate, "TRIANGULATE", Triangulate, "Triangulate", "") -DefNode(GeometryNode, GEO_NODE_TRIM_CURVE, def_geo_curve_trim, "TRIM_CURVE", TrimCurve, "Trim Curve", "") -DefNode(GeometryNode, GEO_NODE_VIEWER, def_geo_viewer, "VIEWER", Viewer, "Viewer", "") -DefNode(GeometryNode, GEO_NODE_VOLUME_CUBE, 0, "VOLUME_CUBE", VolumeCube, "Volume Cube", "") -DefNode(GeometryNode, GEO_NODE_VOLUME_TO_MESH, def_geo_volume_to_mesh, "VOLUME_TO_MESH", VolumeToMesh, "Volume to Mesh", "") -DefNode(GeometryNode, GEO_NODE_UV_PACK_ISLANDS, 0, "UV_PACK_ISLANDS", UVPackIslands, "Pack UV Islands", "") -DefNode(GeometryNode, GEO_NODE_UV_UNWRAP, def_geo_uv_unwrap, "UV_UNWRAP", UVUnwrap, "UV Unwrap", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_DOMAIN_SIZE, def_geo_attribute_domain_size, "ATTRIBUTE_DOMAIN_SIZE", AttributeDomainSize, "Domain Size", "Retrieve the number of elements in a geometry for each attribute domain") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_STATISTIC, def_geo_attribute_statistic, "ATTRIBUTE_STATISTIC",AttributeStatistic, "Attribute Statistic","Calculate statistics about a data set from a field evaluated on a geometry") +DefNode(GeometryNode, GEO_NODE_BOUNDING_BOX, 0, "BOUNDING_BOX", BoundBox, "Bounding Box", "Calculate the limits of a geometry's positions and generate a box mesh with those dimensions") +DefNode(GeometryNode, GEO_NODE_CAPTURE_ATTRIBUTE, def_geo_attribute_capture,"CAPTURE_ATTRIBUTE", CaptureAttribute, "Capture Attribute", "Store the result of a field on a geometry and output the data as a node socket. Allows remembering or interpolating data as the geometry changes, such as positions before deformation") +DefNode(GeometryNode, GEO_NODE_COLLECTION_INFO, def_geo_collection_info, "COLLECTION_INFO", CollectionInfo, "Collection Info", "Retrieve geometry from a collection") +DefNode(GeometryNode, GEO_NODE_CONVEX_HULL, 0, "CONVEX_HULL", ConvexHull, "Convex Hull", "Create a mesh that encloses all points in the input geometry with the smallest number of points") +DefNode(GeometryNode, GEO_NODE_CURVE_ENDPOINT_SELECTION, 0, "CURVE_ENDPOINT_SELECTION", CurveEndpointSelection, "Endpoint Selection", "Provide a selection for an arbitrary number of endpoints in each spline") +DefNode(GeometryNode, GEO_NODE_CURVE_HANDLE_TYPE_SELECTION, def_geo_curve_handle_type_selection, "CURVE_HANDLE_TYPE_SELECTION", CurveHandleTypeSelection, "Handle Type Selection", "Provide a selection based on the handle types of Bézier control points") +DefNode(GeometryNode, GEO_NODE_CURVE_LENGTH, 0, "CURVE_LENGTH", CurveLength, "Curve Length", "Retrieve the length of all splines added together") +DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_ARC, def_geo_curve_primitive_arc, "CURVE_PRIMITIVE_ARC",CurveArc, "Arc", "Generate a poly spline arc") +DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT, def_geo_curve_primitive_bezier_segment, "CURVE_PRIMITIVE_BEZIER_SEGMENT", CurvePrimitiveBezierSegment, "Bezier Segment", "Generate a 2D Bézier spline from the given control points and handles") +DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_CIRCLE,def_geo_curve_primitive_circle, "CURVE_PRIMITIVE_CIRCLE", CurvePrimitiveCircle, "Curve Circle", "Generate a poly spline circle") +DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_LINE, def_geo_curve_primitive_line, "CURVE_PRIMITIVE_LINE", CurvePrimitiveLine, "Curve Line", "Generate a poly spline line with two points") +DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_QUADRATIC_BEZIER, 0, "CURVE_PRIMITIVE_QUADRATIC_BEZIER", CurveQuadraticBezier, "Quadratic Bezier", "Generate a poly spline in a parabola shape with control points positions") +DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_QUADRILATERAL, def_geo_curve_primitive_quadrilateral, "CURVE_PRIMITIVE_QUADRILATERAL", CurvePrimitiveQuadrilateral, "Quadrilateral", "Generate a polygon with four points") +DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_SPIRAL,0, "CURVE_PRIMITIVE_SPIRAL", CurveSpiral, "Curve Spiral", "Generate a poly spline in a spiral shape") +DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_STAR, 0, "CURVE_PRIMITIVE_STAR", CurveStar, "Star", "Generate a poly spline in a star pattern by connecting alternating points of two circles") +DefNode(GeometryNode, GEO_NODE_CURVE_SET_HANDLE_TYPE, def_geo_curve_set_handle_type, "CURVE_SET_HANDLES", CurveSetHandles, "Set Handle Type", "Set the handle type for the control points of a Bézier curve") +DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_PARAMETER,0, "SPLINE_PARAMETER", SplineParameter, "Spline Parameter", "Retrieve how far along each spline a control point is") +DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_TYPE, def_geo_curve_spline_type,"CURVE_SPLINE_TYPE", CurveSplineType, "Set Spline Type", "Change the type of curves") +DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "Convert curves into a mesh, optionally with a custom profile shape defined by curves") +DefNode(GeometryNode, GEO_NODE_CURVE_TO_POINTS, def_geo_curve_to_points, "CURVE_TO_POINTS", CurveToPoints, "Curve to Points", "Generate a point cloud by sampling positions along curves") +DefNode(GeometryNode, GEO_NODE_DEFORM_CURVES_ON_SURFACE, 0, "DEFORM_CURVES_ON_SURFACE", DeformCurvesOnSurface, "Deform Curves on Surface", "Translate and rotate curves based on changes between the object's original and evaluated surface mesh") +DefNode(GeometryNode, GEO_NODE_DELETE_GEOMETRY, def_geo_delete_geometry, "DELETE_GEOMETRY", DeleteGeometry, "Delete Geometry", "Remove selected elements of a geometry") +DefNode(GeometryNode, GEO_NODE_DUPLICATE_ELEMENTS, def_geo_duplicate_elements, "DUPLICATE_ELEMENTS", DuplicateElements, "Duplicate Elements", "Generate an arbitrary number copies of each selected input element") +DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_ON_FACES, def_geo_distribute_points_on_faces, "DISTRIBUTE_POINTS_ON_FACES", DistributePointsOnFaces, "Distribute Points on Faces", "Generate points spread out on the surface of a mesh") +DefNode(GeometryNode, GEO_NODE_ACCUMULATE_FIELD, def_geo_accumulate_field, "ACCUMULATE_FIELD", AccumulateField, "Accumulate Field", "Add the values of an evaluated field together and output the running total for each element") +DefNode(GeometryNode, GEO_NODE_DUAL_MESH, 0, "DUAL_MESH", DualMesh, "Dual Mesh", "Convert Faces into vertices and vertices into faces") +DefNode(GeometryNode, GEO_NODE_EXTRUDE_MESH, def_geo_extrude_mesh, "EXTRUDE_MESH", ExtrudeMesh, "Extrude Mesh", "Generate new vertices, edges, or faces from selected elements and move them based on an offset while keeping them connected by their boundary") +DefNode(GeometryNode, GEO_NODE_FIELD_AT_INDEX, def_geo_field_at_index, "FIELD_AT_INDEX", FieldAtIndex, "Field at Index", "Retrieve data of other elements in the context's geometry") +DefNode(GeometryNode, GEO_NODE_FIELD_ON_DOMAIN, def_geo_field_on_domain, "FIELD_ON_DOMAIN", FieldOnDomain, "Field on Domain", "Retrieve values from a field on a different domain besides the domain from the context") +DefNode(GeometryNode, GEO_NODE_FILL_CURVE, def_geo_curve_fill, "FILL_CURVE", FillCurve, "Fill Curve", "Generate a mesh on the XY plane with faces on the inside of input curves") +DefNode(GeometryNode, GEO_NODE_FILLET_CURVE, def_geo_curve_fillet, "FILLET_CURVE", FilletCurve, "Fillet Curve", "Round corners by generating circular arcs on each control point") +DefNode(GeometryNode, GEO_NODE_FLIP_FACES, 0, "FLIP_FACES", FlipFaces, "Flip Faces", "Reverse the order of the vertices and edges of selected faces, flipping their normal direction") +DefNode(GeometryNode, GEO_NODE_GEOMETRY_TO_INSTANCE, 0, "GEOMETRY_TO_INSTANCE", GeometryToInstance, "Geometry to Instance", "Convert each input geometry into an instance, which can be much faster than the Join Geometry node when the inputs are large") +DefNode(GeometryNode, GEO_NODE_IMAGE_TEXTURE, def_geo_image_texture, "IMAGE_TEXTURE", ImageTexture, "Image Texture", "Sample values from an image texture") +DefNode(GeometryNode, GEO_NODE_INPUT_NAMED_ATTRIBUTE, def_geo_input_named_attribute, "INPUT_ATTRIBUTE", InputNamedAttribute, "Named Attribute", "Retrieve the data of a specified attribute") +DefNode(GeometryNode, GEO_NODE_INPUT_CURVE_HANDLES, 0, "INPUT_CURVE_HANDLES",InputCurveHandlePositions,"Curve Handle Positions", "Retrieve the position of each Bézier control point's handles") +DefNode(GeometryNode, GEO_NODE_INPUT_CURVE_TILT, 0, "INPUT_CURVE_TILT", InputCurveTilt, "Curve Tilt", "Retrieve the angle at each control point used to twist the curve's normal around its tangent") +DefNode(GeometryNode, GEO_NODE_INPUT_ID, 0, "INPUT_ID", InputID, "ID", "Retrieve a stable random identifier value from the \"id\" attribute on the point domain, or the index if the attribute does not exist") +DefNode(GeometryNode, GEO_NODE_INPUT_INDEX, 0, "INDEX", InputIndex, "Index", "Retrieve an integer value indicating the position of each element in the list, starting at zero") +DefNode(GeometryNode, GEO_NODE_INPUT_INSTANCE_ROTATION, 0, "INPUT_INSTANCE_ROTATION", InputInstanceRotation, "Instance Rotation", "Retrieve the rotation of each instance in the geometry") +DefNode(GeometryNode, GEO_NODE_INPUT_INSTANCE_SCALE, 0, "INPUT_INSTANCE_SCALE", InputInstanceScale, "Instance Scale", "Retrieve the scale of each instance in the geometry") +DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL_INDEX, 0, "INPUT_MATERIAL_INDEX", InputMaterialIndex, "Material Index", "Retrieve the index of the material used for each element in the geometry's list of materials") +DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL, def_geo_input_material, "INPUT_MATERIAL", InputMaterial, "Material", "Output a single material") +DefNode(GeometryNode, GEO_NODE_INPUT_MESH_EDGE_ANGLE, 0, "MESH_EDGE_ANGLE", InputMeshEdgeAngle, "Edge Angle", "Calculate the surface area of each face in a mesh") +DefNode(GeometryNode, GEO_NODE_INPUT_MESH_EDGE_NEIGHBORS, 0, "MESH_EDGE_NEIGHBORS",InputMeshEdgeNeighbors, "Edge Neighbors", "Retrieve the number of faces that use each edge as one of their sides") +DefNode(GeometryNode, GEO_NODE_INPUT_MESH_EDGE_VERTICES, 0, "MESH_EDGE_VERTICES", InputMeshEdgeVertices, "Edge Vertices", "Retrieve topology information relating to each edge of a mesh") +DefNode(GeometryNode, GEO_NODE_INPUT_MESH_FACE_AREA, 0, "MESH_FACE_AREA", InputMeshFaceArea, "Face Area", "Calculate the surface area of a mesh's faces") +DefNode(GeometryNode, GEO_NODE_INPUT_MESH_FACE_IS_PLANAR, 0, "MESH_FACE_IS_PLANAR",InputMeshFaceIsPlanar, "Face is Planar", "Retrieve whether all triangles in a face are on the same plane, i.e. whether have the same normal") +DefNode(GeometryNode, GEO_NODE_INPUT_MESH_FACE_NEIGHBORS, 0, "MESH_FACE_NEIGHBORS",InputMeshFaceNeighbors, "Face Neighbors", "Retrieve topology information relating to each face of a mesh") +DefNode(GeometryNode, GEO_NODE_INPUT_MESH_ISLAND, 0, "MESH_ISLAND", InputMeshIsland, "Mesh Island", "Retrieve information about separate connected regions in a mesh") +DefNode(GeometryNode, GEO_NODE_INPUT_MESH_VERTEX_NEIGHBORS, 0, "MESH_VERTEX_NEIGHBORS", InputMeshVertexNeighbors, "Vertex Neighbors", "Retrieve topology information relating to each vertex of a mesh") +DefNode(GeometryNode, GEO_NODE_INPUT_NORMAL, 0, "INPUT_NORMAL", InputNormal, "Normal", "Retrieve a unit length vector indicating the direction pointing away from the geometry at each element") +DefNode(GeometryNode, GEO_NODE_INPUT_POSITION, 0, "POSITION", InputPosition, "Position", "Retrieve a vector indicating the location of each element") +DefNode(GeometryNode, GEO_NODE_INPUT_RADIUS, 0, "INPUT_RADIUS", InputRadius, "Radius", "Retrieve the radius at each point on curve or point cloud geometry") +DefNode(GeometryNode, GEO_NODE_INPUT_SCENE_TIME, 0, "INPUT_SCENE_TIME", InputSceneTime, "Scene Time", "Retrieve the current time in the scene's animation in units of seconds or frames") +DefNode(GeometryNode, GEO_NODE_INPUT_SHADE_SMOOTH, 0, "INPUT_SHADE_SMOOTH", InputShadeSmooth, "Is Shade Smooth", "Retrieve whether each face is marked for smooth shading") +DefNode(GeometryNode, GEO_NODE_INPUT_SPLINE_CYCLIC, 0, "INPUT_SPLINE_CYCLIC",InputSplineCyclic, "Is Spline Cyclic", "Retrieve whether each spline endpoint connects to the beginning") +DefNode(GeometryNode, GEO_NODE_INPUT_SPLINE_LENGTH, 0, "SPLINE_LENGTH", SplineLength, "Spline Length", "Retrieve the total length of each spline, as a distance or as a number of points") +DefNode(GeometryNode, GEO_NODE_INPUT_SPLINE_RESOLUTION, 0, "INPUT_SPLINE_RESOLUTION", InputSplineResolution, "Spline Resolution", "Retrieve the number of evaluated points that will be generated for every control point on curves") +DefNode(GeometryNode, GEO_NODE_INPUT_TANGENT, 0, "INPUT_TANGENT", InputTangent, "Curve Tangent", "Retrieve the direction of curves at each control point") +DefNode(GeometryNode, GEO_NODE_INSTANCE_ON_POINTS, 0, "INSTANCE_ON_POINTS", InstanceOnPoints, "Instance on Points", "Generate a reference to geometry at each of the input points, without duplicating its underlying data") +DefNode(GeometryNode, GEO_NODE_INSTANCES_TO_POINTS, 0, "INSTANCES_TO_POINTS",InstancesToPoints, "Instances to Points","Generate points at the origins of instances.\nNote: Nested instances are not affected by this node") +DefNode(GeometryNode, GEO_NODE_IS_VIEWPORT, 0, "IS_VIEWPORT", IsViewport, "Is Viewport", "Retrieve whether the nodes are being evaluated for the viewport rather than the final render") +DefNode(GeometryNode, GEO_NODE_JOIN_GEOMETRY, 0, "JOIN_GEOMETRY", JoinGeometry, "Join Geometry", "Merge separately generated geometries into a single one") +DefNode(GeometryNode, GEO_NODE_MATERIAL_SELECTION, 0, "MATERIAL_SELECTION", MaterialSelection, "Material Selection", "Provide a selection of faces that use the specified material") +DefNode(GeometryNode, GEO_NODE_MERGE_BY_DISTANCE, def_geo_merge_by_distance,"MERGE_BY_DISTANCE", MergeByDistance, "Merge by Distance", "Merge vertices or points within a given distance") +DefNode(GeometryNode, GEO_NODE_MESH_BOOLEAN, def_geo_boolean, "MESH_BOOLEAN", MeshBoolean, "Mesh Boolean", "Cut, subtract, or join multiple mesh inputs") +DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CIRCLE, def_geo_mesh_circle, "MESH_PRIMITIVE_CIRCLE", MeshCircle, "Mesh Circle", "Generate a circular ring of edges") +DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CONE, def_geo_mesh_cone, "MESH_PRIMITIVE_CONE",MeshCone, "Cone", "Generate a cone mesh") +DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CUBE, 0, "MESH_PRIMITIVE_CUBE",MeshCube, "Cube", "Generate a cuboid mesh with variable side lengths and subdivisions") +DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CYLINDER, def_geo_mesh_cylinder, "MESH_PRIMITIVE_CYLINDER", MeshCylinder, "Cylinder", "Generate a cylinder mesh") +DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_GRID, 0, "MESH_PRIMITIVE_GRID",MeshGrid, "Grid", "Generate a planar mesh on the XY plane") +DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_ICO_SPHERE, 0, "MESH_PRIMITIVE_ICO_SPHERE", MeshIcoSphere, "Ico Sphere", "Generate a spherical mesh that consists of equally sized triangles") +DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_LINE, def_geo_mesh_line, "MESH_PRIMITIVE_LINE",MeshLine, "Mesh Line", "Generate vertices in a line and connect them with edges") +DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_UV_SPHERE, 0, "MESH_PRIMITIVE_UV_SPHERE", MeshUVSphere, "UV Sphere", "Generate a spherical mesh with quads, except for triangles at the top and bottom") +DefNode(GeometryNode, GEO_NODE_MESH_TO_CURVE, 0, "MESH_TO_CURVE", MeshToCurve, "Mesh to Curve", "Generate a curve from a mesh") +DefNode(GeometryNode, GEO_NODE_MESH_TO_POINTS, def_geo_mesh_to_points, "MESH_TO_POINTS", MeshToPoints, "Mesh to Points", "Generate a point cloud from a mesh's vertices") +DefNode(GeometryNode, GEO_NODE_MESH_TO_VOLUME, def_geo_mesh_to_volume, "MESH_TO_VOLUME", MeshToVolume, "Mesh To Volume", "Create a fog volume with the shape of the input mesh's surface") +DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, def_geo_object_info, "OBJECT_INFO", ObjectInfo, "Object Info", "Retrieve information from an object") +DefNode(GeometryNode, GEO_NODE_POINTS, 0, "POINTS", Points, "Points", "Generate a point cloud with positions and radii defined by fields") +DefNode(GeometryNode, GEO_NODE_POINTS_TO_VERTICES, 0, "POINTS_TO_VERTICES", PointsToVertices, "Points to Vertices", "Generate a mesh vertex for each point cloud point") +DefNode(GeometryNode, GEO_NODE_POINTS_TO_VOLUME, def_geo_points_to_volume, "POINTS_TO_VOLUME", PointsToVolume, "Points to Volume", "Generate a fog volume sphere around every point") +DefNode(GeometryNode, GEO_NODE_PROXIMITY, def_geo_proximity, "PROXIMITY", Proximity, "Geometry Proximity", "Compute the closest location on the target geometry") +DefNode(GeometryNode, GEO_NODE_RAYCAST, def_geo_raycast, "RAYCAST", Raycast, "Raycast", "Cast rays from the context geometry onto a target geometry, and retrieve information from each hit point") +DefNode(GeometryNode, GEO_NODE_REMOVE_ATTRIBUTE, 0, "REMOVE_ATTRIBUTE", RemoveAttribute, "Remove Named Attribute", "Delete an attribute with a specified name from a geometry. Typically used to optimize performance") +DefNode(GeometryNode, GEO_NODE_REALIZE_INSTANCES, def_geo_realize_instances,"REALIZE_INSTANCES", RealizeInstances, "Realize Instances", "Change the direction of the curve by swapping each spline's start and end data") +DefNode(GeometryNode, GEO_NODE_REPLACE_MATERIAL, 0, "REPLACE_MATERIAL", ReplaceMaterial, "Replace Material", "Swap one material with another") +DefNode(GeometryNode, GEO_NODE_RESAMPLE_CURVE, def_geo_curve_resample, "RESAMPLE_CURVE", ResampleCurve, "Resample Curve", "Generate a poly spline for each input spline") +DefNode(GeometryNode, GEO_NODE_REVERSE_CURVE, 0, "REVERSE_CURVE", ReverseCurve, "Reverse Curve", "Swap the start and end of splines") +DefNode(GeometryNode, GEO_NODE_ROTATE_INSTANCES, 0, "ROTATE_INSTANCES", RotateInstances, "Rotate Instances", "Rotate geometry instances in local or global space") +DefNode(GeometryNode, GEO_NODE_SAMPLE_CURVE, def_geo_curve_sample, "SAMPLE_CURVE", SampleCurve, "Sample Curve", "Retrieve data from a point on a curve at a certain distance from its start") +DefNode(GeometryNode, GEO_NODE_SCALE_ELEMENTS, def_geo_scale_elements, "SCALE_ELEMENTS", ScaleElements, "Scale Elements", "Scale groups of connected edges and faces") +DefNode(GeometryNode, GEO_NODE_SCALE_INSTANCES, 0, "SCALE_INSTANCES", ScaleInstances, "Scale Instances", "Scale geometry instances in local or global space") +DefNode(GeometryNode, GEO_NODE_SEPARATE_COMPONENTS, 0, "SEPARATE_COMPONENTS",SeparateComponents, "Separate Components","Split a geometry into a separate output for each type of data in the geometry") +DefNode(GeometryNode, GEO_NODE_SEPARATE_GEOMETRY, def_geo_separate_geometry,"SEPARATE_GEOMETRY", SeparateGeometry, "Separate Geometry", "Split a geometry into two geometry outputs based on a selection") +DefNode(GeometryNode, GEO_NODE_SET_CURVE_HANDLES, def_geo_curve_set_handle_positions, "SET_CURVE_HANDLES", SetCurveHandlePositions, "Set Handle Positions", "Set the positions for the handles of Bézier curves") +DefNode(GeometryNode, GEO_NODE_SET_CURVE_RADIUS, 0, "SET_CURVE_RADIUS", SetCurveRadius, "Set Curve Radius", "Set the radius of the curve at each control point") +DefNode(GeometryNode, GEO_NODE_SET_CURVE_TILT, 0, "SET_CURVE_TILT", SetCurveTilt, "Set Curve Tilt", "Set the tilt angle at each curve control point") +DefNode(GeometryNode, GEO_NODE_SET_ID, 0, "SET_ID", SetID, "Set ID", "Set the id attribute on the input geometry, mainly used internally for randomizing") +DefNode(GeometryNode, GEO_NODE_SET_MATERIAL_INDEX, 0, "SET_MATERIAL_INDEX", SetMaterialIndex, "Set Material Index", "Set the material index for each selected geometry element") +DefNode(GeometryNode, GEO_NODE_SET_MATERIAL, 0, "SET_MATERIAL", SetMaterial, "Set Material", "Assign a material to geometry elements") +DefNode(GeometryNode, GEO_NODE_SET_POINT_RADIUS, 0, "SET_POINT_RADIUS", SetPointRadius, "Set Point Radius", "Set the display size of point cloud points") +DefNode(GeometryNode, GEO_NODE_SET_POSITION, 0, "SET_POSITION", SetPosition, "Set Position", "Set the location of each point") +DefNode(GeometryNode, GEO_NODE_SET_SHADE_SMOOTH, 0, "SET_SHADE_SMOOTH", SetShadeSmooth, "Set Shade Smooth", "Control the smoothness of mesh normals around each face by changing the \"shade smooth\" attribute") +DefNode(GeometryNode, GEO_NODE_SET_SPLINE_CYCLIC, 0, "SET_SPLINE_CYCLIC", SetSplineCyclic, "Set Spline Cyclic", "Control whether each spline loops back on itself by changing the \"cyclic\" attribute") +DefNode(GeometryNode, GEO_NODE_SET_SPLINE_RESOLUTION, 0, "SET_SPLINE_RESOLUTION", SetSplineResolution, "Set Spline Resolution", "Control how many evaluated points should be generated on every curve segment") +DefNode(GeometryNode, GEO_NODE_SPLIT_EDGES, 0, "SPLIT_EDGES", SplitEdges, "Split Edges", "Duplicate mesh edges and break connections with the surrounding faces") +DefNode(GeometryNode, GEO_NODE_STORE_NAMED_ATTRIBUTE, def_geo_store_named_attribute, "STORE_NAMED_ATTRIBUTE", StoreNamedAttribute, "Store Named Attribute", "Store the result of a field on a geometry as an attribute with the specified name") +DefNode(GeometryNode, GEO_NODE_STRING_JOIN, 0, "STRING_JOIN", StringJoin, "Join Strings", "Combine any number of input strings") +DefNode(GeometryNode, GEO_NODE_STRING_TO_CURVES, def_geo_string_to_curves, "STRING_TO_CURVES", StringToCurves, "String to Curves", "Generate a paragraph of text with a specific font, using a curve instance to store each character") +DefNode(GeometryNode, GEO_NODE_SUBDIVIDE_CURVE, 0, "SUBDIVIDE_CURVE", SubdivideCurve, "Subdivide Curve", "Dividing each curve segment into a specified number of pieces") +DefNode(GeometryNode, GEO_NODE_SUBDIVIDE_MESH, 0, "SUBDIVIDE_MESH", SubdivideMesh, "Subdivide Mesh", "Divide mesh faces into smaller ones without changing the shape or volume, using linear interpolation to place the new vertices") +DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, def_geo_subdivision_surface, "SUBDIVISION_SURFACE",SubdivisionSurface, "Subdivision Surface","Divide mesh faces to form a smooth surface, using the Catmull-Clark subdivision method") +DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "Switch between two inputs") +DefNode(GeometryNode, GEO_NODE_TRANSFER_ATTRIBUTE, def_geo_transfer_attribute, "ATTRIBUTE_TRANSFER", AttributeTransfer, "Transfer Attribute", "Retrieve values from a source geometry and provides them as a field by interpolating them with the context geomerty") +DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "Translate, rotate or scale the geometry") +DefNode(GeometryNode, GEO_NODE_TRANSLATE_INSTANCES, 0, "TRANSLATE_INSTANCES",TranslateInstances, "Translate Instances","Move top-level geometry instances in local or global space") +DefNode(GeometryNode, GEO_NODE_TRIANGULATE, def_geo_triangulate, "TRIANGULATE", Triangulate, "Triangulate", "Convert all faces in a mesh to triangular faces") +DefNode(GeometryNode, GEO_NODE_TRIM_CURVE, def_geo_curve_trim, "TRIM_CURVE", TrimCurve, "Trim Curve", "Shorten curves by removing portions at the start or end") +DefNode(GeometryNode, GEO_NODE_VIEWER, def_geo_viewer, "VIEWER", Viewer, "Viewer", "Display the input data in the Spreadsheet Editor") +DefNode(GeometryNode, GEO_NODE_VOLUME_CUBE, 0, "VOLUME_CUBE", VolumeCube, "Volume Cube", "Generate a dense volume with a field that controls the density at each grid voxel based on its position") +DefNode(GeometryNode, GEO_NODE_VOLUME_TO_MESH, def_geo_volume_to_mesh, "VOLUME_TO_MESH", VolumeToMesh, "Volume to Mesh", "Generate a mesh on the \"surface\" of a volume") +DefNode(GeometryNode, GEO_NODE_UV_PACK_ISLANDS, 0, "UV_PACK_ISLANDS", UVPackIslands, "Pack UV Islands", "Scale islands of a UV map and move them so they fill the UV space as much as possible") +DefNode(GeometryNode, GEO_NODE_UV_UNWRAP, def_geo_uv_unwrap, "UV_UNWRAP", UVUnwrap, "UV Unwrap", "Generate a UV map islands based on seam edges") /* undefine macros */ #undef DefNode -- cgit v1.2.3 From 7a74d91e323c4d695b908ca4178837cee756eeaf Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Mon, 25 Jul 2022 13:53:48 +0200 Subject: Cleanup: move device BVH code to kernel/device/*/bvh.h Having the OptiX/MetalRT/Embree/MetalRT implementations all in one file with many #ifdefs became too confusing. Instead split it up per device, and also move it together with device specific hit/filter/intersect functions and associated data types. --- intern/cycles/bvh/embree.cpp | 282 +---- intern/cycles/kernel/CMakeLists.txt | 5 +- intern/cycles/kernel/bvh/bvh.h | 814 +++----------- intern/cycles/kernel/bvh/embree.h | 176 --- intern/cycles/kernel/bvh/metal.h | 37 - intern/cycles/kernel/bvh/util.h | 15 + intern/cycles/kernel/device/cpu/bvh.h | 609 +++++++++++ intern/cycles/kernel/device/metal/bvh.h | 1123 ++++++++++++++++++++ intern/cycles/kernel/device/metal/compat.h | 2 - intern/cycles/kernel/device/metal/kernel.metal | 708 ------------ intern/cycles/kernel/device/optix/bvh.h | 646 +++++++++++ intern/cycles/kernel/device/optix/compat.h | 1 - intern/cycles/kernel/device/optix/kernel.cu | 421 -------- .../kernel/integrator/intersect_volume_stack.h | 6 +- .../kernel/integrator/subsurface_random_walk.h | 1 - intern/cycles/kernel/types.h | 8 - 16 files changed, 2528 insertions(+), 2326 deletions(-) delete mode 100644 intern/cycles/kernel/bvh/embree.h delete mode 100644 intern/cycles/kernel/bvh/metal.h create mode 100644 intern/cycles/kernel/device/cpu/bvh.h create mode 100644 intern/cycles/kernel/device/metal/bvh.h create mode 100644 intern/cycles/kernel/device/optix/bvh.h diff --git a/intern/cycles/bvh/embree.cpp b/intern/cycles/bvh/embree.cpp index eed7ae19965..be5785de473 100644 --- a/intern/cycles/bvh/embree.cpp +++ b/intern/cycles/bvh/embree.cpp @@ -21,13 +21,9 @@ # include "bvh/embree.h" -/* Kernel includes are necessary so that the filter function for Embree can access the packed BVH. - */ -# include "kernel/bvh/embree.h" -# include "kernel/bvh/util.h" +# include "kernel/device/cpu/bvh.h" # include "kernel/device/cpu/compat.h" # include "kernel/device/cpu/globals.h" -# include "kernel/sample/lcg.h" # include "scene/hair.h" # include "scene/mesh.h" @@ -46,265 +42,6 @@ static_assert(Object::MAX_MOTION_STEPS <= RTC_MAX_TIME_STEP_COUNT, static_assert(Object::MAX_MOTION_STEPS == Geometry::MAX_MOTION_STEPS, "Object and Geometry max motion steps inconsistent"); -# define IS_HAIR(x) (x & 1) - -/* This gets called by Embree at every valid ray/object intersection. - * Things like recording subsurface or shadow hits for later evaluation - * as well as filtering for volume objects happen here. - * Cycles' own BVH does that directly inside the traversal calls. - */ -static void rtc_filter_intersection_func(const RTCFilterFunctionNArguments *args) -{ - /* Current implementation in Cycles assumes only single-ray intersection queries. */ - assert(args->N == 1); - - RTCHit *hit = (RTCHit *)args->hit; - CCLIntersectContext *ctx = ((IntersectContext *)args->context)->userRayExt; - const KernelGlobalsCPU *kg = ctx->kg; - const Ray *cray = ctx->ray; - - if (kernel_embree_is_self_intersection(kg, hit, cray)) { - *args->valid = 0; - } -} - -/* This gets called by Embree at every valid ray/object intersection. - * Things like recording subsurface or shadow hits for later evaluation - * as well as filtering for volume objects happen here. - * Cycles' own BVH does that directly inside the traversal calls. - */ -static void rtc_filter_occluded_func(const RTCFilterFunctionNArguments *args) -{ - /* Current implementation in Cycles assumes only single-ray intersection queries. */ - assert(args->N == 1); - - const RTCRay *ray = (RTCRay *)args->ray; - RTCHit *hit = (RTCHit *)args->hit; - CCLIntersectContext *ctx = ((IntersectContext *)args->context)->userRayExt; - const KernelGlobalsCPU *kg = ctx->kg; - const Ray *cray = ctx->ray; - - switch (ctx->type) { - case CCLIntersectContext::RAY_SHADOW_ALL: { - Intersection current_isect; - kernel_embree_convert_hit(kg, ray, hit, ¤t_isect); - if (intersection_skip_self_shadow(cray->self, current_isect.object, current_isect.prim)) { - *args->valid = 0; - return; - } - /* If no transparent shadows or max number of hits exceeded, all light is blocked. */ - const int flags = intersection_get_shader_flags(kg, current_isect.prim, current_isect.type); - if (!(flags & (SD_HAS_TRANSPARENT_SHADOW)) || ctx->num_hits >= ctx->max_hits) { - ctx->opaque_hit = true; - return; - } - - ++ctx->num_hits; - - /* Always use baked shadow transparency for curves. */ - if (current_isect.type & PRIMITIVE_CURVE) { - ctx->throughput *= intersection_curve_shadow_transparency( - kg, current_isect.object, current_isect.prim, current_isect.u); - - if (ctx->throughput < CURVE_SHADOW_TRANSPARENCY_CUTOFF) { - ctx->opaque_hit = true; - return; - } - else { - *args->valid = 0; - return; - } - } - - /* Test if we need to record this transparent intersection. */ - const uint max_record_hits = min(ctx->max_hits, INTEGRATOR_SHADOW_ISECT_SIZE); - if (ctx->num_recorded_hits < max_record_hits || ray->tfar < ctx->max_t) { - /* If maximum number of hits was reached, replace the intersection with the - * highest distance. We want to find the N closest intersections. */ - const uint num_recorded_hits = min(ctx->num_recorded_hits, max_record_hits); - uint isect_index = num_recorded_hits; - if (num_recorded_hits + 1 >= max_record_hits) { - float max_t = ctx->isect_s[0].t; - uint max_recorded_hit = 0; - - for (uint i = 1; i < num_recorded_hits; ++i) { - if (ctx->isect_s[i].t > max_t) { - max_recorded_hit = i; - max_t = ctx->isect_s[i].t; - } - } - - if (num_recorded_hits >= max_record_hits) { - isect_index = max_recorded_hit; - } - - /* Limit the ray distance and stop counting hits beyond this. - * TODO: is there some way we can tell Embree to stop intersecting beyond - * this distance when max number of hits is reached?. Or maybe it will - * become irrelevant if we make max_hits a very high number on the CPU. */ - ctx->max_t = max(current_isect.t, max_t); - } - - ctx->isect_s[isect_index] = current_isect; - } - - /* Always increase the number of recorded hits, even beyond the maximum, - * so that we can detect this and trace another ray if needed. */ - ++ctx->num_recorded_hits; - - /* This tells Embree to continue tracing. */ - *args->valid = 0; - break; - } - case CCLIntersectContext::RAY_LOCAL: - case CCLIntersectContext::RAY_SSS: { - /* Check if it's hitting the correct object. */ - Intersection current_isect; - if (ctx->type == CCLIntersectContext::RAY_SSS) { - kernel_embree_convert_sss_hit(kg, ray, hit, ¤t_isect, ctx->local_object_id); - } - else { - kernel_embree_convert_hit(kg, ray, hit, ¤t_isect); - if (ctx->local_object_id != current_isect.object) { - /* This tells Embree to continue tracing. */ - *args->valid = 0; - break; - } - } - if (intersection_skip_self_local(cray->self, current_isect.prim)) { - *args->valid = 0; - return; - } - - /* No intersection information requested, just return a hit. */ - if (ctx->max_hits == 0) { - break; - } - - /* Ignore curves. */ - if (IS_HAIR(hit->geomID)) { - /* This tells Embree to continue tracing. */ - *args->valid = 0; - break; - } - - LocalIntersection *local_isect = ctx->local_isect; - int hit_idx = 0; - - if (ctx->lcg_state) { - /* See triangle_intersect_subsurface() for the native equivalent. */ - for (int i = min((int)ctx->max_hits, local_isect->num_hits) - 1; i >= 0; --i) { - if (local_isect->hits[i].t == ray->tfar) { - /* This tells Embree to continue tracing. */ - *args->valid = 0; - return; - } - } - - local_isect->num_hits++; - - if (local_isect->num_hits <= ctx->max_hits) { - hit_idx = local_isect->num_hits - 1; - } - else { - /* reservoir sampling: if we are at the maximum number of - * hits, randomly replace element or skip it */ - hit_idx = lcg_step_uint(ctx->lcg_state) % local_isect->num_hits; - - if (hit_idx >= ctx->max_hits) { - /* This tells Embree to continue tracing. */ - *args->valid = 0; - return; - } - } - } - else { - /* Record closest intersection only. */ - if (local_isect->num_hits && current_isect.t > local_isect->hits[0].t) { - *args->valid = 0; - return; - } - - local_isect->num_hits = 1; - } - - /* record intersection */ - local_isect->hits[hit_idx] = current_isect; - local_isect->Ng[hit_idx] = normalize(make_float3(hit->Ng_x, hit->Ng_y, hit->Ng_z)); - /* This tells Embree to continue tracing. */ - *args->valid = 0; - break; - } - case CCLIntersectContext::RAY_VOLUME_ALL: { - /* Append the intersection to the end of the array. */ - if (ctx->num_hits < ctx->max_hits) { - Intersection current_isect; - kernel_embree_convert_hit(kg, ray, hit, ¤t_isect); - if (intersection_skip_self(cray->self, current_isect.object, current_isect.prim)) { - *args->valid = 0; - return; - } - - Intersection *isect = &ctx->isect_s[ctx->num_hits]; - ++ctx->num_hits; - *isect = current_isect; - /* Only primitives from volume object. */ - uint tri_object = isect->object; - int object_flag = kernel_data_fetch(object_flag, tri_object); - if ((object_flag & SD_OBJECT_HAS_VOLUME) == 0) { - --ctx->num_hits; - } - /* This tells Embree to continue tracing. */ - *args->valid = 0; - } - break; - } - case CCLIntersectContext::RAY_REGULAR: - default: - if (kernel_embree_is_self_intersection(kg, hit, cray)) { - *args->valid = 0; - return; - } - break; - } -} - -static void rtc_filter_func_backface_cull(const RTCFilterFunctionNArguments *args) -{ - const RTCRay *ray = (RTCRay *)args->ray; - RTCHit *hit = (RTCHit *)args->hit; - - /* Always ignore back-facing intersections. */ - if (dot(make_float3(ray->dir_x, ray->dir_y, ray->dir_z), - make_float3(hit->Ng_x, hit->Ng_y, hit->Ng_z)) > 0.0f) { - *args->valid = 0; - return; - } - - CCLIntersectContext *ctx = ((IntersectContext *)args->context)->userRayExt; - const KernelGlobalsCPU *kg = ctx->kg; - const Ray *cray = ctx->ray; - - if (kernel_embree_is_self_intersection(kg, hit, cray)) { - *args->valid = 0; - } -} - -static void rtc_filter_occluded_func_backface_cull(const RTCFilterFunctionNArguments *args) -{ - const RTCRay *ray = (RTCRay *)args->ray; - RTCHit *hit = (RTCHit *)args->hit; - - /* Always ignore back-facing intersections. */ - if (dot(make_float3(ray->dir_x, ray->dir_y, ray->dir_z), - make_float3(hit->Ng_x, hit->Ng_y, hit->Ng_z)) > 0.0f) { - *args->valid = 0; - return; - } - - rtc_filter_occluded_func(args); -} - static size_t unaccounted_mem = 0; static bool rtc_memory_monitor_func(void *userPtr, const ssize_t bytes, const bool) @@ -535,8 +272,8 @@ void BVHEmbree::add_triangles(const Object *ob, const Mesh *mesh, int i) set_tri_vertex_buffer(geom_id, mesh, false); rtcSetGeometryUserData(geom_id, (void *)prim_offset); - rtcSetGeometryOccludedFilterFunction(geom_id, rtc_filter_occluded_func); - rtcSetGeometryIntersectFilterFunction(geom_id, rtc_filter_intersection_func); + rtcSetGeometryOccludedFilterFunction(geom_id, kernel_embree_filter_occluded_func); + rtcSetGeometryIntersectFilterFunction(geom_id, kernel_embree_filter_intersection_func); rtcSetGeometryMask(geom_id, ob->visibility_for_tracing()); rtcCommitGeometry(geom_id); @@ -739,8 +476,8 @@ void BVHEmbree::add_points(const Object *ob, const PointCloud *pointcloud, int i set_point_vertex_buffer(geom_id, pointcloud, false); rtcSetGeometryUserData(geom_id, (void *)prim_offset); - rtcSetGeometryIntersectFilterFunction(geom_id, rtc_filter_func_backface_cull); - rtcSetGeometryOccludedFilterFunction(geom_id, rtc_filter_occluded_func_backface_cull); + rtcSetGeometryIntersectFilterFunction(geom_id, kernel_embree_filter_func_backface_cull); + rtcSetGeometryOccludedFilterFunction(geom_id, kernel_embree_filter_occluded_func_backface_cull); rtcSetGeometryMask(geom_id, ob->visibility_for_tracing()); rtcCommitGeometry(geom_id); @@ -799,12 +536,13 @@ void BVHEmbree::add_curves(const Object *ob, const Hair *hair, int i) rtcSetGeometryUserData(geom_id, (void *)prim_offset); if (hair->curve_shape == CURVE_RIBBON) { - rtcSetGeometryIntersectFilterFunction(geom_id, rtc_filter_intersection_func); - rtcSetGeometryOccludedFilterFunction(geom_id, rtc_filter_occluded_func); + rtcSetGeometryIntersectFilterFunction(geom_id, kernel_embree_filter_intersection_func); + rtcSetGeometryOccludedFilterFunction(geom_id, kernel_embree_filter_occluded_func); } else { - rtcSetGeometryIntersectFilterFunction(geom_id, rtc_filter_func_backface_cull); - rtcSetGeometryOccludedFilterFunction(geom_id, rtc_filter_occluded_func_backface_cull); + rtcSetGeometryIntersectFilterFunction(geom_id, kernel_embree_filter_func_backface_cull); + rtcSetGeometryOccludedFilterFunction(geom_id, + kernel_embree_filter_occluded_func_backface_cull); } rtcSetGeometryMask(geom_id, ob->visibility_for_tracing()); diff --git a/intern/cycles/kernel/CMakeLists.txt b/intern/cycles/kernel/CMakeLists.txt index 21a78722c0d..94632dff200 100644 --- a/intern/cycles/kernel/CMakeLists.txt +++ b/intern/cycles/kernel/CMakeLists.txt @@ -42,6 +42,7 @@ set(SRC_KERNEL_DEVICE_ONEAPI ) set(SRC_KERNEL_DEVICE_CPU_HEADERS + device/cpu/bvh.h device/cpu/compat.h device/cpu/image.h device/cpu/globals.h @@ -71,11 +72,13 @@ set(SRC_KERNEL_DEVICE_HIP_HEADERS ) set(SRC_KERNEL_DEVICE_OPTIX_HEADERS + device/optix/bvh.h device/optix/compat.h device/optix/globals.h ) set(SRC_KERNEL_DEVICE_METAL_HEADERS + device/metal/bvh.h device/metal/compat.h device/metal/context_begin.h device/metal/context_end.h @@ -214,8 +217,6 @@ set(SRC_KERNEL_BVH_HEADERS bvh/util.h bvh/volume.h bvh/volume_all.h - bvh/embree.h - bvh/metal.h ) set(SRC_KERNEL_CAMERA_HEADERS diff --git a/intern/cycles/kernel/bvh/bvh.h b/intern/cycles/kernel/bvh/bvh.h index 387e74b9885..bcefe5d970c 100644 --- a/intern/cycles/kernel/bvh/bvh.h +++ b/intern/cycles/kernel/bvh/bvh.h @@ -1,40 +1,46 @@ /* SPDX-License-Identifier: Apache-2.0 * Copyright 2011-2022 Blender Foundation */ -/* BVH - * - * Bounding volume hierarchy for ray tracing. We compile different variations - * of the same BVH traversal function for faster rendering when some types of - * primitives are not needed, using #includes to work around the lack of - * C++ templates in OpenCL. - * - * Originally based on "Understanding the Efficiency of Ray Traversal on GPUs", - * the code has been extended and modified to support more primitives and work - * with CPU/CUDA/OpenCL. */ - #pragma once -#ifdef __EMBREE__ -# include "kernel/bvh/embree.h" -#endif - -#ifdef __METALRT__ -# include "kernel/bvh/metal.h" -#endif - #include "kernel/bvh/types.h" #include "kernel/bvh/util.h" #include "kernel/integrator/state_util.h" +/* Device specific accleration structures for ray tracing. */ + +#if defined(__EMBREE__) +# include "kernel/device/cpu/bvh.h" +#elif defined(__METALRT__) +# include "kernel/device/metal/bvh.h" +#elif defined(__KERNEL_OPTIX__) +# include "kernel/device/optix/bvh.h" +#else +# define __BVH2__ +#endif + CCL_NAMESPACE_BEGIN -#if !defined(__KERNEL_GPU_RAYTRACING__) +#ifdef __BVH2__ -/* Regular BVH traversal */ +/* BVH2 + * + * Bounding volume hierarchy for ray tracing, when no native acceleration + * structure is available for the device. + + * We compile different variations of the same BVH traversal function for + * faster rendering when some types of primitives are not needed, using #includes + * to work around the lack of C++ templates in OpenCL. + * + * Originally based on "Understanding the Efficiency of Ray Traversal on GPUs", + * the code has been extended and modified to support more primitives and work + * with CPU and various GPU kernel languages. */ # include "kernel/bvh/nodes.h" +/* Regular BVH traversal */ + # define BVH_FUNCTION_NAME bvh_intersect # define BVH_FUNCTION_FEATURES BVH_POINTCLOUD # include "kernel/bvh/traversal.h" @@ -57,261 +63,15 @@ CCL_NAMESPACE_BEGIN # include "kernel/bvh/traversal.h" # endif -/* Subsurface scattering BVH traversal */ - -# if defined(__BVH_LOCAL__) -# define BVH_FUNCTION_NAME bvh_intersect_local -# define BVH_FUNCTION_FEATURES BVH_HAIR -# include "kernel/bvh/local.h" - -# if defined(__OBJECT_MOTION__) -# define BVH_FUNCTION_NAME bvh_intersect_local_motion -# define BVH_FUNCTION_FEATURES BVH_MOTION | BVH_HAIR -# include "kernel/bvh/local.h" -# endif -# endif /* __BVH_LOCAL__ */ - -/* Volume BVH traversal */ - -# if defined(__VOLUME__) -# define BVH_FUNCTION_NAME bvh_intersect_volume -# define BVH_FUNCTION_FEATURES BVH_HAIR -# include "kernel/bvh/volume.h" - -# if defined(__OBJECT_MOTION__) -# define BVH_FUNCTION_NAME bvh_intersect_volume_motion -# define BVH_FUNCTION_FEATURES BVH_MOTION | BVH_HAIR -# include "kernel/bvh/volume.h" -# endif -# endif /* __VOLUME__ */ - -/* Record all intersections - Shadow BVH traversal */ - -# if defined(__SHADOW_RECORD_ALL__) -# define BVH_FUNCTION_NAME bvh_intersect_shadow_all -# define BVH_FUNCTION_FEATURES BVH_POINTCLOUD -# include "kernel/bvh/shadow_all.h" - -# if defined(__HAIR__) -# define BVH_FUNCTION_NAME bvh_intersect_shadow_all_hair -# define BVH_FUNCTION_FEATURES BVH_HAIR | BVH_POINTCLOUD -# include "kernel/bvh/shadow_all.h" -# endif - -# if defined(__OBJECT_MOTION__) -# define BVH_FUNCTION_NAME bvh_intersect_shadow_all_motion -# define BVH_FUNCTION_FEATURES BVH_MOTION | BVH_POINTCLOUD -# include "kernel/bvh/shadow_all.h" -# endif - -# if defined(__HAIR__) && defined(__OBJECT_MOTION__) -# define BVH_FUNCTION_NAME bvh_intersect_shadow_all_hair_motion -# define BVH_FUNCTION_FEATURES BVH_HAIR | BVH_MOTION | BVH_POINTCLOUD -# include "kernel/bvh/shadow_all.h" -# endif - -# endif /* __SHADOW_RECORD_ALL__ */ - -/* Record all intersections - Volume BVH traversal. */ - -# if defined(__VOLUME_RECORD_ALL__) -# define BVH_FUNCTION_NAME bvh_intersect_volume_all -# define BVH_FUNCTION_FEATURES BVH_HAIR -# include "kernel/bvh/volume_all.h" - -# if defined(__OBJECT_MOTION__) -# define BVH_FUNCTION_NAME bvh_intersect_volume_all_motion -# define BVH_FUNCTION_FEATURES BVH_MOTION | BVH_HAIR -# include "kernel/bvh/volume_all.h" -# endif -# endif /* __VOLUME_RECORD_ALL__ */ - -# undef BVH_FEATURE -# undef BVH_NAME_JOIN -# undef BVH_NAME_EVAL -# undef BVH_FUNCTION_FULL_NAME - -#endif /* !defined(__KERNEL_GPU_RAYTRACING__) */ - -ccl_device_inline bool scene_intersect_valid(ccl_private const Ray *ray) -{ - /* NOTE: Due to some vectorization code non-finite origin point might - * cause lots of false-positive intersections which will overflow traversal - * stack. - * This code is a quick way to perform early output, to avoid crashes in - * such cases. - * From production scenes so far it seems it's enough to test first element - * only. - * Scene intersection may also called with empty rays for conditional trace - * calls that evaluate to false, so filter those out. - */ - return isfinite_safe(ray->P.x) && isfinite_safe(ray->D.x) && len_squared(ray->D) != 0.0f; -} - ccl_device_intersect bool scene_intersect(KernelGlobals kg, ccl_private const Ray *ray, const uint visibility, ccl_private Intersection *isect) { -#ifdef __KERNEL_OPTIX__ - uint p0 = 0; - uint p1 = 0; - uint p2 = 0; - uint p3 = 0; - uint p4 = visibility; - uint p5 = PRIMITIVE_NONE; - uint p6 = ((uint64_t)ray) & 0xFFFFFFFF; - uint p7 = (((uint64_t)ray) >> 32) & 0xFFFFFFFF; - - uint ray_mask = visibility & 0xFF; - uint ray_flags = OPTIX_RAY_FLAG_ENFORCE_ANYHIT; - if (0 == ray_mask && (visibility & ~0xFF) != 0) { - ray_mask = 0xFF; - } - else if (visibility & PATH_RAY_SHADOW_OPAQUE) { - ray_flags |= OPTIX_RAY_FLAG_TERMINATE_ON_FIRST_HIT; - } - - optixTrace(scene_intersect_valid(ray) ? kernel_data.device_bvh : 0, - ray->P, - ray->D, - ray->tmin, - ray->tmax, - ray->time, - ray_mask, - ray_flags, - 0, /* SBT offset for PG_HITD */ - 0, - 0, - p0, - p1, - p2, - p3, - p4, - p5, - p6, - p7); - - isect->t = __uint_as_float(p0); - isect->u = __uint_as_float(p1); - isect->v = __uint_as_float(p2); - isect->prim = p3; - isect->object = p4; - isect->type = p5; - - return p5 != PRIMITIVE_NONE; -#elif defined(__METALRT__) - - if (!scene_intersect_valid(ray)) { - isect->t = ray->tmax; - isect->type = PRIMITIVE_NONE; + if (!intersection_ray_valid(ray)) { return false; } -# if defined(__KERNEL_DEBUG__) - if (is_null_instance_acceleration_structure(metal_ancillaries->accel_struct)) { - isect->t = ray->tmax; - isect->type = PRIMITIVE_NONE; - kernel_assert(!"Invalid metal_ancillaries->accel_struct pointer"); - return false; - } - - if (is_null_intersection_function_table(metal_ancillaries->ift_default)) { - isect->t = ray->tmax; - isect->type = PRIMITIVE_NONE; - kernel_assert(!"Invalid ift_default"); - return false; - } -# endif - - metal::raytracing::ray r(ray->P, ray->D, ray->tmin, ray->tmax); - metalrt_intersector_type metalrt_intersect; - - if (!kernel_data.bvh.have_curves) { - metalrt_intersect.assume_geometry_type(metal::raytracing::geometry_type::triangle); - } - - MetalRTIntersectionPayload payload; - payload.self = ray->self; - payload.u = 0.0f; - payload.v = 0.0f; - payload.visibility = visibility; - - typename metalrt_intersector_type::result_type intersection; - - uint ray_mask = visibility & 0xFF; - if (0 == ray_mask && (visibility & ~0xFF) != 0) { - ray_mask = 0xFF; - /* No further intersector setup required: Default MetalRT behavior is any-hit. */ - } - else if (visibility & PATH_RAY_SHADOW_OPAQUE) { - /* No further intersector setup required: Shadow ray early termination is controlled by the - * intersection handler */ - } - -# if defined(__METALRT_MOTION__) - payload.time = ray->time; - intersection = metalrt_intersect.intersect(r, - metal_ancillaries->accel_struct, - ray_mask, - ray->time, - metal_ancillaries->ift_default, - payload); -# else - intersection = metalrt_intersect.intersect( - r, metal_ancillaries->accel_struct, ray_mask, metal_ancillaries->ift_default, payload); -# endif - - if (intersection.type == intersection_type::none) { - isect->t = ray->tmax; - isect->type = PRIMITIVE_NONE; - - return false; - } - - isect->t = intersection.distance; - - isect->prim = payload.prim; - isect->type = payload.type; - isect->object = intersection.user_instance_id; - - isect->t = intersection.distance; - if (intersection.type == intersection_type::triangle) { - isect->u = 1.0f - intersection.triangle_barycentric_coord.y - - intersection.triangle_barycentric_coord.x; - isect->v = intersection.triangle_barycentric_coord.x; - } - else { - isect->u = payload.u; - isect->v = payload.v; - } - - return isect->type != PRIMITIVE_NONE; - -#else - - if (!scene_intersect_valid(ray)) { - return false; - } - -# ifdef __EMBREE__ - if (kernel_data.device_bvh) { - isect->t = ray->tmax; - CCLIntersectContext ctx(kg, CCLIntersectContext::RAY_REGULAR); - IntersectContext rtc_ctx(&ctx); - RTCRayHit ray_hit; - ctx.ray = ray; - kernel_embree_setup_rayhit(*ray, ray_hit, visibility); - rtcIntersect1(kernel_data.device_bvh, &rtc_ctx.context, &ray_hit); - if (ray_hit.hit.geomID != RTC_INVALID_GEOMETRY_ID && - ray_hit.hit.primID != RTC_INVALID_GEOMETRY_ID) { - kernel_embree_convert_hit(kg, &ray_hit.ray, &ray_hit.hit, isect); - return true; - } - return false; - } -# endif /* __EMBREE__ */ - # ifdef __OBJECT_MOTION__ if (kernel_data.bvh.have_motion) { # ifdef __HAIR__ @@ -322,7 +82,7 @@ ccl_device_intersect bool scene_intersect(KernelGlobals kg, return bvh_intersect_motion(kg, ray, isect, visibility); } -# endif /* __OBJECT_MOTION__ */ +# endif /* __OBJECT_MOTION__ */ # ifdef __HAIR__ if (kernel_data.bvh.have_curves) { @@ -331,10 +91,22 @@ ccl_device_intersect bool scene_intersect(KernelGlobals kg, # endif /* __HAIR__ */ return bvh_intersect(kg, ray, isect, visibility); -#endif /* __KERNEL_OPTIX__ */ } -#ifdef __BVH_LOCAL__ +/* Single object BVH traversal, for SSS/AO/bevel. */ + +# ifdef __BVH_LOCAL__ + +# define BVH_FUNCTION_NAME bvh_intersect_local +# define BVH_FUNCTION_FEATURES BVH_HAIR +# include "kernel/bvh/local.h" + +# if defined(__OBJECT_MOTION__) +# define BVH_FUNCTION_NAME bvh_intersect_local_motion +# define BVH_FUNCTION_FEATURES BVH_MOTION | BVH_HAIR +# include "kernel/bvh/local.h" +# endif + ccl_device_intersect bool scene_intersect_local(KernelGlobals kg, ccl_private const Ray *ray, ccl_private LocalIntersection *local_isect, @@ -342,177 +114,48 @@ ccl_device_intersect bool scene_intersect_local(KernelGlobals kg, ccl_private uint *lcg_state, int max_hits) { -# ifdef __KERNEL_OPTIX__ - uint p0 = pointer_pack_to_uint_0(lcg_state); - uint p1 = pointer_pack_to_uint_1(lcg_state); - uint p2 = pointer_pack_to_uint_0(local_isect); - uint p3 = pointer_pack_to_uint_1(local_isect); - uint p4 = local_object; - uint p6 = ((uint64_t)ray) & 0xFFFFFFFF; - uint p7 = (((uint64_t)ray) >> 32) & 0xFFFFFFFF; - - /* Is set to zero on miss or if ray is aborted, so can be used as return value. */ - uint p5 = max_hits; - - if (local_isect) { - local_isect->num_hits = 0; /* Initialize hit count to zero. */ - } - optixTrace(scene_intersect_valid(ray) ? kernel_data.device_bvh : 0, - ray->P, - ray->D, - ray->tmin, - ray->tmax, - ray->time, - 0xFF, - /* Need to always call into __anyhit__kernel_optix_local_hit. */ - OPTIX_RAY_FLAG_ENFORCE_ANYHIT, - 2, /* SBT offset for PG_HITL */ - 0, - 0, - p0, - p1, - p2, - p3, - p4, - p5, - p6, - p7); - - return p5; -# elif defined(__METALRT__) - if (!scene_intersect_valid(ray)) { - if (local_isect) { - local_isect->num_hits = 0; - } - return false; - } - -# if defined(__KERNEL_DEBUG__) - if (is_null_instance_acceleration_structure(metal_ancillaries->accel_struct)) { + if (!intersection_ray_valid(ray)) { if (local_isect) { local_isect->num_hits = 0; } - kernel_assert(!"Invalid metal_ancillaries->accel_struct pointer"); return false; } - if (is_null_intersection_function_table(metal_ancillaries->ift_local)) { - if (local_isect) { - local_isect->num_hits = 0; - } - kernel_assert(!"Invalid ift_local"); - return false; +# ifdef __OBJECT_MOTION__ + if (kernel_data.bvh.have_motion) { + return bvh_intersect_local_motion(kg, ray, local_isect, local_object, lcg_state, max_hits); } -# endif - - metal::raytracing::ray r(ray->P, ray->D, ray->tmin, ray->tmax); - metalrt_intersector_type metalrt_intersect; +# endif /* __OBJECT_MOTION__ */ + return bvh_intersect_local(kg, ray, local_isect, local_object, lcg_state, max_hits); +} +# endif - metalrt_intersect.force_opacity(metal::raytracing::forced_opacity::non_opaque); - if (!kernel_data.bvh.have_curves) { - metalrt_intersect.assume_geometry_type(metal::raytracing::geometry_type::triangle); - } +/* Transparent shadow BVH traversal, recording multiple intersections. */ - MetalRTIntersectionLocalPayload payload; - payload.self = ray->self; - payload.local_object = local_object; - payload.max_hits = max_hits; - payload.local_isect.num_hits = 0; - if (lcg_state) { - payload.has_lcg_state = true; - payload.lcg_state = *lcg_state; - } - payload.result = false; +# ifdef __SHADOW_RECORD_ALL__ - typename metalrt_intersector_type::result_type intersection; +# define BVH_FUNCTION_NAME bvh_intersect_shadow_all +# define BVH_FUNCTION_FEATURES BVH_POINTCLOUD +# include "kernel/bvh/shadow_all.h" -# if defined(__METALRT_MOTION__) - intersection = metalrt_intersect.intersect( - r, metal_ancillaries->accel_struct, 0xFF, ray->time, metal_ancillaries->ift_local, payload); -# else - intersection = metalrt_intersect.intersect( - r, metal_ancillaries->accel_struct, 0xFF, metal_ancillaries->ift_local, payload); +# if defined(__HAIR__) +# define BVH_FUNCTION_NAME bvh_intersect_shadow_all_hair +# define BVH_FUNCTION_FEATURES BVH_HAIR | BVH_POINTCLOUD +# include "kernel/bvh/shadow_all.h" # endif - if (lcg_state) { - *lcg_state = payload.lcg_state; - } - *local_isect = payload.local_isect; - - return payload.result; - -# else - - if (!scene_intersect_valid(ray)) { - if (local_isect) { - local_isect->num_hits = 0; - } - return false; - } - -# ifdef __EMBREE__ - if (kernel_data.device_bvh) { - const bool has_bvh = !(kernel_data_fetch(object_flag, local_object) & - SD_OBJECT_TRANSFORM_APPLIED); - CCLIntersectContext ctx( - kg, has_bvh ? CCLIntersectContext::RAY_SSS : CCLIntersectContext::RAY_LOCAL); - ctx.lcg_state = lcg_state; - ctx.max_hits = max_hits; - ctx.ray = ray; - ctx.local_isect = local_isect; - if (local_isect) { - local_isect->num_hits = 0; - } - ctx.local_object_id = local_object; - IntersectContext rtc_ctx(&ctx); - RTCRay rtc_ray; - kernel_embree_setup_ray(*ray, rtc_ray, PATH_RAY_ALL_VISIBILITY); - - /* If this object has its own BVH, use it. */ - if (has_bvh) { - RTCGeometry geom = rtcGetGeometry(kernel_data.device_bvh, local_object * 2); - if (geom) { - float3 P = ray->P; - float3 dir = ray->D; - float3 idir = ray->D; - bvh_instance_motion_push(kg, local_object, ray, &P, &dir, &idir); - - rtc_ray.org_x = P.x; - rtc_ray.org_y = P.y; - rtc_ray.org_z = P.z; - rtc_ray.dir_x = dir.x; - rtc_ray.dir_y = dir.y; - rtc_ray.dir_z = dir.z; - rtc_ray.tnear = ray->tmin; - rtc_ray.tfar = ray->tmax; - RTCScene scene = (RTCScene)rtcGetGeometryUserData(geom); - kernel_assert(scene); - if (scene) { - rtcOccluded1(scene, &rtc_ctx.context, &rtc_ray); - } - } - } - else { - rtcOccluded1(kernel_data.device_bvh, &rtc_ctx.context, &rtc_ray); - } - - /* rtcOccluded1 sets tfar to -inf if a hit was found. */ - return (local_isect && local_isect->num_hits > 0) || (rtc_ray.tfar < 0); - ; - } -# endif /* __EMBREE__ */ +# if defined(__OBJECT_MOTION__) +# define BVH_FUNCTION_NAME bvh_intersect_shadow_all_motion +# define BVH_FUNCTION_FEATURES BVH_MOTION | BVH_POINTCLOUD +# include "kernel/bvh/shadow_all.h" +# endif -# ifdef __OBJECT_MOTION__ - if (kernel_data.bvh.have_motion) { - return bvh_intersect_local_motion(kg, ray, local_isect, local_object, lcg_state, max_hits); - } -# endif /* __OBJECT_MOTION__ */ - return bvh_intersect_local(kg, ray, local_isect, local_object, lcg_state, max_hits); -# endif /* __KERNEL_OPTIX__ */ -} -#endif +# if defined(__HAIR__) && defined(__OBJECT_MOTION__) +# define BVH_FUNCTION_NAME bvh_intersect_shadow_all_hair_motion +# define BVH_FUNCTION_FEATURES BVH_HAIR | BVH_MOTION | BVH_POINTCLOUD +# include "kernel/bvh/shadow_all.h" +# endif -#ifdef __SHADOW_RECORD_ALL__ ccl_device_intersect bool scene_intersect_shadow_all(KernelGlobals kg, IntegratorShadowState state, ccl_private const Ray *ray, @@ -521,132 +164,12 @@ ccl_device_intersect bool scene_intersect_shadow_all(KernelGlobals kg, ccl_private uint *num_recorded_hits, ccl_private float *throughput) { -# ifdef __KERNEL_OPTIX__ - uint p0 = state; - uint p1 = __float_as_uint(1.0f); /* Throughput. */ - uint p2 = 0; /* Number of hits. */ - uint p3 = max_hits; - uint p4 = visibility; - uint p5 = false; - uint p6 = ((uint64_t)ray) & 0xFFFFFFFF; - uint p7 = (((uint64_t)ray) >> 32) & 0xFFFFFFFF; - - uint ray_mask = visibility & 0xFF; - if (0 == ray_mask && (visibility & ~0xFF) != 0) { - ray_mask = 0xFF; - } - - optixTrace(scene_intersect_valid(ray) ? kernel_data.device_bvh : 0, - ray->P, - ray->D, - ray->tmin, - ray->tmax, - ray->time, - ray_mask, - /* Need to always call into __anyhit__kernel_optix_shadow_all_hit. */ - OPTIX_RAY_FLAG_ENFORCE_ANYHIT, - 1, /* SBT offset for PG_HITS */ - 0, - 0, - p0, - p1, - p2, - p3, - p4, - p5, - p6, - p7); - - *num_recorded_hits = uint16_unpack_from_uint_0(p2); - *throughput = __uint_as_float(p1); - - return p5; -# elif defined(__METALRT__) - - if (!scene_intersect_valid(ray)) { - return false; - } - -# if defined(__KERNEL_DEBUG__) - if (is_null_instance_acceleration_structure(metal_ancillaries->accel_struct)) { - kernel_assert(!"Invalid metal_ancillaries->accel_struct pointer"); - return false; - } - - if (is_null_intersection_function_table(metal_ancillaries->ift_shadow)) { - kernel_assert(!"Invalid ift_shadow"); - return false; - } -# endif - - metal::raytracing::ray r(ray->P, ray->D, ray->tmin, ray->tmax); - metalrt_intersector_type metalrt_intersect; - - metalrt_intersect.force_opacity(metal::raytracing::forced_opacity::non_opaque); - if (!kernel_data.bvh.have_curves) { - metalrt_intersect.assume_geometry_type(metal::raytracing::geometry_type::triangle); - } - - MetalRTIntersectionShadowPayload payload; - payload.self = ray->self; - payload.visibility = visibility; - payload.max_hits = max_hits; - payload.num_hits = 0; - payload.num_recorded_hits = 0; - payload.throughput = 1.0f; - payload.result = false; - payload.state = state; - - uint ray_mask = visibility & 0xFF; - if (0 == ray_mask && (visibility & ~0xFF) != 0) { - ray_mask = 0xFF; - } - - typename metalrt_intersector_type::result_type intersection; - -# if defined(__METALRT_MOTION__) - payload.time = ray->time; - intersection = metalrt_intersect.intersect(r, - metal_ancillaries->accel_struct, - ray_mask, - ray->time, - metal_ancillaries->ift_shadow, - payload); -# else - intersection = metalrt_intersect.intersect( - r, metal_ancillaries->accel_struct, ray_mask, metal_ancillaries->ift_shadow, payload); -# endif - - *num_recorded_hits = payload.num_recorded_hits; - *throughput = payload.throughput; - - return payload.result; - -# else - if (!scene_intersect_valid(ray)) { + if (!intersection_ray_valid(ray)) { *num_recorded_hits = 0; *throughput = 1.0f; return false; } -# ifdef __EMBREE__ - if (kernel_data.device_bvh) { - CCLIntersectContext ctx(kg, CCLIntersectContext::RAY_SHADOW_ALL); - Intersection *isect_array = (Intersection *)state->shadow_isect; - ctx.isect_s = isect_array; - ctx.max_hits = max_hits; - ctx.ray = ray; - IntersectContext rtc_ctx(&ctx); - RTCRay rtc_ray; - kernel_embree_setup_ray(*ray, rtc_ray, visibility); - rtcOccluded1(kernel_data.device_bvh, &rtc_ctx.context, &rtc_ray); - - *num_recorded_hits = ctx.num_recorded_hits; - *throughput = ctx.throughput; - return ctx.opaque_hit; - } -# endif /* __EMBREE__ */ - # ifdef __OBJECT_MOTION__ if (kernel_data.bvh.have_motion) { # ifdef __HAIR__ @@ -659,7 +182,7 @@ ccl_device_intersect bool scene_intersect_shadow_all(KernelGlobals kg, return bvh_intersect_shadow_all_motion( kg, ray, state, visibility, max_hits, num_recorded_hits, throughput); } -# endif /* __OBJECT_MOTION__ */ +# endif /* __OBJECT_MOTION__ */ # ifdef __HAIR__ if (kernel_data.bvh.have_curves) { @@ -670,180 +193,83 @@ ccl_device_intersect bool scene_intersect_shadow_all(KernelGlobals kg, return bvh_intersect_shadow_all( kg, ray, state, visibility, max_hits, num_recorded_hits, throughput); -# endif /* __KERNEL_OPTIX__ */ } -#endif /* __SHADOW_RECORD_ALL__ */ +# endif /* __SHADOW_RECORD_ALL__ */ + +/* Volume BVH traversal, for initializing or updating the volume stack. */ + +# if defined(__VOLUME__) && !defined(__VOLUME_RECORD_ALL__) + +# define BVH_FUNCTION_NAME bvh_intersect_volume +# define BVH_FUNCTION_FEATURES BVH_HAIR +# include "kernel/bvh/volume.h" + +# if defined(__OBJECT_MOTION__) +# define BVH_FUNCTION_NAME bvh_intersect_volume_motion +# define BVH_FUNCTION_FEATURES BVH_MOTION | BVH_HAIR +# include "kernel/bvh/volume.h" +# endif -#ifdef __VOLUME__ ccl_device_intersect bool scene_intersect_volume(KernelGlobals kg, ccl_private const Ray *ray, ccl_private Intersection *isect, const uint visibility) { -# ifdef __KERNEL_OPTIX__ - uint p0 = 0; - uint p1 = 0; - uint p2 = 0; - uint p3 = 0; - uint p4 = visibility; - uint p5 = PRIMITIVE_NONE; - uint p6 = ((uint64_t)ray) & 0xFFFFFFFF; - uint p7 = (((uint64_t)ray) >> 32) & 0xFFFFFFFF; - - uint ray_mask = visibility & 0xFF; - if (0 == ray_mask && (visibility & ~0xFF) != 0) { - ray_mask = 0xFF; - } - - optixTrace(scene_intersect_valid(ray) ? kernel_data.device_bvh : 0, - ray->P, - ray->D, - ray->tmin, - ray->tmax, - ray->time, - ray_mask, - /* Need to always call into __anyhit__kernel_optix_volume_test. */ - OPTIX_RAY_FLAG_ENFORCE_ANYHIT, - 3, /* SBT offset for PG_HITV */ - 0, - 0, - p0, - p1, - p2, - p3, - p4, - p5, - p6, - p7); - - isect->t = __uint_as_float(p0); - isect->u = __uint_as_float(p1); - isect->v = __uint_as_float(p2); - isect->prim = p3; - isect->object = p4; - isect->type = p5; - - return p5 != PRIMITIVE_NONE; -# elif defined(__METALRT__) - - if (!scene_intersect_valid(ray)) { - return false; - } -# if defined(__KERNEL_DEBUG__) - if (is_null_instance_acceleration_structure(metal_ancillaries->accel_struct)) { - kernel_assert(!"Invalid metal_ancillaries->accel_struct pointer"); + if (!intersection_ray_valid(ray)) { return false; } - if (is_null_intersection_function_table(metal_ancillaries->ift_default)) { - kernel_assert(!"Invalid ift_default"); - return false; +# ifdef __OBJECT_MOTION__ + if (kernel_data.bvh.have_motion) { + return bvh_intersect_volume_motion(kg, ray, isect, visibility); } -# endif - - metal::raytracing::ray r(ray->P, ray->D, ray->tmin, ray->tmax); - metalrt_intersector_type metalrt_intersect; +# endif /* __OBJECT_MOTION__ */ - metalrt_intersect.force_opacity(metal::raytracing::forced_opacity::non_opaque); - if (!kernel_data.bvh.have_curves) { - metalrt_intersect.assume_geometry_type(metal::raytracing::geometry_type::triangle); - } + return bvh_intersect_volume(kg, ray, isect, visibility); +} +# endif /* defined(__VOLUME__) && !defined(__VOLUME_RECORD_ALL__) */ - MetalRTIntersectionPayload payload; - payload.self = ray->self; - payload.visibility = visibility; +/* Volume BVH traversal, for initializing or updating the volume stack. + * Variation that records multiple intersections at once. */ - typename metalrt_intersector_type::result_type intersection; +# if defined(__VOLUME__) && defined(__VOLUME_RECORD_ALL__) - uint ray_mask = visibility & 0xFF; - if (0 == ray_mask && (visibility & ~0xFF) != 0) { - ray_mask = 0xFF; - } +# define BVH_FUNCTION_NAME bvh_intersect_volume_all +# define BVH_FUNCTION_FEATURES BVH_HAIR +# include "kernel/bvh/volume_all.h" -# if defined(__METALRT_MOTION__) - payload.time = ray->time; - intersection = metalrt_intersect.intersect(r, - metal_ancillaries->accel_struct, - ray_mask, - ray->time, - metal_ancillaries->ift_default, - payload); -# else - intersection = metalrt_intersect.intersect( - r, metal_ancillaries->accel_struct, ray_mask, metal_ancillaries->ift_default, payload); +# if defined(__OBJECT_MOTION__) +# define BVH_FUNCTION_NAME bvh_intersect_volume_all_motion +# define BVH_FUNCTION_FEATURES BVH_MOTION | BVH_HAIR +# include "kernel/bvh/volume_all.h" # endif - if (intersection.type == intersection_type::none) { - return false; - } - - isect->prim = payload.prim; - isect->type = payload.type; - isect->object = intersection.user_instance_id; - - isect->t = intersection.distance; - if (intersection.type == intersection_type::triangle) { - isect->u = 1.0f - intersection.triangle_barycentric_coord.y - - intersection.triangle_barycentric_coord.x; - isect->v = intersection.triangle_barycentric_coord.x; - } - else { - isect->u = payload.u; - isect->v = payload.v; - } - - return isect->type != PRIMITIVE_NONE; - -# else - if (!scene_intersect_valid(ray)) { +ccl_device_intersect uint scene_intersect_volume(KernelGlobals kg, + ccl_private const Ray *ray, + ccl_private Intersection *isect, + const uint max_hits, + const uint visibility) +{ + if (!intersection_ray_valid(ray)) { return false; } # ifdef __OBJECT_MOTION__ if (kernel_data.bvh.have_motion) { - return bvh_intersect_volume_motion(kg, ray, isect, visibility); + return bvh_intersect_volume_all_motion(kg, ray, isect, max_hits, visibility); } # endif /* __OBJECT_MOTION__ */ - return bvh_intersect_volume(kg, ray, isect, visibility); -# endif /* __KERNEL_OPTIX__ */ + return bvh_intersect_volume_all(kg, ray, isect, max_hits, visibility); } -#endif /* __VOLUME__ */ -#ifdef __VOLUME_RECORD_ALL__ -ccl_device_intersect uint scene_intersect_volume_all(KernelGlobals kg, - ccl_private const Ray *ray, - ccl_private Intersection *isect, - const uint max_hits, - const uint visibility) -{ - if (!scene_intersect_valid(ray)) { - return false; - } +# endif /* defined(__VOLUME__) && defined(__VOLUME_RECORD_ALL__) */ -# ifdef __EMBREE__ - if (kernel_data.device_bvh) { - CCLIntersectContext ctx(kg, CCLIntersectContext::RAY_VOLUME_ALL); - ctx.isect_s = isect; - ctx.max_hits = max_hits; - ctx.num_hits = 0; - ctx.ray = ray; - IntersectContext rtc_ctx(&ctx); - RTCRay rtc_ray; - kernel_embree_setup_ray(*ray, rtc_ray, visibility); - rtcOccluded1(kernel_data.device_bvh, &rtc_ctx.context, &rtc_ray); - return ctx.num_hits; - } -# endif /* __EMBREE__ */ - -# ifdef __OBJECT_MOTION__ - if (kernel_data.bvh.have_motion) { - return bvh_intersect_volume_all_motion(kg, ray, isect, max_hits, visibility); - } -# endif /* __OBJECT_MOTION__ */ +# undef BVH_FEATURE +# undef BVH_NAME_JOIN +# undef BVH_NAME_EVAL +# undef BVH_FUNCTION_FULL_NAME - return bvh_intersect_volume_all(kg, ray, isect, max_hits, visibility); -} -#endif /* __VOLUME_RECORD_ALL__ */ +#endif /* __BVH2__ */ CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/bvh/embree.h b/intern/cycles/kernel/bvh/embree.h deleted file mode 100644 index fecbccac2f8..00000000000 --- a/intern/cycles/kernel/bvh/embree.h +++ /dev/null @@ -1,176 +0,0 @@ -/* SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2022 Blender Foundation. */ - -#pragma once - -#include -#include - -#include "kernel/device/cpu/compat.h" -#include "kernel/device/cpu/globals.h" - -#include "kernel/bvh/util.h" - -#include "util/vector.h" - -CCL_NAMESPACE_BEGIN - -struct CCLIntersectContext { - typedef enum { - RAY_REGULAR = 0, - RAY_SHADOW_ALL = 1, - RAY_LOCAL = 2, - RAY_SSS = 3, - RAY_VOLUME_ALL = 4, - } RayType; - - KernelGlobals kg; - RayType type; - - /* For avoiding self intersections */ - const Ray *ray; - - /* for shadow rays */ - Intersection *isect_s; - uint max_hits; - uint num_hits; - uint num_recorded_hits; - float throughput; - float max_t; - bool opaque_hit; - - /* for SSS Rays: */ - LocalIntersection *local_isect; - int local_object_id; - uint *lcg_state; - - CCLIntersectContext(KernelGlobals kg_, RayType type_) - { - kg = kg_; - type = type_; - ray = NULL; - max_hits = 1; - num_hits = 0; - num_recorded_hits = 0; - throughput = 1.0f; - max_t = FLT_MAX; - opaque_hit = false; - isect_s = NULL; - local_isect = NULL; - local_object_id = -1; - lcg_state = NULL; - } -}; - -class IntersectContext { - public: - IntersectContext(CCLIntersectContext *ctx) - { - rtcInitIntersectContext(&context); - userRayExt = ctx; - } - RTCIntersectContext context; - CCLIntersectContext *userRayExt; -}; - -ccl_device_inline void kernel_embree_setup_ray(const Ray &ray, - RTCRay &rtc_ray, - const uint visibility) -{ - rtc_ray.org_x = ray.P.x; - rtc_ray.org_y = ray.P.y; - rtc_ray.org_z = ray.P.z; - rtc_ray.dir_x = ray.D.x; - rtc_ray.dir_y = ray.D.y; - rtc_ray.dir_z = ray.D.z; - rtc_ray.tnear = ray.tmin; - rtc_ray.tfar = ray.tmax; - rtc_ray.time = ray.time; - rtc_ray.mask = visibility; -} - -ccl_device_inline void kernel_embree_setup_rayhit(const Ray &ray, - RTCRayHit &rayhit, - const uint visibility) -{ - kernel_embree_setup_ray(ray, rayhit.ray, visibility); - rayhit.hit.geomID = RTC_INVALID_GEOMETRY_ID; - rayhit.hit.instID[0] = RTC_INVALID_GEOMETRY_ID; -} - -ccl_device_inline bool kernel_embree_is_self_intersection(const KernelGlobals kg, - const RTCHit *hit, - const Ray *ray) -{ - bool status = false; - if (hit->instID[0] != RTC_INVALID_GEOMETRY_ID) { - const int oID = hit->instID[0] / 2; - if ((ray->self.object == oID) || (ray->self.light_object == oID)) { - RTCScene inst_scene = (RTCScene)rtcGetGeometryUserData( - rtcGetGeometry(kernel_data.device_bvh, hit->instID[0])); - const int pID = hit->primID + - (intptr_t)rtcGetGeometryUserData(rtcGetGeometry(inst_scene, hit->geomID)); - status = intersection_skip_self_shadow(ray->self, oID, pID); - } - } - else { - const int oID = hit->geomID / 2; - if ((ray->self.object == oID) || (ray->self.light_object == oID)) { - const int pID = hit->primID + (intptr_t)rtcGetGeometryUserData( - rtcGetGeometry(kernel_data.device_bvh, hit->geomID)); - status = intersection_skip_self_shadow(ray->self, oID, pID); - } - } - - return status; -} - -ccl_device_inline void kernel_embree_convert_hit(KernelGlobals kg, - const RTCRay *ray, - const RTCHit *hit, - Intersection *isect) -{ - isect->t = ray->tfar; - if (hit->instID[0] != RTC_INVALID_GEOMETRY_ID) { - RTCScene inst_scene = (RTCScene)rtcGetGeometryUserData( - rtcGetGeometry(kernel_data.device_bvh, hit->instID[0])); - isect->prim = hit->primID + - (intptr_t)rtcGetGeometryUserData(rtcGetGeometry(inst_scene, hit->geomID)); - isect->object = hit->instID[0] / 2; - } - else { - isect->prim = hit->primID + (intptr_t)rtcGetGeometryUserData( - rtcGetGeometry(kernel_data.device_bvh, hit->geomID)); - isect->object = hit->geomID / 2; - } - - const bool is_hair = hit->geomID & 1; - if (is_hair) { - const KernelCurveSegment segment = kernel_data_fetch(curve_segments, isect->prim); - isect->type = segment.type; - isect->prim = segment.prim; - isect->u = hit->u; - isect->v = hit->v; - } - else { - isect->type = kernel_data_fetch(objects, isect->object).primitive_type; - isect->u = 1.0f - hit->v - hit->u; - isect->v = hit->u; - } -} - -ccl_device_inline void kernel_embree_convert_sss_hit( - KernelGlobals kg, const RTCRay *ray, const RTCHit *hit, Intersection *isect, int object) -{ - isect->u = 1.0f - hit->v - hit->u; - isect->v = hit->u; - isect->t = ray->tfar; - RTCScene inst_scene = (RTCScene)rtcGetGeometryUserData( - rtcGetGeometry(kernel_data.device_bvh, object * 2)); - isect->prim = hit->primID + - (intptr_t)rtcGetGeometryUserData(rtcGetGeometry(inst_scene, hit->geomID)); - isect->object = object; - isect->type = kernel_data_fetch(objects, object).primitive_type; -} - -CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/bvh/metal.h b/intern/cycles/kernel/bvh/metal.h deleted file mode 100644 index 04289e259a7..00000000000 --- a/intern/cycles/kernel/bvh/metal.h +++ /dev/null @@ -1,37 +0,0 @@ -/* SPDX-License-Identifier: Apache-2.0 - * Copyright 2021-2022 Blender Foundation */ - -struct MetalRTIntersectionPayload { - RaySelfPrimitives self; - uint visibility; - float u, v; - int prim; - int type; -#if defined(__METALRT_MOTION__) - float time; -#endif -}; - -struct MetalRTIntersectionLocalPayload { - RaySelfPrimitives self; - uint local_object; - uint lcg_state; - short max_hits; - bool has_lcg_state; - bool result; - LocalIntersection local_isect; -}; - -struct MetalRTIntersectionShadowPayload { - RaySelfPrimitives self; - uint visibility; -#if defined(__METALRT_MOTION__) - float time; -#endif - int state; - float throughput; - short max_hits; - short num_hits; - short num_recorded_hits; - bool result; -}; diff --git a/intern/cycles/kernel/bvh/util.h b/intern/cycles/kernel/bvh/util.h index 385e904d20f..02e927decd4 100644 --- a/intern/cycles/kernel/bvh/util.h +++ b/intern/cycles/kernel/bvh/util.h @@ -5,6 +5,21 @@ CCL_NAMESPACE_BEGIN +ccl_device_inline bool intersection_ray_valid(ccl_private const Ray *ray) +{ + /* NOTE: Due to some vectorization code non-finite origin point might + * cause lots of false-positive intersections which will overflow traversal + * stack. + * This code is a quick way to perform early output, to avoid crashes in + * such cases. + * From production scenes so far it seems it's enough to test first element + * only. + * Scene intersection may also called with empty rays for conditional trace + * calls that evaluate to false, so filter those out. + */ + return isfinite_safe(ray->P.x) && isfinite_safe(ray->D.x) && len_squared(ray->D) != 0.0f; +} + /* Offset intersection distance by the smallest possible amount, to skip * intersections at this distance. This works in cases where the ray start * position is unchanged and only tmin is updated, since for self diff --git a/intern/cycles/kernel/device/cpu/bvh.h b/intern/cycles/kernel/device/cpu/bvh.h new file mode 100644 index 00000000000..b5ea3d831f4 --- /dev/null +++ b/intern/cycles/kernel/device/cpu/bvh.h @@ -0,0 +1,609 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright 2021-2022 Blender Foundation */ + +/* CPU Embree implementation of ray-scene intersection. */ + +#pragma once + +#include +#include + +#include "kernel/device/cpu/compat.h" +#include "kernel/device/cpu/globals.h" + +#include "kernel/bvh/types.h" +#include "kernel/bvh/util.h" +#include "kernel/geom/object.h" +#include "kernel/integrator/state.h" +#include "kernel/sample/lcg.h" + +#include "util/vector.h" + +CCL_NAMESPACE_BEGIN + +#define EMBREE_IS_HAIR(x) (x & 1) + +/* Intersection context. */ + +struct CCLIntersectContext { + typedef enum { + RAY_REGULAR = 0, + RAY_SHADOW_ALL = 1, + RAY_LOCAL = 2, + RAY_SSS = 3, + RAY_VOLUME_ALL = 4, + } RayType; + + KernelGlobals kg; + RayType type; + + /* For avoiding self intersections */ + const Ray *ray; + + /* for shadow rays */ + Intersection *isect_s; + uint max_hits; + uint num_hits; + uint num_recorded_hits; + float throughput; + float max_t; + bool opaque_hit; + + /* for SSS Rays: */ + LocalIntersection *local_isect; + int local_object_id; + uint *lcg_state; + + CCLIntersectContext(KernelGlobals kg_, RayType type_) + { + kg = kg_; + type = type_; + ray = NULL; + max_hits = 1; + num_hits = 0; + num_recorded_hits = 0; + throughput = 1.0f; + max_t = FLT_MAX; + opaque_hit = false; + isect_s = NULL; + local_isect = NULL; + local_object_id = -1; + lcg_state = NULL; + } +}; + +class IntersectContext { + public: + IntersectContext(CCLIntersectContext *ctx) + { + rtcInitIntersectContext(&context); + userRayExt = ctx; + } + RTCIntersectContext context; + CCLIntersectContext *userRayExt; +}; + +/* Utilities. */ + +ccl_device_inline void kernel_embree_setup_ray(const Ray &ray, + RTCRay &rtc_ray, + const uint visibility) +{ + rtc_ray.org_x = ray.P.x; + rtc_ray.org_y = ray.P.y; + rtc_ray.org_z = ray.P.z; + rtc_ray.dir_x = ray.D.x; + rtc_ray.dir_y = ray.D.y; + rtc_ray.dir_z = ray.D.z; + rtc_ray.tnear = ray.tmin; + rtc_ray.tfar = ray.tmax; + rtc_ray.time = ray.time; + rtc_ray.mask = visibility; +} + +ccl_device_inline void kernel_embree_setup_rayhit(const Ray &ray, + RTCRayHit &rayhit, + const uint visibility) +{ + kernel_embree_setup_ray(ray, rayhit.ray, visibility); + rayhit.hit.geomID = RTC_INVALID_GEOMETRY_ID; + rayhit.hit.instID[0] = RTC_INVALID_GEOMETRY_ID; +} + +ccl_device_inline bool kernel_embree_is_self_intersection(const KernelGlobals kg, + const RTCHit *hit, + const Ray *ray) +{ + bool status = false; + if (hit->instID[0] != RTC_INVALID_GEOMETRY_ID) { + const int oID = hit->instID[0] / 2; + if ((ray->self.object == oID) || (ray->self.light_object == oID)) { + RTCScene inst_scene = (RTCScene)rtcGetGeometryUserData( + rtcGetGeometry(kernel_data.device_bvh, hit->instID[0])); + const int pID = hit->primID + + (intptr_t)rtcGetGeometryUserData(rtcGetGeometry(inst_scene, hit->geomID)); + status = intersection_skip_self_shadow(ray->self, oID, pID); + } + } + else { + const int oID = hit->geomID / 2; + if ((ray->self.object == oID) || (ray->self.light_object == oID)) { + const int pID = hit->primID + (intptr_t)rtcGetGeometryUserData( + rtcGetGeometry(kernel_data.device_bvh, hit->geomID)); + status = intersection_skip_self_shadow(ray->self, oID, pID); + } + } + + return status; +} + +ccl_device_inline void kernel_embree_convert_hit(KernelGlobals kg, + const RTCRay *ray, + const RTCHit *hit, + Intersection *isect) +{ + isect->t = ray->tfar; + if (hit->instID[0] != RTC_INVALID_GEOMETRY_ID) { + RTCScene inst_scene = (RTCScene)rtcGetGeometryUserData( + rtcGetGeometry(kernel_data.device_bvh, hit->instID[0])); + isect->prim = hit->primID + + (intptr_t)rtcGetGeometryUserData(rtcGetGeometry(inst_scene, hit->geomID)); + isect->object = hit->instID[0] / 2; + } + else { + isect->prim = hit->primID + (intptr_t)rtcGetGeometryUserData( + rtcGetGeometry(kernel_data.device_bvh, hit->geomID)); + isect->object = hit->geomID / 2; + } + + const bool is_hair = hit->geomID & 1; + if (is_hair) { + const KernelCurveSegment segment = kernel_data_fetch(curve_segments, isect->prim); + isect->type = segment.type; + isect->prim = segment.prim; + isect->u = hit->u; + isect->v = hit->v; + } + else { + isect->type = kernel_data_fetch(objects, isect->object).primitive_type; + isect->u = 1.0f - hit->v - hit->u; + isect->v = hit->u; + } +} + +ccl_device_inline void kernel_embree_convert_sss_hit( + KernelGlobals kg, const RTCRay *ray, const RTCHit *hit, Intersection *isect, int object) +{ + isect->u = 1.0f - hit->v - hit->u; + isect->v = hit->u; + isect->t = ray->tfar; + RTCScene inst_scene = (RTCScene)rtcGetGeometryUserData( + rtcGetGeometry(kernel_data.device_bvh, object * 2)); + isect->prim = hit->primID + + (intptr_t)rtcGetGeometryUserData(rtcGetGeometry(inst_scene, hit->geomID)); + isect->object = object; + isect->type = kernel_data_fetch(objects, object).primitive_type; +} + +/* Ray filter functions. */ + +/* This gets called by Embree at every valid ray/object intersection. + * Things like recording subsurface or shadow hits for later evaluation + * as well as filtering for volume objects happen here. + * Cycles' own BVH does that directly inside the traversal calls. */ +ccl_device void kernel_embree_filter_intersection_func(const RTCFilterFunctionNArguments *args) +{ + /* Current implementation in Cycles assumes only single-ray intersection queries. */ + assert(args->N == 1); + + RTCHit *hit = (RTCHit *)args->hit; + CCLIntersectContext *ctx = ((IntersectContext *)args->context)->userRayExt; + const KernelGlobalsCPU *kg = ctx->kg; + const Ray *cray = ctx->ray; + + if (kernel_embree_is_self_intersection(kg, hit, cray)) { + *args->valid = 0; + } +} + +/* This gets called by Embree at every valid ray/object intersection. + * Things like recording subsurface or shadow hits for later evaluation + * as well as filtering for volume objects happen here. + * Cycles' own BVH does that directly inside the traversal calls. + */ +ccl_device void kernel_embree_filter_occluded_func(const RTCFilterFunctionNArguments *args) +{ + /* Current implementation in Cycles assumes only single-ray intersection queries. */ + assert(args->N == 1); + + const RTCRay *ray = (RTCRay *)args->ray; + RTCHit *hit = (RTCHit *)args->hit; + CCLIntersectContext *ctx = ((IntersectContext *)args->context)->userRayExt; + const KernelGlobalsCPU *kg = ctx->kg; + const Ray *cray = ctx->ray; + + switch (ctx->type) { + case CCLIntersectContext::RAY_SHADOW_ALL: { + Intersection current_isect; + kernel_embree_convert_hit(kg, ray, hit, ¤t_isect); + if (intersection_skip_self_shadow(cray->self, current_isect.object, current_isect.prim)) { + *args->valid = 0; + return; + } + /* If no transparent shadows or max number of hits exceeded, all light is blocked. */ + const int flags = intersection_get_shader_flags(kg, current_isect.prim, current_isect.type); + if (!(flags & (SD_HAS_TRANSPARENT_SHADOW)) || ctx->num_hits >= ctx->max_hits) { + ctx->opaque_hit = true; + return; + } + + ++ctx->num_hits; + + /* Always use baked shadow transparency for curves. */ + if (current_isect.type & PRIMITIVE_CURVE) { + ctx->throughput *= intersection_curve_shadow_transparency( + kg, current_isect.object, current_isect.prim, current_isect.u); + + if (ctx->throughput < CURVE_SHADOW_TRANSPARENCY_CUTOFF) { + ctx->opaque_hit = true; + return; + } + else { + *args->valid = 0; + return; + } + } + + /* Test if we need to record this transparent intersection. */ + const uint max_record_hits = min(ctx->max_hits, INTEGRATOR_SHADOW_ISECT_SIZE); + if (ctx->num_recorded_hits < max_record_hits || ray->tfar < ctx->max_t) { + /* If maximum number of hits was reached, replace the intersection with the + * highest distance. We want to find the N closest intersections. */ + const uint num_recorded_hits = min(ctx->num_recorded_hits, max_record_hits); + uint isect_index = num_recorded_hits; + if (num_recorded_hits + 1 >= max_record_hits) { + float max_t = ctx->isect_s[0].t; + uint max_recorded_hit = 0; + + for (uint i = 1; i < num_recorded_hits; ++i) { + if (ctx->isect_s[i].t > max_t) { + max_recorded_hit = i; + max_t = ctx->isect_s[i].t; + } + } + + if (num_recorded_hits >= max_record_hits) { + isect_index = max_recorded_hit; + } + + /* Limit the ray distance and stop counting hits beyond this. + * TODO: is there some way we can tell Embree to stop intersecting beyond + * this distance when max number of hits is reached?. Or maybe it will + * become irrelevant if we make max_hits a very high number on the CPU. */ + ctx->max_t = max(current_isect.t, max_t); + } + + ctx->isect_s[isect_index] = current_isect; + } + + /* Always increase the number of recorded hits, even beyond the maximum, + * so that we can detect this and trace another ray if needed. */ + ++ctx->num_recorded_hits; + + /* This tells Embree to continue tracing. */ + *args->valid = 0; + break; + } + case CCLIntersectContext::RAY_LOCAL: + case CCLIntersectContext::RAY_SSS: { + /* Check if it's hitting the correct object. */ + Intersection current_isect; + if (ctx->type == CCLIntersectContext::RAY_SSS) { + kernel_embree_convert_sss_hit(kg, ray, hit, ¤t_isect, ctx->local_object_id); + } + else { + kernel_embree_convert_hit(kg, ray, hit, ¤t_isect); + if (ctx->local_object_id != current_isect.object) { + /* This tells Embree to continue tracing. */ + *args->valid = 0; + break; + } + } + if (intersection_skip_self_local(cray->self, current_isect.prim)) { + *args->valid = 0; + return; + } + + /* No intersection information requested, just return a hit. */ + if (ctx->max_hits == 0) { + break; + } + + /* Ignore curves. */ + if (EMBREE_IS_HAIR(hit->geomID)) { + /* This tells Embree to continue tracing. */ + *args->valid = 0; + break; + } + + LocalIntersection *local_isect = ctx->local_isect; + int hit_idx = 0; + + if (ctx->lcg_state) { + /* See triangle_intersect_subsurface() for the native equivalent. */ + for (int i = min((int)ctx->max_hits, local_isect->num_hits) - 1; i >= 0; --i) { + if (local_isect->hits[i].t == ray->tfar) { + /* This tells Embree to continue tracing. */ + *args->valid = 0; + return; + } + } + + local_isect->num_hits++; + + if (local_isect->num_hits <= ctx->max_hits) { + hit_idx = local_isect->num_hits - 1; + } + else { + /* reservoir sampling: if we are at the maximum number of + * hits, randomly replace element or skip it */ + hit_idx = lcg_step_uint(ctx->lcg_state) % local_isect->num_hits; + + if (hit_idx >= ctx->max_hits) { + /* This tells Embree to continue tracing. */ + *args->valid = 0; + return; + } + } + } + else { + /* Record closest intersection only. */ + if (local_isect->num_hits && current_isect.t > local_isect->hits[0].t) { + *args->valid = 0; + return; + } + + local_isect->num_hits = 1; + } + + /* record intersection */ + local_isect->hits[hit_idx] = current_isect; + local_isect->Ng[hit_idx] = normalize(make_float3(hit->Ng_x, hit->Ng_y, hit->Ng_z)); + /* This tells Embree to continue tracing. */ + *args->valid = 0; + break; + } + case CCLIntersectContext::RAY_VOLUME_ALL: { + /* Append the intersection to the end of the array. */ + if (ctx->num_hits < ctx->max_hits) { + Intersection current_isect; + kernel_embree_convert_hit(kg, ray, hit, ¤t_isect); + if (intersection_skip_self(cray->self, current_isect.object, current_isect.prim)) { + *args->valid = 0; + return; + } + + Intersection *isect = &ctx->isect_s[ctx->num_hits]; + ++ctx->num_hits; + *isect = current_isect; + /* Only primitives from volume object. */ + uint tri_object = isect->object; + int object_flag = kernel_data_fetch(object_flag, tri_object); + if ((object_flag & SD_OBJECT_HAS_VOLUME) == 0) { + --ctx->num_hits; + } + /* This tells Embree to continue tracing. */ + *args->valid = 0; + } + break; + } + case CCLIntersectContext::RAY_REGULAR: + default: + if (kernel_embree_is_self_intersection(kg, hit, cray)) { + *args->valid = 0; + return; + } + break; + } +} + +ccl_device void kernel_embree_filter_func_backface_cull(const RTCFilterFunctionNArguments *args) +{ + const RTCRay *ray = (RTCRay *)args->ray; + RTCHit *hit = (RTCHit *)args->hit; + + /* Always ignore back-facing intersections. */ + if (dot(make_float3(ray->dir_x, ray->dir_y, ray->dir_z), + make_float3(hit->Ng_x, hit->Ng_y, hit->Ng_z)) > 0.0f) { + *args->valid = 0; + return; + } + + CCLIntersectContext *ctx = ((IntersectContext *)args->context)->userRayExt; + const KernelGlobalsCPU *kg = ctx->kg; + const Ray *cray = ctx->ray; + + if (kernel_embree_is_self_intersection(kg, hit, cray)) { + *args->valid = 0; + } +} + +ccl_device void kernel_embree_filter_occluded_func_backface_cull( + const RTCFilterFunctionNArguments *args) +{ + const RTCRay *ray = (RTCRay *)args->ray; + RTCHit *hit = (RTCHit *)args->hit; + + /* Always ignore back-facing intersections. */ + if (dot(make_float3(ray->dir_x, ray->dir_y, ray->dir_z), + make_float3(hit->Ng_x, hit->Ng_y, hit->Ng_z)) > 0.0f) { + *args->valid = 0; + return; + } + + kernel_embree_filter_occluded_func(args); +} + +/* Scene intersection. */ + +ccl_device_intersect bool scene_intersect(KernelGlobals kg, + ccl_private const Ray *ray, + const uint visibility, + ccl_private Intersection *isect) +{ + if (!intersection_ray_valid(ray)) { + return false; + } + + if (!kernel_data.device_bvh) { + return false; + } + + isect->t = ray->tmax; + CCLIntersectContext ctx(kg, CCLIntersectContext::RAY_REGULAR); + IntersectContext rtc_ctx(&ctx); + RTCRayHit ray_hit; + ctx.ray = ray; + kernel_embree_setup_rayhit(*ray, ray_hit, visibility); + rtcIntersect1(kernel_data.device_bvh, &rtc_ctx.context, &ray_hit); + if (ray_hit.hit.geomID == RTC_INVALID_GEOMETRY_ID || + ray_hit.hit.primID == RTC_INVALID_GEOMETRY_ID) { + return false; + } + + kernel_embree_convert_hit(kg, &ray_hit.ray, &ray_hit.hit, isect); + return true; +} + +#ifdef __BVH_LOCAL__ +ccl_device_intersect bool scene_intersect_local(KernelGlobals kg, + ccl_private const Ray *ray, + ccl_private LocalIntersection *local_isect, + int local_object, + ccl_private uint *lcg_state, + int max_hits) +{ + if (!intersection_ray_valid(ray)) { + if (local_isect) { + local_isect->num_hits = 0; + } + return false; + } + + if (!kernel_data.device_bvh) { + return false; + } + + const bool has_bvh = !(kernel_data_fetch(object_flag, local_object) & + SD_OBJECT_TRANSFORM_APPLIED); + CCLIntersectContext ctx(kg, + has_bvh ? CCLIntersectContext::RAY_SSS : CCLIntersectContext::RAY_LOCAL); + ctx.lcg_state = lcg_state; + ctx.max_hits = max_hits; + ctx.ray = ray; + ctx.local_isect = local_isect; + if (local_isect) { + local_isect->num_hits = 0; + } + ctx.local_object_id = local_object; + IntersectContext rtc_ctx(&ctx); + RTCRay rtc_ray; + kernel_embree_setup_ray(*ray, rtc_ray, PATH_RAY_ALL_VISIBILITY); + + /* If this object has its own BVH, use it. */ + if (has_bvh) { + RTCGeometry geom = rtcGetGeometry(kernel_data.device_bvh, local_object * 2); + if (geom) { + float3 P = ray->P; + float3 dir = ray->D; + float3 idir = ray->D; + bvh_instance_motion_push(kg, local_object, ray, &P, &dir, &idir); + + rtc_ray.org_x = P.x; + rtc_ray.org_y = P.y; + rtc_ray.org_z = P.z; + rtc_ray.dir_x = dir.x; + rtc_ray.dir_y = dir.y; + rtc_ray.dir_z = dir.z; + rtc_ray.tnear = ray->tmin; + rtc_ray.tfar = ray->tmax; + RTCScene scene = (RTCScene)rtcGetGeometryUserData(geom); + kernel_assert(scene); + if (scene) { + rtcOccluded1(scene, &rtc_ctx.context, &rtc_ray); + } + } + } + else { + rtcOccluded1(kernel_data.device_bvh, &rtc_ctx.context, &rtc_ray); + } + + /* rtcOccluded1 sets tfar to -inf if a hit was found. */ + return (local_isect && local_isect->num_hits > 0) || (rtc_ray.tfar < 0); +} +#endif + +#ifdef __SHADOW_RECORD_ALL__ +ccl_device_intersect bool scene_intersect_shadow_all(KernelGlobals kg, + IntegratorShadowStateCPU *state, + ccl_private const Ray *ray, + uint visibility, + uint max_hits, + ccl_private uint *num_recorded_hits, + ccl_private float *throughput) +{ + if (!intersection_ray_valid(ray)) { + *num_recorded_hits = 0; + *throughput = 1.0f; + return false; + } + + if (!kernel_data.device_bvh) { + return false; + } + + CCLIntersectContext ctx(kg, CCLIntersectContext::RAY_SHADOW_ALL); + Intersection *isect_array = (Intersection *)state->shadow_isect; + ctx.isect_s = isect_array; + ctx.max_hits = max_hits; + ctx.ray = ray; + IntersectContext rtc_ctx(&ctx); + RTCRay rtc_ray; + kernel_embree_setup_ray(*ray, rtc_ray, visibility); + rtcOccluded1(kernel_data.device_bvh, &rtc_ctx.context, &rtc_ray); + + *num_recorded_hits = ctx.num_recorded_hits; + *throughput = ctx.throughput; + return ctx.opaque_hit; +} +#endif + +#ifdef __VOLUME__ +ccl_device_intersect uint scene_intersect_volume(KernelGlobals kg, + ccl_private const Ray *ray, + ccl_private Intersection *isect, + const uint max_hits, + const uint visibility) +{ + if (!intersection_ray_valid(ray)) { + return false; + } + + if (!kernel_data.device_bvh) { + return false; + } + + CCLIntersectContext ctx(kg, CCLIntersectContext::RAY_VOLUME_ALL); + ctx.isect_s = isect; + ctx.max_hits = max_hits; + ctx.num_hits = 0; + ctx.ray = ray; + IntersectContext rtc_ctx(&ctx); + RTCRay rtc_ray; + kernel_embree_setup_ray(*ray, rtc_ray, visibility); + rtcOccluded1(kernel_data.device_bvh, &rtc_ctx.context, &rtc_ray); + return ctx.num_hits; +} +#endif + +CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/device/metal/bvh.h b/intern/cycles/kernel/device/metal/bvh.h new file mode 100644 index 00000000000..d3a0ab1b519 --- /dev/null +++ b/intern/cycles/kernel/device/metal/bvh.h @@ -0,0 +1,1123 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright 2021-2022 Blender Foundation */ + +/* MetalRT implementation of ray-scene intersection. */ + +#pragma once + +#include "kernel/bvh/types.h" +#include "kernel/bvh/util.h" + +CCL_NAMESPACE_BEGIN + +/* Payload types. */ + +struct MetalRTIntersectionPayload { + RaySelfPrimitives self; + uint visibility; + float u, v; + int prim; + int type; +#if defined(__METALRT_MOTION__) + float time; +#endif +}; + +struct MetalRTIntersectionLocalPayload { + RaySelfPrimitives self; + uint local_object; + uint lcg_state; + short max_hits; + bool has_lcg_state; + bool result; + LocalIntersection local_isect; +}; + +struct MetalRTIntersectionShadowPayload { + RaySelfPrimitives self; + uint visibility; +#if defined(__METALRT_MOTION__) + float time; +#endif + int state; + float throughput; + short max_hits; + short num_hits; + short num_recorded_hits; + bool result; +}; + +/* Intersection return types. */ + +/* For a bounding box intersection function. */ +struct BoundingBoxIntersectionResult { + bool accept [[accept_intersection]]; + bool continue_search [[continue_search]]; + float distance [[distance]]; +}; + +/* For a triangle intersection function. */ +struct TriangleIntersectionResult { + bool accept [[accept_intersection]]; + bool continue_search [[continue_search]]; +}; + +enum { METALRT_HIT_TRIANGLE, METALRT_HIT_BOUNDING_BOX }; + +/* Utilities. */ + +ccl_device_inline bool intersection_skip_self(ray_data const RaySelfPrimitives &self, + const int object, + const int prim) +{ + return (self.prim == prim) && (self.object == object); +} + +ccl_device_inline bool intersection_skip_self_shadow(ray_data const RaySelfPrimitives &self, + const int object, + const int prim) +{ + return ((self.prim == prim) && (self.object == object)) || + ((self.light_prim == prim) && (self.light_object == object)); +} + +ccl_device_inline bool intersection_skip_self_local(ray_data const RaySelfPrimitives &self, + const int prim) +{ + return (self.prim == prim); +} + +/* Hit functions. */ + +template +TReturn metalrt_local_hit(constant KernelParamsMetal &launch_params_metal, + ray_data MetalKernelContext::MetalRTIntersectionLocalPayload &payload, + const uint object, + const uint primitive_id, + const float2 barycentrics, + const float ray_tmax) +{ + TReturn result; + +#ifdef __BVH_LOCAL__ + uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); + + if ((object != payload.local_object) || intersection_skip_self_local(payload.self, prim)) { + /* Only intersect with matching object and skip self-intersecton. */ + result.accept = false; + result.continue_search = true; + return result; + } + + const short max_hits = payload.max_hits; + if (max_hits == 0) { + /* Special case for when no hit information is requested, just report that something was hit */ + payload.result = true; + result.accept = true; + result.continue_search = false; + return result; + } + + int hit = 0; + if (payload.has_lcg_state) { + for (short i = min(max_hits, short(payload.local_isect.num_hits)) - 1; i >= 0; --i) { + if (ray_tmax == payload.local_isect.hits[i].t) { + result.accept = false; + result.continue_search = true; + return result; + } + } + + hit = payload.local_isect.num_hits++; + + if (payload.local_isect.num_hits > max_hits) { + hit = lcg_step_uint(&payload.lcg_state) % payload.local_isect.num_hits; + if (hit >= max_hits) { + result.accept = false; + result.continue_search = true; + return result; + } + } + } + else { + if (payload.local_isect.num_hits && ray_tmax > payload.local_isect.hits[0].t) { + /* Record closest intersection only. Do not terminate ray here, since there is no guarantee + * about distance ordering in any-hit */ + result.accept = false; + result.continue_search = true; + return result; + } + + payload.local_isect.num_hits = 1; + } + + ray_data Intersection *isect = &payload.local_isect.hits[hit]; + isect->t = ray_tmax; + isect->prim = prim; + isect->object = object; + isect->type = kernel_data_fetch(objects, object).primitive_type; + + isect->u = 1.0f - barycentrics.y - barycentrics.x; + isect->v = barycentrics.x; + + /* Record geometric normal */ + const uint tri_vindex = kernel_data_fetch(tri_vindex, isect->prim).w; + const float3 tri_a = float3(kernel_data_fetch(tri_verts, tri_vindex + 0)); + const float3 tri_b = float3(kernel_data_fetch(tri_verts, tri_vindex + 1)); + const float3 tri_c = float3(kernel_data_fetch(tri_verts, tri_vindex + 2)); + payload.local_isect.Ng[hit] = normalize(cross(tri_b - tri_a, tri_c - tri_a)); + + /* Continue tracing (without this the trace call would return after the first hit) */ + result.accept = false; + result.continue_search = true; + return result; +#endif +} + +[[intersection(triangle, triangle_data, METALRT_TAGS)]] TriangleIntersectionResult +__anyhit__cycles_metalrt_local_hit_tri( + constant KernelParamsMetal &launch_params_metal [[buffer(1)]], + ray_data MetalKernelContext::MetalRTIntersectionLocalPayload &payload [[payload]], + uint instance_id [[user_instance_id]], + uint primitive_id [[primitive_id]], + float2 barycentrics [[barycentric_coord]], + float ray_tmax [[distance]]) +{ + return metalrt_local_hit( + launch_params_metal, payload, instance_id, primitive_id, barycentrics, ray_tmax); +} + +[[intersection(bounding_box, triangle_data, METALRT_TAGS)]] BoundingBoxIntersectionResult +__anyhit__cycles_metalrt_local_hit_box(const float ray_tmax [[max_distance]]) +{ + /* unused function */ + BoundingBoxIntersectionResult result; + result.distance = ray_tmax; + result.accept = false; + result.continue_search = false; + return result; +} + +template +bool metalrt_shadow_all_hit(constant KernelParamsMetal &launch_params_metal, + ray_data MetalKernelContext::MetalRTIntersectionShadowPayload &payload, + uint object, + uint prim, + const float2 barycentrics, + const float ray_tmax) +{ +#ifdef __SHADOW_RECORD_ALL__ +# ifdef __VISIBILITY_FLAG__ + const uint visibility = payload.visibility; + if ((kernel_data_fetch(objects, object).visibility & visibility) == 0) { + /* continue search */ + return true; + } +# endif + + if (intersection_skip_self_shadow(payload.self, object, prim)) { + /* continue search */ + return true; + } + + float u = 0.0f, v = 0.0f; + int type = 0; + if (intersection_type == METALRT_HIT_TRIANGLE) { + u = 1.0f - barycentrics.y - barycentrics.x; + v = barycentrics.x; + type = kernel_data_fetch(objects, object).primitive_type; + } +# ifdef __HAIR__ + else { + u = barycentrics.x; + v = barycentrics.y; + + const KernelCurveSegment segment = kernel_data_fetch(curve_segments, prim); + type = segment.type; + prim = segment.prim; + + /* Filter out curve endcaps */ + if (u == 0.0f || u == 1.0f) { + /* continue search */ + return true; + } + } +# endif + +# ifndef __TRANSPARENT_SHADOWS__ + /* No transparent shadows support compiled in, make opaque. */ + payload.result = true; + /* terminate ray */ + return false; +# else + short max_hits = payload.max_hits; + short num_hits = payload.num_hits; + short num_recorded_hits = payload.num_recorded_hits; + + MetalKernelContext context(launch_params_metal); + + /* If no transparent shadows, all light is blocked and we can stop immediately. */ + if (num_hits >= max_hits || + !(context.intersection_get_shader_flags(NULL, prim, type) & SD_HAS_TRANSPARENT_SHADOW)) { + payload.result = true; + /* terminate ray */ + return false; + } + + /* Always use baked shadow transparency for curves. */ + if (type & PRIMITIVE_CURVE) { + float throughput = payload.throughput; + throughput *= context.intersection_curve_shadow_transparency(nullptr, object, prim, u); + payload.throughput = throughput; + payload.num_hits += 1; + + if (throughput < CURVE_SHADOW_TRANSPARENCY_CUTOFF) { + /* Accept result and terminate if throughput is sufficiently low */ + payload.result = true; + return false; + } + else { + return true; + } + } + + payload.num_hits += 1; + payload.num_recorded_hits += 1; + + uint record_index = num_recorded_hits; + + const IntegratorShadowState state = payload.state; + + const uint max_record_hits = min(uint(max_hits), INTEGRATOR_SHADOW_ISECT_SIZE); + if (record_index >= max_record_hits) { + /* If maximum number of hits reached, find a hit to replace. */ + float max_recorded_t = INTEGRATOR_STATE_ARRAY(state, shadow_isect, 0, t); + uint max_recorded_hit = 0; + + for (int i = 1; i < max_record_hits; i++) { + const float isect_t = INTEGRATOR_STATE_ARRAY(state, shadow_isect, i, t); + if (isect_t > max_recorded_t) { + max_recorded_t = isect_t; + max_recorded_hit = i; + } + } + + if (ray_tmax >= max_recorded_t) { + /* Accept hit, so that we don't consider any more hits beyond the distance of the + * current hit anymore. */ + payload.result = true; + return true; + } + + record_index = max_recorded_hit; + } + + INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, u) = u; + INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, v) = v; + INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, t) = ray_tmax; + INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, prim) = prim; + INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, object) = object; + INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, type) = type; + + /* Continue tracing. */ +# endif /* __TRANSPARENT_SHADOWS__ */ +#endif /* __SHADOW_RECORD_ALL__ */ + + return true; +} + +[[intersection(triangle, triangle_data, METALRT_TAGS)]] TriangleIntersectionResult +__anyhit__cycles_metalrt_shadow_all_hit_tri( + constant KernelParamsMetal &launch_params_metal [[buffer(1)]], + ray_data MetalKernelContext::MetalRTIntersectionShadowPayload &payload [[payload]], + unsigned int object [[user_instance_id]], + unsigned int primitive_id [[primitive_id]], + float2 barycentrics [[barycentric_coord]], + float ray_tmax [[distance]]) +{ + uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); + + TriangleIntersectionResult result; + result.continue_search = metalrt_shadow_all_hit( + launch_params_metal, payload, object, prim, barycentrics, ray_tmax); + result.accept = !result.continue_search; + return result; +} + +[[intersection(bounding_box, triangle_data, METALRT_TAGS)]] BoundingBoxIntersectionResult +__anyhit__cycles_metalrt_shadow_all_hit_box(const float ray_tmax [[max_distance]]) +{ + /* unused function */ + BoundingBoxIntersectionResult result; + result.distance = ray_tmax; + result.accept = false; + result.continue_search = false; + return result; +} + +template +inline TReturnType metalrt_visibility_test( + constant KernelParamsMetal &launch_params_metal, + ray_data MetalKernelContext::MetalRTIntersectionPayload &payload, + const uint object, + const uint prim, + const float u) +{ + TReturnType result; + +#ifdef __HAIR__ + if (intersection_type == METALRT_HIT_BOUNDING_BOX) { + /* Filter out curve endcaps. */ + if (u == 0.0f || u == 1.0f) { + result.accept = false; + result.continue_search = true; + return result; + } + } +#endif + + uint visibility = payload.visibility; +#ifdef __VISIBILITY_FLAG__ + if ((kernel_data_fetch(objects, object).visibility & visibility) == 0) { + result.accept = false; + result.continue_search = true; + return result; + } +#endif + + /* Shadow ray early termination. */ + if (visibility & PATH_RAY_SHADOW_OPAQUE) { + if (intersection_skip_self_shadow(payload.self, object, prim)) { + result.accept = false; + result.continue_search = true; + return result; + } + else { + result.accept = true; + result.continue_search = false; + return result; + } + } + else { + if (intersection_skip_self(payload.self, object, prim)) { + result.accept = false; + result.continue_search = true; + return result; + } + } + + result.accept = true; + result.continue_search = true; + return result; +} + +[[intersection(triangle, triangle_data, METALRT_TAGS)]] TriangleIntersectionResult +__anyhit__cycles_metalrt_visibility_test_tri( + constant KernelParamsMetal &launch_params_metal [[buffer(1)]], + ray_data MetalKernelContext::MetalRTIntersectionPayload &payload [[payload]], + unsigned int object [[user_instance_id]], + unsigned int primitive_id [[primitive_id]]) +{ + uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); + TriangleIntersectionResult result = + metalrt_visibility_test( + launch_params_metal, payload, object, prim, 0.0f); + if (result.accept) { + payload.prim = prim; + payload.type = kernel_data_fetch(objects, object).primitive_type; + } + return result; +} + +[[intersection(bounding_box, triangle_data, METALRT_TAGS)]] BoundingBoxIntersectionResult +__anyhit__cycles_metalrt_visibility_test_box(const float ray_tmax [[max_distance]]) +{ + /* Unused function */ + BoundingBoxIntersectionResult result; + result.accept = false; + result.continue_search = true; + result.distance = ray_tmax; + return result; +} + +/* Primitive intersection functions. */ + +#ifdef __HAIR__ +ccl_device_inline void metalrt_intersection_curve( + constant KernelParamsMetal &launch_params_metal, + ray_data MetalKernelContext::MetalRTIntersectionPayload &payload, + const uint object, + const uint prim, + const uint type, + const float3 ray_P, + const float3 ray_D, + float time, + const float ray_tmin, + const float ray_tmax, + thread BoundingBoxIntersectionResult &result) +{ +# ifdef __VISIBILITY_FLAG__ + const uint visibility = payload.visibility; + if ((kernel_data_fetch(objects, object).visibility & visibility) == 0) { + return; + } +# endif + + Intersection isect; + isect.t = ray_tmax; + + MetalKernelContext context(launch_params_metal); + if (context.curve_intersect( + NULL, &isect, ray_P, ray_D, ray_tmin, isect.t, object, prim, time, type)) { + result = metalrt_visibility_test( + launch_params_metal, payload, object, prim, isect.u); + if (result.accept) { + result.distance = isect.t; + payload.u = isect.u; + payload.v = isect.v; + payload.prim = prim; + payload.type = type; + } + } +} + +ccl_device_inline void metalrt_intersection_curve_shadow( + constant KernelParamsMetal &launch_params_metal, + ray_data MetalKernelContext::MetalRTIntersectionShadowPayload &payload, + const uint object, + const uint prim, + const uint type, + float time, + const float ray_tmin, + const float ray_tmax, + thread BoundingBoxIntersectionResult &result) +{ + const uint visibility = payload.visibility; + + Intersection isect; + isect.t = ray_tmax; + + MetalKernelContext context(launch_params_metal); + if (context.curve_intersect( + NULL, &isect, ray_P, ray_D, ray_tmin, isect.t, object, prim, time, type)) { + result.continue_search = metalrt_shadow_all_hit( + launch_params_metal, payload, object, prim, float2(isect.u, isect.v), ray_tmax); + result.accept = !result.continue_search; + } +} + +[[intersection(bounding_box, triangle_data, METALRT_TAGS)]] BoundingBoxIntersectionResult +__intersection__curve_ribbon(constant KernelParamsMetal &launch_params_metal [[buffer(1)]], + ray_data MetalKernelContext::MetalRTIntersectionPayload &payload + [[payload]], + const uint object [[user_instance_id]], + const uint primitive_id [[primitive_id]], + const float3 ray_P [[origin]], + const float3 ray_D [[direction]], + const float ray_tmin [[min_distance]], + const float ray_tmax [[max_distance]]) +{ + uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); + const KernelCurveSegment segment = kernel_data_fetch(curve_segments, prim); + + BoundingBoxIntersectionResult result; + result.accept = false; + result.continue_search = true; + result.distance = ray_tmax; + + if (segment.type & PRIMITIVE_CURVE_RIBBON) { + metalrt_intersection_curve(launch_params_metal, + payload, + object, + segment.prim, + segment.type, + ray_P, + ray_D, +# if defined(__METALRT_MOTION__) + payload.time, +# else + 0.0f, +# endif + ray_tmin, + ray_tmax, + result); + } + + return result; +} + +[[intersection(bounding_box, triangle_data, METALRT_TAGS)]] BoundingBoxIntersectionResult +__intersection__curve_ribbon_shadow( + constant KernelParamsMetal &launch_params_metal [[buffer(1)]], + ray_data MetalKernelContext::MetalRTIntersectionShadowPayload &payload [[payload]], + const uint object [[user_instance_id]], + const uint primitive_id [[primitive_id]], + const float3 ray_P [[origin]], + const float3 ray_D [[direction]], + const float ray_tmin [[min_distance]], + const float ray_tmax [[max_distance]]) +{ + uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); + const KernelCurveSegment segment = kernel_data_fetch(curve_segments, prim); + + BoundingBoxIntersectionResult result; + result.accept = false; + result.continue_search = true; + result.distance = ray_tmax; + + if (segment.type & PRIMITIVE_CURVE_RIBBON) { + metalrt_intersection_curve_shadow(launch_params_metal, + payload, + object, + segment.prim, + segment.type, + ray_P, + ray_D, +# if defined(__METALRT_MOTION__) + payload.time, +# else + 0.0f, +# endif + ray_tmin, + ray_tmax, + result); + } + + return result; +} + +[[intersection(bounding_box, triangle_data, METALRT_TAGS)]] BoundingBoxIntersectionResult +__intersection__curve_all(constant KernelParamsMetal &launch_params_metal [[buffer(1)]], + ray_data MetalKernelContext::MetalRTIntersectionPayload &payload + [[payload]], + const uint object [[user_instance_id]], + const uint primitive_id [[primitive_id]], + const float3 ray_P [[origin]], + const float3 ray_D [[direction]], + const float ray_tmin [[min_distance]], + const float ray_tmax [[max_distance]]) +{ + uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); + const KernelCurveSegment segment = kernel_data_fetch(curve_segments, prim); + + BoundingBoxIntersectionResult result; + result.accept = false; + result.continue_search = true; + result.distance = ray_tmax; + metalrt_intersection_curve(launch_params_metal, + payload, + object, + segment.prim, + segment.type, + ray_P, + ray_D, +# if defined(__METALRT_MOTION__) + payload.time, +# else + 0.0f, +# endif + ray_tmin, + ray_tmax, + result); + + return result; +} + +[[intersection(bounding_box, triangle_data, METALRT_TAGS)]] BoundingBoxIntersectionResult +__intersection__curve_all_shadow( + constant KernelParamsMetal &launch_params_metal [[buffer(1)]], + ray_data MetalKernelContext::MetalRTIntersectionShadowPayload &payload [[payload]], + const uint object [[user_instance_id]], + const uint primitive_id [[primitive_id]], + const float3 ray_P [[origin]], + const float3 ray_D [[direction]], + const float ray_tmin [[min_distance]], + const float ray_tmax [[max_distance]]) +{ + uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); + const KernelCurveSegment segment = kernel_data_fetch(curve_segments, prim); + + BoundingBoxIntersectionResult result; + result.accept = false; + result.continue_search = true; + result.distance = ray_tmax; + + metalrt_intersection_curve_shadow(launch_params_metal, + payload, + object, + segment.prim, + segment.type, + ray_P, + ray_D, +# if defined(__METALRT_MOTION__) + payload.time, +# else + 0.0f, +# endif + ray_tmin, + ray_tmax, + result); + + return result; +} +#endif /* __HAIR__ */ + +#ifdef __POINTCLOUD__ +ccl_device_inline void metalrt_intersection_point( + constant KernelParamsMetal &launch_params_metal, + ray_data MetalKernelContext::MetalRTIntersectionPayload &payload, + const uint object, + const uint prim, + const uint type, + const float3 ray_P, + const float3 ray_D, + float time, + const float ray_tmin, + const float ray_tmax, + thread BoundingBoxIntersectionResult &result) +{ +# ifdef __VISIBILITY_FLAG__ + const uint visibility = payload.visibility; + if ((kernel_data_fetch(objects, object).visibility & visibility) == 0) { + return; + } +# endif + + Intersection isect; + isect.t = ray_tmax; + + MetalKernelContext context(launch_params_metal); + if (context.point_intersect( + NULL, &isect, ray_P, ray_D, ray_tmin, isect.t, object, prim, time, type)) { + result = metalrt_visibility_test( + launch_params_metal, payload, object, prim, isect.u); + if (result.accept) { + result.distance = isect.t; + payload.u = isect.u; + payload.v = isect.v; + payload.prim = prim; + payload.type = type; + } + } +} + +ccl_device_inline void metalrt_intersection_point_shadow( + constant KernelParamsMetal &launch_params_metal, + ray_data MetalKernelContext::MetalRTIntersectionShadowPayload &payload, + const uint object, + const uint prim, + const uint type, + const float3 ray_P, + const float3 ray_D, + float time, + const float ray_tmin, + const float ray_tmax, + thread BoundingBoxIntersectionResult &result) +{ + const uint visibility = payload.visibility; + + Intersection isect; + isect.t = ray_tmax; + + MetalKernelContext context(launch_params_metal); + if (context.point_intersect( + NULL, &isect, ray_P, ray_D, ray_tmin, isect.t, object, prim, time, type)) { + result.continue_search = metalrt_shadow_all_hit( + launch_params_metal, payload, object, prim, float2(isect.u, isect.v), ray_tmax); + result.accept = !result.continue_search; + + if (result.accept) { + result.distance = isect.t; + } + } +} + +[[intersection(bounding_box, triangle_data, METALRT_TAGS)]] BoundingBoxIntersectionResult +__intersection__point(constant KernelParamsMetal &launch_params_metal [[buffer(1)]], + ray_data MetalKernelContext::MetalRTIntersectionPayload &payload [[payload]], + const uint object [[user_instance_id]], + const uint primitive_id [[primitive_id]], + const float3 ray_origin [[origin]], + const float3 ray_direction [[direction]], + const float ray_tmin [[min_distance]], + const float ray_tmax [[max_distance]]) +{ + const uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); + const int type = kernel_data_fetch(objects, object).primitive_type; + + BoundingBoxIntersectionResult result; + result.accept = false; + result.continue_search = true; + result.distance = ray_tmax; + + metalrt_intersection_point(launch_params_metal, + payload, + object, + prim, + type, + ray_origin, + ray_direction, +# if defined(__METALRT_MOTION__) + payload.time, +# else + 0.0f, +# endif + ray_tmin, + ray_tmax, + result); + + return result; +} + +[[intersection(bounding_box, triangle_data, METALRT_TAGS)]] BoundingBoxIntersectionResult +__intersection__point_shadow(constant KernelParamsMetal &launch_params_metal [[buffer(1)]], + ray_data MetalKernelContext::MetalRTIntersectionShadowPayload &payload + [[payload]], + const uint object [[user_instance_id]], + const uint primitive_id [[primitive_id]], + const float3 ray_origin [[origin]], + const float3 ray_direction [[direction]], + const float ray_tmin [[min_distance]], + const float ray_tmax [[max_distance]]) +{ + const uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); + const int type = kernel_data_fetch(objects, object).primitive_type; + + BoundingBoxIntersectionResult result; + result.accept = false; + result.continue_search = true; + result.distance = ray_tmax; + + metalrt_intersection_point_shadow(launch_params_metal, + payload, + object, + prim, + type, + ray_origin, + ray_direction, +# if defined(__METALRT_MOTION__) + payload.time, +# else + 0.0f, +# endif + ray_tmin, + ray_tmax, + result); + + return result; +} +#endif /* __POINTCLOUD__ */ + +/* Scene intersection. */ + +ccl_device_intersect bool scene_intersect(KernelGlobals kg, + ccl_private const Ray *ray, + const uint visibility, + ccl_private Intersection *isect) +{ + if (!scene_intersect_valid(ray)) { + isect->t = ray->tmax; + isect->type = PRIMITIVE_NONE; + return false; + } + +#if defined(__KERNEL_DEBUG__) + if (is_null_instance_acceleration_structure(metal_ancillaries->accel_struct)) { + isect->t = ray->tmax; + isect->type = PRIMITIVE_NONE; + kernel_assert(!"Invalid metal_ancillaries->accel_struct pointer"); + return false; + } + + if (is_null_intersection_function_table(metal_ancillaries->ift_default)) { + isect->t = ray->tmax; + isect->type = PRIMITIVE_NONE; + kernel_assert(!"Invalid ift_default"); + return false; + } +#endif + + metal::raytracing::ray r(ray->P, ray->D, ray->tmin, ray->tmax); + metalrt_intersector_type metalrt_intersect; + + if (!kernel_data.bvh.have_curves) { + metalrt_intersect.assume_geometry_type(metal::raytracing::geometry_type::triangle); + } + + MetalRTIntersectionPayload payload; + payload.self = ray->self; + payload.u = 0.0f; + payload.v = 0.0f; + payload.visibility = visibility; + + typename metalrt_intersector_type::result_type intersection; + + uint ray_mask = visibility & 0xFF; + if (0 == ray_mask && (visibility & ~0xFF) != 0) { + ray_mask = 0xFF; + /* No further intersector setup required: Default MetalRT behavior is any-hit. */ + } + else if (visibility & PATH_RAY_SHADOW_OPAQUE) { + /* No further intersector setup required: Shadow ray early termination is controlled by the + * intersection handler */ + } + +#if defined(__METALRT_MOTION__) + payload.time = ray->time; + intersection = metalrt_intersect.intersect(r, + metal_ancillaries->accel_struct, + ray_mask, + ray->time, + metal_ancillaries->ift_default, + payload); +#else + intersection = metalrt_intersect.intersect( + r, metal_ancillaries->accel_struct, ray_mask, metal_ancillaries->ift_default, payload); +#endif + + if (intersection.type == intersection_type::none) { + isect->t = ray->tmax; + isect->type = PRIMITIVE_NONE; + + return false; + } + + isect->t = intersection.distance; + + isect->prim = payload.prim; + isect->type = payload.type; + isect->object = intersection.user_instance_id; + + isect->t = intersection.distance; + if (intersection.type == intersection_type::triangle) { + isect->u = 1.0f - intersection.triangle_barycentric_coord.y - + intersection.triangle_barycentric_coord.x; + isect->v = intersection.triangle_barycentric_coord.x; + } + else { + isect->u = payload.u; + isect->v = payload.v; + } + + return isect->type != PRIMITIVE_NONE; +} + +#ifdef __BVH_LOCAL__ +ccl_device_intersect bool scene_intersect_local(KernelGlobals kg, + ccl_private const Ray *ray, + ccl_private LocalIntersection *local_isect, + int local_object, + ccl_private uint *lcg_state, + int max_hits) +{ + if (!intersection_ray_valid(ray)) { + if (local_isect) { + local_isect->num_hits = 0; + } + return false; + } + +# if defined(__KERNEL_DEBUG__) + if (is_null_instance_acceleration_structure(metal_ancillaries->accel_struct)) { + if (local_isect) { + local_isect->num_hits = 0; + } + kernel_assert(!"Invalid metal_ancillaries->accel_struct pointer"); + return false; + } + + if (is_null_intersection_function_table(metal_ancillaries->ift_local)) { + if (local_isect) { + local_isect->num_hits = 0; + } + kernel_assert(!"Invalid ift_local"); + return false; + } +# endif + + metal::raytracing::ray r(ray->P, ray->D, ray->tmin, ray->tmax); + metalrt_intersector_type metalrt_intersect; + + metalrt_intersect.force_opacity(metal::raytracing::forced_opacity::non_opaque); + if (!kernel_data.bvh.have_curves) { + metalrt_intersect.assume_geometry_type(metal::raytracing::geometry_type::triangle); + } + + MetalRTIntersectionLocalPayload payload; + payload.self = ray->self; + payload.local_object = local_object; + payload.max_hits = max_hits; + payload.local_isect.num_hits = 0; + if (lcg_state) { + payload.has_lcg_state = true; + payload.lcg_state = *lcg_state; + } + payload.result = false; + + typename metalrt_intersector_type::result_type intersection; + +# if defined(__METALRT_MOTION__) + intersection = metalrt_intersect.intersect( + r, metal_ancillaries->accel_struct, 0xFF, ray->time, metal_ancillaries->ift_local, payload); +# else + intersection = metalrt_intersect.intersect( + r, metal_ancillaries->accel_struct, 0xFF, metal_ancillaries->ift_local, payload); +# endif + + if (lcg_state) { + *lcg_state = payload.lcg_state; + } + *local_isect = payload.local_isect; + + return payload.result; +} +#endif + +#ifdef __SHADOW_RECORD_ALL__ +ccl_device_intersect bool scene_intersect_shadow_all(KernelGlobals kg, + IntegratorShadowState state, + ccl_private const Ray *ray, + uint visibility, + uint max_hits, + ccl_private uint *num_recorded_hits, + ccl_private float *throughput) +{ + if (!intersection_ray_valid(ray)) { + return false; + } + +# if defined(__KERNEL_DEBUG__) + if (is_null_instance_acceleration_structure(metal_ancillaries->accel_struct)) { + kernel_assert(!"Invalid metal_ancillaries->accel_struct pointer"); + return false; + } + + if (is_null_intersection_function_table(metal_ancillaries->ift_shadow)) { + kernel_assert(!"Invalid ift_shadow"); + return false; + } +# endif + + metal::raytracing::ray r(ray->P, ray->D, ray->tmin, ray->tmax); + metalrt_intersector_type metalrt_intersect; + + metalrt_intersect.force_opacity(metal::raytracing::forced_opacity::non_opaque); + if (!kernel_data.bvh.have_curves) { + metalrt_intersect.assume_geometry_type(metal::raytracing::geometry_type::triangle); + } + + MetalRTIntersectionShadowPayload payload; + payload.self = ray->self; + payload.visibility = visibility; + payload.max_hits = max_hits; + payload.num_hits = 0; + payload.num_recorded_hits = 0; + payload.throughput = 1.0f; + payload.result = false; + payload.state = state; + + uint ray_mask = visibility & 0xFF; + if (0 == ray_mask && (visibility & ~0xFF) != 0) { + ray_mask = 0xFF; + } + + typename metalrt_intersector_type::result_type intersection; + +# if defined(__METALRT_MOTION__) + payload.time = ray->time; + intersection = metalrt_intersect.intersect(r, + metal_ancillaries->accel_struct, + ray_mask, + ray->time, + metal_ancillaries->ift_shadow, + payload); +# else + intersection = metalrt_intersect.intersect( + r, metal_ancillaries->accel_struct, ray_mask, metal_ancillaries->ift_shadow, payload); +# endif + + *num_recorded_hits = payload.num_recorded_hits; + *throughput = payload.throughput; + + return payload.result; +} +#endif + +#ifdef __VOLUME__ +ccl_device_intersect bool scene_intersect_volume(KernelGlobals kg, + ccl_private const Ray *ray, + ccl_private Intersection *isect, + const uint visibility) +{ + if (!intersection_ray_valid(ray)) { + return false; + } + +# if defined(__KERNEL_DEBUG__) + if (is_null_instance_acceleration_structure(metal_ancillaries->accel_struct)) { + kernel_assert(!"Invalid metal_ancillaries->accel_struct pointer"); + return false; + } + + if (is_null_intersection_function_table(metal_ancillaries->ift_default)) { + kernel_assert(!"Invalid ift_default"); + return false; + } +# endif + + metal::raytracing::ray r(ray->P, ray->D, ray->tmin, ray->tmax); + metalrt_intersector_type metalrt_intersect; + + metalrt_intersect.force_opacity(metal::raytracing::forced_opacity::non_opaque); + if (!kernel_data.bvh.have_curves) { + metalrt_intersect.assume_geometry_type(metal::raytracing::geometry_type::triangle); + } + + MetalRTIntersectionPayload payload; + payload.self = ray->self; + payload.visibility = visibility; + + typename metalrt_intersector_type::result_type intersection; + + uint ray_mask = visibility & 0xFF; + if (0 == ray_mask && (visibility & ~0xFF) != 0) { + ray_mask = 0xFF; + } + +# if defined(__METALRT_MOTION__) + payload.time = ray->time; + intersection = metalrt_intersect.intersect(r, + metal_ancillaries->accel_struct, + ray_mask, + ray->time, + metal_ancillaries->ift_default, + payload); +# else + intersection = metalrt_intersect.intersect( + r, metal_ancillaries->accel_struct, ray_mask, metal_ancillaries->ift_default, payload); +# endif + + if (intersection.type == intersection_type::none) { + return false; + } + + isect->prim = payload.prim; + isect->type = payload.type; + isect->object = intersection.user_instance_id; + + isect->t = intersection.distance; + if (intersection.type == intersection_type::triangle) { + isect->u = 1.0f - intersection.triangle_barycentric_coord.y - + intersection.triangle_barycentric_coord.x; + isect->v = intersection.triangle_barycentric_coord.x; + } + else { + isect->u = payload.u; + isect->v = payload.v; + } + + return isect->type != PRIMITIVE_NONE; +} +#endif + +CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/device/metal/compat.h b/intern/cycles/kernel/device/metal/compat.h index 0ed52074a90..80ee8ef5b57 100644 --- a/intern/cycles/kernel/device/metal/compat.h +++ b/intern/cycles/kernel/device/metal/compat.h @@ -260,8 +260,6 @@ void kernel_gpu_##name::run(thread MetalKernelContext& context, \ #ifdef __METALRT__ -# define __KERNEL_GPU_RAYTRACING__ - # if defined(__METALRT_MOTION__) # define METALRT_TAGS instancing, instance_motion, primitive_motion # else diff --git a/intern/cycles/kernel/device/metal/kernel.metal b/intern/cycles/kernel/device/metal/kernel.metal index 8c6f2e1df5e..3df81fcf369 100644 --- a/intern/cycles/kernel/device/metal/kernel.metal +++ b/intern/cycles/kernel/device/metal/kernel.metal @@ -7,711 +7,3 @@ #include "kernel/device/metal/globals.h" #include "kernel/device/metal/function_constants.h" #include "kernel/device/gpu/kernel.h" - -/* MetalRT intersection handlers */ -#ifdef __METALRT__ - -/* Return type for a bounding box intersection function. */ -struct BoundingBoxIntersectionResult -{ - bool accept [[accept_intersection]]; - bool continue_search [[continue_search]]; - float distance [[distance]]; -}; - -/* Return type for a triangle intersection function. */ -struct TriangleIntersectionResult -{ - bool accept [[accept_intersection]]; - bool continue_search [[continue_search]]; -}; - -enum { METALRT_HIT_TRIANGLE, METALRT_HIT_BOUNDING_BOX }; - -ccl_device_inline bool intersection_skip_self(ray_data const RaySelfPrimitives& self, - const int object, - const int prim) -{ - return (self.prim == prim) && (self.object == object); -} - -ccl_device_inline bool intersection_skip_self_shadow(ray_data const RaySelfPrimitives& self, - const int object, - const int prim) -{ - return ((self.prim == prim) && (self.object == object)) || - ((self.light_prim == prim) && (self.light_object == object)); -} - -ccl_device_inline bool intersection_skip_self_local(ray_data const RaySelfPrimitives& self, - const int prim) -{ - return (self.prim == prim); -} - -template -TReturn metalrt_local_hit(constant KernelParamsMetal &launch_params_metal, - ray_data MetalKernelContext::MetalRTIntersectionLocalPayload &payload, - const uint object, - const uint primitive_id, - const float2 barycentrics, - const float ray_tmax) -{ - TReturn result; - -#ifdef __BVH_LOCAL__ - uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); - - if ((object != payload.local_object) || intersection_skip_self_local(payload.self, prim)) { - /* Only intersect with matching object and skip self-intersecton. */ - result.accept = false; - result.continue_search = true; - return result; - } - - const short max_hits = payload.max_hits; - if (max_hits == 0) { - /* Special case for when no hit information is requested, just report that something was hit */ - payload.result = true; - result.accept = true; - result.continue_search = false; - return result; - } - - int hit = 0; - if (payload.has_lcg_state) { - for (short i = min(max_hits, short(payload.local_isect.num_hits)) - 1; i >= 0; --i) { - if (ray_tmax == payload.local_isect.hits[i].t) { - result.accept = false; - result.continue_search = true; - return result; - } - } - - hit = payload.local_isect.num_hits++; - - if (payload.local_isect.num_hits > max_hits) { - hit = lcg_step_uint(&payload.lcg_state) % payload.local_isect.num_hits; - if (hit >= max_hits) { - result.accept = false; - result.continue_search = true; - return result; - } - } - } - else { - if (payload.local_isect.num_hits && ray_tmax > payload.local_isect.hits[0].t) { - /* Record closest intersection only. Do not terminate ray here, since there is no guarantee about distance ordering in any-hit */ - result.accept = false; - result.continue_search = true; - return result; - } - - payload.local_isect.num_hits = 1; - } - - ray_data Intersection *isect = &payload.local_isect.hits[hit]; - isect->t = ray_tmax; - isect->prim = prim; - isect->object = object; - isect->type = kernel_data_fetch(objects, object).primitive_type; - - isect->u = 1.0f - barycentrics.y - barycentrics.x; - isect->v = barycentrics.x; - - /* Record geometric normal */ - const uint tri_vindex = kernel_data_fetch(tri_vindex, isect->prim).w; - const float3 tri_a = float3(kernel_data_fetch(tri_verts, tri_vindex + 0)); - const float3 tri_b = float3(kernel_data_fetch(tri_verts, tri_vindex + 1)); - const float3 tri_c = float3(kernel_data_fetch(tri_verts, tri_vindex + 2)); - payload.local_isect.Ng[hit] = normalize(cross(tri_b - tri_a, tri_c - tri_a)); - - /* Continue tracing (without this the trace call would return after the first hit) */ - result.accept = false; - result.continue_search = true; - return result; -#endif -} - -[[intersection(triangle, triangle_data, METALRT_TAGS)]] -TriangleIntersectionResult -__anyhit__cycles_metalrt_local_hit_tri(constant KernelParamsMetal &launch_params_metal [[buffer(1)]], - ray_data MetalKernelContext::MetalRTIntersectionLocalPayload &payload [[payload]], - uint instance_id [[user_instance_id]], - uint primitive_id [[primitive_id]], - float2 barycentrics [[barycentric_coord]], - float ray_tmax [[distance]]) -{ - return metalrt_local_hit( - launch_params_metal, payload, instance_id, primitive_id, barycentrics, ray_tmax); -} - -[[intersection(bounding_box, triangle_data, METALRT_TAGS)]] -BoundingBoxIntersectionResult -__anyhit__cycles_metalrt_local_hit_box(const float ray_tmax [[max_distance]]) -{ - /* unused function */ - BoundingBoxIntersectionResult result; - result.distance = ray_tmax; - result.accept = false; - result.continue_search = false; - return result; -} - -template -bool metalrt_shadow_all_hit(constant KernelParamsMetal &launch_params_metal, - ray_data MetalKernelContext::MetalRTIntersectionShadowPayload &payload, - uint object, - uint prim, - const float2 barycentrics, - const float ray_tmax) -{ -#ifdef __SHADOW_RECORD_ALL__ -# ifdef __VISIBILITY_FLAG__ - const uint visibility = payload.visibility; - if ((kernel_data_fetch(objects, object).visibility & visibility) == 0) { - /* continue search */ - return true; - } -# endif - - if (intersection_skip_self_shadow(payload.self, object, prim)) { - /* continue search */ - return true; - } - - float u = 0.0f, v = 0.0f; - int type = 0; - if (intersection_type == METALRT_HIT_TRIANGLE) { - u = 1.0f - barycentrics.y - barycentrics.x; - v = barycentrics.x; - type = kernel_data_fetch(objects, object).primitive_type; - } -# ifdef __HAIR__ - else { - u = barycentrics.x; - v = barycentrics.y; - - const KernelCurveSegment segment = kernel_data_fetch(curve_segments, prim); - type = segment.type; - prim = segment.prim; - - /* Filter out curve endcaps */ - if (u == 0.0f || u == 1.0f) { - /* continue search */ - return true; - } - } -# endif - -# ifndef __TRANSPARENT_SHADOWS__ - /* No transparent shadows support compiled in, make opaque. */ - payload.result = true; - /* terminate ray */ - return false; -# else - short max_hits = payload.max_hits; - short num_hits = payload.num_hits; - short num_recorded_hits = payload.num_recorded_hits; - - MetalKernelContext context(launch_params_metal); - - /* If no transparent shadows, all light is blocked and we can stop immediately. */ - if (num_hits >= max_hits || - !(context.intersection_get_shader_flags(NULL, prim, type) & SD_HAS_TRANSPARENT_SHADOW)) { - payload.result = true; - /* terminate ray */ - return false; - } - - /* Always use baked shadow transparency for curves. */ - if (type & PRIMITIVE_CURVE) { - float throughput = payload.throughput; - throughput *= context.intersection_curve_shadow_transparency(nullptr, object, prim, u); - payload.throughput = throughput; - payload.num_hits += 1; - - if (throughput < CURVE_SHADOW_TRANSPARENCY_CUTOFF) { - /* Accept result and terminate if throughput is sufficiently low */ - payload.result = true; - return false; - } - else { - return true; - } - } - - payload.num_hits += 1; - payload.num_recorded_hits += 1; - - uint record_index = num_recorded_hits; - - const IntegratorShadowState state = payload.state; - - const uint max_record_hits = min(uint(max_hits), INTEGRATOR_SHADOW_ISECT_SIZE); - if (record_index >= max_record_hits) { - /* If maximum number of hits reached, find a hit to replace. */ - float max_recorded_t = INTEGRATOR_STATE_ARRAY(state, shadow_isect, 0, t); - uint max_recorded_hit = 0; - - for (int i = 1; i < max_record_hits; i++) { - const float isect_t = INTEGRATOR_STATE_ARRAY(state, shadow_isect, i, t); - if (isect_t > max_recorded_t) { - max_recorded_t = isect_t; - max_recorded_hit = i; - } - } - - if (ray_tmax >= max_recorded_t) { - /* Accept hit, so that we don't consider any more hits beyond the distance of the - * current hit anymore. */ - payload.result = true; - return true; - } - - record_index = max_recorded_hit; - } - - INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, u) = u; - INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, v) = v; - INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, t) = ray_tmax; - INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, prim) = prim; - INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, object) = object; - INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, type) = type; - - /* Continue tracing. */ -# endif /* __TRANSPARENT_SHADOWS__ */ -#endif /* __SHADOW_RECORD_ALL__ */ - - return true; -} - -[[intersection(triangle, triangle_data, METALRT_TAGS)]] -TriangleIntersectionResult -__anyhit__cycles_metalrt_shadow_all_hit_tri(constant KernelParamsMetal &launch_params_metal [[buffer(1)]], - ray_data MetalKernelContext::MetalRTIntersectionShadowPayload &payload [[payload]], - unsigned int object [[user_instance_id]], - unsigned int primitive_id [[primitive_id]], - float2 barycentrics [[barycentric_coord]], - float ray_tmax [[distance]]) -{ - uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); - - TriangleIntersectionResult result; - result.continue_search = metalrt_shadow_all_hit( - launch_params_metal, payload, object, prim, barycentrics, ray_tmax); - result.accept = !result.continue_search; - return result; -} - -[[intersection(bounding_box, triangle_data, METALRT_TAGS)]] -BoundingBoxIntersectionResult -__anyhit__cycles_metalrt_shadow_all_hit_box(const float ray_tmax [[max_distance]]) -{ - /* unused function */ - BoundingBoxIntersectionResult result; - result.distance = ray_tmax; - result.accept = false; - result.continue_search = false; - return result; -} - -template -inline TReturnType metalrt_visibility_test(constant KernelParamsMetal &launch_params_metal, - ray_data MetalKernelContext::MetalRTIntersectionPayload &payload, - const uint object, - const uint prim, - const float u) -{ - TReturnType result; - -# ifdef __HAIR__ - if (intersection_type == METALRT_HIT_BOUNDING_BOX) { - /* Filter out curve endcaps. */ - if (u == 0.0f || u == 1.0f) { - result.accept = false; - result.continue_search = true; - return result; - } - } -# endif - - uint visibility = payload.visibility; -# ifdef __VISIBILITY_FLAG__ - if ((kernel_data_fetch(objects, object).visibility & visibility) == 0) { - result.accept = false; - result.continue_search = true; - return result; - } -# endif - - /* Shadow ray early termination. */ - if (visibility & PATH_RAY_SHADOW_OPAQUE) { - if (intersection_skip_self_shadow(payload.self, object, prim)) { - result.accept = false; - result.continue_search = true; - return result; - } - else { - result.accept = true; - result.continue_search = false; - return result; - } - } - else { - if (intersection_skip_self(payload.self, object, prim)) { - result.accept = false; - result.continue_search = true; - return result; - } - } - - result.accept = true; - result.continue_search = true; - return result; -} - -[[intersection(triangle, triangle_data, METALRT_TAGS)]] -TriangleIntersectionResult -__anyhit__cycles_metalrt_visibility_test_tri(constant KernelParamsMetal &launch_params_metal [[buffer(1)]], - ray_data MetalKernelContext::MetalRTIntersectionPayload &payload [[payload]], - unsigned int object [[user_instance_id]], - unsigned int primitive_id [[primitive_id]]) -{ - uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); - TriangleIntersectionResult result = metalrt_visibility_test( - launch_params_metal, payload, object, prim, 0.0f); - if (result.accept) { - payload.prim = prim; - payload.type = kernel_data_fetch(objects, object).primitive_type; - } - return result; -} - -[[intersection(bounding_box, triangle_data, METALRT_TAGS)]] -BoundingBoxIntersectionResult -__anyhit__cycles_metalrt_visibility_test_box(const float ray_tmax [[max_distance]]) -{ - /* Unused function */ - BoundingBoxIntersectionResult result; - result.accept = false; - result.continue_search = true; - result.distance = ray_tmax; - return result; -} - -#ifdef __HAIR__ -ccl_device_inline -void metalrt_intersection_curve(constant KernelParamsMetal &launch_params_metal, - ray_data MetalKernelContext::MetalRTIntersectionPayload &payload, - const uint object, - const uint prim, - const uint type, - const float3 ray_P, - const float3 ray_D, - float time, - const float ray_tmin, - const float ray_tmax, - thread BoundingBoxIntersectionResult &result) -{ -# ifdef __VISIBILITY_FLAG__ - const uint visibility = payload.visibility; - if ((kernel_data_fetch(objects, object).visibility & visibility) == 0) { - return; - } -# endif - - Intersection isect; - isect.t = ray_tmax; - - MetalKernelContext context(launch_params_metal); - if (context.curve_intersect(NULL, &isect, ray_P, ray_D, ray_tmin, isect.t, object, prim, time, type)) { - result = metalrt_visibility_test( - launch_params_metal, payload, object, prim, isect.u); - if (result.accept) { - result.distance = isect.t; - payload.u = isect.u; - payload.v = isect.v; - payload.prim = prim; - payload.type = type; - } - } -} - -ccl_device_inline -void metalrt_intersection_curve_shadow(constant KernelParamsMetal &launch_params_metal, - ray_data MetalKernelContext::MetalRTIntersectionShadowPayload &payload, - const uint object, - const uint prim, - const uint type, - float time, - const float ray_tmin, - const float ray_tmax, - thread BoundingBoxIntersectionResult &result) -{ - const uint visibility = payload.visibility; - - Intersection isect; - isect.t = ray_tmax; - - MetalKernelContext context(launch_params_metal); - if (context.curve_intersect(NULL, &isect, ray_P, ray_D, ray_tmin, isect.t, object, prim, time, type)) { - result.continue_search = metalrt_shadow_all_hit( - launch_params_metal, payload, object, prim, float2(isect.u, isect.v), ray_tmax); - result.accept = !result.continue_search; - } -} - -[[intersection(bounding_box, triangle_data, METALRT_TAGS)]] -BoundingBoxIntersectionResult -__intersection__curve_ribbon(constant KernelParamsMetal &launch_params_metal [[buffer(1)]], - ray_data MetalKernelContext::MetalRTIntersectionPayload &payload [[payload]], - const uint object [[user_instance_id]], - const uint primitive_id [[primitive_id]], - const float3 ray_P [[origin]], - const float3 ray_D [[direction]], - const float ray_tmin [[min_distance]], - const float ray_tmax [[max_distance]]) -{ - uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); - const KernelCurveSegment segment = kernel_data_fetch(curve_segments, prim); - - BoundingBoxIntersectionResult result; - result.accept = false; - result.continue_search = true; - result.distance = ray_tmax; - - if (segment.type & PRIMITIVE_CURVE_RIBBON) { - metalrt_intersection_curve(launch_params_metal, payload, object, segment.prim, segment.type, ray_P, ray_D, -# if defined(__METALRT_MOTION__) - payload.time, -# else - 0.0f, -# endif - ray_tmin, ray_tmax, result); - } - - return result; -} - -[[intersection(bounding_box, triangle_data, METALRT_TAGS)]] -BoundingBoxIntersectionResult -__intersection__curve_ribbon_shadow(constant KernelParamsMetal &launch_params_metal [[buffer(1)]], - ray_data MetalKernelContext::MetalRTIntersectionShadowPayload &payload [[payload]], - const uint object [[user_instance_id]], - const uint primitive_id [[primitive_id]], - const float3 ray_P [[origin]], - const float3 ray_D [[direction]], - const float ray_tmin [[min_distance]], - const float ray_tmax [[max_distance]]) -{ - uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); - const KernelCurveSegment segment = kernel_data_fetch(curve_segments, prim); - - BoundingBoxIntersectionResult result; - result.accept = false; - result.continue_search = true; - result.distance = ray_tmax; - - if (segment.type & PRIMITIVE_CURVE_RIBBON) { - metalrt_intersection_curve_shadow(launch_params_metal, payload, object, segment.prim, segment.type, ray_P, ray_D, -# if defined(__METALRT_MOTION__) - payload.time, -# else - 0.0f, -# endif - ray_tmin, ray_tmax, result); - } - - return result; -} - -[[intersection(bounding_box, triangle_data, METALRT_TAGS)]] -BoundingBoxIntersectionResult -__intersection__curve_all(constant KernelParamsMetal &launch_params_metal [[buffer(1)]], - ray_data MetalKernelContext::MetalRTIntersectionPayload &payload [[payload]], - const uint object [[user_instance_id]], - const uint primitive_id [[primitive_id]], - const float3 ray_P [[origin]], - const float3 ray_D [[direction]], - const float ray_tmin [[min_distance]], - const float ray_tmax [[max_distance]]) -{ - uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); - const KernelCurveSegment segment = kernel_data_fetch(curve_segments, prim); - - BoundingBoxIntersectionResult result; - result.accept = false; - result.continue_search = true; - result.distance = ray_tmax; - metalrt_intersection_curve(launch_params_metal, payload, object, segment.prim, segment.type, ray_P, ray_D, -# if defined(__METALRT_MOTION__) - payload.time, -# else - 0.0f, -# endif - ray_tmin, ray_tmax, result); - - return result; -} - -[[intersection(bounding_box, triangle_data, METALRT_TAGS)]] -BoundingBoxIntersectionResult -__intersection__curve_all_shadow(constant KernelParamsMetal &launch_params_metal [[buffer(1)]], - ray_data MetalKernelContext::MetalRTIntersectionShadowPayload &payload [[payload]], - const uint object [[user_instance_id]], - const uint primitive_id [[primitive_id]], - const float3 ray_P [[origin]], - const float3 ray_D [[direction]], - const float ray_tmin [[min_distance]], - const float ray_tmax [[max_distance]]) -{ - uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); - const KernelCurveSegment segment = kernel_data_fetch(curve_segments, prim); - - BoundingBoxIntersectionResult result; - result.accept = false; - result.continue_search = true; - result.distance = ray_tmax; - - metalrt_intersection_curve_shadow(launch_params_metal, payload, object, segment.prim, segment.type, ray_P, ray_D, -# if defined(__METALRT_MOTION__) - payload.time, -# else - 0.0f, -# endif - ray_tmin, ray_tmax, result); - - return result; -} -#endif /* __HAIR__ */ - -#ifdef __POINTCLOUD__ -ccl_device_inline -void metalrt_intersection_point(constant KernelParamsMetal &launch_params_metal, - ray_data MetalKernelContext::MetalRTIntersectionPayload &payload, - const uint object, - const uint prim, - const uint type, - const float3 ray_P, - const float3 ray_D, - float time, - const float ray_tmin, - const float ray_tmax, - thread BoundingBoxIntersectionResult &result) -{ -# ifdef __VISIBILITY_FLAG__ - const uint visibility = payload.visibility; - if ((kernel_data_fetch(objects, object).visibility & visibility) == 0) { - return; - } -# endif - - Intersection isect; - isect.t = ray_tmax; - - MetalKernelContext context(launch_params_metal); - if (context.point_intersect(NULL, &isect, ray_P, ray_D, ray_tmin, isect.t, object, prim, time, type)) { - result = metalrt_visibility_test( - launch_params_metal, payload, object, prim, isect.u); - if (result.accept) { - result.distance = isect.t; - payload.u = isect.u; - payload.v = isect.v; - payload.prim = prim; - payload.type = type; - } - } -} - -ccl_device_inline -void metalrt_intersection_point_shadow(constant KernelParamsMetal &launch_params_metal, - ray_data MetalKernelContext::MetalRTIntersectionShadowPayload &payload, - const uint object, - const uint prim, - const uint type, - const float3 ray_P, - const float3 ray_D, - float time, - const float ray_tmin, - const float ray_tmax, - thread BoundingBoxIntersectionResult &result) -{ - const uint visibility = payload.visibility; - - Intersection isect; - isect.t = ray_tmax; - - MetalKernelContext context(launch_params_metal); - if (context.point_intersect(NULL, &isect, ray_P, ray_D, ray_tmin, isect.t, object, prim, time, type)) { - result.continue_search = metalrt_shadow_all_hit( - launch_params_metal, payload, object, prim, float2(isect.u, isect.v), ray_tmax); - result.accept = !result.continue_search; - - if (result.accept) { - result.distance = isect.t; - } - } -} - -[[intersection(bounding_box, triangle_data, METALRT_TAGS)]] -BoundingBoxIntersectionResult -__intersection__point(constant KernelParamsMetal &launch_params_metal [[buffer(1)]], - ray_data MetalKernelContext::MetalRTIntersectionPayload &payload [[payload]], - const uint object [[user_instance_id]], - const uint primitive_id [[primitive_id]], - const float3 ray_origin [[origin]], - const float3 ray_direction [[direction]], - const float ray_tmin [[min_distance]], - const float ray_tmax [[max_distance]]) -{ - const uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); - const int type = kernel_data_fetch(objects, object).primitive_type; - - BoundingBoxIntersectionResult result; - result.accept = false; - result.continue_search = true; - result.distance = ray_tmax; - - metalrt_intersection_point(launch_params_metal, payload, object, prim, type, ray_origin, ray_direction, -# if defined(__METALRT_MOTION__) - payload.time, -# else - 0.0f, -# endif - ray_tmin, ray_tmax, result); - - return result; -} - -[[intersection(bounding_box, triangle_data, METALRT_TAGS)]] -BoundingBoxIntersectionResult -__intersection__point_shadow(constant KernelParamsMetal &launch_params_metal [[buffer(1)]], - ray_data MetalKernelContext::MetalRTIntersectionShadowPayload &payload [[payload]], - const uint object [[user_instance_id]], - const uint primitive_id [[primitive_id]], - const float3 ray_origin [[origin]], - const float3 ray_direction [[direction]], - const float ray_tmin [[min_distance]], - const float ray_tmax [[max_distance]]) -{ - const uint prim = primitive_id + kernel_data_fetch(object_prim_offset, object); - const int type = kernel_data_fetch(objects, object).primitive_type; - - BoundingBoxIntersectionResult result; - result.accept = false; - result.continue_search = true; - result.distance = ray_tmax; - - metalrt_intersection_point_shadow(launch_params_metal, payload, object, prim, type, ray_origin, ray_direction, -# if defined(__METALRT_MOTION__) - payload.time, -# else - 0.0f, -# endif - ray_tmin, ray_tmax, result); - - return result; -} -#endif /* __POINTCLOUD__ */ -#endif /* __METALRT__ */ diff --git a/intern/cycles/kernel/device/optix/bvh.h b/intern/cycles/kernel/device/optix/bvh.h new file mode 100644 index 00000000000..a1621277ec7 --- /dev/null +++ b/intern/cycles/kernel/device/optix/bvh.h @@ -0,0 +1,646 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright 2021-2022 Blender Foundation */ + +/* OptiX implementation of ray-scene intersection. */ + +#pragma once + +#include "kernel/bvh/types.h" +#include "kernel/bvh/util.h" + +#define OPTIX_DEFINE_ABI_VERSION_ONLY +#include + +CCL_NAMESPACE_BEGIN + +/* Utilities. */ + +template ccl_device_forceinline T *get_payload_ptr_0() +{ + return pointer_unpack_from_uint(optixGetPayload_0(), optixGetPayload_1()); +} +template ccl_device_forceinline T *get_payload_ptr_2() +{ + return pointer_unpack_from_uint(optixGetPayload_2(), optixGetPayload_3()); +} + +template ccl_device_forceinline T *get_payload_ptr_6() +{ + return (T *)(((uint64_t)optixGetPayload_7() << 32) | optixGetPayload_6()); +} + +ccl_device_forceinline int get_object_id() +{ +#ifdef __OBJECT_MOTION__ + /* Always get the instance ID from the TLAS + * There might be a motion transform node between TLAS and BLAS which does not have one. */ + return optixGetInstanceIdFromHandle(optixGetTransformListHandle(0)); +#else + return optixGetInstanceId(); +#endif +} + +/* Hit/miss functions. */ + +extern "C" __global__ void __miss__kernel_optix_miss() +{ + /* 'kernel_path_lamp_emission' checks intersection distance, so need to set it even on a miss. */ + optixSetPayload_0(__float_as_uint(optixGetRayTmax())); + optixSetPayload_5(PRIMITIVE_NONE); +} + +extern "C" __global__ void __anyhit__kernel_optix_local_hit() +{ +#if defined(__HAIR__) || defined(__POINTCLOUD__) + if (!optixIsTriangleHit()) { + /* Ignore curves and points. */ + return optixIgnoreIntersection(); + } +#endif + +#ifdef __BVH_LOCAL__ + const int object = get_object_id(); + if (object != optixGetPayload_4() /* local_object */) { + /* Only intersect with matching object. */ + return optixIgnoreIntersection(); + } + + const int prim = optixGetPrimitiveIndex(); + ccl_private Ray *const ray = get_payload_ptr_6(); + if (intersection_skip_self_local(ray->self, prim)) { + return optixIgnoreIntersection(); + } + + const uint max_hits = optixGetPayload_5(); + if (max_hits == 0) { + /* Special case for when no hit information is requested, just report that something was hit */ + optixSetPayload_5(true); + return optixTerminateRay(); + } + + int hit = 0; + uint *const lcg_state = get_payload_ptr_0(); + LocalIntersection *const local_isect = get_payload_ptr_2(); + + if (lcg_state) { + for (int i = min(max_hits, local_isect->num_hits) - 1; i >= 0; --i) { + if (optixGetRayTmax() == local_isect->hits[i].t) { + return optixIgnoreIntersection(); + } + } + + hit = local_isect->num_hits++; + + if (local_isect->num_hits > max_hits) { + hit = lcg_step_uint(lcg_state) % local_isect->num_hits; + if (hit >= max_hits) { + return optixIgnoreIntersection(); + } + } + } + else { + if (local_isect->num_hits && optixGetRayTmax() > local_isect->hits[0].t) { + /* Record closest intersection only. + * Do not terminate ray here, since there is no guarantee about distance ordering in any-hit. + */ + return optixIgnoreIntersection(); + } + + local_isect->num_hits = 1; + } + + Intersection *isect = &local_isect->hits[hit]; + isect->t = optixGetRayTmax(); + isect->prim = prim; + isect->object = get_object_id(); + isect->type = kernel_data_fetch(objects, isect->object).primitive_type; + + const float2 barycentrics = optixGetTriangleBarycentrics(); + isect->u = 1.0f - barycentrics.y - barycentrics.x; + isect->v = barycentrics.x; + + /* Record geometric normal. */ + const uint tri_vindex = kernel_data_fetch(tri_vindex, prim).w; + const float3 tri_a = kernel_data_fetch(tri_verts, tri_vindex + 0); + const float3 tri_b = kernel_data_fetch(tri_verts, tri_vindex + 1); + const float3 tri_c = kernel_data_fetch(tri_verts, tri_vindex + 2); + local_isect->Ng[hit] = normalize(cross(tri_b - tri_a, tri_c - tri_a)); + + /* Continue tracing (without this the trace call would return after the first hit). */ + optixIgnoreIntersection(); +#endif +} + +extern "C" __global__ void __anyhit__kernel_optix_shadow_all_hit() +{ +#ifdef __SHADOW_RECORD_ALL__ + int prim = optixGetPrimitiveIndex(); + const uint object = get_object_id(); +# ifdef __VISIBILITY_FLAG__ + const uint visibility = optixGetPayload_4(); + if ((kernel_data_fetch(objects, object).visibility & visibility) == 0) { + return optixIgnoreIntersection(); + } +# endif + + ccl_private Ray *const ray = get_payload_ptr_6(); + if (intersection_skip_self_shadow(ray->self, object, prim)) { + return optixIgnoreIntersection(); + } + + float u = 0.0f, v = 0.0f; + int type = 0; + if (optixIsTriangleHit()) { + const float2 barycentrics = optixGetTriangleBarycentrics(); + u = 1.0f - barycentrics.y - barycentrics.x; + v = barycentrics.x; + type = kernel_data_fetch(objects, object).primitive_type; + } +# ifdef __HAIR__ + else if ((optixGetHitKind() & (~PRIMITIVE_MOTION)) != PRIMITIVE_POINT) { + u = __uint_as_float(optixGetAttribute_0()); + v = __uint_as_float(optixGetAttribute_1()); + + const KernelCurveSegment segment = kernel_data_fetch(curve_segments, prim); + type = segment.type; + prim = segment.prim; + +# if OPTIX_ABI_VERSION < 55 + /* Filter out curve endcaps. */ + if (u == 0.0f || u == 1.0f) { + return optixIgnoreIntersection(); + } +# endif + } +# endif + else { + type = kernel_data_fetch(objects, object).primitive_type; + u = 0.0f; + v = 0.0f; + } + +# ifndef __TRANSPARENT_SHADOWS__ + /* No transparent shadows support compiled in, make opaque. */ + optixSetPayload_5(true); + return optixTerminateRay(); +# else + const uint max_hits = optixGetPayload_3(); + const uint num_hits_packed = optixGetPayload_2(); + const uint num_recorded_hits = uint16_unpack_from_uint_0(num_hits_packed); + const uint num_hits = uint16_unpack_from_uint_1(num_hits_packed); + + /* If no transparent shadows, all light is blocked and we can stop immediately. */ + if (num_hits >= max_hits || + !(intersection_get_shader_flags(NULL, prim, type) & SD_HAS_TRANSPARENT_SHADOW)) { + optixSetPayload_5(true); + return optixTerminateRay(); + } + + /* Always use baked shadow transparency for curves. */ + if (type & PRIMITIVE_CURVE) { + float throughput = __uint_as_float(optixGetPayload_1()); + throughput *= intersection_curve_shadow_transparency(nullptr, object, prim, u); + optixSetPayload_1(__float_as_uint(throughput)); + optixSetPayload_2(uint16_pack_to_uint(num_recorded_hits, num_hits + 1)); + + if (throughput < CURVE_SHADOW_TRANSPARENCY_CUTOFF) { + optixSetPayload_5(true); + return optixTerminateRay(); + } + else { + /* Continue tracing. */ + optixIgnoreIntersection(); + return; + } + } + + /* Record transparent intersection. */ + optixSetPayload_2(uint16_pack_to_uint(num_recorded_hits + 1, num_hits + 1)); + + uint record_index = num_recorded_hits; + + const IntegratorShadowState state = optixGetPayload_0(); + + const uint max_record_hits = min(max_hits, INTEGRATOR_SHADOW_ISECT_SIZE); + if (record_index >= max_record_hits) { + /* If maximum number of hits reached, find a hit to replace. */ + float max_recorded_t = INTEGRATOR_STATE_ARRAY(state, shadow_isect, 0, t); + uint max_recorded_hit = 0; + + for (int i = 1; i < max_record_hits; i++) { + const float isect_t = INTEGRATOR_STATE_ARRAY(state, shadow_isect, i, t); + if (isect_t > max_recorded_t) { + max_recorded_t = isect_t; + max_recorded_hit = i; + } + } + + if (optixGetRayTmax() >= max_recorded_t) { + /* Accept hit, so that OptiX won't consider any more hits beyond the distance of the + * current hit anymore. */ + return; + } + + record_index = max_recorded_hit; + } + + INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, u) = u; + INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, v) = v; + INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, t) = optixGetRayTmax(); + INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, prim) = prim; + INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, object) = object; + INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, type) = type; + + /* Continue tracing. */ + optixIgnoreIntersection(); +# endif /* __TRANSPARENT_SHADOWS__ */ +#endif /* __SHADOW_RECORD_ALL__ */ +} + +extern "C" __global__ void __anyhit__kernel_optix_volume_test() +{ +#if defined(__HAIR__) || defined(__POINTCLOUD__) + if (!optixIsTriangleHit()) { + /* Ignore curves. */ + return optixIgnoreIntersection(); + } +#endif + + const uint object = get_object_id(); +#ifdef __VISIBILITY_FLAG__ + const uint visibility = optixGetPayload_4(); + if ((kernel_data_fetch(objects, object).visibility & visibility) == 0) { + return optixIgnoreIntersection(); + } +#endif + + if ((kernel_data_fetch(object_flag, object) & SD_OBJECT_HAS_VOLUME) == 0) { + return optixIgnoreIntersection(); + } + + const int prim = optixGetPrimitiveIndex(); + ccl_private Ray *const ray = get_payload_ptr_6(); + if (intersection_skip_self(ray->self, object, prim)) { + return optixIgnoreIntersection(); + } +} + +extern "C" __global__ void __anyhit__kernel_optix_visibility_test() +{ +#ifdef __HAIR__ +# if OPTIX_ABI_VERSION < 55 + if (optixGetPrimitiveType() == OPTIX_PRIMITIVE_TYPE_ROUND_CUBIC_BSPLINE) { + /* Filter out curve endcaps. */ + const float u = __uint_as_float(optixGetAttribute_0()); + if (u == 0.0f || u == 1.0f) { + return optixIgnoreIntersection(); + } + } +# endif +#endif + + const uint object = get_object_id(); + const uint visibility = optixGetPayload_4(); +#ifdef __VISIBILITY_FLAG__ + if ((kernel_data_fetch(objects, object).visibility & visibility) == 0) { + return optixIgnoreIntersection(); + } +#endif + + const int prim = optixGetPrimitiveIndex(); + ccl_private Ray *const ray = get_payload_ptr_6(); + + if (visibility & PATH_RAY_SHADOW_OPAQUE) { + if (intersection_skip_self_shadow(ray->self, object, prim)) { + return optixIgnoreIntersection(); + } + else { + /* Shadow ray early termination. */ + return optixTerminateRay(); + } + } + else { + if (intersection_skip_self(ray->self, object, prim)) { + return optixIgnoreIntersection(); + } + } +} + +extern "C" __global__ void __closesthit__kernel_optix_hit() +{ + const int object = get_object_id(); + const int prim = optixGetPrimitiveIndex(); + + optixSetPayload_0(__float_as_uint(optixGetRayTmax())); /* Intersection distance */ + optixSetPayload_4(object); + + if (optixIsTriangleHit()) { + const float2 barycentrics = optixGetTriangleBarycentrics(); + optixSetPayload_1(__float_as_uint(1.0f - barycentrics.y - barycentrics.x)); + optixSetPayload_2(__float_as_uint(barycentrics.x)); + optixSetPayload_3(prim); + optixSetPayload_5(kernel_data_fetch(objects, object).primitive_type); + } + else if ((optixGetHitKind() & (~PRIMITIVE_MOTION)) != PRIMITIVE_POINT) { + const KernelCurveSegment segment = kernel_data_fetch(curve_segments, prim); + optixSetPayload_1(optixGetAttribute_0()); /* Same as 'optixGetCurveParameter()' */ + optixSetPayload_2(optixGetAttribute_1()); + optixSetPayload_3(segment.prim); + optixSetPayload_5(segment.type); + } + else { + optixSetPayload_1(0); + optixSetPayload_2(0); + optixSetPayload_3(prim); + optixSetPayload_5(kernel_data_fetch(objects, object).primitive_type); + } +} + +/* Custom primitive intersection functions. */ + +#ifdef __HAIR__ +ccl_device_inline void optix_intersection_curve(const int prim, const int type) +{ + const int object = get_object_id(); + +# ifdef __VISIBILITY_FLAG__ + const uint visibility = optixGetPayload_4(); + if ((kernel_data_fetch(objects, object).visibility & visibility) == 0) { + return; + } +# endif + + const float3 ray_P = optixGetObjectRayOrigin(); + const float3 ray_D = optixGetObjectRayDirection(); + const float ray_tmin = optixGetRayTmin(); + +# ifdef __OBJECT_MOTION__ + const float time = optixGetRayTime(); +# else + const float time = 0.0f; +# endif + + Intersection isect; + isect.t = optixGetRayTmax(); + + if (curve_intersect(NULL, &isect, ray_P, ray_D, ray_tmin, isect.t, object, prim, time, type)) { + static_assert(PRIMITIVE_ALL < 128, "Values >= 128 are reserved for OptiX internal use"); + optixReportIntersection(isect.t, + type & PRIMITIVE_ALL, + __float_as_int(isect.u), /* Attribute_0 */ + __float_as_int(isect.v)); /* Attribute_1 */ + } +} + +extern "C" __global__ void __intersection__curve_ribbon() +{ + const KernelCurveSegment segment = kernel_data_fetch(curve_segments, optixGetPrimitiveIndex()); + const int prim = segment.prim; + const int type = segment.type; + if (type & PRIMITIVE_CURVE_RIBBON) { + optix_intersection_curve(prim, type); + } +} + +#endif + +#ifdef __POINTCLOUD__ +extern "C" __global__ void __intersection__point() +{ + const int prim = optixGetPrimitiveIndex(); + const int object = get_object_id(); + const int type = kernel_data_fetch(objects, object).primitive_type; + +# ifdef __VISIBILITY_FLAG__ + const uint visibility = optixGetPayload_4(); + if ((kernel_data_fetch(objects, object).visibility & visibility) == 0) { + return; + } +# endif + + const float3 ray_P = optixGetObjectRayOrigin(); + const float3 ray_D = optixGetObjectRayDirection(); + const float ray_tmin = optixGetRayTmin(); + +# ifdef __OBJECT_MOTION__ + const float time = optixGetRayTime(); +# else + const float time = 0.0f; +# endif + + Intersection isect; + isect.t = optixGetRayTmax(); + + if (point_intersect(NULL, &isect, ray_P, ray_D, ray_tmin, isect.t, object, prim, time, type)) { + static_assert(PRIMITIVE_ALL < 128, "Values >= 128 are reserved for OptiX internal use"); + optixReportIntersection(isect.t, type & PRIMITIVE_ALL); + } +} +#endif + +/* Scene intersection. */ + +ccl_device_intersect bool scene_intersect(KernelGlobals kg, + ccl_private const Ray *ray, + const uint visibility, + ccl_private Intersection *isect) +{ + uint p0 = 0; + uint p1 = 0; + uint p2 = 0; + uint p3 = 0; + uint p4 = visibility; + uint p5 = PRIMITIVE_NONE; + uint p6 = ((uint64_t)ray) & 0xFFFFFFFF; + uint p7 = (((uint64_t)ray) >> 32) & 0xFFFFFFFF; + + uint ray_mask = visibility & 0xFF; + uint ray_flags = OPTIX_RAY_FLAG_ENFORCE_ANYHIT; + if (0 == ray_mask && (visibility & ~0xFF) != 0) { + ray_mask = 0xFF; + } + else if (visibility & PATH_RAY_SHADOW_OPAQUE) { + ray_flags |= OPTIX_RAY_FLAG_TERMINATE_ON_FIRST_HIT; + } + + optixTrace(intersection_ray_valid(ray) ? kernel_data.device_bvh : 0, + ray->P, + ray->D, + ray->tmin, + ray->tmax, + ray->time, + ray_mask, + ray_flags, + 0, /* SBT offset for PG_HITD */ + 0, + 0, + p0, + p1, + p2, + p3, + p4, + p5, + p6, + p7); + + isect->t = __uint_as_float(p0); + isect->u = __uint_as_float(p1); + isect->v = __uint_as_float(p2); + isect->prim = p3; + isect->object = p4; + isect->type = p5; + + return p5 != PRIMITIVE_NONE; +} + +#ifdef __BVH_LOCAL__ +ccl_device_intersect bool scene_intersect_local(KernelGlobals kg, + ccl_private const Ray *ray, + ccl_private LocalIntersection *local_isect, + int local_object, + ccl_private uint *lcg_state, + int max_hits) +{ + uint p0 = pointer_pack_to_uint_0(lcg_state); + uint p1 = pointer_pack_to_uint_1(lcg_state); + uint p2 = pointer_pack_to_uint_0(local_isect); + uint p3 = pointer_pack_to_uint_1(local_isect); + uint p4 = local_object; + uint p6 = ((uint64_t)ray) & 0xFFFFFFFF; + uint p7 = (((uint64_t)ray) >> 32) & 0xFFFFFFFF; + + /* Is set to zero on miss or if ray is aborted, so can be used as return value. */ + uint p5 = max_hits; + + if (local_isect) { + local_isect->num_hits = 0; /* Initialize hit count to zero. */ + } + optixTrace(intersection_ray_valid(ray) ? kernel_data.device_bvh : 0, + ray->P, + ray->D, + ray->tmin, + ray->tmax, + ray->time, + 0xFF, + /* Need to always call into __anyhit__kernel_optix_local_hit. */ + OPTIX_RAY_FLAG_ENFORCE_ANYHIT, + 2, /* SBT offset for PG_HITL */ + 0, + 0, + p0, + p1, + p2, + p3, + p4, + p5, + p6, + p7); + + return p5; +} +#endif + +#ifdef __SHADOW_RECORD_ALL__ +ccl_device_intersect bool scene_intersect_shadow_all(KernelGlobals kg, + IntegratorShadowState state, + ccl_private const Ray *ray, + uint visibility, + uint max_hits, + ccl_private uint *num_recorded_hits, + ccl_private float *throughput) +{ + uint p0 = state; + uint p1 = __float_as_uint(1.0f); /* Throughput. */ + uint p2 = 0; /* Number of hits. */ + uint p3 = max_hits; + uint p4 = visibility; + uint p5 = false; + uint p6 = ((uint64_t)ray) & 0xFFFFFFFF; + uint p7 = (((uint64_t)ray) >> 32) & 0xFFFFFFFF; + + uint ray_mask = visibility & 0xFF; + if (0 == ray_mask && (visibility & ~0xFF) != 0) { + ray_mask = 0xFF; + } + + optixTrace(intersection_ray_valid(ray) ? kernel_data.device_bvh : 0, + ray->P, + ray->D, + ray->tmin, + ray->tmax, + ray->time, + ray_mask, + /* Need to always call into __anyhit__kernel_optix_shadow_all_hit. */ + OPTIX_RAY_FLAG_ENFORCE_ANYHIT, + 1, /* SBT offset for PG_HITS */ + 0, + 0, + p0, + p1, + p2, + p3, + p4, + p5, + p6, + p7); + + *num_recorded_hits = uint16_unpack_from_uint_0(p2); + *throughput = __uint_as_float(p1); + + return p5; +} +#endif + +#ifdef __VOLUME__ +ccl_device_intersect bool scene_intersect_volume(KernelGlobals kg, + ccl_private const Ray *ray, + ccl_private Intersection *isect, + const uint visibility) +{ + uint p0 = 0; + uint p1 = 0; + uint p2 = 0; + uint p3 = 0; + uint p4 = visibility; + uint p5 = PRIMITIVE_NONE; + uint p6 = ((uint64_t)ray) & 0xFFFFFFFF; + uint p7 = (((uint64_t)ray) >> 32) & 0xFFFFFFFF; + + uint ray_mask = visibility & 0xFF; + if (0 == ray_mask && (visibility & ~0xFF) != 0) { + ray_mask = 0xFF; + } + + optixTrace(intersection_ray_valid(ray) ? kernel_data.device_bvh : 0, + ray->P, + ray->D, + ray->tmin, + ray->tmax, + ray->time, + ray_mask, + /* Need to always call into __anyhit__kernel_optix_volume_test. */ + OPTIX_RAY_FLAG_ENFORCE_ANYHIT, + 3, /* SBT offset for PG_HITV */ + 0, + 0, + p0, + p1, + p2, + p3, + p4, + p5, + p6, + p7); + + isect->t = __uint_as_float(p0); + isect->u = __uint_as_float(p1); + isect->v = __uint_as_float(p2); + isect->prim = p3; + isect->object = p4; + isect->type = p5; + + return p5 != PRIMITIVE_NONE; +} +#endif + +CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/device/optix/compat.h b/intern/cycles/kernel/device/optix/compat.h index aa4a6321a8b..1a11a533b7e 100644 --- a/intern/cycles/kernel/device/optix/compat.h +++ b/intern/cycles/kernel/device/optix/compat.h @@ -8,7 +8,6 @@ #include #define __KERNEL_GPU__ -#define __KERNEL_GPU_RAYTRACING__ #define __KERNEL_CUDA__ /* OptiX kernels are implicitly CUDA kernels too */ #define __KERNEL_OPTIX__ #define CCL_NAMESPACE_BEGIN diff --git a/intern/cycles/kernel/device/optix/kernel.cu b/intern/cycles/kernel/device/optix/kernel.cu index 204aa8182a1..6abb5aeacb9 100644 --- a/intern/cycles/kernel/device/optix/kernel.cu +++ b/intern/cycles/kernel/device/optix/kernel.cu @@ -20,34 +20,6 @@ #include "kernel/integrator/intersect_volume_stack.h" // clang-format on -#define OPTIX_DEFINE_ABI_VERSION_ONLY -#include - -template ccl_device_forceinline T *get_payload_ptr_0() -{ - return pointer_unpack_from_uint(optixGetPayload_0(), optixGetPayload_1()); -} -template ccl_device_forceinline T *get_payload_ptr_2() -{ - return pointer_unpack_from_uint(optixGetPayload_2(), optixGetPayload_3()); -} - -template ccl_device_forceinline T *get_payload_ptr_6() -{ - return (T *)(((uint64_t)optixGetPayload_7() << 32) | optixGetPayload_6()); -} - -ccl_device_forceinline int get_object_id() -{ -#ifdef __OBJECT_MOTION__ - /* Always get the instance ID from the TLAS - * There might be a motion transform node between TLAS and BLAS which does not have one. */ - return optixGetInstanceIdFromHandle(optixGetTransformListHandle(0)); -#else - return optixGetInstanceId(); -#endif -} - extern "C" __global__ void __raygen__kernel_optix_integrator_intersect_closest() { const int global_index = optixGetLaunchIndex().x; @@ -84,396 +56,3 @@ extern "C" __global__ void __raygen__kernel_optix_integrator_intersect_volume_st integrator_intersect_volume_stack(nullptr, path_index); } -extern "C" __global__ void __miss__kernel_optix_miss() -{ - /* 'kernel_path_lamp_emission' checks intersection distance, so need to set it even on a miss. */ - optixSetPayload_0(__float_as_uint(optixGetRayTmax())); - optixSetPayload_5(PRIMITIVE_NONE); -} - -extern "C" __global__ void __anyhit__kernel_optix_local_hit() -{ -#if defined(__HAIR__) || defined(__POINTCLOUD__) - if (!optixIsTriangleHit()) { - /* Ignore curves and points. */ - return optixIgnoreIntersection(); - } -#endif - -#ifdef __BVH_LOCAL__ - const int object = get_object_id(); - if (object != optixGetPayload_4() /* local_object */) { - /* Only intersect with matching object. */ - return optixIgnoreIntersection(); - } - - const int prim = optixGetPrimitiveIndex(); - ccl_private Ray *const ray = get_payload_ptr_6(); - if (intersection_skip_self_local(ray->self, prim)) { - return optixIgnoreIntersection(); - } - - const uint max_hits = optixGetPayload_5(); - if (max_hits == 0) { - /* Special case for when no hit information is requested, just report that something was hit */ - optixSetPayload_5(true); - return optixTerminateRay(); - } - - int hit = 0; - uint *const lcg_state = get_payload_ptr_0(); - LocalIntersection *const local_isect = get_payload_ptr_2(); - - if (lcg_state) { - for (int i = min(max_hits, local_isect->num_hits) - 1; i >= 0; --i) { - if (optixGetRayTmax() == local_isect->hits[i].t) { - return optixIgnoreIntersection(); - } - } - - hit = local_isect->num_hits++; - - if (local_isect->num_hits > max_hits) { - hit = lcg_step_uint(lcg_state) % local_isect->num_hits; - if (hit >= max_hits) { - return optixIgnoreIntersection(); - } - } - } - else { - if (local_isect->num_hits && optixGetRayTmax() > local_isect->hits[0].t) { - /* Record closest intersection only. - * Do not terminate ray here, since there is no guarantee about distance ordering in any-hit. - */ - return optixIgnoreIntersection(); - } - - local_isect->num_hits = 1; - } - - Intersection *isect = &local_isect->hits[hit]; - isect->t = optixGetRayTmax(); - isect->prim = prim; - isect->object = get_object_id(); - isect->type = kernel_data_fetch(objects, isect->object).primitive_type; - - const float2 barycentrics = optixGetTriangleBarycentrics(); - isect->u = 1.0f - barycentrics.y - barycentrics.x; - isect->v = barycentrics.x; - - /* Record geometric normal. */ - const uint tri_vindex = kernel_data_fetch(tri_vindex, prim).w; - const float3 tri_a = kernel_data_fetch(tri_verts, tri_vindex + 0); - const float3 tri_b = kernel_data_fetch(tri_verts, tri_vindex + 1); - const float3 tri_c = kernel_data_fetch(tri_verts, tri_vindex + 2); - local_isect->Ng[hit] = normalize(cross(tri_b - tri_a, tri_c - tri_a)); - - /* Continue tracing (without this the trace call would return after the first hit). */ - optixIgnoreIntersection(); -#endif -} - -extern "C" __global__ void __anyhit__kernel_optix_shadow_all_hit() -{ -#ifdef __SHADOW_RECORD_ALL__ - int prim = optixGetPrimitiveIndex(); - const uint object = get_object_id(); -# ifdef __VISIBILITY_FLAG__ - const uint visibility = optixGetPayload_4(); - if ((kernel_data_fetch(objects, object).visibility & visibility) == 0) { - return optixIgnoreIntersection(); - } -# endif - - ccl_private Ray *const ray = get_payload_ptr_6(); - if (intersection_skip_self_shadow(ray->self, object, prim)) { - return optixIgnoreIntersection(); - } - - float u = 0.0f, v = 0.0f; - int type = 0; - if (optixIsTriangleHit()) { - const float2 barycentrics = optixGetTriangleBarycentrics(); - u = 1.0f - barycentrics.y - barycentrics.x; - v = barycentrics.x; - type = kernel_data_fetch(objects, object).primitive_type; - } -# ifdef __HAIR__ - else if ((optixGetHitKind() & (~PRIMITIVE_MOTION)) != PRIMITIVE_POINT) { - u = __uint_as_float(optixGetAttribute_0()); - v = __uint_as_float(optixGetAttribute_1()); - - const KernelCurveSegment segment = kernel_data_fetch(curve_segments, prim); - type = segment.type; - prim = segment.prim; - -# if OPTIX_ABI_VERSION < 55 - /* Filter out curve endcaps. */ - if (u == 0.0f || u == 1.0f) { - return optixIgnoreIntersection(); - } -# endif - } -# endif - else { - type = kernel_data_fetch(objects, object).primitive_type; - u = 0.0f; - v = 0.0f; - } - -# ifndef __TRANSPARENT_SHADOWS__ - /* No transparent shadows support compiled in, make opaque. */ - optixSetPayload_5(true); - return optixTerminateRay(); -# else - const uint max_hits = optixGetPayload_3(); - const uint num_hits_packed = optixGetPayload_2(); - const uint num_recorded_hits = uint16_unpack_from_uint_0(num_hits_packed); - const uint num_hits = uint16_unpack_from_uint_1(num_hits_packed); - - /* If no transparent shadows, all light is blocked and we can stop immediately. */ - if (num_hits >= max_hits || - !(intersection_get_shader_flags(NULL, prim, type) & SD_HAS_TRANSPARENT_SHADOW)) { - optixSetPayload_5(true); - return optixTerminateRay(); - } - - /* Always use baked shadow transparency for curves. */ - if (type & PRIMITIVE_CURVE) { - float throughput = __uint_as_float(optixGetPayload_1()); - throughput *= intersection_curve_shadow_transparency(nullptr, object, prim, u); - optixSetPayload_1(__float_as_uint(throughput)); - optixSetPayload_2(uint16_pack_to_uint(num_recorded_hits, num_hits + 1)); - - if (throughput < CURVE_SHADOW_TRANSPARENCY_CUTOFF) { - optixSetPayload_5(true); - return optixTerminateRay(); - } - else { - /* Continue tracing. */ - optixIgnoreIntersection(); - return; - } - } - - /* Record transparent intersection. */ - optixSetPayload_2(uint16_pack_to_uint(num_recorded_hits + 1, num_hits + 1)); - - uint record_index = num_recorded_hits; - - const IntegratorShadowState state = optixGetPayload_0(); - - const uint max_record_hits = min(max_hits, INTEGRATOR_SHADOW_ISECT_SIZE); - if (record_index >= max_record_hits) { - /* If maximum number of hits reached, find a hit to replace. */ - float max_recorded_t = INTEGRATOR_STATE_ARRAY(state, shadow_isect, 0, t); - uint max_recorded_hit = 0; - - for (int i = 1; i < max_record_hits; i++) { - const float isect_t = INTEGRATOR_STATE_ARRAY(state, shadow_isect, i, t); - if (isect_t > max_recorded_t) { - max_recorded_t = isect_t; - max_recorded_hit = i; - } - } - - if (optixGetRayTmax() >= max_recorded_t) { - /* Accept hit, so that OptiX won't consider any more hits beyond the distance of the - * current hit anymore. */ - return; - } - - record_index = max_recorded_hit; - } - - INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, u) = u; - INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, v) = v; - INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, t) = optixGetRayTmax(); - INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, prim) = prim; - INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, object) = object; - INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, record_index, type) = type; - - /* Continue tracing. */ - optixIgnoreIntersection(); -# endif /* __TRANSPARENT_SHADOWS__ */ -#endif /* __SHADOW_RECORD_ALL__ */ -} - -extern "C" __global__ void __anyhit__kernel_optix_volume_test() -{ -#if defined(__HAIR__) || defined(__POINTCLOUD__) - if (!optixIsTriangleHit()) { - /* Ignore curves. */ - return optixIgnoreIntersection(); - } -#endif - - const uint object = get_object_id(); -#ifdef __VISIBILITY_FLAG__ - const uint visibility = optixGetPayload_4(); - if ((kernel_data_fetch(objects, object).visibility & visibility) == 0) { - return optixIgnoreIntersection(); - } -#endif - - if ((kernel_data_fetch(object_flag, object) & SD_OBJECT_HAS_VOLUME) == 0) { - return optixIgnoreIntersection(); - } - - const int prim = optixGetPrimitiveIndex(); - ccl_private Ray *const ray = get_payload_ptr_6(); - if (intersection_skip_self(ray->self, object, prim)) { - return optixIgnoreIntersection(); - } -} - -extern "C" __global__ void __anyhit__kernel_optix_visibility_test() -{ -#ifdef __HAIR__ -# if OPTIX_ABI_VERSION < 55 - if (optixGetPrimitiveType() == OPTIX_PRIMITIVE_TYPE_ROUND_CUBIC_BSPLINE) { - /* Filter out curve endcaps. */ - const float u = __uint_as_float(optixGetAttribute_0()); - if (u == 0.0f || u == 1.0f) { - return optixIgnoreIntersection(); - } - } -# endif -#endif - - const uint object = get_object_id(); - const uint visibility = optixGetPayload_4(); -#ifdef __VISIBILITY_FLAG__ - if ((kernel_data_fetch(objects, object).visibility & visibility) == 0) { - return optixIgnoreIntersection(); - } -#endif - - const int prim = optixGetPrimitiveIndex(); - ccl_private Ray *const ray = get_payload_ptr_6(); - - if (visibility & PATH_RAY_SHADOW_OPAQUE) { - if (intersection_skip_self_shadow(ray->self, object, prim)) { - return optixIgnoreIntersection(); - } - else { - /* Shadow ray early termination. */ - return optixTerminateRay(); - } - } - else { - if (intersection_skip_self(ray->self, object, prim)) { - return optixIgnoreIntersection(); - } - } -} - -extern "C" __global__ void __closesthit__kernel_optix_hit() -{ - const int object = get_object_id(); - const int prim = optixGetPrimitiveIndex(); - - optixSetPayload_0(__float_as_uint(optixGetRayTmax())); /* Intersection distance */ - optixSetPayload_4(object); - - if (optixIsTriangleHit()) { - const float2 barycentrics = optixGetTriangleBarycentrics(); - optixSetPayload_1(__float_as_uint(1.0f - barycentrics.y - barycentrics.x)); - optixSetPayload_2(__float_as_uint(barycentrics.x)); - optixSetPayload_3(prim); - optixSetPayload_5(kernel_data_fetch(objects, object).primitive_type); - } - else if ((optixGetHitKind() & (~PRIMITIVE_MOTION)) != PRIMITIVE_POINT) { - const KernelCurveSegment segment = kernel_data_fetch(curve_segments, prim); - optixSetPayload_1(optixGetAttribute_0()); /* Same as 'optixGetCurveParameter()' */ - optixSetPayload_2(optixGetAttribute_1()); - optixSetPayload_3(segment.prim); - optixSetPayload_5(segment.type); - } - else { - optixSetPayload_1(0); - optixSetPayload_2(0); - optixSetPayload_3(prim); - optixSetPayload_5(kernel_data_fetch(objects, object).primitive_type); - } -} - -#ifdef __HAIR__ -ccl_device_inline void optix_intersection_curve(const int prim, const int type) -{ - const int object = get_object_id(); - -# ifdef __VISIBILITY_FLAG__ - const uint visibility = optixGetPayload_4(); - if ((kernel_data_fetch(objects, object).visibility & visibility) == 0) { - return; - } -# endif - - const float3 ray_P = optixGetObjectRayOrigin(); - const float3 ray_D = optixGetObjectRayDirection(); - const float ray_tmin = optixGetRayTmin(); - -# ifdef __OBJECT_MOTION__ - const float time = optixGetRayTime(); -# else - const float time = 0.0f; -# endif - - Intersection isect; - isect.t = optixGetRayTmax(); - - if (curve_intersect(NULL, &isect, ray_P, ray_D, ray_tmin, isect.t, object, prim, time, type)) { - static_assert(PRIMITIVE_ALL < 128, "Values >= 128 are reserved for OptiX internal use"); - optixReportIntersection(isect.t, - type & PRIMITIVE_ALL, - __float_as_int(isect.u), /* Attribute_0 */ - __float_as_int(isect.v)); /* Attribute_1 */ - } -} - -extern "C" __global__ void __intersection__curve_ribbon() -{ - const KernelCurveSegment segment = kernel_data_fetch(curve_segments, optixGetPrimitiveIndex()); - const int prim = segment.prim; - const int type = segment.type; - if (type & PRIMITIVE_CURVE_RIBBON) { - optix_intersection_curve(prim, type); - } -} - -#endif - -#ifdef __POINTCLOUD__ -extern "C" __global__ void __intersection__point() -{ - const int prim = optixGetPrimitiveIndex(); - const int object = get_object_id(); - const int type = kernel_data_fetch(objects, object).primitive_type; - -# ifdef __VISIBILITY_FLAG__ - const uint visibility = optixGetPayload_4(); - if ((kernel_data_fetch(objects, object).visibility & visibility) == 0) { - return; - } -# endif - - const float3 ray_P = optixGetObjectRayOrigin(); - const float3 ray_D = optixGetObjectRayDirection(); - const float ray_tmin = optixGetRayTmin(); - -# ifdef __OBJECT_MOTION__ - const float time = optixGetRayTime(); -# else - const float time = 0.0f; -# endif - - Intersection isect; - isect.t = optixGetRayTmax(); - - if (point_intersect(NULL, &isect, ray_P, ray_D, ray_tmin, isect.t, object, prim, time, type)) { - static_assert(PRIMITIVE_ALL < 128, "Values >= 128 are reserved for OptiX internal use"); - optixReportIntersection(isect.t, type & PRIMITIVE_ALL); - } -} -#endif diff --git a/intern/cycles/kernel/integrator/intersect_volume_stack.h b/intern/cycles/kernel/integrator/intersect_volume_stack.h index 9ba4a0a3964..b53bee11312 100644 --- a/intern/cycles/kernel/integrator/intersect_volume_stack.h +++ b/intern/cycles/kernel/integrator/intersect_volume_stack.h @@ -38,8 +38,7 @@ ccl_device void integrator_volume_stack_update_for_subsurface(KernelGlobals kg, #ifdef __VOLUME_RECORD_ALL__ Intersection hits[2 * MAX_VOLUME_STACK_SIZE + 1]; - uint num_hits = scene_intersect_volume_all( - kg, &volume_ray, hits, 2 * volume_stack_size, visibility); + uint num_hits = scene_intersect_volume(kg, &volume_ray, hits, 2 * volume_stack_size, visibility); if (num_hits > 0) { Intersection *isect = hits; @@ -108,8 +107,7 @@ ccl_device void integrator_volume_stack_init(KernelGlobals kg, IntegratorState s #ifdef __VOLUME_RECORD_ALL__ Intersection hits[2 * MAX_VOLUME_STACK_SIZE + 1]; - uint num_hits = scene_intersect_volume_all( - kg, &volume_ray, hits, 2 * volume_stack_size, visibility); + uint num_hits = scene_intersect_volume(kg, &volume_ray, hits, 2 * volume_stack_size, visibility); if (num_hits > 0) { int enclosed_volumes[MAX_VOLUME_STACK_SIZE]; Intersection *isect = hits; diff --git a/intern/cycles/kernel/integrator/subsurface_random_walk.h b/intern/cycles/kernel/integrator/subsurface_random_walk.h index 7857673b271..e43bbb3c50a 100644 --- a/intern/cycles/kernel/integrator/subsurface_random_walk.h +++ b/intern/cycles/kernel/integrator/subsurface_random_walk.h @@ -377,7 +377,6 @@ ccl_device_inline bool subsurface_random_walk(KernelGlobals kg, hit = (ss_isect.num_hits > 0); if (hit) { - /* t is always in world space with OptiX and MetalRT. */ ray.tmax = ss_isect.hits[0].t; } diff --git a/intern/cycles/kernel/types.h b/intern/cycles/kernel/types.h index 4f4b811a8e7..d809f6fc2bd 100644 --- a/intern/cycles/kernel/types.h +++ b/intern/cycles/kernel/types.h @@ -83,7 +83,6 @@ CCL_NAMESPACE_BEGIN #define __LAMP_MIS__ #define __CAMERA_MOTION__ #define __OBJECT_MOTION__ -#define __BAKING__ #define __PRINCIPLED__ #define __SUBSURFACE__ #define __VOLUME__ @@ -99,10 +98,6 @@ CCL_NAMESPACE_BEGIN # define __VOLUME_RECORD_ALL__ #endif /* __KERNEL_CPU__ */ -#ifdef __KERNEL_GPU_RAYTRACING__ -# undef __BAKING__ -#endif /* __KERNEL_GPU_RAYTRACING__ */ - /* MNEE currently causes "Compute function exceeds available temporary registers" * on Metal, disabled for now. */ #ifndef __KERNEL_METAL__ @@ -129,9 +124,6 @@ CCL_NAMESPACE_BEGIN # if !(__KERNEL_FEATURES & KERNEL_FEATURE_SUBSURFACE) # undef __SUBSURFACE__ # endif -# if !(__KERNEL_FEATURES & KERNEL_FEATURE_BAKING) -# undef __BAKING__ -# endif # if !(__KERNEL_FEATURES & KERNEL_FEATURE_PATCH_EVALUATION) # undef __PATCH_EVAL__ # endif -- cgit v1.2.3 From 793d2031395246fb2421d130742e0fe61ab2b29c Mon Sep 17 00:00:00 2001 From: Andrii Symkin Date: Mon, 25 Jul 2022 16:55:48 +0200 Subject: Cycles: add math functions for float8 This patch adds required math functions for float8 to make it possible using float8 instead of float3 for color data. Differential Revision: https://developer.blender.org/D15525 --- intern/cycles/kernel/CMakeLists.txt | 1 + intern/cycles/util/CMakeLists.txt | 1 + intern/cycles/util/math.h | 1 + intern/cycles/util/math_float8.h | 419 +++++++++++++++++++++++++++++++++ intern/cycles/util/types_float8.h | 15 +- intern/cycles/util/types_float8_impl.h | 23 +- 6 files changed, 442 insertions(+), 18 deletions(-) create mode 100644 intern/cycles/util/math_float8.h diff --git a/intern/cycles/kernel/CMakeLists.txt b/intern/cycles/kernel/CMakeLists.txt index 94632dff200..8ecdac6ee27 100644 --- a/intern/cycles/kernel/CMakeLists.txt +++ b/intern/cycles/kernel/CMakeLists.txt @@ -317,6 +317,7 @@ set(SRC_UTIL_HEADERS ../util/math_float2.h ../util/math_float3.h ../util/math_float4.h + ../util/math_float8.h ../util/math_int2.h ../util/math_int3.h ../util/math_int4.h diff --git a/intern/cycles/util/CMakeLists.txt b/intern/cycles/util/CMakeLists.txt index fddac1dbbcf..9bc9f00e142 100644 --- a/intern/cycles/util/CMakeLists.txt +++ b/intern/cycles/util/CMakeLists.txt @@ -63,6 +63,7 @@ set(SRC_HEADERS math_float2.h math_float3.h math_float4.h + math_float8.h math_int2.h math_int3.h math_int4.h diff --git a/intern/cycles/util/math.h b/intern/cycles/util/math.h index 8360ce05a56..2631304c84b 100644 --- a/intern/cycles/util/math.h +++ b/intern/cycles/util/math.h @@ -540,6 +540,7 @@ CCL_NAMESPACE_END #include "util/math_float2.h" #include "util/math_float3.h" #include "util/math_float4.h" +#include "util/math_float8.h" #include "util/rect.h" diff --git a/intern/cycles/util/math_float8.h b/intern/cycles/util/math_float8.h new file mode 100644 index 00000000000..8ed8d56a034 --- /dev/null +++ b/intern/cycles/util/math_float8.h @@ -0,0 +1,419 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright 2022 Blender Foundation */ + +#ifndef __UTIL_MATH_FLOAT8_H__ +#define __UTIL_MATH_FLOAT8_H__ + +#ifndef __UTIL_MATH_H__ +# error "Do not include this file directly, include util/types.h instead." +#endif + +CCL_NAMESPACE_BEGIN + +/******************************************************************************* + * Declaration. + */ + +ccl_device_inline float8 operator+(const float8 &a, const float8 &b); +ccl_device_inline float8 operator+(const float8 &a, const float f); +ccl_device_inline float8 operator+(const float f, const float8 &a); + +ccl_device_inline float8 operator-(const float8 &a); +ccl_device_inline float8 operator-(const float8 &a, const float8 &b); +ccl_device_inline float8 operator-(const float8 &a, const float f); +ccl_device_inline float8 operator-(const float f, const float8 &a); + +ccl_device_inline float8 operator*(const float8 &a, const float8 &b); +ccl_device_inline float8 operator*(const float8 &a, const float f); +ccl_device_inline float8 operator*(const float f, const float8 &a); + +ccl_device_inline float8 operator/(const float8 &a, const float8 &b); +ccl_device_inline float8 operator/(const float8 &a, float f); +ccl_device_inline float8 operator/(const float f, const float8 &a); + +ccl_device_inline float8 operator+=(float8 &a, const float8 &b); + +ccl_device_inline float8 operator*=(float8 &a, const float8 &b); +ccl_device_inline float8 operator*=(float8 &a, float f); + +ccl_device_inline float8 operator/=(float8 &a, float f); + +ccl_device_inline bool operator==(const float8 &a, const float8 &b); + +ccl_device_inline float8 rcp(const float8 &a); +ccl_device_inline float8 sqrt(const float8 &a); +ccl_device_inline float8 sqr(const float8 &a); +ccl_device_inline bool is_zero(const float8 &a); +ccl_device_inline float average(const float8 &a); +ccl_device_inline float8 min(const float8 &a, const float8 &b); +ccl_device_inline float8 max(const float8 &a, const float8 &b); +ccl_device_inline float8 clamp(const float8 &a, const float8 &mn, const float8 &mx); +ccl_device_inline float8 fabs(const float8 &a); +ccl_device_inline float8 mix(const float8 &a, const float8 &b, float t); + +ccl_device_inline float8 safe_divide(const float8 a, const float b); +ccl_device_inline float8 safe_divide(const float8 a, const float8 b); + +ccl_device_inline float reduce_min(const float8 &a); +ccl_device_inline float reduce_max(const float8 &a); +ccl_device_inline float reduce_add(const float8 &a); + +ccl_device_inline float8 saturate(const float8 &a); +ccl_device_inline bool isequal(const float8 a, const float8 b); + +/******************************************************************************* + * Definition. + */ + +ccl_device_inline float8 zero_float8() +{ +#ifdef __KERNEL_AVX2__ + return float8(_mm256_setzero_ps()); +#else + return make_float8(0.0f); +#endif +} + +ccl_device_inline float8 one_float8() +{ + return make_float8(1.0f); +} + +ccl_device_inline float8 operator+(const float8 &a, const float8 &b) +{ +#ifdef __KERNEL_AVX2__ + return float8(_mm256_add_ps(a.m256, b.m256)); +#else + return make_float8( + a.a + b.a, a.b + b.b, a.c + b.c, a.d + b.d, a.e + b.e, a.f + b.f, a.g + b.g, a.h + b.h); +#endif +} + +ccl_device_inline float8 operator+(const float8 &a, const float f) +{ + return a + make_float8(f); +} + +ccl_device_inline float8 operator+(const float f, const float8 &a) +{ + return make_float8(f) + a; +} + +ccl_device_inline float8 operator-(const float8 &a) +{ +#ifdef __KERNEL_AVX2__ + __m256 mask = _mm256_castsi256_ps(_mm256_set1_epi32(0x80000000)); + return float8(_mm256_xor_ps(a.m256, mask)); +#else + return make_float8(-a.a, -a.b, -a.c, -a.d, -a.e, -a.f, -a.g, -a.h); +#endif +} + +ccl_device_inline float8 operator-(const float8 &a, const float8 &b) +{ +#ifdef __KERNEL_AVX2__ + return float8(_mm256_sub_ps(a.m256, b.m256)); +#else + return make_float8( + a.a - b.a, a.b - b.b, a.c - b.c, a.d - b.d, a.e - b.e, a.f - b.f, a.g - b.g, a.h - b.h); +#endif +} + +ccl_device_inline float8 operator-(const float8 &a, const float f) +{ + return a - make_float8(f); +} + +ccl_device_inline float8 operator-(const float f, const float8 &a) +{ + return make_float8(f) - a; +} + +ccl_device_inline float8 operator*(const float8 &a, const float8 &b) +{ +#ifdef __KERNEL_AVX2__ + return float8(_mm256_mul_ps(a.m256, b.m256)); +#else + return make_float8( + a.a * b.a, a.b * b.b, a.c * b.c, a.d * b.d, a.e * b.e, a.f * b.f, a.g * b.g, a.h * b.h); +#endif +} + +ccl_device_inline float8 operator*(const float8 &a, const float f) +{ + return a * make_float8(f); +} + +ccl_device_inline float8 operator*(const float f, const float8 &a) +{ + return make_float8(f) * a; +} + +ccl_device_inline float8 operator/(const float8 &a, const float8 &b) +{ +#ifdef __KERNEL_AVX2__ + return float8(_mm256_div_ps(a.m256, b.m256)); +#else + return make_float8( + a.a / b.a, a.b / b.b, a.c / b.c, a.d / b.d, a.e / b.e, a.f / b.f, a.g / b.g, a.h / b.h); +#endif +} + +ccl_device_inline float8 operator/(const float8 &a, const float f) +{ + return a / make_float8(f); +} + +ccl_device_inline float8 operator/(const float f, const float8 &a) +{ + return make_float8(f) / a; +} + +ccl_device_inline float8 operator+=(float8 &a, const float8 &b) +{ + return a = a + b; +} + +ccl_device_inline float8 operator-=(float8 &a, const float8 &b) +{ + return a = a - b; +} + +ccl_device_inline float8 operator*=(float8 &a, const float8 &b) +{ + return a = a * b; +} + +ccl_device_inline float8 operator*=(float8 &a, float f) +{ + return a = a * f; +} + +ccl_device_inline float8 operator/=(float8 &a, float f) +{ + return a = a / f; +} + +ccl_device_inline bool operator==(const float8 &a, const float8 &b) +{ +#ifdef __KERNEL_AVX2__ + return (_mm256_movemask_ps(_mm256_castsi256_ps( + _mm256_cmpeq_epi32(_mm256_castps_si256(a.m256), _mm256_castps_si256(b.m256)))) & + 0b11111111) == 0b11111111; +#else + return (a.a == b.a && a.b == b.b && a.c == b.c && a.d == b.d && a.e == b.e && a.f == b.f && + a.g == b.g && a.h == b.h); +#endif +} + +ccl_device_inline float8 rcp(const float8 &a) +{ +#ifdef __KERNEL_AVX2__ + return float8(_mm256_rcp_ps(a.m256)); +#else + return make_float8(1.0f / a.a, + 1.0f / a.b, + 1.0f / a.c, + 1.0f / a.d, + 1.0f / a.e, + 1.0f / a.f, + 1.0f / a.g, + 1.0f / a.h); +#endif +} + +ccl_device_inline float8 sqrt(const float8 &a) +{ +#ifdef __KERNEL_AVX2__ + return float8(_mm256_sqrt_ps(a.m256)); +#else + return make_float8(sqrtf(a.a), + sqrtf(a.b), + sqrtf(a.c), + sqrtf(a.d), + sqrtf(a.e), + sqrtf(a.f), + sqrtf(a.g), + sqrtf(a.h)); +#endif +} + +ccl_device_inline float8 sqr(const float8 &a) +{ + return a * a; +} + +ccl_device_inline bool is_zero(const float8 &a) +{ + return a == make_float8(0.0f); +} + +ccl_device_inline float average(const float8 &a) +{ + return reduce_add(a) / 8.0f; +} + +ccl_device_inline float8 min(const float8 &a, const float8 &b) +{ +#ifdef __KERNEL_AVX2__ + return float8(_mm256_min_ps(a.m256, b.m256)); +#else + return make_float8(min(a.a, b.a), + min(a.b, b.b), + min(a.c, b.c), + min(a.d, b.d), + min(a.e, b.e), + min(a.f, b.f), + min(a.g, b.g), + min(a.h, b.h)); +#endif +} + +ccl_device_inline float8 max(const float8 &a, const float8 &b) +{ +#ifdef __KERNEL_AVX2__ + return float8(_mm256_max_ps(a.m256, b.m256)); +#else + return make_float8(max(a.a, b.a), + max(a.b, b.b), + max(a.c, b.c), + max(a.d, b.d), + max(a.e, b.e), + max(a.f, b.f), + max(a.g, b.g), + max(a.h, b.h)); +#endif +} + +ccl_device_inline float8 clamp(const float8 &a, const float8 &mn, const float8 &mx) +{ + return min(max(a, mn), mx); +} + +ccl_device_inline float8 fabs(const float8 &a) +{ +#ifdef __KERNEL_AVX2__ + return float8(_mm256_and_ps(a.m256, _mm256_castsi256_ps(_mm256_set1_epi32(0x7fffffff)))); +#else + return make_float8(fabsf(a.a), + fabsf(a.b), + fabsf(a.c), + fabsf(a.d), + fabsf(a.e), + fabsf(a.f), + fabsf(a.g), + fabsf(a.h)); +#endif +} + +ccl_device_inline float8 mix(const float8 &a, const float8 &b, float t) +{ + return a + t * (b - a); +} + +ccl_device_inline float reduce_min(const float8 &a) +{ + return min(min(min(a.a, a.b), min(a.c, a.d)), min(min(a.e, a.f), min(a.g, a.h))); +} + +ccl_device_inline float reduce_max(const float8 &a) +{ + return max(max(max(a.a, a.b), max(a.c, a.d)), max(max(a.e, a.f), max(a.g, a.h))); +} + +ccl_device_inline float reduce_add(const float8 &a) +{ +#ifdef __KERNEL_AVX2__ + float8 b(_mm256_hadd_ps(a.m256, a.m256)); + float8 h(_mm256_hadd_ps(b.m256, b.m256)); + return h[0] + h[4]; +#else + return a.a + a.b + a.c + a.d + a.e + a.f + a.g + a.h; +#endif +} + +ccl_device_inline float8 saturate(const float8 &a) +{ + return clamp(a, make_float8(0.0f), make_float8(1.0f)); +} + +ccl_device_inline bool isequal(const float8 a, const float8 b) +{ + return a == b; +} + +ccl_device_inline float8 safe_divide(const float8 a, const float b) +{ + return (b != 0.0f) ? a / b : make_float8(0.0f); +} + +ccl_device_inline float8 safe_divide(const float8 a, const float8 b) +{ + return make_float8((b.a != 0.0f) ? a.a / b.a : 0.0f, + (b.b != 0.0f) ? a.b / b.b : 0.0f, + (b.c != 0.0f) ? a.c / b.c : 0.0f, + (b.d != 0.0f) ? a.d / b.d : 0.0f, + (b.e != 0.0f) ? a.e / b.e : 0.0f, + (b.f != 0.0f) ? a.f / b.f : 0.0f, + (b.g != 0.0f) ? a.g / b.g : 0.0f, + (b.h != 0.0f) ? a.h / b.h : 0.0f); +} + +ccl_device_inline float8 ensure_finite(float8 v) +{ + v.a = ensure_finite(v.a); + v.b = ensure_finite(v.b); + v.c = ensure_finite(v.c); + v.d = ensure_finite(v.d); + v.e = ensure_finite(v.e); + v.f = ensure_finite(v.f); + v.g = ensure_finite(v.g); + v.h = ensure_finite(v.h); + + return v; +} + +ccl_device_inline bool isfinite_safe(float8 v) +{ + return isfinite_safe(v.a) && isfinite_safe(v.b) && isfinite_safe(v.c) && isfinite_safe(v.d) && + isfinite_safe(v.e) && isfinite_safe(v.f) && isfinite_safe(v.g) && isfinite_safe(v.h); +} + +ccl_device_inline float8 pow(float8 v, float e) +{ + return make_float8(powf(v.a, e), + powf(v.b, e), + powf(v.c, e), + powf(v.d, e), + powf(v.e, e), + powf(v.f, e), + powf(v.g, e), + powf(v.h, e)); +} + +ccl_device_inline float8 exp(float8 v) +{ + return make_float8( + expf(v.a), expf(v.b), expf(v.c), expf(v.d), expf(v.e), expf(v.f), expf(v.g), expf(v.h)); +} + +ccl_device_inline float8 log(float8 v) +{ + return make_float8( + logf(v.a), logf(v.b), logf(v.c), logf(v.d), logf(v.e), logf(v.f), logf(v.g), logf(v.h)); +} + +ccl_device_inline float dot(const float8 &a, const float8 &b) +{ +#ifdef __KERNEL_AVX2__ + float8 t(_mm256_dp_ps(a.m256, b.m256, 0xFF)); + return t[0] + t[4]; +#else + return (a.a * b.a) + (a.b * b.b) + (a.c * b.c) + (a.d * b.d) + (a.e * b.e) + (a.f * b.f) + + (a.g * b.g) + (a.h * b.h); +#endif +} + +CCL_NAMESPACE_END + +#endif /* __UTIL_MATH_FLOAT8_H__ */ diff --git a/intern/cycles/util/types_float8.h b/intern/cycles/util/types_float8.h index d71149946f7..f04dc675c84 100644 --- a/intern/cycles/util/types_float8.h +++ b/intern/cycles/util/types_float8.h @@ -11,11 +11,13 @@ CCL_NAMESPACE_BEGIN -#if !defined(__KERNEL_GPU__) || defined(__KERNEL_ONEAPI__) - +#ifdef __KERNEL_GPU__ +struct float8 +#else struct ccl_try_align(32) float8 +#endif { -# ifdef __KERNEL_AVX2__ +#ifdef __KERNEL_AVX2__ union { __m256 m256; struct { @@ -32,18 +34,19 @@ struct ccl_try_align(32) float8 __forceinline float8 &operator=(const float8 &a); -# else /* __KERNEL_AVX2__ */ +#else /* __KERNEL_AVX2__ */ float a, b, c, d, e, f, g, h; -# endif /* __KERNEL_AVX2__ */ +#endif /* __KERNEL_AVX2__ */ +#ifndef __KERNEL_GPU__ __forceinline float operator[](int i) const; __forceinline float &operator[](int i); +#endif }; ccl_device_inline float8 make_float8(float f); ccl_device_inline float8 make_float8(float a, float b, float c, float d, float e, float f, float g, float h); -#endif /* !defined(__KERNEL_GPU__) || defined(__KERNEL_ONEAPI__) */ CCL_NAMESPACE_END diff --git a/intern/cycles/util/types_float8_impl.h b/intern/cycles/util/types_float8_impl.h index 0694f5205a5..21931c55071 100644 --- a/intern/cycles/util/types_float8_impl.h +++ b/intern/cycles/util/types_float8_impl.h @@ -15,8 +15,7 @@ CCL_NAMESPACE_BEGIN -#if !defined(__KERNEL_GPU__) || defined(__KERNEL_ONEAPI__) -# ifdef __KERNEL_AVX2__ +#ifdef __KERNEL_AVX2__ __forceinline float8::float8() { } @@ -44,8 +43,9 @@ __forceinline float8 &float8::operator=(const float8 &f) m256 = f.m256; return *this; } -# endif /* __KERNEL_AVX2__ */ +#endif /* __KERNEL_AVX2__ */ +#ifndef __KERNEL_GPU__ __forceinline float float8::operator[](int i) const { util_assert(i >= 0); @@ -59,30 +59,29 @@ __forceinline float &float8::operator[](int i) util_assert(i < 8); return *(&a + i); } +#endif ccl_device_inline float8 make_float8(float f) { -# ifdef __KERNEL_AVX2__ +#ifdef __KERNEL_AVX2__ float8 r(_mm256_set1_ps(f)); -# else +#else float8 r = {f, f, f, f, f, f, f, f}; -# endif +#endif return r; } ccl_device_inline float8 make_float8(float a, float b, float c, float d, float e, float f, float g, float h) { -# ifdef __KERNEL_AVX2__ - float8 r(_mm256_set_ps(a, b, c, d, e, f, g, h)); -# else +#ifdef __KERNEL_AVX2__ + float8 r(_mm256_setr_ps(a, b, c, d, e, f, g, h)); +#else float8 r = {a, b, c, d, e, f, g, h}; -# endif +#endif return r; } -#endif /* !defined(__KERNEL_GPU__) || defined(__KERNEL_ONEAPI__) */ - CCL_NAMESPACE_END #endif /* __UTIL_TYPES_FLOAT8_IMPL_H__ */ -- cgit v1.2.3 From f26aa186b26f46dad08536d95b126afb42abd2f3 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Mon, 25 Jul 2022 17:38:03 +0200 Subject: Cleanup: remove __KERNEL_CPU__ This was tested in some places to check if code was being compiled for the CPU, however this is only defined in the kernel. Checking __KERNEL_GPU__ always works. --- intern/cycles/device/optix/device_impl.cpp | 1 - intern/cycles/device/optix/queue.cpp | 1 - intern/cycles/kernel/bvh/util.h | 2 +- .../cycles/kernel/closure/bsdf_hair_principled.h | 2 +- intern/cycles/kernel/device/cpu/compat.h | 2 -- intern/cycles/kernel/integrator/intersect_shadow.h | 2 +- intern/cycles/kernel/integrator/path_state.h | 2 +- intern/cycles/kernel/integrator/state.h | 6 ++--- intern/cycles/kernel/integrator/state_util.h | 2 +- intern/cycles/kernel/types.h | 26 +++++++++------------- intern/cycles/kernel/util/profiling.h | 6 ++--- intern/cycles/test/util_avxf_avx2_test.cpp | 1 - intern/cycles/test/util_avxf_avx_test.cpp | 1 - intern/cycles/util/defines.h | 2 +- intern/cycles/util/math_fast.h | 2 +- 15 files changed, 24 insertions(+), 34 deletions(-) diff --git a/intern/cycles/device/optix/device_impl.cpp b/intern/cycles/device/optix/device_impl.cpp index 11c0d1bf8a0..151983667c0 100644 --- a/intern/cycles/device/optix/device_impl.cpp +++ b/intern/cycles/device/optix/device_impl.cpp @@ -26,7 +26,6 @@ # include "util/task.h" # include "util/time.h" -# undef __KERNEL_CPU__ # define __KERNEL_OPTIX__ # include "kernel/device/optix/globals.h" diff --git a/intern/cycles/device/optix/queue.cpp b/intern/cycles/device/optix/queue.cpp index 366bf95269d..f0d49ad6f6c 100644 --- a/intern/cycles/device/optix/queue.cpp +++ b/intern/cycles/device/optix/queue.cpp @@ -8,7 +8,6 @@ # include "util/time.h" -# undef __KERNEL_CPU__ # define __KERNEL_OPTIX__ # include "kernel/device/optix/globals.h" diff --git a/intern/cycles/kernel/bvh/util.h b/intern/cycles/kernel/bvh/util.h index 02e927decd4..b67c9394bea 100644 --- a/intern/cycles/kernel/bvh/util.h +++ b/intern/cycles/kernel/bvh/util.h @@ -33,7 +33,7 @@ ccl_device_forceinline float intersection_t_offset(const float t) return __uint_as_float(bits); } -#if defined(__KERNEL_CPU__) +#ifndef __KERNEL_GPU__ ccl_device int intersections_compare(const void *a, const void *b) { const Intersection *isect_a = (const Intersection *)a; diff --git a/intern/cycles/kernel/closure/bsdf_hair_principled.h b/intern/cycles/kernel/closure/bsdf_hair_principled.h index 2cdf6c9f349..e7f24b89458 100644 --- a/intern/cycles/kernel/closure/bsdf_hair_principled.h +++ b/intern/cycles/kernel/closure/bsdf_hair_principled.h @@ -3,7 +3,7 @@ #pragma once -#ifdef __KERNEL_CPU__ +#ifndef __KERNEL_GPU__ # include #endif diff --git a/intern/cycles/kernel/device/cpu/compat.h b/intern/cycles/kernel/device/cpu/compat.h index 3bfc37e98ee..631e55e0d42 100644 --- a/intern/cycles/kernel/device/cpu/compat.h +++ b/intern/cycles/kernel/device/cpu/compat.h @@ -3,8 +3,6 @@ #pragma once -#define __KERNEL_CPU__ - /* Release kernel has too much false-positive maybe-uninitialized warnings, * which makes it possible to miss actual warnings. */ diff --git a/intern/cycles/kernel/integrator/intersect_shadow.h b/intern/cycles/kernel/integrator/intersect_shadow.h index 1b48b360858..25ff3d5b23f 100644 --- a/intern/cycles/kernel/integrator/intersect_shadow.h +++ b/intern/cycles/kernel/integrator/intersect_shadow.h @@ -51,7 +51,7 @@ ccl_device_forceinline int integrate_shadow_max_transparent_hits(KernelGlobals k } #ifdef __TRANSPARENT_SHADOWS__ -# if defined(__KERNEL_CPU__) +# ifndef __KERNEL_GPU__ ccl_device int shadow_intersections_compare(const void *a, const void *b) { const Intersection *isect_a = (const Intersection *)a; diff --git a/intern/cycles/kernel/integrator/path_state.h b/intern/cycles/kernel/integrator/path_state.h index 912c380cdb6..b09bc117d78 100644 --- a/intern/cycles/kernel/integrator/path_state.h +++ b/intern/cycles/kernel/integrator/path_state.h @@ -13,7 +13,7 @@ CCL_NAMESPACE_BEGIN ccl_device_inline void path_state_init_queues(IntegratorState state) { INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = 0; -#ifdef __KERNEL_CPU__ +#ifndef __KERNEL_GPU__ INTEGRATOR_STATE_WRITE(&state->shadow, shadow_path, queued_kernel) = 0; INTEGRATOR_STATE_WRITE(&state->ao, shadow_path, queued_kernel) = 0; #endif diff --git a/intern/cycles/kernel/integrator/state.h b/intern/cycles/kernel/integrator/state.h index d10d31e930e..d1907bd6e16 100644 --- a/intern/cycles/kernel/integrator/state.h +++ b/intern/cycles/kernel/integrator/state.h @@ -140,7 +140,7 @@ typedef struct IntegratorStateGPU { * happen from a kernel which operates on a "main" path. Attempt to use shadow catcher accessors * from a kernel which operates on a shadow catcher state will cause bad memory access. */ -#ifdef __KERNEL_CPU__ +#ifndef __KERNEL_GPU__ /* Scalar access on CPU. */ @@ -159,7 +159,7 @@ typedef const IntegratorShadowStateCPU *ccl_restrict ConstIntegratorShadowState; # define INTEGRATOR_STATE_ARRAY_WRITE(state, nested_struct, array_index, member) \ ((state)->nested_struct[array_index].member) -#else /* __KERNEL_CPU__ */ +#else /* !__KERNEL_GPU__ */ /* Array access on GPU with Structure-of-Arrays. */ @@ -180,6 +180,6 @@ typedef int ConstIntegratorShadowState; # define INTEGRATOR_STATE_ARRAY_WRITE(state, nested_struct, array_index, member) \ INTEGRATOR_STATE_ARRAY(state, nested_struct, array_index, member) -#endif /* __KERNEL_CPU__ */ +#endif /* !__KERNEL_GPU__ */ CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/integrator/state_util.h b/intern/cycles/kernel/integrator/state_util.h index 8dd58ad6bcd..168122d3a78 100644 --- a/intern/cycles/kernel/integrator/state_util.h +++ b/intern/cycles/kernel/integrator/state_util.h @@ -338,7 +338,7 @@ ccl_device_inline IntegratorState integrator_state_shadow_catcher_split(KernelGl return to_state; } -#ifdef __KERNEL_CPU__ +#ifndef __KERNEL_GPU__ ccl_device_inline int integrator_state_bounce(ConstIntegratorState state, const int) { return INTEGRATOR_STATE(state, path, bounce); diff --git a/intern/cycles/kernel/types.h b/intern/cycles/kernel/types.h index d809f6fc2bd..7762c95275e 100644 --- a/intern/cycles/kernel/types.h +++ b/intern/cycles/kernel/types.h @@ -19,10 +19,6 @@ #include "kernel/svm/types.h" -#ifndef __KERNEL_GPU__ -# define __KERNEL_CPU__ -#endif - CCL_NAMESPACE_BEGIN /* Constants */ @@ -51,10 +47,10 @@ CCL_NAMESPACE_BEGIN #define INTEGRATOR_SHADOW_ISECT_SIZE_CPU 1024U #define INTEGRATOR_SHADOW_ISECT_SIZE_GPU 4U -#ifdef __KERNEL_CPU__ -# define INTEGRATOR_SHADOW_ISECT_SIZE INTEGRATOR_SHADOW_ISECT_SIZE_CPU -#else +#ifdef __KERNEL_GPU__ # define INTEGRATOR_SHADOW_ISECT_SIZE INTEGRATOR_SHADOW_ISECT_SIZE_GPU +#else +# define INTEGRATOR_SHADOW_ISECT_SIZE INTEGRATOR_SHADOW_ISECT_SIZE_CPU #endif /* Kernel features */ @@ -91,12 +87,12 @@ CCL_NAMESPACE_BEGIN #define __BRANCHED_PATH__ /* Device specific features */ -#ifdef __KERNEL_CPU__ +#ifndef __KERNEL_GPU__ # ifdef WITH_OSL # define __OSL__ # endif # define __VOLUME_RECORD_ALL__ -#endif /* __KERNEL_CPU__ */ +#endif /* !__KERNEL_GPU__ */ /* MNEE currently causes "Compute function exceeds available temporary registers" * on Metal, disabled for now. */ @@ -722,7 +718,7 @@ typedef struct ccl_align(16) ShaderClosure { SHADER_CLOSURE_BASE; -#ifdef __KERNEL_CPU__ +#ifndef __KERNEL_GPU__ float pad[2]; #endif float data[10]; @@ -1540,15 +1536,15 @@ enum KernelFeatureFlag : uint32_t { /* Must be constexpr on the CPU to avoid compile errors because the state types * are different depending on the main, shadow or null path. For GPU we don't have * C++17 everywhere so can't use it. */ -#ifdef __KERNEL_CPU__ +#ifdef __KERNEL_GPU__ +# define IF_KERNEL_FEATURE(feature) if ((node_feature_mask & (KERNEL_FEATURE_##feature)) != 0U) +# define IF_KERNEL_NODES_FEATURE(feature) \ + if ((node_feature_mask & (KERNEL_FEATURE_NODE_##feature)) != 0U) +#else # define IF_KERNEL_FEATURE(feature) \ if constexpr ((node_feature_mask & (KERNEL_FEATURE_##feature)) != 0U) # define IF_KERNEL_NODES_FEATURE(feature) \ if constexpr ((node_feature_mask & (KERNEL_FEATURE_NODE_##feature)) != 0U) -#else -# define IF_KERNEL_FEATURE(feature) if ((node_feature_mask & (KERNEL_FEATURE_##feature)) != 0U) -# define IF_KERNEL_NODES_FEATURE(feature) \ - if ((node_feature_mask & (KERNEL_FEATURE_NODE_##feature)) != 0U) #endif CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/util/profiling.h b/intern/cycles/kernel/util/profiling.h index 39cabd35967..b8afaf1166d 100644 --- a/intern/cycles/kernel/util/profiling.h +++ b/intern/cycles/kernel/util/profiling.h @@ -3,13 +3,13 @@ #pragma once -#ifdef __KERNEL_CPU__ +#ifndef __KERNEL_GPU__ # include "util/profiling.h" #endif CCL_NAMESPACE_BEGIN -#ifdef __KERNEL_CPU__ +#ifndef __KERNEL_GPU__ # define PROFILING_INIT(kg, event) \ ProfilingHelper profiling_helper((ProfilingState *)&kg->profiler, event) # define PROFILING_EVENT(event) profiling_helper.set_event(event) @@ -22,6 +22,6 @@ CCL_NAMESPACE_BEGIN # define PROFILING_EVENT(event) # define PROFILING_INIT_FOR_SHADER(kg, event) # define PROFILING_SHADER(object, shader) -#endif /* __KERNEL_CPU__ */ +#endif /* !__KERNEL_GPU__ */ CCL_NAMESPACE_END diff --git a/intern/cycles/test/util_avxf_avx2_test.cpp b/intern/cycles/test/util_avxf_avx2_test.cpp index c50fb8cd75c..992c4d9a913 100644 --- a/intern/cycles/test/util_avxf_avx2_test.cpp +++ b/intern/cycles/test/util_avxf_avx2_test.cpp @@ -2,7 +2,6 @@ * Copyright 2011-2022 Blender Foundation */ #define __KERNEL_AVX2__ -#define __KERNEL_CPU__ #define TEST_CATEGORY_NAME util_avx2 diff --git a/intern/cycles/test/util_avxf_avx_test.cpp b/intern/cycles/test/util_avxf_avx_test.cpp index b6a5491bcf1..abb98cdfb38 100644 --- a/intern/cycles/test/util_avxf_avx_test.cpp +++ b/intern/cycles/test/util_avxf_avx_test.cpp @@ -2,7 +2,6 @@ * Copyright 2011-2022 Blender Foundation */ #define __KERNEL_AVX__ -#define __KERNEL_CPU__ #define TEST_CATEGORY_NAME util_avx diff --git a/intern/cycles/util/defines.h b/intern/cycles/util/defines.h index 115a747cf1c..d0df1a221fc 100644 --- a/intern/cycles/util/defines.h +++ b/intern/cycles/util/defines.h @@ -81,7 +81,7 @@ /* macros */ /* hints for branch prediction, only use in code that runs a _lot_ */ -#if defined(__GNUC__) && defined(__KERNEL_CPU__) +#if defined(__GNUC__) && !defined(__KERNEL_GPU__) # define LIKELY(x) __builtin_expect(!!(x), 1) # define UNLIKELY(x) __builtin_expect(!!(x), 0) #else diff --git a/intern/cycles/util/math_fast.h b/intern/cycles/util/math_fast.h index 2221e7a9835..142a664a1d2 100644 --- a/intern/cycles/util/math_fast.h +++ b/intern/cycles/util/math_fast.h @@ -420,7 +420,7 @@ ccl_device_inline float fast_expf(float x) return fast_exp2f(x / M_LN2_F); } -#if defined(__KERNEL_CPU__) && !defined(_MSC_VER) +#if !defined(__KERNEL_GPU__) && !defined(_MSC_VER) /* MSVC seems to have a code-gen bug here in at least SSE41/AVX, see * T78047 and T78869 for details. Just disable for now, it only makes * a small difference in denoising performance. */ -- cgit v1.2.3 From 739136caca5cbe73d0478583d44a3d0d36137b42 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 25 Jul 2022 11:53:06 -0500 Subject: Fix: Assert in resample curve node with single point curve --- source/blender/geometry/intern/resample_curves.cc | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/source/blender/geometry/intern/resample_curves.cc b/source/blender/geometry/intern/resample_curves.cc index 29e358cc3f4..86c1980d9ee 100644 --- a/source/blender/geometry/intern/resample_curves.cc +++ b/source/blender/geometry/intern/resample_curves.cc @@ -233,10 +233,18 @@ static Curves *resample_to_uniform(const CurveComponent &src_component, for (const int i_curve : sliced_selection) { const bool cyclic = curves_cyclic[i_curve]; const IndexRange dst_points = dst_curves.points_for_curve(i_curve); - length_parameterize::sample_uniform(src_curves.evaluated_lengths_for_curve(i_curve, cyclic), - !curves_cyclic[i_curve], - sample_indices.as_mutable_span().slice(dst_points), - sample_factors.as_mutable_span().slice(dst_points)); + const Span lengths = src_curves.evaluated_lengths_for_curve(i_curve, cyclic); + if (lengths.is_empty()) { + /* Handle curves with only one evaluated point. */ + sample_indices.as_mutable_span().slice(dst_points).fill(0); + sample_factors.as_mutable_span().slice(dst_points).fill(0.0f); + } + else { + length_parameterize::sample_uniform(lengths, + !curves_cyclic[i_curve], + sample_indices.as_mutable_span().slice(dst_points), + sample_factors.as_mutable_span().slice(dst_points)); + } } /* For every attribute, evaluate attributes from every curve in the range in the original -- cgit v1.2.3 From 00a3533429133f3be782a4e7d19938a0898f0d29 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 25 Jul 2022 11:59:33 -0500 Subject: Curves: Unify poll functions, add message with no surface The "snap to surface" operators now have "disabled" poll messages when there is no surface object. The implementation in most curves operators is also unified. The goal is to avoid having to define and use the poll failure messages in multiple places, to reduce the boilerplate that tends to be necessary to add an operator, and to increase the likelihood that operators are implemented with proper poll messages. Differential Revision: https://developer.blender.org/D15528 --- source/blender/editors/curves/intern/curves_ops.cc | 92 +++++++++++----------- source/blender/editors/include/ED_curves.h | 6 +- .../editors/sculpt_paint/curves_sculpt_ops.cc | 31 ++------ 3 files changed, 56 insertions(+), 73 deletions(-) diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc index f4d1c8046f1..740e53b59f8 100644 --- a/source/blender/editors/curves/intern/curves_ops.cc +++ b/source/blender/editors/curves/intern/curves_ops.cc @@ -94,6 +94,47 @@ VectorSet get_unique_editable_curves(const bContext &C) return unique_curves; } +static bool curves_poll_impl(bContext *C, const bool check_editable, const bool check_surface) +{ + Object *object = CTX_data_active_object(C); + if (object == nullptr || object->type != OB_CURVES) { + return false; + } + if (check_editable) { + if (!ED_operator_object_active_editable_ex(C, object)) { + return false; + } + } + if (check_surface) { + Curves &curves = *static_cast(object->data); + if (curves.surface == nullptr || curves.surface->type != OB_MESH) { + CTX_wm_operator_poll_msg_set(C, "Curves must have a mesh surface object set"); + return false; + } + } + return true; +} + +bool editable_curves_with_surface_poll(bContext *C) +{ + return curves_poll_impl(C, true, true); +} + +bool curves_with_surface_poll(bContext *C) +{ + return curves_poll_impl(C, false, true); +} + +bool editable_curves_poll(bContext *C) +{ + return curves_poll_impl(C, false, false); +} + +bool curves_poll(bContext *C) +{ + return curves_poll_impl(C, false, false); +} + using bke::CurvesGeometry; namespace convert_to_particle_system { @@ -337,16 +378,6 @@ static int curves_convert_to_particle_system_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } -static bool curves_convert_to_particle_system_poll(bContext *C) -{ - Object *ob = CTX_data_active_object(C); - if (ob == nullptr || ob->type != OB_CURVES) { - return false; - } - Curves &curves = *static_cast(ob->data); - return curves.surface != nullptr; -} - } // namespace convert_to_particle_system static void CURVES_OT_convert_to_particle_system(wmOperatorType *ot) @@ -355,7 +386,7 @@ static void CURVES_OT_convert_to_particle_system(wmOperatorType *ot) ot->idname = "CURVES_OT_convert_to_particle_system"; ot->description = "Add a new or update an existing hair particle system on the surface object"; - ot->poll = convert_to_particle_system::curves_convert_to_particle_system_poll; + ot->poll = curves_with_surface_poll; ot->exec = convert_to_particle_system::curves_convert_to_particle_system_exec; ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; @@ -501,22 +532,6 @@ enum class AttachMode { Deform, }; -static bool snap_curves_to_surface_poll(bContext *C) -{ - Object *ob = CTX_data_active_object(C); - if (ob == nullptr || ob->type != OB_CURVES) { - return false; - } - if (!ED_operator_object_active_editable_ex(C, ob)) { - return false; - } - Curves &curves = *static_cast(ob->data); - if (curves.surface == nullptr) { - return false; - } - return true; -} - static void snap_curves_to_surface_exec_object(Object &curves_ob, const Object &surface_ob, const AttachMode attach_mode, @@ -694,7 +709,7 @@ static void CURVES_OT_snap_curves_to_surface(wmOperatorType *ot) ot->idname = "CURVES_OT_snap_curves_to_surface"; ot->description = "Move curves so that the first point is exactly on the surface mesh"; - ot->poll = snap_curves_to_surface_poll; + ot->poll = editable_curves_with_surface_poll; ot->exec = snap_curves_to_surface_exec; ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; @@ -723,21 +738,6 @@ static void CURVES_OT_snap_curves_to_surface(wmOperatorType *ot) "How to find the point on the surface to attach to"); } -bool selection_operator_poll(bContext *C) -{ - const Object *object = CTX_data_active_object(C); - if (object == nullptr) { - return false; - } - if (object->type != OB_CURVES) { - return false; - } - if (!BKE_id_is_editable(CTX_data_main(C), static_cast(object->data))) { - return false; - } - return true; -} - namespace set_selection_domain { static int curves_set_selection_domain_exec(bContext *C, wmOperator *op) @@ -791,7 +791,7 @@ static void CURVES_OT_set_selection_domain(wmOperatorType *ot) ot->description = "Change the mode used for selection masking in curves sculpt mode"; ot->exec = set_selection_domain::curves_set_selection_domain_exec; - ot->poll = selection_operator_poll; + ot->poll = editable_curves_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -827,7 +827,7 @@ static void CURVES_OT_disable_selection(wmOperatorType *ot) ot->description = "Disable the drawing of influence of selection in sculpt mode"; ot->exec = disable_selection::curves_disable_selection_exec; - ot->poll = selection_operator_poll; + ot->poll = editable_curves_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } @@ -933,7 +933,7 @@ static void SCULPT_CURVES_OT_select_all(wmOperatorType *ot) ot->description = "(De)select all control points"; ot->exec = select_all::select_all_exec; - ot->poll = selection_operator_poll; + ot->poll = editable_curves_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; diff --git a/source/blender/editors/include/ED_curves.h b/source/blender/editors/include/ED_curves.h index 0817241a5c2..00831ff7cc3 100644 --- a/source/blender/editors/include/ED_curves.h +++ b/source/blender/editors/include/ED_curves.h @@ -26,10 +26,14 @@ void ED_operatortypes_curves(void); namespace blender::ed::curves { bke::CurvesGeometry primitive_random_sphere(int curves_size, int points_per_curve); -bool selection_operator_poll(bContext *C); bool has_anything_selected(const Curves &curves_id); VectorSet get_unique_editable_curves(const bContext &C); void ensure_surface_deformation_node_exists(bContext &C, Object &curves_ob); +bool editable_curves_with_surface_poll(bContext *C); +bool curves_with_surface_poll(bContext *C); +bool editable_curves_poll(bContext *C); +bool curves_poll(bContext *C); + } // namespace blender::ed::curves #endif diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc index 9271309d922..02ebd622469 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc @@ -269,18 +269,6 @@ static void SCULPT_CURVES_OT_brush_stroke(struct wmOperatorType *ot) /** \name * CURVES_OT_sculptmode_toggle * \{ */ -static bool curves_sculptmode_toggle_poll(bContext *C) -{ - const Object *ob = CTX_data_active_object(C); - if (ob == nullptr) { - return false; - } - if (ob->type != OB_CURVES) { - return false; - } - return true; -} - static void curves_sculptmode_enter(bContext *C) { Scene *scene = CTX_data_scene(C); @@ -342,7 +330,7 @@ static void CURVES_OT_sculptmode_toggle(wmOperatorType *ot) ot->description = "Enter/Exit sculpt mode for curves"; ot->exec = curves_sculptmode_toggle_exec; - ot->poll = curves_sculptmode_toggle_poll; + ot->poll = curves::curves_poll; ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; } @@ -485,7 +473,7 @@ static void SCULPT_CURVES_OT_select_random(wmOperatorType *ot) ot->description = "Randomizes existing selection or create new random selection"; ot->exec = select_random::select_random_exec; - ot->poll = curves::selection_operator_poll; + ot->poll = curves::editable_curves_poll; ot->ui = select_random::select_random_ui; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -529,7 +517,7 @@ static void SCULPT_CURVES_OT_select_random(wmOperatorType *ot) namespace select_end { static bool select_end_poll(bContext *C) { - if (!curves::selection_operator_poll(C)) { + if (!curves::editable_curves_poll(C)) { return false; } const Curves *curves_id = static_cast(CTX_data_active_object(C)->data); @@ -911,7 +899,7 @@ static void SCULPT_CURVES_OT_select_grow(wmOperatorType *ot) ot->invoke = select_grow::select_grow_invoke; ot->modal = select_grow::select_grow_modal; - ot->poll = curves::selection_operator_poll; + ot->poll = curves::editable_curves_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -932,16 +920,7 @@ namespace min_distance_edit { static bool min_distance_edit_poll(bContext *C) { - Object *ob = CTX_data_active_object(C); - if (ob == nullptr) { - return false; - } - if (ob->type != OB_CURVES) { - return false; - } - Curves *curves_id = static_cast(ob->data); - if (curves_id->surface == nullptr || curves_id->surface->type != OB_MESH) { - CTX_wm_operator_poll_msg_set(C, "Curves must have a mesh surface object set"); + if (!curves::curves_with_surface_poll(C)) { return false; } Scene *scene = CTX_data_scene(C); -- cgit v1.2.3 From 703dff333ce6cbc25c990042858a0c40f9a212cb Mon Sep 17 00:00:00 2001 From: Germano Cavalcante Date: Mon, 25 Jul 2022 13:41:35 -0300 Subject: Fix T99459: GPencil: Fill tool on the surface not in the correct place There is a 1 pixel error in the size registered for the buffer dimensions. NOTE: This issue indicates that the texture scale is different from the region, so the mouse-based coordinates used are actually misaligned. This misalignment will be fixed in another commit. Regression probably introduced in rB1d49293b8044 + rB45f167237f0c8 --- source/blender/editors/space_view3d/view3d_draw.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/blender/editors/space_view3d/view3d_draw.c b/source/blender/editors/space_view3d/view3d_draw.c index 5405867be56..df5ff163cf2 100644 --- a/source/blender/editors/space_view3d/view3d_draw.c +++ b/source/blender/editors/space_view3d/view3d_draw.c @@ -2247,16 +2247,16 @@ void view3d_depths_rect_create(ARegion *region, rcti *rect, ViewDepths *r_d) static ViewDepths *view3d_depths_create(ARegion *region) { ViewDepths *d = MEM_callocN(sizeof(ViewDepths), "ViewDepths"); - d->w = region->winx; - d->h = region->winy; { GPUViewport *viewport = WM_draw_region_get_viewport(region); GPUTexture *depth_tx = GPU_viewport_depth_texture(viewport); uint32_t *int_depths = GPU_texture_read(depth_tx, GPU_DATA_UINT_24_8, 0); + d->w = GPU_texture_width(depth_tx); + d->h = GPU_texture_height(depth_tx); d->depths = (float *)int_depths; /* Convert in-place. */ - int pixel_count = GPU_texture_width(depth_tx) * GPU_texture_height(depth_tx); + int pixel_count = d->w * d->h; for (int i = 0; i < pixel_count; i++) { d->depths[i] = (int_depths[i] >> 8u) / (float)0xFFFFFF; } -- cgit v1.2.3 From 46dbfce7fc59cd93566251b6cc7a2d8521c991bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Mon, 25 Jul 2022 19:11:29 +0200 Subject: Cycles: Nishita Sky: Fix sun disk imprecision for large elevation The issue was introduced by rBad5e3d30a2d2 which made possible to use unbounded elevation angle. In order to not touch the shading code, we just remap the value to the expected range the shading code expects. This means that elevation angles above +/-PI/2 effectively flip the sun rotation angle. --- intern/cycles/blender/shader.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/intern/cycles/blender/shader.cpp b/intern/cycles/blender/shader.cpp index 81a64457c88..4218a9a8a68 100644 --- a/intern/cycles/blender/shader.cpp +++ b/intern/cycles/blender/shader.cpp @@ -928,8 +928,22 @@ static ShaderNode *add_node(Scene *scene, sky->set_sun_disc(b_sky_node.sun_disc()); sky->set_sun_size(b_sky_node.sun_size()); sky->set_sun_intensity(b_sky_node.sun_intensity()); - sky->set_sun_elevation(b_sky_node.sun_elevation()); - sky->set_sun_rotation(b_sky_node.sun_rotation()); + /* Patch sun position to be able to animate daylight cycle while keeping the shading code + * simple. */ + float sun_rotation = b_sky_node.sun_rotation(); + /* Wrap into [-2PI..2PI] range. */ + float sun_elevation = fmodf(b_sky_node.sun_elevation(), M_2PI_F); + /* Wrap into [-PI..PI] range. */ + if (fabsf(sun_elevation) >= M_PI_F) { + sun_elevation -= copysignf(2.0f, sun_elevation) * M_PI_F; + } + /* Wrap into [-PI/2..PI/2] range while keeping the same absolute position. */ + if (sun_elevation >= M_PI_2_F || sun_elevation <= -M_PI_2_F) { + sun_elevation = copysignf(M_PI_F, sun_elevation) - sun_elevation; + sun_rotation += M_PI_F; + } + sky->set_sun_elevation(sun_elevation); + sky->set_sun_rotation(sun_rotation); sky->set_altitude(b_sky_node.altitude()); sky->set_air_density(b_sky_node.air_density()); sky->set_dust_density(b_sky_node.dust_density()); -- cgit v1.2.3 From fb9f12eeec9f04e8b715d5c21bcbd8cc67117eba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Mon, 25 Jul 2022 19:23:46 +0200 Subject: UI: Nishita sky: Increase Sun Elevation UI sensitivity and remove min/max This now use default angle precision which matches the sun rotation. Feeling is much more natural. --- source/blender/makesrna/intern/rna_nodetree.c | 1 - 1 file changed, 1 deletion(-) diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 80217decb13..c2d17f73023 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -5282,7 +5282,6 @@ static void def_sh_tex_sky(StructRNA *srna) prop = RNA_def_property(srna, "sun_elevation", PROP_FLOAT, PROP_ANGLE); RNA_def_property_ui_text(prop, "Sun Elevation", "Sun angle from horizon"); - RNA_def_property_ui_range(prop, -M_PI, M_PI, 1, 2); RNA_def_property_float_default(prop, M_PI_2); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); -- cgit v1.2.3 From 462f99bf38648a08226b1fba423315aec2bc577b Mon Sep 17 00:00:00 2001 From: Joseph Eagar Date: Mon, 25 Jul 2022 11:52:07 -0700 Subject: Sculpt: Fix T99779, pbvh gets wrong active vertex for multires The recent multires winding fix missed a code branch. --- source/blender/blenkernel/intern/pbvh.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/blenkernel/intern/pbvh.c b/source/blender/blenkernel/intern/pbvh.c index 00a4eee47e3..6cebcdfea4e 100644 --- a/source/blender/blenkernel/intern/pbvh.c +++ b/source/blender/blenkernel/intern/pbvh.c @@ -2424,7 +2424,7 @@ static bool pbvh_grids_node_raycast(PBVH *pbvh, madd_v3_v3v3fl(location, ray_start, ray_normal, *depth); const int x_it[4] = {0, 1, 1, 0}; - const int y_it[4] = {0, 0, 1, 1}; + const int y_it[4] = {1, 1, 0, 0}; for (int j = 0; j < 4; j++) { /* Always assign nearest_vertex_co in the first iteration to avoid comparison against -- cgit v1.2.3