feat(CAD): 337-3a 나-1 그리기 8 — 오토캐드 물음 차례 · 옵션 · 기본값 · 빈 Enter · Esc · 끝난 뒤

- LINE: 「첫 번째 점 지정」 빈 Enter = 마지막 끝점에서 이음 · U = 마지막 토막(되돌림 한 칸) · 둘째 토막부터 C · Enter · Esc = 끝(토막 남음)
- PLINE(새 pline-tool): 호(A) 접선 호 · 길이(L) · 닫기(C · 호 모드 CL) · 명령 취소(U) · 선(L) · Esc · Enter = 그린 데까지 남김
- RECTANG: 치수(D) 길이 · 폭 기억(RECT.L · RECT.W) → 누른 쪽 사분면 · 없는 옵션 안내
- CIRCLE: 3P · 2P · 지름(D) <2r> · 반지름 기억(CIRCLERAD · 처음 <> 없음) · 한 번 뒤 끝
- POLYGON(새 polygon-tool): 변 수 기억 <4> · 3~1024 정수 · 모서리(E) · 내접/외접 기억 · 숫자 = 아래 변 수평
- ARC(새 arc-tool): 세 점 · 중심(C) · 각도(A) · 끝(E) · 반지름(R)
- ELLIPSE(새 ellipse-tool): 축 두 끝 · 중심(C) · 회전(R) · 다른 축 숫자 = 중심에서 반 길이(N10)
- SPLINE: 다음 점 입력 · U · 넷째 점부터 C · Esc 버림 · DONUT 중심 되풀이(loopFrom)
- 시험 draw-acad 22 · keyboard-chain 옛 선 · 폴리선 줄 두 곳 새 동작으로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmC34fsxxbweEG3RUjs3Mn
This commit is contained in:
2026-10-16 00:16:15 +09:00
co-authored by Claude Opus 5.5
parent 6e346cfb5e
commit 3cebb37ed0
11 changed files with 1757 additions and 458 deletions
@@ -1,114 +1,65 @@
/** 그리기 명령 — 폴리선·호·다각형·타원·스플라인·점 (조사표 1절) */
/** 그리기 명령 — 스플라인·점·도넛 + 폴리선·호·다각형·타원 다시 내보냄 (조사표 1절 · 337 설계 1-1) */
import { CircleEntity } from '../../entities/CircleEntity';
import type { Entity } from '../../entities/Entity';
import {
arcThroughThreePoints,
ellipsePoints,
regularPolygonPoints,
splinePoints,
} from '../../helpers/geometry/shape-points';
import type { Point } from '@flatten-js/core';
import { OPT } from '../../commands/prompt';
import { splinePoints } from '../../helpers/geometry/shape-points';
import { addEntities, getActiveLayerId } from '../../state';
import { Tool } from '../../tools';
import {
arcEntity,
lineEntity,
pointEntity,
polyLineEntity,
styled,
} from '../factories/entity-factory';
import { pointEntity, polyLineEntity, styled } from '../factories/entity-factory';
import { createSequenceTool } from '../factories/sequence-tool';
import { option } from './draw-steps';
export const plineToolStateMachine = createSequenceTool({
tool: Tool.PLINE,
steps: [
{ kind: 'point', instructions: '폴리선의 시작점을 지정하십시오.' },
{ kind: 'point', instructions: '다음 점을 지정하십시오. ENTER로 종료합니다.' },
],
repeatLastStep: true,
preview: (input) => {
const preview = polyLineEntity([...input.points(), input.cursor]);
return preview ? [preview] : [];
},
commit: (input) => {
const polyline = polyLineEntity(input.points());
if (polyline) addEntities([polyline], true);
},
});
export { arcToolStateMachine } from './arc-tool';
export { ellipseToolStateMachine } from './ellipse-tool';
export { plineToolStateMachine } from './pline-tool';
export { polygonToolStateMachine } from './polygon-tool';
export const arcToolStateMachine = createSequenceTool({
tool: Tool.ARC,
steps: [
{ kind: 'point', instructions: '호의 시작점을 지정하십시오.' },
{ kind: 'point', instructions: '호가 지나갈 두 번째 점을 지정하십시오.' },
{ kind: 'point', instructions: '호의 끝점을 지정하십시오.' },
],
preview: (input) => {
const points = input.points();
if (points.length === 1) return [lineEntity(points[0], input.cursor)];
if (points.length === 2) {
const definition = arcThroughThreePoints(points[0], points[1], input.cursor);
return definition ? [arcEntity(definition)] : [lineEntity(points[0], input.cursor)];
}
return [];
},
commit: (input) => {
const [start, through, end] = input.points();
const definition = arcThroughThreePoints(start, through, end);
// 세 점이 일직선이면 호가 성립하지 않으므로 선으로 대체한다
addEntities([definition ? arcEntity(definition) : lineEntity(start, end)], true);
},
});
/** 닫힌 스플라인 — 끝 둘레 이웃을 이어 붙여 그린 뒤 이음 구간만 남김 */
function closedSplinePoints(points: Point[]): Point[] {
const spans = 12;
const wrapped = splinePoints([points[points.length - 1], ...points, points[0], points[1]], spans);
const loop = wrapped.slice(spans, spans * (points.length + 1));
return [...loop, loop[0]];
}
export const polygonToolStateMachine = createSequenceTool({
tool: Tool.POLYGON,
steps: [
{ kind: 'number', instructions: '면의 수를 입력하십시오 <6>.', defaultValue: 6 },
{ kind: 'point', instructions: '다각형의 중심을 지정하십시오.' },
{ kind: 'point', instructions: '꼭짓점을 지정하십시오 (중심에서의 반지름).' },
],
preview: (input) => {
const points = input.points();
if (points.length !== 1) return [];
const polygon = polyLineEntity(regularPolygonPoints(points[0], input.cursor, input.number(0)));
return polygon ? [polygon] : [];
},
commit: (input) => {
const [center, vertex] = input.points();
const polygon = polyLineEntity(regularPolygonPoints(center, vertex, input.number(0)));
if (polygon) addEntities([polygon], true);
},
});
export const ellipseToolStateMachine = createSequenceTool({
tool: Tool.ELLIPSE,
steps: [
{ kind: 'point', instructions: '타원의 중심을 지정하십시오.' },
{ kind: 'point', instructions: '장축의 끝점을 지정하십시오.' },
{ kind: 'point', instructions: '단축 거리를 지정하십시오.' },
],
preview: (input) => {
const points = input.points();
if (points.length === 1) return [lineEntity(points[0], input.cursor)];
if (points.length === 2) {
const minor = points[0].distanceTo(input.cursor)[0];
const ellipse = polyLineEntity(ellipsePoints(points[0], points[1], minor));
return ellipse ? [ellipse] : [];
}
return [];
},
commit: (input) => {
const [center, majorPoint, minorPoint] = input.points();
const minorRadius = center.distanceTo(minorPoint)[0];
const ellipse = polyLineEntity(ellipsePoints(center, majorPoint, minorRadius));
if (ellipse) addEntities([ellipse], true);
},
});
const NEXT_POINT = '다음 점 입력';
const TOLERANCE = option('TOLERANCE', 'L', '공차');
/**
* 스플라인(SPLINE) — 337 설계 1-1. 「첫 번째 점 지정:」 → 「다음 점 입력:」 → 「다음 점 입력 또는 [명령 취소(U)]:」 →
* 넷째 점부터 [명령 취소(U)/닫기(C)] 되풀이 · 빈 Enter = 확정 · C = 닫고 끝 · Esc = 버림.
*/
export const splineToolStateMachine = createSequenceTool({
tool: Tool.SPLINE,
steps: [
{ kind: 'point', instructions: '스플라인의 첫 점을 지정하십시오.' },
{ kind: 'point', instructions: '다음 점을 지정하십시오. ENTER로 종료합니다.' },
{
kind: 'point',
prompt: {
text: '첫 번째 점 지정',
absent: [
option('METHOD', 'M', '방법'),
option('KNOTS', 'K', '매듭'),
option('OBJECT', 'O', '객체'),
],
},
},
{
kind: 'point',
prompt: {
text: NEXT_POINT,
absent: [option('TANGENT', 'T', '시작 접촉부'), TOLERANCE],
},
},
{
kind: 'point',
prompt: (input) => ({
text: NEXT_POINT,
options: input.points().length >= 3 ? [OPT.UNDO, OPT.CLOSE] : [OPT.UNDO],
absent: [option('TANGENT', 'T', '끝 접촉부'), TOLERANCE],
}),
options: { UNDO: 'undo', CLOSE: 'close' },
},
],
repeatLastStep: true,
preview: (input) => {
@@ -116,7 +67,8 @@ export const splineToolStateMachine = createSequenceTool({
return preview ? [preview] : [];
},
commit: (input) => {
const spline = polyLineEntity(splinePoints(input.points()));
const points = input.points();
const spline = polyLineEntity(input.closed ? closedSplinePoints(points) : splinePoints(points));
if (spline) addEntities([spline], true);
},
});
@@ -138,6 +90,7 @@ export const donutToolStateMachine = createSequenceTool({
{ kind: 'number', instructions: '도넛의 외부 지름을 입력하십시오 <2>.', defaultValue: 2 },
{ kind: 'point', instructions: '도넛의 중심을 지정하십시오.' },
],
loopFrom: 2, // 오토캐드 — 중심 되풀이 · 빈 Enter 끝
preview: (input) => donutEntities(input.number(0), input.number(1), input.cursor),
commit: (input) => {
addEntities(donutEntities(input.number(0), input.number(1), input.point(2)), true);