[2294] 케라바 출폭 surgical — vExt cascade + corner snap + ExtRidge 연결
This commit is contained in:
parent
44401a3cc1
commit
8ef0810058
@ -1839,7 +1839,9 @@ export function useEavesGableEdit(id) {
|
|||||||
if (typeof il.setCoords === 'function') il.setCoords()
|
if (typeof il.setCoords === 'function') il.setCoords()
|
||||||
const newSz = calcLinePlaneSize({ x1: il.x1, y1: il.y1, x2: il.x2, y2: il.y2 })
|
const newSz = calcLinePlaneSize({ x1: il.x1, y1: il.y1, x2: il.x2, y2: il.y2 })
|
||||||
il.attributes = { ...il.attributes, planeSize: newSz, actualSize: newSz }
|
il.attributes = { ...il.attributes, planeSize: newSz, actualSize: newSz }
|
||||||
trimCascadePts.push(oldPt)
|
// [KERAB-EXTRIDGE-CONNECT 2026-06-01] cascade hide 시 ExtRidge 끝점을 vExt 끝점(=ip)로 연장.
|
||||||
|
// ExtRidge 와 vExt 끝점이 분리돼 split graph 의 sub-roof 외곽이 안 닫히는 문제 해소.
|
||||||
|
trimCascadePts.push({ pt: oldPt, newPt: { x: ip.x, y: ip.y } })
|
||||||
newTrimRecords.push({
|
newTrimRecords.push({
|
||||||
line: il,
|
line: il,
|
||||||
end: trimEnd,
|
end: trimEnd,
|
||||||
@ -1856,8 +1858,9 @@ export function useEavesGableEdit(id) {
|
|||||||
const cascadeHidden = new Set()
|
const cascadeHidden = new Set()
|
||||||
let cascadeGuard = 0
|
let cascadeGuard = 0
|
||||||
while (trimCascadePts.length && cascadeGuard++ < 200) {
|
while (trimCascadePts.length && cascadeGuard++ < 200) {
|
||||||
const pt = trimCascadePts.shift()
|
const entry = trimCascadePts.shift()
|
||||||
if (!pt) continue
|
if (!entry) continue
|
||||||
|
const pt = entry.pt || entry
|
||||||
for (const il of roof.innerLines || []) {
|
for (const il of roof.innerLines || []) {
|
||||||
if (!il || il.visible === false) continue
|
if (!il || il.visible === false) continue
|
||||||
if (cascadeHidden.has(il)) continue
|
if (cascadeHidden.has(il)) continue
|
||||||
@ -1880,7 +1883,7 @@ export function useEavesGableEdit(id) {
|
|||||||
'[KERAB-VALLEY-EXT-TRIM] cascade hide ' +
|
'[KERAB-VALLEY-EXT-TRIM] cascade hide ' +
|
||||||
JSON.stringify({ lineName: il.lineName, x1: il.x1, y1: il.y1, x2: il.x2, y2: il.y2 }),
|
JSON.stringify({ lineName: il.lineName, x1: il.x1, y1: il.y1, x2: il.x2, y2: il.y2 }),
|
||||||
)
|
)
|
||||||
trimCascadePts.push(m1 ? { x: il.x2, y: il.y2 } : { x: il.x1, y: il.y1 })
|
trimCascadePts.push({ pt: m1 ? { x: il.x2, y: il.y2 } : { x: il.x1, y: il.y1 } })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// [KERAB-VALLEY-EXT-TRIM 2026-05-29] revert 위해 target.__valleyExtTrims 에 누적.
|
// [KERAB-VALLEY-EXT-TRIM 2026-05-29] revert 위해 target.__valleyExtTrims 에 누적.
|
||||||
|
|||||||
@ -129,7 +129,9 @@ export const applyKerabOffsetSurgical = (canvas, target, newOffset) => {
|
|||||||
(il.lineName === 'kerabPatternRidge' ||
|
(il.lineName === 'kerabPatternRidge' ||
|
||||||
il.lineName === 'kerabPatternExtRidge' ||
|
il.lineName === 'kerabPatternExtRidge' ||
|
||||||
il.lineName === 'kerabPatternHip' ||
|
il.lineName === 'kerabPatternHip' ||
|
||||||
il.lineName === 'kerabPatternExtHip')
|
il.lineName === 'kerabPatternExtHip' ||
|
||||||
|
il.lineName === 'kerabPatternValleyExt' ||
|
||||||
|
il.lineName === 'kerabValleyOverlapLine')
|
||||||
const oldSegDx = oldCorner2.x - oldCorner1.x
|
const oldSegDx = oldCorner2.x - oldCorner1.x
|
||||||
const oldSegDy = oldCorner2.y - oldCorner1.y
|
const oldSegDy = oldCorner2.y - oldCorner1.y
|
||||||
const oldSegLen2 = oldSegDx * oldSegDx + oldSegDy * oldSegDy || 1
|
const oldSegLen2 = oldSegDx * oldSegDx + oldSegDy * oldSegDy || 1
|
||||||
@ -150,18 +152,87 @@ export const applyKerabOffsetSurgical = (canvas, target, newOffset) => {
|
|||||||
if (!il) continue
|
if (!il) continue
|
||||||
|
|
||||||
// 케라바 패턴 라인: 옛 roofLine segment 위 끝점 → 새 segment 의 동일 t 점.
|
// 케라바 패턴 라인: 옛 roofLine segment 위 끝점 → 새 segment 의 동일 t 점.
|
||||||
// 절삭/복원 흐름 skip.
|
// 한 끝만 segment 위(vExt 등 self-extension)일 때는 다른 끝도 같은 변위로 평행 이동 →
|
||||||
|
// 직각/형상 보존. 절삭/복원 흐름 skip.
|
||||||
if (isKerabPatternLine(il)) {
|
if (isKerabPatternLine(il)) {
|
||||||
|
const oldX1 = il.x1
|
||||||
|
const oldY1 = il.y1
|
||||||
|
const oldX2 = il.x2
|
||||||
|
const oldY2 = il.y2
|
||||||
const np1 = mapToNewSeg({ x: il.x1, y: il.y1 })
|
const np1 = mapToNewSeg({ x: il.x1, y: il.y1 })
|
||||||
if (np1) il.set({ x1: np1.x, y1: np1.y })
|
|
||||||
const np2 = mapToNewSeg({ x: il.x2, y: il.y2 })
|
const np2 = mapToNewSeg({ x: il.x2, y: il.y2 })
|
||||||
if (np2) il.set({ x2: np2.x, y2: np2.y })
|
if (np1 && np2) {
|
||||||
|
il.set({ x1: np1.x, y1: np1.y, x2: np2.x, y2: np2.y })
|
||||||
|
} else if (np1 && !np2) {
|
||||||
|
const dx = np1.x - il.x1
|
||||||
|
const dy = np1.y - il.y1
|
||||||
|
il.set({ x1: np1.x, y1: np1.y, x2: il.x2 + dx, y2: il.y2 + dy })
|
||||||
|
} else if (!np1 && np2) {
|
||||||
|
const dx = np2.x - il.x2
|
||||||
|
const dy = np2.y - il.y2
|
||||||
|
il.set({ x1: il.x1 + dx, y1: il.y1 + dy, x2: np2.x, y2: np2.y })
|
||||||
|
}
|
||||||
if (typeof il.setCoords === 'function') il.setCoords()
|
if (typeof il.setCoords === 'function') il.setCoords()
|
||||||
if (np1 || np2) {
|
if (np1 || np2) {
|
||||||
logger.log(
|
logger.log(
|
||||||
'[KERAB-PATTERN-CORNER-SNAP] mapped ' +
|
'[KERAB-PATTERN-CORNER-SNAP] mapped ' +
|
||||||
JSON.stringify({ lineName: il.lineName, np1, np2, newPts: { x1: il.x1, y1: il.y1, x2: il.x2, y2: il.y2 } }),
|
JSON.stringify({ lineName: il.lineName, np1, np2, newPts: { x1: il.x1, y1: il.y1, x2: il.x2, y2: il.y2 } }),
|
||||||
)
|
)
|
||||||
|
// [KERAB-PATTERN-CASCADE 2026-06-01] vExt 등 평행 이동 시 옛 끝점에 닿아있던
|
||||||
|
// 다른 innerLine 끝점도 같은 변위로 평행 이동. RG-1 의 valley-trim 결과 끝점이
|
||||||
|
// vExt 끝점과 분리되어 split graph 의 closed path 안 만들어지는 문제 해소.
|
||||||
|
// cascade: vExt 옛 segment 위에 끝점이 있는 다른 innerLine 도 같은 변위로 평행 이동.
|
||||||
|
// 끝점-끝점 일치 외에 segment 위 중간 점(예: kLine 끝점이 vExt segment 위)도 매칭.
|
||||||
|
// 케라바 패턴 라인 가드 제거 — 자체 매핑된 라인은 이미 새 좌표라 옛 segment 위 X → 자동 skip.
|
||||||
|
const dxVExt = il.x1 - oldX1
|
||||||
|
const dyVExt = il.y1 - oldY1
|
||||||
|
const pointOnOldSeg = (px, py) => {
|
||||||
|
const sdx = oldX2 - oldX1
|
||||||
|
const sdy = oldY2 - oldY1
|
||||||
|
const slen2 = sdx * sdx + sdy * sdy
|
||||||
|
if (slen2 < 1e-6) return Math.hypot(px - oldX1, py - oldY1) < 1.0
|
||||||
|
const t = ((px - oldX1) * sdx + (py - oldY1) * sdy) / slen2
|
||||||
|
if (t < -0.02 || t > 1.02) return false
|
||||||
|
const projX = oldX1 + t * sdx
|
||||||
|
const projY = oldY1 + t * sdy
|
||||||
|
return Math.hypot(px - projX, py - projY) < 1.0
|
||||||
|
}
|
||||||
|
if (Math.hypot(dxVExt, dyVExt) > 0.01) {
|
||||||
|
// cascade 대상: roof.innerLines + canvas 의 kerabValleyOverlapLine (innerLines 미포함).
|
||||||
|
// overlap 보조선들은 vExt 의 끝점과 90도로 만나므로 vExt 이동에 같이 따라가야 정합.
|
||||||
|
const overlapInCanvas = (canvas.getObjects() || []).filter(
|
||||||
|
(o) =>
|
||||||
|
o &&
|
||||||
|
o.lineName === 'kerabValleyOverlapLine' &&
|
||||||
|
(o.roofId === roof.id || o.attributes?.roofId === roof.id),
|
||||||
|
)
|
||||||
|
const cascadeTargets = [...(roof.innerLines || []), ...overlapInCanvas]
|
||||||
|
for (const other of cascadeTargets) {
|
||||||
|
if (!other || other === il) continue
|
||||||
|
let moved = false
|
||||||
|
if (pointOnOldSeg(other.x1, other.y1)) {
|
||||||
|
other.set({ x1: other.x1 + dxVExt, y1: other.y1 + dyVExt })
|
||||||
|
moved = true
|
||||||
|
}
|
||||||
|
if (pointOnOldSeg(other.x2, other.y2)) {
|
||||||
|
other.set({ x2: other.x2 + dxVExt, y2: other.y2 + dyVExt })
|
||||||
|
moved = true
|
||||||
|
}
|
||||||
|
if (moved) {
|
||||||
|
if (typeof other.setCoords === 'function') other.setCoords()
|
||||||
|
logger.log(
|
||||||
|
'[KERAB-PATTERN-CASCADE] moved ' +
|
||||||
|
JSON.stringify({
|
||||||
|
lineName: other.lineName,
|
||||||
|
name: other.name,
|
||||||
|
dx: dxVExt,
|
||||||
|
dy: dyVExt,
|
||||||
|
newPts: { x1: other.x1, y1: other.y1, x2: other.x2, y2: other.y2 },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -169,6 +240,41 @@ export const applyKerabOffsetSurgical = (canvas, target, newOffset) => {
|
|||||||
const orig = il.__shrinkOrig || { x1: il.x1, y1: il.y1, x2: il.x2, y2: il.y2 }
|
const orig = il.__shrinkOrig || { x1: il.x1, y1: il.y1, x2: il.x2, y2: il.y2 }
|
||||||
const orig1 = { x: orig.x1, y: orig.y1 }
|
const orig1 = { x: orig.x1, y: orig.y1 }
|
||||||
const orig2 = { x: orig.x2, y: orig.y2 }
|
const orig2 = { x: orig.x2, y: orig.y2 }
|
||||||
|
|
||||||
|
// [KERAB-OFFSET-CORNER-SHORTCUT 2026-06-01] orig 끝점이 옛 corner 와 일치하면 새 corner 로 직접 snap.
|
||||||
|
// 일반 절삭 흐름은 il segment 와 새 roofLine 변의 lineLineIntersection 으로 ip 계산하는데,
|
||||||
|
// 끝점이 옛 corner 위에 있으면 ip 가 새 corner segment 밖으로 떨어져 segOk 가 reject → 절삭 실패.
|
||||||
|
// 그 결과 c1·c2 비대칭으로 kLine 대각선 변형됨.
|
||||||
|
const CORNER_SNAP_TOL_TRIM = 0.5
|
||||||
|
let cornerSnapped = false
|
||||||
|
const trySnap = (epx, epy, which) => {
|
||||||
|
if (Math.hypot(epx - oldCorner1.x, epy - oldCorner1.y) < CORNER_SNAP_TOL_TRIM) {
|
||||||
|
if (!il.__shrinkOrig) il.__shrinkOrig = { x1: orig.x1, y1: orig.y1, x2: orig.x2, y2: orig.y2 }
|
||||||
|
if (which === 1) il.set({ x1: newCorner1.x, y1: newCorner1.y })
|
||||||
|
else il.set({ x2: newCorner1.x, y2: newCorner1.y })
|
||||||
|
cornerSnapped = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (Math.hypot(epx - oldCorner2.x, epy - oldCorner2.y) < CORNER_SNAP_TOL_TRIM) {
|
||||||
|
if (!il.__shrinkOrig) il.__shrinkOrig = { x1: orig.x1, y1: orig.y1, x2: orig.x2, y2: orig.y2 }
|
||||||
|
if (which === 1) il.set({ x1: newCorner2.x, y1: newCorner2.y })
|
||||||
|
else il.set({ x2: newCorner2.x, y2: newCorner2.y })
|
||||||
|
cornerSnapped = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
trySnap(orig.x1, orig.y1, 1)
|
||||||
|
trySnap(orig.x2, orig.y2, 2)
|
||||||
|
if (cornerSnapped) {
|
||||||
|
if (typeof il.setCoords === 'function') il.setCoords()
|
||||||
|
logger.log(
|
||||||
|
'[KERAB-OFFSET-CORNER-SHORTCUT] snapped ' +
|
||||||
|
JSON.stringify({ lineName: il.lineName, x1: il.x1, y1: il.y1, x2: il.x2, y2: il.y2 }),
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
const d1 = (orig1.x - newAxisMid.x) * nx + (orig1.y - newAxisMid.y) * ny
|
const d1 = (orig1.x - newAxisMid.x) * nx + (orig1.y - newAxisMid.y) * ny
|
||||||
const d2 = (orig2.x - newAxisMid.x) * nx + (orig2.y - newAxisMid.y) * ny
|
const d2 = (orig2.x - newAxisMid.x) * nx + (orig2.y - newAxisMid.y) * ny
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user