diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index 29868836..a0fcb57f 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -2,7 +2,7 @@ import { fabric } from 'fabric' import { v4 as uuidv4 } from 'uuid' import { QLine } from '@/components/fabric/QLine' import { distanceBetweenPoints, findTopTwoIndexesByDistance, getDirectionByPoint, sortedPointLessEightPoint, sortedPoints } from '@/util/canvas-util' -import { calculateAngle, drawGableRoof, drawRoofByAttribute, drawShedRoof, toGeoJSON } from '@/util/qpolygon-utils' +import { calculateAngle, drawGableRoof, drawRoofByAttribute, drawShedRoof, snapNearAxisEdges, toGeoJSON } from '@/util/qpolygon-utils' import * as turf from '@turf/turf' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import Big from 'big.js' @@ -120,6 +120,20 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { this.adjustRoofLines = [] // this.colorLines = [] + // [1956-RACK-TILT 2026-05-13] 빨강 점선 폴리곤군(가대선/모듈설치면) FP drift 보정. + // - trestle, dormerTrestle: createRoofRack 출력 + // - trestlePolygon: useMode.handleOuterlinesTest 출력 + // - moduleSetupSurface: useModuleBasicSetting (eavesMargin/ridgeMargin/kerabaMargin 적용) + // 가로/세로 edge 의 FP drift 가 저장된 JSON 에 박혀있는 경우 복원 시 라인이 기울어 보이는 문제. + if ( + options.name === 'trestle' || + options.name === 'dormerTrestle' || + options.name === 'trestlePolygon' || + options.name === 'moduleSetupSurface' + ) { + points = snapNearAxisEdges(points, 2) + } + // 소수점 전부 제거 points.forEach((point) => { point.x = Number(point.x.toFixed(this.toFixed)) diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js index 4bb91ba8..af134ece 100644 --- a/src/hooks/useMode.js +++ b/src/hooks/useMode.js @@ -33,7 +33,7 @@ import { import { QLine } from '@/components/fabric/QLine' import { fabric } from 'fabric' import { QPolygon } from '@/components/fabric/QPolygon' -import offsetPolygon, { calculateAngle } from '@/util/qpolygon-utils' +import offsetPolygon, { calculateAngle, snapNearAxisEdges } from '@/util/qpolygon-utils' import { isObjectNotEmpty } from '@/util/common-utils' import * as turf from '@turf/turf' import { INPUT_TYPE, LINE_TYPE, Mode, POLYGON_TYPE } from '@/common/common' @@ -1505,7 +1505,16 @@ export function useMode() { offsetPoints.push(offsetPoint) } - return makePolygon(offsetPoints, false) + // [1956-RACK-TILT 2026-05-13] trestlePolygon(モジュール配置領域 빨강점선) FP drift 보정. + // 입력 polygon.lines 의 미세 drift 가 평균법선 offset 으로 전파되어 가로/세로 라인이 + // 기울어 보이는 문제 차단. tolerance 0.5mm 보다 작은 dy/dx 만 축으로 스냅. + const _snapped = snapNearAxisEdges( + offsetPoints.map((p) => ({ x: p.x1, y: p.y1 })), + 2, + ) + const snappedOffsetPoints = _snapped.map((p) => ({ x1: p.x, y1: p.y })) + + return makePolygon(snappedOffsetPoints, false) } /** @@ -5115,6 +5124,7 @@ export function useMode() { roofs.forEach((roof, index) => { // const offsetPolygonPoint = offsetPolygon(roof.points(), -20) //이동되서 찍을라고 바꿈 + // [1956-RACK-TILT 2026-05-13] trestle name 으로 QPolygon.initialize 가 축-스냅 처리 const offsetPolygonPoint = offsetPolygon(roof.getCurrentPoints(), -20) const trestlePoly = new QPolygon(offsetPolygonPoint, { diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index dccd3414..5fc1a2e7 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -405,6 +405,57 @@ export function cleanSelfIntersectingPolygon(vertices) { return vertices } +/** + * [1956-RACK-TILT 2026-05-13] 거의 축에 평행한 edge 를 정확히 수평/수직으로 스냅. + * 빨강 점선(가대선/모듈설치면) edge 가 FP drift 누적으로 미세 기울어져 보이는 문제 보정. + * **각도 기반 판정**: |dy|/len < sin(angleTolDeg) 면 수평으로 간주. + * 절대값 tolerance 가 아닌 각도 임계를 쓰는 이유 — 긴 edge(예: 576mm) 에 작은 누적 + * drift(15mm) 가 실려 closing edge 가 흡수하면 절대값은 크지만 각도는 작음(1.5°). + * vertex 단위로 인접 edge 의 축 목표값을 적용해 코너 어긋남 방지. + * 대각(hip/ridge, 45° 등) 은 임계 2° 보다 훨씬 크니 무영향. + */ +export function snapNearAxisEdges(vertices, angleTolDeg = 2) { + if (!vertices || vertices.length < 3) return vertices + const n = vertices.length + const sinTol = Math.sin((angleTolDeg * Math.PI) / 180) + + const edgeY = new Array(n).fill(null) // 거의 수평 → 목표 y + const edgeX = new Array(n).fill(null) // 거의 수직 → 목표 x + + for (let i = 0; i < n; i++) { + const a = vertices[i] + const b = vertices[(i + 1) % n] + const dx = b.x - a.x + const dy = b.y - a.y + const len = Math.sqrt(dx * dx + dy * dy) + if (len < 1) continue // 0-length edge 안전장치 + const absSinDy = Math.abs(dy) / len // sin(angle from horizontal) + const absSinDx = Math.abs(dx) / len // sin(angle from vertical) + if (absSinDy < sinTol && absSinDx >= sinTol) { + edgeY[i] = (a.y + b.y) / 2 // 거의 수평 + } else if (absSinDx < sinTol && absSinDy >= sinTol) { + edgeX[i] = (a.x + b.x) / 2 // 거의 수직 + } + } + + return vertices.map((v, i) => { + const prev = (i - 1 + n) % n + let nx = v.x + let ny = v.y + const yPrev = edgeY[prev] + const yCur = edgeY[i] + if (yPrev !== null && yCur !== null) ny = (yPrev + yCur) / 2 + else if (yPrev !== null) ny = yPrev + else if (yCur !== null) ny = yCur + const xPrev = edgeX[prev] + const xCur = edgeX[i] + if (xPrev !== null && xCur !== null) nx = (xPrev + xCur) / 2 + else if (xPrev !== null) nx = xPrev + else if (xCur !== null) nx = xCur + return { x: nx, y: ny } + }) +} + export default function offsetPolygon(vertices, offset) { const polygon = createPolygon(vertices) const arcSegments = 0