Spec 070: from-blank pen mesh authoring + re-editable outline#166
Spec 070: from-blank pen mesh authoring + re-editable outline#166danko-nobre wants to merge 1 commit into
Conversation
…pec 070) A New Pen Mesh button creates a mesh element from a chosen image (the Spine/Moho model: the mesh overlays a texture region) and opens the 066 contour pen on a blank outline to draw it point by point. The clicked outer anchors persist (proscenio_authored_outer_contour), so re-launching an applied mesh reloads them for editing instead of re-tracing the alpha silhouette. The launch mode (re-edit / blank / trace) resolves via a bpy-free helper; re-edit wins over a stale from-blank flag. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds a ChangesPen Mesh New + Authored Outer Re-edit
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/blender/operators/automesh/automesh_authoring.py`:
- Around line 904-910: The authored contour is being persisted too early in
_commit_contour(), before the OUTER stage is finalized, so a later switch back
to auto can still leave a stale re-edit key behind. Update the commit flow
around _commit_contour() and the OUTER handling so
write_authored_outer_contour() is called only when APPLY actually commits the
contour-driven outer, or explicitly clear the authored-contour state whenever
the OUTER path falls back to auto.
- Around line 208-212: The class flag _launch_from_blank in AutomeshAuthoring
can persist into later launches because it is only cleared on the successful
invoke path. Update AutomeshAuthoring.invoke to reset this flag before any
poll/mesh/image validation guards, and ensure the caller in pen_mesh_new uses
try/finally to clear it even when invoke exits early. If possible, avoid relying
on cross-invocation class state for launch mode selection and keep the
blank/trace decision local to the current invocation.
- Around line 333-345: The re-edit branch in automesh_authoring.py restores
authored_outer into _output.outer, but _enter_draw() clears _pen_points so the
contour tool starts with no editable anchors. Update the re-edit flow in the
_active_tool = "contour" path to seed the pen state from authored_outer before
calling _enter_draw(), so _pen_finish() continues editing the saved outline
instead of creating a fresh line. Keep the blank-launch branch unchanged and
make the fix in the same logic that handles authored_outer / launch_mode.
In `@apps/blender/operators/automesh/pen_mesh_new.py`:
- Around line 70-83: The pen-mesh launch helper only searches the current
screen, so it can miss an already-open 3D viewport when running from the file
browser. Update the override lookup in the blank-pen flow to iterate through
context.window_manager.windows, find the first window containing a VIEW_3D area
and WINDOW region, and use that with context.temp_override before calling
PROSCENIO_OT_automesh_authoring. Keep the existing fallback return False
behavior if no suitable viewport is found.
In `@apps/blender/tests/operators/test_automesh_authoring.py`:
- Around line 460-476: The test in create_pen_mesh_element is only verifying the
default from element_type_of(), so it will still pass even if
_tag_element_type() is no longer called. Update the assertion around
create_pen_mesh_element and PROSCENIO_IMPORT_PLACEMENT to check the persisted
element-type tag directly on the created object, so the test validates that the
tag is actually stored rather than inferred.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 75ff4806-549b-4242-bfec-3b7c258fbd9f
📒 Files selected for processing (9)
apps/blender/core/_shared/cp_keys.pyapps/blender/core/bpy_helpers/automesh/authoring_pipeline.pyapps/blender/core/skinning/authoring_stages.pyapps/blender/operators/automesh/__init__.pyapps/blender/operators/automesh/automesh_authoring.pyapps/blender/operators/automesh/pen_mesh_new.pyapps/blender/panels/mesh_generation.pyapps/blender/tests/operators/test_automesh_authoring.pytests/skinning/test_authoring_stages.py
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
- GitHub Check: SonarCloud
⚠️ CI failures not shown inline (2)
GitHub Actions: CI / test-blender: Spec 070: from-blank pen mesh authoring + re-editable outline
Conclusion: failure
##[group]Run ~/blender/blender --background \
�[36;1m~/blender/blender --background \�[0m
�[36;1m --python packages/validator/scripts/validate_automesh.py \�[0m
�[36;1m -- --ci-only�[0m
shell: /usr/bin/bash -e {0}
##[endgroup]
Traceback (most recent call last):
File "/home/runner/work/proscenio/proscenio/packages/validator/scripts/validate_automesh.py", line 50, in <module>
from proscenio_validator.cli import main # noqa: E402
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/proscenio/proscenio/packages/validator/src/proscenio_validator/cli.py", line 16, in <module>
from .measurement import load_fixture, run_validation
File "/home/runner/work/proscenio/proscenio/packages/validator/src/proscenio_validator/measurement.py", line 18, in <module>
from .coverage import measure_coverage
File "/home/runner/work/proscenio/proscenio/packages/validator/src/proscenio_validator/coverage.py", line 17, in <module>
from core.geometry_2d import point_in_triangle_xz
ModuleNotFoundError: No module named 'core.geometry_2d'
Blender 5.1.1 (hash b70da489d7f4 built )
Blender quit
GitHub Actions: CI / 1_test-blender.txt: Spec 070: from-blank pen mesh authoring + re-editable outline
Conclusion: failure
Current runner version: '2.335.1'
##[group]Runner Image Provisioner
Hosted Compute Agent
Version: 20260611.554
Commit: 5e0782fdc9014723d3be820dd114dd31555c2bd1
Build Date:
Worker ID: {a512383a-a081-401f-a560-f9e7a1a97ca6}
Azure Region: westus
##[endgroup]
##[group]Operating System
Ubuntu
24.04.4
LTS
##[endgroup]
##[group]Runner Image
Image: ubuntu-24.04
Version: 20260622.220.1
Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20260622.220/images/ubuntu/Ubuntu2404-Readme.md
Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20260622.220
##[endgroup]
##[group]GITHUB_TOKEN Permissions
Contents: read
Metadata: read
##[endgroup]
Secret source: Actions
Prepare workflow directory
Prepare all required actions
Getting action download info
Download action repository 'actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0' (SHA:9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0)
Download action repository 'actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8' (SHA:2c8a9bd7457de244a408f35966fab2fb45fda9c8)
Complete job name: test-blender
##[group]Run actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
with:
lfs: true
repository: firebound/proscenio
***REDACTED***
ssh-strict: true
ssh-user: git
persist-credentials: true
clean: true
sparse-checkout-cone-mode: true
fetch-depth: 1
fetch-tags: false
show-progress: true
submodules: false
set-safe-directory: true
allow-unsafe-pr-checkout: false
##[endgroup]
Syncing repository: firebound/proscenio
##[group]Getting Git version info
Working directory is '/home/runner/work/proscenio/proscenio'
[command]/usr/bin/git version
git version 2.54.0
[command]/usr/bin/git lfs version
git-lfs/3.7.1 (GitHub; linux amd64; go 1.24.4)
##[endgroup]
Temporarily overriding HOME='/home/runner/work/_temp/7c5da913-1ef7-4a3d-a20c-934c42d30fd8' before making global git config changes
Adding repository directory to the te...
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2026-06-17T16:09:35.107Z
Learnt from: danko-nobre
Repo: firebound/proscenio PR: 123
File: apps/blender/panels/skeleton.py:0-0
Timestamp: 2026-06-17T16:09:35.107Z
Learning: In the Proscenio Blender add-on panel code under `apps/blender/panels/`, `draw_header` and `draw_header_preset` implementations must read `context.region` from their callback `context` parameter (e.g., `context.region`), and must not use global `bpy.context.region`. This avoids incorrect region targeting in multi-editor layouts where `bpy.context` may refer to a different area/region. Likewise, `draw_subpanel_header` in `apps/blender/panels/_helpers.py` should be called with an explicit `context` argument, since that `context` must be threaded through by every `draw_header_preset` caller.
Applied to files:
apps/blender/panels/mesh_generation.py
📚 Learning: 2026-06-11T17:28:22.365Z
Learnt from: danko-nobre
Repo: firebound/proscenio PR: 107
File: apps/blender/operators/skinning/sidecar_io.py:1-1
Timestamp: 2026-06-11T17:28:22.365Z
Learning: In the proscenio firebound/proscenio Blender app tests (apps/blender/tests/operators/**/*.py), avoid writing asserts like `assert <very_long_boolean_expression>, "msg"` that depend on ruff version–specific line wrapping. Instead, extract the long boolean expression into a named variable and then assert on that variable (e.g., `cond = <long expr>; assert cond, "msg"`) so ruff pre-commit vs CI produces identical formatting/output.
Applied to files:
apps/blender/tests/operators/test_automesh_authoring.py
📚 Learning: 2026-06-25T06:48:46.567Z
Learnt from: danko-nobre
Repo: firebound/proscenio PR: 163
File: apps/blender/tests/operators/test_import_placement.py:19-28
Timestamp: 2026-06-25T06:48:46.567Z
Learning: In Blender operator test files (e.g., under apps/blender/tests/operators), ensure the test harness fresh-loads the fixture .blend for each individual test. This forces Blender to reset bpy.data between tests, bounding any temporary datablock/datablock accumulation created by helpers and preventing cross-test contamination/leaks.
Applied to files:
apps/blender/tests/operators/test_automesh_authoring.py
🪛 ast-grep (0.44.0)
apps/blender/core/bpy_helpers/automesh/authoring_pipeline.py
[info] 204-204: use jsonify instead of json.dumps for JSON output
Context: json.dumps([[float(x), float(y)] for x, y in points])
Note: [CWE-116] Improper Encoding or Escaping of Output.
(use-jsonify)
🔇 Additional comments (7)
apps/blender/core/_shared/cp_keys.py (1)
94-98: LGTM!apps/blender/core/bpy_helpers/automesh/authoring_pipeline.py (1)
16-18: LGTM!Also applies to: 190-205
apps/blender/core/skinning/authoring_stages.py (1)
85-102: LGTM!tests/skinning/test_authoring_stages.py (1)
11-11: LGTM!Also applies to: 126-134
apps/blender/operators/automesh/__init__.py (1)
7-24: LGTM!apps/blender/panels/mesh_generation.py (1)
48-52: LGTM!apps/blender/tests/operators/test_automesh_authoring.py (1)
481-501: LGTM!
| # Set by ``proscenio.pen_mesh_new`` right before it launches this modal on a | ||
| # freshly created (empty, no alpha to trace) mesh element: the next invoke | ||
| # starts from a blank outer with the contour pen armed and the SIMPLE stage | ||
| # list, instead of alpha-tracing. Read-and-cleared in invoke (spec 070). | ||
| _launch_from_blank: ClassVar[bool] = False |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
_launch_from_blank can leak into the next unrelated launch.
apps/blender/operators/automesh/pen_mesh_new.py:62-83 arms this class flag before invoking the modal, but it is only cleared here after the early validation path. If poll fails or invoke() exits on the mesh/image guards, the flag stays True, and the next normal authoring launch can resolve to "blank" instead of "trace". Clear it before the guards and reset it in the caller with try/finally, or stop using cross-invocation class state here.
Also applies to: 249-258
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/blender/operators/automesh/automesh_authoring.py` around lines 208 -
212, The class flag _launch_from_blank in AutomeshAuthoring can persist into
later launches because it is only cleared on the successful invoke path. Update
AutomeshAuthoring.invoke to reset this flag before any poll/mesh/image
validation guards, and ensure the caller in pen_mesh_new uses try/finally to
clear it even when invoke exits early. If possible, avoid relying on
cross-invocation class state for launch mode selection and keep the blank/trace
decision local to the current invocation.
| if authored_outer is not None: | ||
| # Re-edit: load the stored anchors as the outer ring and arm the | ||
| # contour pen so the artist can redraw / extend it (add points in | ||
| # EDIT_OUTLINE), instead of re-tracing the alpha silhouette. | ||
| self._output.outer = list(authored_outer) | ||
| self._active_tool = "contour" | ||
| self._enter_draw(context, "stroke") | ||
| elif launch_mode == "blank": | ||
| # From blank: nothing to trace; arm the contour pen on an empty | ||
| # outer so the first click drops the first vert. | ||
| self._output.outer = [] | ||
| self._active_tool = "contour" | ||
| self._enter_draw(context, "stroke") |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Re-edit still starts from an empty contour.
_enter_draw() clears _pen_points, and this branch only copies authored_outer into _output.outer. On reopen the old ring is visible, but the contour tool itself has no seeded anchors, so _pen_finish() can only commit a brand-new line. Seed the pen state from authored_outer here so reopening actually edits the saved outline.
Suggested fix
if authored_outer is not None:
# Re-edit: load the stored anchors as the outer ring and arm the
# contour pen so the artist can redraw / extend it (add points in
# EDIT_OUTLINE), instead of re-tracing the alpha silhouette.
self._output.outer = list(authored_outer)
self._active_tool = "contour"
self._enter_draw(context, "stroke")
+ self._pen_points[:] = list(authored_outer)
+ self._pen_active = True📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if authored_outer is not None: | |
| # Re-edit: load the stored anchors as the outer ring and arm the | |
| # contour pen so the artist can redraw / extend it (add points in | |
| # EDIT_OUTLINE), instead of re-tracing the alpha silhouette. | |
| self._output.outer = list(authored_outer) | |
| self._active_tool = "contour" | |
| self._enter_draw(context, "stroke") | |
| elif launch_mode == "blank": | |
| # From blank: nothing to trace; arm the contour pen on an empty | |
| # outer so the first click drops the first vert. | |
| self._output.outer = [] | |
| self._active_tool = "contour" | |
| self._enter_draw(context, "stroke") | |
| if authored_outer is not None: | |
| # Re-edit: load the stored anchors as the outer ring and arm the | |
| # contour pen so the artist can redraw / extend it (add points in | |
| # EDIT_OUTLINE), instead of re-tracing the alpha silhouette. | |
| self._output.outer = list(authored_outer) | |
| self._active_tool = "contour" | |
| self._enter_draw(context, "stroke") | |
| self._pen_points[:] = list(authored_outer) | |
| self._pen_active = True | |
| elif launch_mode == "blank": | |
| # From blank: nothing to trace; arm the contour pen on an empty | |
| # outer so the first click drops the first vert. | |
| self._output.outer = [] | |
| self._active_tool = "contour" | |
| self._enter_draw(context, "stroke") |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/blender/operators/automesh/automesh_authoring.py` around lines 333 -
345, The re-edit branch in automesh_authoring.py restores authored_outer into
_output.outer, but _enter_draw() clears _pen_points so the contour tool starts
with no editable anchors. Update the re-edit flow in the _active_tool =
"contour" path to seed the pen state from authored_outer before calling
_enter_draw(), so _pen_finish() continues editing the saved outline instead of
creating a fresh line. Keep the blank-launch branch unchanged and make the fix
in the same logic that handles authored_outer / launch_mode.
| # Persist the user's clicked anchors (pre-subdivision) so a later launch | ||
| # reloads this outline for re-editing instead of re-tracing the alpha | ||
| # silhouette (spec 070). The ring above is the subdivided render/build | ||
| # geometry; the anchors are the editable handles. | ||
| obj = context.active_object | ||
| if obj is not None: | ||
| write_authored_outer_contour(obj, list(pts)) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
The authored-outline CP is written before the final outer is chosen.
_commit_contour() persists the re-edit key immediately, but the OUTER stage can still switch back to "auto" on Lines 531-546 and apply the traced silhouette instead. Nothing clears the authored-contour CP on that path, so the next reopen resolves to "reedit" and reloads a contour the user already backed out of. Persist this only when APPLY commits a contour-driven outer, or clear it whenever OUTER returns to auto.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/blender/operators/automesh/automesh_authoring.py` around lines 904 -
910, The authored contour is being persisted too early in _commit_contour(),
before the OUTER stage is finalized, so a later switch back to auto can still
leave a stale re-edit key behind. Update the commit flow around
_commit_contour() and the OUTER handling so write_authored_outer_contour() is
called only when APPLY actually commits the contour-driven outer, or explicitly
clear the authored-contour state whenever the OUTER path falls back to auto.
| screen = context.screen | ||
| if screen is None: | ||
| return False | ||
| for area in screen.areas: | ||
| if area.type != "VIEW_3D": | ||
| continue | ||
| region = next((r for r in area.regions if r.type == "WINDOW"), None) | ||
| if region is None: | ||
| continue | ||
| PROSCENIO_OT_automesh_authoring._launch_from_blank = True | ||
| with context.temp_override(window=context.window, area=area, region=region): | ||
| bpy.ops.proscenio.automesh_authoring("INVOKE_DEFAULT") | ||
| return True | ||
| return False |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Search all Blender windows for the VIEW_3D override.
Line 70 only walks context.screen, but after fileselect_add() this operator can be executing on the file-browser screen. In that case the loop never sees the main window's VIEW_3D, so the new-pen flow falls back to the manual “Author Mesh” path even though a viewport is already open. Iterate context.window_manager.windows and override into the first VIEW_3D window instead.
Suggested fix
def _launch_authoring_from_blank(context: bpy.types.Context) -> bool:
@@
- screen = context.screen
- if screen is None:
- return False
- for area in screen.areas:
- if area.type != "VIEW_3D":
- continue
- region = next((r for r in area.regions if r.type == "WINDOW"), None)
- if region is None:
- continue
- PROSCENIO_OT_automesh_authoring._launch_from_blank = True
- with context.temp_override(window=context.window, area=area, region=region):
- bpy.ops.proscenio.automesh_authoring("INVOKE_DEFAULT")
- return True
+ for window in context.window_manager.windows:
+ screen = window.screen
+ if screen is None:
+ continue
+ for area in screen.areas:
+ if area.type != "VIEW_3D":
+ continue
+ region = next((r for r in area.regions if r.type == "WINDOW"), None)
+ if region is None:
+ continue
+ PROSCENIO_OT_automesh_authoring._launch_from_blank = True
+ with context.temp_override(window=window, area=area, region=region):
+ bpy.ops.proscenio.automesh_authoring("INVOKE_DEFAULT")
+ return True
return False📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| screen = context.screen | |
| if screen is None: | |
| return False | |
| for area in screen.areas: | |
| if area.type != "VIEW_3D": | |
| continue | |
| region = next((r for r in area.regions if r.type == "WINDOW"), None) | |
| if region is None: | |
| continue | |
| PROSCENIO_OT_automesh_authoring._launch_from_blank = True | |
| with context.temp_override(window=context.window, area=area, region=region): | |
| bpy.ops.proscenio.automesh_authoring("INVOKE_DEFAULT") | |
| return True | |
| return False | |
| for window in context.window_manager.windows: | |
| screen = window.screen | |
| if screen is None: | |
| continue | |
| for area in screen.areas: | |
| if area.type != "VIEW_3D": | |
| continue | |
| region = next((r for r in area.regions if r.type == "WINDOW"), None) | |
| if region is None: | |
| continue | |
| PROSCENIO_OT_automesh_authoring._launch_from_blank = True | |
| with context.temp_override(window=window, area=area, region=region): | |
| bpy.ops.proscenio.automesh_authoring("INVOKE_DEFAULT") | |
| return True | |
| return False |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/blender/operators/automesh/pen_mesh_new.py` around lines 70 - 83, The
pen-mesh launch helper only searches the current screen, so it can miss an
already-open 3D viewport when running from the file browser. Update the override
lookup in the blank-pen flow to iterate through context.window_manager.windows,
find the first window containing a VIEW_3D area and WINDOW region, and use that
with context.temp_override before calling PROSCENIO_OT_automesh_authoring. Keep
the existing fallback return False behavior if no suitable viewport is found.
| from proscenio.core._shared.props_access import ( # type: ignore[import-not-found] | ||
| element_type_of, | ||
| ) | ||
| from proscenio.operators.automesh.pen_mesh_new import ( # type: ignore[import-not-found] | ||
| create_pen_mesh_element, | ||
| ) | ||
|
|
||
| img_path = Path(tmp_path) / "pen_src.png" | ||
| img = bpy.data.images.new("pen_src", width=64, height=32) | ||
| img.filepath_raw = str(img_path) | ||
| img.file_format = "PNG" | ||
| img.save() | ||
|
|
||
| obj = create_pen_mesh_element(bpy.context, img_path, "PenTest") | ||
| assert obj.type == "MESH" | ||
| assert element_type_of(obj) == "mesh" | ||
| assert PROSCENIO_IMPORT_PLACEMENT in obj |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Assert the persisted element-type tag here.
element_type_of() defaults to "mesh" when the property is missing, so Line 475 still passes even if create_pen_mesh_element() stops calling _tag_element_type(). Check the stored tag directly so this test actually protects the new contract.
Suggested fix
- from proscenio.core._shared.props_access import ( # type: ignore[import-not-found]
- element_type_of,
- )
@@
- assert element_type_of(obj) == "mesh"
+ props = getattr(obj, "proscenio", None)
+ if props is not None:
+ assert props.element_type == "mesh"
+ assert obj.get("proscenio_type") == "mesh"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| from proscenio.core._shared.props_access import ( # type: ignore[import-not-found] | |
| element_type_of, | |
| ) | |
| from proscenio.operators.automesh.pen_mesh_new import ( # type: ignore[import-not-found] | |
| create_pen_mesh_element, | |
| ) | |
| img_path = Path(tmp_path) / "pen_src.png" | |
| img = bpy.data.images.new("pen_src", width=64, height=32) | |
| img.filepath_raw = str(img_path) | |
| img.file_format = "PNG" | |
| img.save() | |
| obj = create_pen_mesh_element(bpy.context, img_path, "PenTest") | |
| assert obj.type == "MESH" | |
| assert element_type_of(obj) == "mesh" | |
| assert PROSCENIO_IMPORT_PLACEMENT in obj | |
| from proscenio.operators.automesh.pen_mesh_new import ( # type: ignore[import-not-found] | |
| create_pen_mesh_element, | |
| ) | |
| img_path = Path(tmp_path) / "pen_src.png" | |
| img = bpy.data.images.new("pen_src", width=64, height=32) | |
| img.filepath_raw = str(img_path) | |
| img.file_format = "PNG" | |
| img.save() | |
| obj = create_pen_mesh_element(bpy.context, img_path, "PenTest") | |
| assert obj.type == "MESH" | |
| props = getattr(obj, "proscenio", None) | |
| if props is not None: | |
| assert props.element_type == "mesh" | |
| assert obj.get("proscenio_type") == "mesh" | |
| assert PROSCENIO_IMPORT_PLACEMENT in obj |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/blender/tests/operators/test_automesh_authoring.py` around lines 460 -
476, The test in create_pen_mesh_element is only verifying the default from
element_type_of(), so it will still pass even if _tag_element_type() is no
longer called. Update the assertion around create_pen_mesh_element and
PROSCENIO_IMPORT_PLACEMENT to check the persisted element-type tag directly on
the created object, so the test validates that the tag is actually stored rather
than inferred.
The first draft misread the feature (image-picker / from-blank element creation / persisted re-edit). The real feature is the SIMPLE contour placed by hand on a selected element, with the live triangulation preview the auto-gen already shows: LMB place, RMB drag verts, DEL last, ENTER apply, ESC cancel. Upgrades spec 066's manual contour tool rather than creating elements. Marks PR #166 (the wrong build) for discard.



Implements spec 070 (mesh-pen-authoring), locked to the Spine / Moho precedent.
What
proscenio.pen_mesh_new: pick an image, it creates a mesh element (the imported-mesh shape: UV quad + image material + placement tag +element_type="mesh") and opens the spec 066 contour pen on a blank outline to draw point by point over the image.proscenio_authored_outer_contour; re-launching an applied pen mesh reloads them (and arms the contour pen) instead of re-tracing the alpha silhouette. "Add points in the middle" then rides the existing EDIT_OUTLINE extend tool.resolve_launch_mode; re-edit wins over a stale from-blank flag.Reuse
The 066 modal does the heavy lifting (click-pen, bare-Tab tool cycle, CDT triangulation, APPLY) unchanged. New surface: one create-and-launch operator, one persistence field, two invoke branches.
Tests / gates
resolve_launch_mode(tests/skinning).create_pen_mesh_elementbuilds a valid element.Notes / follow-ups
pen_mesh_newreuses the photoshop importer's private_ensure_mesh/_attach_material/_tag_element_type; a later refactor could promote them to a shared element-build home.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes