표가 DXF의 표로 나가야 한다(사용자 확정). 지금까지 도면의 표는 선과 문자 뭉치라 내보낼 때 고를 수 있는 길이 하나뿐이었다. - TableEntity: 열별 폭·행별 높이·칸 문자·병합을 한 객체가 들고 있다. 격자선은 담지 않고 병합 자리에서 선을 끊는 규칙을 표가 스스로 안다(helpers/table-geometry.ts). 회전·대칭은 지원하지 않는다 — 표는 축에 붙어 있다. - 명령: TABLE을 표 객체 생성으로 다시 쓰고 TABLEEDIT(칸 문자)·TABLEROW·TABLECOL· TABLEMERGE·TABLEUNMERGE를 더했다. EXPLODE는 표를 선과 문자로 흩는다. - 그립: 좌측 상단으로 표를 옮기고, 열·행 경계로 폭·높이를 바꾼다. - 백엔드: 유역 정보표와 횡단 수량 산출표를 표 객체로 낸다. 횡단표는 머리행이 폭 8등분, 본문이 11열 가중치로 격자가 서로 달라 두 경계를 합친 18열로 만들고 병합으로 원래 칸을 되살렸다 — 손으로 하던 가로선 끊기가 사라졌다. - 수량 역추출: 값 Text의 결정적 id로 읽던 것을 칸에 실은 key로 읽도록 옮겼다. 이미 저장된 도면을 위해 옛 방식을 폴백으로 남겼다. 토적도·종단표는 값이 칸이 아니라 측점 위치에 놓이는 성격이라 이관하지 않았다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
159 lines
6.0 KiB
TypeScript
159 lines
6.0 KiB
TypeScript
/** 지시선·표·구름형·스타일 설정 (조사표 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);
|
|
},
|
|
});
|