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 { useSetRecoilState, useRecoilValue } from 'recoil'
|
||||||
import { fabric } from 'fabric'
|
import { fabric } from 'fabric'
|
||||||
import { canvasState } from '@/store/canvasAtom'
|
import { canvasState, canvasZoomState } from '@/store/canvasAtom'
|
||||||
import { outerLineFixState, outerLinePointsState } from '@/store/outerLineAtom'
|
import { outerLineFixState, outerLinePointsState } from '@/store/outerLineAtom'
|
||||||
import { outlineDisplaySelector } from '@/store/settingAtom'
|
import { outlineDisplaySelector } from '@/store/settingAtom'
|
||||||
import { POLYGON_TYPE } from '@/common/common'
|
import { POLYGON_TYPE } from '@/common/common'
|
||||||
@ -12,9 +12,9 @@ import { snapNearAxisEdges, cleanSelfIntersectingPolygon } from '@/util/qpolygon
|
|||||||
import * as turf from '@turf/turf'
|
import * as turf from '@turf/turf'
|
||||||
|
|
||||||
const MM_PER_CANVAS_UNIT = 10
|
const MM_PER_CANVAS_UNIT = 10
|
||||||
const MAX_DIM = 1500
|
|
||||||
// useLine.addLine 이 length < 1(=10mm) 라인을 생성하지 않으므로 동일 임계값으로 사전 제거한다.
|
// useLine.addLine 이 length < 1(=10mm) 라인을 생성하지 않으므로 동일 임계값으로 사전 제거한다.
|
||||||
const MIN_CANVAS_UNIT_LENGTH = 1
|
const MIN_CANVAS_UNIT_LENGTH = 1
|
||||||
|
const VIEWPORT_FIT_RATIO = 0.6 // 화면맞춤 시 캔버스 대비 도면 최대 비율
|
||||||
const SIMPLIFY_TOLERANCE = 0.5 // canvas unit ≈ 5mm. 직선 위 잉여 정점/미세 엣지 제거(보수적)
|
const SIMPLIFY_TOLERANCE = 0.5 // canvas unit ≈ 5mm. 직선 위 잉여 정점/미세 엣지 제거(보수적)
|
||||||
const SNAP_ANGLE_TOL_DEG = 2 // 거의 수평/수직 엣지를 정확히 축정렬로 스냅
|
const SNAP_ANGLE_TOL_DEG = 2 // 거의 수평/수직 엣지를 정확히 축정렬로 스냅
|
||||||
|
|
||||||
@ -50,31 +50,22 @@ const dedupShortEdges = (points) => {
|
|||||||
return out
|
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 xs = points.map((p) => p.x)
|
||||||
const ys = points.map((p) => p.y)
|
const ys = points.map((p) => p.y)
|
||||||
const minX = Math.min(...xs)
|
const minX = Math.min(...xs)
|
||||||
const minY = Math.min(...ys)
|
const minY = Math.min(...ys)
|
||||||
const maxX = Math.max(...xs)
|
const width = Math.max(...xs) - minX
|
||||||
const maxY = Math.max(...ys)
|
const height = Math.max(...ys) - minY
|
||||||
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 cx = (canvas?.getWidth?.() ?? 1000) / 2
|
const cx = (canvas?.getWidth?.() ?? 1000) / 2
|
||||||
const cy = (canvas?.getHeight?.() ?? 1000) / 2
|
const cy = (canvas?.getHeight?.() ?? 1000) / 2
|
||||||
|
|
||||||
return points.map((p) => ({
|
return points.map((p) => ({
|
||||||
x: cx + (p.x - (minX + width / 2)) * scale,
|
x: cx + (p.x - (minX + width / 2)),
|
||||||
y: cy + (p.y - (minY + height / 2)) * scale,
|
y: cy + (p.y - (minY + height / 2)),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,6 +97,7 @@ export function usePdfImport() {
|
|||||||
const canvas = useRecoilValue(canvasState)
|
const canvas = useRecoilValue(canvasState)
|
||||||
const setOuterLinePoints = useSetRecoilState(outerLinePointsState)
|
const setOuterLinePoints = useSetRecoilState(outerLinePointsState)
|
||||||
const setOuterLineFix = useSetRecoilState(outerLineFixState)
|
const setOuterLineFix = useSetRecoilState(outerLineFixState)
|
||||||
|
const setCanvasZoom = useSetRecoilState(canvasZoomState)
|
||||||
const isOutlineDisplay = useRecoilValue(outlineDisplaySelector)
|
const isOutlineDisplay = useRecoilValue(outlineDisplaySelector)
|
||||||
const { addLine } = useLine()
|
const { addLine } = useLine()
|
||||||
const { swalFire } = useSwal()
|
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 = {}) => {
|
const applyOuterline = async (mmPoints, options = {}) => {
|
||||||
if (!canvas) {
|
if (!canvas) {
|
||||||
logger.warn('[usePdfImport] canvas 가 준비되지 않았습니다.')
|
logger.warn('[usePdfImport] canvas 가 준비되지 않았습니다.')
|
||||||
@ -202,7 +217,7 @@ export function usePdfImport() {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
canvasPoints = fitAndCenter(canvasPoints, canvas)
|
canvasPoints = centerOnCanvas(canvasPoints, canvas)
|
||||||
canvasPoints = ensureCounterClockwise(canvasPoints)
|
canvasPoints = ensureCounterClockwise(canvasPoints)
|
||||||
|
|
||||||
const proceed = async () => {
|
const proceed = async () => {
|
||||||
@ -218,6 +233,7 @@ export function usePdfImport() {
|
|||||||
canvas.outerLineFix = true
|
canvas.outerLineFix = true
|
||||||
setOuterLineFix(true)
|
setOuterLineFix(true)
|
||||||
|
|
||||||
|
fitViewport(closedPoints)
|
||||||
canvas.renderAll()
|
canvas.renderAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user