fix(pdf-import): 좌표 축소 스케일 제거 — 치수 라벨 실측 정합(R1)
[작업내용] : - fitAndCenter 가 큰 도면(9.6m 초과)에 scale<1 을 좌표에 곱해 'QLine 길이×10=표시 mm' 불변식을 깨뜨리던 문제 해소 — 좌표는 실측 그대로 평행이동만 하는 centerOnCanvas 로 대체 - 화면맞춤은 fitViewport 로 분리: 캔버스 중앙 고정 viewportTransform 줌 + canvasZoomState 동기화 (useCanvasEvent 의 setZoom 은 동일 zoom 값에서 translation 을 보존) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
29fd4cbd59
commit
7b15b76054
@ -1,6 +1,6 @@
|
||||
import { useSetRecoilState, useRecoilValue } from 'recoil'
|
||||
import { fabric } from 'fabric'
|
||||
import { canvasState } from '@/store/canvasAtom'
|
||||
import { canvasState, canvasZoomState } from '@/store/canvasAtom'
|
||||
import { outerLineFixState, outerLinePointsState } from '@/store/outerLineAtom'
|
||||
import { outlineDisplaySelector } from '@/store/settingAtom'
|
||||
import { POLYGON_TYPE } from '@/common/common'
|
||||
@ -12,9 +12,9 @@ import { snapNearAxisEdges, cleanSelfIntersectingPolygon } from '@/util/qpolygon
|
||||
import * as turf from '@turf/turf'
|
||||
|
||||
const MM_PER_CANVAS_UNIT = 10
|
||||
const MAX_DIM = 1500
|
||||
// useLine.addLine 이 length < 1(=10mm) 라인을 생성하지 않으므로 동일 임계값으로 사전 제거한다.
|
||||
const MIN_CANVAS_UNIT_LENGTH = 1
|
||||
const VIEWPORT_FIT_RATIO = 0.6 // 화면맞춤 시 캔버스 대비 도면 최대 비율
|
||||
const SIMPLIFY_TOLERANCE = 0.5 // canvas unit ≈ 5mm. 직선 위 잉여 정점/미세 엣지 제거(보수적)
|
||||
const SNAP_ANGLE_TOL_DEG = 2 // 거의 수평/수직 엣지를 정확히 축정렬로 스냅
|
||||
|
||||
@ -50,31 +50,22 @@ const dedupShortEdges = (points) => {
|
||||
return out
|
||||
}
|
||||
|
||||
const fitAndCenter = (points, canvas) => {
|
||||
// 좌표에 축소 scale 을 곱하면 'QLine 길이×10 = 표시 mm' 불변식이 깨져 치수 라벨이 실측과 어긋난다.
|
||||
// 좌표는 실측 그대로 평행이동만 하고, 화면맞춤은 fitViewport 의 zoom(viewportTransform)으로 분리 처리한다.
|
||||
const centerOnCanvas = (points, canvas) => {
|
||||
const xs = points.map((p) => p.x)
|
||||
const ys = points.map((p) => p.y)
|
||||
const minX = Math.min(...xs)
|
||||
const minY = Math.min(...ys)
|
||||
const maxX = Math.max(...xs)
|
||||
const maxY = Math.max(...ys)
|
||||
const width = maxX - minX
|
||||
const height = maxY - minY
|
||||
|
||||
const targetWidth = (canvas?.getWidth?.() ?? 1000) * 0.6
|
||||
const targetHeight = (canvas?.getHeight?.() ?? 1000) * 0.6
|
||||
const maxAllowed = Math.min(MAX_DIM, Math.max(targetWidth, targetHeight))
|
||||
|
||||
let scale = 1
|
||||
if (width > maxAllowed || height > maxAllowed) {
|
||||
scale = Math.min(maxAllowed / width, maxAllowed / height)
|
||||
}
|
||||
const width = Math.max(...xs) - minX
|
||||
const height = Math.max(...ys) - minY
|
||||
|
||||
const cx = (canvas?.getWidth?.() ?? 1000) / 2
|
||||
const cy = (canvas?.getHeight?.() ?? 1000) / 2
|
||||
|
||||
return points.map((p) => ({
|
||||
x: cx + (p.x - (minX + width / 2)) * scale,
|
||||
y: cy + (p.y - (minY + height / 2)) * scale,
|
||||
x: cx + (p.x - (minX + width / 2)),
|
||||
y: cy + (p.y - (minY + height / 2)),
|
||||
}))
|
||||
}
|
||||
|
||||
@ -106,6 +97,7 @@ export function usePdfImport() {
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
const setOuterLinePoints = useSetRecoilState(outerLinePointsState)
|
||||
const setOuterLineFix = useSetRecoilState(outerLineFixState)
|
||||
const setCanvasZoom = useSetRecoilState(canvasZoomState)
|
||||
const isOutlineDisplay = useRecoilValue(outlineDisplaySelector)
|
||||
const { addLine } = useLine()
|
||||
const { swalFire } = useSwal()
|
||||
@ -168,6 +160,29 @@ export function usePdfImport() {
|
||||
})
|
||||
}
|
||||
|
||||
// 도면이 화면보다 크면 캔버스 중앙 고정 줌으로 화면맞춤한다. canvasZoomState 를 함께 동기화하면
|
||||
// useCanvasEvent 의 setZoom(동일 zoom 값) 은 translation 을 보존하므로 중앙 정렬이 유지된다.
|
||||
const fitViewport = (points) => {
|
||||
const xs = points.map((p) => p.x)
|
||||
const ys = points.map((p) => p.y)
|
||||
const width = Math.max(...xs) - Math.min(...xs)
|
||||
const height = Math.max(...ys) - Math.min(...ys)
|
||||
|
||||
const targetWidth = canvas.getWidth() * VIEWPORT_FIT_RATIO
|
||||
const targetHeight = canvas.getHeight() * VIEWPORT_FIT_RATIO
|
||||
|
||||
let zoomPercent = 100
|
||||
if (width > targetWidth || height > targetHeight) {
|
||||
zoomPercent = Math.max(1, Math.floor(Math.min(targetWidth / width, targetHeight / height) * 100))
|
||||
}
|
||||
const zoom = zoomPercent / 100
|
||||
|
||||
const cx = canvas.getWidth() / 2
|
||||
const cy = canvas.getHeight() / 2
|
||||
canvas.setViewportTransform([zoom, 0, 0, zoom, cx * (1 - zoom), cy * (1 - zoom)])
|
||||
setCanvasZoom(zoomPercent)
|
||||
}
|
||||
|
||||
const applyOuterline = async (mmPoints, options = {}) => {
|
||||
if (!canvas) {
|
||||
logger.warn('[usePdfImport] canvas 가 준비되지 않았습니다.')
|
||||
@ -202,7 +217,7 @@ export function usePdfImport() {
|
||||
return false
|
||||
}
|
||||
|
||||
canvasPoints = fitAndCenter(canvasPoints, canvas)
|
||||
canvasPoints = centerOnCanvas(canvasPoints, canvas)
|
||||
canvasPoints = ensureCounterClockwise(canvasPoints)
|
||||
|
||||
const proceed = async () => {
|
||||
@ -218,6 +233,7 @@ export function usePdfImport() {
|
||||
canvas.outerLineFix = true
|
||||
setOuterLineFix(true)
|
||||
|
||||
fitViewport(closedPoints)
|
||||
canvas.renderAll()
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user