feat(pdf-import): 외곽선 기하 후처리(축정렬 스냅·단순화·자기교차 복구) 추가
- normalizeGeometry 단계 신설: snapNearAxisEdges(축정렬 스냅) → turf.simplify(과다 정점 단순화) → cleanSelfIntersectingPolygon(자기교차 복구) - applyOuterline의 중복점 제거 직후·fitAndCenter 전에 삽입(스냅→단순화 순서로 축정렬 보존) - 기존 검증 순수함수 재사용, 신규 의존성 없음 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
afda525be9
commit
0418dd84ee
@ -7,10 +7,14 @@ import { usePolygon } from '@/hooks/usePolygon'
|
|||||||
import { useSwal } from '@/hooks/useSwal'
|
import { useSwal } from '@/hooks/useSwal'
|
||||||
import { useMessage } from '@/hooks/useMessage'
|
import { useMessage } from '@/hooks/useMessage'
|
||||||
import { logger } from '@/util/logger'
|
import { logger } from '@/util/logger'
|
||||||
|
import { snapNearAxisEdges, cleanSelfIntersectingPolygon } from '@/util/qpolygon-utils'
|
||||||
|
import * as turf from '@turf/turf'
|
||||||
|
|
||||||
const MM_PER_CANVAS_UNIT = 10
|
const MM_PER_CANVAS_UNIT = 10
|
||||||
const MAX_DIM = 1500
|
const MAX_DIM = 1500
|
||||||
const MIN_CANVAS_UNIT_LENGTH = 0.5
|
const MIN_CANVAS_UNIT_LENGTH = 0.5
|
||||||
|
const SIMPLIFY_TOLERANCE = 0.5 // canvas unit ≈ 5mm. 직선 위 잉여 정점/미세 엣지 제거(보수적)
|
||||||
|
const SNAP_ANGLE_TOL_DEG = 2 // 거의 수평/수직 엣지를 정확히 축정렬로 스냅
|
||||||
|
|
||||||
const toCanvasUnits = (mmPoints) => mmPoints.map((p) => ({ x: p.x / MM_PER_CANVAS_UNIT, y: p.y / MM_PER_CANVAS_UNIT }))
|
const toCanvasUnits = (mmPoints) => mmPoints.map((p) => ({ x: p.x / MM_PER_CANVAS_UNIT, y: p.y / MM_PER_CANVAS_UNIT }))
|
||||||
|
|
||||||
@ -57,6 +61,30 @@ const fitAndCenter = (points, canvas) => {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const simplifyRing = (points, tolerance) => {
|
||||||
|
if (points.length < 4) return points
|
||||||
|
try {
|
||||||
|
const coords = points.map((p) => [p.x, p.y])
|
||||||
|
coords.push([points[0].x, points[0].y])
|
||||||
|
const simplified = turf.simplify(turf.polygon([coords]), { tolerance, highQuality: true })
|
||||||
|
const ring = simplified.geometry.coordinates[0]
|
||||||
|
const out = ring.slice(0, -1).map((c) => ({ x: c[0], y: c[1] }))
|
||||||
|
return out.length >= 3 ? out : points
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn('[usePdfImport] simplifyRing 실패', e?.message || e)
|
||||||
|
return points
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AI 추출 좌표의 결정적 정규화: 축정렬 스냅 → 과다정점 단순화 → 자기교차 복구.
|
||||||
|
// 순서 주의 — 스냅(정점 이동) 후 단순화(정점 제거)해야 축정렬이 보존된다.
|
||||||
|
const normalizeGeometry = (points) => {
|
||||||
|
let pts = snapNearAxisEdges(points, SNAP_ANGLE_TOL_DEG)
|
||||||
|
pts = simplifyRing(pts, SIMPLIFY_TOLERANCE)
|
||||||
|
pts = cleanSelfIntersectingPolygon(pts)
|
||||||
|
return pts.length >= 3 ? pts : points
|
||||||
|
}
|
||||||
|
|
||||||
export function usePdfImport() {
|
export function usePdfImport() {
|
||||||
const canvas = useRecoilValue(canvasState)
|
const canvas = useRecoilValue(canvasState)
|
||||||
const setOuterLinePoints = useSetRecoilState(outerLinePointsState)
|
const setOuterLinePoints = useSetRecoilState(outerLinePointsState)
|
||||||
@ -133,6 +161,7 @@ export function usePdfImport() {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canvasPoints = normalizeGeometry(canvasPoints)
|
||||||
canvasPoints = fitAndCenter(canvasPoints, canvas)
|
canvasPoints = fitAndCenter(canvasPoints, canvas)
|
||||||
canvasPoints = ensureCounterClockwise(canvasPoints)
|
canvasPoints = ensureCounterClockwise(canvasPoints)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user