Compare commits
74 Commits
ee46d98b93
...
122f609156
| Author | SHA1 | Date | |
|---|---|---|---|
| 122f609156 | |||
| 4a3981a1e7 | |||
| 531f7a4dfb | |||
| 0e0287d340 | |||
| 076a8c90f1 | |||
| 2811c7bf3a | |||
| 0aeb61f818 | |||
| ac7f5a7f6d | |||
| efd117dd9b | |||
| c681d51fd1 | |||
| 78bbff6351 | |||
| c62ec2cd48 | |||
| 7f69630371 | |||
| 6ca084ee4a | |||
| 7f878a2f3f | |||
| cf512dc013 | |||
| 474fc10f0f | |||
| 2dfa9b2add | |||
| 94130ff38f | |||
| 4035f5c9e4 | |||
| 591e62085a | |||
| ab282e4fdf | |||
| dc000e50b1 | |||
| 09778c4b48 | |||
| 1bf858b17e | |||
| a0dd6e4a88 | |||
| 2a00ed0ee6 | |||
| 18799e8828 | |||
| 938d95db0f | |||
| e8268504c2 | |||
| 5b2dc59f79 | |||
| dd32ccdc44 | |||
| 31074bd511 | |||
| e8129fe495 | |||
| 8ae3c1d5b7 | |||
| 489003c2cc | |||
| 27ca25224a | |||
| 1622afa7a2 | |||
| f48bbb3d05 | |||
| 89087fc2e2 | |||
| f5aff50b13 | |||
| 4c902ed7db | |||
| 196b964efc | |||
| 7dec3b7ca4 | |||
| a0adf79db0 | |||
| 7e55af6722 | |||
| 1994d38ccd | |||
| bbaa4bff46 | |||
| bdbde1a10d | |||
| a6041cbe03 | |||
| 8d279e9ab3 | |||
| 5a011b3b57 | |||
| 7f6e47432e | |||
| 2ff7b04f56 | |||
| 4d4b892d64 | |||
| c97b4a3e41 | |||
| 14d63001d2 | |||
| e7aeeb21d4 | |||
| 377eee43f5 | |||
| 87d7721128 | |||
| 08309f719d | |||
| 32bcd45d7b | |||
| 7d39fe8c5e | |||
| 6c8358f92f | |||
| 2b79a342b0 | |||
| 0cf04a13c6 | |||
| 1613bd3194 | |||
| f42cba1932 | |||
| 988332548e | |||
| af5603045c | |||
| b5a2f9690c | |||
| 8afcf48ddf | |||
| 4e1231790b | |||
| 13ef705226 |
156
.codex/skills/openspec-apply-change/SKILL.md
Normal file
156
.codex/skills/openspec-apply-change/SKILL.md
Normal file
@@ -0,0 +1,156 @@
|
||||
---
|
||||
name: openspec-apply-change
|
||||
description: Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks.
|
||||
license: MIT
|
||||
compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.3.1"
|
||||
---
|
||||
|
||||
Implement tasks from an OpenSpec change.
|
||||
|
||||
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||
|
||||
**Steps**
|
||||
|
||||
1. **Select the change**
|
||||
|
||||
If a name is provided, use it. Otherwise:
|
||||
- Infer from conversation context if the user mentioned a change
|
||||
- Auto-select if only one active change exists
|
||||
- If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select
|
||||
|
||||
Always announce: "Using change: <name>" and how to override (e.g., `/opsx:apply <other>`).
|
||||
|
||||
2. **Check status to understand the schema**
|
||||
```bash
|
||||
openspec status --change "<name>" --json
|
||||
```
|
||||
Parse the JSON to understand:
|
||||
- `schemaName`: The workflow being used (e.g., "spec-driven")
|
||||
- Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others)
|
||||
|
||||
3. **Get apply instructions**
|
||||
|
||||
```bash
|
||||
openspec instructions apply --change "<name>" --json
|
||||
```
|
||||
|
||||
This returns:
|
||||
- `contextFiles`: artifact ID -> array of concrete file paths (varies by schema - could be proposal/specs/design/tasks or spec/tests/implementation/docs)
|
||||
- Progress (total, complete, remaining)
|
||||
- Task list with status
|
||||
- Dynamic instruction based on current state
|
||||
|
||||
**Handle states:**
|
||||
- If `state: "blocked"` (missing artifacts): show message, suggest using openspec-continue-change
|
||||
- If `state: "all_done"`: congratulate, suggest archive
|
||||
- Otherwise: proceed to implementation
|
||||
|
||||
4. **Read context files**
|
||||
|
||||
Read every file path listed under `contextFiles` from the apply instructions output.
|
||||
The files depend on the schema being used:
|
||||
- **spec-driven**: proposal, specs, design, tasks
|
||||
- Other schemas: follow the contextFiles from CLI output
|
||||
|
||||
5. **Show current progress**
|
||||
|
||||
Display:
|
||||
- Schema being used
|
||||
- Progress: "N/M tasks complete"
|
||||
- Remaining tasks overview
|
||||
- Dynamic instruction from CLI
|
||||
|
||||
6. **Implement tasks (loop until done or blocked)**
|
||||
|
||||
For each pending task:
|
||||
- Show which task is being worked on
|
||||
- Make the code changes required
|
||||
- Keep changes minimal and focused
|
||||
- Mark task complete in the tasks file: `- [ ]` → `- [x]`
|
||||
- Continue to next task
|
||||
|
||||
**Pause if:**
|
||||
- Task is unclear → ask for clarification
|
||||
- Implementation reveals a design issue → suggest updating artifacts
|
||||
- Error or blocker encountered → report and wait for guidance
|
||||
- User interrupts
|
||||
|
||||
7. **On completion or pause, show status**
|
||||
|
||||
Display:
|
||||
- Tasks completed this session
|
||||
- Overall progress: "N/M tasks complete"
|
||||
- If all done: suggest archive
|
||||
- If paused: explain why and wait for guidance
|
||||
|
||||
**Output During Implementation**
|
||||
|
||||
```
|
||||
## Implementing: <change-name> (schema: <schema-name>)
|
||||
|
||||
Working on task 3/7: <task description>
|
||||
[...implementation happening...]
|
||||
✓ Task complete
|
||||
|
||||
Working on task 4/7: <task description>
|
||||
[...implementation happening...]
|
||||
✓ Task complete
|
||||
```
|
||||
|
||||
**Output On Completion**
|
||||
|
||||
```
|
||||
## Implementation Complete
|
||||
|
||||
**Change:** <change-name>
|
||||
**Schema:** <schema-name>
|
||||
**Progress:** 7/7 tasks complete ✓
|
||||
|
||||
### Completed This Session
|
||||
- [x] Task 1
|
||||
- [x] Task 2
|
||||
...
|
||||
|
||||
All tasks complete! Ready to archive this change.
|
||||
```
|
||||
|
||||
**Output On Pause (Issue Encountered)**
|
||||
|
||||
```
|
||||
## Implementation Paused
|
||||
|
||||
**Change:** <change-name>
|
||||
**Schema:** <schema-name>
|
||||
**Progress:** 4/7 tasks complete
|
||||
|
||||
### Issue Encountered
|
||||
<description of the issue>
|
||||
|
||||
**Options:**
|
||||
1. <option 1>
|
||||
2. <option 2>
|
||||
3. Other approach
|
||||
|
||||
What would you like to do?
|
||||
```
|
||||
|
||||
**Guardrails**
|
||||
- Keep going through tasks until done or blocked
|
||||
- Always read context files before starting (from the apply instructions output)
|
||||
- If task is ambiguous, pause and ask before implementing
|
||||
- If implementation reveals issues, pause and suggest artifact updates
|
||||
- Keep code changes minimal and scoped to each task
|
||||
- Update task checkbox immediately after completing each task
|
||||
- Pause on errors, blockers, or unclear requirements - don't guess
|
||||
- Use contextFiles from CLI output, don't assume specific file names
|
||||
|
||||
**Fluid Workflow Integration**
|
||||
|
||||
This skill supports the "actions on a change" model:
|
||||
|
||||
- **Can be invoked anytime**: Before all artifacts are done (if tasks exist), after partial implementation, interleaved with other actions
|
||||
- **Allows artifact updates**: If implementation reveals design issues, suggest updating artifacts - not phase-locked, work fluidly
|
||||
114
.codex/skills/openspec-archive-change/SKILL.md
Normal file
114
.codex/skills/openspec-archive-change/SKILL.md
Normal file
@@ -0,0 +1,114 @@
|
||||
---
|
||||
name: openspec-archive-change
|
||||
description: Archive a completed change in the experimental workflow. Use when the user wants to finalize and archive a change after implementation is complete.
|
||||
license: MIT
|
||||
compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.3.1"
|
||||
---
|
||||
|
||||
Archive a completed change in the experimental workflow.
|
||||
|
||||
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||
|
||||
**Steps**
|
||||
|
||||
1. **If no change name provided, prompt for selection**
|
||||
|
||||
Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
|
||||
|
||||
Show only active changes (not already archived).
|
||||
Include the schema used for each change if available.
|
||||
|
||||
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
|
||||
|
||||
2. **Check artifact completion status**
|
||||
|
||||
Run `openspec status --change "<name>" --json` to check artifact completion.
|
||||
|
||||
Parse the JSON to understand:
|
||||
- `schemaName`: The workflow being used
|
||||
- `artifacts`: List of artifacts with their status (`done` or other)
|
||||
|
||||
**If any artifacts are not `done`:**
|
||||
- Display warning listing incomplete artifacts
|
||||
- Use **AskUserQuestion tool** to confirm user wants to proceed
|
||||
- Proceed if user confirms
|
||||
|
||||
3. **Check task completion status**
|
||||
|
||||
Read the tasks file (typically `tasks.md`) to check for incomplete tasks.
|
||||
|
||||
Count tasks marked with `- [ ]` (incomplete) vs `- [x]` (complete).
|
||||
|
||||
**If incomplete tasks found:**
|
||||
- Display warning showing count of incomplete tasks
|
||||
- Use **AskUserQuestion tool** to confirm user wants to proceed
|
||||
- Proceed if user confirms
|
||||
|
||||
**If no tasks file exists:** Proceed without task-related warning.
|
||||
|
||||
4. **Assess delta spec sync state**
|
||||
|
||||
Check for delta specs at `openspec/changes/<name>/specs/`. If none exist, proceed without sync prompt.
|
||||
|
||||
**If delta specs exist:**
|
||||
- Compare each delta spec with its corresponding main spec at `openspec/specs/<capability>/spec.md`
|
||||
- Determine what changes would be applied (adds, modifications, removals, renames)
|
||||
- Show a combined summary before prompting
|
||||
|
||||
**Prompt options:**
|
||||
- If changes needed: "Sync now (recommended)", "Archive without syncing"
|
||||
- If already synced: "Archive now", "Sync anyway", "Cancel"
|
||||
|
||||
If user chooses sync, use Task tool (subagent_type: "general-purpose", prompt: "Use Skill tool to invoke openspec-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>"). Proceed to archive regardless of choice.
|
||||
|
||||
5. **Perform the archive**
|
||||
|
||||
Create the archive directory if it doesn't exist:
|
||||
```bash
|
||||
mkdir -p openspec/changes/archive
|
||||
```
|
||||
|
||||
Generate target name using current date: `YYYY-MM-DD-<change-name>`
|
||||
|
||||
**Check if target already exists:**
|
||||
- If yes: Fail with error, suggest renaming existing archive or using different date
|
||||
- If no: Move the change directory to archive
|
||||
|
||||
```bash
|
||||
mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name>
|
||||
```
|
||||
|
||||
6. **Display summary**
|
||||
|
||||
Show archive completion summary including:
|
||||
- Change name
|
||||
- Schema that was used
|
||||
- Archive location
|
||||
- Whether specs were synced (if applicable)
|
||||
- Note about any warnings (incomplete artifacts/tasks)
|
||||
|
||||
**Output On Success**
|
||||
|
||||
```
|
||||
## Archive Complete
|
||||
|
||||
**Change:** <change-name>
|
||||
**Schema:** <schema-name>
|
||||
**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/
|
||||
**Specs:** ✓ Synced to main specs (or "No delta specs" or "Sync skipped")
|
||||
|
||||
All artifacts complete. All tasks complete.
|
||||
```
|
||||
|
||||
**Guardrails**
|
||||
- Always prompt for change selection if not provided
|
||||
- Use artifact graph (openspec status --json) for completion checking
|
||||
- Don't block archive on warnings - just inform and confirm
|
||||
- Preserve .openspec.yaml when moving to archive (it moves with the directory)
|
||||
- Show clear summary of what happened
|
||||
- If sync is requested, use openspec-sync-specs approach (agent-driven)
|
||||
- If delta specs exist, always run the sync assessment and show the combined summary before prompting
|
||||
288
.codex/skills/openspec-explore/SKILL.md
Normal file
288
.codex/skills/openspec-explore/SKILL.md
Normal file
@@ -0,0 +1,288 @@
|
||||
---
|
||||
name: openspec-explore
|
||||
description: Enter explore mode - a thinking partner for exploring ideas, investigating problems, and clarifying requirements. Use when the user wants to think through something before or during a change.
|
||||
license: MIT
|
||||
compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.3.1"
|
||||
---
|
||||
|
||||
Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes.
|
||||
|
||||
**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing.
|
||||
|
||||
**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.
|
||||
|
||||
---
|
||||
|
||||
## The Stance
|
||||
|
||||
- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script
|
||||
- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions.
|
||||
- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking
|
||||
- **Adaptive** - Follow interesting threads, pivot when new information emerges
|
||||
- **Patient** - Don't rush to conclusions, let the shape of the problem emerge
|
||||
- **Grounded** - Explore the actual codebase when relevant, don't just theorize
|
||||
|
||||
---
|
||||
|
||||
## What You Might Do
|
||||
|
||||
Depending on what the user brings, you might:
|
||||
|
||||
**Explore the problem space**
|
||||
- Ask clarifying questions that emerge from what they said
|
||||
- Challenge assumptions
|
||||
- Reframe the problem
|
||||
- Find analogies
|
||||
|
||||
**Investigate the codebase**
|
||||
- Map existing architecture relevant to the discussion
|
||||
- Find integration points
|
||||
- Identify patterns already in use
|
||||
- Surface hidden complexity
|
||||
|
||||
**Compare options**
|
||||
- Brainstorm multiple approaches
|
||||
- Build comparison tables
|
||||
- Sketch tradeoffs
|
||||
- Recommend a path (if asked)
|
||||
|
||||
**Visualize**
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Use ASCII diagrams liberally │
|
||||
├─────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌────────┐ ┌────────┐ │
|
||||
│ │ State │────────▶│ State │ │
|
||||
│ │ A │ │ B │ │
|
||||
│ └────────┘ └────────┘ │
|
||||
│ │
|
||||
│ System diagrams, state machines, │
|
||||
│ data flows, architecture sketches, │
|
||||
│ dependency graphs, comparison tables │
|
||||
│ │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Surface risks and unknowns**
|
||||
- Identify what could go wrong
|
||||
- Find gaps in understanding
|
||||
- Suggest spikes or investigations
|
||||
|
||||
---
|
||||
|
||||
## OpenSpec Awareness
|
||||
|
||||
You have full context of the OpenSpec system. Use it naturally, don't force it.
|
||||
|
||||
### Check for context
|
||||
|
||||
At the start, quickly check what exists:
|
||||
```bash
|
||||
openspec list --json
|
||||
```
|
||||
|
||||
This tells you:
|
||||
- If there are active changes
|
||||
- Their names, schemas, and status
|
||||
- What the user might be working on
|
||||
|
||||
### When no change exists
|
||||
|
||||
Think freely. When insights crystallize, you might offer:
|
||||
|
||||
- "This feels solid enough to start a change. Want me to create a proposal?"
|
||||
- Or keep exploring - no pressure to formalize
|
||||
|
||||
### When a change exists
|
||||
|
||||
If the user mentions a change or you detect one is relevant:
|
||||
|
||||
1. **Read existing artifacts for context**
|
||||
- `openspec/changes/<name>/proposal.md`
|
||||
- `openspec/changes/<name>/design.md`
|
||||
- `openspec/changes/<name>/tasks.md`
|
||||
- etc.
|
||||
|
||||
2. **Reference them naturally in conversation**
|
||||
- "Your design mentions using Redis, but we just realized SQLite fits better..."
|
||||
- "The proposal scopes this to premium users, but we're now thinking everyone..."
|
||||
|
||||
3. **Offer to capture when decisions are made**
|
||||
|
||||
| Insight Type | Where to Capture |
|
||||
|----------------------------|--------------------------------|
|
||||
| New requirement discovered | `specs/<capability>/spec.md` |
|
||||
| Requirement changed | `specs/<capability>/spec.md` |
|
||||
| Design decision made | `design.md` |
|
||||
| Scope changed | `proposal.md` |
|
||||
| New work identified | `tasks.md` |
|
||||
| Assumption invalidated | Relevant artifact |
|
||||
|
||||
Example offers:
|
||||
- "That's a design decision. Capture it in design.md?"
|
||||
- "This is a new requirement. Add it to specs?"
|
||||
- "This changes scope. Update the proposal?"
|
||||
|
||||
4. **The user decides** - Offer and move on. Don't pressure. Don't auto-capture.
|
||||
|
||||
---
|
||||
|
||||
## What You Don't Have To Do
|
||||
|
||||
- Follow a script
|
||||
- Ask the same questions every time
|
||||
- Produce a specific artifact
|
||||
- Reach a conclusion
|
||||
- Stay on topic if a tangent is valuable
|
||||
- Be brief (this is thinking time)
|
||||
|
||||
---
|
||||
|
||||
## Handling Different Entry Points
|
||||
|
||||
**User brings a vague idea:**
|
||||
```
|
||||
User: I'm thinking about adding real-time collaboration
|
||||
|
||||
You: Real-time collab is a big space. Let me think about this...
|
||||
|
||||
COLLABORATION SPECTRUM
|
||||
════════════════════════════════════════════
|
||||
|
||||
Awareness Coordination Sync
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌────────┐ ┌────────┐ ┌────────┐
|
||||
│Presence│ │Cursors │ │ CRDT │
|
||||
│ "3 │ │ Multi │ │Conflict│
|
||||
│online" │ │ select │ │ free │
|
||||
└────────┘ └────────┘ └────────┘
|
||||
│ │ │
|
||||
trivial moderate complex
|
||||
|
||||
Where's your head at?
|
||||
```
|
||||
|
||||
**User brings a specific problem:**
|
||||
```
|
||||
User: The auth system is a mess
|
||||
|
||||
You: [reads codebase]
|
||||
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ CURRENT AUTH FLOW │
|
||||
└─────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────┼─────────────┐
|
||||
▼ ▼ ▼
|
||||
┌─────────┐ ┌─────────┐ ┌─────────┐
|
||||
│ Google │ │ GitHub │ │ Email │
|
||||
│ OAuth │ │ OAuth │ │ Magic │
|
||||
└────┬────┘ └────┬────┘ └────┬────┘
|
||||
│ │ │
|
||||
└─────────────┼─────────────┘
|
||||
▼
|
||||
┌───────────┐
|
||||
│ Session │
|
||||
└─────┬─────┘
|
||||
│
|
||||
▼
|
||||
┌───────────┐
|
||||
│ Perms │
|
||||
└───────────┘
|
||||
|
||||
I see three tangles. Which one's burning?
|
||||
```
|
||||
|
||||
**User is stuck mid-implementation:**
|
||||
```
|
||||
User: /opsx:explore add-auth-system
|
||||
The OAuth integration is more complex than expected
|
||||
|
||||
You: [reads change artifacts]
|
||||
|
||||
You're on task 4: "Implement OAuth flow"
|
||||
|
||||
Let me trace what's involved...
|
||||
|
||||
[draws diagram, explores options, suggests paths]
|
||||
|
||||
Want to update the design to reflect this?
|
||||
Or add a spike task to investigate?
|
||||
```
|
||||
|
||||
**User wants to compare options:**
|
||||
```
|
||||
User: Should we use Postgres or SQLite?
|
||||
|
||||
You: Generic answer is boring. What's the context?
|
||||
|
||||
User: A CLI tool that tracks local dev environments
|
||||
|
||||
You: That changes everything.
|
||||
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ CLI TOOL DATA STORAGE │
|
||||
└─────────────────────────────────────────────────┘
|
||||
|
||||
Key constraints:
|
||||
• No daemon running
|
||||
• Must work offline
|
||||
• Single user
|
||||
|
||||
SQLite Postgres
|
||||
Deployment embedded ✓ needs server ✗
|
||||
Offline yes ✓ no ✗
|
||||
Single file yes ✓ no ✗
|
||||
|
||||
SQLite. Not even close.
|
||||
|
||||
Unless... is there a sync component?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ending Discovery
|
||||
|
||||
There's no required ending. Discovery might:
|
||||
|
||||
- **Flow into a proposal**: "Ready to start? I can create a change proposal."
|
||||
- **Result in artifact updates**: "Updated design.md with these decisions"
|
||||
- **Just provide clarity**: User has what they need, moves on
|
||||
- **Continue later**: "We can pick this up anytime"
|
||||
|
||||
When it feels like things are crystallizing, you might summarize:
|
||||
|
||||
```
|
||||
## What We Figured Out
|
||||
|
||||
**The problem**: [crystallized understanding]
|
||||
|
||||
**The approach**: [if one emerged]
|
||||
|
||||
**Open questions**: [if any remain]
|
||||
|
||||
**Next steps** (if ready):
|
||||
- Create a change proposal
|
||||
- Keep exploring: just keep talking
|
||||
```
|
||||
|
||||
But this summary is optional. Sometimes the thinking IS the value.
|
||||
|
||||
---
|
||||
|
||||
## Guardrails
|
||||
|
||||
- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not.
|
||||
- **Don't fake understanding** - If something is unclear, dig deeper
|
||||
- **Don't rush** - Discovery is thinking time, not task time
|
||||
- **Don't force structure** - Let patterns emerge naturally
|
||||
- **Don't auto-capture** - Offer to save insights, don't just do it
|
||||
- **Do visualize** - A good diagram is worth many paragraphs
|
||||
- **Do explore the codebase** - Ground discussions in reality
|
||||
- **Do question assumptions** - Including the user's and your own
|
||||
110
.codex/skills/openspec-propose/SKILL.md
Normal file
110
.codex/skills/openspec-propose/SKILL.md
Normal file
@@ -0,0 +1,110 @@
|
||||
---
|
||||
name: openspec-propose
|
||||
description: Propose a new change with all artifacts generated in one step. Use when the user wants to quickly describe what they want to build and get a complete proposal with design, specs, and tasks ready for implementation.
|
||||
license: MIT
|
||||
compatibility: Requires openspec CLI.
|
||||
metadata:
|
||||
author: openspec
|
||||
version: "1.0"
|
||||
generatedBy: "1.3.1"
|
||||
---
|
||||
|
||||
Propose a new change - create the change and generate all artifacts in one step.
|
||||
|
||||
I'll create a change with artifacts:
|
||||
- proposal.md (what & why)
|
||||
- design.md (how)
|
||||
- tasks.md (implementation steps)
|
||||
|
||||
When ready to implement, run /opsx:apply
|
||||
|
||||
---
|
||||
|
||||
**Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build.
|
||||
|
||||
**Steps**
|
||||
|
||||
1. **If no clear input provided, ask what they want to build**
|
||||
|
||||
Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:
|
||||
> "What change do you want to work on? Describe what you want to build or fix."
|
||||
|
||||
From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`).
|
||||
|
||||
**IMPORTANT**: Do NOT proceed without understanding what the user wants to build.
|
||||
|
||||
2. **Create the change directory**
|
||||
```bash
|
||||
openspec new change "<name>"
|
||||
```
|
||||
This creates a scaffolded change at `openspec/changes/<name>/` with `.openspec.yaml`.
|
||||
|
||||
3. **Get the artifact build order**
|
||||
```bash
|
||||
openspec status --change "<name>" --json
|
||||
```
|
||||
Parse the JSON to get:
|
||||
- `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`)
|
||||
- `artifacts`: list of all artifacts with their status and dependencies
|
||||
|
||||
4. **Create artifacts in sequence until apply-ready**
|
||||
|
||||
Use the **TodoWrite tool** to track progress through the artifacts.
|
||||
|
||||
Loop through artifacts in dependency order (artifacts with no pending dependencies first):
|
||||
|
||||
a. **For each artifact that is `ready` (dependencies satisfied)**:
|
||||
- Get instructions:
|
||||
```bash
|
||||
openspec instructions <artifact-id> --change "<name>" --json
|
||||
```
|
||||
- The instructions JSON includes:
|
||||
- `context`: Project background (constraints for you - do NOT include in output)
|
||||
- `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
|
||||
- `template`: The structure to use for your output file
|
||||
- `instruction`: Schema-specific guidance for this artifact type
|
||||
- `outputPath`: Where to write the artifact
|
||||
- `dependencies`: Completed artifacts to read for context
|
||||
- Read any completed dependency files for context
|
||||
- Create the artifact file using `template` as the structure
|
||||
- Apply `context` and `rules` as constraints - but do NOT copy them into the file
|
||||
- Show brief progress: "Created <artifact-id>"
|
||||
|
||||
b. **Continue until all `applyRequires` artifacts are complete**
|
||||
- After creating each artifact, re-run `openspec status --change "<name>" --json`
|
||||
- Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array
|
||||
- Stop when all `applyRequires` artifacts are done
|
||||
|
||||
c. **If an artifact requires user input** (unclear context):
|
||||
- Use **AskUserQuestion tool** to clarify
|
||||
- Then continue with creation
|
||||
|
||||
5. **Show final status**
|
||||
```bash
|
||||
openspec status --change "<name>"
|
||||
```
|
||||
|
||||
**Output**
|
||||
|
||||
After completing all artifacts, summarize:
|
||||
- Change name and location
|
||||
- List of artifacts created with brief descriptions
|
||||
- What's ready: "All artifacts created! Ready for implementation."
|
||||
- Prompt: "Run `/opsx:apply` or ask me to implement to start working on the tasks."
|
||||
|
||||
**Artifact Creation Guidelines**
|
||||
|
||||
- Follow the `instruction` field from `openspec instructions` for each artifact type
|
||||
- The schema defines what each artifact should contain - follow it
|
||||
- Read dependency artifacts for context before creating new ones
|
||||
- Use `template` as the structure for your output file - fill in its sections
|
||||
- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file
|
||||
- Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact
|
||||
- These guide what you write, but should never appear in the output
|
||||
|
||||
**Guardrails**
|
||||
- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`)
|
||||
- Always read dependency artifacts before creating a new one
|
||||
- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum
|
||||
- If a change with that name already exists, ask if user wants to continue it or create a new one
|
||||
- Verify each artifact file exists after writing before proceeding to next
|
||||
3
.env.example
Normal file
3
.env.example
Normal file
@@ -0,0 +1,3 @@
|
||||
# 复制为 .env,并填写本机 Blender 可执行文件路径。
|
||||
# Windows 路径可使用正斜杠。
|
||||
BLENDER_BIN="C:/Program Files/Blender Foundation/Blender 4.5/blender.exe"
|
||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
run: uv run --frozen pytest
|
||||
|
||||
- name: 构建扩展
|
||||
run: uv run --frozen python pack.py
|
||||
run: uv run --frozen pack
|
||||
|
||||
- name: 发布扩展包
|
||||
uses: https://gitea.com/actions/gitea-release-action@v1.3.6
|
||||
|
||||
6
.github/workflows/docs.yml
vendored
6
.github/workflows/docs.yml
vendored
@@ -28,8 +28,8 @@ jobs:
|
||||
|
||||
- name: 整理发布目录
|
||||
run: |
|
||||
mkdir -p public/snapform
|
||||
cp -a site/. public/snapform/
|
||||
mkdir -p public/anyimage
|
||||
cp -a site/. public/anyimage/
|
||||
|
||||
- name: 配置 Node.js
|
||||
uses: actions/setup-node@v6
|
||||
@@ -50,5 +50,5 @@ jobs:
|
||||
--name "$CLOUDFLARE_WORKER_NAME" \
|
||||
--compatibility-date 2026-08-19 \
|
||||
--keep-vars \
|
||||
--message "SnapForm docs $GITHUB_SHA" \
|
||||
--message "AnyImage docs $GITHUB_SHA" \
|
||||
--autoconfig=false
|
||||
|
||||
403
.gitignore
vendored
403
.gitignore
vendored
@@ -1,11 +1,394 @@
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
.venv/
|
||||
.uv-cache/
|
||||
dist/
|
||||
build/
|
||||
*.zip
|
||||
.pytest_cache/
|
||||
# Tempary files
|
||||
temp
|
||||
sandbox.*
|
||||
/notes/
|
||||
|
||||
/models/
|
||||
wheels/
|
||||
# Environments
|
||||
.env
|
||||
.envrc
|
||||
.venv
|
||||
.env.*
|
||||
!.env.example
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
.secrets
|
||||
.secrets.*
|
||||
!.secrets.example
|
||||
secrets/
|
||||
SECRETS/
|
||||
secrets.bak/
|
||||
|
||||
# Claude Code
|
||||
.claude/settings.local.json
|
||||
.claudian
|
||||
|
||||
# Obsidian
|
||||
.obsidian/*
|
||||
!.obsidian/snippets
|
||||
!.obsidian/app.json
|
||||
|
||||
*.canvas
|
||||
|
||||
# Keep odox for extract to openwiki
|
||||
!.obsidian/plugins
|
||||
.obsidian/plugins/*
|
||||
!.obsidian/plugins/odox
|
||||
|
||||
# Blender
|
||||
*.blend1
|
||||
blender_assets.cats.txt~
|
||||
blendcache_*
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[codz]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
# Pipfile.lock
|
||||
|
||||
# UV
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
# poetry.lock
|
||||
# poetry.toml
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
||||
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
||||
# pdm.lock
|
||||
# pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# pixi
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
||||
# pixi.lock
|
||||
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
||||
# in the .venv directory. It is recommended not to include this directory in version control.
|
||||
.pixi
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# Redis
|
||||
*.rdb
|
||||
*.aof
|
||||
*.pid
|
||||
|
||||
# RabbitMQ
|
||||
mnesia/
|
||||
rabbitmq/
|
||||
rabbitmq-data/
|
||||
|
||||
# ActiveMQ
|
||||
activemq-data/
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
# .idea/
|
||||
|
||||
# Abstra
|
||||
# Abstra is an AI-powered process automation framework.
|
||||
# Ignore directories containing user credentials, local state, and settings.
|
||||
# Learn more at https://abstra.io/docs
|
||||
.abstra/
|
||||
|
||||
# Visual Studio Code
|
||||
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
||||
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
||||
# you could uncomment the following to ignore the entire vscode folder
|
||||
# .vscode/
|
||||
|
||||
# Ruff stuff:
|
||||
.ruff_cache/
|
||||
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
# Marimo
|
||||
marimo/_static/
|
||||
marimo/_lsp/
|
||||
__marimo__/
|
||||
|
||||
# Streamlit
|
||||
.streamlit/secrets.toml
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
.output
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Sveltekit cache directory
|
||||
.svelte-kit/
|
||||
|
||||
# vitepress build output
|
||||
**/.vitepress/dist
|
||||
|
||||
# vitepress cache directory
|
||||
**/.vitepress/cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# Firebase cache directory
|
||||
.firebase/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# pnpm
|
||||
.pnpm-store
|
||||
|
||||
# yarn v3
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
# Vite files
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
.vite/
|
||||
|
||||
models/
|
||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.11
|
||||
122
AGENTS.md
122
AGENTS.md
@@ -1,81 +1,99 @@
|
||||
# SnapForm 开发约定
|
||||
# AnyImage 项目规范
|
||||
|
||||
## 总则
|
||||
|
||||
- 说明性内容(提交说明、文档、注释、Mermaid 等)以中文为主,专有名词和约定俗成的表达可使用英文
|
||||
- 动手前先读 `docs/index.md`;程序边界、调用流程和模块职责见 `docs/development.md#程序设计`
|
||||
- 修改代码、依赖或执行方式后,同步更新 `README.md` 与对应主题文档,保持文档、测试和实现一致
|
||||
- 优先选择依赖少、直接、易验证的实现;保留工作区中与当前任务无关的已有改动
|
||||
**不管是代码还是文档,遵循「少就是多」的原则,以好理解为首要要求**
|
||||
|
||||
## 项目结构
|
||||
- 说明性内容以中文为主,专有名词和约定俗成的表达可使用英文
|
||||
- 修改代码、依赖或执行方式后,
|
||||
- 不保留旧路径、转发函数、兼容层或胶水代码
|
||||
- 仅更新并运行相关测试,但不主动同步说明文档、构建文档或打包扩展
|
||||
- 优先选择依赖少、直接、易验证的实现
|
||||
- 复用来自 `common` 的函数,避免重复造轮子
|
||||
- 确保文件名/文件夹名和内容边界一致
|
||||
|
||||
```text
|
||||
.
|
||||
├── README.md # 用户入口与快速上手
|
||||
├── docs/ # 按功能拆分的设计与开发文档
|
||||
├── pack.py # 构建各平台 Blender Extension ZIP
|
||||
├── sync_models.py # 准备并同步 ONNX 模型
|
||||
├── src/snapform/
|
||||
│ ├── __init__.py # Blender 注册入口
|
||||
│ ├── operators.py # Job Operator 与结果导入
|
||||
│ ├── panel.py # 3D View 面板
|
||||
│ ├── preferences.py # Extension Preferences
|
||||
│ ├── properties.py # Scene 设置与动态枚举
|
||||
│ ├── job_runtime.py # BlendJob Runtime 配置
|
||||
│ ├── blender/ # Depth Plane、Surface 的 Blender 侧实现
|
||||
│ ├── server/ # Job、模型、ONNX 推理与输出实现
|
||||
│ ├── assets/O_SnapForm.blend # Geometry Nodes 与材质资产
|
||||
│ └── wheels/ # 随扩展分发的 BlendJob wheel
|
||||
├── tools/ # Blender 资产构建与验证脚本
|
||||
└── tests/ # Python 自动化测试
|
||||
```
|
||||
## 全面整理代码
|
||||
|
||||
文档按主题定位:功能与使用 `docs/features.md`、Depth Plane `docs/depth-plane.md`、Surface `docs/surface.md`、输出格式 `docs/outputs.md`、Job Server `docs/server.md`、程序设计与开发 `docs/development.md`。
|
||||
当用户说「全面整理代码」时,依据本文件规范,直接执行以下整理:
|
||||
|
||||
- 按业务职责梳理代码结构与命名;简单功能用单文件,复杂 Operator 功能包统一以 `operators.py` 为入口,`__init__.py` 负责导出与注册清单。
|
||||
- 沿真实调用链清理无用代码,不把仅有测试引用当作业务用途;查证动态调用、框架回调和注册入口后再删除。
|
||||
- 精简测试,保留有效行为覆盖,使测试名称、归属与功能对应,并检查状态隔离。
|
||||
- 同步整理 `tools/nodes`、`docs/internals` 和相关引用,使名称、说明与实际实现一致。本触发指令包含内部文档整理。
|
||||
- 保留已有的其他修改和有效功能,运行全量测试并确认正常退出,检查旧引用与最终差异,简要报告调整及验证结果;不构建文档或打包扩展。
|
||||
|
||||
## 查询文档
|
||||
|
||||
下列是重要文档
|
||||
|
||||
- [总览](docs/internals/index.md):内部实现的总揽
|
||||
- [程序架构](docs/internals/architecture.md):进程边界、职责与调用主链
|
||||
- [Operator 索引](docs/internals/operators/index.md):Operator 实现功能和对应源码
|
||||
- [节点资产](docs/internals/node-assets.md):节点组的构建与验证(节点组不在运行时新建)
|
||||
|
||||
## 常见命令
|
||||
|
||||
优先在项目根目录使用 Git Bash;Windows 路径在 Bash 命令中使用正斜杠。
|
||||
|
||||
```bash
|
||||
# 安装或同步开发依赖
|
||||
uv sync
|
||||
|
||||
# 本地 BlendJob wheel 内容变化后刷新锁文件
|
||||
uv lock --refresh
|
||||
|
||||
# 运行全部测试
|
||||
uv run pytest
|
||||
|
||||
# 运行插件注册、Manifest 与打包相关的快速测试
|
||||
uv run pytest tests/test_addon.py tests/test_manifest.py tests/test_pack.py -v
|
||||
|
||||
# 构建 Windows、macOS、Linux 扩展包到 dist/
|
||||
uv run python pack.py
|
||||
uv run pack
|
||||
|
||||
# 本地 BlendJob wheel 内容变化后刷新锁文件
|
||||
uv lock --refresh
|
||||
# 构建并验证 Blender 节点资产
|
||||
uv run node-group build
|
||||
|
||||
# 准备、校验并同步 ONNX 模型到 R2
|
||||
uv run sync-models
|
||||
```
|
||||
|
||||
资产脚本必须由 Blender 执行;具体入口与验证范围见 `docs/development.md#资产构建与验证`,不要用普通 Python 解释器运行。
|
||||
|
||||
## 命名规范
|
||||
|
||||
- 变量、函数、Docstring 使用英文;同一概念在代码和文档中使用统一表达
|
||||
- Python 类使用大驼峰,不添加项目名前缀。Operator 使用动宾结构,如 `ConvertToDepthPlane`、`RemoveImageBackground`;Menu、Panel、AddonPreferences、PropertyGroup 分别使用实际类型后缀,如 `SnapFormImageMenu`、`ServerPanel`、`SnapFormPreferences`、`SnapFormSettings`
|
||||
- `bl_idname` 与挂载到公共 Blender RNA 类型的 Property 使用 `snapform` 项目前缀:Operator 如 `snapform.convert_to_depth_plane`,Menu 如 `SNAPFORM_MT_image_context`,Panel 如 `SNAPFORM_PT_server`,公共 Property 如 `Scene.snapform_settings`
|
||||
- 变量、函数、Docstring 必须使用英文;同一概念在代码和文档中使用统一表达
|
||||
- 函数的动作前缀应表达产物边界:`generate_*` 表示 AI 生成。`build_*` 表示代码层构建。`create_*` 表示 Blender 内创建
|
||||
- 名称应说明业务职责,并与用户概念、公开协议和界面术语保持一致;不要使用脱离上下文后无法判断用途的宽泛名称
|
||||
- 文件和目录名应表达稳定的业务分类和模块职责,不直接沿用函数名,也不按临时执行步骤命名
|
||||
- 集合层使用复数,单项分类使用单数
|
||||
|
||||
- Operator Class 使用动宾结构,如 `ConvertToDepthPlane`、`RemoveImageBackground`
|
||||
- Menu、Panel、AddonPreferences、PropertyGroup 分别使用实际类型后缀,如 `AnyImageImageMenu`、`ServerPanel`、`AnyImagePreferences`、`AnyImageSettings`
|
||||
- `bl_idname` 使用 `anyimage` 项目前缀:
|
||||
- Operator 如 `anyimage.convert_to_depth_plane`
|
||||
- Menu 如 `ANYIMAGE_MT_image_context`
|
||||
- Panel 如 `ANYIMAGE_PT_server`
|
||||
- 公共 Property 如 `Scene.anyimage_settings`
|
||||
- Operator 自身 Property 不加项目前缀,使用业务名称,如 `input_path`、`process_res`、`resolution_level`
|
||||
- 扩展包名使用 Manifest 中的真实名称、版本和平台,例如 `SnapForm.v0.11.2.windows-x64.zip`;节点组名称沿用资产中的 `O DA3 Depth Plane`、`O MoGe2 Surface` 等既有名称
|
||||
- 扩展包名使用 Manifest 中的真实名称、版本和平台,例如 `AnyImage.v0.11.2.windows-x64.zip`;节点组名称沿用资产中的 `O DA3 Depth Plane`、`O MoGe2 Surface` 等既有名称
|
||||
|
||||
## 代码规范
|
||||
## 文档规范
|
||||
|
||||
- 避免不必要的嵌套,优先使用提前返回和小而明确的函数
|
||||
- 只为特殊、非常规或存在兼容性原因的实现写注释,不复述代码本身
|
||||
- 删除功能时同时删除无效参数、分支、测试和文档,不保留没有实际消费者的兼容层
|
||||
- Blender 侧不载入 AI 依赖:UI、Operator 和结果导入放在 `src/snapform/` 或 `src/snapform/blender/`,模型下载、ONNX 推理和输出放在 `src/snapform/server/`
|
||||
- `README.md` 面向用户,不在其中堆叠内部细节。实现原理放入 `docs/internals` 对应主题
|
||||
- 文档只正面描述当前主题的职责与实现,不记录无关模块、未采用路径、历史差异、边界辩解、“不做什么”的排除性信息。
|
||||
- 根据内容选择最清晰的表达方式,优先简洁文字;复杂流程或关系在有助于理解时使用图表。Mermaid 流程图默认从上到下。
|
||||
|
||||
## Blender 开发规范
|
||||
|
||||
- 扩展级设置及其读取入口统一放在 `preferences.py`;使用 `addon_preferences()` 读取
|
||||
- Job 参数使用可序列化的普通字典;Blender 数据只能在主线程的 Operator 响应阶段读写
|
||||
- 新增或修改 Blender 类型时同步检查 `src/snapform/__init__.py` 的注册与逆序注销,并补充对应测试
|
||||
- 新增或修改 Blender 类型时同步检查 `src/anyimage/__init__.py` 的注册与逆序注销,并补充对应测试
|
||||
- 依赖版本、wheel 或 Manifest 改变时同步检查 `pyproject.toml`、`uv.lock`、`blender_manifest.toml` 和打包测试
|
||||
- `server/` 是 blendjob 后端代码,无法引用来自它上级文件夹的内容。共享函数需单独实现。
|
||||
|
||||
## 文档与 Markdown 规范
|
||||
## Blender 节点组规范
|
||||
|
||||
- `README.md` 面向用户;实现原理放入 `docs/` 对应主题,不在 README 堆叠内部细节
|
||||
- 不使用 `---` 分隔线;一级标题只在文档开头使用一次
|
||||
- Mermaid 节点 ID 使用英文字符,节点显示文本可以使用中文
|
||||
- 文档描述当前已实现行为;限制、默认值、命令和文件名必须能从代码或测试中验证
|
||||
- 节点组统一保存于 `src/anyimage/assets/O_AnyImage.blend`。
|
||||
- 插件运行时必须使用 `load_node_group(group_name)` 从资产加载,禁止调用 `bpy.data.node_groups.new()` 创建节点组。
|
||||
- `tools/nodes` 构建脚本和测试允许通过代码创建节点组。
|
||||
- 字段默认直接连接,按消费位置的几何、域、类型及输入属性版本验证求值等价性;多个消费者可复用同一输出。
|
||||
- 所有几何节点、嵌套组及测试辅助图禁止使用 Capture Attribute。域转换显式表达;跨几何变化优先调整消费顺序或在明确源几何上采样。
|
||||
- Store Named Attribute 仅用于实际命名协议或经论证仍需跨几何变化保存的最少数据;明确名称、类型、域、Selection、写入阶段及消费者。多个消费者或跨区域连线不构成存值理由。
|
||||
- 内部临时命名属性使用专用名称,在最后消费者之后精确清理,保留用户属性;源码和保存资产均检查 Capture 为零。
|
||||
- 节点布局通过区域内输入、字段排列和必要的 Reroute 保持可读;存储须有数据生命周期依据,构建阶段通过明确的节点或 socket 引用接线。
|
||||
- 修改节点组的逻辑、接口、布局或共用构建函数后,必须运行 `uv run node-group build`,无需另行确认;源码、测试与 `.blend` 资产均更新且验证通过,才算完成。
|
||||
- 具体构建规范与操作步骤见[节点资产](docs/internals/node-assets.md)。
|
||||
|
||||
117
README.md
117
README.md
@@ -1,111 +1,12 @@
|
||||
# SnapForm
|
||||
# AnyImage
|
||||
|
||||
SnapForm 是一个 Blender 扩展,可以把一张照片、一段视频或一组序列帧快速变成有真实深度的立体面片。
|
||||
把图片里的空间感带进 Blender。
|
||||
|
||||
完整使用说明请从[文档首页](docs/index.md)开始;首次使用请依次阅读
|
||||
[安装与首次配置](docs/installation.md)和[五分钟上手](docs/quick-start.md)。
|
||||
AnyImage 是一款 Blender 扩展,可以粘贴和编辑图片、创建普通 Plane 或 Depth Plane、从局部选区生成 Cutout Shape,并在独立 AI Environment 中完成去背景与高清放大。
|
||||
|
||||
|
||||
## 它能做什么
|
||||
|
||||
- 一键把 Image Empty 转换为立体面片:让 AI 估算深度,或直接用原图作为深度
|
||||
- 把单张 Image Empty 转换为透视纹理网格,并自动匹配原对象的 Transform 与 Offset
|
||||
- 一键去除图片背景,生成透明前景图
|
||||
- 支持视频与序列帧输入,逐帧处理,动画也能得到逐帧对应的立体面片
|
||||
- 全部操作都在右键菜单里完成,不需要写任何代码
|
||||
|
||||
## 安装
|
||||
|
||||
1. 下载对应系统的扩展压缩包
|
||||
2. 打开 Blender,进入 `Preferences > Extensions`
|
||||
3. 点击 `Install from Disk`,选择下载的压缩包
|
||||
4. 在扩展偏好设置中确认 Storage Root 与 Device(保持默认即可);Device 默认取本机
|
||||
最快的 DirectML provider;Apple M4 或更新芯片可选 MPS/CoreML,其他 Mac 只提供 CPU
|
||||
5. 打开 3D 视图右侧的 `SnapForm` 面板:使用官方源点击 `Install Environment`,使用国内镜像点击 `Install Environment (Mirror)`
|
||||
|
||||
扩展不内置 uv 或 AI 运行库。首次使用会先下载并校验固定版本的 uv,随后自动安装轻量的 ONNX Runtime 环境,再下载 `MoGe-2 ViT-S Normal` 和
|
||||
`BEN2 Base`;不安装 PyTorch,也不下载各模型的 Python 源码包。其他 AI 模型可在
|
||||
`Models` panel 中按需下载,请保持网络畅通,首次安装需要一些时间。
|
||||
Environment Bootstrap 使用连续的 0–100%:uv 为 1–20%、Python 为 20–50%、packages 为 50–98%,最终验证为 98–100%。三个阶段显示真实的 `1/3`、`2/3`、`3/3`,下载占各阶段的 84–87%;开始与最终验证不显示编号,单阶段状态不显示 `1/1`。官方按钮固定使用 GitHub、uv 默认 Python 下载源和 PyPI;镜像按钮固定使用 CNB uv Release、CNB Python 解释器镜像和清华 PyPI,两种方式都不竞速或自动切换。MoGe-2 与 BEN2 属于 post-install,各自使用独立进度,不计入 Environment 的这轮 0–100%,其模型下载源逻辑不受按钮选择影响。
|
||||
任一必需模型在两个来源都下载失败时,`Install Environment` 会明确报错;Environment 本身仍会保留。
|
||||
选中 Image Empty 后,右键 `SnapForm` 子菜单会在缺少 `MoGe-2 ViT-S Normal` 或 `BEN2 Base`
|
||||
时显示 `Download Required Models`,一次补齐缺失项,不会重复下载已经就绪的模型。
|
||||
Environment 未安装时,同一位置显示两个安装按钮。Environment 或必需模型未就绪时,
|
||||
菜单仍显示 `Convert to Depth Plane`、`Convert to Surface` 和 `Remove Background`,但三个入口都会保持禁用。
|
||||
这些 UI 状态直接检查 Storage Root 的 `models/` 文件,不会仅因打开菜单或面板而启动 Job Server。
|
||||
|
||||
## 快速上手
|
||||
|
||||
### 转换为立体面片
|
||||
|
||||
1. 在 3D 视图中选中一个 Image Empty(空物体,显示方式为 Image)
|
||||
2. 右键打开 `SnapForm` 子菜单,选择 `Convert to Depth Plane`
|
||||
3. 在对话框中选择:
|
||||
- `Depth from`:`Original` 直接用原图,`DA3` 使用现有 DA3 流程,`MoGe-2` 生成 metric depth、normal 与有效区域 mask
|
||||
- `Resolution Scale` 使用 `1–100%`,按原图长边计算 Process Res;结果会限制在 `252–1008` 并自动对齐到 14 的倍数
|
||||
- `Remove Background` 默认关闭;开启后在转换期间使用 BEN2 删除背景
|
||||
- `Shadeless` 使用 `O Shadeless` 节点组显示不受场景灯光影响的贴图颜色
|
||||
- `Keep Original` 默认关闭,位于分割线下方;开启后保留源 Image Empty
|
||||
4. 点击确认,稍等片刻就得到带位移的立体面片。`O DA3 Depth Plane` 与 `O MoGe2 Depth Plane` 都使用 `.color` 的 `Alpha > 0.5` 划分前景范围,范围外由 `Background Depth` 控制。
|
||||
|
||||
### 去除背景
|
||||
|
||||
选中 Image Empty 后,右键打开 `SnapForm` 子菜单并选择 `Remove Background`,完成后 Empty 会自动
|
||||
换成透明前景图。该操作在子菜单中使用独立分割区域。
|
||||
|
||||
### 转换为透视网格
|
||||
|
||||
选中单张图片的 Image Empty,右键打开 `SnapForm` 子菜单并选择 `Convert to Surface`。可选择 MoGe-2
|
||||
ViT-S、ViT-B、ViT-L 或 DA3 Small、Base,其中 MoGe-2 ViT-S 为默认模型。SnapForm 会按模型输出的
|
||||
相机内参与三维点建立透视网格,并匹配原 Empty 的可见尺寸、Offset 与 Transform。Server 通过统一的
|
||||
Surface 输出流程构建 DA3 与 MoGe-2 源网格:MoGe-2 使用深度断层 mask,DA3 保留 confidence 与边拉伸
|
||||
数据。两者再由独立的 Geometry Nodes
|
||||
修改器完成剔面、快速重拓扑、Relax 与 UV 重投;顶层 `Iterations` 可设为 `0`。开启 Remove Background
|
||||
时,BEN2 Alpha 只要大于 `0` 就视为前景;三角形三个顶点必须全部为前景,任一顶点 Alpha 为 `0`
|
||||
都会删除该面并且不再交给 Geometry Nodes;Surface 材质使用 `.color`。匹配相机默认不创建;开启后会作为 Surface 的子级。
|
||||
MoGe-2 的像素级 normal 会作为 Object Space normal 贴图接入 `O MoGe2 Surface Layer`,并与原图
|
||||
Bump 合并;网格本身不写入 custom normal,因此重拓扑降低顶点数后仍能保留细腻的法线细节。
|
||||
当前此功能仅支持单张图片。
|
||||
|
||||
MoGe-2 ViT-S、ViT-B、ViT-L、DA3 Small、DA3 Base 与 BEN2 全部使用 ONNX。模型执行、相机与深度恢复、
|
||||
点云重投影和网格生成均不依赖 PyTorch。Windows 使用 DirectML 加速;Apple M4 或更新芯片可使用 CoreML 的 `MLProgram` 模型格式,较早或无法识别的 Mac 只提供 CPU;provider 不可用时会回退到 ONNX Runtime CPU,
|
||||
并在任务状态中显示实际 provider。
|
||||
|
||||
转换对话框提供模型与 `Create Matching Camera`、`Remove Background`、`Shadeless`;DA3 显示
|
||||
`Resolution Scale`,MoGe-2 显示 `Resolution Level`,
|
||||
以及分割线下方的 `Keep Original`。
|
||||
界面会显示计算所得的实际 Process Res。剔除和重拓扑参数直接在
|
||||
Geometry Nodes 修改器中调整;`Shadeless` 与 Depth Plane 共用 `O Shadeless` 材质节点组。颜色贴图始终使用统一的 `.color` 转换产物;Remove Background 只决定是否由 BEN2 修改 Alpha。颜色贴图会按当前场景的 View Transform 选择对应输入色彩空间:AgX、ACES 1.3、ACES 2.0、Filmic、Khronos PBR Neutral 分别使用 `AgX Base sRGB`、`ACES 1.3 sRGB`、`ACES 2.0 sRGB`、`Filmic sRGB`、`Khronos PBR Neutral sRGB`;无法匹配当前 View Transform 时回退 `sRGB`。普通材质的
|
||||
Bump Scale 会在材质组内乘以 `0.001`,并按透视表面的深度空间缩放。Surface 与 Depth Plane 的
|
||||
Layer Color 同时驱动 Principled BSDF 的 Base Color、Subsurface Radius 和 Emission Color,Layer Normal
|
||||
同时驱动 Normal 与 Coat Normal;Subsurface、Emission、Coat 的其他设置保持默认。Surface attributes
|
||||
统一使用 `o_` 前缀;Server 准备过程显示
|
||||
`Preparing surface data n/6`,可看到当前阶段与总步骤;Surface 与 Depth 单帧模型推理都不显示多余的 `1/1`。
|
||||
|
||||
### 视频与序列帧
|
||||
|
||||
SnapForm 支持视频(mp4、mkv、mov 等)和序列帧,会逐帧处理并生成逐帧对应的立体面片。`.color` 与 `.depth` 按源 Empty Image 的类型载入:普通单图为 `IMAGE`,Movie/Sequence 即使只处理一帧也仍为 `SEQUENCE`。处理范围默认
|
||||
跟随 Empty 上设置的 `Image Frames` 与 `Offset`,不需要额外配置。DA3 的转换任务复用同一个 ONNX
|
||||
Session 逐帧推理,显存峰值不随视频帧数平方增长。Debug 只保存模型 API 的直接返回:DA3 多帧输入
|
||||
保留联合推理,MoGe-2 与 BEN2 按各自的单图 API 逐帧保存。
|
||||
|
||||
## 任务结果保存在哪里
|
||||
|
||||
任务结果保存在 Runtime Storage Root 的 `jobs/` 下,默认是 `~/.snapform/jobs/`。Job Server 接受任务时自动生成 opaque Job ID,并创建同名文件夹;Blender 不预先申请 ID 或目录。Packed Image 和序列帧的临时输入由 Blender 在任务结束后清理,Server 产物直接写入该 Job 目录。视频或序列帧生成的面片会引用其中的序列帧,请保留对应文件夹,不要随意删除。
|
||||
|
||||
## 常见问题
|
||||
|
||||
- 从 0.12.0–0.12.5 升级到 0.12.6 或更高版本后,请重新执行一次 `Install Environment`,重建不再单独安装 BlendJob 的 Server Environment
|
||||
- 首次使用需要下载模型,如果下载慢或失败,可在 Image Empty 的 `SnapForm` 右键菜单点击
|
||||
`Download Required Models`,也可在 `Models` panel 里逐项重试
|
||||
- 安装 Environment 时下载状态会显示平均速度;网络适合海外源时选择 `Install Environment`,国内网络选择 `Install Environment (Mirror)`。每个按钮固定使用对应来源,失败时不会自动切换
|
||||
- 默认模型下载会在 Hugging Face 与 OmooLab 镜像之间自动回退;某一来源暂时断线不会阻止后续模型尝试另一来源
|
||||
- 模型直链建连超过 10 秒,或连接后连续 60 秒没有收到数据,会切换到下一来源;不限制总下载时长
|
||||
- 找不到右键入口时,请确认选中了 Empty,且显示方式为 Image
|
||||
- 动画面片不动:检查 Empty 的 `Image Frames` 是否大于 1,并确认播放场景帧
|
||||
|
||||
## 技术文档
|
||||
|
||||
Blender 侧只负责界面、任务提交与结果导入,AI 与文件输出由独立 Job Server 执行。详细功能见
|
||||
[文档索引](docs/index.md),模块边界、调用流程、项目结构与开发命令见
|
||||
[程序设计、开发与许可证](docs/development.md)。CI 在 `main` push 与 Pull Request 上运行测试,仅由 `v*` tag 构建并发布扩展包;文档在 `main` 的文档相关文件变化时部署。
|
||||
- 按 **Ctrl+V**(macOS 为 **Cmd+V**)把剪贴板图片粘贴到当前编辑器
|
||||
- 用 **Plane** 创建可调厚度和细分的图片网格
|
||||
- 用 **Depth Plane** 把静态图片转换为可调节的立体面片
|
||||
- 用 **Cutout** 从套索选区生成 Surface、Balloon 与两种 Depth Shape
|
||||
- 用 **Frame** 从当前视角重新取景并合并 Image Empty,用 **Mask** 的 Lasso、Brush 或 Polyline 编辑 Alpha;Brush 拖动时以红色或灰色填充预览准确范围,释放后提交;用 **Rectify** 校正透视
|
||||
- 用 **Remove Background / Upscale** 处理单图、视频和序列帧
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
# 视频与序列帧
|
||||
|
||||
SnapForm 可以逐帧处理视频和序列帧,并生成随场景时间播放的 Depth Plane 或透明 Image Empty。
|
||||
|
||||
## 支持的工作流
|
||||
|
||||
- **Convert to Depth Plane**:支持视频和序列帧。
|
||||
- **Remove Background**:支持视频和序列帧。
|
||||
- **Convert to Surface**:当前只支持单张图片。
|
||||
|
||||
## 设置处理范围
|
||||
|
||||
处理范围来自 Image Empty 自身的图片设置:
|
||||
|
||||
- **Image Frames** 决定处理的帧数。
|
||||
- **Offset** 决定从源素材的哪个位置开始。
|
||||
- **Start** 决定结果从场景的哪一帧开始播放。
|
||||
- **Cyclic** 会保留到生成的颜色与深度序列中。
|
||||
|
||||
显式把 Image Frames 设为 `1` 时,SnapForm 只处理一帧。范围超过视频长度时,SnapForm 会把窗口收回有效范围内。
|
||||
|
||||
## 性能建议
|
||||
|
||||
- 先用较少帧数和较低分辨率测试效果。
|
||||
- DA3 和 MoGe-2 的 Depth Plane 会逐帧推理并复用模型 Session,显存峰值主要由单帧分辨率决定。
|
||||
- 视频越长,处理时间和任务目录占用越大。
|
||||
- 任务运行期间可以从 Blender 状态栏查看进度并取消。
|
||||
|
||||
## 保存和移动工程
|
||||
|
||||
Blender 无法把整组动画图片序列完整打包进 `.blend`。SnapForm 因此让动画结果直接引用 Storage Root 的 Job 文件夹。
|
||||
|
||||
!!! danger
|
||||
删除、移动或清理仍被工程引用的 Job 文件夹,会导致动画颜色或深度序列丢失。
|
||||
|
||||
需要把工程移动到另一台机器时,请连同对应 Job 文件夹一起复制,并在 Blender 中重新定位缺失文件。也可以先把需要长期保留的序列复制到项目自己的素材目录,再用 Blender 的外部数据工具重新映射路径。
|
||||
BIN
docs/assets/fonts/fontutti.woff2
LFS
Normal file
BIN
docs/assets/fonts/fontutti.woff2
LFS
Normal file
Binary file not shown.
BIN
docs/assets/logo.png
LFS
Normal file
BIN
docs/assets/logo.png
LFS
Normal file
Binary file not shown.
@@ -1,73 +0,0 @@
|
||||
# Depth Plane
|
||||
|
||||
本页记录 Depth Plane 的数据、节点和材质实现。日常操作与参数说明请阅读
|
||||
[Depth Plane 使用指南](guide-depth-plane.md)。
|
||||
|
||||
## Image Empty 转深度面片
|
||||
|
||||
1. 在 3D View 选中 `Object Type = Empty`、`Display As = Image` 的对象。
|
||||
2. 右键打开 `SnapForm` 子菜单并选择 `Convert to Depth Plane`,通过 `Depth from` 选择深度来源:
|
||||
- `DA3`:显示 Model 和 Resolution Scale。ONNX 模型使用 camera decoder 恢复相机。Process Res 先以原图长边乘以
|
||||
Resolution Scale,再钳制到 `252–1008`,最后对齐到最接近的 `14` 倍数。Resolution Scale 以百分比
|
||||
显示,范围为 `1–100%`,默认 `100%`;界面只在标签中实时显示最终 Process Res。
|
||||
转换导出 `mini_npz`,使用前需要在
|
||||
`Models` panel 下载所选模型,并固定使用 `process_res_method="upper_bound_resize"`;设备和参考视图策略
|
||||
继续使用场景设置。Model 下拉菜单只列出已经下载的 DA3 模型;若没有可用模型,对话框会引导到
|
||||
`Models` panel 下载。
|
||||
- MoGe-2:使用所选模型输出 metric depth、normal、validity mask 与 normalized intrinsics。Depth Plane 不保存体积较大的 points;Surface 会在 AI 进程内用内置 NumPy 网格构建生成 mesh.npz。Debug 则逐帧保存 API 直接返回的 points、depth、normal、mask 与 intrinsics,不生成网格或预览。
|
||||
- `Original`:直接把原图作为深度贴图,不显示估算参数。仅在开启 `Remove Background` 时运行 BEN2
|
||||
取得前景 Alpha;视频或序列帧按需同时抽取原始帧与前景序列,把原始帧作为 `.depth` 序列。
|
||||
`MoGe-2 ViT-S` 是默认深度来源。选择 `DA3` 时,其 Model 与 Resolution Scale
|
||||
参数会集中显示在独立边框中。`Remove Background` 与 `Shadeless` 直接显示,不再使用 Options 折叠;
|
||||
`Keep Original` 默认关闭,并在分割线下方保持为最后一项。仅当开启 `Remove Background` 时才需要
|
||||
`BEN2 Base`,并把 `.color` 的 Alpha 作为独立 Mask,不在 Server 中改写深度值。无论颜色层是直接
|
||||
复制原图,还是由 BEN2 为单图或序列修改 Alpha,Server 都统一写为 `color.0001.*`;Blender 只导入
|
||||
这一份 `.color`,不复用源 Empty 的 Image。颜色贴图会按当前场景的 View Transform 选择对应输入色彩空间:AgX、ACES 1.3、ACES 2.0、Filmic、Khronos PBR Neutral 分别使用 `AgX Base sRGB`、`ACES 1.3 sRGB`、`ACES 2.0 sRGB`、`Filmic sRGB`、`Khronos PBR Neutral sRGB`;无法匹配当前 View Transform 时回退 `sRGB`。
|
||||
Image Empty 引用视频或序列帧时,DA3 与 MoGe-2 会按 Empty 的 Image Frames 数值逐帧估计
|
||||
深度(未设置时为完整帧数,显式设为 1 时只处理 1 帧),起始帧跟随 Empty 的 Offset,与背景蒙版范围一致;
|
||||
DA3 会复用同一个 ONNX Session 对每帧独立推理,再合并为原有序列输出,避免多帧 Attention 的平方级显存占用。
|
||||
DA3 与 MoGe-2 均先用未经抠图的原始 RGB 生成深度,再运行可选 BEN2;BEN2 Alpha 只参与最终输出过滤和材质 Mask。
|
||||
该行为只用于 Depth Plane 转换;Surface 仅支持单张图片。Debug 中 DA3 多帧输入联合推理,
|
||||
MoGe-2 与 BEN2 按单图 API 逐帧运行。
|
||||
Offset 超出视频长度时同样把窗口收进视频内。材质节点与置换贴图的 Start 保持与原 Empty
|
||||
一致;Original 模式的视频或序列帧使用 `color.0001.*` 颜色层同时作为颜色与深度输入;开启
|
||||
Remove Background 时同名颜色层包含 BEN2 修改后的 Alpha。
|
||||
DA3 的深度仍以 EXR 序列帧(`depth.0001.exr` 起)保存在 job 目录根下;MoGe-2 分别使用 `moge_depth`、`moge_mask` 与 `moge_normal` EXR 序列。
|
||||
DA3 与 MoGe-2 的单帧推理进度只显示模型与阶段名称,不追加 `1/1`;多帧时继续显示当前帧与总数。`Shadeless`、`Remove Background` 与 `Keep Original` 均默认关闭,因此默认删除原对象。`Shadeless` 会以 `O Shadeless` 节点组替代普通 Principled BSDF 路径,并继续通过 Alpha 与 Transparent BSDF 混合。
|
||||
3. DA3 或 MoGe-2 完成后会自动创建面片。DA3 面片几何先由 `mini_npz` 的相机内参与深度确定,再通过对象
|
||||
Transform 匹配原 Image Empty 的可见尺寸、位置、旋转和 Offset。Original 模式直接使用源 Empty
|
||||
的可见尺寸、Offset 与 Transform。DA3 会把用于匹配画面尺寸的缩放预乘到 `.depth` 数值,
|
||||
XY 缩放烘焙到 Mesh,Z 不参与平面匹配;最终对象 Scale 为 `(1, 1, 1)`,视觉深度与旧逻辑一致。
|
||||
关闭 `Keep Original` 时,成功生成后删除源 Empty。生成面片 Object 使用源 Empty 的原名;开启
|
||||
`Keep Original` 时不处理
|
||||
同名冲突,由 Blender 自动为生成面片追加编号。
|
||||
4. Blender 只创建保留图像纵横比的最低密度基础网格,再把 `O DA3 Depth Plane` 作为 Geometry Nodes
|
||||
修改器添加到对象。节点组与 `O DA3 Surface` 一同保存在 `O_SnapForm.blend`,关闭
|
||||
`show_group_selector`,不为每个对象复制节点组。MoGe-2 使用单独的 `O MoGe2 Depth Plane`,处理
|
||||
网格细分、metric depth 位移和材质设置;材质使用 `O MoGe2 Depth Plane Layer`,直接接入模型 normal。
|
||||
`O MoGe2 Depth Plane` 与 DA3 一样通过隐藏的 Color Image 读取 `.color` Alpha,以 `Alpha > 0.5`
|
||||
划分前景范围,并在范围外改用 `Background Depth`。两个修改器公开以下参数:
|
||||
- `Displacement`:位于 Geometry 之后的第一项,菜单包含 `Geometry`(默认)与 `Material`。`Geometry`
|
||||
在节点组内沿法线修改网格,`Material` 保持网格平面并启用材质置换;
|
||||
- `Subdivision`:位于第二项,默认 `5`。节点读取基础平面的 Bounding Box,短边分段数为 `2^Subdivision`,
|
||||
长边按宽高比计算并四舍五入;例如 16:9 图片在等级 `5` 时生成 `57×32` 个面;
|
||||
- `Background Depth`:默认 `-1`,允许正负值,Structure Type 为 `Single`,直接显示且不再放在 Panel 中。前景 Mask 外的结构或
|
||||
材质置换使用该值;是否存在背景只取决于转换所得色彩贴图的 `Alpha > 0.5` 二值结果;
|
||||
- `Depth Scale`:默认 `1`,允许正负值,Structure Type 为 `Single`,同时控制 Geometry 与 Material
|
||||
两条置换路径。
|
||||
Grid 重建后通过隐藏的 Material 输入重新赋予转换材质,并对所有面执行 Shade Smooth。
|
||||
材质使用显式指向 `UVMap` 的 UV Map 节点连接颜色、前景 Mask 与深度 Image Texture,不依赖活动 UV。
|
||||
前景深度先与 `Background Depth` 取 Maximum,再按色彩贴图 Alpha 与背景深度混合,
|
||||
深度分支在混合前使用 `Alpha > 0.5` 转为二值 Mask,避免半透明前景被背景深度拉扯;
|
||||
材质表面 Alpha 同样使用二值 Mask。材质置换使用相同的二值和钳制顺序。
|
||||
节点组还会把 `o_material_displacement` 与 `o_background_depth` 写入点域,材质通过
|
||||
`Geometry` 类型的 Attribute 节点读取。点域会为顶点保存重复值,但能在应用修改器后继续保留参数;
|
||||
相比自适应网格本身,其占用很小。Depth Image 与 Color Image 是修改器隐藏数据输入,
|
||||
由导入流程自动设置。
|
||||
`.color` 与 `.depth` 的 Blender Image 类型只依据源 Empty Image:普通单图使用 `IMAGE`,Movie 或 Sequence 使用 `SEQUENCE`;即使动画源这次只处理一帧也仍为 `SEQUENCE`,不按输出文件数量推断。视频与序列帧还会为 Depth 与 Color 共同写入隐藏的 `Frame`、`Frame Start`、`Frames` 与 `Cyclic`。
|
||||
节点组内部不再使用 Scene Time。只有原 Image Empty 是视频或序列时,导入流程才直接给修改器的 `Frame`
|
||||
socket 添加 Scripted Expression Driver `frame`;静态图保持默认值 `1`,不创建 Driver。导入内容已经
|
||||
是截取后的帧,因此 Offset 在节点组内恒定为 `0`。
|
||||
节点组根据 `Frame` 计算从 `1` 开始的 Image Texture Frame:场景帧与 Start 都为 `1` 时读取图片第 `1` 帧;非循环输入
|
||||
钳制到有效范围;循环输入使用 Floored Modulo,让 `Frame Start` 前后的负、正相对帧都映射到
|
||||
`1…Frames`,因此开始帧之前也会循环播放。Geometry 置换会像材质 Image Texture 一样随场景帧刷新。
|
||||
@@ -1,178 +0,0 @@
|
||||
# 程序设计、开发与许可证
|
||||
|
||||
## 程序设计
|
||||
|
||||
SnapForm 分成 Blender 主进程与独立 Job Server 两侧。Blender 侧只负责界面、任务参数和 `.blend` 数据修改;模型下载、媒体准备、ONNX 推理和文件输出都在 Python 3.12 Environment 中运行。两侧由随扩展分发的 BlendJob wheel 连接,避免把 FastAPI、ONNX Runtime 等计算依赖载入 Blender Python。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
User["Image Empty / Debug Panel"] --> Operator["Blender Operator"]
|
||||
Operator --> Runtime["JobRuntime"]
|
||||
Runtime --> Server["JobServer FIFO Queue"]
|
||||
Server --> Handler["SnapForm Job Handler"]
|
||||
Handler --> Model["ModelManager / ONNX"]
|
||||
Handler --> JobFiles["Job Directory"]
|
||||
JobFiles --> Response["Operator response"]
|
||||
Response --> Scene["Blender Scene"]
|
||||
```
|
||||
|
||||
核心边界如下:
|
||||
|
||||
- `src/snapform/__init__.py` 是注册入口,集中注册 Blender 类型、`Scene.snapform_settings`、右键菜单与 Runtime
|
||||
- `panel.py`、`preferences.py`、`properties.py` 只描述 UI 和 Blender 属性,不直接导入 Server 模型实现
|
||||
- `operators.py` 将 Blender 状态转换为普通字典参数;Job 完成后在主线程导入图片、网格、材质和相机
|
||||
- `blender/depth_plane.py` 与 `blender/surface.py` 实现 Image Empty 输入检查、几何构建和资产挂载
|
||||
- `job_runtime.py` 以入口字符串声明独立 Server,并配置 Environment、Storage Root 与首次安装后的推荐模型下载
|
||||
- `server/__init__.py` 创建 `JobServer`、注册公开 Job,并把 `ModelManager` 注册为 Resource
|
||||
- `server/jobs/` 按任务组织完整流程;DA3、MoGe-2、BEN2 Debug 各自拥有独立模块和 Job,不用模型分支总控函数
|
||||
- `server/models/` 包含三个模型适配器及其 ONNX 实现,只负责模型载入、推理和 Prediction;不接收 Job Context、不写文件
|
||||
- `server/geometry/` 包含投影、像素网格和唯一 Surface Builder,均为纯数据转换;不负责 Debug 或文件输出
|
||||
- `server/outputs/` 只负责 Depth、Surface 与各模型 Debug 的序列化;Debug writer 原样保存 API 字段,不构建派生几何
|
||||
- `server/media/input.py` 只处理图片、序列和视频输入;共享的可选前景流程保留在 `jobs/foreground.py`
|
||||
- 根目录 `model_catalog.py` 供 Blender UI 与本地就绪检查使用;`server/model_catalog.py` 独立声明下载与推理所需模型,二者的模型键、目录名和 `ready_patterns` 由测试约束一致
|
||||
- `server/__init__.py` 只注册 Job 与 Resource;`jobs/background.py`、`jobs/frames.py` 分别实现去背景和帧提取
|
||||
- 所有任务产物写入 Runtime 分配的 opaque-ID Job 目录
|
||||
|
||||
功能细节继续按主题维护:[Depth Plane](depth-plane.md)、[Surface](surface.md)、[输出格式](outputs.md) 和 [Job Server](server.md)。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```text
|
||||
.
|
||||
├── AGENTS.md
|
||||
├── README.md
|
||||
├── pack.py
|
||||
├── sync_models.py
|
||||
├── docs/
|
||||
├── src/
|
||||
│ └── snapform/
|
||||
│ ├── __init__.py
|
||||
│ ├── blender_manifest.toml
|
||||
│ ├── job_runtime.py
|
||||
│ ├── operators.py
|
||||
│ ├── panel.py
|
||||
│ ├── preferences.py
|
||||
│ ├── properties.py
|
||||
│ ├── assets/
|
||||
│ │ └── O_SnapForm.blend
|
||||
│ ├── blender/
|
||||
│ │ ├── depth_plane.py
|
||||
│ │ └── surface.py
|
||||
│ ├── model_catalog.py
|
||||
│ ├── server/
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── jobs/
|
||||
│ │ │ ├── background.py
|
||||
│ │ │ ├── debug_ben2.py
|
||||
│ │ │ ├── debug_da3.py
|
||||
│ │ │ ├── debug_moge2.py
|
||||
│ │ │ ├── depth.py
|
||||
│ │ │ ├── foreground.py
|
||||
│ │ │ ├── frames.py
|
||||
│ │ │ └── surface.py
|
||||
│ │ ├── geometry/
|
||||
│ │ │ ├── projection.py
|
||||
│ │ │ ├── grid_mesh.py
|
||||
│ │ │ └── surface.py
|
||||
│ │ ├── media/
|
||||
│ │ │ └── input.py
|
||||
│ │ ├── model_download.py
|
||||
│ │ ├── model_catalog.py
|
||||
│ │ ├── model_manager.py
|
||||
│ │ ├── models/
|
||||
│ │ │ ├── ben2.py
|
||||
│ │ │ ├── da3.py
|
||||
│ │ │ ├── moge2.py
|
||||
│ │ │ ├── onnx_ben2.py
|
||||
│ │ │ ├── onnx_da3.py
|
||||
│ │ │ ├── onnx_moge2.py
|
||||
│ │ │ └── onnx_runtime.py
|
||||
│ │ └── outputs/
|
||||
│ │ ├── debug_da3.py
|
||||
│ │ ├── debug_moge2.py
|
||||
│ │ ├── depth.py
|
||||
│ │ └── surface.py
|
||||
│ └── wheels/
|
||||
│ └── blendjob-0.1.13-py3-none-any.whl
|
||||
├── tests/
|
||||
└── tools/
|
||||
```
|
||||
|
||||
## 常见命令
|
||||
|
||||
在项目根目录使用 Git Bash:
|
||||
|
||||
```bash
|
||||
uv sync
|
||||
uv run pytest
|
||||
uv run pytest tests/test_addon.py tests/test_manifest.py tests/test_pack.py -v
|
||||
uv run python pack.py
|
||||
```
|
||||
|
||||
`.github/workflows/test.yml` 在 `main` push、Pull Request 与手动触发时使用 Python 3.11 和锁定依赖运行完整测试。`.github/workflows/build.yml` 只响应 `v*` tag,在再次通过完整测试后运行 `pack.py`,并把生成的 ZIP 上传到该 tag 对应的 Gitea Release。`pack.py` 从 `pyproject.toml` 的默认 `dependencies` 读取 Extension wheel 需求,按 Manifest 平台从 PyPI 下载 wheel,并把各平台对应的 wheel 列入包内 Manifest;`src/snapform/wheels/` 只保留本地资产,不作为 uv source。`.github/workflows/docs.yml` 在 `main` 的文档内容、MkDocs 配置或自身 Workflow 变化时部署,也可手动触发;它只使用 `docs` 依赖组,因此不安装默认依赖。Server Environment 与测试依赖声明在 `dev` 组,文档工具声明在 `docs` 组;新增依赖后应更新锁文件,不要在 Workflow 中另行维护下载清单。
|
||||
|
||||
本地预览用户文档:
|
||||
|
||||
```bash
|
||||
uv run mkdocs serve
|
||||
```
|
||||
|
||||
构建静态文档或使用 Mike 发布版本化文档:
|
||||
|
||||
```bash
|
||||
uv run mkdocs build
|
||||
uv run mike deploy <version> -p
|
||||
uv run mike set-default <version> -p
|
||||
```
|
||||
|
||||
### 文档部署
|
||||
|
||||
`.github/workflows/docs.yml` 在 `main` 分支的文档相关文件变化后运行,也可从 Gitea Actions 手动触发。工作流使用锁定的 Python 依赖严格构建 `site/`,将其放入与站点 URL 对应的 `public/snapform/`,再通过 Wrangler 部署为 Cloudflare Workers Static Assets。
|
||||
|
||||
在 Gitea 仓库的 **Settings > Actions** 中配置:
|
||||
|
||||
- Secret `CLOUDFLARE_ACCOUNT_ID`:Cloudflare Account ID
|
||||
- Secret `CLOUDFLARE_API_TOKEN`:限制到目标账号的 Cloudflare API Token,使用 **Edit Cloudflare Workers** 模板
|
||||
- Variable `CLOUDFLARE_WORKER_NAME`:承载文档的 Worker 名称
|
||||
|
||||
API Token 不写入仓库。Workflow 只在 `main` 上部署生产文档,不在 Pull Request 中读取 Cloudflare Secret。
|
||||
|
||||
目标 Worker 必须专门用于 SnapForm,并把 `docs.omoolab.xyz/snapform/*` 路由到该 Worker。每次 Wrangler 部署都会更新目标 Worker 的整套静态资源;如果一个 Worker 同时承载多个项目,应改由中央文档仓库聚合全部站点后统一部署,不能从各项目仓库分别覆盖它。
|
||||
|
||||
|
||||
Mike 不依赖 GitHub API;`mike deploy --branch <branch> --push` 也可以向 Gitea 保存版本化静态站点。当前自动部署先发布 `mkdocs build` 生成的单版本站点,等确定版本 URL 规则后再启用 Mike 分支,避免在 Cloudflare 上提前固化不合适的路径结构。
|
||||
|
||||
|
||||
|
||||
`pack.py` 根据 `blender_manifest.toml` 分别生成 Windows、macOS 和 Linux 扩展包,例如 `dist/SnapForm.v0.12.14.windows-x64.zip`。更换 `src/snapform/wheels/` 中随扩展分发的 wheel 后,先刷新锁文件再测试:
|
||||
|
||||
```bash
|
||||
uv lock --refresh
|
||||
uv run pytest
|
||||
```
|
||||
|
||||
## 资产构建与验证
|
||||
|
||||
`tools/` 脚本依赖 `bpy`,必须由 Blender 执行。设置本机 Blender 可执行文件后,可以重建或验证 `O_SnapForm.blend`:
|
||||
|
||||
```bash
|
||||
BLENDER_BIN="/c/Program Files/Blender Foundation/Blender 4.5/blender.exe"
|
||||
|
||||
"$BLENDER_BIN" --background --python tools/build_geometry_nodes_asset.py
|
||||
"$BLENDER_BIN" --background --python tools/validate_depth_plane_asset.py
|
||||
"$BLENDER_BIN" --background --python tools/validate_geometry_nodes_asset.py
|
||||
"$BLENDER_BIN" --background --python tools/validate_moge_depth_plane.py
|
||||
"$BLENDER_BIN" --background --python tools/validate_moge_surface.py
|
||||
```
|
||||
|
||||
验证脚本会自动把 `src/snapform/wheels/` 中的 BlendJob wheel 加入 Blender Python 路径,因此可在
|
||||
`--factory-startup` 的干净环境中直接运行。
|
||||
|
||||
只改 Python、文档或打包逻辑时不需要重建 `.blend` 资产;修改节点组、材质组或其接口时,应运行相应资产验证脚本,并再次运行完整测试。
|
||||
|
||||
## 上游与许可证
|
||||
|
||||
- Depth Anything 3 代码采用 Apache 2.0
|
||||
- BEN2 ONNX 模型采用其仓库声明的 Apache 2.0 License
|
||||
- SnapForm Extension 代码采用 GPL-3.0-or-later
|
||||
- 模型权重许可证由所选 Hugging Face 模型仓库决定,Extension 下载前会在界面显示
|
||||
57
docs/faq.md
57
docs/faq.md
@@ -1,57 +0,0 @@
|
||||
# 常见问题
|
||||
|
||||
## 找不到 SnapForm 右键菜单
|
||||
|
||||
SnapForm 菜单只会在选中 Image Empty 时出现。确认对象类型是 Empty,并且显示方式为 Image。普通 Mesh、图片编辑器中的图片或未选中的 Image Empty 都不会显示入口。
|
||||
|
||||
## 转换入口是灰色的
|
||||
|
||||
通常有以下原因:
|
||||
|
||||
- Environment 尚未安装。
|
||||
- 默认必需模型尚未就绪。
|
||||
- 另一个 SnapForm 任务正在运行。
|
||||
- Surface 的输入不是单张图片。
|
||||
|
||||
如果子菜单显示 **Install Environment** 或 **Download Required Models**,先完成对应操作。
|
||||
|
||||
## Environment 安装失败
|
||||
|
||||
- 海外源访问不稳定时尝试 **Install Environment (Mirror)**。
|
||||
- 检查 Blender 的 **Online Access** 是否开启。
|
||||
- 确认 Storage Root 有足够空间且可写。
|
||||
- 查看 Storage Root 下的 `install.log`。
|
||||
|
||||
安装模型失败不会删除已经成功创建的 Environment。可以稍后使用 **Download Required Models** 或 Models 面板重试。
|
||||
|
||||
## 模型下载很慢或失败
|
||||
|
||||
模型会尝试 Hugging Face 和 OmooLab 备用来源。检查网络、代理和磁盘空间后重新点击 Download。模型直链连接或长时间无数据时会自动尝试下一来源。
|
||||
|
||||
## 第一次转换较慢
|
||||
|
||||
第一次任务需要启动 Job Server 并把模型载入内存。Server 运行且模型仍在缓存中时,后续同类任务通常会更快。
|
||||
|
||||
## Depth Plane 太平或深度方向反了
|
||||
|
||||
在 Geometry Nodes 修改器中调整 **Depth Scale**。增大绝对值会加强深度,使用负值可以反转前后关系。
|
||||
|
||||
## Surface 边缘破碎或细节太多
|
||||
|
||||
先调整 **Depth Continuity** 清理断层,再提高 **Resolution** 简化网格,并用 **Smooth** 稳定表面。必要时尝试另一个模型或降低输入分辨率。
|
||||
|
||||
## 动画面片不播放
|
||||
|
||||
检查源 Image Empty 的 **Image Frames** 是否大于 `1`,并确认场景时间正在变化。生成后的动画还需要对应 Job 文件夹存在;文件被移动或删除时应重新定位序列。
|
||||
|
||||
## 可以删除 jobs 文件夹吗
|
||||
|
||||
静态图的部分结果会 Pack 到 `.blend`,但视频与序列帧会直接引用 Job 文件。只有确认没有工程继续引用对应目录时才应删除。
|
||||
|
||||
## 如何完全撤销一次转换
|
||||
|
||||
转换完成后直接按 `Ctrl+Z`。SnapForm 会把网格、材质、图片和修改器的创建合并为一次 Blender Undo 操作。
|
||||
|
||||
## 从旧版本升级后功能不可用
|
||||
|
||||
如果 Environment 声明或依赖发生变化,请重新执行一次 **Install Environment**。从 0.12.0–0.12.5 升级到 0.12.6 或更高版本时需要重建 Environment。
|
||||
105
docs/features.md
105
docs/features.md
@@ -1,105 +0,0 @@
|
||||
# 功能参考
|
||||
|
||||
本页记录完整功能行为和实现边界,主要供开发、测试与故障排查使用。第一次使用 SnapForm 时,请从
|
||||
[安装与首次配置](installation.md)或[五分钟上手](quick-start.md)开始。
|
||||
|
||||
`SnapForm` 是一套从图片快速构建空间结构的 Blender Extension。目前以
|
||||
[Depth Anything 3](https://github.com/ByteDance-Seed/Depth-Anything-3)
|
||||
作为首个 AI 能力,在独立 Environment 中运行 DA3,可将模型 API Prediction 保存在独立 job 目录,
|
||||
也可把 Image Empty 转换成带几何与材质置换的立体面片。MoGe-2 作为独立深度来源,保留其 metric depth、normal、mask 与 intrinsics 输出,不复用 DA3 Prediction 后处理。Debug 面板还可运行
|
||||
[BEN2](https://github.com/PramaLLC/BEN2) 背景剔除功能测试。
|
||||
|
||||
## 当前功能
|
||||
|
||||
- 使用 Blender Extension manifest,可通过 `Install from Disk` 安装
|
||||
- 在独立 Environment 中通过常驻 Server 运行 ONNX Runtime;MoGe-2、DA3 与 BEN2 的模型执行及后处理完全不使用 PyTorch,避免污染 Blender Python 进程
|
||||
- Server 的深度模型缓存只保留 DA3 或 MoGe-2 其中一个,切换时主动释放前一个;BEN2 使用独立缓存,
|
||||
可与当前深度模型共存
|
||||
- Blender 使用内置 `urllib.request` 访问仅监听 `127.0.0.1` 的 Server;连接由随机端口与 Instance ID 归属当前 Runtime
|
||||
- 常驻 Server 复用 ONNX Runtime session、BEN2 与最近使用的深度模型,连续执行时不再重复加载
|
||||
- 每个 Blender 实例持有自己的 Job Server;`Job Server` 面板显示状态与模型缓存,并提供安装、启动、清理、重启和停止控制
|
||||
- Environment、模型和任务结果默认保存在 `~/.snapform`,可由多个 Blender 版本共享
|
||||
- 当前仅启用 FP32 DA3 Small 和 Base;它们不随首次安装下载,Giant 1.1 与 Nested Giant Large 1.1 暂停使用
|
||||
- 支持 BEN2 Base 单图背景剔除测试,输出透明 PNG 与推理耗时
|
||||
- 支持单张 JPG、JPEG、PNG、WEBP 图片或常见视频文件输入
|
||||
- 视频按顺序截取前若干帧,可设置参与重建的最大帧数
|
||||
- `Models` panel 默认折叠,展开后可逐项查看和下载所有可用模型
|
||||
- Environment 已安装时不额外占用面板空间;模型列表每个模型只占一行
|
||||
- `Debug` 默认折叠,内部的 `DA3`、`MoGe-2` 与 `BEN2` 也分别默认折叠
|
||||
- DA3、MoGe-2 与 BEN2 分别使用独立 Debug Job,直接保存各自 API 返回,不共享按模型分支的总控函数
|
||||
- DA3 Debug 保存 Prediction NPZ;MoGe-2 Debug 保存逐帧原始预测 NPZ;BEN2 Debug 保存直接返回的透明前景
|
||||
- Debug 与 Convert to Depth Plane 的模型选项直接显示下载状态;未下载模型带
|
||||
`(Missing)`,已下载模型保持原名
|
||||
- Debug 不运行点云、Surface、深度预览、置信度 percentile 或坐标变换等 SnapForm 后处理
|
||||
- Image Empty 右键菜单可直接运行 DA3,根据 `mini_npz` 深度生成面片
|
||||
- Image Empty 右键菜单可直接运行 BEN2,保留原图并换用新建的透明背景图片
|
||||
- 缺少必需的 `MoGe-2 ViT-S Normal` 或 `BEN2 Base` 时,Image Empty 的 `SnapForm`
|
||||
右键子菜单显示 `Download Required Models`;单次任务只下载缺失项,并在补齐前禁用
|
||||
`Convert to Depth Plane`、`Convert to Surface` 和 `Remove Background`。Environment 未安装时,
|
||||
同一位置改为显示 `Install Environment` 与 `Install Environment (Mirror)`,三个禁用的功能入口仍然可见
|
||||
- 菜单、模型面板和转换对话框直接检查 Storage Root 的 `models/` 必需文件,显示模型状态时不会启动 Job Server
|
||||
- Image Empty 支持视频与序列帧输入:`Convert to Depth Plane` 逐帧估计深度,
|
||||
`Remove Background` 逐帧抠图,入口保持不变。DA3 转换时每帧独立执行 ONNX 推理并复用模型 Session;
|
||||
DA3 Debug 多帧任务继续联合推理;MoGe-2 与 BEN2 Debug 按单图 API 逐帧运行
|
||||
- 视频生成的序列帧以外部文件引用保留在 job 目录(Blender 无法整体 Pack 序列帧);
|
||||
Geometry Nodes 与材质 Image Texture 均可按场景帧播放序列帧
|
||||
- 使用浮点深度贴图、`O DA3 Depth Plane` Geometry Nodes 和原图 Bump 构建立体面片
|
||||
- 转换对话框选择推理模式、处理分辨率与背景移除;细分和置换方式在 Geometry Nodes 修改器中调整
|
||||
- Debug 的 DA3 输出或 BEN2 背景剔除完成后只打开对应 job 文件夹,不修改当前场景
|
||||
- Debug 完整保留模型 API 返回的数组字段,不混入 SnapForm 派生几何
|
||||
- 安装和模型下载使用后台子进程;推理提交给常驻 Server,状态栏实时显示阶段、百分比进度条与取消按钮
|
||||
- 后台任务运行时,Install、Download 和 Export 按钮保持原位置并变灰,任务完成后恢复
|
||||
|
||||
## 使用
|
||||
|
||||
1. 运行 `uv run python pack.py`,分别构建 `windows-x64`、`macos-arm64`、
|
||||
`linux-x64` 三个平台的 Extension ZIP(如 `SnapForm.v0.12.17.macos-arm64.zip`)。
|
||||
安装包不携带 uv 或 AI 项目源码;首次安装 Environment 时下载固定版本 uv,并校验固定 SHA-256。官方安装按钮使用 GitHub、uv 默认 Python 下载源和 PyPI;镜像按钮使用 CNB uv Release、CNB Python 解释器镜像和清华 PyPI。
|
||||
2. 在 Blender 的 `Preferences > Extensions` 中选择 `Install from Disk`。
|
||||
3. 在 Extension Preferences 中确认 Storage Root 与 Device。Device 是正式功能
|
||||
(Image Empty 右键菜单)使用的推理设备。Windows 默认取 DirectML,否则 CPU;Apple M4 或更新芯片列出 MPS/CoreML 与 CPU,Intel、M1–M3 和无法识别的 Mac 只列出 CPU。
|
||||
4. 在 `Job Server` 选择 `Install Environment` 或 `Install Environment (Mirror)`。首次安装会下载 Python、ONNX Runtime、FastAPI、Uvicorn;
|
||||
uv 由 Job Runtime 的 Environment 安装器下载并校验,DA3、BEN2 与 MoGe 不安装源码包,
|
||||
并在 Environment 安装后自动下载 `MoGe-2 ViT-S Normal` 和 FP16 `BEN2 Base` 推荐模型。首次安装
|
||||
Environment Bootstrap 的 0–100% 只包含 uv、Python 与 packages:三阶段显示 `1/3`、`2/3`、`3/3`,范围分别为 1–20%、20–50%、50–98%,最终验证使用 98–100%。下载占各阶段的 84–87%;开始与验证不显示编号,单阶段状态不显示 `1/1`。
|
||||
两个默认模型由普通的 `post_install(runtime)` 发起正式 Server Job。
|
||||
安装完成后直接写入 Environment Manifest,不再额外导入全部组件进行验证;`DA3 Base` 等其他模型可稍后在 `Models` 面板按需下载。
|
||||
Environment packages 使用固定的 Resolving、Downloading 与 Installing 状态,原始 uv 输出写入 `install.log`。模型下载显示速度,例如
|
||||
`Downloading DA3 Small 14.2 MB/s`;镜像来源显示为
|
||||
`Downloading DA3 Small (mirror) 14.2 MB/s`。下载按字节连续推进,重试或切换镜像时进度不会倒退。
|
||||
Environment 安装阶段与 `Models` 面板单独下载使用相同的模型名称格式,不会改写为通用的 `Downloading packages`。
|
||||
Environment 安装后即使模型下载中断也仍会保留,但 `Install Environment` 会以失败结束并显示模型下载错误。
|
||||
可稍后在 Image Empty 的 `SnapForm` 右键菜单用 `Download Required Models` 一次补齐缺失的
|
||||
MoGe-2 ViT-S 与 BEN2,也可在 `Models` 面板单独补下。
|
||||
安装不做启动网络预检;每个模型都先尝试 Hugging Face,失败后尝试 OmooLab R2,两者都失败时当前 Job 失败。
|
||||
官方安装固定使用 GitHub、uv 默认 Python 下载源和 PyPI;镜像安装固定使用 CNB uv Release、`python-build-standalone` 镜像和清华 PyPI。两种方式都不测速、不竞速、不自动切换,镜像配置只注入当前安装子进程。模型仍按自身的 Hugging Face 与 OmooLab R2 逻辑下载。
|
||||
5. 在 3D View 按 `N`,打开 `SnapForm` 页签。
|
||||
6. 如需其他模型,展开 `Models` panel,找到模型并点击该行的 `Download`。
|
||||
7. 展开默认收起的 `Debug`,在 DA3、MoGe-2 或 BEN2 区域选择模型与输入,然后直接运行对应 API。
|
||||
8. 选择一张图片或一个视频文件。视频可设置从头截取的帧数,然后点击 `Generate Output`。
|
||||
9. 测试背景剔除时,确认 `Models` panel 中的 `BEN2 Base` 已就绪,然后在 `Debug` 的
|
||||
`BEN2` 区域选择单张图片并点击 `Remove Background`。
|
||||
完成后会打开 job 文件夹,其中的 `foreground.png` 带透明 Alpha;状态栏显示
|
||||
BEN2 推理、模型加载与任务总耗时。当前测试固定关闭较慢的 foreground refinement。
|
||||
|
||||
## Image Empty 去背景
|
||||
|
||||
1. 确认 `Models` panel 中的 `BEN2 Base` 已就绪;首次安装 Environment 时会默认下载。
|
||||
2. 在 3D View 选中 Image Empty,右键打开 `SnapForm` 子菜单并选择 `Remove Background`。
|
||||
3. 完成后以 Fake User 保留原 Image datablock,新建并 Pack 一张透明 PNG 到当前 `.blend`,然后只把
|
||||
当前 Empty 的 `data` 换成新图;Empty 的 Transform、显示尺寸、Offset 及其他设置保持不变。
|
||||
4. 新 Image 使用原图去掉格式后再追加 `.color` 的名称。例如 `img.png` 生成
|
||||
`img.color`,`img.png.001` 生成 `img.001.color`。已有同名 datablock 时由
|
||||
Blender 自动追加编号。
|
||||
|
||||
原图已 Pack 时会先写出临时 PNG 交给 BEN2,任务结束后删除临时输入;原 Image 仍保持 Pack
|
||||
状态和原始内容。正式任务的 `color.0001.png` 也会保留在对应 job 目录中。
|
||||
|
||||
Image Empty 引用视频或序列帧时,Remove Background 直接执行,默认取 Empty 的 Image Frames
|
||||
数值(未设置时为完整帧数,显式设为 1 时只处理 1 帧),起始帧跟随 Empty 的 Offset;若 Offset 加 Frames 超出视频长度,
|
||||
会把窗口整体收进视频内(保证实际处理 Frames 帧数),Offset 过小时从第 1 帧开始。输出
|
||||
`color.0001.png` 等透明序列帧(帧号从 0001 开始,与 Blender 场景帧一一对应),并把
|
||||
关闭去背景时,原始颜色帧同样以 `color.0001.*` 起始的统一名称写入 job 目录。替换 Empty 的 Image 为
|
||||
加载这些帧的 SEQUENCE Image,从原 Empty 的 Start 位置开始播放(Start 原样保留)。图片类型只取决于源 Empty:普通单图的单个 `color.0001.*` 仍作为 `IMAGE`,Movie/Sequence 即使只导出一帧也作为 `SEQUENCE`;`.depth` 同样遵循该规则。
|
||||
Cyclic 沿用原 Empty 的设置。序列帧无法被 Blender 整体 Pack,因此保持外部引用 job 目录;
|
||||
请保留对应 job 目录,否则序列会失效。
|
||||
80
docs/getting-started/installation.md
Normal file
80
docs/getting-started/installation.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# 安装与首次配置
|
||||
|
||||
页面会按当前系统显示对应的安装包,按住按钮拖到 Blender 窗口任意位置即可安装。首次拖入会提示添加 AnyImage 扩展仓库,确认后重新拖入一次,之后升级同样直接拖入。
|
||||
|
||||
<div class="anyimage-install">
|
||||
<div class="anyimage-install-item" data-platform="windows-x64">
|
||||
<div class="anyimage-install-group">
|
||||
<button type="button" draggable="true" class="anyimage-install-button" data-install-url="https://extensions.omoolab.xyz/AnyImage.v1.0.1.windows-x64.zip?repository=.%2Findex.json&blender_version_min=4.5.0&platforms=windows-x64&python_versions=3.11%2C3.13">
|
||||
<i class="i-move"></i><span>拖入 Blender 安装</span>
|
||||
</button>
|
||||
<div class="anyimage-install-platform"><strong>Windows</strong> – 78.3 MB</div>
|
||||
</div>
|
||||
<small class="anyimage-install-note">…或<a href="https://extensions.omoolab.xyz/AnyImage.v1.0.1.windows-x64.zip" download="AnyImage.v1.0.1.windows-x64.zip">下载</a>后通过 <a href="https://docs.blender.org/manual/zh-hans/latest/editors/preferences/extensions.html#install" target="_blank">Install from Disk</a> 安装</small>
|
||||
</div>
|
||||
<div class="anyimage-install-item" data-platform="macos-arm64">
|
||||
<div class="anyimage-install-group">
|
||||
<button type="button" draggable="true" class="anyimage-install-button" data-install-url="https://extensions.omoolab.xyz/AnyImage.v1.0.1.macos-arm64.zip?repository=.%2Findex.json&blender_version_min=4.5.0&platforms=macos-arm64&python_versions=3.11%2C3.13">
|
||||
<i class="i-move"></i><span>拖入 Blender 安装</span>
|
||||
</button>
|
||||
<div class="anyimage-install-platform"><strong>macOS<span class="anyimage-install-platform-rest"> - Apple Silicon</span></strong> – 57.5 MB</div>
|
||||
</div>
|
||||
<small class="anyimage-install-note">…或<a href="https://extensions.omoolab.xyz/AnyImage.v1.0.1.macos-arm64.zip" download="AnyImage.v1.0.1.macos-arm64.zip">下载</a>后通过 <a href="https://docs.blender.org/manual/zh-hans/latest/editors/preferences/extensions.html#install" target="_blank">Install from Disk</a> 安装</small>
|
||||
</div>
|
||||
<div class="anyimage-install-item" data-platform="linux-x64">
|
||||
<div class="anyimage-install-group">
|
||||
<button type="button" draggable="true" class="anyimage-install-button" data-install-url="https://extensions.omoolab.xyz/AnyImage.v1.0.1.linux-x64.zip?repository=.%2Findex.json&blender_version_min=4.5.0&platforms=linux-x64&python_versions=3.11%2C3.13">
|
||||
<i class="i-move"></i><span>拖入 Blender 安装</span>
|
||||
</button>
|
||||
<div class="anyimage-install-platform"><strong>Linux</strong> – 71.0 MB</div>
|
||||
</div>
|
||||
<small class="anyimage-install-note">…或<a href="https://extensions.omoolab.xyz/AnyImage.v1.0.1.linux-x64.zip" download="AnyImage.v1.0.1.linux-x64.zip">下载</a>后通过 <a href="https://docs.blender.org/manual/zh-hans/latest/editors/preferences/extensions.html#install" target="_blank">Install from Disk</a> 安装</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## 检查扩展偏好设置
|
||||
|
||||
AnyImage 的扩展偏好设置按功能分组。Environment 区包含:
|
||||
|
||||
- **Storage Root**:保存运行环境、模型和任务文件的位置。默认是用户目录下的 `.anyimage`,一般无需修改。
|
||||
- **Device**:正式转换任务使用的计算设备。Apple Silicon Mac 默认使用 CPU,Windows 默认使用 DirectML;也可手动选择其他可用设备。
|
||||
|
||||
macOS arm64 提供 **CPU** 与 **CoreML**。CoreML 由系统在 Apple CPU、GPU 与 Neural Engine 之间调度,不是 PyTorch MPS backend,也不表示只使用 GPU;它的实际速度取决于模型。
|
||||
|
||||
Environment 检测后显示 Diagnostics。Depth & Normal、Remove Background、Upscale 与 Cutout 是默认展开的可折叠功能区,各自包含对应设置和模型状态。其中:
|
||||
|
||||
- **Default Upscale Model**:Upscale 使用的模型,默认是 General WDN x4v3。
|
||||
- **Maximum AI Input Size**:Upscale、Depth 与 Normal Map 的 AI 输入长边上限,默认 2048 px;颜色贴图保留原始分辨率。
|
||||
- **Maximum Cutout Mesh Resolution**:所有 Cutout Shape 的网格采样长边上限,默认 1024,独立于 AI 输入尺寸。
|
||||
|
||||
Depth & Normal 提供三档 MoGe-2,Depth Plane 与 Cutout Depth / Normal Map 共用当前选择。可选模型的 **Ready** 状态或 **Download** 按钮位于选择项下方;Cutout 区提供独立网格上限与 Balloon Profile。
|
||||
|
||||
如果准备把 Storage Root 放到其他磁盘,建议在首次安装 Environment 之前修改。更换目录后,旧目录中的环境和模型不会自动移动。
|
||||
|
||||
## 配置 AI Environment
|
||||
|
||||
选中 Image Empty,在右键 **AnyImage** 子菜单中点击 **Set Up AI Environment…**;也可以在 AnyImage 扩展偏好设置中点击同名按钮。
|
||||
|
||||
确认安装前,对话框会说明以下内容:
|
||||
|
||||
- 安装独立 Python、ONNX Runtime 与支持包,并下载 **MoGe-2 ViT-S Normal**、**BEN2 Base** 和 **Real-ESRGAN General WDN x4v3**。
|
||||
- 默认模型合计约 360 MB;连同 Environment 预计需要约 600 MB 下载量和约 1 GB 磁盘空间。
|
||||
- 根据网络速度,首次安装通常需要 5–20 分钟。
|
||||
- **Use Mirror** 只让 uv、Python 和 Python 包使用国内镜像;模型下载源保持不变。
|
||||
|
||||
安装过程中请保持 Blender 打开。状态栏会显示当前阶段、进度和下载速度。
|
||||
|
||||
如果 Environment 已安装但任一默认模型缺失,入口会改为 **Download Required Models…**;对话框列出缺失模型和下载体积,确认后只下载缺失模型,不会重装 Environment 或重复下载已就绪内容。
|
||||
|
||||
## 确认安装完成
|
||||
|
||||
完成后可以用以下方式确认:
|
||||
|
||||
- AnyImage 扩展偏好设置中的 MoGe-2 ViT-S Normal、BEN2 Base 和 Real-ESRGAN General WDN x4v3 显示 **Ready**。
|
||||
- 选中 Image Empty 后,右键菜单底部显示 **AI Environment Settings…**。
|
||||
|
||||
点击 **Convert to Depth Plane**、**Remove Background** 或 **Upscale** 时,AnyImage 会根据状态进入 **Set Up AI Environment…** 或 **Download Required Models…**。普通 Plane、Image 编辑和基础 Cutout Surface / Balloon 直接在 Blender 中执行。
|
||||
|
||||
## 下一步
|
||||
|
||||
继续阅读[五分钟上手](quick-start.md),用一张图片完成第一次转换。
|
||||
@@ -1,57 +0,0 @@
|
||||
# Depth Plane
|
||||
|
||||
Depth Plane 将图片转换为带位移的立体面片。它适合快速场景搭建、2.5D 合成、立体插画以及视频素材。
|
||||
|
||||
## 创建 Depth Plane
|
||||
|
||||
1. 选中 Image Empty。
|
||||
2. 在 3D View 中右键,选择 **SnapForm > Convert to Depth Plane**。
|
||||
3. 选择深度来源和需要的选项。
|
||||
4. 确认后等待任务完成。
|
||||
|
||||
## 选择 Depth From
|
||||
|
||||
### MoGe-2
|
||||
|
||||
默认推荐。MoGe-2 会估算尺度一致的深度、法线和有效区域,通常适合单张图片和逐帧动画。模型越大,细节潜力和资源占用通常越高。
|
||||
|
||||
- **Resolution Level** 越高,处理更细致,也会更慢并占用更多内存。
|
||||
- 第一次使用建议保持默认值 `5`。
|
||||
|
||||
### DA3
|
||||
|
||||
适合尝试不同的深度估算结果。DA3 使用 **Resolution Scale** 控制处理尺寸;界面会显示实际 Process Res。
|
||||
|
||||
- 较低比例适合预览和显存有限的设备。
|
||||
- 较高比例可能保留更多细节,但不保证每张图片都更好。
|
||||
|
||||
### Original
|
||||
|
||||
直接把原图亮度作为深度来源,不运行深度估算模型。它适合已经准备好灰度高度图或手绘深度图的情况。
|
||||
|
||||
## 转换选项
|
||||
|
||||
- **Remove Background**:转换期间使用 BEN2 去除背景,让 Alpha 同时控制可见区域和背景深度。
|
||||
- **Shadeless**:让贴图颜色不受场景灯光影响,适合插画、合成和需要保持原图观感的场景。
|
||||
- **Keep Original**:保留源 Image Empty。默认关闭,转换成功后由新网格替代原对象。
|
||||
|
||||
## 调整结果
|
||||
|
||||
生成对象的 Geometry Nodes 修改器提供以下常用参数:
|
||||
|
||||
- **Displacement**:`Geometry` 直接移动网格;`Material` 保持平面并使用材质置换。
|
||||
- **Subdivision**:控制网格密度。数值越高,深度细节越多,场景也越重。
|
||||
- **Background Depth**:设置背景区域的位置。
|
||||
- **Depth Scale**:控制深度强度;负值可以反转前后关系。
|
||||
|
||||
调整时建议先降低 Subdivision 提高交互速度,确定 Depth Scale 后再提高细分。
|
||||
|
||||
## 动画输入
|
||||
|
||||
Depth Plane 支持视频和序列帧。处理范围跟随 Image Empty 的 **Image Frames** 与 **Offset**,生成后的颜色和深度会随场景帧播放。
|
||||
|
||||
动画结果会引用任务目录中的图片序列。移动工程或清理磁盘前,请先阅读[视频与序列帧](animation.md)和[模型、设备与存储](models-and-storage.md)。
|
||||
|
||||
## 进一步了解
|
||||
|
||||
需要查看节点、数据字段、色彩空间和位移计算方式时,请阅读[Depth Plane 实现参考](depth-plane.md)。
|
||||
@@ -1,46 +0,0 @@
|
||||
# Surface
|
||||
|
||||
Surface 从单张图片构建带透视关系的纹理网格。它适合建筑、室内、街景和需要匹配原始观察视角的画面。
|
||||
|
||||
!!! warning
|
||||
Surface 当前只支持单张图片。视频和序列帧请使用 [Depth Plane](guide-depth-plane.md)。
|
||||
|
||||
## 创建 Surface
|
||||
|
||||
1. 选中只包含一帧的 Image Empty。
|
||||
2. 在 3D View 中右键,选择 **SnapForm > Convert to Surface**。
|
||||
3. 选择模型和转换选项。
|
||||
4. 确认后等待网格、材质和 Geometry Nodes 修改器创建完成。
|
||||
|
||||
## 模型与分辨率
|
||||
|
||||
默认的 **MoGe-2 ViT-S Normal** 适合多数图片。MoGe-2 使用 **Resolution Level**,DA3 使用 **Resolution Scale**。提高分辨率会增加耗时和内存占用,建议先用默认值判断整体结构。
|
||||
|
||||
如果某个模型名称后显示 **(Missing)**,请先在 3D View 的 **SnapForm > Models** 面板下载它。
|
||||
|
||||
## 转换选项
|
||||
|
||||
- **Create Matching Camera**:创建与图片投影相匹配的透视相机。相机会成为 Surface 的子级,移动 Surface 时仍保持对应关系。
|
||||
- **Remove Background**:在生成网格前删除背景三角面。转换后关闭节点中的 Mask 也不会恢复这些面。
|
||||
- **Shadeless**:让贴图颜色不受场景灯光影响。
|
||||
- **Keep Original**:保留源 Image Empty。
|
||||
|
||||
## 调整网格
|
||||
|
||||
生成对象的 Geometry Nodes 修改器负责剔除深度断层、合并网格、修复拓扑、平滑和重新投影 UV。常用参数包括:
|
||||
|
||||
- **Iterations**:控制重拓扑迭代次数。设为 `0` 可以跳过迭代,便于观察原始结构或提高速度。
|
||||
- **Resolution**:控制合并后的网格密度。值越大,结果越简化。
|
||||
- **Smooth**:控制表面平滑程度。
|
||||
- **Depth Continuity**:剔除深度断层处的面。提高数值会更积极地清理边缘。
|
||||
- **Confidence**:DA3 Surface 可用,用于过滤低置信度区域。
|
||||
|
||||
先调整 Culling 让主体边缘干净,再调整 Resolution 和 Smooth,通常更容易获得稳定结果。
|
||||
|
||||
## 匹配相机的使用方式
|
||||
|
||||
开启 **Create Matching Camera** 后,切换到生成的相机视图即可重新对齐原图构图。Surface 与相机存在父子关系,建议把二者作为一个整体移动或缩放。
|
||||
|
||||
## 进一步了解
|
||||
|
||||
需要查看源网格属性、重拓扑过程、相机匹配和材质法线实现时,请阅读[Surface 实现参考](surface.md)。
|
||||
@@ -1,54 +1,43 @@
|
||||
# SnapForm
|
||||
# AnyImage
|
||||
|
||||
把图片里的空间感带进 Blender。
|
||||
|
||||
SnapForm 是一款 Blender 扩展,可以把照片、视频或序列帧转换为带真实深度变化的立体面片。你只需导入图片、选中 Image Empty,然后从右键菜单选择需要的结果。
|
||||
AnyImage 是一款 Blender 扩展,可以粘贴和编辑图片、创建普通 Plane 或 Depth Plane、从局部选区生成 Cutout Shape,并在独立 AI Environment 中完成去背景与高清放大。
|
||||
|
||||
- 用 **Depth Plane** 快速得到可调节的浮雕式面片
|
||||
- 用 **Surface** 从单张图片生成透视纹理网格
|
||||
- 用 **Remove Background** 一键获得透明前景
|
||||
- 处理视频和序列帧,生成可随时间播放的 Depth Plane
|
||||
- 使用 Geometry Nodes 继续调整深度、细分、重拓扑与平滑效果
|
||||
- 按 **Ctrl+V**(macOS 为 **Cmd+V**)把剪贴板图片粘贴到当前编辑器
|
||||
- 用 **Plane** 创建可调厚度和细分的图片网格
|
||||
- 用 **Depth Plane** 把静态图片转换为可调节的立体面片
|
||||
- 用 **Cutout** 从套索选区生成 Surface、Balloon 与两种 Depth Shape
|
||||
- 用 **Frame** 从当前视角重新取景并合并 Image Empty,用 **Mask** 的 Lasso、Brush 或 Polyline 编辑 Alpha;Brush 拖动时以红色或灰色填充预览准确范围,释放后提交;用 **Rectify** 校正透视
|
||||
- 用 **Remove Background / Upscale** 处理单图、视频和序列帧
|
||||
|
||||
!!! info "运行环境与 Blender 分离"
|
||||
SnapForm 的 AI 计算运行在独立环境中,不会把 ONNX Runtime 等计算依赖装进 Blender Python。首次配置完成后,日常操作都可以从 Image Empty 的右键菜单完成。
|
||||
|
||||
## 从这里开始
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- **安装 SnapForm**
|
||||
|
||||
下载与你的系统匹配的扩展包,完成环境和默认模型配置。
|
||||
|
||||
[阅读安装指南](installation.md)
|
||||
|
||||
- **五分钟上手**
|
||||
|
||||
从导入一张图片开始,完成第一次 Depth Plane 转换。
|
||||
|
||||
[开始第一个作品](quick-start.md)
|
||||
|
||||
- **选择合适的结果**
|
||||
|
||||
了解 Depth Plane、Surface 和去背景之间的区别。
|
||||
|
||||
[查看工作流对比](workflows.md)
|
||||
|
||||
- **遇到问题?**
|
||||
|
||||
检查模型下载、菜单状态、动画文件和运行日志。
|
||||
|
||||
[查看常见问题](faq.md)
|
||||
|
||||
</div>
|
||||
|
||||
## 使用要求
|
||||
|
||||
- Blender 4.5 或更高版本
|
||||
- Windows x64、macOS Apple Silicon 或 Linux x64
|
||||
- 首次安装环境与模型时需要网络连接
|
||||
- 正式使用前需要安装独立 Environment,以及默认的 MoGe-2 和 BEN2 模型
|
||||
|
||||
!!! tip
|
||||
不确定从哪里开始时,先使用默认设置将一张人物或物体照片转换为 Depth Plane。它速度快、参数直观,也最适合熟悉 SnapForm 的基本工作方式。
|
||||
<div class="anyimage-install">
|
||||
<div class="anyimage-install-item" data-platform="windows-x64">
|
||||
<div class="anyimage-install-group">
|
||||
<button type="button" draggable="true" class="anyimage-install-button" data-install-url="https://extensions.omoolab.xyz/AnyImage.v1.0.1.windows-x64.zip?repository=.%2Findex.json&blender_version_min=4.5.0&platforms=windows-x64&python_versions=3.11%2C3.13">
|
||||
<i class="i-move"></i><span>拖入 Blender 安装</span>
|
||||
</button>
|
||||
<div class="anyimage-install-platform"><strong>Windows</strong> – 78.3 MB</div>
|
||||
</div>
|
||||
<small class="anyimage-install-note">…或<a href="https://extensions.omoolab.xyz/AnyImage.v1.0.1.windows-x64.zip" download="AnyImage.v1.0.1.windows-x64.zip">下载</a>后通过 <a href="https://docs.blender.org/manual/zh-hans/latest/editors/preferences/extensions.html#install" target="_blank">Install from Disk</a> 安装</small>
|
||||
</div>
|
||||
<div class="anyimage-install-item" data-platform="macos-arm64">
|
||||
<div class="anyimage-install-group">
|
||||
<button type="button" draggable="true" class="anyimage-install-button" data-install-url="https://extensions.omoolab.xyz/AnyImage.v1.0.1.macos-arm64.zip?repository=.%2Findex.json&blender_version_min=4.5.0&platforms=macos-arm64&python_versions=3.11%2C3.13">
|
||||
<i class="i-move"></i><span>拖入 Blender 安装</span>
|
||||
</button>
|
||||
<div class="anyimage-install-platform"><strong>macOS<span class="anyimage-install-platform-rest"> - Apple Silicon</span></strong> – 57.5 MB</div>
|
||||
</div>
|
||||
<small class="anyimage-install-note">…或<a href="https://extensions.omoolab.xyz/AnyImage.v1.0.1.macos-arm64.zip" download="AnyImage.v1.0.1.macos-arm64.zip">下载</a>后通过 <a href="https://docs.blender.org/manual/zh-hans/latest/editors/preferences/extensions.html#install" target="_blank">Install from Disk</a> 安装</small>
|
||||
</div>
|
||||
<div class="anyimage-install-item" data-platform="linux-x64">
|
||||
<div class="anyimage-install-group">
|
||||
<button type="button" draggable="true" class="anyimage-install-button" data-install-url="https://extensions.omoolab.xyz/AnyImage.v1.0.1.linux-x64.zip?repository=.%2Findex.json&blender_version_min=4.5.0&platforms=linux-x64&python_versions=3.11%2C3.13">
|
||||
<i class="i-move"></i><span>拖入 Blender 安装</span>
|
||||
</button>
|
||||
<div class="anyimage-install-platform"><strong>Linux</strong> – 71.0 MB</div>
|
||||
</div>
|
||||
<small class="anyimage-install-note">…或<a href="https://extensions.omoolab.xyz/AnyImage.v1.0.1.linux-x64.zip" download="AnyImage.v1.0.1.linux-x64.zip">下载</a>后通过 <a href="https://docs.blender.org/manual/zh-hans/latest/editors/preferences/extensions.html#install" target="_blank">Install from Disk</a> 安装</small>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,53 +0,0 @@
|
||||
# 安装与首次配置
|
||||
|
||||
SnapForm 的安装分为两步:先安装 Blender Extension,再安装独立运行环境和默认模型。第二步只需在首次使用或环境版本发生变化时执行。
|
||||
|
||||
## 安装 Blender Extension
|
||||
|
||||
1. 下载与你的系统匹配的 ZIP 文件:
|
||||
- `SnapForm.v版本号.windows-x64.zip`
|
||||
- `SnapForm.v版本号.macos-arm64.zip`
|
||||
- `SnapForm.v版本号.linux-x64.zip`
|
||||
2. 打开 Blender,进入 **Edit > Preferences > Extensions**。
|
||||
3. 点击右上角菜单,选择 **Install from Disk**。
|
||||
4. 选择下载的 ZIP 文件并完成安装。
|
||||
|
||||
!!! warning
|
||||
不要解压扩展包。Blender 需要直接安装 ZIP 文件,并且安装包必须与你的操作系统匹配。
|
||||
|
||||
## 检查扩展偏好设置
|
||||
|
||||
在 SnapForm 的扩展偏好设置中可以看到两个选项:
|
||||
|
||||
- **Storage Root**:保存运行环境、模型和任务文件的位置。默认是用户目录下的 `.snapform`,一般无需修改。
|
||||
- **Device**:正式转换任务使用的计算设备。SnapForm 默认选择本机可用的最快设备;如果 GPU Provider 不可用,会使用 CPU。
|
||||
|
||||
如果准备把 Storage Root 放到其他磁盘,建议在首次安装 Environment 之前修改。更换目录后,旧目录中的环境和模型不会自动移动。
|
||||
|
||||
## 安装 Environment
|
||||
|
||||
打开 3D View,按 `N` 显示侧栏并切换到 **SnapForm** 页签。在 **Job Server** 面板中选择一个安装入口:
|
||||
|
||||
- **Install Environment**:使用官方源,适合能稳定访问 GitHub、Python 下载源和 PyPI 的网络。
|
||||
- **Install Environment (Mirror)**:使用国内镜像下载 uv、Python 和 Python 包。
|
||||
|
||||
安装程序会依次准备 uv、独立 Python 和运行包。Environment 完成后,SnapForm 还会下载日常功能需要的 **MoGe-2 ViT-S Normal** 与 **BEN2 Base**。
|
||||
|
||||
!!! note
|
||||
两个安装按钮只影响 Environment 的下载源。模型会按自己的下载源与备用镜像规则获取。
|
||||
|
||||
安装过程中请保持 Blender 打开。状态栏会显示当前阶段、进度和下载速度。首次安装耗时取决于网络与磁盘速度。
|
||||
|
||||
## 确认安装完成
|
||||
|
||||
完成后可以用以下方式确认:
|
||||
|
||||
- **Job Server** 不再显示 “Environment is not installed”。
|
||||
- 展开 **Models** 面板,MoGe-2 ViT-S Normal 和 BEN2 Base 显示 **Ready**。
|
||||
- 选中 Image Empty 后,右键菜单中的 **Convert to Depth Plane**、**Convert to Surface** 和 **Remove Background** 可以点击。
|
||||
|
||||
如果 Environment 已安装但默认模型缺失,Image Empty 的 **SnapForm** 右键子菜单会显示 **Download Required Models**。点击一次即可补齐缺失项。
|
||||
|
||||
## 下一步
|
||||
|
||||
继续阅读[五分钟上手](quick-start.md),用一张图片完成第一次转换。
|
||||
34
docs/internals/architecture.md
Normal file
34
docs/internals/architecture.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# 程序架构
|
||||
|
||||
| 模块 | 职责 |
|
||||
| --- | --- |
|
||||
| `__init__.py` | 注册类型、工具、菜单、快捷键与 Runtime |
|
||||
| `menu.py`、`panel.py` | Image Empty 与 Shader Image Texture 上下文菜单与 Job Server 面板 |
|
||||
| `preferences.py` | 扩展设置及读取入口 |
|
||||
| `properties.py` | Scene 手势状态、模型状态查询、枚举与 Operator 属性构造 |
|
||||
| `common/` | Blender 侧公共能力 |
|
||||
| `operators/` | 交互、任务参数、结果导入和 Undo |
|
||||
| `server/` | 独立环境内的媒体处理、推理和产物写出 |
|
||||
|
||||
## 注册与配置
|
||||
|
||||
注销先关闭 Runtime、快捷键与工具,再移除菜单和 Scene 属性,按 `CLASSES` 逆序注销类型。设备、存储目录、模型、材质显示适配和功能上限统一通过 `preferences.py` 读取。
|
||||
|
||||
**Maximum AI Input Size** 限制模型分析输入和 Upscale 可提交源图的长边;**Maximum Frame Resolution** 限制 Frame 输出长边;**Minimum Relative Edge Length** 与 **Boundary Padding** 控制 Cutout 的网格密度和轮廓边界采样。
|
||||
|
||||
## 执行边界
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Operator 在主线程读取 Blender 数据] --> B[准备输入文件]
|
||||
B --> C[request 返回普通字典]
|
||||
C --> D[BlendJob 队列执行 Server Job]
|
||||
D --> E[模型与媒体处理写入 Job 目录]
|
||||
E --> F[主线程 response 加载结果]
|
||||
F --> G[提交 Image 或 Object 修改及 Undo]
|
||||
G --> H[清理临时输入]
|
||||
```
|
||||
|
||||
Plane、Frame、Mask、Rectify 和无需 AI 的 Cutout 在 Blender 内执行。Server 的 `jobs/` 编排任务,`models/` 负责模型适配,`geometry/` 处理深度与法线产物。Server 模块通过内部相对导入共享能力,Blender 侧通过 `common/` 复用。
|
||||
|
||||
MoGe-2 与 MoGe-3 输出统一为 `GeometryFrame`:包含 Depth、Validity、Intrinsics,以及按需提供的 Normal 和 Points。产物写入器消费同一个 Frame,保持深度、法线和相机数据一致。参见 [Runtime](runtime.md) 与 [产物协议](outputs.md)。
|
||||
44
docs/internals/common.md
Normal file
44
docs/internals/common.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Blender 公共能力
|
||||
|
||||
`src/anyimage/common/` 按业务职责提供公共函数,调用方从对应模块直接导入。
|
||||
|
||||
| 模块 | 职责与主要入口 |
|
||||
| --- | --- |
|
||||
| `ai.py` | 环境和模型就绪检查、输入路径校验、`production_device()`、`moge2_parameters()` 与安装 UI |
|
||||
| `image.py` | `image_rgba()`、Image User 帧设置读取、Job 输入准备、像素采样、结果 Image 和 `replace_empty_image()` |
|
||||
| `color_image.py` | 材质颜色与 AI 分析输入的准备和清理 |
|
||||
| `hdr_image.py` | HDR 去背景分析输入及浮点颜色保留 |
|
||||
| `image_target.py` | Image Empty 与 Shader Image Texture 编辑目标 |
|
||||
| `selection.py` | `SelectionPath`、`SelectionMask`、non-zero winding 栅格化与 Alpha 修整 |
|
||||
| `viewport.py` | 原生点选、屏幕投影、`ImageGesture`、Lasso / Brush / Polyline 与 Overlay |
|
||||
| `depth.py` | 深度图片和元数据加载、相机坐标与显示范围的尺度换算 |
|
||||
| `material.py` | Image Layer、Normal、Alpha、Shadeless 与 Depth 材质 |
|
||||
| `object.py` | Modifier 输入设置与结果对象激活、源对象替换 |
|
||||
| `node.py` | `node_asset_path()` 与 `load_node_group()` |
|
||||
|
||||
## 图像生命周期
|
||||
|
||||
业务像素采用 Top-down RGBA,`image_pixels()` 使用 Blender 像素顺序。预乘采样用于投影插值;文件或 Packed 图像的业务读取保留隐藏 RGB。`ImageEditTarget` 校验目标身份后经 `replace_empty_image()` 或 `replace_texture_image()` 提交:未共享的 Image 原地替换内容,共享时改用新的 Image 数据块,失败时回滚源数据。
|
||||
|
||||
`prepare_image_input()` 返回路径及临时标记;`cleanup_image_input()` 与创建方配对。`color_image.py` 的 `prepare_material_color_input()` 准备材质颜色,`material_analysis_input()` 提供分析输入;Cutout 按几何 bounds 裁切。
|
||||
|
||||
## 手势与 Selection
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[原生对象点选] --> B[ImageGesture 冻结源图与交互状态]
|
||||
B --> C[屏幕路径与 Overlay]
|
||||
C --> D[提交时投影到图片空间]
|
||||
D --> E[SelectionPath]
|
||||
E --> F[局部 non-zero winding 栅格化]
|
||||
F --> G[SelectionMask values 与 bounds]
|
||||
G --> H[Mask Alpha 运算或 Cutout 内容轮廓]
|
||||
```
|
||||
|
||||
Brush 的圆形印记和连接条由 `brush_footprint_polygons()` 生成,供预览与提交共用。拖动时维护屏幕路径与局部 Overlay,提交时生成覆盖值。Mask、Rectify 与 Cutout 的空命中保留 active Image Empty;点中另一对象时完成原生选择。
|
||||
|
||||
## 材质、节点与对象
|
||||
|
||||
`create_image_material()` 按 Shadeless、Normal 和 Depth 组合节点、连接颜色与 Alpha;材质显示适配读取当前 Preferences。`set_modifier_input()` 按实际 RNA 输入形式设置值,`finalize_object_result()` 完成结果对象激活和源对象处理。Image 替换路径保留 Empty 的矩阵、显示尺寸、偏移和 Image User 帧设置。
|
||||
|
||||
Geometry Nodes 从 `src/anyimage/assets/O_AnyImage.blend` 按公开名称加载。构建与布局职责见[节点资产](node-assets.md),深度字段见[Job 产物](outputs.md)。
|
||||
48
docs/internals/development.md
Normal file
48
docs/internals/development.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# 开发与发布
|
||||
|
||||
仓库使用 CPython 3.11 与 `bpy==4.5.3` 测试 Blender 代码;AI Server 使用 `runtime.py` 声明的独立 Python 3.12 Environment。
|
||||
|
||||
```bash
|
||||
uv sync
|
||||
uv run pytest
|
||||
```
|
||||
|
||||
## 测试组织
|
||||
|
||||
| 测试范围 | 文件 |
|
||||
| --- | --- |
|
||||
| 扩展注册、设置、安装和 Job 交互 | `test_addon_registration.py`、`test_preferences.py`、`test_ai_setup.py`、`test_job_operator.py` |
|
||||
| Frame、Mask、Rectify、Plane、Cutout | 对应 `test_*_tool.py`、`test_*_operators.py` 与对象、投影、网格测试 |
|
||||
| 图像身份、Alpha、材质与 Undo | 图像编辑集成、共享替换、材质适配与 Undo 测试 |
|
||||
| Server | `test_job_server.py`、`test_server_jobs.py`、`test_input_validation.py`、`test_geometry_artifacts.py` |
|
||||
| 模型 | `test_model_manager.py`、下载、推理与各 ONNX 模型测试 |
|
||||
| 节点资产 | 布局与接口等价、几何求值、Cutout 与临时属性清理 |
|
||||
| 工具与包协议 | 扩展 Manifest、打包、模型同步与节点启动器 |
|
||||
|
||||
Blender Stub 共用 `tests/support/blender.py`;Server 和实际 Blender 几何 fixture 按使用范围存放在对应 support 模块。测试命名描述功能、触发条件与可观察结果。
|
||||
|
||||
## 发布命令
|
||||
|
||||
| 命令 | 职责 |
|
||||
| --- | --- |
|
||||
| `uv run pack` | 生成 Windows、macOS arm64、Linux 扩展包 |
|
||||
| `uv run pack --prepare` | 为本机同步扩展 wheel 与 Manifest |
|
||||
| `uv run node-group build` | 构建、验证节点资产并运行测试 |
|
||||
| `uv run model sync` | 准备、校验并上传模型声明的文件 |
|
||||
| `uv run docs -dev` | 本地预览文档 |
|
||||
| `uv run docs` | 通过 Mike 发布项目版本并更新 latest |
|
||||
|
||||
`tools/pack.py` 从 `pyproject.toml` 读取依赖,按平台与 Blender Python 版本解析 wheel,使用 Blender 提供的 NumPy。产物命名为 `AnyImage.v<version>.<platform>.zip`,写入 `dist/`。本地 BlendJob wheel 内容变化后运行 `uv lock --refresh`,并核对锁文件、Manifest 与打包测试。
|
||||
|
||||
`tools/models/cli.py` 根据模型目录选择上游 ONNX 或本地导出流程;`tools/models/exports.py` 处理对应 PyTorch 权重导出。文件校验依据声明大小和 SHA-256,上传使用逐文件复制。`uv run model prepare` 用于本地准备。
|
||||
|
||||
## CI 与文档导航
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[main push、Pull Request 或手动触发] --> B[test.yml 运行测试]
|
||||
C[v 前缀 Tag] --> D[build.yml 测试、打包并发布 ZIP]
|
||||
E[main 文档路径变化或手动触发] --> F[docs.yml 构建站点并部署到 Cloudflare Workers]
|
||||
```
|
||||
|
||||
站点导航由 `mkdocs.yml` 显式维护,页面移动时同步导航和相对链接。节点资产的脚本职责及检查入口见[节点资产](node-assets.md)。
|
||||
15
docs/internals/index.md
Normal file
15
docs/internals/index.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# 内部实现总览
|
||||
|
||||
AnyImage 在 Blender 主线程处理交互、像素编辑和数据创建,通过 BlendJob 把模型推理交给独立 Job Server。
|
||||
|
||||
| 主题 | 对应职责 |
|
||||
| --- | --- |
|
||||
| [程序架构](architecture.md) | 进程、注册、配置与主线程边界 |
|
||||
| [Common](common.md) | Blender 侧图像、手势、几何与 AI 公共能力 |
|
||||
| [Runtime 与 Server](runtime.md) | Environment、Job、模型下载与 Session 生命周期 |
|
||||
| [Job 产物](outputs.md) | 文件协议、相机坐标和 Blender 消费方式 |
|
||||
| [Operator 索引](operators/index.md) | 按实际用户功能查找实现 |
|
||||
| [节点资产](node-assets.md) | 资产、源码职责、构建和验证 |
|
||||
| [开发与发布](development.md) | 测试组织、工具命令与发布流程 |
|
||||
|
||||
源码位于 `src/anyimage/`,开发工具位于 `tools/`,自动化测试位于 `tests/`。扩展级设置由 `preferences.py` 定义,运行时资源由 `runtime.py` 接入 BlendJob。
|
||||
56
docs/internals/node-assets.md
Normal file
56
docs/internals/node-assets.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# 节点资产
|
||||
|
||||
## 资产清单
|
||||
|
||||
节点组由 `tools/nodes/groups/` 中的构建模块定义。
|
||||
|
||||
| 节点组 | 构建模块 |
|
||||
| ------------------------- | --------------------------- |
|
||||
| `O Image Plane` | `image_plane.py` |
|
||||
| `O Image Depth Plane` | `image_depth_plane.py` |
|
||||
| `O Image Relief Plane` | `image_relief_plane.py` |
|
||||
| `O Image Cutout` | `image_cutout.py` |
|
||||
| `O Image Depth Cutout` | `image_depth_cutout.py` |
|
||||
| `O Image Cutout Symmetry` | `image_cutout_symmetry.py` |
|
||||
| `O Image Layer` | `image_layer.py` |
|
||||
| `O Image Depth Layer` | `image_depth_layer.py` |
|
||||
| `O Shadeless` | `shadeless.py` |
|
||||
| `O Image Depth Panorama` | `image_depth_panorama.py` |
|
||||
|
||||
## 构建节点资产
|
||||
|
||||
生产节点组由 `uv run node-group build` 构建到 `src/anyimage/assets/O_AnyImage.blend`
|
||||
|
||||
节点组规则:
|
||||
|
||||
- 字段默认直接连接,多个消费者可复用同一输出;按消费位置的几何、域、类型和输入属性版本验证求值等价性
|
||||
- 所有几何节点、嵌套组及测试辅助图禁止使用 Capture Attribute;构建定义和保存资产递归检查 Capture 为零
|
||||
- 域转换使用 Evaluate on Domain 或明确域的采样节点表达;跨几何变化优先调整消费顺序或在源几何上采样,明确索引对应关系
|
||||
- Store Named Attribute 用于实际命名协议或仍需跨几何变化保存的最少数据,逐项明确名称、类型、域、Selection、写入阶段、消费者及必要性;内部临时属性使用 `_o_` 前缀,在最后消费者之后用通配符 `_o_*` 清理,保留 UV、用户属性和外部协议属性。清理必须走 `remove_attribute_pattern`:按精确名称删除时,若某个几何分支从未写入该属性,Remove Named Attribute 会报出缺失警告
|
||||
- Group Input 按区域重复放置,通过字段排列和必要的 Reroute 保持连线可读,隐藏未使用输出;存储须有数据生命周期依据
|
||||
- 构建阶段通过明确的节点或 socket 引用接线
|
||||
- 保留节点默认 `name` 和标题,不用 `label` 覆盖,不用 String 节点注解。Frame 仅用于确有包裹语义的区域
|
||||
- 全接线的简单数学运算可折叠;需要阅读的常数、运算域和模式保持可见
|
||||
- 比较统一用 Compare 节点并按操作数数据类型选择输入,布尔运算统一用 Boolean Math,不用 Math 节点做比较、也不用数值乘法代替布尔运算;材质树不支持这两个节点,仍用 Math
|
||||
- 修改器 Geometry Nodes 的输入 socket 默认使用 `SINGLE`
|
||||
- socket `subtype` 按业务语义设置:长度用 `DISTANCE`,`0–1` 比例用 `FACTOR`,`0–100` 用 `PERCENTAGE`;无对应范围语义时保留默认
|
||||
- 接口或布局变更须验证 socket 顺序与结构、未使用输出隐藏及几何求值;纯布局变更比较运算、默认值和实际连接,确认功能不变
|
||||
|
||||
## 构建资产脚本
|
||||
|
||||
`tools/nodes/build.py` 按依赖顺序构建资产,调用 `arrangement/` 统一排列几何节点组后保存。`common/` 提供共用构建函数:`nodes.py` 创建接口与节点,`depth_surface.py`、`cutout.py` 等组装几何,`shader.py` 构建材质着色节点,`material_slots.py` 继承几何材质槽;`tools/nodes/preview/` 导出交互预览。
|
||||
|
||||
保存后,`check.py` 在独立 Blender 进程中重新加载资产并做基本几何求值。接口、默认值和详细几何行为由 `tests/` 检查。
|
||||
|
||||
```bash
|
||||
uv run node-group build --blender "/path/to/blender"
|
||||
uv run node-group build --skip-tests
|
||||
uv run node-group check
|
||||
uv run node-group preview
|
||||
```
|
||||
|
||||
脚本按 `--blender`、环境变量 `BLENDER_BIN`、项目 `.env`、`PATH` 的顺序查找 Blender。无法自动找到时,在项目根目录的 `.env` 中配置:
|
||||
|
||||
```dotenv
|
||||
BLENDER_BIN="/absolute/path/to/blender"
|
||||
```
|
||||
42
docs/internals/operators/ai-setup.md
Normal file
42
docs/internals/operators/ai-setup.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# AI 环境管理
|
||||
|
||||
AI 环境管理从当前安装状态出发,补齐独立运行环境和必需模型,并提供设置入口与模型缓存释放操作。
|
||||
|
||||
## 执行过程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Entry["Set Up AI Server"] --> Status["读取环境与必需模型状态"]
|
||||
Status --> Ready{"是否全部就绪?"}
|
||||
Ready -- "是" --> Done["结束"]
|
||||
Ready -- "否" --> Environment{"运行环境是否就绪?"}
|
||||
Environment -- "否" --> Install["安装隔离 Python 环境"]
|
||||
Environment -- "是" --> Download["下载缺失的必需模型"]
|
||||
Install --> Download --> Done
|
||||
```
|
||||
|
||||
`SetupAIEnvironment` 是统一入口。首次使用时先安装运行环境,再下载默认模型;环境已经存在时,只补齐缺失模型。环境安装由 Blender Operator 启动,模型下载通过 Job Server 执行。
|
||||
|
||||
## 节点说明
|
||||
|
||||
### 读取状态,选择下一步
|
||||
|
||||
入口位于 `src/anyimage/operators/ai_setup.py`。`SetupAIEnvironment` 通过 `properties.py` 中的 `ai_status()` 读取环境和模型状态,并根据结果结束、安装环境或调用 `DownloadRequiredModels`。对话框同时显示预计下载量和缺失模型名称。
|
||||
|
||||
`runtime.py` 提供当前 Environment 与 Server 状态;Server 正忙时不会启动新的安装流程。`OpenAIEnvironmentSettings` 打开 AnyImage 的扩展首选项。
|
||||
|
||||
### 安装独立环境
|
||||
|
||||
环境尚未就绪时,`SetupAIEnvironment` 调用 Runtime 注册的安装 Operator。`use_mirror` 决定 uv、Python 和安装包的下载源,模型使用模型目录中声明的来源。
|
||||
|
||||
安装完成后通过 `post_install()` 下载默认模型,模型依赖加载于独立 Server 环境。
|
||||
|
||||
### 下载模型
|
||||
|
||||
`ai_setup.py` 中的 `DownloadModel` 处理单个模型,`DownloadRequiredModels` 处理默认必需模型集合。两者先检查 Environment、Blender Online Access 和模型记录,再提交 `download-model` 或 `download-required-models` Job。
|
||||
|
||||
Job 在 `server/app.py` 注册;`server/model_catalog.py` 保存模型名称、文件和来源,`server/model_download.py` 下载并校验文件,`server/model_manager.py` 管理模型目录与已加载 Session。下载进度和错误通过同一套 BlendJob Runtime 返回 Blender。
|
||||
|
||||
### 释放模型缓存
|
||||
|
||||
`ai_setup.py` 中的 `ClearModels` 调用 `runtime.clear_resource("model_manager")`,释放 Server 中已加载的模型 Session。下次推理从模型目录重新加载 Session。
|
||||
41
docs/internals/operators/clipboard-image.md
Normal file
41
docs/internals/operators/clipboard-image.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# 剪贴板图片
|
||||
|
||||
剪贴板图片从 `Ctrl/Cmd + V` 开始,把操作系统中的图片变成 Packed Blender Image,并放到当前编辑器支持的位置。
|
||||
|
||||
## 执行过程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Paste["Ctrl/Cmd + V"] --> Priority{"刚执行过 Blender Copy?"}
|
||||
Priority -- "是" --> Native["交回原生 Paste"]
|
||||
Priority -- "否" --> Read["读取系统图片"]
|
||||
Read --> Image["复用或创建 Packed Image"]
|
||||
Image --> Target{"当前编辑器"}
|
||||
Target --> View["Reference / Plane"]
|
||||
Target --> Node["Image Node"]
|
||||
Target --> Paint["Brush Texture"]
|
||||
```
|
||||
|
||||
Windows 快捷键优先处理 Blender 自己的 Copy/Paste,系统剪贴板出现新的图片内容时 AnyImage 接管粘贴;macOS 和 Linux 没有系统剪贴板序列号,始终由 AnyImage 处理粘贴。
|
||||
|
||||
## 节点说明
|
||||
|
||||
### 判断是否交给 Blender
|
||||
|
||||
`operators/clipboard_image/operators.py` 定义 `TrackNativeCopy`、`PasteClipboardImage` 和 3D View、Node Editor 快捷键。`TrackNativeCopy` 记录 Blender Copy 时的系统剪贴板序列号;粘贴时若序列号没有变化,`PasteClipboardImage` 返回 `PASS_THROUGH`,让 Blender 继续处理对象或节点粘贴。
|
||||
|
||||
同一文件随后调用 `actions.target_for_context()` 判断当前编辑器是否支持图片落点。
|
||||
|
||||
### 读取系统图片
|
||||
|
||||
`clipboard.py` 负责平台差异。Windows 依次尝试 PNG、DIBV5 和 DIB,DIB 补成 BMP;macOS 通过 `osascript` 导出 PNG;Linux 依次尝试 `wl-paste` 和 `xclip` 提供的图片格式。各平台输出统一为图片字节和扩展名,再交给后续步骤。
|
||||
|
||||
### 创建或复用 Packed Image
|
||||
|
||||
`actions.py` 对图片内容计算 SHA-256。相同内容已经粘贴过时会复用现有 Packed Image,否则创建新 Image 并 Pack 到当前 `.blend`,避免依赖临时剪贴板文件。
|
||||
|
||||
### 放到当前编辑器
|
||||
|
||||
落点仍在 `actions.py` 中完成:3D View 创建 Reference 或 Plane,Node Editor 创建与当前节点树匹配的 Image 节点,绘制模式把 Image 配置为当前画笔的 Texture。鼠标位置和视图位置也在这里换算。
|
||||
|
||||
Plane 路径调用 `convert_to_plane/image_plane.py` 创建中心原点基础网格,并从节点资产加载 `O Image Plane`。`actions.py` 创建图像材质并放入 Mesh 第 `0` 个材质槽,节点组只生成几何。
|
||||
14
docs/internals/operators/convert-to-panorama.md
Normal file
14
docs/internals/operators/convert-to-panorama.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Panorama 转换
|
||||
|
||||
`operators/convert_to_panorama/` 将静态等距柱状 Image Empty 转换为径向深度表面,提交前裁剪出最大居中 2:1 区域。`operators.py` 提交任务,`object.py` 导入对象,`__init__.py` 汇总导出与注册清单。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[准备颜色与分析图] --> B[generate-panorama-geometry]
|
||||
B --> C[采样 12 个透视视图并推理]
|
||||
C --> D[融合径向距离与有效性]
|
||||
D --> E[写出 depth.exr 与 depth.json]
|
||||
E --> F[创建材质与 O Image Depth Panorama]
|
||||
```
|
||||
|
||||
任务使用 Preferences 选择的几何模型。源颜色由 Blender 侧保存,Server 的 `geometry/panorama.py` 负责视图采样与距离融合,`jobs/panorama.py` 编排推理和产物写入。
|
||||
21
docs/internals/operators/convert-to-plane.md
Normal file
21
docs/internals/operators/convert-to-plane.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Plane 转换
|
||||
|
||||
`operators/convert_to_plane/operators.py` 提供 `ConvertToPlane`、`ConvertToDepthPlane`、`ConvertToReliefPlane` 与 `GenerateDepthPlane`。`object.py` 导入对象和材质,`image_plane.py` 构建基础矩形网格,`__init__.py` 汇总导出与注册清单。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Image Empty] --> B[准备材质颜色]
|
||||
B --> C{转换类型}
|
||||
C -- Plane --> D[四边形与 O Image Plane]
|
||||
C -- Depth 或 Relief --> E[静态图提交几何 Job]
|
||||
E --> F[加载深度、元数据与法线]
|
||||
F --> G[对应节点修改器与材质]
|
||||
D --> H[创建对象并替换源 Empty]
|
||||
G --> H
|
||||
```
|
||||
|
||||
三种转换都只接受静态图片。基础网格以局部中心为原点,对象矩阵补偿源 Empty 的显示范围。
|
||||
|
||||
Depth 与 Relief 从 Preferences 读取几何模型、分辨率级别和 AI 输入上限。`create_depth_plane_from_result()` 导入产物并创建修改器。`O Image Depth Plane` 使用相机 XYZ 与对象空间法线构建深度表面;`O Image Relief Plane` 使用深度 Z 与切线空间法线构建浮雕。
|
||||
|
||||
节点构建见[节点资产](../node-assets.md),文件协议见[Job 产物](../outputs.md)。
|
||||
43
docs/internals/operators/cutout-tool.md
Normal file
43
docs/internals/operators/cutout-tool.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Cutout
|
||||
|
||||
Cutout 在静态 Image Empty 上圈定 Selection,按轮廓创建 Flat、Solid、Depth Symmetry 或 Depth Solid。入口位于 `operators/cutout_tool/operators.py`,Shape 菜单位于 `interaction.py`,预设标识与节点组对应关系位于 `shape.py`。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Lasso 或 Polyline 提交 SelectionPath] --> B[选择 Shape]
|
||||
B --> C[栅格化 SelectionMask 并读取源 RGBA]
|
||||
C --> D{需要 Depth 或 Normal?}
|
||||
D -- 是 --> E[准备 bounds 矩形 RGBA]
|
||||
E --> F[generate-cutout-artifacts]
|
||||
F --> G[加载 Depth、Metadata 与 Normal]
|
||||
D -- 否 --> H[源 Alpha 与 SelectionMask 组合]
|
||||
G --> H
|
||||
H --> I[边界映射、轮廓提取与质量三角化]
|
||||
I --> J[BaseShape 与 o_balloon]
|
||||
J --> K[创建材质、对象与对应节点修改器]
|
||||
```
|
||||
|
||||
| 预设 | 节点组 | AI 产物 |
|
||||
| --- | --- | --- |
|
||||
| Flat | `O Image Cutout`,Thickness 0 | 可选切线空间法线 |
|
||||
| Solid | `O Image Cutout`,Thickness 1 | 可选切线空间法线 |
|
||||
| Depth Symmetry | `O Image Depth Cutout` 与 `O Image Cutout Symmetry` | 深度与对象空间法线 |
|
||||
| Depth Solid | `O Image Depth Cutout` | 深度与对象空间法线 |
|
||||
|
||||
Flat 与 Solid 可以不带 AI 产物生成;Depth 预设和 Normal Map 需要就绪的几何模型,Shape 菜单按该状态提供选项。
|
||||
|
||||
## 轮廓与网格
|
||||
|
||||
`geometry.py` 从内容 Alpha 提取连通轮廓与孔洞,按 Edge Length 生成 `BaseShape`。Edge Length 是世界空间的网格目标边长,Minimum Relative Edge Length 会按内容范围提高其下界。
|
||||
|
||||
`mesh.py` 负责轮廓分离、孔洞处理、质量三角化和边界支撑,`polygon.py` 提供轮廓简化,`tangle.py` 是随包分发的 Delaunay 三角化核心。Fine Outline 展开时保留小孔与细节轮廓,关闭时填洞并简化。
|
||||
|
||||
`balloon.py` 负责 Balloon Profile,按 Preferences 选择 Poisson 或 Teddy,生成所有预设共用的 POINT 属性 `o_balloon`。`boundary_padding.py` 按 Boundary Padding 沿结构内法线重采样轮廓附近的颜色与深度。
|
||||
|
||||
## AI 与对象创建
|
||||
|
||||
Server 接收 Selection bounds 内保留源 Alpha 的 RGBA,一次 MoGe 推理按 `generate_depth` 与 `normal_mode` 返回产物。深度参考区域使用所选 Alpha Threshold,Normal 使用原预测;Maximum AI Input Size 只影响分析分辨率。
|
||||
|
||||
`object.py` 的 `create_shape_object()` 消费 BaseShape、Color、Depth 和 Normal,加载对应节点组。基础网格位于局部 XZ 平面,厚度沿 Y 展开;对象矩阵把网格放入 Image Empty 的图片平面,Depth Symmetry 额外叠加对称节点组与方向参数。Object Normal 随方向同步转换。
|
||||
|
||||
材质使用 IOR 1.2,颜色与法线接入 Image Layer,法线按切线或对象空间解释。节点计算见[节点资产](../node-assets.md)。
|
||||
21
docs/internals/operators/frame.md
Normal file
21
docs/internals/operators/frame.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Frame 多图合成
|
||||
|
||||
`operators/frame_tool/operators.py` 定义 `FrameImages` 和 `FrameTool`。`projection.py` 负责视图投影、交叠和结果平面定位,`compositing.py` 负责像素深度排序与边界覆盖采样,`__init__.py` 导出类型及注册清单。Frame 将选中的静态 Image Empty 按当前视图合成为一张图片。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[拖出屏幕矩形] --> B[冻结视图矩阵并收集选中静态图片]
|
||||
B --> C[检查矩形与 active 图片的有效投射交集]
|
||||
C --> D[按 Region 与 Maximum Frame Resolution 计算输出尺寸]
|
||||
D --> E[观察射线投影到各图片平面]
|
||||
E --> F[按样本深度从远到近合成,等深度 active 优先]
|
||||
F --> G[边界覆盖采样与独立 Alpha]
|
||||
G --> H[创建 Packed Image 并复用 active Object]
|
||||
H --> I[删除其他参与图片对象并提交 Undo]
|
||||
```
|
||||
|
||||
正交视图接受有限深度,透视视图采样观察方向前方的交点。交叠检查采用连续投影范围;输出尺寸受 Region、最长边上限和总像素上限约束。
|
||||
|
||||
RGB 与 Alpha 分别累计,保留被 Mask 隐藏的有效颜色。边界使用覆盖采样,内部复用直接采样,避免黑边和不必要的模糊。结果平面朝向观察者,位置由 active 图片的有效深度确定。
|
||||
|
||||
Frame 保留 active 对象身份、名称和 Collection;图像替换复用 [Common](../common.md) 的共享数据与失败恢复逻辑。
|
||||
16
docs/internals/operators/index.md
Normal file
16
docs/internals/operators/index.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Operator 实现索引
|
||||
|
||||
| 源码(相对 `src/anyimage/`) | 功能 |
|
||||
| --- | --- |
|
||||
| `operators/ai_setup.py` | [AI 环境管理](ai-setup.md) |
|
||||
| `operators/clipboard_image/` | [剪贴板图片](clipboard-image.md) |
|
||||
| `operators/convert_to_plane/` | [Plane 与 Depth Plane](convert-to-plane.md) |
|
||||
| `operators/convert_to_panorama/` | [Panorama](convert-to-panorama.md) |
|
||||
| `operators/cutout_tool/` | [Cutout](cutout-tool.md) |
|
||||
| `operators/frame_tool/operators.py` | [Frame 多图合成](frame.md) |
|
||||
| `operators/mask_tool.py` | [Mask Alpha 编辑](mask.md) |
|
||||
| `operators/rectify_tool/operators.py` | [Rectify 透视校正](rectify.md) |
|
||||
| `operators/remove_background.py` | [Remove Background](remove-background.md) |
|
||||
| `operators/upscale.py` | [Upscale](upscale.md) |
|
||||
|
||||
单文件功能按业务命名;复杂功能包在 `operators.py` 定义 Operator 和入口协调逻辑,交互式工具同时定义 WorkSpaceTool,其余模块承载具体实现。包的 `__init__.py` 汇总导出与 `CLASSES` 注册清单,类型在 `operators/__init__.py` 和扩展 `__init__.py` 汇总注册。AI Operator 使用普通字典提交任务,并在主线程响应中读取文件和修改 Blender 数据。
|
||||
23
docs/internals/operators/mask.md
Normal file
23
docs/internals/operators/mask.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Mask Alpha 编辑
|
||||
|
||||
`operators/mask_tool.py` 定义 `EditImageAlpha` 和 `MaskTool`,使用 `common/viewport.py` 的 `ImageGesture` 管理 Lasso、Brush 与 Polyline。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[冻结 Scene 的 Gesture、Mode、Radius] --> B[屏幕手势与 Overlay]
|
||||
B --> C[完成后投影并生成覆盖 M]
|
||||
C --> D[读取源 RGBA]
|
||||
D --> E[按模式计算 Alpha]
|
||||
E --> F[保留 RGB 和源画布尺寸]
|
||||
F --> G[替换 Packed Image 并提交一个 Undo]
|
||||
```
|
||||
|
||||
| 模式 | 源 Alpha A 与覆盖 M 的结果 |
|
||||
| --- | --- |
|
||||
| Set | `A × M` |
|
||||
| Extend | `max(A, M)` |
|
||||
| Subtract | `A × (1 − M)` |
|
||||
|
||||
Brush 拖动时维护简化屏幕路径、圆形印记与连接条的并集预览;释放时一次生成图片覆盖。Polyline 使用逐点路径,至少三个点后通过闭合圈、双击或 Enter 完成,Backspace 删除末点。
|
||||
|
||||
结果保留 Empty 的身份、矩阵、显示尺寸和偏移。全透明结果有效,取消或切换工具清理未提交的 Overlay;共享 Image 通过统一替换入口分流。
|
||||
17
docs/internals/operators/rectify.md
Normal file
17
docs/internals/operators/rectify.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Rectify 透视校正
|
||||
|
||||
`operators/rectify_tool/operators.py` 定义 `RectifyImagePerspective` 与 `RectifyTool`,将图片内的四角区域校正为矩形。`geometry.py` 负责四角验证与像素校正,`preview.py` 负责预览图、Overlay 和宽高比控制,`__init__.py` 导出类型及注册清单。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[点选四角] --> B[规范为凸 Quad 并检查图片交叠]
|
||||
B --> C[512×512 预览与宽高比调整]
|
||||
C --> D[按像素密度和输出上限确定尺寸]
|
||||
D --> E[分块 Homography 与预乘 Alpha 插值]
|
||||
E --> F[按可见 Alpha 裁剪]
|
||||
F --> G[映射 placement bounds 并替换 Packed Image]
|
||||
```
|
||||
|
||||
四角允许任意点击顺序,退化或非凸输入在提交前校验。预览根据源颜色空间配置显示,像素重采样复用 `common/image.py`。输出最长边上限 8192,总像素上限 16,777,216。
|
||||
|
||||
`replace_empty_image()` 根据校正区域的 placement bounds 调整画框,保留源 Image 名称、对象身份和共享引用语义。
|
||||
19
docs/internals/operators/remove-background.md
Normal file
19
docs/internals/operators/remove-background.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Remove Background
|
||||
|
||||
`operators/remove_background.py` 从 Image Empty 或当前 Shader Image Texture 准备输入。`RemoveImageBackground` 协调设置与输入,`RunBackgroundRemoval` 提交 Job 并应用结果。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[ImageEditTarget 取得编辑目标] --> B[准备图像输入]
|
||||
B --> C[remove-background Job]
|
||||
C --> D[所选背景模型生成前景 Alpha]
|
||||
D --> E{输入类型}
|
||||
E -- 普通图片 --> F[透明 PNG 前景]
|
||||
E -- HDR --> G[alpha.npy]
|
||||
F --> H[应用结果与 Undo]
|
||||
G --> H
|
||||
```
|
||||
|
||||
`server/jobs/remove_background.py` 读取单张输入并调用 `server/models/background.py`,支持 BiRefNet 和 BEN2。模型前景 Alpha 与源 Alpha 合成。
|
||||
|
||||
普通结果通过 `ImageEditTarget` 保留共享图像引用与帧设置。HDR 使用 `common/hdr_image.py` 的 `HdrBackgroundInput` 准备分析输入,并将返回 Alpha 应用于原浮点 RGB。Operator 负责临时输入清理。
|
||||
15
docs/internals/operators/upscale.md
Normal file
15
docs/internals/operators/upscale.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Upscale
|
||||
|
||||
`operators/upscale.py` 从 Image Empty 或当前 Shader Image Texture 生成 2× 结果,源图长边必须小于 Maximum AI Input Size。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[检查源图尺寸并冻结模型设置] --> B[准备图片输入]
|
||||
B --> C[upscale-image 单张处理]
|
||||
C --> D[分块执行 4× RGB 推理]
|
||||
D --> E[统一缩放为源图 2×]
|
||||
E --> F[原 Alpha 独立 Lanczos 缩放]
|
||||
F --> G[ImageEditTarget 应用结果并提交 Undo]
|
||||
```
|
||||
|
||||
`server/models/upscale.py` 处理输入上限和最终尺寸,`onnx_upscale.py` 执行 Real-ESRGAN 与 HAT 模型。ModelManager 管理 Session;Provider 资源错误会释放当前 Session 并返回处理建议。
|
||||
40
docs/internals/outputs.md
Normal file
40
docs/internals/outputs.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Job 产物
|
||||
|
||||
Server 将结果写入 Job 目录,返回以相对文件名为值的字典。Blender 主线程通过 JobResult 读取文件,创建或更新 Image、Mesh、Material 和 Object。
|
||||
|
||||
| Job | 文件 | 消费方 |
|
||||
| --- | --- | --- |
|
||||
| `remove-background` | `foreground.png` 或 `alpha.npy` | ImageEditTarget 或 HDR Alpha 编辑 |
|
||||
| `upscale-image` | `upscale.png` | 图像替换 |
|
||||
| `generate-depth-plane-geometry` | `depth.exr`、`depth.json` 和法线 | Depth / Relief Plane |
|
||||
| `generate-cutout-artifacts` | 按请求提供深度、元数据和法线 | Cutout |
|
||||
| `generate-panorama-geometry` | `depth.exr`、`depth.json` | Panorama |
|
||||
|
||||
## 深度与法线协议
|
||||
|
||||
| 返回字段 | 内容 |
|
||||
| --- | --- |
|
||||
| `depth` | float32 EXR,透视产物 RGB 保存相机空间 XYZ,Alpha 保存源 Alpha × 模型有效性 |
|
||||
| `depth_metadata` | JSON,包含 `image_size` 与 3×3 `intrinsics` |
|
||||
| `tangent_normal` | `tangent-normal.png`,切线空间法线 |
|
||||
| `object_normal` | `object-normal.png`,对象空间法线 |
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[源 RGB 与 Alpha] --> B[限尺寸 RGB 推理]
|
||||
B --> C[GeometryFrame]
|
||||
C --> D[写出深度与法线]
|
||||
A --> E[源 Alpha 写入有效通道]
|
||||
E --> D
|
||||
D --> F[Blender 加载并 Pack]
|
||||
```
|
||||
|
||||
参考深度由 `common/depth.py` 从产物计算:先按 Alpha 阈值和有限正深度筛选样本,剔除超过样本中位数 1.1 倍的远处背景,再取 95 分位数;无可用样本时返回基准值。加载后设置 Non-Color 和 CHANNEL_PACKED。Depth Plane 使用对象空间法线,Relief Plane 使用切线空间法线。
|
||||
|
||||
Panorama 的 EXR RGB 保存径向距离,Alpha 保存源 Alpha 与融合有效性;元数据包含 `projection: equirectangular` 和 `image_size`。
|
||||
|
||||
## 颜色与图像编辑
|
||||
|
||||
`common/color_image.py` 为 Plane、Panorama 和 Cutout 准备独立的材质颜色与 AI 分析输入。Cutout 使用 Selection bounds 内的源图;网格创建时组合源 Alpha 与 SelectionMask。颜色分辨率与分析尺寸分别处理。
|
||||
|
||||
`ImageEditTarget` 统一 Image Empty 和 Shader Image Texture 的编辑目标及共享引用。HDR 去背景通过 `HdrBackgroundInput` 保留 Blender 浮点 RGB,并应用 Server 返回的连续 Alpha。
|
||||
36
docs/internals/runtime.md
Normal file
36
docs/internals/runtime.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Job Runtime 与 Server
|
||||
|
||||
`runtime.py` 配置 BlendJob `JobRuntime` 和独立 Python 3.12 Environment。`server/app.py` 注册 Job,`model_manager.py` 管理模型文件与缓存 Session。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Operator request] --> B[JobRuntime 队列]
|
||||
B --> C[Server Job 调用媒体与模型模块]
|
||||
C --> D[写入 Job 目录]
|
||||
D --> E[主线程 response 导入结果]
|
||||
```
|
||||
|
||||
| Job | 实现与用途 |
|
||||
| --- | --- |
|
||||
| `download-model`、`download-required-models` | `app.py` 下载单个或缺失模型 |
|
||||
| `remove-background` | `jobs/remove_background.py` 生成透明前景或连续 Alpha |
|
||||
| `upscale-image` | `jobs/upscale.py` 生成 2× 图像 |
|
||||
| `generate-depth-plane-geometry` | `jobs/depth_plane.py` 生成深度、元数据和法线 |
|
||||
| `generate-cutout-artifacts` | `jobs/cutout.py` 按请求生成深度和法线 |
|
||||
| `generate-panorama-geometry` | `jobs/panorama.py` 生成全景径向深度 |
|
||||
|
||||
`media/` 校验输入图片并限制分析尺寸,Server 只处理静态图片。Job 在阶段边界检查取消,通过 `context.progress()` 返回进度,Operator 清理 Blender 导出的临时输入。
|
||||
|
||||
`geometry/prediction_artifacts.py` 缩放源 RGB 后运行一次 MoGe,将同一预测交给深度、法线和元数据写入器;源 Alpha 写入深度与法线产物的有效通道。参考深度由 Blender 侧按产物 Alpha 计算,文件协议见 [Job 产物](outputs.md)。
|
||||
|
||||
## 模型与缓存
|
||||
|
||||
| 用途 | 模型 | Session 分类 |
|
||||
| --- | --- | --- |
|
||||
| Depth 与 Normal | MoGe-2 ViT-S / ViT-B Normal、MoGe-3 ViT-L | geometry |
|
||||
| Remove Background | BiRefNet Lite、BEN2、BiRefNet HR Matting | background |
|
||||
| Upscale | Real-ESRGAN WDN、Real-ESRGAN x4plus、HAT Sharper | upscale |
|
||||
|
||||
`model_catalog.py` 声明文件来源、许可、大小和 SHA-256;`model_download.py` 下载并校验。ModelManager 按模型路径和设备缓存 Session,切换模型或设备时释放对应类别。Clear Models 或关闭 Server 时释放已加载资源。
|
||||
|
||||
首次安装通过 `post_install()` 下载默认 MoGe-2 ViT-S Normal、BiRefNet Lite 与 Real-ESRGAN General WDN x4v3。Storage Root 默认 `~/.anyimage`,保存环境、模型、Job 产物和日志。设置入口见 [AI 环境管理](operators/ai-setup.md)。
|
||||
41
docs/javascripts/extra.js
Normal file
41
docs/javascripts/extra.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 首页的扩展安装按钮:按系统只留一个平台,并把下载地址拖进 Blender。
|
||||
*/
|
||||
const INSTALL_PLATFORMS = { win: "windows", mac: "macos", lin: "linux" };
|
||||
|
||||
function initInstallDrag() {
|
||||
const container = document.querySelector(".anyimage-install");
|
||||
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
// navigator.platform 的取值由浏览器决定,只取前三个字符做粗略匹配。
|
||||
const detectedPlatform = INSTALL_PLATFORMS[navigator.platform.toLowerCase().slice(0, 3)];
|
||||
|
||||
container.querySelectorAll(".anyimage-install-item").forEach(function (item) {
|
||||
const matches = !detectedPlatform || item.dataset.platform.startsWith(detectedPlatform);
|
||||
item.classList.toggle("is-active", matches);
|
||||
item.hidden = !matches;
|
||||
});
|
||||
|
||||
container.querySelectorAll(".anyimage-install-button").forEach(function (button) {
|
||||
const group = button.closest(".anyimage-install-group");
|
||||
|
||||
button.addEventListener("dragstart", function (event) {
|
||||
event.dataTransfer.setData("text/plain", button.dataset.installUrl);
|
||||
event.dataTransfer.effectAllowed = "copy";
|
||||
group.classList.add("is-dragging");
|
||||
});
|
||||
|
||||
button.addEventListener("dragend", function () {
|
||||
group.classList.remove("is-dragging");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", initInstallDrag);
|
||||
} else {
|
||||
initInstallDrag();
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
# 模型、设备与存储
|
||||
|
||||
SnapForm 把运行环境、模型和任务结果放在 Blender 之外,多个 Blender 版本可以共享同一套数据。
|
||||
|
||||
## Models 面板
|
||||
|
||||
打开 3D View 侧栏中的 **SnapForm > Models**,可以查看和下载模型。
|
||||
|
||||
- **Ready** 表示模型文件已经就绪。
|
||||
- **Download** 用于按需下载模型。
|
||||
- 默认安装会准备 MoGe-2 ViT-S Normal 和 BEN2 Base。
|
||||
- DA3 与更大的 MoGe-2 模型需要时再下载即可。
|
||||
|
||||
较大的模型不一定适合所有图片。建议先用默认模型完成构图,再针对细节与性能需求比较其他模型。
|
||||
|
||||
## Device
|
||||
|
||||
扩展偏好设置中的 **Device** 控制 Depth Plane、Surface 和 Remove Background 等正式任务。
|
||||
|
||||
- Windows 通常优先使用 DirectML。
|
||||
- Apple Silicon macOS 通常优先使用 CoreML。
|
||||
- Provider 不可用时会回退到 CPU。
|
||||
|
||||
Debug 面板中的 **Debug Device** 只影响 Debug 任务,不会覆盖正式任务的 Device。
|
||||
|
||||
## Storage Root
|
||||
|
||||
默认 Storage Root 是 `~/.snapform`,主要包含:
|
||||
|
||||
```text
|
||||
~/.snapform/
|
||||
├── .venv/ # 独立 Python Environment
|
||||
├── models/ # 模型文件
|
||||
├── jobs/ # 每次任务的结果
|
||||
├── install.log # Environment 安装日志
|
||||
└── server.log # Job Server 日志
|
||||
```
|
||||
|
||||
更改 Storage Root 相当于切换到另一套环境、模型和任务数据。SnapForm 不会自动移动旧目录。
|
||||
|
||||
## Job 文件夹
|
||||
|
||||
每个任务会在 `jobs/` 下创建一个独立目录。单张图生成的关键贴图通常会 Pack 到 `.blend`;视频和序列帧则继续引用 Job 目录中的文件。
|
||||
|
||||
可以清理已经确认不再使用的任务目录,但清理前应检查是否有 `.blend` 仍引用其中的序列帧。
|
||||
|
||||
## 日志
|
||||
|
||||
遇到安装或 Server 问题时:
|
||||
|
||||
- 在扩展偏好设置中点击 **Open Server Log** 查看运行日志。
|
||||
- Environment 安装问题查看 Storage Root 下的 `install.log`。
|
||||
- 修改 Storage Root 后,确认你检查的是当前目录中的日志。
|
||||
@@ -1,37 +0,0 @@
|
||||
# 输出格式
|
||||
|
||||
本页说明任务文件的字段与序列化约定,主要用于开发与数据检查。普通使用者只需了解
|
||||
[模型、设备与存储](models-and-storage.md)中的 Job 文件夹说明。
|
||||
|
||||
## Debug
|
||||
|
||||
Debug 用于直接比较 DA3、MoGe-2 与 BEN2 模型 API 的返回效果,不复用 Depth、Surface 或 Geometry
|
||||
后处理。三个入口对应三个独立 Job:
|
||||
|
||||
- debug-da3:保存 prediction.npz,字段直接来自 DA3 Prediction,包括 depth、conf、
|
||||
extrinsics、intrinsics、processed_images 及模型实际返回的可选标量或数组
|
||||
- debug-moge2:每张输入保存一个 prediction.npz 或 prediction.0001.npz,完整保留 MoGe-2 API
|
||||
字典中的 points、depth、normal、mask 与 intrinsics
|
||||
- debug-ben2:直接保存 BEN2 API 返回的 foreground.png 或 foreground.0001.png
|
||||
|
||||
Debug 不生成点云、三角网格、深度预览、置信度 percentile 或坐标变换后的派生产物。输入为多帧时,
|
||||
DA3 保留联合推理;MoGe-2 与 BEN2 按其单图 API 逐帧保存结果。
|
||||
|
||||
## Depth
|
||||
|
||||
generate-depth 输出 Blender Depth Plane 所需的 results.npz。DA3 保存 depth、conf、
|
||||
extrinsics 与 intrinsics;MoGe-2 保存 depth、normal、mask 与 intrinsics,并额外写出
|
||||
Depth Plane 导入所需的贴图。这里的文件转换属于正式 Depth 工作流,不属于 Debug。
|
||||
|
||||
## Surface
|
||||
|
||||
generate-surface 统一写出 `mesh.npz`,不再生成 `texture.png`。DA3 与 MoGe-2 都使用
|
||||
`geometry/surface.py` 的同一套 Surface 构建算法;序列化位于 `outputs/surface.py`。MoGe-2 normal
|
||||
仍由 Server 单独导出为 `normal.png`,Blender 只负责加载并用于材质,不写入几何 NPZ。未去背景时
|
||||
无论颜色来自原图复制还是 BEN2,Server 都统一写为 `color.0001.*` 起始的颜色层。Blender 不依据文件数量判断图片类型:源 Empty 为普通单图时载入为 `IMAGE`,源 Empty 为 Movie/Sequence 时即使只有一帧也载入为 `SEQUENCE`;`.depth` 使用相同规则。材质只在 Blender
|
||||
端创建。MoGe-2 的 `normal.png` 仍由 Server 生成,Blender 只负责加载使用。
|
||||
|
||||
## BEN2
|
||||
|
||||
正式 remove-background 工作流输出 `color.0001.png` 起始的透明颜色层。BEN2 ONNX API 返回的 Alpha
|
||||
恢复到输入分辨率,RGB 保留输入图像颜色。Debug BEN2 仍保留模型 API 语义,使用 `foreground` 文件名。
|
||||
@@ -1,48 +0,0 @@
|
||||
# 五分钟上手
|
||||
|
||||
这个流程会把一张图片转换为可继续编辑的 Depth Plane。
|
||||
|
||||
## 1. 导入图片
|
||||
|
||||
在 3D View 中选择 **Add > Image > Reference**,导入一张图片。Blender 会创建一个显示方式为 Image 的 Empty。
|
||||
|
||||
建议第一次尝试使用主体清晰、前后景关系明确的图片。人物、产品、建筑立面和插画通常都比较适合。
|
||||
|
||||
## 2. 打开 SnapForm 菜单
|
||||
|
||||
保持 Image Empty 处于选中状态,在 3D View 中点击右键,选择 **SnapForm > Convert to Depth Plane**。
|
||||
|
||||
如果菜单不存在,请确认当前对象是 Image Empty;如果转换入口是灰色,请先完成[安装与首次配置](installation.md)。
|
||||
|
||||
## 3. 使用默认设置转换
|
||||
|
||||
第一次使用建议保留默认设置:
|
||||
|
||||
- **Depth From**:MoGe-2 ViT-S Normal
|
||||
- **Resolution Level**:5
|
||||
- **Remove Background**:关闭
|
||||
- **Shadeless**:关闭
|
||||
- **Keep Original**:关闭
|
||||
|
||||
点击确认后,状态栏会显示模型加载和处理进度。首次推理需要加载模型,之后连续处理通常会更快。
|
||||
|
||||
## 4. 查看和调整结果
|
||||
|
||||
转换完成后,原 Image Empty 会被新的网格对象替代。打开 Geometry Nodes 修改器,可以调整:
|
||||
|
||||
- **Displacement**:选择直接改变几何,或改用材质置换。
|
||||
- **Subdivision**:控制面片细分程度。
|
||||
- **Background Depth**:控制透明区域或背景区域的深度。
|
||||
- **Depth Scale**:放大、减小或反转深度变化。
|
||||
|
||||
如果希望保留原 Image Empty,转换前开启 **Keep Original**。
|
||||
|
||||
## 5. 让画面不受灯光影响
|
||||
|
||||
希望颜色尽量贴近原图时,在转换对话框中开启 **Shadeless**。需要让结果参与场景光照、阴影和材质表现时,则保持关闭。
|
||||
|
||||
## 接下来可以尝试
|
||||
|
||||
- 主体需要透明背景:[转换时去除背景](remove-background.md)
|
||||
- 想要更强的透视结构:[转换为 Surface](guide-surface.md)
|
||||
- 输入是视频或序列帧:[制作动画 Depth Plane](animation.md)
|
||||
@@ -1,36 +0,0 @@
|
||||
# 去除背景
|
||||
|
||||
Remove Background 使用 BEN2 为 Image Empty 生成透明前景,不创建网格。它适合在转换前检查抠图效果,或继续把结果当作普通 Image Empty 使用。
|
||||
|
||||
## 使用方法
|
||||
|
||||
1. 选中 Image Empty。
|
||||
2. 在 3D View 中右键,选择 **SnapForm > Remove Background**。
|
||||
3. 等待状态栏提示任务完成。
|
||||
|
||||
SnapForm 会创建新的透明颜色图,并把当前 Empty 切换到新图。对象的 Transform、显示尺寸、Offset 和其他显示设置保持不变。
|
||||
|
||||
## 在转换时去除背景
|
||||
|
||||
Depth Plane 和 Surface 的转换对话框也提供 **Remove Background**:
|
||||
|
||||
- Depth Plane 会使用透明 Alpha 区分前景和背景深度。
|
||||
- Surface 会在写出网格前删除背景三角面。
|
||||
- 深度估算仍使用未经抠图的原始 RGB,避免透明区域提前影响模型判断。
|
||||
|
||||
## 图片、视频和序列帧
|
||||
|
||||
普通图片会得到一张透明 PNG。视频或序列帧会得到透明图片序列,并从源 Empty 的 Start 位置开始播放。处理范围跟随 **Image Frames** 和 **Offset**。
|
||||
|
||||
!!! note
|
||||
动画结果引用 Storage Root 中任务目录里的序列帧。不要在仍使用这些素材时删除对应的 Job 文件夹。
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Remove Background 是灰色的
|
||||
|
||||
确认 Environment 已安装,且 **MoGe-2 ViT-S Normal** 与 **BEN2 Base** 都显示 Ready。正式功能会等默认必需模型全部就绪后启用。
|
||||
|
||||
### 透明边缘需要调整
|
||||
|
||||
当前正式入口不提供 BEN2 refinement 参数。可以在结果材质中继续处理 Alpha,或在外部图像软件中修整生成的颜色图。
|
||||
129
docs/server.md
129
docs/server.md
@@ -1,129 +0,0 @@
|
||||
# Job Server
|
||||
|
||||
本页记录 Environment、模型缓存和 Job Runtime 的内部设计。安装或使用遇到问题时,请先查看
|
||||
[安装与首次配置](installation.md)和[常见问题](faq.md)。
|
||||
|
||||
## 运行架构
|
||||
|
||||
SnapForm 在独立的 Python 3.12 Environment 中运行 HTTP Job Server,Blender 只通过标准库 HTTP Client 提交和轮询任务,不把计算依赖载入 Blender Python。Server 只监听 `127.0.0.1`,由外置 BlendJob wheel 提供的 `ServerController` 保存随机端口与 Instance ID;专属本地进程协议不使用额外 Token 鉴权。
|
||||
|
||||
每个 Blender 实例启动并持有一个 SnapForm 专属 Server,不发现或复用其它 Blender、其它插件启动的本地 Server。多个 Blender 可以对应多个进程,但模型、Environment 与任务产物仍可共享同一个 Storage Root。
|
||||
|
||||
Environment、模型和任务结果默认保存在 `~/.snapform`:
|
||||
|
||||
```text
|
||||
~/.snapform/
|
||||
├── .venv/
|
||||
├── environment.json
|
||||
├── models/
|
||||
├── jobs/
|
||||
├── manifest.json
|
||||
├── install.log
|
||||
├── install-status.json # 仅在安装期间存在
|
||||
└── server.log
|
||||
```
|
||||
|
||||
不同 Blender 版本可共享此目录。也可以在 Extension Preferences 的 `Storage Root` 中修改位置。连接端口与 Instance ID 只保存在启动它们的 Blender 进程内存中。
|
||||
|
||||
Blender Client 与独立 Server 的通用 HTTP Runtime 都复用 `src/snapform/wheels/` 中随扩展分发的同一份 BlendJob package;Server Environment 不再从 PyPI 安装第二份。`JobServer` 包装 FastAPI 并提供 Job、FIFO Queue 与 Resource API,`JobClient` 是无第三方依赖的 Blender 侧访问器。Blender 侧只把 `server/__init__.py:server` 入口字符串交给 Runtime,不导入 `server` package;独立进程启动后才取得其中的 `server` 实例、注册全部公开 Job,并创建 `model_manager` Resource。
|
||||
|
||||
SnapForm 根 package 不再有 `client.py`、`environment.py`、`storage.py` 或 `models.py`。这些文件会制造并不存在的业务边界:
|
||||
|
||||
- Storage Root、`.venv`、Manifest、安装状态与日志路径由 `JobRuntime` 唯一管理;
|
||||
- `InstallEnvironment` 由 `runtime.operator_classes()` 提供,Python 与 Job 自身的额外 packages 直接声明在传给 Runtime 的 `environment` 字典中;BlendJob 不需要重复声明;
|
||||
- Blender Client 使用根目录 `model_catalog.py` 构造 UI 并检查 Storage Root 下的模型必需文件;
|
||||
- 独立 Server 使用 `server/model_catalog.py` 下载和验证模型,不依赖 Extension 根 package。两份声明的共享存储字段由测试保持一致,已加载 Session 状态仍读取 Server Snapshot。
|
||||
|
||||
BlendJob 的 `JobClient` 只是无依赖 HTTP transport,不是 SnapForm 业务抽象,也不向 Operator 暴露 `JobRequest`。
|
||||
|
||||
SnapForm 的业务 Operator 直接提交参数字典,Job Type 一一对应:
|
||||
|
||||
- `download_model` → `download-model`
|
||||
- `download_required_models` → `download-required-models`
|
||||
- `remove_background` → `remove-background`
|
||||
- `extract_frames` → `extract-frames`
|
||||
- `generate_depth` → `generate-depth`
|
||||
- `generate_surface` → `generate-surface`
|
||||
- `debug_da3` → `debug-da3`
|
||||
- `debug_moge2` → `debug-moge2`
|
||||
- `debug_ben2` → `debug-ben2`
|
||||
|
||||
DA3 与 MoGe-2 的生产任务不再合并成带 Backend 参数的通用请求。每个 Operator 的 `request(context)` 直接构造自己的有效参数,Debug 任务也与生产任务完全分开。
|
||||
|
||||
Server 端没有 `execute_*`、`*_result` 或 CLI worker 中转。全部 `@server.job(...)` 都集中在
|
||||
`server/__init__.py`,这里只负责注册 Job 与 Resource。完整工作流位于 `jobs/`;模型适配器和
|
||||
ONNX 实现位于 `models/`;纯投影、网格与唯一 Surface Builder 位于 `geometry/`;文件序列化位于
|
||||
`outputs/`;输入准备位于 `media/`。模型下载由 `ModelManager` 单向调用
|
||||
`model_download.py`,不存在循环 import。
|
||||
|
||||
## ONNX-only Environment
|
||||
|
||||
扩展不内置 `uv` 可执行文件或 AI 运行库。`Install Environment` 首先下载固定版本的 uv,校验 SHA-256 后解压到共享 Storage Root,再用它创建环境并安装:
|
||||
|
||||
- ONNX Runtime 1.24.4;Windows 使用 `onnxruntime-directml`
|
||||
- NumPy 2.4.2、Pillow 12.1.1、PyAV 17.0.0、trimesh 4.11.2
|
||||
- FastAPI 0.139.2、Uvicorn 0.51.0、huggingface-hub 1.4.1
|
||||
|
||||
运行环境不安装 PyTorch、TorchVision、xFormers,也不安装 DA3、MoGe、BEN2 或 utils3d 的 Python 源码包。模型推理和 SnapForm 所需的相机、点云、网格后处理均由 ONNX Runtime、NumPy 与内部实现完成。
|
||||
|
||||
Windows 当前发行环境提供 DirectML 与 CPU;`Auto` 优先使用 `DmlExecutionProvider`。显式指定不可用设备时会安全回退到 CPU。其他平台安装标准 ONNX Runtime,并按可用 provider 选择 CoreML、OpenVINO 或 CPU。Blender 侧通过 `sysctl` 识别 Apple Silicon 代际,只有 M4 或更新芯片才提供 MPS 选项;其他 Mac 只提供 CPU。M4 或更新芯片的 CoreML Session 使用 `MLProgram` 模型格式、全部可用计算单元并允许动态输入 Shape。
|
||||
|
||||
`environment.json` 保存规范化后的声明,`manifest.json` 保存该声明与基础 Server packages 的 Hash。Python、packages、平台 packages、基础 Server packages 或 Environment Schema 改变后需要重新执行 `Install Environment`,不再维护手工 Manifest Revision。BlendJob 直接随 Extension 更新,不参与 Environment Hash。
|
||||
|
||||
## 模型
|
||||
|
||||
所有正式功能都使用 ONNX:
|
||||
|
||||
- MoGe-2 ViT-S、ViT-B、ViT-L Normal
|
||||
- DA3 Small、DA3 Base
|
||||
- BEN2 Base
|
||||
|
||||
首次安装在 `post_install(runtime)` 中自动下载 `MoGe-2 ViT-S Normal` 与 FP16 `BEN2 Base`。它们和 `Models` panel 的后续下载一样,都是 `download-model` Job,与推理共用队列、HTTP 进度和取消协议。模型按声明的目录名保存在 `~/.snapform/models/`,并保留仓库内部结构。声明了文件清单与校验值的模型先走 Hugging Face `resolve` 直链流式下载,失败后尝试 `https://models.omoolab.xyz/<repository-name>/<relative-path>`;两者都失败时当前 Job 直接失败,不记录或猜测后续下载源。模型直链的 TCP、代理 CONNECT 与 TLS 建连最长等待 10 秒;连接后连续 60 秒没有收到响应数据则视为当前来源失败并切换下一来源。该空闲超时不限制模型总下载时长。R2 文件会校验大小与 SHA-256。未声明清单的仓库仍使用 `huggingface_hub` 快照下载。可用 `HF_ENDPOINT` 指定 Hugging Face 镜像。
|
||||
|
||||
Image Empty 的右键菜单会读取 `model_manager` 状态;缺少 MoGe-2 ViT-S 或 BEN2 时显示
|
||||
`Download Required Models`。对应的 `download-required-models` Job 在 Server 端再次检查就绪状态,
|
||||
只下载缺失项,并把多个下载的进度合并为单次 0–100%。必需模型补齐前,右键菜单中的
|
||||
`Convert to Depth Plane` 保持禁用。
|
||||
|
||||
`model_manager` Resource 在 Server 内持有磁盘就绪状态和内存 Session。Blender UI 使用 Client catalog 声明的目录名与 `ready_patterns` 直接检查 `<storage-root>/models/`,因此展开模型面板、打开转换对话框或 Image Empty 右键菜单都不会仅为状态检测启动 Server。真正下载或推理时,Server 仍按自身 catalog 通过 `ModelManager.ready()` 重新验证文件。`ModelSessionCache` 保留 BEN2,并在 DA3 与 MoGe-2 之间只保留最近使用的一种 Depth Session。`GET /resources/model_manager` 只返回当前加载状态;`POST /resources/model_manager/clear` 只允许在没有 Active 或 Queued Job 时执行。
|
||||
|
||||
DA3 ONNX 使用模型的 camera decoder 恢复外参与内参,不提供 ray-pose 参数。MoGe-2 的 metric depth、normal、mask、normalized intrinsics,以及 BEN2 的 Alpha,均在 NumPy 后处理中恢复。
|
||||
|
||||
|
||||
## 安装网络与镜像
|
||||
|
||||
安装器不做带宽测速、来源竞速或失败后的自动切换。`Install Environment` 固定使用 Astral GitHub Release、uv 默认 Python 下载源和 PyPI;`Install Environment (Mirror)` 固定使用 CNB uv Release、CNB `python-build-standalone` 与清华 PyPI。两个 uv 来源使用同一固定版本和 SHA-256;下载压缩包只作临时文件,安装完成后删除。
|
||||
|
||||
镜像模式只在当前安装子进程中设置 `UV_PYTHON_INSTALL_MIRROR` 与 `UV_INDEX_URL`,不修改用户环境变量。Windows 系统代理会注入子进程;同时支持 `HTTP_PROXY`、`HTTPS_PROXY` 与 `ALL_PROXY`。post-install 模型下载保持 Hugging Face 与 OmooLab R2 的独立来源逻辑,不读取 Environment 安装模式。
|
||||
|
||||
Environment Bootstrap 的单次 0–100% 只包含 uv、Python 与 packages,不包含后续模型下载。开始显示 `Starting installation ...`;uv 使用 1–20%,显示 `1/3 Starting uv download ...`、`1/3 Downloading uv [速度]` 或镜像形式、`1/3 Installing uv ...`;Python 使用 20–50%,显示 Checking、Downloading、Installing,镜像下载带 `(mirror)`;packages 使用 50–98%,显示 Resolving、`Downloading packages [已开始数]/[解析总数]`、Installing,镜像模式带 `(mirror)`;98–100% 显示 `Verifying installation ...`。三个下载区间分别占对应阶段的 84–87%,缓存命中时直接跳过。单阶段状态不显示 `1/1`。
|
||||
|
||||
首次安装依次显示 Environment、MoGe-2 下载与 BEN2 下载,每项分别使用 0–100%。Environment 由 Job Runtime 依次完成 uv 下载、Python Environment 创建与 packages 安装,uv 不再占用独立一轮进度。两个模型下载由 SnapForm 的 `post_install(runtime)` 通过 `runtime.request()` 顺序发起;切换模型时进度归零,但不显示 `2/2` 等阶段编号。
|
||||
任一 post-install 模型失败时,Environment 文件仍然保留,但安装 Operator 返回失败并显示原始模型错误。
|
||||
|
||||
## 生命周期
|
||||
|
||||
扩展启用后不启动 Server;第一次执行 Job 或点击 `Start` 时才为当前 Blender 启动专属实例。正常关闭或禁用扩展时直接停止自己启动的进程;Server 也监视 Blender 父进程 PID,父进程异常退出后会自行停止。
|
||||
|
||||
AI、帧提取和 Models panel 下载的 Job 进度保存在 Server 内存中。Handler 只使用 `context.progress(value, message)` 发布进度,终态由 JobServer 内部管理;SnapForm 不再维护独立的 `server/progress.py` 或 Job 状态 JSON。Blender 每 0.25 秒轮询 `GET /jobs/{job_id}`,任务目录不写 `status.json`。Operator 直接提交参数字典;Job 被接受时由 `JobServer` 自动生成 ID 并创建 `jobs/<opaque-job-id>/`,提交响应同时返回 `job_id` 与 `directory`。路径通过 `JobContext.directory` 传给 Handler,SnapForm Server 强制将输出写入该目录。
|
||||
|
||||
`job_runtime.py` 创建唯一的 `runtime = JobRuntime(...)`,以自身目录为 `entrypoint_root`,传入 `server:server` 相对入口字符串与显式 `snapform` namespace,并导出 `runtime.JobOperatorBase`。Runtime 独占 Storage Root、Environment 路径、安装进程、Server 控制和 Status Bar。Operator 未覆盖 `request(context)` 时会提交全部 Property;覆盖后返回值纯替换默认参数。可选的 `response(context, result)` 在 Blender 主线程直接完成结果导入。SnapForm 不再提供 Client 请求层、Job Mixin、Status Bar 或 Build 中转 Operator。
|
||||
|
||||
Environment 安装是 Server 启动前唯一的 Bootstrap 流程:`environment.json` 记录期望安装的环境声明,`install-status.json` 在安装期间回传进度并在任务收尾后删除,`install.log` 持久记录安装输出。Bootstrap 不创建 Job 目录。Environment 成功后执行普通的 `post_install(runtime)`;默认模型下载此时已经是正式 Server Job,会创建自己的 Job 目录,并使用与其它任务相同的进度和取消协议。
|
||||
|
||||
Server 使用标准库单 Worker FIFO Queue,全局同时只执行一个 Job,包括模型下载。Server 忙碌时新 Job 进入 `queued`,不再返回 409;排队任务可以在执行前取消。取消运行中任务不会重启 Server;流式模型下载会在分块检查点结束,ONNX Runtime 当前调用则在返回后的最近检查点结束。JobServer 最多保留 100 个已完成 Job 的内存状态,后续提交时清理更早记录。
|
||||
|
||||
Server 由 Environment 通过 `python -m blendjob.runner` 启动,避免 BlendJob 内部模块名遮蔽 Python 标准库。启动与崩溃信息写入 `~/.snapform/server.log`。
|
||||
|
||||
## 已知限制
|
||||
|
||||
DA3 ONNX 的注意力是朴素实现(`MatMul → Softmax → MatMul`),峰值显存与 `(帧数 × 每帧 patch 数)²` 成正比。迁移到 ONNX 前的 PyTorch 版使用 `scaled_dot_product_attention`(`XFORMERS_DISABLED=1`),按块计算注意力分数、不落地完整注意力矩阵,显存近似线性,因此视频能处理几十上百帧;ONNX 导出把 SDPA 回退成朴素注意力,多帧在中高分辨率下会显存爆炸(约 12 帧 @ 1008 即需 ~90 GB)。
|
||||
|
||||
Depth Plane 的视频、序列帧转换会复用同一个 ONNX Session 逐帧独立推理,再把结果合并到现有序列输出,因此峰值显存只取决于单帧分辨率。逐帧结果不保留 DA3 的跨视图尺度与相机位姿一致性,适用于当前逐帧动画结构。Surface 只支持单张图片。DA3 Debug 多帧输入仍联合推理;MoGe-2 与 BEN2 Debug 按单图 API 逐帧保存直接返回。
|
||||
|
||||
联合推理时,`onnx_da3.infer` 会在执行前按 `views × patches` 预估注意力张量,超过 4 GiB 直接报错并提示减少帧数或 Resolution Scale,避免 onnxruntime 报出难以定位的分配失败或 `UnicodeDecodeError`(DirectML 在中文 Windows 下报错信息为 GBK 编码,被 UTF-8 解码掩盖)。
|
||||
|
||||
待解决方向:
|
||||
|
||||
- 重新导出 DA3 ONNX,使用 ORT 的 `Attention` / `MultiHeadAttention` 贡献算子,或注入内存高效的注意力算子;
|
||||
- 若未来需要统一坐标系的视频重建,可按小窗口分批推理,再对齐相机位姿与深度尺度。
|
||||
@@ -1,13 +1,133 @@
|
||||
@import url("https://fontsapi.zeoseven.com/292/main/result.css");
|
||||
@import url("https://fontsapi.zeoseven.com/293/main/result.css");
|
||||
|
||||
/* [data-md-color-scheme="slate"] {
|
||||
--md-code-hl-string-color: #8b8b8b;
|
||||
--md-code-hl-keyword-color: #ffe8a3;
|
||||
--md-code-hl-number-color: #ffe8a3;
|
||||
--md-typeset-a-color: #ffe8a3;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] {
|
||||
--md-code-hl-string-color: #444444;
|
||||
--md-code-hl-keyword-color: #bd9100;
|
||||
--md-code-hl-number-color: #bd9100;
|
||||
}
|
||||
|
||||
[data-md-color-scheme=slate][data-md-color-primary=black] {
|
||||
--md-typeset-a-color: #ffe8a3;
|
||||
}
|
||||
|
||||
[data-md-color-scheme=default][data-md-color-primary=black] {
|
||||
--md-typeset-a-color: #bd9100;
|
||||
} */
|
||||
|
||||
:root {
|
||||
--md-text-font: "Open Sans", "Noto Sans SC", "Microsoft YaHei", sans-serif;
|
||||
--md-code-font: "Cascadia Code", "SFMono-Regular", Consolas, monospace;
|
||||
--md-text-font: "LXGW WenKai";
|
||||
--md-code-font: "LXGW WenKai Mono";
|
||||
}
|
||||
|
||||
.md-typeset h1 {
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
/* 首页的扩展安装拖拽组件,数值取自 extensions.blender.org 的按钮。 */
|
||||
@font-face {
|
||||
font-family: "fontutti";
|
||||
src: url("../assets/fonts/fontutti.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.md-typeset .grid.cards > ul > li {
|
||||
border-radius: 0.5rem;
|
||||
.anyimage-install {
|
||||
font-size: 14px;
|
||||
line-height: 1.4286;
|
||||
}
|
||||
|
||||
.anyimage-install-item {
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
.anyimage-install [class^="i-"]:before,
|
||||
.anyimage-install [class*=" i-"]:before {
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
margin: 0 0.2em;
|
||||
font-family: "fontutti";
|
||||
font-style: normal;
|
||||
font-variant: normal;
|
||||
font-weight: normal;
|
||||
line-height: 1em;
|
||||
text-align: center;
|
||||
text-decoration: inherit;
|
||||
text-transform: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.anyimage-install .i-move:before {
|
||||
content: "\e8d5";
|
||||
}
|
||||
|
||||
.anyimage-install-item.is-active ~ .anyimage-install-item.is-active {
|
||||
border-top: 1px solid var(--md-default-fg-color--lightest);
|
||||
padding-top: 1.6rem;
|
||||
}
|
||||
|
||||
.anyimage-install-group {
|
||||
border: 2px solid transparent;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.anyimage-install-group.is-dragging {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.anyimage-install-button {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background-color: rgba(0, 153, 255, 0.1);
|
||||
color: #0099ff;
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
outline: 2px dashed currentColor;
|
||||
outline-offset: -2px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
transition: background-color 150ms, color 150ms ease-out;
|
||||
user-select: none;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.anyimage-install-button:hover {
|
||||
background-color: rgba(0, 153, 255, 0.2);
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.anyimage-install-platform {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.anyimage-install-platform strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.anyimage-install-platform-rest {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.anyimage-install small.anyimage-install-note {
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
opacity: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.anyimage-install-note a {
|
||||
color: #0099ff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
194
docs/surface.md
194
docs/surface.md
@@ -1,194 +0,0 @@
|
||||
# Surface
|
||||
|
||||
本页记录 Surface 的网格、相机、Geometry Nodes 和材质实现。日常操作与参数说明请阅读
|
||||
[Surface 使用指南](guide-surface.md)。
|
||||
|
||||
## Image Empty 转透视网格
|
||||
|
||||
1. 在 3D View 选中仅包含一帧的 Image Empty,右键打开 `SnapForm` 子菜单并选择
|
||||
`Convert to Surface`。该入口独立于
|
||||
`Convert to Depth Plane`,当前不处理视频或序列帧。
|
||||
模型列表包含三个 MoGe-2 Normal 模型和两个 DA3 模型,默认使用 MoGe-2 ViT-S Normal。DA3 的
|
||||
Resolution Scale 与 Depth Plane 使用相同设计;MoGe-2 改为显示共享的 Resolution Level。
|
||||
2. Server 先用未经抠图的原始 RGB 请求 DA3 的 `depth`、`conf` 与 `intrinsics`,随后仅在开启
|
||||
`Remove Background` 时运行 BEN2 取得前景 Alpha;Alpha 经目标网格分辨率缩放与轻微模糊后,只要
|
||||
大于 `0` 就把对应点视为前景;每个三角形的三个顶点必须全部为前景,任一顶点 Alpha 为 `0` 就在输出前删除该面;
|
||||
关闭时不加载 BEN2。Depth 不做 smoothing,Server 反投影深度网格,建立源三角面,
|
||||
计算 confidence percentile 与 3×3 相对深度不连续度,并写入
|
||||
基于原图像素坐标的源 `UVMap`。这些步骤只准备修改器的输入网格,不执行 Relax、拓扑修复、平滑、
|
||||
小岛清理或最终 UV 重投影。运行时显示的六个 `Preparing surface data` 步骤均指这段 Server 端源数据准备。
|
||||
DA3 与 MoGe-2 最终都由 `server/geometry/surface.py` 构建同一份 Surface 数据;模型模块只提供各自的
|
||||
深度、点、法线、Mask 与相机数据,不再自行拥有 Surface 网格输出实现。
|
||||
3. Blender 把勾选 `Usage > Modifier` 的 `O DA3 Surface` 节点组直接作为 Geometry Nodes 修改器
|
||||
添加到 Surface,不另设修改器名称。节点组内没有子节点组,依次完成:
|
||||
- 修改器顶层依次显示 `Iterations`、`Resolution`、`Smooth`;`Iterations` 默认 `2`、最小 `0`,
|
||||
默认展开的 `Culling` Panel 依次包含 `Depth Continuity` 与 `Confidence`。前者直接读取面域 3×3
|
||||
相对深度差,并使用指数映射:
|
||||
默认 `50%` 对应官方 `rtol=0.04`,数值越大,内部阈值越小,剔除的断层面越多;
|
||||
- 每轮 Remesh Iteration 依次执行 Connected Merge、循环拓扑修复与一次 Smooth,使新拓扑在进入下一轮
|
||||
Merge 前先稳定;拓扑修复删除非流形面、单面拓扑和小面积 mesh islands;
|
||||
- 默认 `Resolution` 为原始有效三角边长中位数乘以 `sqrt(10) * 2`。`sqrt(10)` 来自原先 90% 面数缩减
|
||||
的二维边距估计,再乘以 2 为迭代合并留出更平滑、稳定的默认尺度;节点内部 Connected Merge 的
|
||||
Distance 为 `Resolution * 0.5`;
|
||||
- Smooth 采用 `O Smooth` 的边界检测和模糊法线权重,固定开放边界与薄面,只保留一个
|
||||
`Smooth` 程度系数;节点组不再额外执行 Flip Faces;
|
||||
- 从修改前的原始表面以 nearest-surface 重新采样 `UVMap`,最终明确写为 Corner 域 `2D Vector`。
|
||||
MoGe-2 使用独立的 `O MoGe2 Surface`。Server 由内置 NumPy 网格构建函数
|
||||
生成有限深度范围内的紧凑三角网格,并保留模型 mask、逐点 depth 及相邻点相对深度差。Geometry Nodes
|
||||
按官方 `mask & ~depth_map_edge(depth, rtol=0.04)` 的含义,先由 `Mask` 删除 mask 外的面,再由
|
||||
`Depth Continuity` 按同一份 3×3 邻域相对深度差删除断层面。DA3 与 MoGe-2 使用相同的属性、
|
||||
名称、指数滑块和 FACE 删除逻辑,不再额外删除孤立点。之后才执行拓扑修复、Relax、孤岛清理与 UV 重投影。
|
||||
节点组不提供 Confidence 或 Remesh 开关;Remesh 仍是节点处理流程的一部分。
|
||||
4. 节点组随插件放在 `O_SnapForm.blend`。Blender 4.5 首次 Append 后复用同一 datablock;
|
||||
Blender 5.0 以 linked ID 载入后调用 Pack,后续 Surface 继续复用。插件不会为每个 Surface 新建节点组,
|
||||
也不会在添加后重命名 Geometry Nodes 修改器。节点组只启用 `Usage > Modifier`,不 Mark as Asset;
|
||||
修改器关闭 `show_group_selector`。`Fix Topology` 位于默认折叠的 `Options` Panel。
|
||||
节点图按 Culling、Remesh、Topology Fix、Island Cleanup 与 UV 分区摆放;各区就近使用独立的
|
||||
Group Input,并隐藏没有连线的接口,避免跨越整个节点图的输入连线。
|
||||
`O DA3 Surface` 与 `O MoGe2 Surface` 的 `Iterations` 默认值均为 `2`、最小值为 `0`,
|
||||
`Resolution` 默认值均为 `0.1`,所有输入 socket 的 Structure Type 均固定为 `Single`。
|
||||
5. Surface Job 统一按输入准备、模型推理、可选 BEN2、构建 SurfaceMesh、写出产物五个阶段更新进度;单帧推理只显示模型与阶段名称,不追加 `1/1`,仅多帧时显示当前帧与总数;
|
||||
Surface Builder 是不接收 Job Context 或 progress callback 的纯数据转换。
|
||||
6. Server 在 job 根目录写出 `mesh.npz`。NPZ 保存 camera-space `vertices`、`faces`、`uv`、
|
||||
`intrinsics`、`image_size` 与有效深度中位数 `reference_depth`。颜色层无论来自原图复制还是 BEN2,
|
||||
Server 都统一写为 `color.0001.*`。Blender 读取网格数据后额外导入为 `.color`,材质不复用源
|
||||
Empty 的 Image。颜色贴图会按当前场景的 View Transform 选择对应输入色彩空间:AgX、ACES 1.3、ACES 2.0、Filmic、Khronos PBR Neutral 分别使用 `AgX Base sRGB`、`ACES 1.3 sRGB`、`ACES 2.0 sRGB`、`Filmic sRGB`、`Khronos PBR Neutral sRGB`;无法匹配当前 View Transform 时回退 `sRGB`。 Server 不生成 `texture.png`,材质只在
|
||||
Blender 端创建。
|
||||
DA3 Surface 只保留 Geometry Nodes 后处理实际使用的 attributes:点域 `o_depth`、面域
|
||||
`o_confidence` 与面域 `o_depth_discontinuity`。Remove Background 已在 Server
|
||||
建面前直接删除背景三角面,不写入任何 background attribute。MoGe-2 的导入网格保留点域 `o_depth`,
|
||||
以及面域 `o_mask`、`o_depth_discontinuity`,不伪造 confidence、stretch 或 background 属性,
|
||||
也不写入 custom normal。MoGe-2 推理返回的 normal 直接编码为独立的 `normal.png`,Blender 将它
|
||||
作为 Non-Color 贴图导入现有材质;normal 不进入 `mesh.npz`。
|
||||
所有 MoGe-2 Image datablock 都以源图片名而非对象名为基名:depth、normal 与 mask 分别使用
|
||||
`图片名.depth`、`图片名.normal`、`图片名.mask`,不附加 `.moge2`。
|
||||
7. 网格以 `reference_depth` 截面匹配 Image Empty 的显示矩形:该截面的投影四角与 Empty 的可见
|
||||
边界重合,主点同时匹配 Empty Offset。网格对象直接沿用源 Empty 的 `matrix_world`,因此位置、旋转、
|
||||
缩放及父级关系产生的世界变换保持一致。更近的顶点沿 Empty 局部 `+Z` 方向凸出,三角面正面朝向
|
||||
观察相机,启用 Backface Culling 时可从正面看到。
|
||||
8. 转换对话框不再提供后处理参数,只保留模型与 Resolution Scale 设置、`Create Matching Camera`、
|
||||
`Remove Background`、`Shadeless`,以及分割线下方的 `Keep Original`。背景移除默认关闭;开启后 Server
|
||||
使用 BEN2,并在写出 Surface 前直接删除背景三角面,不再由 Geometry Nodes 的 Background 开关处理。
|
||||
该预删除同时适用于 DA3 与 MoGe-2;因此 MoGe-2 转换后即使关闭 `Mask`,BEN2 已删除的背景也不会恢复。
|
||||
Surface 材质的 Color Image Texture 始终使用 `.color`;Remove Background 只决定该层是否带 BEN2 Alpha,原 Empty 始终保持原图。颜色贴图会按当前场景的 View Transform 选择对应输入色彩空间:AgX、ACES 1.3、ACES 2.0、Filmic、Khronos PBR Neutral 分别使用 `AgX Base sRGB`、`ACES 1.3 sRGB`、`ACES 2.0 sRGB`、`Filmic sRGB`、`Khronos PBR Neutral sRGB`;无法匹配当前 View Transform 时回退 `sRGB`。
|
||||
`Shadeless` 使用与 Depth Plane 相同的 `O Shadeless` 节点组,并继续通过 Alpha 与 Transparent BSDF 混合;普通材质的
|
||||
Bump Scale 按 `abs(object.scale.z) × camera scale_z` 设置,并在材质组内乘以固定基准 `0.001`,
|
||||
与透视表面实际深度尺度一致;它允许负数以反转凹凸方向。
|
||||
相机默认关闭;开启后会在 Empty 局部 `+Z` 侧创建一台朝向局部 `-Z` 的透视相机,并成为生成 Surface
|
||||
的子级,使二者移动时保持投影关系。关闭 `Keep Original` 时,网格成功创建后删除源 Empty。
|
||||
表面、材质与相机会归入同一个 `Convert to Surface` Undo 步骤。
|
||||
|
||||
Process Res 的设计依据 DA3 官方实现:API 与 CLI 默认使用 `process_res=504` 和
|
||||
`process_res_method="upper_bound_resize"`;输入预处理的 `PATCH_SIZE=14`,会把宽高分别调整到最接近的
|
||||
`14` 倍数。DA3 论文以 `504×504` 为 base resolution,公开的训练尺寸长边为 `504`、`672`、`756` 或
|
||||
`896`。官方 CLI 虽给出 `1024` 示例,但没有声明更高分辨率会提升质量,也没有规定 `1000` 为硬上限。
|
||||
因此转换入口使用相对原图的 Resolution Scale,同时通过上下限避免 Process Res 过低或过高,并把训练范围
|
||||
之外的值明确视为实验设置。参考
|
||||
[DA3 API](https://github.com/ByteDance-Seed/Depth-Anything-3/blob/main/docs/API.md)、
|
||||
[输入预处理实现](https://github.com/ByteDance-Seed/Depth-Anything-3/blob/main/src/depth_anything_3/utils/io/input_processor.py)
|
||||
与[技术报告](https://depth-anything-3.github.io/assets/da3_tech_report_2025.pdf)。
|
||||
|
||||
所有任务使用统一的状态栏进度显示。变化中的阶段说明显示在右侧的进度条内部,取消按钮
|
||||
位于进度条右侧。状态字段支持阶段、当前操作与预计剩余秒数,界面统一组合为紧凑文案并保证
|
||||
进度单调递增。任务进程与 Server 请求在后台启动,按钮点击后立即进入进度状态。Image Depth 的阶段包括 Environment 启动、输入准备、模型加载、深度估算、缓存
|
||||
写出和面片构建。进度更新会强制刷新状态栏与窗口,无需移动鼠标即可实时显示。安装中断后残留的
|
||||
半成品 Environment 会在下次安装前自动清理,不会因为中断而无法再次安装。
|
||||
|
||||
导入过程精确保留 NPZ 内深度值之间的差值。例如深度 `2.0` 和 `5.5` 在
|
||||
`Displacement Strength = -1` 时,沿局部 Z 轴相差 `3.5 Blender Unit`。它不会把数值压缩到
|
||||
`0..1`,也不再提供视觉归一化模式。
|
||||
|
||||
面片尺寸以有效深度的中位数 `Zref` 作为参考截面。先用 `mini_npz` 中的相机内参计算原始
|
||||
投影面积,再恢复源图纵横比:
|
||||
|
||||
```text
|
||||
RawWidth = depth_width × Zref × abs(Displacement Strength) / fx
|
||||
RawHeight = depth_height × Zref × abs(Displacement Strength) / fy
|
||||
Area = RawWidth × RawHeight
|
||||
Aspect = source_image_width / source_image_height
|
||||
Width = sqrt(Area × Aspect)
|
||||
Height = sqrt(Area / Aspect)
|
||||
```
|
||||
|
||||
这样即使 DA3 估计出的 `fx/fy` 不一致,也不会把正方形原图拉成长方形。主点 `cx/cy` 仍用于
|
||||
确定原始相机面片,随后对象 XY Transform 将它匹配到源 Image Empty。生成对象会记录
|
||||
`snapform_reference_depth`、`snapform_camera_plane_width` 和
|
||||
`snapform_camera_plane_height`,便于检查实际采用的
|
||||
参考深度和尺寸。
|
||||
|
||||
材质始终导入深度 Image Texture,并连接到 Displacement 节点和 Material Output;材质 Surface
|
||||
Displacement 设置为 `Displacement Only`,DA3 与 Original 都使用 `Midlevel = 0`、`Scale = 1`。MoGe-2 的位移只在专用 Geometry Nodes 中处理。
|
||||
`o_material_displacement` 为 `False` 时,材质把 Height 乘以零,置换只在几何节点中生效;为 `True` 时,
|
||||
几何节点关闭结构置换,材质 Height 恢复。色彩贴图的 Alpha 二值结果同时用于选择原始深度或有符号的
|
||||
`Background Depth`,使两种置换路径保持一致;未移除背景的普通不透明图片因此全部视为前景。
|
||||
视频与序列帧的颜色、深度与前景 Image Texture
|
||||
通过 image_user 的 Frames / Offset / Start 把第 i 帧映射到场景 `Start+i` 帧,并启用 Auto Refresh。
|
||||
颜色贴图到 Principled BSDF 之间使用独立的 Shader Node Group。DA3 Surface 使用 `O DA3 Surface Layer`,
|
||||
MoGe-2 Surface 使用独立的 `O MoGe2 Surface Layer`;它额外接收模型输出的 `Normal` 贴图,使用
|
||||
`Normal Map` 节点按 `Object Space` 解码后连接到 Bump 的 `Normal` 输入,使像素级模型法线与原图
|
||||
Bump 合并,不依赖重拓扑后的顶点密度;`Normal Strength` 默认值为 `1.0`,只调节模型 normal map,
|
||||
颜色贴图 Bump 仍由独立的 `Bump Scale` 控制;
|
||||
Depth Plane 使用 `O DA3 Depth Plane Layer` 材质组,并把背景剔除、背景深度与材质置换 Attribute
|
||||
读取全部收进组内。两者都接收 `Color`、`Alpha` 与 `Bump Scale`,只有 `O DA3 Depth Plane Layer`
|
||||
额外接收 Float 类型的 `Depth`。
|
||||
`O DA3 Surface Layer` 只输出 `Color`、`Roughness`、`Alpha` 与 `Normal`,以原图 Color 保留 Bump 效果,
|
||||
不创建 Displacement 节点或输出;`O DA3 Depth Plane Layer` 另外输出 `Displacement`。外层材质只保留贴图、
|
||||
材质组、Principled BSDF 与 Material Output,方便继续调整。
|
||||
Depth Plane 的 Bump Height 直接使用材质组的 `Color` 输入,只受独立的 `Bump Scale` 影响,不使用深度贴图,
|
||||
也不经过 `Displacement` 模式开关、`Depth Scale` 或 `Background Depth`;材质 Displacement 仍使用这些参数处理后的深度。
|
||||
无论选择哪种置换方式,Layer 的 `Color` 都会连接 Principled BSDF 的 `Base Color`、
|
||||
`Subsurface Radius` 与 `Emission Color`;Layer 的 `Normal` 同时连接 `Normal` 与 `Coat Normal`,
|
||||
通过 `Bump.Height → Bump.Normal` 提供表面细节。Subsurface、Emission 与 Coat 的强度等其他输入
|
||||
保持 Principled BSDF 默认值。Bump 的基础 Distance 为
|
||||
`0.001`,会乘以面片 Transform 的统一缩放倍率。原图 Color 还会经过 `Float Curve` 连接到
|
||||
Principled BSDF 的 `Roughness`,曲线两端输出分别为 `0.5` 和 `0.1`;材质 IOR 保持
|
||||
Principled BSDF 默认值 `1.5`。
|
||||
开启 `Shadeless` 后,色彩贴图 Color 接入 `O Shadeless`。该组沿用 Blender Images as Planes 的
|
||||
Diffuse、Emission、Light Path 与射线判断连接;二值 Alpha 仍作为 Mix Shader 系数,在 Transparent BSDF
|
||||
与 `O Shadeless` 之间混合后连接 Material Output。外层不再创建 Emission、Principled BSDF、Bump 或
|
||||
Float Curve。开启 `Remove Background` 时色彩贴图使用 `.color`;关闭时使用 `.color`。
|
||||
|
||||
这里的“原始距离”是 DA3 模型输出的尺度。普通 Small、Base、Large 和 Giant 模型的深度不应
|
||||
被解释为现实世界的米;需要现实尺度时应选择带 metric depth 的
|
||||
`DA3 Nested Giant Large 1.1`。此外,当前结果仍是固定 XY 平面上的深度位移面,不是使用
|
||||
相机内参逐像素反投影得到的完整 camera-space 网格;中位深度截面为 Geometry Nodes
|
||||
深度面片提供正确的整体高宽尺度。
|
||||
|
||||
生成完成后会直接创建面片,不提供缓存重建入口或单独的导入参数。重新选择
|
||||
`Convert to Depth Plane` 会按当前 Depth from 再次转换。
|
||||
面片、深度 Image、颜色 Image、Material 与 Geometry Nodes 修改器会在同一个 Blender Undo 步骤中创建;转换完成后执行
|
||||
`Ctrl+Z` 会完整撤销本次转换,不会越过转换去回退更早的 Image Empty 操作。
|
||||
|
||||
Image Empty 使用 Packed Image 时,SnapForm 会通过 Blender 写出临时 PNG 交给 DA3,
|
||||
不会 Unpack 或修改源 Image;任务结束后自动删除临时文件。普通图片仍直接读取源文件。DA3 的原始
|
||||
`results.npz` 保留在对应 job 文件夹根下,生成的浮点深度 Image 会 Pack 到
|
||||
当前 `.blend`。Depth Image 与 Displace Texture 根据源 Image datablock 命名。例如 Empty Object
|
||||
名为 `obj_img`、引用的 Image 名为 `img.png` 时,Depth Image 与 Texture 都命名为 `img.depth`;
|
||||
生成的 Object、Mesh 与 Material 仍命名为 `obj_img`。Pack 使用 OpenEXR,因而不会把大于 `1.0`
|
||||
的原始深度截断。已有同名 datablock 时由 Blender 自动追加 `.001` 等编号。
|
||||
视频输入时 Blender 无法整体 Pack 序列帧,因此 `.depth` 与颜色序列改为引用 job 目录根下的
|
||||
`depth.0001.exr`、`color.0001.jpg`(`.color`)或 `color.0001.png`,并在每帧保留原始
|
||||
浮点深度;保存 `.blend` 后再次打开仍需对应 job 目录存在。
|
||||
|
||||
每个 Server Job 被接受时,Job Runtime 在 Runtime Storage Root 的 `jobs/` 下创建一个独立文件夹。文件夹名是 32 位 opaque Job ID,不包含任务类型或对象名称。执行逻辑从 `JobContext.directory` 获取路径,输出产物直接写入该目录。
|
||||
|
||||
Blender 为 Packed Image 或序列帧生成的输入副本先保存在系统临时目录,Server 读取完成后由 Blender 清理,不归档到 Job 目录。普通磁盘文件和视频仍直接读取原文件。Server 日志统一写入 `server.log`,进度只保存在 Server 内存中。Environment Bootstrap 使用 Storage Root 下的 `install.log` 与临时的 `install-status.json`,不创建 Job 目录。
|
||||
|
||||
正式几何输出由 `generate-depth` 与 `generate-surface` 负责。Debug 使用彼此独立的
|
||||
`debug-da3`、`debug-moge2` 与 `debug-ben2`,只保存模型 API 的直接返回,不进入 Geometry 或
|
||||
Surface 输出流程。
|
||||
|
||||
Job 目录中的典型正式产物如下:
|
||||
|
||||
```text
|
||||
<opaque-job-id>/
|
||||
├── results.npz、mesh.npz 或 normal.png
|
||||
└── prediction.npz Debug 原始 API 返回
|
||||
|
||||
<opaque-job-id>/
|
||||
├── color.0001.jpg/png… 正式工作流颜色层(原图复制或 BEN2 Alpha)
|
||||
├── depth.0001.exr… AI 深度序列帧
|
||||
└── normal.png MoGe-2 Surface 法线贴图
|
||||
```
|
||||
|
||||
建议从 `DA3 Base` 开始。它使用 Apache 2.0 模型权重。
|
||||
@@ -1,27 +0,0 @@
|
||||
# 选择转换方式
|
||||
|
||||
SnapForm 提供三种日常工作流。选择结果时,先考虑你需要的是可调节的“深度面片”、带透视结构的“空间网格”,还是只需要透明前景。
|
||||
|
||||
| 工作流 | 适合场景 | 输入 | 结果 | 主要优点 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Depth Plane | 人物、产品、插画、动态背景、快速场景搭建 | 图片、视频、序列帧 | 带深度位移的平面 | 快速、可动画、Geometry Nodes 参数直观 |
|
||||
| Surface | 建筑、室内、街景、需要匹配视角的单图 | 单张图片 | 透视纹理网格 | 空间感更强,可创建匹配相机 |
|
||||
| Remove Background | 抠出人物或物体,继续作为 Image Empty 使用 | 图片、视频、序列帧 | 带透明 Alpha 的颜色图 | 不创建网格,保留 Empty 的位置和显示设置 |
|
||||
|
||||
## 什么时候选择 Depth Plane
|
||||
|
||||
Depth Plane 把估算深度作为面片位移,特别适合需要快速获得层次感、又希望继续用少量参数控制结果的场景。它也支持视频和序列帧,是制作动态立体素材的首选。
|
||||
|
||||
[阅读 Depth Plane 指南](guide-depth-plane.md)
|
||||
|
||||
## 什么时候选择 Surface
|
||||
|
||||
Surface 会根据模型估算的相机和深度构建透视网格。相较 Depth Plane,它能更自然地表达近大远小和画面内部的空间关系,但目前只支持单张图片。
|
||||
|
||||
[阅读 Surface 指南](guide-surface.md)
|
||||
|
||||
## 什么时候只去除背景
|
||||
|
||||
如果只想把 Image Empty 换成透明前景图,不需要任何几何结构,直接选择 **Remove Background**。对象的 Transform、显示尺寸和 Offset 会保留。
|
||||
|
||||
[阅读去除背景指南](remove-background.md)
|
||||
86
mkdocs.yml
86
mkdocs.yml
@@ -1,57 +1,70 @@
|
||||
site_name: SnapForm 文档
|
||||
site_url: https://docs.omoolab.xyz/snapform/
|
||||
site_name: AnyImage 文档
|
||||
site_url: https://docs.omoolab.xyz/anyimage/
|
||||
site_author: OmooLab
|
||||
site_description: SnapForm 是一款将图片、视频和序列帧转换为立体面片的 Blender 扩展。
|
||||
site_description: AnyImage 是一款将图片、视频和序列帧转换为立体面片的 Blender 扩展。
|
||||
|
||||
repo_name: OmooLab/SnapForm
|
||||
repo_url: https://git.omoolab.xyz/icrdr/SnapForm
|
||||
repo_name: OmooLab/AnyImage
|
||||
repo_url: https://git.omoolab.xyz/icrdr/AnyImage
|
||||
|
||||
nav:
|
||||
- SnapForm:
|
||||
- AnyImage:
|
||||
- index.md
|
||||
- 开始使用:
|
||||
- 安装与首次配置: installation.md
|
||||
- 五分钟上手: quick-start.md
|
||||
- 使用指南:
|
||||
- 选择转换方式: workflows.md
|
||||
- Depth Plane: guide-depth-plane.md
|
||||
- Surface: guide-surface.md
|
||||
- 去除背景: remove-background.md
|
||||
- 视频与序列帧: animation.md
|
||||
- 模型、设备与存储: models-and-storage.md
|
||||
- 常见问题: faq.md
|
||||
- 参考与开发:
|
||||
- 功能参考: features.md
|
||||
- Depth Plane 实现: depth-plane.md
|
||||
- Surface 实现: surface.md
|
||||
- 输出格式: outputs.md
|
||||
- Job Server: server.md
|
||||
- 程序设计与开发: development.md
|
||||
- 安装与首次配置: getting-started/installation.md
|
||||
- 内部实现:
|
||||
- 总览: internals/index.md
|
||||
- 程序架构: internals/architecture.md
|
||||
- Common 公共函数: internals/common.md
|
||||
- 基础设施:
|
||||
- Job Runtime 与 Server: internals/runtime.md
|
||||
- Job 产物: internals/outputs.md
|
||||
- Operators:
|
||||
- 总览: internals/operators/index.md
|
||||
- AI 环境管理: internals/operators/ai-setup.md
|
||||
- Clipboard Image: internals/operators/clipboard-image.md
|
||||
- Convert to Plane: internals/operators/convert-to-plane.md
|
||||
- Convert to Panorama: internals/operators/convert-to-panorama.md
|
||||
- Cutout Tool: internals/operators/cutout-tool.md
|
||||
- Frame: internals/operators/frame.md
|
||||
- Mask: internals/operators/mask.md
|
||||
- Rectify: internals/operators/rectify.md
|
||||
- Remove Background: internals/operators/remove-background.md
|
||||
- Upscale: internals/operators/upscale.md
|
||||
- 开发维护:
|
||||
- 开发与发布: internals/development.md
|
||||
- 节点资产: internals/node-assets.md
|
||||
|
||||
theme:
|
||||
name: material
|
||||
favicon: assets/logo.png
|
||||
logo: assets/logo.png
|
||||
language: zh
|
||||
font:
|
||||
text: Open Sans
|
||||
code: Cascadia Code
|
||||
palette:
|
||||
# Palette toggle for automatic mode
|
||||
- media: "(prefers-color-scheme)"
|
||||
primary: black
|
||||
toggle:
|
||||
icon: fontawesome/solid/circle-half-stroke
|
||||
name: 切换到浅色模式
|
||||
name: Switch to light mode
|
||||
|
||||
# Palette toggle for light mode
|
||||
- media: "(prefers-color-scheme: light)"
|
||||
scheme: default
|
||||
primary: black
|
||||
toggle:
|
||||
icon: fontawesome/regular/moon
|
||||
name: 切换到深色模式
|
||||
name: Switch to dark mode
|
||||
|
||||
# Palette toggle for dark mode
|
||||
- media: "(prefers-color-scheme: dark)"
|
||||
scheme: slate
|
||||
primary: black
|
||||
toggle:
|
||||
icon: fontawesome/solid/moon
|
||||
name: 跟随系统外观
|
||||
name: Switch to system preference
|
||||
features:
|
||||
- header.autohide
|
||||
- navigation.footer
|
||||
@@ -59,10 +72,8 @@ theme:
|
||||
- navigation.sections
|
||||
- navigation.tabs
|
||||
- navigation.tabs.sticky
|
||||
- navigation.top
|
||||
- search.highlight
|
||||
- search.suggest
|
||||
- content.code.copy
|
||||
- content.code.select
|
||||
|
||||
extra:
|
||||
version:
|
||||
@@ -71,16 +82,27 @@ extra:
|
||||
extra_css:
|
||||
- stylesheets/extra.css
|
||||
|
||||
extra_javascript:
|
||||
- javascripts/extra.js
|
||||
|
||||
copyright: Copyright © 2026 OmooLab
|
||||
|
||||
plugins:
|
||||
- search
|
||||
|
||||
markdown_extensions:
|
||||
- attr_list
|
||||
- md_in_html
|
||||
- mkdocs-click
|
||||
- admonition
|
||||
- pymdownx.highlight:
|
||||
anchor_linenums: true
|
||||
line_spans: __span
|
||||
pygments_lang_class: true
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.snippets
|
||||
- pymdownx.details
|
||||
- pymdownx.superfences
|
||||
- pymdownx.superfences:
|
||||
custom_fences:
|
||||
- name: mermaid
|
||||
class: mermaid
|
||||
- pymdownx.tabbed:
|
||||
alternate_style: true
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-10
|
||||
@@ -0,0 +1,72 @@
|
||||
## Context
|
||||
|
||||
Remove Background 通过 ImageEditTarget 准备输入并提交结果。普通文件按路径校验,packed 图片导出 PNG;后端 BEN2 接收 Pillow RGB,预测 mask 后转换成 8-bit RGBA,Job 写入 PNG。源像素、结果载入和图片替换都涉及 HDR 精度边界。
|
||||
|
||||
BEN2 网络输出是 alpha,识别本身可复用既有模型。HDR 支持的关键是分离识别预览与原始 RGB,并让浮点信息贯穿主线程合成、EXR 编码和结果替换。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
本次交付为 HDR 静态图去背景:保留源图的场景线性色彩、有效动态范围和尺寸,以模型预测更新透明度。支持 Image Empty 和材质 Image Texture 两种已有入口,以及 Blender 可读取的外部、packed、generated 浮点静态图片。普通媒体沿用既有处理方式;HDR 动画在提交前明确提示首版支持范围。Upscale 的 HDR 适配作为独立能力评估。
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. 在 Blender 侧捕获源图和准备预览
|
||||
|
||||
以 `Image.is_float` 及真实图片来源判断 HDR 路径,扩展名仅用于来源识别与提示。所有可读浮点静态图片走该路径,包括数值暂时处于 0–1 内的图片。准备时读取当前像素,包含尚未保存的编辑;为本次任务保存独立的 RGBA 数值快照、尺寸、色彩空间和 alpha 解释信息。
|
||||
|
||||
Blender 数据仅在主线程准备与提交阶段访问,Job 参数只包含普通字典中的路径、字符串和数值。后端始终读取 PNG 识别预览,因此公共媒体格式校验仍校验实际提交文件。源图与临时文件的所有权由准备阶段和 Job cleanup 明确交接。
|
||||
|
||||
### 2. 用固定映射生成可识别的普通 RGB
|
||||
|
||||
识别预览从场景线性、非预乘 RGB 生成。Blender 的浮点缓冲已经采用场景线性颜色与预乘 alpha;预览先解关联,再采用按亮度的 Reinhard 映射,以非负亮度 Y 计算 `RGB / (1 + Y)`,执行线性 Rec.709 到 sRGB 编码并限制到可编码范围。负值只在预览中处理;源浮点数值保持原样。全黑图保持有限值。预览按源 alpha 合成到黑底,符合现有 BEN2 输入语义。
|
||||
|
||||
映射固定且不读取用户当前 View Transform、Look、Exposure,避免切换显示设置导致 mask 变化。源颜色解码由 Blender 完成。数据图片与 CHANNEL_PACKED 输入在准备时给出明确错误。
|
||||
|
||||
该映射是首版确定性基线。用实际高动态范围图片验证高光边缘、暗部与半透明物体;若识别不足,在本变更内调整并固定预览算法和对应行为测试。
|
||||
|
||||
### 3. 让 BEN2 推理核心返回数值 alpha
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[主线程捕获 HDR RGBA 快照] --> B[固定色调映射生成 PNG 预览]
|
||||
B --> C[BEN2 推理]
|
||||
C --> D[浮点 alpha 归一化并还原源尺寸]
|
||||
D --> E[Job 写入 alpha.npy]
|
||||
A --> F[主线程合成原始 RGB 与新 alpha]
|
||||
E --> F
|
||||
F --> G[写入 32-bit float RGBA EXR]
|
||||
G --> H[验证并提交到原图片目标]
|
||||
```
|
||||
|
||||
将现有推理核心重构为返回原尺寸 float32 alpha 数组。保留既有 mask 归一化语义,缩放过程使用浮点数据并在最后限制到 0–1,检查尺寸与有限值。普通 RGBA 消费者在自身产物边界完成量化与合成,清理旧核心入口并更新真实调用者。
|
||||
|
||||
Remove Background Job 增加明确的产物模式,例如 `output_kind: "alpha"`,HDR 静态图产出 `alpha.npy` 并通过结果字段声明。读取使用 `allow_pickle=False`,验证形状、范围和有限值。现有普通图片与动画消费者继续取得对应的 RGBA 结果。
|
||||
|
||||
### 4. 主线程合成和 EXR 持久化
|
||||
|
||||
在非预乘语义下计算 `A_out = A_source × A_predicted`,保留原始 RGB。Blender 原生浮点缓冲使用预乘 RGB,因此实现直接将 RGBA 乘以预测 alpha,保持解关联后的颜色;解码 STRAIGHT EXR 后同样采用该缓冲语义。零 alpha 像素的不可恢复颜色置零,NONE 模式按不透明源处理。测试分别比较 straight RGB 或重新合成后的可见颜色。
|
||||
|
||||
创建 `float_buffer=True` 的结果图片,使用独立临时场景指定 32-bit float RGBA EXR、ZIP 压缩,通过 `save_render` 写出场景线性值。结果采用 Linear Rec.709 与 PREMUL,保存后载入并核对数值,再打包和提交;临时场景及文件在对应阶段结束后释放。
|
||||
|
||||
复用 ImageEditTarget 和 common 内的提交、共享引用隔离、图片内容备份与恢复。HDR 的 Empty 提交同时保护材质节点等其他实际使用者。独占源图包含未保存像素或为生成图时,将其当前浮点内容转为内嵌 EXR 并建立撤销基线,保留 Image 身份;一次撤销恢复原始内容,重做恢复处理结果。提交失败时恢复操作前的源状态。先验证候选结果再提交,其他共享使用者保持原状。
|
||||
|
||||
### 5. 保持异步编辑事务完整
|
||||
|
||||
使用提交时的源快照进行合成,完成前复核原目标及图片绑定。纹理节点保存独立的 `anyimage_identity`,提交时与捕获值核对,避免 Blender 删除重建节点后复用 RNA 地址造成误认。增加对捕获后源像素或色彩解释被修改的冲突检查,发现冲突则提示重新执行并保留当前编辑。
|
||||
|
||||
临时预览由输入 cleanup 释放,alpha 由 Job 目录管理,候选 EXR 完成载入和打包后释放临时副本。取消、推理失败、合成失败、提交失败均释放快照与本次临时数据。成功结果支持一次撤销、重做以及保存重开。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- 固定预览映射可能压低极暗部的可辨识度:用代表性 HDR 图片进行真实 BEN2 验收。
|
||||
- Blender 像素、alpha 模式与 EXR 编码之间存在转换:以已知数值的真实 Blender 保存重开测试约束实现。
|
||||
- 源快照增加内存占用:仅保留合成和冲突检测所需的数据,所有终止路径释放;大型输入准备失败时提供可读错误。
|
||||
- 工作区相关公共入口正在修改:实施前核对现有变更,复用最新公共校验和事务接口。
|
||||
|
||||
## Migration Plan
|
||||
|
||||
先完成数值 alpha 核心与现有消费者回归,再实现 HDR 准备、合成和事务提交。验证普通媒体行为及 HDR round-trip 后完成能力交付。Job 协议由同版本插件与后端一起更新。
|
||||
|
||||
## Open Questions
|
||||
|
||||
无需要用户决策的前置问题。Blender 像素空间、PREMUL round-trip 与固定预览的识别质量已通过数值测试及实际 BEN2 推理确认。
|
||||
@@ -0,0 +1,27 @@
|
||||
## Why
|
||||
|
||||
BEN2 通过普通 RGB 图像预测前景透明度。当前去背景流程用 Pillow 合成并保存 PNG,无法保留 HDR 图像的浮点颜色。将识别预览与原始颜色分离,可以让用户对 HDR 图片去背景,同时保留用于渲染的亮度和色彩。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 为 Remove Background 增加 HDR 静态图处理,覆盖 Blender 已加载的 EXR、Radiance HDR、已打包及生成的浮点图片。
|
||||
- 从提交时的浮点像素生成经过固定色调映射的 RGB 识别预览,原始颜色留在 Blender 侧。
|
||||
- BEN2 提供浮点 alpha 结果;主线程将其与源 alpha 相乘,并与原始颜色合成。
|
||||
- 结果使用 32-bit float RGBA EXR 持久化,支持保存重开、共享图片隔离、撤销和失败恢复。
|
||||
- 对 HDR 动画输入在准备阶段给出明确提示;普通图片与现有视频、序列沿用当前行为。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `hdr-background-removal`: HDR 静态图片的识别预览、alpha 预测、浮点结果及生命周期。
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
无已发布能力规格需要修改;与现有 ImageEditTarget 的目标提交语义衔接。
|
||||
|
||||
## Impact
|
||||
|
||||
- Blender:`common/image.py`、`common/image_target.py`、`operators/remove_background.py`;复杂的 HDR 图像处理可独立放入 `common/hdr_image.py`。
|
||||
- 后端:`server/models/onnx_ben2.py`、`server/models/ben2.py`、`server/jobs/remove_background.py`,以及对应测试。
|
||||
- 输入仍为普通预览 PNG,新增 Job 的 alpha 产物模式,复用 NumPy 保存数值结果。BEN2 权重与运行依赖保持现有配置。
|
||||
- 与进行中的 `unify-ai-input-validation` 及图片目标编辑变更共同使用实施时的真实调用链。
|
||||
@@ -0,0 +1,89 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: HDR still image preparation
|
||||
|
||||
系统 SHALL 为可读的浮点静态图片提供 Remove Background,覆盖 Image Empty 与材质 Image Texture 的外部、packed 和 generated 图片。输入 SHALL 采用任务开始时的当前像素快照。
|
||||
|
||||
#### Scenario: An HDR still is submitted
|
||||
- **WHEN** 用户对可读的 EXR、HDR 或其他浮点静态图片执行 Remove Background
|
||||
- **THEN** 系统捕获当前 RGBA、尺寸与颜色解释,并提交普通 RGB 识别预览
|
||||
|
||||
#### Scenario: Unsaved pixels are present
|
||||
- **WHEN** 源浮点图包含未保存的像素编辑
|
||||
- **THEN** 识别预览与最终 RGB 均使用当前编辑后的快照
|
||||
|
||||
#### Scenario: An HDR animation is submitted
|
||||
- **WHEN** 用户对 HDR 序列或浮点动画执行 Remove Background
|
||||
- **THEN** 系统在创建推理任务前提示本次 HDR 功能支持静态图,并保持源图状态
|
||||
|
||||
### Requirement: Deterministic recognition preview
|
||||
|
||||
系统 SHALL 通过确定性的曝光范围压缩及色彩编码生成 BEN2 的普通 RGB 预览,原始 RGB SHALL 独立保留。预览 SHALL 正确解释源 alpha,且不受用户显示变换设置影响。
|
||||
|
||||
#### Scenario: Highlights exceed one
|
||||
- **WHEN** 源图包含多个大于 1 的不同亮度值
|
||||
- **THEN** 预览先进行色调映射再编码,原始快照保留这些数值
|
||||
|
||||
#### Scenario: Display settings change
|
||||
- **WHEN** 相同源像素分别在不同 View Transform、Look 或 Exposure 下提交
|
||||
- **THEN** 识别预览一致
|
||||
|
||||
#### Scenario: The image is black or contains negative values
|
||||
- **WHEN** 可读源像素为全黑或包含有限负颜色值
|
||||
- **THEN** 预览数值有限,源 RGB 不因预览映射改变
|
||||
|
||||
### Requirement: Float alpha result
|
||||
|
||||
HDR 去背景 Job SHALL 返回原始尺寸的 float32 alpha 数组,数值有限且位于 0–1。系统 SHALL 在使用前验证产物。
|
||||
|
||||
#### Scenario: BEN2 predicts a soft edge
|
||||
- **WHEN** 模型预测连续透明度边缘
|
||||
- **THEN** alpha 经过浮点缩放返回,合成前不量化为 8-bit
|
||||
|
||||
#### Scenario: Alpha is invalid
|
||||
- **WHEN** alpha 文件损坏、尺寸不符或包含非有限及越界数值
|
||||
- **THEN** 系统报告结果错误,保持源图片与目标绑定
|
||||
|
||||
### Requirement: HDR color and transparency persistence
|
||||
|
||||
系统 SHALL 保留源 RGB 的场景线性色彩及动态范围,并以源 alpha 与预测 alpha 的乘积作为结果透明度。结果 SHALL 为同尺寸浮点图像并采用 32-bit float RGBA EXR 持久化,颜色误差 SHALL 限于浮点运算与必要颜色转换的容差。
|
||||
|
||||
#### Scenario: Straight alpha is present
|
||||
- **WHEN** 源图为 straight alpha 且存在大于 1 的 RGB 和部分透明像素
|
||||
- **THEN** 结果 RGB 保留,alpha 等于源 alpha 乘预测 alpha
|
||||
|
||||
#### Scenario: Premultiplied alpha is present
|
||||
- **WHEN** 源图采用 PREMUL 且包含部分透明及完全透明像素
|
||||
- **THEN** 系统按 alpha 关联语义合成,边缘颜色正确且结果没有 NaN 或 Inf
|
||||
|
||||
#### Scenario: The project is saved and reopened
|
||||
- **WHEN** 用户保存含处理结果的 blend 文件后重新打开
|
||||
- **THEN** 结果尺寸、HDR 数值、颜色解释和透明度保持一致
|
||||
|
||||
### Requirement: Transactional target update
|
||||
|
||||
系统 SHALL 复用既有图片目标提交规则,保护共享使用者并支持撤销、重做与错误恢复。Blender 数据访问 SHALL 发生于主线程,任务通信 SHALL 使用可序列化普通参数。
|
||||
|
||||
#### Scenario: Another owner shares the image
|
||||
- **WHEN** 源图片存在其他实际使用者
|
||||
- **THEN** 仅发起操作的目标取得独立 HDR 结果,其他使用者仍引用原图
|
||||
|
||||
#### Scenario: The source changes during inference
|
||||
- **WHEN** 原目标失效、改绑图片或源像素与颜色解释发生修改
|
||||
- **THEN** 系统拒绝覆盖当前状态,提示重新执行并清理本次临时资源
|
||||
|
||||
#### Scenario: The operation is cancelled or fails
|
||||
- **WHEN** 准备、推理、合成或提交取消或失败
|
||||
- **THEN** 系统保持或恢复源图及目标绑定,释放本次快照和临时数据
|
||||
|
||||
#### Scenario: The user undoes and redoes
|
||||
- **WHEN** 用户撤销并重做一次成功的 HDR 去背景
|
||||
- **THEN** 原始和处理后的浮点内容及绑定正确恢复,重做无需重新推理
|
||||
|
||||
### Requirement: Existing media behavior
|
||||
|
||||
系统 SHALL 保留普通图片、视频和普通序列的现有去背景行为,所有 BEN2 消费者 SHALL 适配统一的数值 alpha 核心。
|
||||
|
||||
#### Scenario: An existing regular image or animation is processed
|
||||
- **WHEN** 用户提交现有支持的普通图片或动画
|
||||
- **THEN** 系统按既有帧选择、透明度合成和输出规则完成操作
|
||||
@@ -0,0 +1,31 @@
|
||||
## 1. 验证浮点图片基础行为
|
||||
|
||||
- [x] 1.1 核对最新输入校验、BEN2 消费者与图片提交调用链,保留工作区已有修改。
|
||||
- [x] 1.2 在真实 Blender 中验证已知 HDR 数值、straight/PREMUL alpha、色彩空间及 32-bit EXR 保存载入行为,确定可用颜色转换与数值容差。
|
||||
- [x] 1.3 检查图片内容备份、原位替换、打包及撤销对 float buffer 的支持,定位需要修改的公共边界。
|
||||
|
||||
## 2. 分离 BEN2 alpha 核心
|
||||
|
||||
- [x] 2.1 将推理核心改为返回原尺寸 float32 alpha,保留归一化行为,使用浮点缩放并校验有限值。
|
||||
- [x] 2.2 更新所有真实 BEN2 消费者及相关测试,在各自输出边界合成 RGBA,移除旧核心入口。
|
||||
- [x] 2.3 为 Remove Background Job 增加静态 alpha 产物模式、结果字段及安全的 NumPy 读写校验,覆盖错误与取消路径。
|
||||
|
||||
## 3. 准备 HDR 识别输入
|
||||
|
||||
- [x] 3.1 在主线程识别浮点静态图并捕获当前 RGBA 快照,覆盖外部、packed、generated 和未保存像素。
|
||||
- [x] 3.2 实现固定色调映射及 sRGB 预览编码,覆盖 alpha、负值、全黑及显示设置独立性。
|
||||
- [x] 3.3 接入 Remove Background 的准备与普通参数提交,明确拒绝 HDR 动画并验证临时输入清理。
|
||||
|
||||
## 4. 合成与提交 HDR 结果
|
||||
|
||||
- [x] 4.1 校验 float alpha 并按 straight/PREMUL 语义合成,保留源 HDR RGB 和尺寸。
|
||||
- [x] 4.2 创建浮点结果并写入无损 32-bit RGBA EXR,完成载入、打包及临时文件释放。
|
||||
- [x] 4.3 复用并补全 ImageEditTarget 的浮点提交与恢复行为,覆盖共享隔离、独占 Image 身份、源修改冲突、撤销和重做。
|
||||
- [x] 4.4 验证取消、失败、目标失效各路径的资源释放和源状态恢复。
|
||||
|
||||
## 5. 验收
|
||||
|
||||
- [x] 5.1 更新并运行相关后端、common 和 Operator 测试,覆盖浮点 alpha 精度与现有普通媒体行为。
|
||||
- [x] 5.2 用已知 RGB 大于 1、有限负值、软 alpha 的图片进行真实 Blender 两种入口及保存重开验证;记录数值误差。
|
||||
- [x] 5.3 用代表性 HDR 实图运行 BEN2,检查高光、暗部、半透明边缘与黑底预览效果,必要时调整固定映射并补齐验证。
|
||||
- [x] 5.4 运行全量 `uv run pytest` 并确认正常退出,检查最终差异及旧入口引用,记录验证结果。
|
||||
@@ -0,0 +1,32 @@
|
||||
## 验证环境
|
||||
|
||||
2026-09-10,Windows,项目 uv 环境,Blender Python 4.5.3 LTS,BEN2 本地 `models/BEN2-ONNX/onnx/model_fp16.onnx`,ONNX Runtime CPU。
|
||||
|
||||
## 浮点与编辑事务
|
||||
|
||||
- 真实 Blender 验证外部 EXR、packed、generated、未保存像素,覆盖 Image Empty 与材质 Image Texture,以及共享和独占图片。
|
||||
- RGBA 样本包含大于 1 的颜色、有限负值、零 alpha 与部分透明像素;32-bit ZIP EXR 保存、打包、载入和 blend 保存重开均通过数值检查。
|
||||
- 单独验证 STRAIGHT EXR 解码后的预乘缓冲语义,处理后解关联颜色在 `rtol=1e-6` 内保持一致。
|
||||
- 已验证 Radiance `.hdr` 实际文件读取与识别预览生成,源浮点像素保持不变。
|
||||
- 原生 Operator 的撤销、重做、取消、提交异常和资源释放通过测试;未保存的独占浮点源图采用内嵌 EXR 撤销基线。
|
||||
- 纹理节点删除重建测试覆盖独立身份字段,防止 RNA 地址复用造成异步目标误认。
|
||||
|
||||
## 实际 BEN2 推理
|
||||
|
||||
Cycles 渲染 320×320 金属 Suzanne,16 samples,两盏面光源,原始 RGB 峰值约 112.432。用渲染 alpha 作为轮廓参考,构造三档曝光的灰底 HDR 图,以及保留原始软 alpha 的透明图。四组均执行真实 BEN2 Job、浮点 alpha 读回、Blender 合成及 EXR 持久化。
|
||||
|
||||
| 输入 | 原始 RGB 峰值 | 输出预乘 RGB 峰值 | 轮廓 IoU |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| 正常亮度 | 112.4321 | 112.4235 | 0.9822 |
|
||||
| 16 倍亮度 | 1798.9137 | 1796.3881 | 0.9874 |
|
||||
| 0.05 倍亮度 | 5.6216 | 5.6176 | 0.9757 |
|
||||
| 原始透明度 | 112.4321 | 112.4292 | 0.9656 |
|
||||
|
||||
IoU 使用预测 alpha 与渲染 alpha 的 0.5 阈值轮廓计算。四组 EXR 读回值相对于 `source_rgba × predicted_alpha` 的最大绝对误差均为 0。已目视检查高亮预览、低亮度预览与预测轮廓。该结果验证当前渲染样例的识别效果;不同实图的抠图质量取决于模型预测。
|
||||
|
||||
## 自动检查
|
||||
|
||||
- OpenSpec 严格校验通过。
|
||||
- BEN2 的旧 `infer` 入口及旧节点使用者检查名称已清理,相关调用者和测试已更新。
|
||||
- 最终 `uv run pytest -q`:1074 passed、2 xfailed、52 subtests passed,175.71 秒,退出码 0。两项 xfail 为已有的浮点图片显示色彩空间适配测试。
|
||||
- `git diff --check` 通过。
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-12
|
||||
@@ -0,0 +1,49 @@
|
||||
## Context
|
||||
|
||||
Cutout 的 `SelectCutoutSelection` 已继承 `common.viewport.ImageGesture`,目前固定为 Lasso 并通过拖动键位启动。Mask 的 Polyline 已由该共用类实现。Cutout 完成选区后打开 Shape 饼菜单,Depth Solid 经异步生成后使用 `O Image Depth Cutout` 创建对象。
|
||||
|
||||
节点构建内部的 Uniform Thickness 输入在保存前显示名改为 Thickness;节点组同时存在 Balloon 使用的 Thickness 输入,初始化必须区分 socket 标识。
|
||||
|
||||
## Goals
|
||||
|
||||
支持 Cutout 逐点剪出及 Uniform 初始化;统一 Mask 的 Extend 命名和 Set 默认值。
|
||||
|
||||
## Decisions
|
||||
|
||||
### 共用手势与独立设置
|
||||
|
||||
在 `properties.py` 定义 Cutout 的 Lasso / Polyline 属性,由 Scene 保存,默认 Lasso。Cutout 在启动时读取手势,沿用 `ImageGesture` 的逐点输入、实时预览、闭合提示和取消清理。使用共用按下启动键位支持首点输入,并验证 Lasso 拖动完成以及空白处源对象保持行为。共用逻辑留在 `common`,Cutout 负责自己的状态文本和选区提交。
|
||||
|
||||
### 创建时应用 Uniform
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[读取 Cutout Gesture] --> B[ImageGesture 收集并确认选区]
|
||||
B --> C[Shape 饼菜单携带本次 Gesture 快照]
|
||||
C --> D[CutoutSelectionToShape 保存 Gesture]
|
||||
D --> E[生成所需图像并在主线程创建对象]
|
||||
E --> F{Polyline 且 Depth Solid}
|
||||
F -->|是| G[设置新修改器 Mode 为 Uniform]
|
||||
F -->|否| H[按 Shape 初始化修改器]
|
||||
```
|
||||
|
||||
手势作为普通字符串从选择 Operator 显式传到饼菜单、转换 Operator 和对象创建链路,异步响应使用该快照,避免用户中途切换 Scene 设置影响结果。服务器生成协议继续处理深度和法线产物;修改器初始化由主线程完成。Uniform 自动设置针对本次新建的 Depth Solid 对象,之后用户可手动修改 Mode。
|
||||
|
||||
### 节点厚度默认值
|
||||
|
||||
将构建内部 Uniform Thickness 默认值设为 0.5,并验证最终资产对应 socket 的默认值和新修改器实际值。检查现有通用 Thickness 赋值只初始化其目标输入,避免同名 socket 覆盖 Uniform 默认值。按现有资产修订机制更新 Depth Solid 版本并运行 `uv run node-group build`。
|
||||
|
||||
### Mask 模式
|
||||
|
||||
统一为 `SET` / Set、`EXTEND` / Extend、`SUBTRACT` / Subtract,保留枚举数值 0、1、2。Scene 和 Operator 均通过共用属性默认 Set。Extend 保持 `max(alpha, mask)`,Set 保持 `alpha * mask`,Subtract 保持 `alpha * (1-mask)`;状态文本和测试同步命名。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- 按下启动可能改变 Cutout 的源对象选择时机 → 覆盖 Lasso、Polyline、空白点击和取消行为。
|
||||
- 异步生成期间手势被切换 → 在提交时保存本次手势,并覆盖设置改变后的响应。
|
||||
- 同名 Thickness socket 容易导致写入错误 → 验证具体 socket identifier、资产默认值及实际修改器值。
|
||||
- 旧脚本传入 `ADD` 将失效 → 调用方统一使用 `EXTEND`,保持原枚举数值且不增加别名。
|
||||
|
||||
## Migration Plan
|
||||
|
||||
实现代码与测试后重建节点资产,执行相关测试及全量测试。回退时将本次代码、测试和资产作为同一变更回退。
|
||||
@@ -0,0 +1,27 @@
|
||||
## Why
|
||||
|
||||
Cutout 目前只能自由绘制 Lasso,沿直线轮廓剪出时需要逐点定义边界。复用 Mask 的 Polyline 交互,并为这种剪出方式设置 Uniform 厚度,可减少手动调整;Mask 使用 Extend 和默认 Set,使名称与常用操作一致。
|
||||
|
||||
## What Changes
|
||||
|
||||
- Cutout 增加 Gesture 选项 Lasso / Polyline,默认 Lasso,复用共用逐点交互和选区提交链路。
|
||||
- Polyline 剪出创建使用 `O Image Depth Cutout` 的 Depth Solid 对象时,自动设置修改器 Mode 为 Uniform。
|
||||
- `O Image Depth Cutout` 的 Uniform Thickness 接口默认值改为 0.5(资产中显示为 Thickness)。
|
||||
- **BREAKING** Mask 的 `ADD` / Add 统一重命名为 `EXTEND` / Extend,保留扩展 Alpha 的运算语义;默认模式从 Subtract 改为 Set。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `cutout-polyline`: Cutout 手势选择、共用 Polyline 交互,以及创建结果的 Uniform 初始化。
|
||||
- `mask-mode-options`: Mask 的 Set / Extend / Subtract 名称、运算和默认值。
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
无。当前 `openspec/specs` 尚无已归档的对应规范。
|
||||
|
||||
## Impact
|
||||
|
||||
- `properties.py`、`common/viewport.py`、`operators/cutout_tool`、`operators/mask_tool.py` 和相关注册、交互、对象创建测试。
|
||||
- `tools/nodes/groups/image_depth_cutout.py`、节点资产 `O_AnyImage.blend` 及节点测试;实现阶段需运行节点构建与验证。
|
||||
- 无新增依赖。已有 change 中的 Mask、Polyline 和 Cutout 提案作为实现背景,本 change 定义本次行为。
|
||||
@@ -0,0 +1,57 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Cutout offers persistent gesture selection
|
||||
|
||||
Cutout SHALL 提供 Lasso 和 Polyline 手势,默认 Lasso,并保存到 Scene 的独立 Cutout 设置。
|
||||
|
||||
#### Scenario: User selects Polyline
|
||||
- **WHEN** 用户将 Cutout Gesture 切换为 Polyline 并开始剪出
|
||||
- **THEN** 工具以首次左键按下的位置为起点,随后通过点击添加顶点
|
||||
|
||||
#### Scenario: User returns to Cutout
|
||||
- **WHEN** 用户选择 Polyline 后切换到其他工具再返回
|
||||
- **THEN** Cutout 继续使用 Polyline,Mask Gesture 保持自己的设置
|
||||
|
||||
#### Scenario: Default Lasso interaction
|
||||
- **WHEN** 新 Scene 中用户拖动 Cutout 并释放左键
|
||||
- **THEN** 工具以 Lasso 收集轮廓并在有效选区完成后打开 Shape 饼菜单
|
||||
|
||||
### Requirement: Cutout reuses shared Polyline interaction
|
||||
|
||||
Cutout MUST 复用 Mask 所用的共用 Polyline 手势,提供轮廓预览、起点闭合提示、左键添加顶点、Backspace 删除末点、点击起点或双击或 Enter 完成,以及 RMB、Esc 和切换工具取消。
|
||||
|
||||
#### Scenario: User completes a polygon
|
||||
- **WHEN** 至少三个顶点后,用户点击起点闭合范围内、双击或按 Enter / Numpad Enter
|
||||
- **THEN** 工具提交已确认的闭合 SelectionPath 并打开 Shape 饼菜单,闭合点击和双击第二击不添加重复顶点
|
||||
|
||||
#### Scenario: Too few points
|
||||
- **WHEN** 少于三个顶点时用户尝试完成
|
||||
- **THEN** 工具继续收集顶点,不打开 Shape 菜单
|
||||
|
||||
#### Scenario: User edits or cancels a polygon
|
||||
- **WHEN** 用户按 Backspace,或通过 RMB、Esc、切换工具取消
|
||||
- **THEN** Backspace 删除末点并至少保留起点;取消清理预览且不创建结果对象
|
||||
|
||||
#### Scenario: Empty click preserves the source
|
||||
- **WHEN** 用户在空白处启动 Cutout 手势
|
||||
- **THEN** 工具保持现有源对象选择规则,不因空白点击清空源对象
|
||||
|
||||
### Requirement: Polyline initializes Depth Solid with Uniform mode
|
||||
|
||||
通过 Polyline 创建 Depth Solid 时,系统 MUST 将新对象的 `O Image Depth Cutout` 修改器 Mode 初始化为 Uniform。手势 MUST 使用本次提交快照。
|
||||
|
||||
#### Scenario: Polyline depth result completes asynchronously
|
||||
- **WHEN** 用户提交 Polyline 并选择 Depth Solid,在生成结束前将 Cutout Gesture 改为 Lasso
|
||||
- **THEN** 本次结果的 Mode 仍为 Uniform,用户可在创建后手动调整
|
||||
|
||||
#### Scenario: Lasso or another shape is used
|
||||
- **WHEN** 用户通过 Lasso 创建 Depth Solid,或通过 Polyline 创建 Flat、Solid、Depth Balloon
|
||||
- **THEN** 对象沿用该 Shape 的现有 Mode 初始化规则
|
||||
|
||||
### Requirement: Uniform thickness defaults to half a unit
|
||||
|
||||
`O Image Depth Cutout` 的 Uniform Thickness socket MUST 默认 0.5,保存资产中对应显示为 Thickness 的 socket 和新建修改器 MUST 使用该值。
|
||||
|
||||
#### Scenario: Node asset is built and loaded
|
||||
- **WHEN** 完成节点资产构建并从资产加载节点组创建 Polyline Depth Solid
|
||||
- **THEN** Mode 为 Uniform,Uniform 厚度值为 0.5,通用 Thickness 初始化不会覆盖该值
|
||||
@@ -0,0 +1,25 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Mask exposes Set Extend and Subtract
|
||||
|
||||
Mask MUST 按顺序提供 `SET` / Set、`EXTEND` / Extend、`SUBTRACT` / Subtract,枚举数值分别为 0、1、2;界面和状态文本 MUST 使用 Extend。
|
||||
|
||||
#### Scenario: User extends Alpha
|
||||
- **WHEN** 用户以 Extend 提交值为 mask 的选区到现有 alpha
|
||||
- **THEN** 结果 Alpha 为 `max(alpha, mask)`,RGB 保持不变
|
||||
|
||||
#### Scenario: User uses Set or Subtract
|
||||
- **WHEN** 用户使用 Set 或 Subtract
|
||||
- **THEN** 结果 Alpha 分别为 `alpha * mask` 或 `alpha * (1-mask)`,RGB 保持不变
|
||||
|
||||
### Requirement: Mask defaults to Set
|
||||
|
||||
Scene 的 Mask Mode 和 Mask Operator 的模式属性 MUST 默认 Set,并通过同一属性定义保持一致。
|
||||
|
||||
#### Scenario: New scene and operator defaults
|
||||
- **WHEN** 创建新 Scene 或未指定模式的 Mask Operator
|
||||
- **THEN** 模式默认值为 `SET`
|
||||
|
||||
#### Scenario: User chooses another mode
|
||||
- **WHEN** 用户主动设置 Extend 或 Subtract 后启动 Mask
|
||||
- **THEN** 本次操作使用用户保存的模式
|
||||
@@ -0,0 +1,21 @@
|
||||
## 1. Cutout 手势
|
||||
|
||||
- [x] 1.1 在 properties.py 添加默认 Lasso 的 Cutout Gesture 属性并接入 Scene、Operator 和工具设置,检查注册与逆序注销测试。
|
||||
- [x] 1.2 将 Cutout 接入共用 Polyline 启动、预览与完成逻辑,更新状态文本;覆盖逐点、闭合、双击、Enter、Backspace、取消、Lasso 回归和空白处源对象保持行为。
|
||||
|
||||
## 2. Uniform 初始化与资产
|
||||
|
||||
- [x] 2.1 将本次 Gesture 快照从选区提交经 Shape 菜单传入转换 Operator 和对象创建链路;覆盖异步期间切换设置的行为。
|
||||
- [x] 2.2 为 Polyline 创建的 Depth Solid 设置 Mode 为 Uniform,验证 Lasso 与其他 Shape 的初始化行为。
|
||||
- [x] 2.3 将节点内部 Uniform Thickness 默认值改为 0.5,检查同名 Thickness socket 的赋值目标并更新资产修订号;补充资产默认值与实际修改器值测试。
|
||||
- [x] 2.4 运行 `uv run node-group build`,更新并验证 O_AnyImage.blend,确认源码与保存资产的 Capture Attribute 数量为零。
|
||||
|
||||
## 3. Mask 模式
|
||||
|
||||
- [x] 3.1 将 Mask 的 ADD / Add 统一改为 EXTEND / Extend,保持枚举数值与 Alpha 运算语义,将共用模式属性默认值改为 SET。
|
||||
- [x] 3.2 更新模式、状态文本、设置持久化和注册测试,验证三种模式的 Alpha 结果及 RGB 保持行为。
|
||||
|
||||
## 4. 验证
|
||||
|
||||
- [x] 4.1 运行相关测试及 `uv run pytest`,确认正常退出。
|
||||
- [x] 4.2 检查运行时代码和测试中的旧 Mask 模式引用及最终差异,确认源码、测试和节点资产一致。
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-09
|
||||
@@ -0,0 +1,44 @@
|
||||
## Context
|
||||
|
||||
`build_material_layer_group()` 共用构建 O Image Layer 和 O Image Depth Layer。普通层直接传出输入 Alpha,深度层使用前景阈值。`create_image_material()` 已集中创建材质,并通过 `configured_material_view_adaptation()` 配置颜色图片。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
目标是将参考图的 Alpha 调整内置到 O Image Layer,支持连续强度调节,并在创建时读取现有偏好。变更范围为普通图像材质层;深度层继续使用其前景阈值协议。
|
||||
|
||||
## Decisions
|
||||
|
||||
### 曲线与接口
|
||||
|
||||
O Image Layer 的默认折叠面板 `Options` 依次包含 `Alpha Fix`、`Normal Scale`、`Object Space`。Alpha Fix 类型为 NodeSocketFloat,subtype 为 FACTOR,范围 0–1,默认 0。直接连接 ShaderNodeFloatCurve 的 Factor,复用节点自身的混合语义:`output = (1 - factor) * alpha + factor * curve(alpha)`。
|
||||
|
||||
曲线使用四个点:起点 `(0, 0)`、低端控制点 `(0.8, 0)`、上方控制点 `(0.925, 0.925)` 和终点 `(1, 1)`。上方控制点为参考图估计值。采用自动钳制手柄、水平延伸及 0–1 的裁剪与采样区间。低端控制点之后快速上升,完整调整时输入 0.85 输出约 0.1593、输入 0.9 输出约 0.774;低端附近存在 Blender 曲线查表插值误差,测试容差为 0.0003。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[组输入 Alpha] --> C[Float Curve Value]
|
||||
B[组输入 Alpha Fix] --> D[Float Curve Factor]
|
||||
C --> E[曲线调整并按 Factor 混合]
|
||||
D --> E
|
||||
E --> F[组输出 Alpha]
|
||||
F --> G[Principled Alpha 或 Shadeless 混合系数]
|
||||
```
|
||||
|
||||
Alpha 曲线放在独立且就近的支线,保持曲线可见、默认节点标题和未使用输出隐藏;Color、Roughness 与 Normal 保持各自连接。
|
||||
|
||||
### 创建时应用偏好
|
||||
|
||||
`create_image_material()` 在普通层节点绑定后读取现有公共偏好函数,将节点实例的 `Alpha Fix` 设为 1 或 0。判断依据就是偏好值,因此开启偏好但颜色空间回退到 sRGB 时仍设为 1。手动拖入节点组时采用资产默认值 0。
|
||||
|
||||
用户随后可独立调整每个材质实例;偏好切换只影响后续创建。普通着色与 Shadeless 共用组输出 Alpha。Color 图片的颜色空间和 Alpha 模式继续由现有配置入口处理。
|
||||
|
||||
### 验证与交付
|
||||
|
||||
修改构建脚本、资产验证及材质测试;通过测试期间构建的节点组检查接口、连接与曲线采样。节点资产文档按节点组专项规范同步描述接口。用户已授权更新节点资产,O_AnyImage.blend 中的 O Image Layer 与运行时代码配套交付;在 Blender 节点编辑器确认实际尺寸、节点间距和连接,并运行全量测试及资产验证。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- 截图未展示所有精确坐标与手柄设置 → 用上述初值实现,检查曲线采样,并对照实际节点编辑器及透明边缘效果验收。
|
||||
- 曲线会削弱中低 Alpha → 默认手动实例强度为 0,创建时按用户偏好启用,保留每个实例的强度控制。
|
||||
- 工作区已有材质与节点资产修改 → 在当前实现上追加变更,保留已有修改并检查最终差异。
|
||||
- 旧资产缺少输入 → 验证资产接口与运行时代码一致,正式发布配套资产。
|
||||
@@ -0,0 +1,27 @@
|
||||
## Why
|
||||
|
||||
使用 inverse 类颜色空间时,用户观察到 Alpha 无法正确遮盖图片边缘,手动添加 Float Curve 后可改善。将这项调整放入 O Image Layer,让材质创建时自动应用,并允许用户调节强度。
|
||||
|
||||
## What Changes
|
||||
|
||||
- O Image Layer 内新增 Alpha Float Curve,依据参考图设置曲线。
|
||||
- 默认折叠的 `Options` 面板依次包含 `Alpha Fix`、`Normal Scale`、`Object Space`;Alpha Fix 连接 Float Curve 的 Factor,范围 0–1,默认 0。
|
||||
- 创建使用 O Image Layer 的材质时,读取 `Adapt to Scene View Transform`:开启设为 1,关闭设为 0。
|
||||
- 验证曲线、接口、材质创建和着色输出行为。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `image-layer-alpha-adjustment`: 材质层 Alpha 曲线调整、强度输入与创建时偏好联动。
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
无。
|
||||
|
||||
## Impact
|
||||
|
||||
- `tools/node_assets/material_layers.py`:节点组接口、曲线与布局。
|
||||
- `src/anyimage/common/material.py`:创建节点实例时设置调整强度。
|
||||
- `tools/node_assets/asset_validation.py` 及材质相关测试:接口、连接、曲线求值和偏好联动验证。
|
||||
- 发布节点资产 `src/anyimage/assets/O_AnyImage.blend` 需要包含新接口;本轮仅生成提案。
|
||||
@@ -0,0 +1,56 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Image layer exposes alpha adjustment strength
|
||||
|
||||
O Image Layer SHALL 在默认折叠的 `Options` 面板中依次提供 `Alpha Fix`、`Normal Scale`、`Object Space`。Alpha Fix SHALL 为浮点输入,范围 0–1,subtype 为 FACTOR,默认 0,并以该值控制内部 Alpha Float Curve 的 Factor。
|
||||
|
||||
#### Scenario: Options are initially collapsed
|
||||
- **WHEN** 创建 O Image Layer 节点实例
|
||||
- **THEN** Options 面板 SHALL 默认折叠,包含 Alpha Fix、Normal Scale、Object Space
|
||||
- **AND** Color、Alpha、Normal、Bump Scale SHALL 位于面板外
|
||||
|
||||
#### Scenario: Adjustment is disabled
|
||||
- **WHEN** Alpha Fix 为 0
|
||||
- **THEN** 组输出 Alpha SHALL 等于输入 Alpha
|
||||
|
||||
#### Scenario: Adjustment is fully applied
|
||||
- **WHEN** Alpha Fix 为 1
|
||||
- **THEN** 组输出 Alpha SHALL 等于内部 Float Curve 对输入 Alpha 的求值
|
||||
|
||||
#### Scenario: Adjustment is partially applied
|
||||
- **WHEN** Alpha Fix 为 0.5
|
||||
- **THEN** 输出 SHALL 等于原始 Alpha 与完整曲线结果的平均值
|
||||
|
||||
### Requirement: Alpha curve follows the reference profile
|
||||
|
||||
Alpha 曲线 SHALL 使用四个点:起点 `(0, 0)`、低端控制点 `(0.8, 0)`、靠近右上方的平滑控制点和终点 `(1, 1)`,形成参考图中的快速上升轮廓。对输入范围 0–1,完整调整后的输出 SHALL 保持在 0–1,保持单调不减,并保持完全透明与完全不透明端值。
|
||||
|
||||
#### Scenario: Low alpha is suppressed
|
||||
- **WHEN** 输入 Alpha 在 0–0.8 且调整强度为 1
|
||||
- **THEN** 输出 Alpha SHALL 为 0,允许浮点求值误差
|
||||
|
||||
#### Scenario: Opaque alpha is preserved
|
||||
- **WHEN** 输入 Alpha 为 1
|
||||
- **THEN** 任意调整强度下输出 Alpha SHALL 为 1
|
||||
|
||||
### Requirement: Material creation initializes adjustment from preferences
|
||||
|
||||
创建使用 O Image Layer 的材质时,系统 SHALL 通过现有偏好读取入口,将 Alpha Fix 初始化为 `Adapt to Scene View Transform` 开启时的 1、关闭时的 0。
|
||||
|
||||
#### Scenario: Adaptation is enabled
|
||||
- **WHEN** 开启适配后创建普通着色或 Shadeless 材质
|
||||
- **THEN** O Image Layer 实例的 Alpha Fix SHALL 为 1
|
||||
- **AND** 对应着色器 SHALL 使用该组调整后的 Alpha 输出
|
||||
|
||||
#### Scenario: Adaptation is disabled
|
||||
- **WHEN** 关闭适配后创建材质
|
||||
- **THEN** O Image Layer 实例的 Alpha Fix SHALL 为 0
|
||||
|
||||
#### Scenario: Color space falls back
|
||||
- **WHEN** 适配开启且颜色空间解析回退到 sRGB
|
||||
- **THEN** 新建 O Image Layer 实例的 Alpha Fix SHALL 仍为 1
|
||||
|
||||
#### Scenario: Existing material is manually adjusted
|
||||
- **WHEN** 用户修改现有材质的 Alpha Fix 后切换偏好
|
||||
- **THEN** 已有实例的调整值 SHALL 保留
|
||||
- **AND** 后续新建材质 SHALL 使用当前偏好初始化
|
||||
@@ -0,0 +1,18 @@
|
||||
## 1. Alpha 曲线与接口
|
||||
|
||||
- [x] 1.1 在 `material_layers.py` 的 O Image Layer 分支加入 Alpha Fix 输入及 Float Curve,按设计设置坐标、手柄、裁剪和连接。
|
||||
- [x] 1.2 调整节点布局及未使用输出隐藏,在实际 Blender 节点编辑器检查总览与曲线局部。
|
||||
- [x] 1.3 更新资产验证及对应测试,检查 socket 顺序、默认值、连接和曲线采样;覆盖强度 0、0.5、1、低 Alpha、端点、单调性与范围。
|
||||
|
||||
## 2. 材质创建联动
|
||||
|
||||
- [x] 2.1 在 `create_image_material()` 中复用偏好读取入口,为 O Image Layer 实例设置 Alpha Fix。
|
||||
- [x] 2.2 补充材质行为测试,覆盖偏好开关、sRGB 回退、普通着色、Shadeless 及现有材质手动值保留,验证 O Image Depth Layer 的既有 Alpha 行为。
|
||||
|
||||
## 3. 验收
|
||||
|
||||
- [x] 3.1 对照参考图验证曲线形状,使用透明边缘图片比较强度 0、0.5、1 的效果,记录估计控制点的最终取值。
|
||||
- [x] 3.2 按节点组专项规范同步 `docs/internals/node-assets.md` 的新输入与曲线说明。
|
||||
- [x] 3.3 按用户授权更新 O Image Layer 节点资产,运行全量测试并确认正常退出,检查最终差异和接口引用;验证 Blender 节点资产,不构建文档或扩展发布包。
|
||||
|
||||
验证结果:766 passed、2 xfailed、50 subtests passed,退出码 0;Blender 4.5.10 资产验证与 OpenSpec 严格校验通过。
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-03
|
||||
@@ -0,0 +1,84 @@
|
||||
## Context
|
||||
|
||||
Mask 的 Lasso、Brush 与 Polyline 共用 `ImageGesture`。第一版 Brush 实时预览在 Modal 期间创建同尺寸 Generated Image,并以最多 30 FPS 更新 Pixel Buffer;4K 实测表明 `image.update()` 仍可能上传完整纹理,频繁更新会造成明显卡顿,红色填充 Overlay 也会随着 Flush 时机出现或消失。
|
||||
|
||||
因此 Brush 回到与 Lasso、Polyline 一致的事务边界:手势期间只保留简化后的屏幕轨迹并绘制几何 Overlay,不读取、创建或修改 Image;鼠标释放后才投影和栅格化完整笔画,创建一个最终 Image 并替换 active Image Empty。
|
||||
|
||||
Keep Original 与 Refine Selection 已无运行时代码,但测试中仍有旧字段缺席断言和一个多余参数。`SelectionPath.invert` 也已无生产调用方,却继续扩大 JSON 协议并让栅格器保留完整画布反选分支。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- Brush 拖动期间显示与 Lasso、Polyline 相同的虚线和 Mode 填充,不更新 active Image Empty。
|
||||
- Overlay 与释放提交复用同一组 Brush Primitive 和 union 语义,保证预览范围就是最终 Selection。
|
||||
- 增加与原 Crop Polyline 手势一致、但使用当前 Mask 术语和 Alpha 主链的 Polyline。
|
||||
- 将 Mask 默认 Mode 改为 Subtract。
|
||||
- 删除 Keep Original、Refine Selection、Selection Invert 的剩余状态和历史测试噪音。
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- Lasso 与 Polyline 在路径闭合前只显示几何 Overlay,不实时修改 Image Alpha。
|
||||
- 不编辑共享源 Image 的 Pixel Buffer,不改变 Frame、Rectify、Cutout Shape 或 Server 行为。
|
||||
- 不在 Modal 拖动阶段创建预览 Image、读取源 RGBA 或执行图片级计算。
|
||||
- 不增加 Pillow、OpenCV 或 GPU Compute 依赖。
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. Brush 拖动阶段只维护屏幕轨迹
|
||||
|
||||
Brush 确认开始编辑后只保存屏幕坐标。鼠标移动使用与 Lasso 相同的最小距离和近共线简化,避免静止事件或直线移动积累冗余点。Modal 期间不调用 `image_rgba()`、`bpy.data.images.new()`、Pixel Buffer 写入、`image.update()` 或 `pack()`,active Image Empty 始终引用原 Image。
|
||||
|
||||
RMB、Esc、工具切换和异常只移除 Draw Handler 并结束 Operator,不需要临时 Image 恢复或数据块清理。这样拖动性能只与 Viewport Overlay 和简化后的屏幕轨迹有关,不随图片分辨率变化。
|
||||
|
||||
### 2. Overlay 与提交共享 Brush Primitive Union
|
||||
|
||||
简化后的中心轨迹生成一组稳定 Primitive:每个采样点对应一个圆,每对相邻点对应一个等宽连接条。Viewport 在 screen space 对这些 Primitive 做 union;鼠标释放后把同一组 Primitive 投影到图片,逐个栅格化并以逐像素 `max` 合入单个覆盖画布。转向、折返与交错都由圆和连接条的并集定义,不再从中心线偏移出一个可能改变 winding 的自交多边形,因此不会产生 miter 尖角或未填充三角。
|
||||
|
||||
Selection 完成后读取一次源 RGBA,并根据操作开始 Alpha `A0` 与 Mask `M` 合成:
|
||||
|
||||
```text
|
||||
Set A' = A0 × M
|
||||
Add A' = max(A0, M)
|
||||
Subtract A' = A0 × (1 - M)
|
||||
```
|
||||
|
||||
`apply_alpha_mask()` 使用同一纯 Alpha 合成函数;释放阶段只保留一个累计覆盖画布,不保存逐 Primitive Mask 列表。
|
||||
|
||||
不选择在每次刷新时重新栅格化完整轨迹,因为成本随轨迹长度持续增加;也不保存每个 Primitive 的独立 Mask,因为内存会随鼠标事件数增长。
|
||||
|
||||
### 3. Brush 使用与 Lasso、Polyline 相同的 Overlay
|
||||
|
||||
Viewport Overlay 在每个有效移动事件后,把全部 Brush Primitive 在局部屏幕 bounds 内以 2 px scanline bands 做区间 union,再由合并 bands 生成填充三角形,并从相邻 bands 的边界差异连接出闭合外轮廓。内部重叠边和 band 接缝不会进入虚线,急转与交错区域保持连续填充。该过程只处理 Viewport 坐标,不读取或栅格化源 Image。
|
||||
|
||||
Subtract 使用红色半透明填充,Set 与 Add 使用灰色半透明填充;虚线保持相同样式。Overlay 不随任何 Image 状态变化,并让用户在释放前看清即将作用的准确区域。
|
||||
|
||||
### 4. Polyline 是 Mask 的第三种 Gesture
|
||||
|
||||
`mask_gesture_property()` 增加 `POLYLINE`,单一 Mask WorkspaceTool 和 `EditImageAlpha` Operator 保持不变。`ImageGesture` 恢复无 Crop 术语的 Polyline 状态机:首次点击建立起点,后续左键提交顶点,鼠标移动预览下一条边;至少三个点后,点击起点 8 px 范围或按 Enter 完成,Backspace 删除最后一点,RMB/Esc 取消。
|
||||
|
||||
完成后把已提交顶点转换为一个 `SelectionPath`,与 Lasso 一样栅格化并进入当前 Mode 的 Alpha 合成。Radius 只属于 Brush;Polyline Tool Settings 只显示 Gesture 与 Mode。
|
||||
|
||||
### 5. SelectionPath 只保存 Points
|
||||
|
||||
删除 `SelectionPath.invert`、JSON 中的 `invert`、完整画布反选 bounds 和 `1 - mask` 分支。Cutout 的 Lasso 与双击整图都已经提供正向 Path,不需要迁移运行时状态。JSON 继续用于 Cutout Operator 间传值,但只包含 `points`。
|
||||
|
||||
删除重复验证 Keep Original、Refine Selection 和旧 Crop 字段缺席的历史断言;仍通过当前 Scene Property、Operator annotation、Job request 和打包文件的正向精确集合测试固定公开边界。测试夹具中的多余 `keep_original=False` 同步删除。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [高分辨率图片会让释放后的提交耗时可感知] → 将图片级投影、栅格化和结果创建集中到一次释放事件,避免拖动期间反复上传纹理。
|
||||
- [长而复杂的笔画会保留较多屏幕点] → 使用与 Lasso 相同的距离与近共线简化,只保留表达轨迹形状所需的点。
|
||||
- [Set 模式会让笔画外 Alpha 归零] → 保持既有 Set 公式;默认 Subtract 仍覆盖最常用的擦除行为。
|
||||
- [复杂笔画包含大量重叠 Primitive] → screen-space scanline 先合并每个 band 的区间,再生成一次填充和外轮廓;图片空间只在释放时使用单个累计覆盖画布。
|
||||
- [Polyline PRESS 事件与对象点选冲突] → 沿用 `resolve_image_edit_click()`,首次点击另一对象只切换 active,再次点击当前 Image 才开始手势。
|
||||
|
||||
## Migration Plan
|
||||
|
||||
先完成已实现的 `replace-crop-with-mask-and-rectify` 作为基线。实现时先精简 SelectionPath,再加入 Polyline 状态机,随后实现 Brush 虚线 Overlay 与释放栅格化,最后删除失去调用方的 Helper 和历史测试。Scene 中未显式保存 Mode 的文件自动使用新的 Subtract 默认值;已经保存的 Mode 值保持不变。
|
||||
|
||||
回滚时可恢复本 change 前的 Mask 实现;它不迁移持久文件、Server 数据或 Blender Object 结构。
|
||||
|
||||
## Open Questions
|
||||
|
||||
无。4K 实测决定拖动阶段不再更新 Image;后续实测进一步要求 Overlay 与提交共享圆角 footprint,并恢复 Mode 填充。
|
||||
@@ -0,0 +1,26 @@
|
||||
## Why
|
||||
|
||||
Mask Brush 的实时 Image 预览在 4K 图片上会频繁上传大纹理,造成明显卡顿;红色填充 Overlay 还会随 Image 刷新时机出现或消失。Brush 更适合像 Lasso、Polyline 一样在手势期间只显示稳定的几何反馈,释放后再提交 Alpha 修改。与此同时,已删除的 Keep Original、Refine Selection 和 Invert 仍有测试或通用数据分支残留,Brush 还会为整条轨迹积累大量临时 Mask,适合在扩展 Gesture 时一并收紧。
|
||||
|
||||
## What Changes
|
||||
|
||||
- **BREAKING**:Mask Mode 默认值从 Set 改为 Subtract,使 Mask 默认表现为擦除 Alpha。
|
||||
- Brush 按住拖动期间不读取或修改 Image Pixel Buffer;Overlay 与释放提交复用同一组圆形印记、连接条和 union 规则,急转与交错区域融合且虚线只保留融合后的外轮廓。显示沿用 Lasso、Polyline 的 Mode 填充:Subtract 为红色,Set/Add 为灰色。
|
||||
- Mask Gesture 增加 Polyline,沿用逐点单击、移动预览、点击起点或 Enter 完成、Backspace 撤回一点的交互,并复用 Set、Add、Subtract Alpha 合成。
|
||||
- 删除无生产调用方的 `SelectionPath.invert`、全画布反选栅格分支及旧字段序列化,清理只验证 Keep Original、Refine Selection 缺席的历史测试和测试参数。
|
||||
- 删除由中心线偏移生成单一自交外框的方案,避免急转时 winding 产生三角孔洞;改为直接合并 Brush 圆和连接条。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `mask-editing`: 定义 Mask 的 Lasso、Brush、Polyline Gesture,Subtract 默认模式,Brush 虚线预览、释放提交与取消语义,以及无旧编辑状态的精简 Selection 数据边界。
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
## Impact
|
||||
|
||||
- 影响 `properties.py` 的 Mask 默认设置,以及 `operators/image_edit_tool/mask.py` 的释放提交与 Brush Selection 主链。
|
||||
- 影响 `common/viewport.py` 的 Gesture 状态机、Polyline 与 Brush Overlay,以及 `common/selection.py` 的 SelectionPath 表示。
|
||||
- 影响 Mask、Selection、Cutout、注册、交互与打包测试,并同步 Image Edit、Cutout 和 Common 内部文档。
|
||||
- 不增加第三方依赖,不改变 Frame、Rectify、Cutout Shape、Remove Background 或 Server Job 协议。
|
||||
@@ -0,0 +1,94 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Mask exposes three gestures with Subtract by default
|
||||
系统 SHALL 在单一 Mask Tool 中提供 Lasso、Brush 和 Polyline Gesture,并 SHALL 提供 Set、Add、Subtract Mode。新 Scene 的 Mask Mode MUST 默认为 Subtract;已有 Scene 中已保存的 Mode MUST 保持其值。Radius MUST 只在 Brush Gesture 下显示和生效。
|
||||
|
||||
#### Scenario: New scene uses Subtract
|
||||
- **WHEN** 新 Scene 尚未保存 Mask Mode
|
||||
- **THEN** Mask Tool Settings 与新启动的 Mask Operator 使用 Subtract
|
||||
|
||||
#### Scenario: Gesture settings remain unified
|
||||
- **WHEN** 用户在 Lasso、Brush 与 Polyline 之间切换
|
||||
- **THEN** 系统仍使用同一个 Mask Tool、Mode 设置和 Alpha 合成入口,并只在 Brush 下显示 Radius
|
||||
|
||||
### Requirement: Brush defers Image work until release
|
||||
Brush MUST 在手势完成前保持 active Image Empty 的 Image 不变。拖动期间 MUST NOT 读取源 RGBA、创建临时 Image、栅格化图片级 Mask、写入 Pixel Buffer、调用 `image.update()` 或 Pack;鼠标释放后 SHALL 一次性计算并提交结果。
|
||||
|
||||
#### Scenario: Dragging leaves the Image untouched
|
||||
- **WHEN** 用户按住左键移动 Brush 且手势尚未完成
|
||||
- **THEN** active Image Empty 继续显示并引用操作开始时的 Image,只通过 Viewport Overlay 呈现待提交范围
|
||||
|
||||
#### Scenario: Shared source remains unchanged during the gesture
|
||||
- **WHEN** active Image Empty 与另一个对象共享源 Image 并开始 Brush
|
||||
- **THEN** 两个对象都继续引用且显示原始 Image,直到 active Image Empty 在释放后接收新结果
|
||||
|
||||
#### Scenario: Stationary hold performs no Image work
|
||||
- **WHEN** 用户按住 Brush 且鼠标没有产生有效位移
|
||||
- **THEN** 系统不执行 Image 更新或图片级计算
|
||||
|
||||
### Requirement: Brush completion and cancellation are transactional
|
||||
Brush 完成时 MUST 创建并 Pack 一个最终结果 Image,并 SHALL 通过公共替换入口让 active Image Empty 承载结果。取消、工具切换或错误 MUST 保持源 Image 不变且不产生完成的编辑;成功完成 MUST 保持对象 identity、matrix、display size、offset、共享源引用和单步 Undo 语义。
|
||||
|
||||
#### Scenario: Completing a Brush persists one result
|
||||
- **WHEN** 用户释放左键完成 Brush
|
||||
- **THEN** 系统计算完整覆盖、创建并 Pack 一个同尺寸结果,active Image Empty 的画框和对象身份保持不变
|
||||
|
||||
#### Scenario: Cancelling restores the source
|
||||
- **WHEN** 用户在 Brush 期间按 RMB 或 Esc
|
||||
- **THEN** active Image Empty 仍引用操作前 Image,且没有临时 Image 或完成的 Undo 编辑
|
||||
|
||||
#### Scenario: Tool switch cleans the preview
|
||||
- **WHEN** Brush 运行期间 active WorkspaceTool 发生变化
|
||||
- **THEN** 系统只结束 Overlay 与 Modal,Image 保持不变
|
||||
|
||||
### Requirement: Brush uses one continuous primitive union
|
||||
Brush MUST 从简化后的屏幕轨迹生成圆形采样点和等宽连接条。Overlay 与释放后的 Selection MUST 使用同一组 Primitive 和 union 语义;急转、折返、自交与重叠区域 MUST 保持连续,MUST NOT 出现超出 Radius 的尖角或未填充三角。图片空间 union MUST 以逐像素最大值累计到单个覆盖画布,不得保存逐 Primitive Mask 列表。
|
||||
|
||||
#### Scenario: Sparse events leave no gap
|
||||
- **WHEN** 两个连续 Brush 采样点之间的距离大于 Radius
|
||||
- **THEN** 两点之间的胶囊完整覆盖连线且不出现空隙
|
||||
|
||||
#### Scenario: Turning does not create a horn or hole
|
||||
- **WHEN** Brush 中心轨迹发生方向变化
|
||||
- **THEN** 圆形采样点与连接条融合,Overlay 与最终 Selection 都不出现 miter 尖角或未填充三角
|
||||
|
||||
#### Scenario: Preview and result share the footprint
|
||||
- **WHEN** 用户释放 Brush
|
||||
- **THEN** 系统投影并栅格化拖动时显示的同一组 Brush Primitive,并使用相同的 union 规则
|
||||
|
||||
### Requirement: Brush matches the Lasso and Polyline overlay
|
||||
Brush SHALL 在每个有效鼠标事件后绘制整次未提交笔画的 footprint。Overlay MUST 复用 Lasso、Polyline 的虚线边界、non-zero winding 三角化和半透明填充;自交区域 MUST 融合为一个填充区域,虚线 MUST 只绘制融合后的外轮廓而不得保留内部交错边。Subtract MUST 使用红色填充,Set 与 Add MUST 使用灰色填充。
|
||||
|
||||
#### Scenario: Overlay persists until release
|
||||
- **WHEN** 用户继续拖动 Brush 且尚未释放左键
|
||||
- **THEN** Viewport Overlay 以当前 Mode 的填充和虚线边界显示从起点到当前点的准确 footprint,Image 本身保持不变
|
||||
|
||||
#### Scenario: Crossing strokes merge visually
|
||||
- **WHEN** Brush footprint 与自身交错或重叠
|
||||
- **THEN** 交错部分显示为连续的一整块填充,虚线只围绕融合区域的外边界
|
||||
|
||||
### Requirement: Polyline edits Alpha through the Mask pipeline
|
||||
Polyline SHALL 以逐点单击构建闭合 Selection:鼠标移动 MUST 预览下一条边;至少三个点后点击起点 8 px 范围或按 Enter MUST 完成;Backspace MUST 删除最后一个已提交点;RMB 或 Esc MUST 取消。完成的 Polyline MUST 栅格化为一个 SelectionMask,并 MUST 以当前 Set、Add 或 Subtract Mode 修改当前 Alpha,保持 RGB、图片尺寸和 Image Empty 画框不变。
|
||||
|
||||
#### Scenario: Clicking the start closes Polyline
|
||||
- **WHEN** 用户已提交至少三个点并在起点 8 px 范围内单击
|
||||
- **THEN** 系统闭合路径并应用当前 Mask Mode
|
||||
|
||||
#### Scenario: Enter confirms Polyline
|
||||
- **WHEN** 用户已提交至少三个点并按 Enter 或 Numpad Enter
|
||||
- **THEN** 系统闭合路径并应用当前 Mask Mode
|
||||
|
||||
#### Scenario: Backspace removes one point
|
||||
- **WHEN** Polyline 已有两个以上已提交点且用户按 Backspace
|
||||
- **THEN** 系统删除最后一点并继续预览未完成路径
|
||||
|
||||
### Requirement: Image selections carry only positive path geometry
|
||||
`SelectionPath` SHALL 只保存并序列化 Points。Mask、Cutout、Scene Property、Operator 参数和 Server Job 请求 MUST NOT 声明或传递 Invert、Keep Original、Refine Selection 或 Refine Model 状态;Selection 栅格化 SHALL 只建立带抗锯齿 Padding 的局部 Path Bounds。
|
||||
|
||||
#### Scenario: Cutout serializes a path
|
||||
- **WHEN** Cutout 把 Lasso SelectionPath 传给 Shape Operator
|
||||
- **THEN** JSON 只包含 `points`,恢复后得到相同的正向路径
|
||||
|
||||
#### Scenario: Public state contains current concepts
|
||||
- **WHEN** 系统枚举 Scene Property、Mask/Cutout Operator annotation 和 Cutout Job 请求
|
||||
- **THEN** 只出现当前 Gesture、Mode、Radius、Shape、Depth 与 Normal 业务字段
|
||||
@@ -0,0 +1,35 @@
|
||||
## 1. Mask 设置与 Selection 精简
|
||||
|
||||
- [x] 1.1 将 Mask Mode 默认值改为 Subtract,在 Gesture 中增加 Polyline,并更新 Scene Property、Operator 与 Tool Settings 测试
|
||||
- [x] 1.2 删除 `SelectionPath.invert`、JSON `invert` 字段、完整画布反选 bounds 和补集栅格化分支,使 Cutout 序列化只传递 points
|
||||
- [x] 1.3 清理测试中遗留的 Keep Original、Refine Selection 参数及重复历史缺席断言,以当前 Property、Operator、Job 和打包内容的精确集合固定公开边界
|
||||
- [x] 1.4 更新 Selection 与 Cutout 测试,验证局部抗锯齿 bounds、JSON 往返、整图 Cutout 和普通 Lasso 在精简后保持正确
|
||||
|
||||
## 2. Polyline Gesture
|
||||
|
||||
- [x] 2.1 在无 Crop 术语的 `ImageGesture` 中实现 Polyline 点提交、移动预览、8 px 起点闭合、Enter 完成、Backspace 回退和取消状态机
|
||||
- [x] 2.2 为 Polyline 绘制闭合预览、虚线轮廓和起点指示,并更新 Mask 状态文字与 Radius 显示条件
|
||||
- [x] 2.3 将完成的 Polyline 接入 Lasso 共用的 SelectionMask 与 Alpha 合成入口,验证 Set、Add、Subtract、画布保持和对象点选语义
|
||||
- [x] 2.4 补充 Polyline Modal、Overlay、闭合方式、无效路径、取消和工具切换测试
|
||||
|
||||
## 3. Brush Footprint 与 Alpha
|
||||
|
||||
- [x] 3.1 将 Alpha 公式整理为完整 Selection 共用的纯函数,固定 Set、Add、Subtract 结果
|
||||
- [x] 3.2 从简化后的屏幕轨迹生成圆形采样点和等宽连接条,并在 screen space 与图片空间分别执行同语义 union
|
||||
- [x] 3.3 删除逐 Primitive Mask 列表和失去调用方的 `merge_selection_masks()`,图片空间只保留一个累计覆盖画布
|
||||
- [x] 3.4 补充单击、稀疏移动、急转折返、自交、倾斜/缩放投影与图片边界测试
|
||||
|
||||
## 4. Brush 虚线预览与延迟提交
|
||||
|
||||
- [x] 4.1 删除临时 Image、dirty RGBA、30 FPS Flush 与实时 Pixel Buffer 更新,使 Brush 拖动阶段不读取或修改 Image
|
||||
- [x] 4.2 将 Brush Primitive 在 screen-space scanline bands 中融合,消除内部交错边、急转尖角和未填充三角,并复用 Lasso、Polyline 的填充配色
|
||||
- [x] 4.3 让 Overlay 与鼠标释放后的 Selection 栅格化复用同一组 Primitive 与 union 语义,再通过公共 Alpha 与 Image 替换入口一次性提交
|
||||
- [x] 4.4 取消、异常与工具切换只结束 Overlay,不创建临时 Image 或恢复分支,并保持对象 identity、transform、画框和单步 Undo
|
||||
- [x] 4.5 更新 Brush 急转折返、Primitive Union、融合外轮廓、Overlay 配色、释放提交和真实 Blender Image 生命周期测试
|
||||
|
||||
## 5. 文档与验证
|
||||
|
||||
- [x] 5.1 更新 README 与 Image Edit、Common、Cutout 内部文档,说明三种 Gesture、Subtract 默认值、Brush Mode 填充、Primitive Union、释放提交和只含 points 的 SelectionPath
|
||||
- [x] 5.2 搜索并清除运行时与文档中的 Keep Original、Refine Selection、Selection Invert、旧 Brush 全轨迹合并 Helper 和历史 Crop Polyline 名称
|
||||
- [x] 5.3 运行 Selection、Blender Addon、Cutout、Image Edit 与 Packaging 定向测试并修正失败
|
||||
- [x] 5.4 运行 `uv run pytest` 和 OpenSpec strict validation,不构建文档、节点资产或发布产物
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-10
|
||||
@@ -0,0 +1,63 @@
|
||||
## Context
|
||||
|
||||
项目现有几何推理使用 ONNX Runtime,Windows 使用 DirectML,其他平台使用默认 Runtime。MoGe 3 的稀疏细化已在项目外改写为标准 ONNX 索引、聚合和矩阵运算,完整三次细化已实际运行。
|
||||
|
||||
实现使用上游提交 `74fbce054ebed49800de42d0ad0e83495065719a`,骨干与细化分别导出为 ONNX 图。骨干支持动态输入和 token 数,细化支持动态点数。在 RTX 5070 Ti 上,1200–3600 tokens 的完整动态推理约需 4.86–17.11 秒,平均深度相对误差约 0.002%,二值 Mask 完全一致。参考比较关闭 FlexGEMM 独立的 TF32 开关。Windows DirectML 与 Linux CPU 均已通过真实 Job 验证,详细结果见 [验收记录](validation.md)。
|
||||
|
||||
实验脚本与日志位于项目外 `C:/Users/icrdr/AppData/Local/Temp/anyimage-moge3-trial/`。正式转换位于 `tools/models/moge3_export.py`,模型准备和运行使用项目已有的资产机制。
|
||||
|
||||
## Goals
|
||||
|
||||
以最小改动将完整 MoGe 3 接入 MoGe 2 的现有工程流程,沿用输入准备、参数、下载、会话管理和几何产物契约。
|
||||
|
||||
## Decisions
|
||||
|
||||
### 动态尺寸与分辨率等级作为接入门槛
|
||||
|
||||
正式模型接收现有输入准备流程产生的图片尺寸及宽高比,沿用现有 resolution_level 到 token 数的控制方式,输出尺寸对应分析图片。256×192 / 1200 tokens 是原型测试配置,正式推理需通过动态形状及可变 token 数验证,同一组模型资产在任务间接受不同配置。
|
||||
|
||||
沿用已验证的两个图边界:骨干输出初始坐标、编码特征、法线、Mask 和尺度;细化图输出 log-depth 增量。MoGe 3 模块内部使用 NumPy 生成体素邻域并执行三轮调度,学习网络计算全部由 ONNX Runtime 执行,几何恢复复用现有后处理能力。逐层对比用于验证动态化后的数值一致性。
|
||||
|
||||
正式 Runtime 使用标准 ONNX 图和现有执行提供程序,完整执行三次细化。原型中主要网络计算由 DirectML 执行,部分索引与计数节点由 ONNX Runtime CPU 执行;正式验收继续记录实际执行路径。
|
||||
|
||||
### 最小工程改动
|
||||
|
||||
沿用现有图像准备、最大分析尺寸、分辨率等级、设备选择、Job 参数和产物。新模型特有的图集合、索引生成及迭代封装在 `models/onnx_moge3.py`;已有推理调用点只增加必要的模型分派。
|
||||
|
||||
复用现有模型下载与会话生命周期,通用编排名称仅在职责确实扩展时局部调整并一次更新真实调用者。Operator、Blender 响应和节点资产继续消费现有契约。正式运行依赖保持现有 ONNX 和后处理体系,离线转换依赖独立管理。
|
||||
|
||||
### 精度和性能验证
|
||||
|
||||
比较同权重、相同输入与分辨率等级的原始三次细化结果和 ONNX 结果。先用 FP32 验证语义,再评估 FP16。实验需在导出前声明验收容差,记录有效区域深度与点图误差、法线角误差、内参偏差、Mask 一致性及每次细化误差,不得根据结果事后放宽。
|
||||
|
||||
样本包含室内、细小物体、室外、不同宽高比及全景透视视图;覆盖低/高分辨率等级、自动 FOV 与固定 90 度 FOV。记录转换耗时、ONNX 文件大小、会话启动、热推理耗时及峰值内存/显存。Windows 验证 CPU 和 DirectML 的完整执行情况及实际算子回退;其他平台需分别验证,不沿用 PyTorch 的 CUDA 平台限制。
|
||||
|
||||
### 验证后的正式接入
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[已验证的双 ONNX 图原型] --> B[动态尺寸及分辨率等级验证]
|
||||
B --> C{精度 性能 提供程序通过}
|
||||
C -->|通过| D[生成并校验模型资产]
|
||||
D --> E[登记模型与下载]
|
||||
E --> F[统一 MoGe ONNX 推理入口]
|
||||
F --> G[深度平面 裁切 全景]
|
||||
C -->|未通过| H[记录阻碍与实验结果]
|
||||
```
|
||||
|
||||
转换工具放在 `tools/models`,依赖固定版本或上游提交;最终登记所有图文件及 external data 的大小和哈希,完整下载后标记就绪。镜像发布后才登记对应来源。生产推理只读取已准备的 ONNX 资产。
|
||||
|
||||
新增模型沿用现有几何模型枚举,保留已有模型键和默认值;仅调整识别新模型所必需的元数据与筛选。输出使用现有 CPU NumPy 数组和相机内参约定。全景传入 90 度 FOV,保持 Mask 与源有效性、Alpha 合成以及径向距离融合契约。
|
||||
|
||||
缓存按模型、设备和图集合识别,连续帧及视图复用会话,切换模型和关闭时释放资源。取消与失败沿用现有 Job 事务。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- 动态体素分箱可能放大浮点数值差异 → 对相同 FP32 配置按预定容差比较。
|
||||
- 高分辨率等级增加计算量与内存 → 已记录 CPU/DirectML 性能及峰值占用,继续沿用现有分辨率等级控制。
|
||||
- 多图执行会增加 CPU/GPU 数据搬运 → 测量完整流水线耗时并据此决定图边界。
|
||||
- 提供程序算子覆盖不同 → Windows DirectML 与 Linux CPU 已验收,macOS 仍需实机验证。
|
||||
|
||||
## Migration Plan
|
||||
|
||||
先基于已完成原型验证动态尺寸、分辨率等级、精度与性能,再完成转换工具和模型登记。沿用现有设置与默认模型,只增加新模型选择及必要分派。实施更新相关测试,检查依赖与打包内容一致性。
|
||||
@@ -0,0 +1,23 @@
|
||||
## Why
|
||||
|
||||
MoGe-3 ViT-L 的完整三次细化已在项目外通过 CPU ONNX Runtime 与 DirectML 原型验证。正式接入应沿用 MoGe 2 的工程流程,以最小改动增加模型选项,并完成动态输入和分辨率等级验证。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 将已跑通的双 ONNX 图原型扩展到现有输入尺寸处理、宽高比和分辨率等级,建立可复现导出与精度比较流程。
|
||||
- 验证通过后新增 `MOGE3_VITL` 模型选项、ONNX 资产下载和就绪检查,保留已有模型及默认值。
|
||||
- 深度平面、裁切与全景统一按模型分派,推理统一采用现有 ONNX Runtime,复用几何产物。
|
||||
- 两个 ONNX 图、体素邻域生成与三次细化封装在 MoGe 3 模型内部;复用现有 Operator、节点资产、Job 参数和用户操作流程。
|
||||
- 以完整细化、数值一致性、执行提供程序兼容性与可用性能作为正式接入门槛。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `moge3-model`: 完整 MoGe 3 的 ONNX 推理、模型选择、资产准备与几何工作流集成。
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
## Impact
|
||||
|
||||
改动集中在离线转换工具、模型登记、MoGe 3 ONNX 模块和必要的推理分派;模型枚举、下载与会话管理沿用既有机制。PyTorch、MoGe、FlexGEMM 与 Triton 仅用于隔离的离线转换及参考验证环境;正式 Server 沿用 ONNX Runtime 与现有后处理依赖。正式接入以动态尺寸、分辨率等级及完整流程验收通过为前提。
|
||||
@@ -0,0 +1,73 @@
|
||||
## Purpose
|
||||
|
||||
让用户通过现有 ONNX Runtime 体系使用完整的 MoGe-3 ViT-L 几何预测,使模型准备、选择和执行在深度平面、裁切与全景中具有一致行为,并通过完整细化及数值验证保证结果可信。
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Execute complete MoGe 3 with ONNX
|
||||
|
||||
正式推理 SHALL 通过 ONNX Runtime 执行 MoGe 3 的学习网络计算,并完成三次细化;运行环境 SHALL 无需 PyTorch、FlexGEMM 或 Triton。
|
||||
|
||||
#### Scenario: Production inference
|
||||
- **WHEN** 在仅安装声明的 ONNX 运行依赖的 Server 中执行 MoGe 3
|
||||
- **THEN** 完成包含三次细化的预测,不依赖外部 Torch 环境或服务
|
||||
|
||||
### Requirement: Validate conversion before availability
|
||||
|
||||
系统 SHALL 在完整流程的动态尺寸、分辨率等级、精度、性能和执行提供程序验证通过后才将新模型登记为正式可用模型。
|
||||
|
||||
#### Scenario: Incomplete conversion
|
||||
- **WHEN** 只有关闭细化的模型可以运行,或完整模型未通过预定数值验收
|
||||
- **THEN** 保留验证结果且不将其作为完整 MoGe 3 发布
|
||||
|
||||
### Requirement: Select and prepare model
|
||||
|
||||
系统 SHALL 提供 MOGE3_VITL 选项,保留已有模型键和默认值,下载并校验推理所需的全部 ONNX 资产。
|
||||
|
||||
#### Scenario: Download success
|
||||
- **WHEN** 所需图文件及外部权重全部下载并通过完整性校验
|
||||
- **THEN** 标记模型就绪,深度平面、裁切与全景使用用户所选模型
|
||||
|
||||
#### Scenario: Interrupted download
|
||||
- **WHEN** 下载失败、取消或文件不完整
|
||||
- **THEN** 不标记就绪,并允许重新准备
|
||||
|
||||
### Requirement: Preserve geometry contract
|
||||
|
||||
MoGe 3 SHALL 按所选分辨率等级提供与分析图尺寸对应的深度、法线、有效性、点图和相机内参,并遵循既有源 Alpha 处理契约。
|
||||
|
||||
#### Scenario: Image inference
|
||||
- **WHEN** 有效图片完成推理
|
||||
- **THEN** 有效区域深度为有限正值,产物可供既有几何工作流应用
|
||||
|
||||
#### Scenario: Panorama inference
|
||||
- **WHEN** 全景任务选择 MoGe 3
|
||||
- **THEN** 透视视图使用已知 90 度水平 FOV,模型 Mask 与源有效性共同约束融合及 Alpha 输出
|
||||
|
||||
### Requirement: Reuse existing input and workflow controls
|
||||
|
||||
MoGe 3 SHALL 沿用 MoGe 2 的输入准备、最大分析尺寸、分辨率等级及设备选择流程。同一组已下载模型资产 SHALL 接受现有流程产生的不同图片尺寸、宽高比和分辨率等级,输出尺寸对应分析图片。
|
||||
|
||||
#### Scenario: Vary image dimensions
|
||||
- **WHEN** 用户连续提交现有输入流程支持的不同尺寸和宽高比图片
|
||||
- **THEN** 使用同一组模型资产完成预测,遵循现有分析尺寸限制及输出尺寸契约
|
||||
|
||||
#### Scenario: Change resolution level
|
||||
- **WHEN** 用户修改现有 MoGe Resolution Level 后再次执行任务
|
||||
- **THEN** 新任务采用对应 token 数并执行三次细化,复用已下载模型资产
|
||||
|
||||
#### Scenario: Switch to MoGe 3 in an existing workflow
|
||||
- **WHEN** 用户在深度平面、裁切或全景工作流中将模型改为 MoGe 3
|
||||
- **THEN** 沿用原有任务参数、操作步骤与几何产物,模型内部完成多图及细化调度
|
||||
|
||||
### Requirement: Manage supported sessions
|
||||
|
||||
系统 SHALL 按已验证的 ONNX 设备能力执行,复用连续帧或视图的会话,在模型切换及关闭时释放资源,对不支持的执行方式和推理失败给出明确反馈。
|
||||
|
||||
#### Scenario: Model switching
|
||||
- **WHEN** 用户在 MoGe 2 与 MoGe 3 之间切换
|
||||
- **THEN** 执行新选择并释放旧模型资源
|
||||
|
||||
#### Scenario: Unsupported execution or cancellation
|
||||
- **WHEN** 执行设备不支持、资源不足或任务取消
|
||||
- **THEN** 给出明确状态,不应用部分结果,并允许后续重新运行
|
||||
20
openspec/changes/archive/2026-09-17-add-moge3-model/tasks.md
Normal file
20
openspec/changes/archive/2026-09-17-add-moge3-model/tasks.md
Normal file
@@ -0,0 +1,20 @@
|
||||
## 1. 原型与动态配置验收
|
||||
|
||||
- [x] 1.1 在项目外将完整 MoGe 3 拆为骨干与细化 ONNX,使用固定 256×192 / 1200 tokens 在 CPU 和 DirectML 上运行三张图、三次细化,保存参考比较及耗时记录。
|
||||
- [x] 1.2 将骨干扩展为动态输入尺寸和可变 token 数,用同一组 ONNX 资产连续运行不同宽高比、分析尺寸与分辨率等级,验证输出尺寸遵循现有契约。
|
||||
- [x] 1.3 定位原型中较大像素误差,确定后续验收容差,在 CPU 与 DirectML 验证动态配置及 90 度 FOV,记录精度、实际执行路径、耗时与内存峰值。
|
||||
- [x] 1.4 汇总动态配置、精度、性能及平台支持矩阵;完整验收通过后进入第 2 阶段,未通过项保留具体实验结论。
|
||||
|
||||
## 2. 模型资产与推理
|
||||
|
||||
- [x] 2.1 将已验证的转换方案整理至 tools/models,固定离线依赖,重新导出并通过相同精度验收以验证可复现性。
|
||||
- [x] 2.2 登记 MOGE3_VITL 的全部 ONNX 图及外部数据文件,验证大小和哈希,测试下载成功、失败、取消与就绪判断。
|
||||
- [x] 2.3 将双图、邻域生成和三次细化封装在 MoGe 3 ONNX 模块,仅增加必要推理分派;测试现有参数、输出尺寸、内参、有效性及分辨率等级传递。
|
||||
- [x] 2.4 更新会话缓存和释放,测试模型交替、连续帧复用与失败后恢复,确认正式环境不需 Torch 或 Triton。
|
||||
|
||||
## 3. 工作流接入与验收
|
||||
|
||||
- [x] 3.1 在现有模型登记与枚举中加入新选项,测试旧模型键和默认值保持、新模型选择及设备错误提示,核对继续使用既有 Job 参数。
|
||||
- [x] 3.2 在深度平面、裁切与全景的必要调用点接入分派,复用现有 Operator 和节点资产,运行产物、90 度 FOV、Alpha、取消与融合回归测试。
|
||||
- [x] 3.3 在正式 ONNX Runtime 环境运行完整工作流并记录结果,在支持矩阵中的其他平台完成对应验收。(Windows 与 Linux 已通过,macOS 见 validation.md。)
|
||||
- [x] 3.4 检查依赖、Manifest 和打包测试的一致性,运行相关测试与全量 pytest,确认正常退出及最终差异范围。
|
||||
@@ -0,0 +1,51 @@
|
||||
# MoGe 3 ONNX 验收
|
||||
|
||||
2026-09-10。新增 `MOGE3_VITL`,复用 MoGe 2 的输入准备、0–9 分辨率等级、下载、会话缓存和几何产物。骨干支持动态图片尺寸及 token 数,细化图支持动态点数,正式推理执行三次细化。
|
||||
|
||||
## 资产
|
||||
|
||||
转换源码:`tools/models/moge3_export.py`。上游提交为 `74fbce054ebed49800de42d0ad0e83495065719a`,权重 revision 为 `184008f877d7ad1ad4c2cd2182a9bd1f63d0e5be`。固定 Torch、ONNX、NumPy 和上游提交后重复导出,两次文件哈希一致。
|
||||
|
||||
| 文件 | 字节数 | SHA-256 |
|
||||
|---|---:|---|
|
||||
| backbone.onnx | 1324190318 | f21246c6921373c8c79ecdb8c78818a79bd2506c2fb88f4d4cdec21b92f725e3 |
|
||||
| refiner.onnx | 157729628 | 67ad7297aba18f8ef7939e8f700a5b25c41a19ddb68656bc2cd08b54c248fe2a |
|
||||
|
||||
权重内嵌于两个 ONNX 文件。镜像已发布到 `models.omoolab.xyz/moge-3-vitl-onnx/`,使用项目 `ModelManager.download` 下载到新目录,耗时 136.1 秒,大小、哈希及 `ready()` 校验通过。
|
||||
|
||||
## 精度与动态配置
|
||||
|
||||
参考为原始 MoGe 3、相同权重和三次细化。上游 FlexGEMM 的 `SPCONV_ALLOW_TF32` 独立于 Torch TF32 设置;关闭该选项后进行 FP32 比较。验收脚本预设平均深度相对误差小于 0.1%、99 分位小于 1%、二值 Mask 一致率大于 99.9%。
|
||||
|
||||
同一组动态图依次执行以下配置;耗时包含骨干、邻域生成、三次细化和后处理,不包含会话创建。
|
||||
|
||||
| 图片宽×高 | tokens | 水平 FOV | Windows CPU | DirectML | DirectML 平均深度相对误差 | 99 分位 |
|
||||
|---|---:|---|---:|---:|---:|---:|
|
||||
| 384×256 | 1200 | 自动 | 10.65 s | 4.86 s | 0.00216% | 0.02068% |
|
||||
| 257×385 | 2400 | 90° | 22.92 s | 9.67 s | 0.00228% | 0.01823% |
|
||||
| 320×320 | 3600 | 自动 | 36.41 s | 17.11 s | 0.00167% | 0.01609% |
|
||||
|
||||
三项均通过 CPU 和 DirectML 容差,二值 Mask 一致率为 100%。正式模块使用现有 MoGe 2 后处理,等级 0/9 的平均点图相对误差约 0.00217%/0.00173%,平均法线角误差约 0.006°,归一化内参最大绝对偏差小于 0.000002。2400 tokens 是图层测试值;公开等级继续按原公式映射至 1200–3600 tokens。
|
||||
|
||||
## 工作流与平台
|
||||
|
||||
| 平台 / Runtime | 验收结果 |
|
||||
|---|---|
|
||||
| Windows / DirectML 1.24.4 / RTX 5070 Ti | 动态精度、深度、裁切、12 视图全景通过;纯 ONNX 环境 |
|
||||
| Windows / CPU | 动态精度通过;原型 CPU Runtime 为 1.29.0 |
|
||||
| Linux x64 / CPU 1.24.4 / Python 3.12 | 使用项目声明的正式依赖,深度、裁切、12 视图全景通过 |
|
||||
| macOS / CPU、CoreML | 当前没有可用设备,待实机验收 |
|
||||
|
||||
真实 Job 调用仅替换进度/取消传输上下文,图片准备、模型管理、推理、几何处理与文件写入均使用项目代码。Windows 深度、裁切、全景分别约 9.81、5.45、67.13 秒;Linux 分别约 19.19、9.10、112.10 秒。深度/裁切生成 EXR、元数据及法线,全景完成 12 个 90° 视图推理和径向融合并生成 EXR 与元数据。
|
||||
|
||||
DirectML 的主要网络计算在 GPU 执行,部分 Gather、Unsqueeze、Concat、Less、Cast、ReduceSum 由 ONNX Runtime CPU 提供程序执行。正式环境依赖保持原样,未引入 Torch、FlexGEMM 或 Triton。
|
||||
|
||||
等级 0/9 连续运行时,Windows 进程峰值工作集约 1483 MiB;显卡整体占用由 6370 MiB 上升至最高 15716 MiB(每 0.5 秒采样,包含桌面及其他进程,不能视为模型独占显存)。Linux 三个 Job 进程峰值 RSS 约 4664 MiB。高等级的内存占用明显高于低等级。
|
||||
|
||||
## 回归与复现记录
|
||||
|
||||
全量 pytest:1143 passed、52 subtests passed,187.79 秒,正常退出。随后新增会话加载失败恢复测试,MoGe 3 专项 7 passed。覆盖下载完整性、缓存复用/切换/恢复、三次细化、尺寸、参数及现有 Alpha、FOV、取消、全景融合和打包内容契约。OpenSpec 严格校验通过。
|
||||
|
||||
实验与运行日志保存在 `C:/Users/icrdr/AppData/Local/Temp/anyimage-moge3-trial/`:`dynamic_reference.py`、`dynamic_validate.py`、`production_validate.py`、`job_validate.py`、`linux_jobs.py`、`download_validate.py`、`memory_validate.py`,及对应 JSON/日志。Linux 使用只读项目挂载的 Python 3.12 容器;Windows 全量测试使用按项目锁文件创建的独立测试环境。
|
||||
|
||||
任务 3.3 保留未完成状态,待补充 macOS 实机验收。
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-09
|
||||
@@ -0,0 +1,86 @@
|
||||
## Context
|
||||
|
||||
独立原型验证了 12 个透视视图的 MoGe2 推理、距离融合和 Quad Sphere。正式转换沿用 Image Empty 的异步转换流程。用户明确选择通过现有 Remove Background 预先处理背景,转换消费当前图片和 Alpha。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
本次范围是静态 2:1 等距柱状全景的菜单转换、正式节点资产和 Split Threshold。沿用原型的 Subdivide、Depth Scale 和单张深度图片约定。
|
||||
|
||||
## Decisions
|
||||
|
||||
### 入口与对象生命周期
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Image Empty 右键 AnyImage] --> B[Convert to Panorama]
|
||||
B --> C[检查静态图和 2 比 1 尺寸]
|
||||
C --> D[读取当前 RGBA 和 Preferences]
|
||||
D --> E[提交全景 Job]
|
||||
E --> F[加载并打包颜色和深度]
|
||||
F --> G[创建全景对象和节点修改器]
|
||||
G --> H[替换源 Empty 并记录撤销]
|
||||
```
|
||||
|
||||
新增 `operators/convert_to_panorama/`,`operators.py` 定义 `ConvertToPanorama` 与 `GeneratePanorama`,`object.py` 创建结果,`__init__.py` 导出注册清单。公开入口为 `anyimage.convert_to_panorama`,Job 为 `generate-panorama-geometry`。复用 `common` 的输入暂存、材质、深度加载、修改器赋值和对象收尾函数。
|
||||
|
||||
模型、推理级别和分析尺寸从 Preferences 读取,Job 参数为普通字典。Blender 数据只在主线程读取输入和处理响应。新对象中心对应原 Empty 原点,继承其世界变换;局部 +Z 为全景上方,U=0 朝向 +X,U 增加沿 +Y 转动。生成成功后替换源对象并激活结果;失败、取消或源对象已失效时清理本次临时资源。图片显示偏移只属于平面预览,球面方向由完整图片 UV 定义。
|
||||
|
||||
### 后端与数据协议
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[当前 RGBA 全景] --> B[12 个重叠 90 度视图]
|
||||
B --> C[MoGe2 与已知 FOV 的几何恢复]
|
||||
C --> D[有效视图的径向距离]
|
||||
D --> E[横向周期的对数梯度融合]
|
||||
E --> F[尺度锚定与有限距离场]
|
||||
A --> G[源 Alpha 对齐到深度尺寸]
|
||||
F --> H[depth.exr]
|
||||
G --> H
|
||||
```
|
||||
|
||||
`server/jobs/panorama.py` 负责任务调度,`server/geometry/panorama.py` 负责投影与融合。将已知 FOV 接入已有 ONNX 后处理和模型会话生命周期,普通图片继续使用焦距估计。保留原分辨率颜色;视图分析尺寸遵循输入上限,首版目标视图为 512×512,融合网格最长边上限 512 且保持 2:1。Subdivide 独立控制最终网格密度。
|
||||
|
||||
源 Alpha 对齐、黑底模型输入及可见区域排除采用原型已验证的流程。全透明视图跳过推理,部分视图无效时继续融合其余有效观测;全局无有效几何时报错。逐视图及融合前后检查取消,释放模型资源。
|
||||
|
||||
| 字段 | 文件 | 内容 |
|
||||
| --- | --- | --- |
|
||||
| 颜色文件 | `image.0001.png` | 当前 RGBA,保留源 Alpha |
|
||||
| `depth` | `depth.exr` | float32 RGB 重复保存径向距离,A 为融合 Mask × 源 Alpha |
|
||||
| `depth_metadata` | `depth.json` | `projection: equirectangular`、`image_size`、`reference_distance` |
|
||||
|
||||
`reference_distance` 为满足 Alpha 阈值的有限正径向距离中位数,用于 Split Threshold 的尺度归一化。全景元数据按投影类型单独读取;复用现有 EXR 写入及 Non-Color、CHANNEL_PACKED、Pack 配置。颜色由材质采样,几何节点只有一个深度图片输入。
|
||||
|
||||
### Quad Sphere 与 Split Threshold
|
||||
|
||||
新增资产 `O Image Panorama`。Quad Sphere 与球面校正子组的构建定义归入本仓库节点构建模块,参考已检查的 `O Quad Sphere`,运行时从 `O_AnyImage.blend` 加载。面数为 `6 × 4^Subdivide`,默认 Subdivide=7;Depth Scale 默认 1,范围 0–1;Validity Threshold 默认 0.9;Split Threshold 默认 0,范围 0–1。深度图片、材质和参考距离作为数据输入。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[可调细分 Quad Sphere] --> B[固定单位射线与球面 UV]
|
||||
B --> C[按深度 EXR Alpha 剔除无效面]
|
||||
C --> D[相邻面径向距离变化判断]
|
||||
D --> E[Split Edges 与条带面清理]
|
||||
E --> F[分离面角沿原射线取所属面距离]
|
||||
F --> G[Depth Scale 插值到重建表面]
|
||||
G --> H[材质与临时属性清理]
|
||||
```
|
||||
|
||||
提取并复用 `tools/nodes/common/depth_surface.py` 中的相邻面采样、内边判断、Split Edges、面角归属和条带面清理。共用逻辑消费明确的深度字段、基础面中心和归一化比例;Depth Plane 的调用同步适配,保持已有求值结果。
|
||||
|
||||
对全景,令相邻面距离为 rA/rB,单位球面上的面中心为 cA/cB,参考距离为 R。沿用现有 2.5 系数,以 `abs(rA-rB) / R × Split Threshold > 2.5 × distance(cA,cB)` 选择有两个邻面的内边。阈值为 0 时直接保留通过有效性剔除的原网格;阈值增大时选择更多断层边。
|
||||
|
||||
选中断层附近的面角使用其所属面的距离,未选中的顶点保持原采样;所有点沿原始球面射线定位。最终半径为 `1 + Depth Scale × (目标距离 - 1)`。条带面清理遵循当前 Depth Plane 的同一拓扑快照规则。
|
||||
|
||||
全景面中心采样由面中心方向重新计算 UV,横向循环采样;材质面角 UV 独立处理经度分支和极点。这样左右接缝与普通内边参与同样的判断。分裂后保留原射线及面角 UV,末尾清理专用临时属性。
|
||||
|
||||
### 验证与交付
|
||||
|
||||
正式模块从原型迁移有效算法及测试,运行时依赖正式模块与打包资产。此次实现运行相关单元测试、完整回归测试和独立 Blender 求值检查。节点资产更新走现有离线资产流程;扩展包及文档构建属于另行发布操作。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- 径向距离与平面 Z 深度的比较尺度不同 → 用参考距离归一化,并测试相同形状整体缩放后的分裂选择一致。
|
||||
- 全景接缝和极点的 UV 有多种等价表示 → 用方向生成采样坐标,并验证接缝两侧的等价深度断层。
|
||||
- 共享分裂代码近期增加了条带清理 → 保留现有 Depth Plane 回归结果,并覆盖全景分裂后孤立条带、Alpha 边缘和连续斜面。
|
||||
- 天空及遮挡导致预测缺失 → 以当前源图 Alpha 和融合 Mask 确定有效几何,允许通过现有 Remove Background 预先处理。
|
||||
@@ -0,0 +1,25 @@
|
||||
## Why
|
||||
|
||||
全景原型已验证 Quad Sphere、径向深度和单张深度 EXR 的可行性。现在需要把生成流程接入 Image Empty 的常用入口,并通过 Depth Plane 的 Split Threshold 机制处理前后景连接处的拉伸。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 在 Image Empty 的 AnyImage 右键菜单增加 `Convert to Panorama`,通过后台 Job 将静态 2:1 全景转换成可编辑的全景表面。
|
||||
- 将原型的多视图 MoGe2 推理、距离融合和深度 Alpha 导出接入正式 Server。
|
||||
- 新增 `O Image Panorama` 节点资产,提供 Quad Sphere 细分、Depth Scale、Validity Threshold 和 Split Threshold。
|
||||
- 复用 Depth Plane 的分边、面角深度重定位和条带面清理机制,按全景径向距离适配判断。
|
||||
- 延续单深度图片输入:`depth.exr` 的 Alpha 在导出时合成模型 Mask 与当前源图 Alpha。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `panorama-conversion`: Image Empty 菜单、异步转换、对象替换与撤销。
|
||||
- `panorama-geometry-generation`: 全景多视图推理、融合与文件产物。
|
||||
- `panorama-surface`: Quad Sphere、球面 UV、深度有效性剔除与 Split Threshold。
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
## Impact
|
||||
|
||||
涉及 `menu.py`、类型注册、新增 `operators/convert_to_panorama/`、Server Job 与全景几何模块、共享深度表面节点构建函数,以及对应测试。生产节点组由资产构建流程写入 `O_AnyImage.blend`,运行时按名称加载。使用现有 MoGe2、ONNX Runtime、NumPy 与 SciPy 环境。
|
||||
@@ -0,0 +1,33 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Image Empty panorama conversion entry
|
||||
|
||||
系统 SHALL 在 Image Empty 的 AnyImage 右键菜单展示 `Convert to Panorama`,调用 `anyimage.convert_to_panorama`。
|
||||
|
||||
#### Scenario: Select panorama conversion
|
||||
- **WHEN** 用户选中静态 2:1 Image Empty 并选择 Convert to Panorama
|
||||
- **THEN** 提交当前图片及 Alpha 的全景生成任务,使用 Preferences 中的模型、推理级别和分析尺寸
|
||||
|
||||
#### Scenario: Unsupported image
|
||||
- **WHEN** 输入是动画、视频或非 2:1 图片
|
||||
- **THEN** 提示输入限制并保留源对象
|
||||
|
||||
### Requirement: Consume current image alpha
|
||||
|
||||
转换 SHALL 直接使用当前源图及 Alpha。背景处理由用户通过现有 Remove Background 完成。
|
||||
|
||||
#### Scenario: Already removed background
|
||||
- **WHEN** 源 Empty 的天空已透明
|
||||
- **THEN** 当前 Alpha 进入全景任务,转换过程中只运行几何模型
|
||||
|
||||
### Requirement: Complete conversion transaction
|
||||
|
||||
系统 SHALL 在主线程加载结果、打包贴图并创建节点对象,成功后替换源 Empty,并提供完整撤销。
|
||||
|
||||
#### Scenario: Successful conversion
|
||||
- **WHEN** Job 返回有效产物
|
||||
- **THEN** 创建使用 O Image Panorama 的对象,继承源 Empty 世界变换并以其原点为拍摄中心,激活结果并支持撤销恢复源对象
|
||||
|
||||
#### Scenario: Cancelled or failed conversion
|
||||
- **WHEN** 任务失败、取消或响应时源对象已失效
|
||||
- **THEN** 清理本任务暂存及未使用数据块,不应用部分转换结果
|
||||
@@ -0,0 +1,33 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Generate panoramic radial distance
|
||||
|
||||
Server SHALL 从当前 RGBA 全景生成 12 个重叠的 90 度透视视图,使用已知 FOV 恢复 MoGe2 几何并融合为横向周期的径向距离场。
|
||||
|
||||
#### Scenario: Continuous panorama
|
||||
- **WHEN** 输入包含跨越 U=0/1 的连续表面
|
||||
- **THEN** 两侧使用同一周期求解,产出有限正距离且无额外拼接断层
|
||||
|
||||
#### Scenario: Invalid views and cancellation
|
||||
- **WHEN** 部分视图全透明或模型无有效预测
|
||||
- **THEN** 利用其余有效观测完成融合,全局无有效几何时返回明确错误,并在视图与融合阶段响应取消
|
||||
|
||||
### Requirement: Export combined depth alpha
|
||||
|
||||
系统 SHALL 返回颜色文件、`depth.exr` 与 `depth.json`。深度 RGB 保存径向距离,Alpha 在导出时一次合成融合 Mask × 源 Alpha;元数据 SHALL 标明等距柱状投影、深度图尺寸和参考距离。
|
||||
|
||||
#### Scenario: Transparent background
|
||||
- **WHEN** 源图某区域 Alpha 为零
|
||||
- **THEN** 该区域深度 EXR Alpha 为零,颜色文件保留源 Alpha
|
||||
|
||||
#### Scenario: Fractional alpha
|
||||
- **WHEN** 有效模型 Mask 为 1 且源 Alpha 为 0.5
|
||||
- **THEN** 深度导出 Alpha 为 0.5,几何端直接消费该值
|
||||
|
||||
### Requirement: Use managed production runtime
|
||||
|
||||
全景生成 SHALL 使用现有模型会话、Preferences 配置和 Job 生命周期;Job 参数可序列化,Server 与 Blender 数据保持进程边界。
|
||||
|
||||
#### Scenario: Configured inference
|
||||
- **WHEN** 用户修改模型或推理分辨率级别后发起转换
|
||||
- **THEN** 新任务采用配置值,最终网格细分仍由独立 Subdivide 参数控制
|
||||
@@ -0,0 +1,41 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Editable Quad Sphere surface
|
||||
|
||||
系统 SHALL 提供资产 `O Image Panorama`,使用全四边面 Quad Sphere,并提供 Subdivide、Depth Scale、Validity Threshold 和 Split Threshold。几何节点 SHALL 只有一个深度图片输入。
|
||||
|
||||
#### Scenario: Sphere and depth endpoints
|
||||
- **WHEN** 所有样本有效且 Split Threshold 为 0
|
||||
- **THEN** Subdivide=n 产生 6×4^n 个四边面,Depth Scale=0 为单位球,Depth Scale=1 为预测径向距离表面
|
||||
|
||||
#### Scenario: Validity culling
|
||||
- **WHEN** 深度 EXR Alpha 低于 Validity Threshold
|
||||
- **THEN** 按 Depth Plane 的点域布尔字段与面域删除方式剔除对应区域
|
||||
|
||||
### Requirement: Depth Plane split semantics
|
||||
|
||||
全景 SHALL 复用 Depth Plane 的相邻面深度差判断、内边分裂、面角深度重定位和条带面清理机制,并使用参考距离归一化的径向距离适配球面。
|
||||
|
||||
#### Scenario: Disabled splitting
|
||||
- **WHEN** Split Threshold 为 0
|
||||
- **THEN** 分裂与条带清理完全旁路,有效性剔除保持生效
|
||||
|
||||
#### Scenario: Foreground depth discontinuity
|
||||
- **WHEN** 相邻有效面的归一化距离跳变达到分裂条件
|
||||
- **THEN** 分离选中的内边,断层附近面角沿原球面射线使用所属面的距离,未选中顶点保持原始深度采样
|
||||
|
||||
#### Scenario: Threshold and scale consistency
|
||||
- **WHEN** 增大 Split Threshold 或对所有距离与参考距离同时乘以相同正比例
|
||||
- **THEN** 增大阈值不会减少被选中的断层边,等比例距离变化不会改变断层边选择
|
||||
|
||||
#### Scenario: Seam and strip cleanup
|
||||
- **WHEN** 深度断层跨越全景经度接缝或分裂产生孤立条带
|
||||
- **THEN** 经度接缝采用周期采样,面角 UV 保持正确,条带面按 Depth Plane 的同一拓扑快照规则清理
|
||||
|
||||
### Requirement: Packaged node assets
|
||||
|
||||
生产节点组及 Quad Sphere 子组 SHALL 包含在 `O_AnyImage.blend` 中,通过现有加载入口使用。
|
||||
|
||||
#### Scenario: Reopen converted object
|
||||
- **WHEN** 在新 Blender 进程重新打开转换后的文件
|
||||
- **THEN** 无需访问原型目录或外部参考库即可求值,贴图、细分、Depth Scale 和 Split Threshold 均正常工作
|
||||
@@ -0,0 +1,24 @@
|
||||
## 1. 全景后端
|
||||
|
||||
- [x] 1.1 将原型的投影、周期距离融合和参考距离计算迁移到正式 Server 模块,保留解析几何测试
|
||||
- [x] 1.2 为现有 MoGe2 ONNX 推理增加已知 FOV 的几何恢复,复用模型会话并验证普通图片回归
|
||||
- [x] 1.3 实现 generate-panorama-geometry Job、尺寸和有效性检查、取消与清理,导出当前颜色、深度及全景元数据
|
||||
- [x] 1.4 验证源 Alpha 与 Mask 仅在深度导出合成一次,覆盖透明视图、全局无有效数据和跨经度接缝
|
||||
|
||||
## 2. 全景节点资产
|
||||
|
||||
- [x] 2.1 在本仓库实现 Quad Sphere 及校正子组的资产构建,创建 O Image Panorama 的细分、球面 UV 和 Depth Scale
|
||||
- [x] 2.2 复用深度 EXR 加载与 Alpha 剔除,保持几何节点单深度图片输入
|
||||
- [x] 2.3 提取共享 Split Threshold 机制并同步适配现有调用,将径向距离判断、面角重定位和条带清理接入全景
|
||||
- [x] 2.4 覆盖阈值旁路、断层强度、尺度不变性、连续斜面、经度接缝、极点及条带清理,并运行现有深度表面回归
|
||||
|
||||
## 3. 菜单与对象转换
|
||||
|
||||
- [x] 3.1 实现 ConvertToPanorama 与 GeneratePanorama,接入 Preferences、输入暂存、Job 参数与注册清单
|
||||
- [x] 3.2 在 Image Empty 的 AnyImage 菜单添加 Convert to Panorama,验证静态 2:1 输入限制及当前 Alpha 传递
|
||||
- [x] 3.3 实现主线程结果加载、材质、全景对象和修改器,覆盖源变换继承、替换、撤销及失败清理
|
||||
|
||||
## 4. 集成验证
|
||||
|
||||
- [x] 4.1 通过既有离线节点资产流程更新 O_AnyImage.blend,验证新进程加载和打包后独立求值
|
||||
- [x] 4.2 使用已去天空的真实全景与解析样本检查正式菜单流程和 Split Threshold,运行完整测试并检查最终差异
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-08
|
||||
@@ -0,0 +1,74 @@
|
||||
## Context
|
||||
|
||||
当前 `onnx_upscale.py` 用 256 像素 tile 执行三个 4× 模型,`models/upscale.py` 负责输入尺寸限制和 alpha 回填,`ModelManager` 缓存单个 Upscale Session。Operator 的倍率和提示均为 4×。
|
||||
|
||||
本地实验位于 `build/upscale-explore/pisa-research/`。已验证固定 512×512 的 PiSA-SR FP16 ONNX 全流程在 DirectML 上出图,0.7 预设对应像素强度 1.0、语义强度 0.7、空提示词和 seed 42。两套 UNet 常驻时,512 输出约 1.57–2.37 秒;默认强度 1.0 的单 UNet 速度不代表 0.7。分块和跨设备部署尚待验证。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:** 四个选项统一 2× 输出;复现已选定的 PiSA-SR 0.7 观感;通过已有 ONNX 运行时服务图片和逐帧任务。
|
||||
|
||||
**Non-Goals:** 本次范围限定为固定预设与既有 Upscale 操作,强度滑杆、提示词编辑和视频时序增强属于独立能力。
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. 最终倍率与模型原生倍率分别定义
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[读取并限制输入尺寸] --> B{模型}
|
||||
B -->|现有三个模型| C[原生 4× 分块推理]
|
||||
C --> D[Lanczos 缩至输入宽高的 2 倍]
|
||||
B -->|PiSA-SR| E[Bicubic 放大至 2×]
|
||||
E --> F[VAE 编码与固定噪声采样]
|
||||
F --> G[像素与完整 UNet 预测]
|
||||
G --> H[按语义强度 0.7 组合并解码]
|
||||
H --> I[AdaIN 颜色校正与裁切]
|
||||
D --> J[按最终尺寸回填原始 alpha]
|
||||
I --> J
|
||||
J --> K[保存结果并替换图片]
|
||||
```
|
||||
|
||||
公共结果倍率为 2,旧 ONNX 图的 shape 校验仍为原生 4。先实现完整 4× 拼接后一次 Lanczos 缩小,作为明确的像素基线;避免先逐 tile 缩小引入新的滤波接缝。最终尺寸相对于 `limit_image()` 之后的输入,沿用 Operator 对输入长边的既有限制。
|
||||
|
||||
保留三个模型的 key、原生名称和默认选择,说明文字明确最终输出 2×。新增 key `PISA_SR`、显示名 `PiSA-SR`,说明固定细节强度 0.7;生产和 Debug 复用相同输出入口。
|
||||
|
||||
### 2. 固定 0.7 的定义与实验一致
|
||||
|
||||
使用同一采样 latent 计算 `pixel + 0.7 * (full - pixel)`,再按已验证的公式解码。空提示词 embedding 与 timestep 固定在资产中,seed 固定为 42;采样算法、精度和噪声布局通过测试锁定。保留实验中的 Bicubic 输入插值、VAE scaling、输出量化和 AdaIN 颜色校正。
|
||||
|
||||
该强度是在预测结果上插值。将 LoRA 权重乘以 0.7 或插值两套 UNet 权重,不能视为等价的单 UNet 优化;任何这样的优化必须另行通过效果对照。
|
||||
|
||||
### 3. PiSA-SR 使用独立适配器与多文件资产
|
||||
|
||||
新增 `server/models/onnx_pisa_sr.py` 承担 VAE、UNet、采样及分块。`ModelManager` 按模型类型创建单 Session 或 PiSA-SR 管线,并统一缓存 key、释放和状态展示。Session 创建和运行复用 `onnx_runtime.py`,补齐 DirectML 所需的 sequential execution 与禁用 memory pattern 配置。
|
||||
|
||||
资产以 encoder、decoder、pixel UNet、full UNet 为明确文件集合,通过既有 catalog 下载与完整性校验。实验包约 3.63 GB,发布清单必须使用最终导出文件的尺寸和 SHA256。导出工具固定 PiSA 源码 commit、基础模型 revision、LoRA 校验值和导出环境;训练依赖仅用于离线工具。实验目录仅作为参考,不作为运行时路径。
|
||||
|
||||
### 4. 分阶段执行限制模型常驻
|
||||
|
||||
优先采用按阶段处理全部 tile 的调度:编码并把 latent 存入主机内存,运行像素 UNet 保存预测,释放该 Session 后运行完整 UNet并组合,最后解码。GPU 上同一时刻只常驻一套 UNet;切换在整图阶段边界发生。大图中间数组可落入任务临时目录,结束或取消后清理。
|
||||
|
||||
此方案以降低显存峰值为优先,代价是阶段加载成本;实施时同时测冷启动、连续图片和多帧耗时。任务结束按实际 Session 最后使用点释放临时推理内存,资源错误使整个 PiSA 管线缓存失效,Clear Models 释放全部组件。
|
||||
|
||||
### 5. 固定尺寸 ONNX 通过重叠分块覆盖任意输入
|
||||
|
||||
以已验证的 512×512 输出 tile 为初始部署尺寸,采用重叠区域加权融合,边缘填充后裁回精确的 2× 尺寸。噪声按整图 latent 坐标生成并切片,重叠区域共享噪声;AdaIN 在拼接完成后按整图执行。单 tile 情况保持实验基线,多 tile 的重叠宽度通过接缝对照确定并固化为内部常量。
|
||||
|
||||
alpha 从受尺寸限制的源图直接 Lanczos 放大到最终 2×,独立于生成模型;透明 RGB 的处理沿用现有输入语义,并加入半透明边缘目视验收。多帧逐帧处理并复用固定噪声规则,支持取消;生成纹理的时序稳定性列入验收观察。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [分块改变生成纹理或形成接缝] → 对两张实验样图及跨 tile 的纹理、渐变、透明边缘做完整图与局部对照;接缝未通过时继续调整分块方案。
|
||||
- [分阶段 Session 加载拖慢连续任务] → 记录端到端冷暖耗时、峰值显存和主机内存;不将单 UNet 默认模式的数字作为 0.7 性能承诺。
|
||||
- [FP16 图在 CPU/CoreML 上兼容性未知] → 实测项目支持的设备路径;需要时导出匹配的精度资产,并在 catalog 中表达准确支持范围。不可运行的设备返回明确原因。
|
||||
- [新细节改变原始图案或引起逐帧闪动] → 保留已有模型选择,验收脸部、文字、纹理和短序列样例,界面说明生成型细节补充。
|
||||
- [多 GB 权重分发与依赖来源] → 发布前核对基础模型和 PiSA 权重的分发条款,登记固定来源及最终文件校验信息。
|
||||
|
||||
## Migration Plan
|
||||
|
||||
先准备与验证 PiSA 资产及分块管线,再接入 catalog、模型管理器和界面,统一最终 2× 结果并运行测试。现有用户保存的模型 key 继续有效,后续执行采用 2×;已有图片产物无需迁移。回退时同步回退模型选项和推理入口,已下载资产可由已有缓存管理处理。
|
||||
|
||||
## Open Questions
|
||||
|
||||
实施实验需确定分块重叠宽度、阶段加载的可接受耗时,以及 CPU/CoreML 的实际精度与支持范围。这些验证不改变 0.7 和最终 2× 的产品约定。
|
||||
@@ -0,0 +1,27 @@
|
||||
## Why
|
||||
|
||||
现有 Upscale 每次把宽高放大 4 倍,尺寸增长过快。PiSA-SR 在已测试的 0.7 细节强度下能够补充更丰富的纹理,适合作为新的生成型选项;所有模型统一每次输出 2×,方便逐步放大。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 新增 `PiSA-SR` 模型选项,固定 `lambda_pix=1.0`、`lambda_sem=0.7`,输出宽高各为输入的 2 倍。
|
||||
- **BREAKING**:现有 General WDN x4v3、SAFMN Real x4、Real-ESRGAN x4plus 仍使用已有 4× 权重,完成推理后以 Lanczos 缩小至一半,最终输出 2×。
|
||||
- Upscale 的尺寸计算、界面说明、生产与调试任务统一采用最终 2× 语义;保留现有默认模型。
|
||||
- PiSA-SR 通过 ONNX Runtime 接入模型下载、设备选择、缓存、取消和资源释放流程,支持任意合法输入尺寸与透明度保留。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `two-times-upscale`: 四个模型的统一 2× 输出、PiSA-SR 固定细节预设及模型资源生命周期。
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
无已发布的对应 capability。
|
||||
|
||||
## Impact
|
||||
|
||||
- `server/model_catalog.py`、模型下载与导出工具:增加 PiSA-SR 多文件模型资产及校验信息。
|
||||
- `server/models/`、`server/model_manager.py`:PiSA-SR 推理、分块及多 Session 生命周期;旧模型结果缩小。
|
||||
- `operators/upscale.py`、模型选择与相关界面:模型选项和最终倍率。
|
||||
- 生产、调试、透明图片和多帧处理相关测试。运行时沿用 ONNX Runtime;PyTorch 等仅用于离线导出与数值验证。
|
||||
@@ -0,0 +1,74 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Unified two-times output
|
||||
|
||||
系统 SHALL 对全部 Upscale 模型返回处理输入宽高各 2 倍的结果,并将该倍率用于界面说明和尺寸计算;处理输入为应用已有最大输入尺寸限制后的图像。
|
||||
|
||||
#### Scenario: Existing models produce two-times images
|
||||
- **WHEN** 任一现有模型处理 W×H 输入
|
||||
- **THEN** 系统先得到原生 4W×4H 结果,再使用 Lanczos 缩小到 2W×2H
|
||||
- **AND** 使用已有 4× 权重
|
||||
|
||||
#### Scenario: Repeated upscale
|
||||
- **WHEN** 用户对一次 2× 结果再次执行 Upscale 且输入尺寸仍满足限制
|
||||
- **THEN** 新结果宽高各为本次输入的 2 倍,即最初输入的 4 倍
|
||||
|
||||
### Requirement: Fixed PiSA-SR preset
|
||||
|
||||
系统 SHALL 提供 `PISA_SR` 模型选项,使用固定像素强度 1.0、语义强度 0.7、空提示词和 seed 42,通过 ONNX Runtime 生成 2× 结果。现有默认模型 SHALL 保持有效。
|
||||
|
||||
#### Scenario: Selected preset is executed
|
||||
- **WHEN** 用户选择 PiSA-SR 并执行 Upscale
|
||||
- **THEN** 模型对同一 latent 的预测按 `pixel + 0.7 * (full - pixel)` 组合
|
||||
- **AND** 输入插值、采样、解码与颜色校正遵循已验证的实验基线
|
||||
|
||||
#### Scenario: Repeatable inference
|
||||
- **WHEN** 相同图像在相同运行环境重复执行 PiSA-SR
|
||||
- **THEN** 系统使用相同噪声和处理规则,结果在该设备的数值容差内一致
|
||||
|
||||
### Requirement: Arbitrary dimensions and alpha preservation
|
||||
|
||||
系统 SHALL 支持合法输入范围内的小图、非方形图、奇数尺寸和跨 tile 图像,输出精确 2× 尺寸;有 alpha 的生产输入 SHALL 保留由源 alpha 插值而来的透明度。
|
||||
|
||||
#### Scenario: Tiled PiSA-SR image
|
||||
- **WHEN** 输入需要多个 PiSA-SR tile
|
||||
- **THEN** 系统融合重叠区域并裁去填充,输出无多余边缘的 2W×2H 图像
|
||||
- **AND** 连续纹理与渐变测试图的 tile 边界不出现可辨识拼接线
|
||||
|
||||
#### Scenario: Transparent input
|
||||
- **WHEN** 输入包含全透明和半透明像素
|
||||
- **THEN** 输出 alpha 等于源 alpha 直接 Lanczos 放大到最终尺寸的结果
|
||||
- **AND** RGB 生成过程不生成或覆盖 alpha
|
||||
|
||||
### Requirement: Model readiness and resource lifecycle
|
||||
|
||||
系统 SHALL 对 PiSA-SR 全部必需资产完成下载及校验后才判定就绪,按所选设备创建管线;其模型切换、取消、资源错误和显式清理 SHALL 覆盖全部组件。
|
||||
|
||||
#### Scenario: Incomplete model package
|
||||
- **WHEN** PiSA-SR 任一必需文件缺失或校验失败
|
||||
- **THEN** 系统提示准备模型并阻止使用不完整管线进行推理
|
||||
|
||||
#### Scenario: Bounded UNet residency
|
||||
- **WHEN** PiSA-SR 执行像素与完整预测阶段
|
||||
- **THEN** GPU 同时常驻的 UNet 至多一套,阶段间中间数据被保留以完成 0.7 组合
|
||||
|
||||
#### Scenario: Cancel or resource failure
|
||||
- **WHEN** 推理被取消或发生资源错误
|
||||
- **THEN** 系统停止后续阶段并清理任务中间文件
|
||||
- **AND** 资源错误释放整个 PiSA-SR 管线并报告可理解的失败原因
|
||||
|
||||
#### Scenario: Unsupported device
|
||||
- **WHEN** 所选设备无法执行已分发的 PiSA-SR 资产
|
||||
- **THEN** 系统报告明确的设备支持原因或执行已验证并明确呈现的回退路径
|
||||
|
||||
### Requirement: Consistent job behavior
|
||||
|
||||
系统 SHALL 在生产与调试任务采用同一模型预设和最终倍率,并对多帧输入逐帧应用该约定。
|
||||
|
||||
#### Scenario: Multiple frames
|
||||
- **WHEN** Upscale 处理多帧输入
|
||||
- **THEN** 每帧输出均为其处理输入的 2× 尺寸,帧顺序正确且阶段之间可以取消
|
||||
|
||||
#### Scenario: Debug matches production
|
||||
- **WHEN** 相同 RGB 输入、模型和设备分别通过生产与 Debug 入口处理
|
||||
- **THEN** 两者的 RGB 处理规则和最终尺寸一致
|
||||
@@ -0,0 +1,26 @@
|
||||
## 1. PiSA-SR 资产与数值基线
|
||||
|
||||
- [x] 1.1 将实验的固定来源、revision、LoRA 校验信息与离线依赖纳入正式导出工具,核对分发条款并实现四个 ONNX 组件的可复现导出。
|
||||
- [x] 1.2 固化 0.7 预测组合、seed 42 采样、空提示词、插值、量化和 AdaIN 规则;对两张样图验证 PyTorch 与 ONNX 0.7 的最终 RGB 平均绝对误差不超过 1 个 8-bit 灰度级,并目视确认细节。
|
||||
- [x] 1.3 验证 DirectML、CUDA 及 CPU/CoreML 的实际支持路径,确定精度资产与设备支持信息,记录不可用路径的明确处理方式。
|
||||
|
||||
## 2. 分块与资源管理
|
||||
|
||||
- [x] 2.1 实现 PiSA-SR 独立 ONNX 适配器,复用共享运行时,补齐 DirectML Session 配置并覆盖其他模型配置回归。
|
||||
- [x] 2.2 实现编码、像素预测、完整预测、解码的分阶段调度,GPU 至多常驻一套 UNet;覆盖阶段切换、中间数据保存及临时文件清理。
|
||||
- [x] 2.3 实现固定尺寸重叠分块、共享坐标噪声、加权拼接与最终裁切;用单 tile 基线、奇数尺寸、小图、渐变和跨边界纹理确定重叠参数并确认无可辨识接缝。
|
||||
- [x] 2.4 接入模型管理器的管线缓存、切换、Clear Models、取消与 OOM 释放;验证完整任务各组件的最后使用点和多帧行为。
|
||||
- [x] 2.5 实测 0.7 的 512、1024 和多 tile 输出,记录加载时间、端到端冷暖耗时、连续帧耗时及峰值显存/主机内存,确认阶段调度消除双 UNet 常驻。
|
||||
|
||||
## 3. 模型选项与统一倍率
|
||||
|
||||
- [x] 3.1 将最终 PiSA-SR 文件及 SHA256、尺寸、来源加入 catalog 和模型同步工具,覆盖多文件下载、损坏校验与就绪判定。
|
||||
- [x] 3.2 将三个旧模型的完整原生 4× 输出以 Lanczos 缩至 2×,保留原生 shape 校验;测试结果与先完整 4× 再缩半的基线一致。
|
||||
- [x] 3.3 在公共 Upscale 入口按模型分派,统一输入限制及最终 alpha 回填,覆盖生产与 Debug 两种路径。
|
||||
- [x] 3.4 新增 PiSA-SR 选项和 0.7 说明,更新 Operator 最终倍率、尺寸计算及所有相关 4× 行为提示,保留现有模型 key 与默认选择。
|
||||
|
||||
## 4. 验收
|
||||
|
||||
- [x] 4.1 覆盖四模型最终尺寸、连续两次 2×、输入限制、透明度、帧顺序、取消与资源错误的必要回归测试。
|
||||
- [x] 4.2 在 Blender 中验证模型选择、准备模型、图片替换与撤销,对样图、文字、半透明边缘和短序列目视检查细节、接缝及闪动。
|
||||
- [x] 4.3 运行相关测试及 `uv run pytest`;依赖或 Manifest 如有变更,核对锁文件与打包测试。记录实际设备验证范围及未解决问题。
|
||||
@@ -0,0 +1,32 @@
|
||||
## 实施结果
|
||||
|
||||
四个 Upscale 选项统一输出 2×。PiSA-SR 固定像素强度 1.0、语义强度 0.7;旧模型完成原生 4× 拼接后使用 Lanczos 缩半。默认模型与现有 key 保持有效。
|
||||
|
||||
PiSA-SR 使用 512 输出 tile、128 像素重叠。固定 seed 42 的 64×64 latent 噪声作为 noise.npy 分发,按整图坐标周期索引,重叠区域使用相同噪声。中间 latent 与像素预测存入任务临时目录,GPU 同时只加载一个组件;最终颜色校正在整图拼接后执行。
|
||||
|
||||
正式导出器从固定源码、基础权重和 LoRA 重新生成的四个 ONNX 文件与实验图逐文件 SHA256 相同。发布资产另外写入许可和修改元数据,并随包提供 Apache 2.0、CreativeML Open RAIL++-M 与 NOTICE。最终 8 个文件已上传 R2;远端大小核对零差异,应用实际下载函数完成 noise.npy 的下载与 SHA256 校验。
|
||||
|
||||
## 验证
|
||||
|
||||
- `uv run --no-sync pytest -q`:680 passed、2 xfailed、60 subtests passed。
|
||||
- 两张 0.7 样图,DirectML 与 PyTorch 最终成品平均绝对差异分别为 0.693、0.596 个 8-bit 灰度级;CPU 为 0.257、0.569。
|
||||
- DirectML 测试覆盖 1024×1024、1114×1518、1026×514 和 2×6 输出;完整主体、渐变的目视检查未见明显拼接线。
|
||||
- 三帧轻微平移、文字和半透明边缘通过生产入口验证;alpha 与源 alpha 直接 Lanczos 放大完全一致。细纹理随帧有变化,文字出现生成型模型的笔画变化。
|
||||
- Blender 4.5.3 实际 Python 接口验证四个模型枚举、Operator 注册、结果尺寸与图片替换;真实 Undo 测试恢复原图尺寸和像素。窗口菜单未通过鼠标操作,菜单与请求参数由接口测试覆盖。
|
||||
|
||||
## 速度与设备
|
||||
|
||||
RTX 5070 Ti、其他 GPU 应用同时运行。计时包含阶段加载、采样、拼接和颜色校正;不含模型下载。
|
||||
|
||||
| DirectML 输出 | 完整耗时 |
|
||||
| --- | --- |
|
||||
| 512 首次 | 5.24 秒 |
|
||||
| 512 重复 | 4.67 秒 |
|
||||
| 1024 | 5.55 秒 |
|
||||
| 三帧逐帧 | 每帧 4.79–5.05 秒 |
|
||||
|
||||
采样进程 RSS 峰值约 437 MiB;显卡总占用从约 7985 MiB 到峰值 11930 MiB,包含其他应用,不能视作本进程独占显存。阶段加载约占单张 512 耗时的大部分。
|
||||
|
||||
DirectML 和 CPU 完整出图。独立 CUDA ONNX Runtime 1.24.4 配合 torch 2.7.1 的 cuDNN 9.7.1 在本机 VAE 卷积上报 `No valid engine configs for ConvFwd`;该组合未通过 CUDA 验收,运行错误明确提示改选 DirectML 或 CPU,关闭 Session 的运行中隐式回退。CoreML 无可用测试设备,当前 PiSA-SR 明确提示选择 CPU。Windows 的既有生产路径使用 DirectML。
|
||||
|
||||
实验日志、输出图片和各阶段计时在 `build/upscale-explore/pisa-research/`。项目运行时依赖与 Manifest 未修改。
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-08
|
||||
@@ -0,0 +1,78 @@
|
||||
## Context
|
||||
|
||||
普通 Plane 的 `create_mesh_plane_mesh()` 创建中心四边形和完整 UV,`O Mesh Plane` 负责矩形细分。现有 Depth Plane 使用固定底面的浮雕置换;Depth Cutout 的曲面分支使用相机 XYZ 投影。整图 Job 已通过通用 Artifact 层执行一次 MoGe-2 推理,该层支持 VECTOR Depth。
|
||||
|
||||
Cutout 的双击入口集中在 `SelectCutoutSelection.invoke()`、`full_image` Property 和 WorkspaceTool keymap。工作区中的 Cutout Shape 重组仍在进行,本提案以当前文件中的实际实现为基线。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
本次目标是建立独立的整图 Depth Surface 转换,并让 Cutout 通过套索提交区域。Depth Surface 的用户控制固定为 Subdivide、Thickness、Depth Scale、Reference Depth、Normal Smooth。
|
||||
|
||||
## Decisions
|
||||
|
||||
### 入口与命名
|
||||
|
||||
采用 `ConvertToDepthSurface`、`anyimage.convert_to_depth_surface`、界面文字 **Convert to Depth Surface** 和资产名 `O Image Depth Surface`,表达输出是相机投影曲面。菜单中的顺序为 Plane、Depth Plane、Depth Surface,现有 Depth Plane 继续承担固定底面浮雕。
|
||||
|
||||
Cutout 直接使用共享拖动 keymap,删除双击整图绑定、`full_image` 声明和完整矩形 Selection 构造代码;`invoke()` 仅记录 Pie Event 后启动共享手势。清理因此失去用途的 import 和测试夹具。
|
||||
|
||||
### 整图生成主链
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Image[Image Empty] --> Convert[Convert to Depth Surface]
|
||||
Convert --> Check[静态图片与 AI 环境检查]
|
||||
Check --> Job[整图 Depth Job:VECTOR 模式]
|
||||
Job --> Prediction[一次 MoGe-2 推理]
|
||||
Prediction --> Files[完整 Color、vector-depth.exr、depth.json、tangent-normal.png]
|
||||
Files --> Load[主线程加载并 Pack 深度与法线]
|
||||
Load --> Mesh[共用矩形 Mesh、中心矩阵与材质]
|
||||
Mesh --> Modifier[加载 O Image Depth Surface]
|
||||
Modifier --> Result[替换源 Empty 并创建一个 Undo 步骤]
|
||||
```
|
||||
|
||||
在现有整图 Job 上增加明确的深度产物选择,Depth Plane 使用 Z,Depth Surface 使用 VECTOR;两者均从一次预测生成 Tangent Normal。Blender 侧将转换类型随请求保存至响应阶段,据此选择节点组和结果标识。复用 `common` 的图片加载、深度尺度、材质与对象替换函数;Server 只引用自身目录内的共享 Artifact 层。
|
||||
|
||||
### 基础网格与节点接口
|
||||
|
||||
Depth Surface 使用 `create_mesh_plane_mesh()` 和 `build_mesh_plane_matrix()`,保持整张图片的矩形边界、中心原点、UV 和材质槽。节点内部调用 `O Mesh Plane`,以 Thickness 0 生成基础曲面;同一 Subdivide 下,它与普通 Plane 的零厚度网格具有相同顶点、面连接与 UV。Alpha 参与材质显示,模型 Validity 用于数据有效性处理,几何始终覆盖完整矩形。
|
||||
|
||||
| 输入 | 默认值 | 语义 |
|
||||
| --- | --- | --- |
|
||||
| Subdivide | 6,范围 0–10 | 沿用 O Mesh Plane 的细分 |
|
||||
| Thickness | 0,最小 0 | 均匀曲面壳厚度,DISTANCE |
|
||||
| Depth Scale | 1,最小 0 | 平面到相机曲面的缩放,可大于 1 |
|
||||
| Normal Smooth | 50,范围 0–50 | 厚度方向的法线平滑次数,放在 Options |
|
||||
| Reference Depth | Metadata reference_depth × Uniform Scale | 局部深度基准,DISTANCE,放在 Options |
|
||||
|
||||
Geometry 为结构输入;Depth Image 与 Uniform Scale 为隐藏数据输入。所有修改器输入使用 SINGLE。
|
||||
|
||||
### 投影与厚度
|
||||
|
||||
沿用 Depth Cutout 的 VECTOR Depth 坐标约定:将采样相机 XYZ 乘以 Uniform Scale,翻转 Y 得到局部 XY,局部 Z 为 `(Reference Depth − scaled_camera_z) × Depth Scale`。局部 XY 从基础矩形位置按 Depth Scale 插值到缩放后的相机 XY;0 为原平面,1 为完整投影,大于 1 为外推。Reference Depth 使用与采样相同的局部单位。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Base[O Mesh Plane 零厚度矩形细分] --> Projection[相机 XY 插值与相对 Z 深度]
|
||||
Texture[VECTOR Depth × Uniform Scale] --> Projection
|
||||
Projection --> Thickness{Thickness}
|
||||
Thickness -- 0 --> Surface[单层曲面]
|
||||
Thickness -- 大于 0 --> Shell[沿平滑法线向内生成均匀壳并连接外边界]
|
||||
Surface --> Output[保留 UV 与材质,清理临时属性]
|
||||
Shell --> Output
|
||||
```
|
||||
|
||||
提取现有曲面分支中可复用的投影、均匀厚度节点构建函数,按业务职责集中在共用构建模块;新组只组合自身需要的步骤。零厚度保持单层矩形拓扑,正厚度保留投影正面、沿内侧扩展背面并连接四周。厚度采用正面 POINT 法线,按 Normal Smooth 迭代次数进行邻域平滑后归一化,使对应正背面顶点之间的距离等于 Thickness。
|
||||
|
||||
### 验证边界
|
||||
|
||||
以合成相机 XYZ 纹理验证投影和坐标方向,用相同 Subdivide 的普通 Plane 对照基础拓扑与 UV;使用非方图、偏移 Image Empty、旋转缩放矩阵验证定位。检查五项可见参数、隐藏数据输入、SINGLE、材质槽和临时属性清理。
|
||||
|
||||
节点布局按实际 Blender 绘制尺寸检查总览和局部,几何主链连续、计算支线就近接入。实现阶段按项目总则只运行测试;资产构建、资产视觉验收和节点资产文档同步作为后续发布准备,当前提案不执行构建。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- 相机纹理的 Y 方向或中心对齐错误会使结果镜像、偏移 → 使用带非中心主点的合成相机数据验证,并保持与现有 Depth Cutout 相同的投影公式。
|
||||
- 深度不连续处的矩形面会被拉伸 → 将连续矩形拓扑作为明确输出约定,用阶跃深度验证连接关系稳定。
|
||||
- 新增类型和共享 Job 分支可能影响现有转换 → 检查注册与逆序注销、Z/VECTOR 产物选择、单次推理及 Undo 回归。
|
||||
- 工作区同时调整 Cutout 节点 → 实施前核对当前构建函数和 Shape 设置,在现有修改基础上提取共用步骤。
|
||||
@@ -0,0 +1,30 @@
|
||||
## Why
|
||||
|
||||
整图相机投影曲面需要与普通 Plane 一样稳定的矩形基础网格,以及少量直接的深度控制。将它作为独立转换入口,也让 Cutout 的交互集中在用户圈选区域。
|
||||
|
||||
## What Changes
|
||||
|
||||
- **BREAKING**:完全删除 Cutout 双击整图生成入口,包括快捷键、`full_image` 属性和整图 Selection 构造分支。
|
||||
- 新增 **Convert to Depth Surface**,与 Convert to Plane、Convert to Depth Plane 并列,用完整图片生成相机投影深度曲面。
|
||||
- 新增 `O Image Depth Surface`:基础网格复用普通 Plane 的矩形、UV、中心原点和 `O Mesh Plane` 细分,只公开 `Subdivide`、`Thickness`、`Depth Scale`、`Reference Depth` 四项用户参数。
|
||||
- 相机投影沿用 Depth Cutout 的坐标与深度缩放语义;厚度采用均匀曲面壳,默认零厚度。
|
||||
- 整图转换复用现有 MoGe-2 单次推理、材质与对象替换流程。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `projected-depth-surface`: 整图 Depth Surface 转换、矩形基础网格、相机投影和精简节点接口。
|
||||
- `cutout-lasso-entry`: Cutout 通过有效套索进入 Shape 选择,彻底移除双击整图入口。
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
无。当前 `openspec/specs/` 尚无已归档规范;本次独立规定新增及调整后的行为。
|
||||
|
||||
## Impact
|
||||
|
||||
- `operators/cutout_tool/main.py` 及其交互测试。
|
||||
- `operators/convert_to_plane/`、`menu.py`、`__init__.py` 的转换入口、结果创建与类型注册。
|
||||
- `common/depth.py` 等共用深度数据处理;按需扩展整图 Job 的产物选择,保持后端模块边界。
|
||||
- `tools/node_assets/` 的构建和验证、新节点组及节点测试;实施阶段按项目约束执行测试,节点资产构建和产物更新单独安排。
|
||||
- 无新增第三方依赖;实现以当前工作区为基线,衔接正在进行的 Cutout Shape 调整。
|
||||
@@ -0,0 +1,23 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Cutout starts shape generation from a lasso
|
||||
Cutout SHALL 在用户提交有效套索后打开 Shape Pie Menu,并将圈选路径传给 Shape 生成入口。
|
||||
|
||||
#### Scenario: Submit a lasso
|
||||
- **WHEN** 用户在 active Image Empty 上完成有效 Cutout 套索
|
||||
- **THEN** 系统打开当前可用 Shape 菜单并传递该套索路径
|
||||
|
||||
### Requirement: Full-image double-click entry is removed
|
||||
系统 MUST 删除 Cutout 的双击整图 keymap、`full_image` Property、整图 Selection 构造分支及专属调用代码。双击 MUST NOT 打开整图 Shape 菜单或提交整图生成任务。
|
||||
|
||||
#### Scenario: Double-click an image
|
||||
- **WHEN** Cutout 已激活,用户在 Image Empty 上双击且未形成有效套索
|
||||
- **THEN** 不打开 Shape 菜单、不创建对象、不提交生成 Job
|
||||
|
||||
#### Scenario: Double-click viewport space
|
||||
- **WHEN** active Image Empty 存在,用户在图片外空白区域双击
|
||||
- **THEN** 不启动整图 Cutout,选择处理沿用共享图片手势规则
|
||||
|
||||
#### Scenario: Inspect registration
|
||||
- **WHEN** 检查 Cutout 的 Operator Property 和 WorkspaceTool keymap
|
||||
- **THEN** 不存在 `full_image` 或用于整图生成的 `DOUBLE_CLICK` 绑定
|
||||
@@ -0,0 +1,85 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Full-image depth surface has a conversion entry
|
||||
系统 SHALL 提供 `anyimage.convert_to_depth_surface`,界面显示 Convert to Depth Surface,与 Plane、Depth Plane 并列;该入口 SHALL 从当前静态 Image Empty 创建完整图片的相机投影曲面。
|
||||
|
||||
#### Scenario: Convert a still image
|
||||
- **WHEN** 当前 Image Empty 是静态图片且 AI 环境可用
|
||||
- **THEN** 执行一次完整图片转换,成功后结果沿用源对象名称并替换源 Empty
|
||||
- **AND** 一次 Undo 恢复转换前状态
|
||||
|
||||
#### Scenario: Reject unsupported media
|
||||
- **WHEN** 输入为多帧 Movie 或 Sequence
|
||||
- **THEN** 提示仅支持静态图片,不提交推理任务
|
||||
|
||||
#### Scenario: AI setup is required
|
||||
- **WHEN** AI 环境未就绪
|
||||
- **THEN** 沿用现有 AI 设置流程,源 Empty 保持可用
|
||||
|
||||
### Requirement: Base geometry matches Plane conversion
|
||||
Depth Surface SHALL 使用普通 Convert to Plane 的基础 Mesh 创建、中心矩阵、UV 和 O Mesh Plane 细分。相同 Subdivide 下投影前零厚度几何 MUST 与普通 Plane 的零厚度几何一致。整图矩形拓扑 SHALL 覆盖透明区域及深度跳变区域。
|
||||
|
||||
#### Scenario: Compare planar meshes
|
||||
- **WHEN** 对同一图片以相同 Subdivide 创建普通 Plane 和 Thickness 0、Depth Scale 0 的 Depth Surface
|
||||
- **THEN** 两者顶点位置、面连接、UV 和材质索引一致
|
||||
|
||||
#### Scenario: Preserve placement
|
||||
- **WHEN** 源 Image Empty 具有非中心显示偏移以及平移、旋转、缩放
|
||||
- **THEN** Depth Scale 0、Thickness 0 的结果四角与源显示矩形的世界空间四角一致
|
||||
- **AND** 对象原点位于显示矩形中心
|
||||
|
||||
#### Scenario: Preserve rectangular coverage
|
||||
- **WHEN** 图片包含透明孔洞或纹理深度跳变
|
||||
- **THEN** 基础网格仍为完整矩形细分,Alpha 由材质表达
|
||||
|
||||
### Requirement: Surface controls include normal smoothing
|
||||
`O Image Depth Surface` SHALL 只公开 Subdivide、Thickness、Depth Scale、Reference Depth 以及 Normal Smooth 五项用户控制。默认值 SHALL 分别为 6、0、1 和 Metadata reference_depth 乘以 Uniform Scale。Geometry SHALL 为结构输入,Depth Image 与 Uniform Scale SHALL 为隐藏数据输入。输入 MUST 使用 SINGLE;Thickness、Reference Depth MUST 使用 DISTANCE。
|
||||
|
||||
#### Scenario: Inspect a new modifier
|
||||
- **WHEN** 创建 Depth Surface 修改器
|
||||
- **THEN** 用户看到规定的五项控制,Reference Depth 与 Normal Smooth 位于 Options;Normal Smooth 默认 50,范围 0–50
|
||||
- **AND** 不出现 Cutout 的 Mode、Split、Balloon、Inflation、轮廓或轴向控制
|
||||
|
||||
### Requirement: Geometry follows camera projection
|
||||
Depth Surface SHALL 按现有 Depth Cutout 的相机 XYZ 坐标约定投影。采样 XYZ 乘以 Uniform Scale 后,XY SHALL 以 Depth Scale 从基础平面插值至相机 X、负相机 Y;Z SHALL 为 `(Reference Depth − scaled_camera_z) × Depth Scale`。
|
||||
|
||||
#### Scenario: Full projection
|
||||
- **WHEN** Thickness 为 0、Depth Scale 为 1,输入合成相机 XYZ 纹理
|
||||
- **THEN** 每个顶点的位置符合相机投影公式,包括 Y 方向与深度基准
|
||||
|
||||
#### Scenario: Adjust depth strength
|
||||
- **WHEN** Depth Scale 分别为 0、0.5、1 和大于 1 的值
|
||||
- **THEN** XY 与 Z 按同一缩放语义连续变化,0 恢复基础平面,大于 1 正常外推
|
||||
|
||||
#### Scenario: Adjust reference depth
|
||||
- **WHEN** Reference Depth 增加局部距离 d,Depth Scale 为 s
|
||||
- **THEN** 曲面 Z 增加 d × s,投影 XY 保持不变
|
||||
|
||||
### Requirement: Thickness forms a uniform surface shell
|
||||
Thickness 为 0 时系统 SHALL 输出单层曲面;Thickness 大于 0 时 SHALL 保留投影正面,按正面 POINT 法线按 Normal Smooth 进行邻域平滑并归一化后的方向向内生成背面并连接矩形边界。
|
||||
|
||||
#### Scenario: Zero thickness
|
||||
- **WHEN** Thickness 为 0
|
||||
- **THEN** 仅存在一层曲面,无重复背面或退化侧壁
|
||||
|
||||
#### Scenario: Positive thickness
|
||||
- **WHEN** 对平坦深度输入设置正 Thickness
|
||||
- **THEN** 对应正背面顶点之间的距离等于 Thickness,边界闭合且法线朝向一致
|
||||
|
||||
### Requirement: One prediction produces complete image artifacts
|
||||
整图 Depth Surface SHALL 通过共用 Artifact 层执行一次 MoGe-2 推理,生成 VECTOR Depth、Metadata 与 Tangent Normal,并保留完整 Color。Blender SHALL 在主线程加载、Pack 并设置图片色彩空间,将 Normal 用于材质,将 VECTOR Depth 用于几何。
|
||||
|
||||
#### Scenario: Complete a generation job
|
||||
- **WHEN** 整图 Depth Surface Job 成功
|
||||
- **THEN** 只执行一次推理,并返回 `vector_depth`、`depth_metadata`、`tangent_normal` 及完整颜色产物
|
||||
|
||||
#### Scenario: Preserve existing depth plane generation
|
||||
- **WHEN** 用户执行现有 Convert to Depth Plane
|
||||
- **THEN** 继续生成 Z Depth 与 Tangent Normal,并创建固定底面浮雕
|
||||
|
||||
### Requirement: Node assets preserve geometry data
|
||||
新节点组 SHALL 从 `assets/O_AnyImage.blend` 按名称加载。节点求值 SHALL 保留 UV 与第 0 个材质槽,并清理自身临时属性;构建验证 SHALL 检查接口、实际几何与 Blender 中的节点布局。
|
||||
|
||||
#### Scenario: Evaluate the asset
|
||||
- **WHEN** 加载并求值 `O Image Depth Surface`
|
||||
- **THEN** 结果保留 UV、用户属性和材质,不残留自身 `o_depth_*` 临时属性
|
||||
@@ -0,0 +1,31 @@
|
||||
## 1. 删除 Cutout 双击整图入口
|
||||
|
||||
- [x] 1.1 删除 Cutout 的 DOUBLE_CLICK 整图绑定、full_image Property、整图 Selection 分支及失去用途的 import,保留共享套索交互。
|
||||
- [x] 1.2 调整交互和注册测试,验证图片内外双击均不打开 Shape 菜单或提交任务,有效套索继续生成 Shape。
|
||||
|
||||
## 2. 新增整图 Depth Surface 转换
|
||||
|
||||
- [x] 2.1 增加 ConvertToDepthSurface、菜单入口和正逆序注册,验证静态图片、AI 环境及忙碌状态检查。
|
||||
- [x] 2.2 扩展共用整图 Job 的 Z/VECTOR 产物选择及 Blender 响应分派,保持普通 Depth Plane 的 Z 路径;验证 VECTOR Depth 和 Tangent Normal 来自一次推理。
|
||||
- [x] 2.3 复用 common 的图片、深度、材质及对象收尾函数创建 Depth Surface;共用 Plane 的基础 Mesh 和中心矩阵,初始化四项参数及隐藏深度数据。
|
||||
- [x] 2.4 验证完整颜色、材质槽、深度与法线 Pack、源对象替换、失败清理和单步 Undo。
|
||||
|
||||
## 3. 构建定义与几何验证
|
||||
|
||||
- [x] 3.1 按业务职责提取共用相机投影和均匀厚度构建步骤,新增 O Image Depth Surface 构建定义,并接入节点组构建和验证索引。
|
||||
- [x] 3.2 以 O Mesh Plane 的零厚度输出生成完整矩形基础网格,设置 Subdivide、Thickness、Depth Scale、Reference Depth 以及隐藏数据接口,检查 SINGLE 与 socket subtype。
|
||||
- [x] 3.3 增加合成纹理几何测试:同级 Plane 拓扑与 UV 对照、Depth Scale 0/0.5/1/>1、Reference Depth、相机 Y 方向和非中心主点。
|
||||
- [x] 3.4 增加零厚度单层、正厚度闭合、透明区域完整矩形、深度跳变连接、材质和临时属性清理验证。
|
||||
- [x] 3.5 编排新节点组布局与验证入口,覆盖未用输出隐藏、主链及支线间距;记录发布准备时所需的实际绘制尺寸视觉验收项目。
|
||||
|
||||
## 4. 回归与交付检查
|
||||
|
||||
- [x] 4.1 运行相关转换、交互、注册、Server Job 和节点构建定义测试,修复回归;检查现有 Depth Plane 和圈选 Cutout 的行为。
|
||||
- [x] 4.2 运行 uv run pytest,记录测试结果及需要 Blender 或已构建新资产才能完成的验收项。按项目总则,代码实施只运行测试,资产构建、二进制产物和节点资产文档同步留给发布准备。
|
||||
|
||||
|
||||
发布准备验收:构建节点资产后,在 Blender 节点编辑器按实际绘制尺寸检查新组总览、投影支线、厚度分支及旁路;检查修改器四项参数的实际显示,并对已发布资产运行几何验证。本次不生成资产或文档产物。
|
||||
|
||||
测试记录:全量测试两次均为 723 passed、2 xfailed、62 subtests passed;显式调用 pytest.main 返回 0。两次完整进程均在 pytest 返回后以退出码 1 结束,退出阶段异常尚未定位。新曲面几何、接口、对象定位及失败清理测试全部通过。OpenSpec strict 校验通过。
|
||||
|
||||
资产更新记录:已按用户要求运行 build_node --skip-tests,Blender 4.5.10 LTS 构建及三项资产验证均成功。更新后资产与转换回归 53 passed;构建定义和磁盘发布资产使用相同几何用例验证,31 passed,命令退出码均为 0。已同步节点资产说明。Computer Use 应用授权超时,实际节点编辑器视觉验收仍待完成。
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-14
|
||||
@@ -0,0 +1,77 @@
|
||||
## Context
|
||||
|
||||
`O Image Depth Balloon` 当前以原始 Cutout 网格作为对称平面上的轮廓,通过 `o_balloon` 和深度差构建两侧。新需求已经用用户提供的小车图完成独立实验:正面投射后截留正 Z 区域,两侧围绕固定 XY 平面镜像,并以直壁连接对应外边界。
|
||||
|
||||
实验文件位于 `C:/Users/icrdr/AppData/Local/Temp/anyimage-car-symmetry/`:`car-depth-balloon-fast-modes.blend`、`fast_balloon.py`、`performance-results.json`、`mode-validation.json`。这些是可复核的实验材料,正式实现和测试自行提供所需资源。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:** 双模式、手势预设、完整对称网格、独立且可复用的节点构建、统一命名。
|
||||
|
||||
**Non-Goals:** 本变更不包含旧资产兼容层、AI 模型调整或自动推断最佳车身对称方向。
|
||||
|
||||
## Decisions
|
||||
|
||||
### 名称与手势
|
||||
|
||||
| 对象 | 名称 |
|
||||
|---|---|
|
||||
| 功能与菜单 | `Depth Symmetry` |
|
||||
| 节点组 | `O Image Depth Symmetry` |
|
||||
| 构建模块与入口 | `image_depth_symmetry.py` / `build_image_depth_symmetry_group()` |
|
||||
| Shape 标识 | `DEPTH_SYMMETRY` |
|
||||
| Mode 顺序 | `Balloon`、`Projected` |
|
||||
|
||||
`Balloon` 描述原有鼓起方式;`Projected` 描述新模式的投射轮廓。功能名称表达两者共同的对称职责,避免用其中一个模式命名整个节点组。
|
||||
|
||||
| 手势 | Depth Cutout(现有) | Depth Symmetry |
|
||||
|---|---|---|
|
||||
| Lasso | Balloon | Balloon |
|
||||
| Polyline | Shell | Projected |
|
||||
|
||||
在 Cutout 对象创建阶段按 gesture 设置 Mode。预设只影响初值,用户仍可直接切换节点 Mode。裸节点默认 Balloon,保持原有入口的默认行为。
|
||||
|
||||
### 直接构建 Projected 正面
|
||||
|
||||
复用 `_position_field` 读取 Depth Image 的相机 XYZ,乘以 Uniform Scale,将 Y 转为现有 Cutout 坐标约定,以 `Reference Depth − metric depth` 构建 Z。Depth Scale 沿用已验证的平面坐标与相机坐标插值,并缩放深度差。
|
||||
|
||||
将 Depth Direction 对应的方向对齐到局部 Z 轴,输出成形坐标中的正面。Reference Depth 和 Depth Direction 共同改变位移与投射后的轮廓;最终镜像面固定在成形坐标 `Z=0`,不把镜像结果转回倾斜平面。既有 Depth Axis 作为统一的最终坐标变换保留。
|
||||
|
||||
复用公共边界平滑,随后在 POINT 域判断负 Z,并在 FACE 域判断是否包含越界顶点。删除包含越界顶点的整面及其失去面的边线、孤点。仅保留正 Z 面,不产生压扁在对称平面上的外围裙边。平面附近使用与几何尺度相适应的小容差处理零厚度退化。
|
||||
|
||||
采用整面删除,因为它只依赖现有网格与字段求值;相较精确裁切,边界具有网格分辨率级别的近似。新分支不使用 Mesh Boolean,也不嵌套 Depth Plane、Depth Cutout 节点组。
|
||||
|
||||
### 镜像与直壁
|
||||
|
||||
先完成正面和平滑,再镜像到负 Z,避免封闭后的全网格平滑破坏直壁。以边的面邻接数识别正面外边界,将每个边界点 `(x,y,z)` 与 `(x,y,-z)` 连接;生成的侧壁沿 Z 轴延伸,其法线垂直于 Z 轴。保留正背面平滑着色,侧壁使用平面着色。
|
||||
|
||||
合并对应顶点,正确处理对称平面上的零厚度边。Double Sided 打开时生成完整封闭网格;关闭时沿用已验证原型的正面预览行为。Balloon 分支原有 Double Sided、Thickness、平滑等语义保持不变。Thickness 继续控制 Balloon 轮廓;Projected 的形体由 Depth Scale、Reference Depth、Depth Direction 控制,不引入未经验证的厚度推断。
|
||||
|
||||
### 复用与模式分派
|
||||
|
||||
复用公共节点接口、深度采样、岛清理、边界平滑、普通平滑和属性函数。节点内部保留直接构建的两条几何分支,通过 Mode 选择;共享最终输出和坐标约定,不通过嵌套节点组包装旧实现。
|
||||
|
||||
Projected 需要局部辅助函数时放在该业务模块;只有确有其他消费者的函数才进入 common。UV 和现有命名属性继续随拓扑传递,新增临时属性需要明确生命周期;源码与保存资产中 Capture Attribute 数量均为零。
|
||||
|
||||
### 性能与验证
|
||||
|
||||
同一小车输入、相同参数与 3 次边界平滑,预热 4 次后连续修改 Reference Depth,记录 24 次依赖图求值的中位数。外部实验的双模式 Projected 分支结果如下:
|
||||
|
||||
| 输入顶点 | 布尔原型 | 双模式直接构建 | 提速 |
|
||||
|---|---:|---:|---:|
|
||||
| 27,860 | 174.9 ms | 30.9 ms | 约 5.7 倍 |
|
||||
| 165,771 | 960.7 ms | 135.6 ms | 约 7.1 倍 |
|
||||
|
||||
这些数值是当前机器的对比证据,不作为跨机器测试断言。正式验证保留同输入、同平滑、同参数的对照,并分别记录 Reference Depth 与 Depth Direction 调整耗时。几何测试覆盖封闭性、顶点流形性、面绕序、XY 镜像、直壁法线、越界删除、空结果及模式切换;Balloon 分支与变更前的基准几何比较。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- 整面删除使切口随网格密度离散变化 → 使用已有采样密度控制,比较多个密度与连续参数变化,明确其为近似边界。
|
||||
- 深度噪声可能形成小岛或点接触 → 复用岛清理,测试连通性与顶点流形性,退化处删除无效拓扑。
|
||||
- 在补壁后平滑可能令侧壁弯曲 → Projected 的平滑在正面阶段完成,再镜像和补壁。
|
||||
- 只在中等网格测量可能掩盖扩展成本 → 同时验证普通与加密输入,记录求值而非渲染耗时。
|
||||
- 资产与代码名称失配 → 同步重命名 Shape 标识、模块、校准函数、构建/校验清单和界面文本,构建保存资产后检查旧引用。
|
||||
|
||||
## Migration Plan
|
||||
|
||||
实现时整体替换旧名称并更新相关代码、测试和节点资产,不保留旧路径或别名。通过 `uv run node-group build` 构建并验证 `O_AnyImage.blend`,检查新组存在、旧组不再由构建产出。已保存在用户工程中的旧节点组不执行自动扫描或重写。
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user