From 933c4c5d718379dcb1259e7b7e45f81aa3ab127a Mon Sep 17 00:00:00 2001 From: umsangdon Date: Fri, 16 Oct 2026 00:34:02 +0900 Subject: [PATCH] =?UTF-8?q?refactor(CAD):=20337-6=20=EA=B0=80=20=EB=81=9D?= =?UTF-8?q?=20=EB=AA=AB=20=E2=80=94=20=EC=98=9B=20OFFSET=20=C2=B7=20LENGTH?= =?UTF-8?q?EN=20=C2=B7=20=EC=98=9B=20=EA=BC=B4=20=ED=86=B5=EA=B3=BC=20?= =?UTF-8?q?=EC=A7=80=EC=9B=80=20=C2=B7=20ALL=20=C2=B7=20L=20=C2=B7=20P=20?= =?UTF-8?q?=EC=8B=A4=EC=A0=9C=20=EA=B3=A0=EB=A6=84=20=EC=8B=9C=ED=97=98=20?= =?UTF-8?q?=C2=B7=20=EC=95=88=EB=82=B4=20=EA=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - transform-acad: 고르기 물음 ALL · L · P = 「n개를 찾음, 총 m개」 · 다시 「객체 선택:」 · 빈 Enter = 고르기 끝(설계 1-4 · 2-15) - transform-tools: 안 쓰는 옛 OFFSET · LENGTHEN 머신 지움(새 offset-tool · lengthen-tool 이 대신) - prompt-input · prompt · sequence-tool: 옛 꼴 통과(meta.options 글 배열 · TEXT_INPUT 대문자 통과 · chainFromLastPoint) 지움 - prompt-contract: 옛 꼴 안 쓰는지 지키는 시험 더함 - 안내 글(설계 4 장 끝): 오른쪽 단추 · 휠 두 번 · CAD 키 줄 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QfHKQs7J3hQELKMWQMUJEy --- .../openwebcad/src/commands/prompt.ts | 2 - .../src/inputController/prompt-input.ts | 20 +-------- .../src/tools/factories/sequence-tool.ts | 18 +++----- .../src/tools/modify/transform-tools.ts | 44 +------------------ resources/tester/cad/prompt-contract.test.ts | 11 +++++ resources/tester/cad/transform-acad.test.ts | 21 ++++++--- ui_template/ui_template_locale_common.ts | 8 ++-- 7 files changed, 39 insertions(+), 85 deletions(-) diff --git a/B07_DesignDetail/openwebcad/src/commands/prompt.ts b/B07_DesignDetail/openwebcad/src/commands/prompt.ts index dc38a47aa..5efa82684 100644 --- a/B07_DesignDetail/openwebcad/src/commands/prompt.ts +++ b/B07_DesignDetail/openwebcad/src/commands/prompt.ts @@ -47,8 +47,6 @@ export interface PromptMeta { kind?: StepKind; prompt?: PromptLike; instructions?: string; - /** 옛 꼴 — 점 물음이 받는 옵션 글자(대문자) */ - options?: string[]; } /** 함수 꼴 물음을 지금 context 로 풂 */ diff --git a/B07_DesignDetail/openwebcad/src/inputController/prompt-input.ts b/B07_DesignDetail/openwebcad/src/inputController/prompt-input.ts index d08439020..92192699e 100644 --- a/B07_DesignDetail/openwebcad/src/inputController/prompt-input.ts +++ b/B07_DesignDetail/openwebcad/src/inputController/prompt-input.ts @@ -147,14 +147,12 @@ export interface CurrentPrompt { spec: PromptSpec | null; /** 그린 한 줄(새 꼴) 또는 옛 instructions */ line: string | null; - /** 옛 꼴 — 점 물음이 받는 옵션 글자 */ - legacyOptions: string[]; } export function currentPrompt(): CurrentPrompt { const sub = subPrompt(); if (sub) { - return { kind: sub.kind, spec: sub.prompt, line: renderPrompt(sub.prompt, sub.kind), legacyOptions: [] }; + return { kind: sub.kind, spec: sub.prompt, line: renderPrompt(sub.prompt, sub.kind) }; } const meta = activeMeta(); const spec = resolvePrompt(meta?.prompt, activeSnapshot()?.context); @@ -163,7 +161,6 @@ export function currentPrompt(): CurrentPrompt { kind, spec, line: spec ? renderPrompt(spec, meta?.kind) : (meta?.instructions ?? null), - legacyOptions: meta?.options ?? [], }; } @@ -276,12 +273,6 @@ function numberProblem(value: number, rule: PromptSpec['number']): string | null // ---- 사다리(설계 2-5) ---- -/** 옛 꼴 통과 — 새 물음이 없는 상태가 TEXT_INPUT 을 받고 숫자는 안 받음 */ -const legacyTakesText = (): boolean => { - const on = (flatConfig()?.on ?? {}) as Record; - return !!on.TEXT_INPUT && !on.NUMBER_INPUT; -}; - /** ② 빈 Enter · Space · 우클릭 */ function routeEmpty(prompt: CurrentPrompt) { const lockedLength = getLockedLength(); @@ -400,17 +391,8 @@ export function routeTyped(raw: string): void { notice(`「${hit.absent.label}(${hit.absent.kw})」 옵션은 이 CAD 에 없음.`); return; } - const upper = normalizeKeyword(text); - if (!prompt.spec && prompt.legacyOptions.includes(upper)) { - send({ type: ActorEvent.TEXT_INPUT, value: upper }); // 옛 꼴 통과 - return; - } if (prompt.kind === 'point') return routePointText(text, prompt); if (prompt.kind === 'number') return routeNumber(text, prompt); - if (!prompt.spec && legacyTakesText()) { - send({ type: ActorEvent.TEXT_INPUT, value: upper }); // 옛 꼴 통과 - return; - } reject(prompt); // ⑦ ⑩ } diff --git a/B07_DesignDetail/openwebcad/src/tools/factories/sequence-tool.ts b/B07_DesignDetail/openwebcad/src/tools/factories/sequence-tool.ts index 2100d7062..7569d4553 100644 --- a/B07_DesignDetail/openwebcad/src/tools/factories/sequence-tool.ts +++ b/B07_DesignDetail/openwebcad/src/tools/factories/sequence-tool.ts @@ -118,8 +118,6 @@ export interface SequenceToolConfig { pickfirst?: boolean; /** 옛 꼴 — true = `loopFrom: 0` · false = 끝 */ restart?: boolean; - /** 옛 꼴 — 확정 뒤 마지막 점을 첫 값으로 이어받아 계속 그린다 */ - chainFromLastPoint?: boolean; /** 점 입력 중 스냅·각도 가이드 사용 여부 (기본 true) */ helpers?: boolean; /** ESC 로 확정 없이 그만둘 때 */ @@ -201,9 +199,7 @@ export function createSequenceTool(config: SequenceToolConfig) { ? indexOf(config.loopFrom) : config.restart === true ? 0 - : config.chainFromLastPoint && config.restart !== false - ? 1 - : null; + : null; for (const step of steps) if (step.next !== undefined) indexOf(step.next); const isRepeat = (index: number) => !!config.repeatLastStep && index === lastIndex; @@ -375,18 +371,16 @@ export function createSequenceTool(config: SequenceToolConfig) { setGhostHelperEntities([]); }; - /** 확정 뒤 되풀이 — loopFrom 앞 값만 둠(옛 chainFromLastPoint = 마지막 점 이어받음) */ + /** 확정 뒤 되풀이 — loopFrom 앞 값만 둠 */ const loopAgain = assign(({ context }: Ctx): Partial => { const from = loopIndex as number; - const tail = lastPoint(context.values); - const chain = config.chainFromLastPoint && config.loopFrom === undefined && tail; - const keep = context.values.map((_, index) => chain || context.stepOf[index] < from); - const values = chain ? [tail] : context.values.filter((_, index) => keep[index]); + const keep = context.values.map((_, index) => context.stepOf[index] < from); + const values = context.values.filter((_, index) => keep[index]); const next = { ...context, values, - picks: chain ? [tail] : context.picks.filter((_, index) => keep[index]), - stepOf: chain ? [0] : context.stepOf.filter((_, index) => keep[index]), + picks: context.picks.filter((_, index) => keep[index]), + stepOf: context.stepOf.filter((_, index) => keep[index]), closed: false, loops: context.loops + 1, }; diff --git a/B07_DesignDetail/openwebcad/src/tools/modify/transform-tools.ts b/B07_DesignDetail/openwebcad/src/tools/modify/transform-tools.ts index 21e573119..7ab0210b2 100644 --- a/B07_DesignDetail/openwebcad/src/tools/modify/transform-tools.ts +++ b/B07_DesignDetail/openwebcad/src/tools/modify/transform-tools.ts @@ -1,6 +1,5 @@ -/** 대칭·정렬·간격띄우기·신축·길이조정 (조사표 2절) */ +/** 대칭·정렬·신축 (조사표 2절) */ import { Box, Point, Polygon } from '@flatten-js/core'; -import { toast } from 'react-toastify'; import { type AnyActorRef, assign } from 'xstate'; import { OPT } from '../../commands/prompt'; import type { Entity } from '../../entities/Entity'; @@ -21,7 +20,7 @@ import { createDisplaceTool } from '../move-tool'; import { SelectState } from '../select-tool'; import type { MouseClickEvent, StateEvent } from '../tool.types'; import { SELECT_CHILD } from '../transform.helpers'; -import { copyStyle, lengthenLine, offsetEntity } from './modify.helpers'; +import { copyStyle } from './modify.helpers'; /** 원본 특성을 유지한 복사본 */ const cloneStyled = (entity: Entity): Entity => copyStyle(entity, entity.clone()); @@ -110,23 +109,6 @@ export const alignToolStateMachine = createSequenceTool({ }, }); -export const offsetToolStateMachine = createSequenceTool({ - tool: Tool.OFFSET, - steps: [ - { kind: 'number', instructions: '간격띄우기 거리를 입력하십시오 <1>.', defaultValue: 1 }, - { kind: 'entity', instructions: '간격띄우기할 객체를 선택하십시오.' }, - { kind: 'point', instructions: '간격을 띄울 방향의 점을 지정하십시오.' }, - ], - commit: (input) => { - const result = offsetEntity(input.entity(1), input.number(0), input.point(2)); - if (!result) { - toast.warn('이 객체는 지정한 거리로 간격을 띄울 수 없습니다.'); - return; - } - addEntities([result], true); - }, -}); - /** 바꾼 것을 원본 자리에 — id · 묶음 · 꼴 그대로 */ const inPlaceOf = (source: Entity, made: T): T => { copyStyle(source, made); @@ -208,25 +190,3 @@ export const stretchToolStateMachine = createDisplaceTool({ return context.originals.flatMap((entity) => stretchEntity(entity, box, dx, dy) ?? []); }, }); - -export const lengthenToolStateMachine = createSequenceTool({ - tool: Tool.LENGTHEN, - steps: [ - { kind: 'entity', instructions: '길이를 바꿀 선을 늘릴 쪽 끝 근처에서 선택하십시오.' }, - { - kind: 'number', - instructions: '증분 길이를 입력하십시오 (음수는 단축) <10>.', - defaultValue: 10, - }, - ], - commit: (input) => { - const entity = input.entity(0); - const changed = lengthenLine(entity, input.number(1), input.pick(0)); - if (!changed) { - toast.warn('선만 길이를 조정할 수 있습니다.'); - return; - } - deleteEntities([entity], false); - addEntities([changed], true); - }, -}); diff --git a/resources/tester/cad/prompt-contract.test.ts b/resources/tester/cad/prompt-contract.test.ts index 634b321d1..b94ad3240 100644 --- a/resources/tester/cad/prompt-contract.test.ts +++ b/resources/tester/cad/prompt-contract.test.ts @@ -62,4 +62,15 @@ describe("물음 약속(2-16)", () => { } expect(problems).toEqual([]); }); + + it("옛 꼴 통과 안 씀 — 옵션 글 배열 없음 · 숫자 없이 TEXT_INPUT 만 받는 상태 없음(글 물음 빼고)", () => { + const problems: string[] = []; + for (const { where, meta, on } of rows) { + if ((meta as { options?: unknown } | undefined)?.options) + problems.push(`${where}: meta.options`); + if (on.TEXT_INPUT && !on.NUMBER_INPUT && meta?.kind !== "text") + problems.push(`${where}: TEXT_INPUT 만`); + } + expect(problems).toEqual([]); + }); }); diff --git a/resources/tester/cad/transform-acad.test.ts b/resources/tester/cad/transform-acad.test.ts index 02974f246..f905d9868 100644 --- a/resources/tester/cad/transform-acad.test.ts +++ b/resources/tester/cad/transform-acad.test.ts @@ -621,14 +621,23 @@ describe("PEDIT 고르기 · 고르기 공용(2-14 ④ · 2-15)", () => { expect(entities()).toHaveLength(1); }); - it("고르기 물음의 ALL · L · P 는 거절 없이 선택 도구로 넘어감", () => { + it("고르기 물음의 ALL · L · P = 실제 고름 · 「n개를 찾음」 · 다시 「객체 선택:」 · 빈 Enter = 고르기 끝", () => { line("0,0", "10,0"); + line("0,20", "10,20"); type("m"); - for (const word of ["all", "l", "p"]) { - type(word); - expect(noticeLine()).toBeNull(); - expect(activeTool()).toBe(Tool.MOVE); - } + type("all"); + expect(noticeLine()).toBe("2개를 찾음, 총 2개"); + expect(promptLine()).toBe("객체 선택:"); + expect(activeTool()).toBe(Tool.MOVE); + type("l"); + expect(noticeLine()).toBe("1개를 찾음, 총 2개"); + expect(promptLine()).toBe("객체 선택:"); + type("p"); + expect(noticeLine()).toMatch(/^\d+개를 찾음, 총 2개$/); + expect(promptLine()).toBe("객체 선택:"); + enter(); + expect(activeTool()).toBe(Tool.MOVE); + expect(promptLine()).toBe("기준점 지정 또는 [변위(D)] <변위>:"); }); const selectTakesKeywords = !!( diff --git a/ui_template/ui_template_locale_common.ts b/ui_template/ui_template_locale_common.ts index f78b3f6c8..b2035a21e 100644 --- a/ui_template/ui_template_locale_common.ts +++ b/ui_template/ui_template_locale_common.ts @@ -121,8 +121,8 @@ export const ui_locales_common = { App_Settings_Rule_Pan: ["화면 옮기기 = 가운데 단추 끌기", "Pan = drag with the middle button"], App_Settings_Rule_Pick: ["고르기 = 왼쪽 단추", "Select = left button"], App_Settings_Rule_Right: [ - "오른쪽 단추 = 메뉴 · CAD 에서는 Enter", - "Right button = menu · Enter in CAD", + "오른쪽 단추 = 메뉴 · CAD 에서는 명령 중 Enter · 쉴 때 반복 · Shift = 스냅 메뉴 · CAD 휠 두 번 = 범위", + "Right button = menu · in CAD Enter during a command · repeat when idle · Shift = snap menu · CAD wheel double-click = extents", ], App_Settings_Rule_Pinch: [ "트랙패드 · 손가락 벌림 = 늘 확대(휠 방향과 상관없음)", @@ -164,8 +164,8 @@ export const ui_locales_common = { ], App_Settings_Keys_Cad: ["CAD", "CAD"], App_Settings_Keys_Cad_Keys: [ - "F3 스냅 · F7 그리드 · F8 직교 · F10 45° · Ctrl+Z · Ctrl+Y · Ctrl+A · Ctrl+S(M02) · Delete · Esc · Enter · Space = Enter · 방향키 = 화면 옮김 · 글자 = 명령", - "F3 snap · F7 grid · F8 ortho · F10 45° · Ctrl+Z · Ctrl+Y · Ctrl+A · Ctrl+S (M02) · Delete · Esc · Enter · Space = Enter · arrows = pan · letters = command", + "F3 스냅 · F7 그리드 · F8 직교 · F10 45° · Ctrl+Z · Ctrl+Y · Ctrl+A · Ctrl+S(M02) · Delete · Esc · Enter · Space = Enter · ↑↓ = 기록 · 최근 값 · Ctrl+C · X · 1 · ← → · Shift+↑↓ = 화면 옮김 · 글자 = 명령", + "F3 snap · F7 grid · F8 ortho · F10 45° · Ctrl+Z · Ctrl+Y · Ctrl+A · Ctrl+S (M02) · Delete · Esc · Enter · Space = Enter · ↑↓ = history · recent values · Ctrl+C · X · 1 · ← → · Shift+↑↓ = pan · letters = command", ], App_Settings_Keys_Table: ["B08 · B09 표", "B08 · B09 tables"], App_Settings_Keys_Table_Keys: [