修正自动切分素材检查条件

移除自动切分对预先导入独立素材的错误要求。
统一自动切分前置与结果检查,并补齐图片和字体引用校验。
在新建和重新开始自动切分时执行前置检查,恢复流程继续尽力完成。
This commit is contained in:
2026-09-10 18:43:31 +08:00
parent 1509235c1b
commit 74f726e65d
4 changed files with 62 additions and 59 deletions
@@ -72,7 +72,7 @@ export function validateComponentRecognitionPrerequisites(
return issues;
}
export function validateAssetRecognitionPrerequisites(
export function validateAssetSeparationPrerequisites(
state: State,
): UiEditorPrerequisiteIssue[] {
const issues = validateComponentRecognitionPrerequisites(state);
@@ -91,58 +91,9 @@ export function validateAssetRecognitionPrerequisites(
});
}
}
if (Object.keys(state.sprite_assets).length === 0) {
issues.push({
code: 'missing-sprite-assets',
message: '请先导入独立素材',
});
}
return issues;
}
export function validateLayoutGenerationPrerequisites(
state: State,
): UiEditorPrerequisiteIssue[] {
const issues = validateAssetRecognitionPrerequisites(state);
const visit = (nodes: State['ui_trees'][number]['root']['children']) => {
for (const node of nodes) {
const component = node.component;
if (component) {
if (
'Image' in component &&
component.Image.target_graphic !== null &&
!(component.Image.target_graphic in state.sprite_assets)
) {
issues.push({
code: 'missing-target-graphic',
message: '图片组件引用的独立素材不存在',
resourceId: component.Image.target_graphic,
});
}
if ('Text' in component) {
const font = component.Text.font;
if (typeof font !== 'string' && !(font.Bound in state.font_assets)) {
issues.push({
code: 'missing-font',
message: '文本组件引用的字体不存在',
resourceId: font.Bound,
});
}
}
}
visit(node.children);
}
};
for (const tree of state.ui_trees) visit([tree.root]);
return issues;
}
export function validateLayoutReviewPrerequisites(
state: State,
): UiEditorPrerequisiteIssue[] {
return validateLayoutGenerationPrerequisites(state);
}
function layoutStatusIssue(
status: StageStatus,
): UiEditorPrerequisiteIssue | null {
@@ -214,7 +165,7 @@ export function validateStructureRecognitionResult(
return issues;
}
export function validateVisualBindingResult(
export function validateAssetSeparationResult(
state: State,
): UiEditorPrerequisiteIssue[] {
const issues: UiEditorPrerequisiteIssue[] = [];
@@ -225,6 +176,32 @@ export function validateVisualBindingResult(
}
const issue = componentStatusIssue(node.metadata.component_status);
if (issue) issues.push(issue);
const component = node.component;
if ('Image' in component) {
const targetGraphic = component.Image.target_graphic;
if (targetGraphic === null) {
issues.push({
code: 'missing-separated-image',
message: '图片组件尚未完成素材切分',
});
} else if (!(targetGraphic in state.sprite_assets)) {
issues.push({
code: 'missing-target-graphic',
message: '图片组件引用的切分素材不存在',
resourceId: targetGraphic,
});
}
}
if ('Text' in component) {
const font = component.Text.font;
if (typeof font !== 'string' && !(font.Bound in state.font_assets)) {
issues.push({
code: 'missing-font',
message: '文本组件引用的字体不存在',
resourceId: font.Bound,
});
}
}
});
}
return issues;
@@ -1,11 +1,10 @@
import {
type UiEditorPrerequisiteIssue,
validateAssetRecognitionPrerequisites,
validateAssetSeparationPrerequisites,
validateAssetSeparationResult,
validateComponentRecognitionPrerequisites,
validateLayoutReviewPrerequisites,
validateReferenceAnalysisResult,
validateStructureRecognitionResult,
validateVisualBindingResult,
} from '../../../features/ui-editor/requisites';
import type { State } from '../../../features/ui-editor/types/State';
import type { UiEditorStepId } from '../model';
@@ -22,7 +21,7 @@ export function prerequisiteIssuesForStep(
case 'structure-recognition':
return validateComponentRecognitionPrerequisites(state);
case 'asset-separation':
return validateAssetRecognitionPrerequisites(state);
return validateAssetSeparationPrerequisites(state);
}
}
@@ -36,7 +35,7 @@ export function postCheckIssuesForStep(
case 'structure-recognition':
return validateStructureRecognitionResult(state);
case 'asset-separation':
return validateVisualBindingResult(state);
return validateAssetSeparationResult(state);
}
}
@@ -46,7 +45,7 @@ export function postCheckIssuesForSave(
return [
...validateReferenceAnalysisResult(state),
...validateStructureRecognitionResult(state),
...validateVisualBindingResult(state),
...validateAssetSeparationResult(state),
];
}
@@ -58,8 +57,8 @@ export function activeStepPrerequisiteIssues(
case 'reference-analysis':
return validateComponentRecognitionPrerequisites(state);
case 'structure-recognition':
return validateAssetRecognitionPrerequisites(state);
return validateComponentRecognitionPrerequisites(state);
case 'asset-separation':
return validateLayoutReviewPrerequisites(state);
return validateAssetSeparationPrerequisites(state);
}
}
@@ -1241,6 +1241,19 @@ export function useUiEditorSession(
setSeparationRecovery(recovery);
return;
}
const prerequisiteIssues = prerequisiteIssuesForStep(
editor.state,
'asset-separation',
);
if (prerequisiteIssues.length > 0) {
reportWorkflowCompletion(
'asset-separation',
'failure',
prerequisiteIssues.map((issue) => issue.message).join(''),
setSeparationStatus,
);
return;
}
await runSeparationWorkflow();
} catch (cause) {
setSeparationStatus(
@@ -1257,6 +1270,19 @@ export function useUiEditorSession(
async function restartSeparation() {
if (!resourceId) return;
setSeparationRecovery(null);
const prerequisiteIssues = prerequisiteIssuesForStep(
editor.state,
'asset-separation',
);
if (prerequisiteIssues.length > 0) {
reportWorkflowCompletion(
'asset-separation',
'failure',
prerequisiteIssues.map((issue) => issue.message).join(''),
setSeparationStatus,
);
return;
}
try {
await invoke('discard_separation_recovery', {
projectPath,
@@ -85,6 +85,7 @@ Rust 在接收并校验工具结果后,才把这两个枚举变体映射为正
## 前端正式接入
- UI 编辑器点击“自动切分素材”时,前端只调用一次 `separate_ui`,消费完整 `SeparationDTO`separation 内部 batch 不向前端暴露,也不在 UI 中显示 batch 进度。
- 自动切分的前置检查只要求界面图及对应 UI tree 有效,不要求用户预先导入 SpriteAsset;结果检查负责报告未回填的 Image、丢失的切分素材引用、丢失的字体引用及组件审阅状态。新建和重新开始切分时执行前置检查,继续已有 sidecar 时直接按恢复状态尽力完成。
- 如果 sidecar 已存在未完成的 `state.json`,点击入口时先打开独立弹窗,由用户选择“继续上次自动切分素材”或“开始新的自动切分素材”。继续复用 sidecar 的 pending tree;重新开始只替换当前 `state.json`,不删除 sidecar 图片。
- `BoundNode.cut_image_path` 必须是项目根相对路径。前端使用现有 `import_local_project_image_assets` 登记 cut 图片;由于该通用命令单次最多 100 个路径,前端可以在资源登记阶段按 100 条分组调用,但这不属于 separation batch,也不向用户展示。
- 现有本地资源导入按清洗后的文件名 stem 与内容摘要生成目标路径;相同目标路径直接复用已有 manifest asset ID,内容不同则拒绝覆盖或生成不同摘要路径。前端不自行猜测 SpriteAsset 是否存在,也不从 NodeId 派生 SpriteAssetId。