Skip to content

Spec 070: from-blank pen mesh authoring + re-editable outline#166

Closed
danko-nobre wants to merge 1 commit into
mainfrom
feat/070-mesh-pen-authoring
Closed

Spec 070: from-blank pen mesh authoring + re-editable outline#166
danko-nobre wants to merge 1 commit into
mainfrom
feat/070-mesh-pen-authoring

Conversation

@danko-nobre

@danko-nobre danko-nobre commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Implements spec 070 (mesh-pen-authoring), locked to the Spine / Moho precedent.

What

  • New Pen Mesh button (Mesh Generation panel) -> 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.
  • Re-editable outline: the clicked outer anchors persist on 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.
  • The launch mode (re-edit / blank / trace) resolves through a bpy-free 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

  • Pure: resolve_launch_mode (tests/skinning).
  • In-Blender: authored-contour round-trip + create_pen_mesh_element builds a valid element.
  • ruff + mypy (210) + repo pytest (881) + operator suite (242) + goldens (8/8) green locally.

Notes / follow-ups

  • pen_mesh_new reuses the photoshop importer's private _ensure_mesh / _attach_material / _tag_element_type; a later refactor could promote them to a shared element-build home.
  • Live vertex-grab editing of existing anchors is out of scope (the contour pen is add-only); re-edit = reload the outline + redraw/extend.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a New Pen Mesh option to start creating a pen mesh from an image.
    • Pen mesh creation now opens the authoring flow in a blank-start mode when appropriate.
    • Previously drawn outer contours can now be restored for re-editing instead of being retraced.
  • Bug Fixes

    • Improved launch behavior so the authoring flow chooses the correct start mode based on existing contour data.

…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>
@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a pen_mesh_new operator that creates a textured quad mesh element from an image file and immediately opens the authoring modal in "from blank" mode. Extends the authoring modal to persist authored outer-contour anchor points to a new custom property (PROSCENIO_AUTHORED_OUTER_CONTOUR) and restore them on re-launch, selecting between reedit, blank, or trace start modes via a new resolve_launch_mode helper.

Changes

Pen Mesh New + Authored Outer Re-edit

Layer / File(s) Summary
CP key and authoring pipeline read/write helpers
apps/blender/core/_shared/cp_keys.py, apps/blender/core/bpy_helpers/automesh/authoring_pipeline.py
Defines PROSCENIO_AUTHORED_OUTER_CONTOUR constant and adds read_authored_outer_contour / write_authored_outer_contour to serialize anchor points to/from a custom property, returning None for absent or degenerate payloads.
resolve_launch_mode helper and tests
apps/blender/core/skinning/authoring_stages.py, tests/skinning/test_authoring_stages.py
Adds resolve_launch_mode(has_authored_outer, from_blank) returning "reedit" > "blank" > "trace" with matching tests covering all three branches.
automesh_authoring modal invoke and outer setup
apps/blender/operators/automesh/automesh_authoring.py
Adds class-level _launch_from_blank flag; invoke reads authored anchors and routes to resolve_launch_mode; initial outer setup branches into reedit/blank/trace paths; manual contour commit now calls write_authored_outer_contour.
pen_mesh_new operator and panel wiring
apps/blender/operators/automesh/pen_mesh_new.py, apps/blender/operators/automesh/__init__.py, apps/blender/panels/mesh_generation.py
New PROSCENIO_OT_pen_mesh_new operator opens a file picker, creates a textured mesh element via create_pen_mesh_element, then invokes the authoring modal in blank mode; registered in __init__ and exposed as a "New Pen Mesh" button in the mesh generation panel.
Tests for create_pen_mesh_element and contour persistence
apps/blender/tests/operators/test_automesh_authoring.py
Adds tests asserting correct mesh element creation from a PNG and round-trip correctness of authored outer contour read/write, including degenerate payload handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • firebound/proscenio#162: Adds the manual contour pen and ring generation in automesh_authoring.py; this PR extends that by persisting authored anchors and adding reedit launch mode on top of it.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: from-blank pen mesh authoring with a re-editable outline.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/070-mesh-pen-authoring
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/070-mesh-pen-authoring

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9dbac04 and 0ce9765.

📒 Files selected for processing (9)
  • apps/blender/core/_shared/cp_keys.py
  • apps/blender/core/bpy_helpers/automesh/authoring_pipeline.py
  • apps/blender/core/skinning/authoring_stages.py
  • apps/blender/operators/automesh/__init__.py
  • apps/blender/operators/automesh/automesh_authoring.py
  • apps/blender/operators/automesh/pen_mesh_new.py
  • apps/blender/panels/mesh_generation.py
  • apps/blender/tests/operators/test_automesh_authoring.py
  • tests/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

View job details

##[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

View job details

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!

Comment on lines +208 to +212
# 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Comment on lines +333 to +345
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")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
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.

Comment on lines +904 to +910
# 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))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Comment on lines +70 to +83
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
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.

Comment on lines +460 to +476
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
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.

danko-nobre added a commit that referenced this pull request Jun 28, 2026
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.
@danko-nobre danko-nobre deleted the feat/070-mesh-pen-authoring branch June 28, 2026 04:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant