Compare commits
3 Commits
76a53c64d9
...
cd95b68d96
| Author | SHA1 | Date | |
|---|---|---|---|
| cd95b68d96 | |||
| a332737654 | |||
| 4f120e6e11 |
@ -2,7 +2,7 @@ import { fabric } from 'fabric'
|
|||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { QLine } from '@/components/fabric/QLine'
|
import { QLine } from '@/components/fabric/QLine'
|
||||||
import { distanceBetweenPoints, findTopTwoIndexesByDistance, getDirectionByPoint, sortedPointLessEightPoint, sortedPoints } from '@/util/canvas-util'
|
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 * as turf from '@turf/turf'
|
||||||
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
|
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
|
||||||
import Big from 'big.js'
|
import Big from 'big.js'
|
||||||
@ -120,6 +120,20 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
this.adjustRoofLines = []
|
this.adjustRoofLines = []
|
||||||
// this.colorLines = []
|
// 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) => {
|
points.forEach((point) => {
|
||||||
point.x = Number(point.x.toFixed(this.toFixed))
|
point.x = Number(point.x.toFixed(this.toFixed))
|
||||||
|
|||||||
@ -33,7 +33,7 @@ import {
|
|||||||
import { QLine } from '@/components/fabric/QLine'
|
import { QLine } from '@/components/fabric/QLine'
|
||||||
import { fabric } from 'fabric'
|
import { fabric } from 'fabric'
|
||||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
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 { isObjectNotEmpty } from '@/util/common-utils'
|
||||||
import * as turf from '@turf/turf'
|
import * as turf from '@turf/turf'
|
||||||
import { INPUT_TYPE, LINE_TYPE, Mode, POLYGON_TYPE } from '@/common/common'
|
import { INPUT_TYPE, LINE_TYPE, Mode, POLYGON_TYPE } from '@/common/common'
|
||||||
@ -1505,7 +1505,16 @@ export function useMode() {
|
|||||||
offsetPoints.push(offsetPoint)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1730,12 +1739,27 @@ export function useMode() {
|
|||||||
// edge[i] 가 tiny 이면 시작 정점 vertex[i] 를 제거해 앞쪽 edge 와 병합한다.
|
// edge[i] 가 tiny 이면 시작 정점 vertex[i] 를 제거해 앞쪽 edge 와 병합한다.
|
||||||
// 이렇게 하면 남는 cleaned edge 의 line 매핑(cleanedLines[k]=lines[kept[k]]) 이
|
// 이렇게 하면 남는 cleaned edge 의 line 매핑(cleanedLines[k]=lines[kept[k]]) 이
|
||||||
// edge[i+1] (실제 긴 edge) 의 line 을 쓰게 되므로 offset 계산이 안정해진다.
|
// edge[i+1] (실제 긴 edge) 의 line 을 쓰게 되므로 offset 계산이 안정해진다.
|
||||||
|
// [tiny edge axis guard 2026-05-13] 단, 제거 후 새로 생기는 엣지가 axis-aligned 인 경우에만 안전.
|
||||||
|
// 직각 노치(예: H 91mm + V 24.5mm 코너) 의 24.5mm edge 를 단순 제거하면 v[i-1]→v[i+1] 가
|
||||||
|
// 15° 대각선이 되어 inset+clipOffsetToOriginal 후 mm 단위 zigzag 토막으로 보임 (RACK-TILT).
|
||||||
|
// 원본 데이터가 wallBaseLine 의 axis-aligned 직각 노치를 보존해야 하므로, 대각선 결과가 되면 skip.
|
||||||
const TINY_EDGE_THRESHOLD = 30
|
const TINY_EDGE_THRESHOLD = 30
|
||||||
|
const AXIS_TOL = 0.5
|
||||||
for (let i = 0; i < n; i++) {
|
for (let i = 0; i < n; i++) {
|
||||||
if (edges[i].length < TINY_EDGE_THRESHOLD) {
|
if (edges[i].length < TINY_EDGE_THRESHOLD) {
|
||||||
|
const prev = (i + n - 1) % n
|
||||||
|
const nxt = (i + 1) % n
|
||||||
|
const vPrev = vertices[prev]
|
||||||
|
const vNext = vertices[nxt]
|
||||||
|
// 새 엣지 v[prev]→v[nxt] 가 수평 또는 수직인 경우만 머지 (drift artifact 흡수).
|
||||||
|
// 그 외(직각 노치 사이의 short step) 는 진짜 geometry 이므로 보존.
|
||||||
|
const isAxisAligned =
|
||||||
|
Math.abs(vPrev.y - vNext.y) < AXIS_TOL || Math.abs(vPrev.x - vNext.x) < AXIS_TOL
|
||||||
|
if (isAxisAligned) {
|
||||||
removeIndices.add(i)
|
removeIndices.add(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (removeIndices.size === 0) return { vertices, lines }
|
if (removeIndices.size === 0) return { vertices, lines }
|
||||||
|
|
||||||
@ -5100,6 +5124,7 @@ export function useMode() {
|
|||||||
|
|
||||||
roofs.forEach((roof, index) => {
|
roofs.forEach((roof, index) => {
|
||||||
// const offsetPolygonPoint = offsetPolygon(roof.points(), -20) //이동되서 찍을라고 바꿈
|
// const offsetPolygonPoint = offsetPolygon(roof.points(), -20) //이동되서 찍을라고 바꿈
|
||||||
|
// [1956-RACK-TILT 2026-05-13] trestle name 으로 QPolygon.initialize 가 축-스냅 처리
|
||||||
const offsetPolygonPoint = offsetPolygon(roof.getCurrentPoints(), -20)
|
const offsetPolygonPoint = offsetPolygon(roof.getCurrentPoints(), -20)
|
||||||
|
|
||||||
const trestlePoly = new QPolygon(offsetPolygonPoint, {
|
const trestlePoly = new QPolygon(offsetPolygonPoint, {
|
||||||
|
|||||||
@ -405,6 +405,57 @@ export function cleanSelfIntersectingPolygon(vertices) {
|
|||||||
return 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) {
|
export default function offsetPolygon(vertices, offset) {
|
||||||
const polygon = createPolygon(vertices)
|
const polygon = createPolygon(vertices)
|
||||||
const arcSegments = 0
|
const arcSegments = 0
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user