/** 그리기 명령 — 스플라인·점·도넛 + 폴리선·호·다각형·타원 다시 내보냄 (조사표 1절 · 337 설계 1-1) */ import { CircleEntity } from '../../entities/CircleEntity'; import type { Entity } from '../../entities/Entity'; 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 { pointEntity, polyLineEntity, styled } from '../factories/entity-factory'; import { createSequenceTool } from '../factories/sequence-tool'; import { option } from './draw-steps'; export { arcToolStateMachine } from './arc-tool'; export { ellipseToolStateMachine } from './ellipse-tool'; export { plineToolStateMachine } from './pline-tool'; export { polygonToolStateMachine } from './polygon-tool'; /** 닫힌 스플라인 — 끝 둘레 이웃을 이어 붙여 그린 뒤 이음 구간만 남김 */ 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]]; } 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', 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) => { const preview = polyLineEntity(splinePoints([...input.points(), input.cursor])); return preview ? [preview] : []; }, commit: (input) => { const points = input.points(); const spline = polyLineEntity(input.closed ? closedSplinePoints(points) : splinePoints(points)); if (spline) addEntities([spline], true); }, }); export const pointToolStateMachine = createSequenceTool({ tool: Tool.POINT, steps: [{ kind: 'point', instructions: '점의 위치를 지정하십시오.' }], preview: (input) => [pointEntity(input.cursor)], commit: (input) => { addEntities([pointEntity(input.point(0))], true); }, }); /** 도넛 — 안쪽·바깥쪽 지름을 받아 동심원 두 개를 놓는다 */ export const donutToolStateMachine = createSequenceTool({ tool: Tool.DONUT, steps: [ { kind: 'number', instructions: '도넛의 내부 지름을 입력하십시오 <1>.', defaultValue: 1 }, { 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); }, }); function donutEntities( innerDiameter: number, outerDiameter: number, center: Parameters[0] ): Entity[] { const circles: Entity[] = []; for (const diameter of [innerDiameter, outerDiameter]) { if (diameter > 0) { circles.push(styled(new CircleEntity(getActiveLayerId(), center, diameter / 2))); } } return circles; }