取消画笔自动合并选中遮罩
- 移除画笔与选中 mask 连通或重叠时自动 union 到旧 mask 的逻辑,画笔每次松手都生成新的独立 mask。 - 更新画笔工具提示,明确画笔会生成新的独立 mask。 - 调整 CanvasArea 回归测试,验证画笔触碰选中 mask 时仍保留旧 mask 并新建独立 mask。 - 更新 README、AGENTS、前端审计、需求冻结和测试计划文档,移除画笔自动合并描述。
This commit is contained in:
@@ -1723,10 +1723,10 @@ describe('CanvasArea', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('merges a connected brush stroke into the selected mask', () => {
|
||||
it('creates an independent brush mask even when it touches the selected mask', () => {
|
||||
useStore.setState({
|
||||
activeTemplateId: '2',
|
||||
activeClass: { id: 'c1', name: '胆囊', color: '#ff0000', zIndex: 20 },
|
||||
activeClass: { id: 'c1', name: '胆囊', color: '#ff0000', zIndex: 20, maskId: 1 },
|
||||
activeClassId: 'c1',
|
||||
selectedMaskIds: ['m1'],
|
||||
masks: [
|
||||
@@ -1749,15 +1749,23 @@ describe('CanvasArea', () => {
|
||||
fireEvent.mouseMove(stage, { clientX: 170, clientY: 100 });
|
||||
fireEvent.mouseUp(stage, { clientX: 210, clientY: 110 });
|
||||
|
||||
expect(useStore.getState().masks).toHaveLength(1);
|
||||
expect(useStore.getState().masks).toHaveLength(2);
|
||||
expect(useStore.getState().masks[0]).toEqual(expect.objectContaining({
|
||||
id: 'm1',
|
||||
label: '胆囊',
|
||||
color: '#ff0000',
|
||||
saveStatus: 'draft',
|
||||
area: 2500,
|
||||
}));
|
||||
expect(useStore.getState().masks[0].area).toBeGreaterThan(2500);
|
||||
expect(useStore.getState().selectedMaskIds).toEqual(['m1']);
|
||||
expect(useStore.getState().masks[1]).toEqual(expect.objectContaining({
|
||||
label: '胆囊',
|
||||
color: '#ff0000',
|
||||
classId: 'c1',
|
||||
classMaskId: 1,
|
||||
saveStatus: 'draft',
|
||||
metadata: expect.objectContaining({ shape: '画笔' }),
|
||||
}));
|
||||
expect(useStore.getState().masks[1].area).toBeGreaterThan(1000);
|
||||
expect(useStore.getState().selectedMaskIds).toEqual([useStore.getState().masks[1].id]);
|
||||
});
|
||||
|
||||
it('erases from the selected mask with a sampled stroke', () => {
|
||||
|
||||
@@ -512,10 +512,6 @@ function imageBoundsGeometry(width: number, height: number): MultiPolygon | null
|
||||
]]);
|
||||
}
|
||||
|
||||
function geometriesOverlap(first: MultiPolygon, second: MultiPolygon): boolean {
|
||||
return polygonClipping.intersection(first, second).length > 0;
|
||||
}
|
||||
|
||||
export function CanvasArea({
|
||||
activeTool,
|
||||
frame,
|
||||
@@ -643,7 +639,7 @@ export function CanvasArea({
|
||||
return {
|
||||
title: '画笔',
|
||||
body: activeClass
|
||||
? '按住并拖动画出连续区域;若与当前选中 mask 连通,会自动合并到该 mask。'
|
||||
? '按住并拖动画出连续区域,松开后生成一个新的独立 mask。'
|
||||
: '先在右侧语义分类树选择类别,然后按住并拖动画出连续区域。',
|
||||
};
|
||||
}
|
||||
@@ -1150,34 +1146,6 @@ export function CanvasArea({
|
||||
return;
|
||||
}
|
||||
|
||||
const targetGeometry = selectedMask ? maskToMultiPolygon(selectedMask) : null;
|
||||
const shouldMerge = Boolean(targetGeometry && geometriesOverlap(targetGeometry, strokeGeometry));
|
||||
if (selectedMask && targetGeometry && shouldMerge) {
|
||||
const resultGeometry = polygonClipping.union(targetGeometry, strokeGeometry);
|
||||
const resultSegmentation = multiPolygonToSegmentation(resultGeometry);
|
||||
if (resultSegmentation.length === 0) return;
|
||||
const nextMask = {
|
||||
...maskWithSegmentation(selectedMask, resultSegmentation, {
|
||||
area: multiPolygonArea(resultGeometry),
|
||||
hasHoles: multiPolygonHasHoles(resultGeometry),
|
||||
polygonRingCounts: multiPolygonRingCounts(resultGeometry),
|
||||
}),
|
||||
templateId: activeTemplateId || selectedMask.templateId,
|
||||
classId: activeClass.id,
|
||||
className: activeClass.name,
|
||||
classZIndex: activeClass.zIndex,
|
||||
classMaskId: activeClass.maskId,
|
||||
label: activeClass.name,
|
||||
color: activeClass.color,
|
||||
};
|
||||
setMasks(masks.map((mask) => (mask.id === selectedMask.id ? nextMask : mask)));
|
||||
setSelectedMaskId(selectedMask.id);
|
||||
setSelectedMaskIds([selectedMask.id]);
|
||||
setGlobalSelectedMaskIds([selectedMask.id]);
|
||||
setSelectedVertexIndex(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const nextMask = createManualMaskFromGeometry('画笔', strokeGeometry);
|
||||
if (nextMask) {
|
||||
setSelectedMaskId(nextMask.id);
|
||||
|
||||
Reference in New Issue
Block a user