/** 지시선·표·구름형·스타일 설정 (조사표 5절 지시선·표·표식·주석 축척) */ import { Point } from '@flatten-js/core'; import { toast } from 'react-toastify'; import { getAnnotationScale, getDimTextHeight, setAnnotationScale, setDimStyle, } from '../../commands/dim-settings'; import type { Entity } from '../../entities/Entity'; import { EntityName } from '../../entities/Entity'; import type { TextEntity } from '../../entities/TextEntity'; import { revisionCloudPoints } from '../../helpers/geometry/shape-points'; import { addEntities, getEntities, setEntities, setSelectedEntityIds } from '../../state'; import { Tool } from '../../tools'; import { lineEntity, polyLineEntity } from '../factories/entity-factory'; import { createSequenceTool } from '../factories/sequence-tool'; import { annotationText, arrowHead, worldFactor } from './annotation.helpers'; export const mleaderToolStateMachine = createSequenceTool({ tool: Tool.MLEADER, steps: [ { kind: 'point', instructions: '지시선 화살표 위치를 지정하십시오.' }, { kind: 'point', instructions: '지시선 꺾임점을 지정하십시오.' }, { kind: 'text', instructions: '지시선 문자를 입력하십시오.' }, ], preview: (input) => { const points = input.points(); if (points.length !== 1) return []; return [lineEntity(points[0], input.cursor), arrowHead(points[0], input.cursor)]; }, commit: (input) => { const [tip, knee] = input.points(); const textHeight = getDimTextHeight() / worldFactor(); // 꺾임점에서 문자 쪽으로 짧은 가로선을 하나 더 뽑는다 (AutoCAD 지시선 모양) const landingLength = textHeight * 2; const toRight = knee.x >= tip.x; const landingEnd = new Point(knee.x + (toRight ? landingLength : -landingLength), knee.y); const label = annotationText( input.text(2), new Point(landingEnd.x, landingEnd.y + textHeight * 0.4) ); const groupId = crypto.randomUUID(); const parts: Entity[] = [ lineEntity(tip, knee), lineEntity(knee, landingEnd), arrowHead(tip, knee), label, ]; for (const part of parts) part.groupId = groupId; addEntities(parts, true); }, }); export const mleaderStyleToolStateMachine = createSequenceTool({ tool: Tool.MLEADERSTYLE, helpers: false, steps: [ { kind: 'number', instructions: '지시선 문자 높이를 입력하십시오 <16>.', defaultValue: 16 }, { kind: 'number', instructions: '화살표 크기를 입력하십시오 <20>.', defaultValue: 20 }, ], commit: (input) => { setDimStyle(input.number(0), input.number(1), -1); toast.success(`지시선 스타일: 문자 ${input.number(0)} · 화살표 ${input.number(1)}`); }, }); /** 지시선 정렬 — 선택한 지시선 문자의 X 위치를 첫 문자에 맞춘다 */ export const mleaderAlignToolStateMachine = createSequenceTool({ tool: Tool.MLEADERALIGN, helpers: false, steps: [{ kind: 'selection', instructions: '정렬할 지시선 문자를 선택한 뒤 ENTER.' }], commit: (input) => { const texts = input .entities(0) .filter((entity) => entity.getType() === EntityName.Text) as TextEntity[]; if (texts.length < 2) { toast.warn('지시선 문자를 두 개 이상 선택하십시오.'); return; } const targetX = texts[0].getBoundingBox().xmin; for (const text of texts.slice(1)) { text.move(targetX - text.getBoundingBox().xmin, 0); } setEntities([...getEntities()], true); setSelectedEntityIds([]); toast.success(`${texts.length}개 지시선 문자를 정렬했습니다.`); }, }); export const dimStyleToolStateMachine = createSequenceTool({ tool: Tool.DIMSTYLE, helpers: false, steps: [ { kind: 'number', instructions: '치수 문자 높이를 입력하십시오 <16>.', defaultValue: 16 }, { kind: 'number', instructions: '화살표 크기를 입력하십시오 <20>.', defaultValue: 20 }, { kind: 'number', instructions: '소수 자릿수를 입력하십시오 <2>.', defaultValue: 2 }, ], commit: (input) => { setDimStyle(input.number(0), input.number(1), input.number(2)); setEntities([...getEntities()], false); toast.success('치수 스타일을 바꿨습니다.'); }, }); /** 치수 업데이트 — 스타일을 바꾼 뒤 화면을 다시 그린다 */ export function updateDimensions(): string { setEntities([...getEntities()], false); toast.success('치수를 현재 스타일로 갱신했습니다.'); return '치수 업데이트'; } export const annotationScaleToolStateMachine = createSequenceTool({ tool: Tool.ANNOSCALE, helpers: false, steps: [{ kind: 'number', instructions: '주석 축척 배율을 입력하십시오 <1>.', defaultValue: 1 }], commit: (input) => { setAnnotationScale(input.number(0)); setEntities([...getEntities()], false); toast.success(`주석 축척 ${getAnnotationScale()}배`); }, }); export const revCloudToolStateMachine = createSequenceTool({ tool: Tool.REVCLOUD, 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 points = input.points(); if (points.length < 2) return; const closed = [...points, points[0].clone()]; const box = closed.reduce( (size, point) => ({ minX: Math.min(size.minX, point.x), minY: Math.min(size.minY, point.y), maxX: Math.max(size.maxX, point.x), maxY: Math.max(size.maxY, point.y), }), { minX: Number.POSITIVE_INFINITY, minY: Number.POSITIVE_INFINITY, maxX: Number.NEGATIVE_INFINITY, maxY: Number.NEGATIVE_INFINITY, } ); // 스캘럽 크기는 구름 크기에 비례시켜 어떤 축척에서도 비슷하게 보이게 한다 const arcRadius = Math.max(Math.hypot(box.maxX - box.minX, box.maxY - box.minY) / 40, 1e-6); const cloud = polyLineEntity(revisionCloudPoints(closed, arcRadius)); if (cloud) addEntities([cloud], true); }, });