/** 선형 치수 계열 — 선형·정렬·기준선·연속·빠른 치수·자동 치수 (조사표 5절) */ import { Circle, Point } from '@flatten-js/core'; import { toast } from 'react-toastify'; import { EntityName } from '../../entities/Entity'; import { MeasurementEntity } from '../../entities/MeasurementEntity'; import { sampleEntityPoints } from '../../helpers/geometry/sample-entity'; import { addEntities, getActiveLayerId, setSelectedEntityIds } from '../../state'; import { Tool } from '../../tools'; import { styled } from '../factories/entity-factory'; import { createSequenceTool } from '../factories/sequence-tool'; import { getLastDimension, rememberDimension } from './annotation.helpers'; /** 치수 객체 하나 만들기 — 현재 선 특성을 입히고 직전 치수로 기억한다 */ function makeDimension(start: Point, end: Point, offset: Point): MeasurementEntity { const dimension = styled(new MeasurementEntity(getActiveLayerId(), start, end, offset)); rememberDimension(dimension); return dimension; } /** * 선형 치수 — 치수선을 놓은 방향으로 수평/수직을 고른다. * 위·아래에 놓으면 가로 거리, 좌·우에 놓으면 세로 거리를 잰다 (AutoCAD와 같다). */ function projectForLinear(start: Point, end: Point, offset: Point): [Point, Point] { const horizontalSpan = Math.abs(end.x - start.x); const verticalSpan = Math.abs(end.y - start.y); const offsetIsVertical = Math.abs(offset.y - (start.y + end.y) / 2) >= Math.abs(offset.x - (start.x + end.x) / 2); if (offsetIsVertical || horizontalSpan >= verticalSpan) { return [start, new Point(end.x, start.y)]; } return [start, new Point(start.x, end.y)]; } export const dimLinearToolStateMachine = createSequenceTool({ tool: Tool.DIMLINEAR, steps: [ { kind: 'point', instructions: '첫 번째 치수보조선 원점을 지정하십시오.' }, { kind: 'point', instructions: '두 번째 치수보조선 원점을 지정하십시오.' }, { kind: 'point', instructions: '치수선 위치를 지정하십시오.' }, ], preview: (input) => { const points = input.points(); if (points.length !== 2) return []; const [start, end] = projectForLinear(points[0], points[1], input.cursor); return [new MeasurementEntity(getActiveLayerId(), start, end, input.cursor)]; }, commit: (input) => { const [first, second, offset] = input.points(); const [start, end] = projectForLinear(first, second, offset); addEntities([makeDimension(start, end, offset)], true); }, }); export const dimAlignedToolStateMachine = createSequenceTool({ tool: Tool.DIMALIGNED, steps: [ { kind: 'point', instructions: '첫 번째 치수보조선 원점을 지정하십시오.' }, { kind: 'point', instructions: '두 번째 치수보조선 원점을 지정하십시오.' }, { kind: 'point', instructions: '치수선 위치를 지정하십시오.' }, ], preview: (input) => { const points = input.points(); if (points.length !== 2) return []; return [new MeasurementEntity(getActiveLayerId(), points[0], points[1], input.cursor)]; }, commit: (input) => { const [start, end, offset] = input.points(); addEntities([makeDimension(start, end, offset)], true); }, }); export const dimBaselineToolStateMachine = createSequenceTool({ tool: Tool.DIMBASELINE, steps: [{ kind: 'point', instructions: '다음 치수보조선 원점을 지정하십시오.' }], commit: (input) => { const previous = getLastDimension(); if (!previous) { toast.warn('먼저 치수를 하나 작성하십시오.'); return; } const start = previous.getStartPoint(); const offset = previous.getOffsetPoint(); // 기준선 치수는 같은 시작점에서 재고, 치수선을 한 칸 더 띄운다 const spacing = offset.distanceTo(start)[0] * 0.35 || 10; const direction = new Point(offset.x - start.x, offset.y - start.y); const length = Math.hypot(direction.x, direction.y) || 1; const nextOffset = new Point( offset.x + (direction.x / length) * spacing, offset.y + (direction.y / length) * spacing ); addEntities([makeDimension(start, input.point(0), nextOffset)], true); }, }); export const dimContinueToolStateMachine = createSequenceTool({ tool: Tool.DIMCONTINUE, steps: [{ kind: 'point', instructions: '다음 치수보조선 원점을 지정하십시오.' }], commit: (input) => { const previous = getLastDimension(); if (!previous) { toast.warn('먼저 치수를 하나 작성하십시오.'); return; } addEntities( [makeDimension(previous.getEndPoint(), input.point(0), previous.getOffsetPoint())], true ); }, }); export const qDimToolStateMachine = createSequenceTool({ tool: Tool.QDIM, helpers: false, steps: [ { kind: 'selection', instructions: '치수를 넣을 객체를 선택한 뒤 ENTER.' }, { kind: 'point', instructions: '치수선 위치를 지정하십시오.' }, ], commit: (input) => { const offset = input.point(1); const dimensions = input .entities(0) .map((entity) => { const points = sampleEntityPoints(entity); if (points.length < 2) return null; return makeDimension(points[0], points[points.length - 1], offset); }) .filter((dimension): dimension is MeasurementEntity => !!dimension); if (!dimensions.length) { toast.warn('치수를 넣을 수 있는 객체가 없습니다.'); return; } addEntities(dimensions, true); setSelectedEntityIds([]); }, }); /** DIM — 선택한 객체 종류에 맞는 치수를 자동으로 고른다 */ export const dimAutoToolStateMachine = createSequenceTool({ tool: Tool.DIM, helpers: false, steps: [ { kind: 'entity', instructions: '치수를 넣을 객체를 선택하십시오.' }, { kind: 'point', instructions: '치수선 위치를 지정하십시오.' }, ], commit: (input) => { const entity = input.entity(0); const offset = input.point(1); const shape = entity.getShape(); if (shape instanceof Circle || entity.getType() === EntityName.Arc) { // 원·호는 반지름 치수 명령이 더 알맞다 toast.info('원·호에는 DIMRADIUS 또는 DIMDIAMETER를 사용하십시오.'); return; } const points = sampleEntityPoints(entity); if (points.length < 2) { toast.warn('치수를 넣을 수 없는 객체입니다.'); return; } addEntities([makeDimension(points[0], points[points.length - 1], offset)], true); }, });