Anim Picker 2.0#646
Merged
Merged
Conversation
…loses #598 Behavior-preserving cleanup of the anim_picker module (internal version -> 2.0.0), the base for the planned decomposition and feature work. - Storage: picker data on the PICKER_DATAS node is now clean JSON (json.dumps/ json.loads) instead of a stringified Python literal read back with eval(); no arbitrary code execution on load. BREAKING: pickers stored only in a scene node with no external .pkr are not migrated and must be re-exported (.pkr files and file-backed pickers are unaffected). - Fix #598 "Picker shape from curve not working": store the real tab name on the extraction group so the round-trip survives Maya node renaming (default -> default1, spaces, duplicates) instead of silently dropping edited data; guard shape-less transforms during curves->picker; remove the json true->True replace hack that corrupted names/paths containing "true"; warn on unmatched tabs. - Fix latent bugs: .format() typo, a setAttr that never hid the data node, an assertion that never fired on referenced nodes, an always-false dict type check. - Python 3 only: drop __future__ imports, super(Class, self) -> super(), remove module-reload NameError guards. - Logging via mgear.log instead of print/sys.stderr (user-facing menu prints kept). - SelectionCheck migrated from Maya API 1.0 to maya.api.OpenMaya (API 2.0). - Remove commented-out PySide2 import blocks, stale TODOs, and dead code.
…mData (Phase 2) Behavior-preserving decomposition of the two anim_picker God-files into focused modules, on top of the Phase 1 cleanup. Public API, importable names, Shifter's MAYA_OVERRIDE_COLOR, and the .pkr / PICKER_DATAS formats are unchanged. - gui.py (2361 -> 40 lines, shim) split into constants.py, scene.py, view.py, tab_widget.py, main_window.py. - widgets/picker_widgets.py (2942 -> 53 lines, shim) split into widgets/graphics.py, widgets/picker_item.py, and widgets/dialogs/ (script_dialog, search_replace_dialog, handles_window, item_options, copy_paste_dialog). - New Qt/Maya-free widgets/item_model.py (PickerItemData); PickerItem.get_data / set_data delegate to its to_dict / from_dict, preserving the exact schema round-trip. - gui.py and picker_widgets.py kept as compatibility shims re-exporting the same objects (isinstance identity preserved). Import cycles broken with lazy imports (copy_paste_dialog -> PickerItem, tab_widget -> MainDockWindow). - Curve<->picker conversion kept in view.py; picker_curves.py extraction deferred.
…nvas #108 A tab background is now an ordered list of image layers instead of a single image. Each layer carries its own path, position (center), and size, drawn back-to-front. The scene rect spans the union of the background layers AND the picker items, floored at the default canvas, so pan and zoom reach all content and backgrounds can be larger and non-proportional (the former 6000 cap is removed). - New Qt/Maya-free BackgroundLayer model (widgets/background_model.py) as the serialization authority; get_data/set_data delegate to it. - BackgroundOptionsDialog becomes a layer manager (add/remove/reorder, per-layer position and size). - Maya curve round-trip creates one image plane per layer and reads them back. - Legacy single background/background_size pickers still load (mapped to one centered layer); writes emit a "backgrounds" list.
…-select With the Background layers panel open (edit mode), background layers can be manipulated directly on the canvas: click / shift-click / marquee-select layers, drag the body to move, drag the bounding-box handles to scale (corner = both axes, edge = one, Shift keeps aspect). Multi-selected layers transform together as a group about the opposite handle; the panel selection and X/Y/W/H fields stay in sync. - widgets/background_transform.py: Qt/Maya-free group move/scale math (union_bounds, move_layers, scale_factors, scale_layers), unit-tested. - widgets/background_manipulator.py: BackgroundManipulator controller (selection, hit-test, overlay paint at constant screen size, drag lifecycle). - view.py: background-edit sub-mode routes mouse events to the manipulator and paints the overlay in drawForeground; panel-sync accessors. - BackgroundOptionsDialog: multi-selection, two-way canvas/panel sync, opens and closes the sub-mode.
Add a reachable, working dockable picker. A new "Anim Picker (Dockable)" menu entry launches the window as a Maya workspaceControl through the shared mgear.core.pyqt.showDialog helper (the same path the other dockable tools use), with a partial supplying the edit/dockable args. MainDockableWindow now forwards edit/dockable to MainDockWindow (they were dropped, leaving is_dockable False). Only the dockable window carries the object name, so Maya names its workspaceControl deterministically and the floating window no longer shares that identity -- fixing the cases where opening both swapped the opacity UI into the docked window, depended on open order, or where closing the floating window closed the docked one. close() only tears down the workspaceControl when the window is dockable. The floating window remains the default.
…and left tool strip Add roadmap 6a of the anim_picker 2.0 creation/edit UX overhaul. - Shared Qt-free manipulator_transform (handle points, scale factors, rotate helpers); background_transform now re-exports from it. - item_manipulator: bounding-box overlay with scale handles + a rotate handle; group scale about the opposite anchor (Shift = uniform) and rigid rotate about the group center; single item rotates in place. - edit_panel: right-docked, collapsible, edit-mode-only inline editor for the whole selection (Transform/Appearance/Shape/Controls/Action) with mixed-value-aware fields; reuses PickerItem setters and dialogs. - tool_bar: Photoshop-style left strip with an exclusive Select (default) / Transform tool group; the manipulator is opt-in via the Transform tool. add_command hook reserved for future quick commands. - Route double-click / Options to the inline panel (modal kept as a fallback). Fix: drag-moving a selected group no longer collapses the selection to the item under the cursor.
… picker script editor - mgear.core.pycodeeditor.PythonCodeEditor: QPlainTextEdit with a line number gutter, current-line highlight, Python syntax highlighting, configurable monospace font (Consolas default), spaces-or-tabs indent with configurable width, show-whitespace, convert-tabs-to-spaces, and smart Tab / Shift-Tab + auto-indent on Enter. Prefs persist via QSettings. DCC-agnostic (no Maya import) so any tool can embed it. - anim_picker custom script / menu dialog now embeds it, with a File / Edit / View menu bar (open/save scripts, edit ops, convert to spaces, show indentation) and a font / indentation toolbar.
…te, and tool-strip commands Roadmap 6b of the anim_picker 2.0 creation/edit UX overhaul. - Qt-free mirror math (widgets/mirror.py: reflect position / rotation / handles); PickerItem.mirror_* refactored onto it. - Persistent, realtime live mirror relationships about a symmetry axis: optional additive item-data keys id / mirror (old pickers load unchanged), lazy pair resolution + dangling cleanup, a _mirroring loop guard, a cached has-links flag, and mirroring on the manipulator drag, the plain item drag-move, and panel edits. Linked items get a pink dotted shape outline; a symmetry-axis guide is drawn. - Left tool-strip quick commands with bundled mGear icons (Add, Duplicate, Duplicate & Mirror, Mirror Shape, Shapes); Select / Transform tools use default Maya icons. - Shape library: Qt-free widgets/shape_library.py (named handle-lists, bundled default_shapes.json + per-user JSON in the Maya prefs dir with legacy migration) + a swatch-grid dialog; apply premade / save custom. - Explicit L/R color palette bar (widgets/color_palette.py): 6 role swatches, click applies (partner gets the opposite side, same level), double / right-click edits (persisted); color is no longer auto-swapped on a live geometry mirror. - Edit-mode UX: clicking an item selects it on press (single click-drag selects + moves) while preserving group drag, Shift/Ctrl, marquee, Alt control-select, and anim-mode behavior.
Add pinned picker items that lock to the viewport and ignore canvas pan and zoom, staying at a fixed screen position and constant size (HUD-like) - e.g. a global reset, a space switch, or a settings button locked to a corner. A pinned item is placed by a 3x3 anchor (corners / edges / center) plus an inward pixel offset and keeps all its normal button behavior. - New Qt/Maya-free widgets/overlay.py: anchor_point / offset_from_anchor / nearest_anchor (3x3 anchor + inward offset math), standalone-tested. - PickerItem gains pinned / anchor / offset state; set_pinned toggles ItemIgnoresTransformations (constant screen size) with a scale(1,-1) compensation so shape / text stay upright in the Y-flipped view. - Additive optional item keys (pinned / anchor / offset), emitted only when pinned, so older pickers load unchanged and non-pinned items are unaffected. - view._update_pinned_items re-anchors pins on every pan / zoom / resize / fit / load; pins are excluded from the scene extent and fit bounds via scene.content_bounding_rect so a HUD never enlarges the canvas or gets framed by reset view. In edit mode a pinned item can be dragged to set its offset (the anchor snaps to the nearest region). - edit_panel Pin section (Pinned checkbox, 3x3 anchor picker, X / Y offset, mixed-value aware) and a tool-strip Pin quick command. - Refactor: shared _set_tristate / _resolve_tristate panel helpers.
Add two tab-presentation improvements to the picker. Tabs can be dragged to reorder on the tab bar in edit mode (ContextMenuTabWidget.setMovable), the right-click Move actions kept as a fallback; the order saves exactly as before (get_data is positional). A View selector (Tabbed / Grid / Rows / Columns) in the character bar switches the picker area between the one-at-a-time tabbed view and a multi-tab tiled view that shows every tab's picker at once. Each tiled section is a fully live, interactive picker, laid out in nested draggable QSplitters (minimal dividers) so the user configures each picker's relative space; no per-cell title/frame is drawn. - New Qt/Maya-free widgets/tiled_layout.py: grid_shape(count, columns) -> (rows, cols), standalone-tested (near-square / vertical / horizontal). - New widgets/tiled_view.py: TiledPickerView (borrows the live views into splitters, tracks the active view via a viewport click) and PickerTabArea (facade presenting the tabs as tabbed or tiled; routes active view / all views / data / fit so callers work in either mode). - ContextMenuTabWidget.borrow_views / restore_views + shared tab_data_entry (one serialization authority for both presentations). - main_window routes _current_view / selection sync / data / fit through the area; forces tabbed before load; hands the whole splitter to the picker area when the inline panel is hidden; persists the mode + grid columns via QSettings (never in the .pkr, so older pickers load unchanged).
…s and align tools Interactive widgets: a picker item can be a checkbox, 1D slider or 2D slider bound to a Maya attribute and/or a per-state script (state/value exposed as __STATE__/__VALUE__/__X__/__Y__), reflecting the bound value on the existing selection-change refresh. New Qt-free widget_binding + safe attr binding handler; modern compact rendering (grooved track, fixed round handle/knob, upright checkbox); drag-to-canvas palette (Button/Checkbox/Slider/2D/Backdrop) with double-click-to-create; seeded "print value" default scripts; script editor documents the widget vars and focuses the code area. Control trace: a tool-strip Trace command builds one silhouette button per selected control (front/side/top convex-hull projection via Qt-free silhouette.py + OM2 shape-point extraction), colored from the control, centered on the viewport, auto-linking _L/_R pairs as mirrors (convertRLName). Backdrop containers: additive backdrop/title/corner_radius keys, a rounded or straight translucent panel drawn behind the buttons with a clipped title bar, that moves its geometrically-contained items together (nesting supported); inner nested backdrops stay selectable via smallest-under-cursor resolution. Align + distribute: a left-strip Align section (Qt-free alignment.py) aligns edges/centers and distributes by bounding box, fanning a stack of overlapping items into an evenly spaced row/column. Also: per-item text alignment (center/top/bottom/left/right + offset) and viewport-pinned items keep their apparent size. All persistence is additive optional keys, so older pickers load unchanged.
The fixed-size slider / 2D-slider knob overhangs a thin track, but WidgetGraphic.boundingRect() stopped at the item rect, so the knob's overhang at the previous position was never repainted -- leaving a ghost of the old knob after a drag. boundingRect now pads by the knob radius so the whole knob (at any position) is cleared on repaint; shape() (hit testing) stays the plain item rect.
…e + zoom level) A picker item can now be shown only when a condition passes in animation mode, so a busy picker declutters itself. Two condition modes: channel-state (a Maya attribute compared to a threshold, e.g. IK controls only when arm_L_ctl.ikBlend >= 0.5) and zoom-level (view zoom within a range, e.g. fine detail controls only when zoomed in). Conditions re-evaluate on demand without polling -- zoom conditions on every zoom/fit, channel conditions on selection change and on mouse-over the picker (like the Channel Master tool: hovering re-reads the bound attributes so a manual or animated channel change is picked up without focus, and also re-syncs interactive checkbox/slider widgets). There is no per-playback-frame refresh, so a heavy rig is not slowed. Edit mode always shows every item so a condition never blocks authoring, and a missing/malformed condition fails open (stays visible). New Qt-free widgets/visibility.py owns the pure evaluation (unit-testable standalone); PickerItem bridges the safe namespaced attribute read and setVisible; the view owns the timing and a cached "any conditioned item?" gate, mirroring the pinned-item pattern. Authored from a new Visibility section in the inline edit panel (mode + attribute/operator/threshold or min/max zoom with a "capture current zoom" helper). Persisted as one additive optional item key (visibility), emitted only when set, so older pickers load unchanged and unconditioned items are unaffected.
…ial import A picker item can now be a real vector shape -- smooth curves and holes, not just a straight-line polygon or circle. Drop an .svg file on the canvas in edit mode (or use the Import SVG command), and it becomes one curved item at the drop position, editable like any item: select on its silhouette, color, mirror, scale and rotate (the transform is baked into the vector geometry, the analog of moving polygon handles). Import is gracefully partial: the supported geometry (path with full curve/arc commands, rect, circle, ellipse, polygon, polyline, line, nested in g groups with transforms and viewBox) is kept, while unsupported elements (text, images, gradients, filters, clip/mask, use, style, script, animation) are discarded and reported. A mixed file still imports its shapes and lists what it dropped; an all-unsupported file creates nothing and says why; a malformed file never raises. Vector shapes render filled or as lines of an adjustable thickness (stroke); the mode is auto-detected from the SVG's fill/stroke on import (line-art comes in as strokes, so it is not filled to nothing) and is switchable per item in the Shape panel. Vector items hide the polygon "show handles" option and show an "SVG" badge on hover instead of the border highlight. The SVG parser ships as a reusable, Qt/Maya-free mgear.core.svg_import (subpaths + suggested render mode; a flip_y option for y-up views) so other tools can use it. Persisted as one additive optional item key (svg), so older pickers load unchanged and polygon items are unaffected; the existing curve<->picker conversion is kept.
…lays On a high-DPI display (4K / Retina, or Windows / Maya UI scaling at 150% / 200%) the picker's on-canvas item labels and edit-mode handles stayed at their small 96-DPI size while the window chrome grew, leaving them hard to read and hard to hit. They now scale with the display through the same mgear.core.pyqt.dpi_scale the chrome already uses. Scene text (item label, edit-mode handle index, backdrop title, vector "SVG" badge) and screen-fixed affordances (point handles, item / background transform manipulator pick + rotate radii) grow with the display DPI. Sizes that already scale with the canvas zoom (widget groove / knob, backdrop bar, badge box, fit margin, polygon default) are left as-is so nothing is enlarged twice. Consolidated the scattered raw point-size / pixel literals into named constants routed through one cached DPI seam. The scaling is a no-op at 100% (standard-DPI pickers render exactly as before, since dpi_scale clamps the factor to [1x, 2x]), and the authored text size is stored display-independently (GraphicText keeps the authored point size separate from the DPI-scaled font), so a picker made on one monitor renders correctly on another.
…tcuts, and expand/contract tools Add a single snapshot-based undo/redo stack (widgets/edit_undo.py) that every edit-mode operation records to via the view's baseline diff: add/delete/dup/ paste/cut, move/scale/rotate/align/distribute/expand/contract/nudge, color/ text/shape/point/pin/mirror/widget/backdrop/visibility/svg/control/menu edits, and z-order. A whole gesture (drag, manipulator) is one step; restore uses the existing item serialization (no data-format change). Replaces the move-only undo_move_order. Adds focus-scoped keyboard shortcuts (Ctrl+Z/Shift+Z/Y, C/V/X, D, Shift+D, Delete/Backspace, Ctrl+A, arrows, F, Esc) and keeps edit-mode keyboard focus on the canvas so they fire after clicking an item. Also adds the Align strip's expand/contract spacing tools (fine-tune the spread of a selection, backed by alignment.scale_spread_offsets).
…, and create-from-selection Extend the shape library to hold polygon and vector (SVG) shapes, split across Polygons / SVG tabs with previews that render real curves (shared graphics.build_vector_path). Ship 16 bundled templates (gear, face, hand, foot, 2-/4-dir arrows, rounded square/rect, heart, plus, minus, check, cross, ring, eye, lock). Add a tool-strip library button (grouped with Add/SVG, always enabled), drag-a-tile-to-create at the drop point, and right-click create-from-selection (row/column, one linked item per Maya control). Save current shape stores polygon or imported-SVG shapes (live selection) into the matching tab. All create ops are single undo steps. Vector items show no dead polygon handles (Toggle handles is a no-op for them); removed the redundant right-click Options entry (item options live in the inline panel). Extracts a shared DragTileButton (palette + shape tiles) and promotes the vector path builder to graphics.build_vector_path.
A checkbox widget can show / hide a named group of picker items directly in the picker (no rig attribute required). Items gain an additive 'group' tag; the checkbox binding gains 'visibility_group' + 'visibility_invert'. The view caches an 'any group controller?' gate, resolves each controller to whether it shows its group (checked XOR invert), and passes a group-hidden set into each item's evaluate_visibility(zoom, group_hidden) -- composing group AND the item's own condition (edit mode shows all). Consistent on load and the existing selection / hover / zoom refresh, and immediate on toggle. Authoring: a Group field in the Visibility section and a checkbox 'Controls group' + invert, undoable via the panel seam. Additive optional keys keep older pickers unaffected.
The editor-undo restore applied the before-snapshot via set_data, which is partial (only sets the optional additive keys the dict carries) and so never cleared a key the item gained since the snapshot -- undoing the addition of a group tag / visibility condition / SVG shape / pin / widget type / backdrop / text / controls / menus / mirror link left it behind. The restore now first calls PickerItem.clear_keys_absent_from(data), which resets each optional key the restored data lacks, guarded to run only when the item has that key (a plain move / property undo does no extra work). Redo is symmetric.
… click-through Rework the long-broken 'Enable opacity passthrough' option into a predictable window mask. When the floating picker is transparent (opacity < 100%) with Auto opacity off, the window is masked down to the item buttons + tabs so every gap is see-through and click-through to the viewport behind. - Build the mask by rendering the view's visible items to a transparent QImage (view.render with the canvas background cleared, so orientation and strokes match the display), then a QRegion from its alpha. Cut on the real painted pixels, so vector/stroke SVGs and rounded backdrops follow their silhouette and hidden group items stay passthrough. - Additively boost alpha before masking so thin antialiased strokes (arrows, open SVGs) survive createAlphaMask's coverage threshold. - Rebuild per frame while active (via _notify_passthrough at the view's transform sites) so the holes track pan/zoom with no ghosting; the rebuild short-circuits cheaply when passthrough is off. - Add a top-right 'move' grip to drag the window while click-through. - Remove the old event-filter passthrough that made the whole window click-through and required holding Shift. - Selection/hover item border is now a cosmetic (constant-screen-width) outline so it stays readable when zoomed far out.
The 'SVG' badge on a hovered vector item is an authoring cue, so it should not appear in animation mode. Gate VectorGraphic's badge paint on a new lazy _edit_mode_active() helper (imports __EDIT_MODE__ only when called, so graphics.py stays importable without Maya, matching the _dpi helper). Hovering a vector item in anim mode now shows nothing; edit mode still shows the badge.
…, clean edges Refine the opacity-passthrough feature into a per-window, in-UI control. - In-UI toggle: a 'Passthrough' checkbox left of Sync Namespace, plus a floating one by the move grip while the character-selector row is masked out; both stay in sync. Checking an opaque window drops it to a transparent default so it engages in one click (last transparency remembered); Auto opacity is turned off (mutually exclusive). - Per-window: replace the global mgear_ap_passthrough_OV option var + the 'Enable opacity passthrough' menu item with a per-instance flag, so enabling passthrough on one open picker no longer affects the others. - Motion: during a pan / zoom the mask is dropped (the full window returns and moves smoothly) and re-applied once motion settles via a short timer, instead of reshaping the window every frame -- fixes the redraw glitch. - Edges: erode the 1-bit mask by 1px so the antialiased item edge (blended into the dark canvas) no longer shows as a dark hairline.
New Sphinx page (animPickerUserDocumentation.rst, linked from the index) covering the 2.0 rewrite: opening modes, the animation window + opacity passthrough, edit mode (tool strip, auto-build-from-rig trace, align, drag-to-add), the shape library, and every Item Editor panel (transform, appearance, shape, controls, action, widgets, backdrop, visibility, pin, mirror), plus the palette and .pkr / PICKER_DATAS storage. Ships the 22 screenshots under images/animpicker/.
…/065/066) Behavior-preserving cleanup of the passthrough control / motion code. - SB-066: drop the _wheel_zooming flag -- a wheel flags itself as a brief zoom (zoom_active, saved/restored), and one _view_in_motion() helper is the single 'mid-gesture' check for pan / drag-zoom / wheel. - SB-065: extract _apply_passthrough_opacity() so _toggle_passthrough reads as intent and the opacity-slider / auto-button coupling lives in one helper. - SB-064: unify the in-row and floating passthrough checkboxes' creation through a _make_passthrough_checkbox() factory (both mirror the single _passthrough_enabled state).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Full rewrite of the Anim Picker (internal version 2.0.0).
Features: flexible/composite tab backgrounds (#108), on-canvas
background + item manipulators, dockable mode, mirror + color palette,
shape library (polygon + SVG) with drag/create-from-selection,
vector (SVG) shapes, interactive widgets (checkbox/slider/2D-slider),
control-trace auto-build, viewport pins, conditional visibility +
checkbox group master-toggle, editor undo/redo + focus-scoped hotkeys,
HDPI text/handle scaling, expand/contract spacing, and a per-window
opacity-passthrough HUD.
Docs: new Anim Picker 2.0 user documentation page + screenshots.
Backward compatible via additive optional keys; picker data stored as
clean JSON on PICKER_DATAS (breaking only for scene-node-only pickers
from very old versions — re-export once).