dev #870
@ -1507,48 +1507,78 @@ export function useEavesGableEdit(id) {
|
||||
if (!vr.source || !vr.source.startsWith('roofBase')) continue
|
||||
const segA = { x: vr.x1, y: vr.y1 }
|
||||
const segB = { x: vr.x2, y: vr.y2 }
|
||||
// 사용자 규칙: "조건 다 필요 없고 확장하는 라인 절삭."
|
||||
// trim 검사 ray = segA(corner) → wallEnd(polygon-wall). segB(대칭중앙) 은 segA-wallEnd 의 중점.
|
||||
// ray 위에서 만나는 hip/ridge 의 진행방향 쪽 끝점(= u 투영값 큰 쪽) 을 ip 로 단축.
|
||||
// collinear (valleyExt 와 평행+동일직선) 라인은 별도 처리 — segB 너머 끝점을 segB 로 단축.
|
||||
const wallEnd = { x: 2 * segB.x - segA.x, y: 2 * segB.y - segA.y }
|
||||
const dxV = wallEnd.x - segA.x
|
||||
const dyV = wallEnd.y - segA.y
|
||||
const lenV = Math.hypot(dxV, dyV) || 1
|
||||
const ux = dxV / lenV
|
||||
const uy = dyV / lenV
|
||||
const tB = (segB.x - segA.x) * ux + (segB.y - segA.y) * uy
|
||||
const COLLINEAR_TOL = 1.0
|
||||
for (const il of roof.innerLines || []) {
|
||||
if (!il) continue
|
||||
if (il.lineName === 'kerabPatternValleyExt') continue
|
||||
if (il.name !== LINE_TYPE.SUBLINE.HIP && il.name !== LINE_TYPE.SUBLINE.RIDGE) continue
|
||||
// 사용자 규칙: "Y 모양일때 hip 있는 쪽은 살리고 마루쪽을 절삭." + "1522 도 삭제."
|
||||
// trim 대상 = ridge(마루) + 케라바 토글 생성 hip(kerabPatternHip / kerabPatternExtHip).
|
||||
// 원래 지붕 hip(H-5/H-6/H-7/H-1) 은 보존. hip-presence 룰이 자기 자신 제외하므로 안전.
|
||||
const isTrimCandidate =
|
||||
il.name === LINE_TYPE.SUBLINE.RIDGE ||
|
||||
(il.name === LINE_TYPE.SUBLINE.HIP && (il.lineName === 'kerabPatternHip' || il.lineName === 'kerabPatternExtHip'))
|
||||
if (!isTrimCandidate) continue
|
||||
if (il.visible === false) continue
|
||||
const a = { x: il.x1, y: il.y1 }
|
||||
const b = { x: il.x2, y: il.y2 }
|
||||
const ip = lineLineIntersection(segA, segB, a, b)
|
||||
if (!ip) continue
|
||||
if (!isOnSegV(ip, segA.x, segA.y, segB.x, segB.y)) continue
|
||||
if (!isOnSegV(ip, a.x, a.y, b.x, b.y)) continue
|
||||
// [KERAB-VALLEY-EXT-TRIM 2026-05-27] 끝점 연결 보존: hip/ridge 의 어느 한쪽 끝점이라도
|
||||
// 교차점과 일치(= 끝점에서 valleyExt 와 만남) → 살림.
|
||||
const TOL_EP = 1.0
|
||||
if (Math.hypot(il.x1 - ip.x, il.y1 - ip.y) < TOL_EP) continue
|
||||
if (Math.hypot(il.x2 - ip.x, il.y2 - ip.y) < TOL_EP) continue
|
||||
// [KERAB-VALLEY-EXT-TRIM-Y-GUARD 2026-05-27] Y junction 보존:
|
||||
// 사용자 규칙: "Y 모양이 있는 쪽은 살리고 그 반대쪽은 절삭."
|
||||
// hip/ridge 의 양 끝점이 모두 다른 inner line 의 끝점과 일치(= 양쪽 다 Y junction)
|
||||
// → 절삭 skip (= 정상 polygon 구조 보존).
|
||||
// 한쪽만 junction → 자유 끝점(non-Y) 쪽 단축.
|
||||
const TOL_J = 1.0
|
||||
const isOtherInnerEndAt = (px, py) => {
|
||||
let ip = lineLineIntersection(segA, wallEnd, a, b)
|
||||
if (ip) {
|
||||
if (!isOnSegV(ip, segA.x, segA.y, wallEnd.x, wallEnd.y)) ip = null
|
||||
else if (!isOnSegV(ip, a.x, a.y, b.x, b.y)) ip = null
|
||||
}
|
||||
if (!ip) {
|
||||
const perp1 = Math.abs((il.x1 - segA.x) * uy - (il.y1 - segA.y) * ux)
|
||||
const perp2 = Math.abs((il.x2 - segA.x) * uy - (il.y2 - segA.y) * ux)
|
||||
if (perp1 > COLLINEAR_TOL || perp2 > COLLINEAR_TOL) continue
|
||||
const tc1 = (il.x1 - segA.x) * ux + (il.y1 - segA.y) * uy
|
||||
const tc2 = (il.x2 - segA.x) * ux + (il.y2 - segA.y) * uy
|
||||
if (Math.max(tc1, tc2) <= tB + 0.5) continue
|
||||
ip = { x: segB.x, y: segB.y }
|
||||
}
|
||||
// 사용자 규칙: "Y 모양일때 hip 있는 쪽은 살리고 마루(ridge) 쪽을 절삭."
|
||||
// ridge 의 각 끝점에 *원래 지붕* hip 끝점이 붙어있는지 검사.
|
||||
// 케라바 토글 생성 hip(kerabPatternHip/ExtHip) 은 골짜기 부속물이므로 hip-presence 카운트 제외.
|
||||
// hip 있는 쪽 보존, 없는 쪽 단축. 양쪽 다 원래 hip → 통째 보존(continue).
|
||||
// 양쪽 다 원래 hip 없음 → Y-junction 아닌 골짜기 부속물 → 통째 hide.
|
||||
const isOriginalHipEndAt = (px, py) => {
|
||||
for (const other of roof.innerLines || []) {
|
||||
if (!other || other === il) continue
|
||||
if (other.lineName === 'kerabPatternValleyExt') continue
|
||||
if (other.name !== LINE_TYPE.SUBLINE.HIP && other.name !== LINE_TYPE.SUBLINE.RIDGE) continue
|
||||
if (other.lineName === 'kerabPatternHip' || other.lineName === 'kerabPatternExtHip') continue
|
||||
if (other.name !== LINE_TYPE.SUBLINE.HIP) continue
|
||||
if (other.visible === false) continue
|
||||
if (Math.hypot(other.x1 - px, other.y1 - py) < TOL_J) return true
|
||||
if (Math.hypot(other.x2 - px, other.y2 - py) < TOL_J) return true
|
||||
if (Math.hypot(other.x1 - px, other.y1 - py) < 1.0) return true
|
||||
if (Math.hypot(other.x2 - px, other.y2 - py) < 1.0) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
const end1J = isOtherInnerEndAt(il.x1, il.y1)
|
||||
const end2J = isOtherInnerEndAt(il.x2, il.y2)
|
||||
if (end1J && end2J) continue
|
||||
const d1 = Math.hypot(il.x1 - segA.x, il.y1 - segA.y)
|
||||
const d2 = Math.hypot(il.x2 - segA.x, il.y2 - segA.y)
|
||||
const hip1 = isOriginalHipEndAt(il.x1, il.y1)
|
||||
const hip2 = isOriginalHipEndAt(il.x2, il.y2)
|
||||
let trimEnd
|
||||
if (end1J && !end2J) trimEnd = 2
|
||||
else if (!end1J && end2J) trimEnd = 1
|
||||
else trimEnd = d1 < d2 ? 1 : 2
|
||||
if (hip1 && hip2) {
|
||||
logger.log('[KERAB-VALLEY-EXT-TRIM] both-hip preserve ' + JSON.stringify({ name: il.name, lineName: il.lineName }))
|
||||
continue
|
||||
} else if (hip1 && !hip2) {
|
||||
trimEnd = 2
|
||||
} else if (!hip1 && hip2) {
|
||||
trimEnd = 1
|
||||
} else {
|
||||
// 양쪽 다 원래 hip 없음 — 진행벡터 fallback (cascade pass 에서 부속물 정리)
|
||||
const t1 = (il.x1 - segA.x) * ux + (il.y1 - segA.y) * uy
|
||||
const t2 = (il.x2 - segA.x) * ux + (il.y2 - segA.y) * uy
|
||||
trimEnd = t1 > t2 ? 1 : 2
|
||||
}
|
||||
const oldX = trimEnd === 1 ? il.x1 : il.x2
|
||||
const oldY = trimEnd === 1 ? il.y1 : il.y2
|
||||
trimRecords.push({
|
||||
@ -1560,6 +1590,7 @@ export function useEavesGableEdit(id) {
|
||||
})
|
||||
if (trimEnd === 1) il.set({ x1: ip.x, y1: ip.y })
|
||||
else il.set({ x2: ip.x, y2: ip.y })
|
||||
if (typeof il.setCoords === 'function') il.setCoords()
|
||||
const newSz = calcLinePlaneSize({ x1: il.x1, y1: il.y1, x2: il.x2, y2: il.y2 })
|
||||
il.attributes = { ...il.attributes, planeSize: newSz, actualSize: newSz }
|
||||
logger.log(
|
||||
@ -1568,6 +1599,50 @@ export function useEavesGableEdit(id) {
|
||||
)
|
||||
}
|
||||
}
|
||||
// 사용자 규칙: "원 라인이 절삭되었으면 그 밑으로 확장된 라인도 삭제."
|
||||
// cascade pass — trim 결과 옛 끝점(oldPt) 에 끝점이 일치하는 케라바산 라인(kerabPatternHip/ExtHip/Ridge) 을 hide.
|
||||
// 가드: 원래 지붕 ridge/hip(lineName 없거나 kerabPattern* 아닌 것) 은 cascade 대상 외 — junction 공유 의도 외 절삭 방지.
|
||||
const isKerabSynthetic = (line) => {
|
||||
if (!line || !line.lineName) return false
|
||||
return (
|
||||
line.lineName === 'kerabPatternHip' ||
|
||||
line.lineName === 'kerabPatternExtHip' ||
|
||||
line.lineName === 'kerabPatternRidge' ||
|
||||
line.lineName === 'kerabPatternExtRidge'
|
||||
)
|
||||
}
|
||||
const cascadeHidden = new Set()
|
||||
// BFS — cascade hide 된 라인의 다른 끝점에 붙은 케라바산 라인도 재귀 hide.
|
||||
const cascadeQueue = trimRecords.map((r) => r.oldPt).filter(Boolean)
|
||||
let cascadeGuard = 0
|
||||
while (cascadeQueue.length && cascadeGuard++ < 200) {
|
||||
const pt = cascadeQueue.shift()
|
||||
if (!pt) continue
|
||||
for (const il of roof.innerLines || []) {
|
||||
if (!il || il.visible === false) continue
|
||||
if (cascadeHidden.has(il)) continue
|
||||
if (il.lineName === 'kerabPatternValleyExt') continue
|
||||
if (!isKerabSynthetic(il)) continue
|
||||
const m1 = Math.hypot(il.x1 - pt.x, il.y1 - pt.y) < 1.0
|
||||
const m2 = Math.hypot(il.x2 - pt.x, il.y2 - pt.y) < 1.0
|
||||
if (!m1 && !m2) continue
|
||||
cascadeHidden.add(il)
|
||||
trimRecords.push({
|
||||
line: il,
|
||||
hide: true,
|
||||
originalAttrs: { ...(il.attributes || {}) },
|
||||
originalVisible: il.visible !== false,
|
||||
})
|
||||
il.visible = false
|
||||
if (typeof il.setCoords === 'function') il.setCoords()
|
||||
logger.log(
|
||||
'[KERAB-VALLEY-EXT-TRIM] cascade hide ' +
|
||||
JSON.stringify({ name: il.name, lineName: il.lineName, x1: il.x1, y1: il.y1, x2: il.x2, y2: il.y2 }),
|
||||
)
|
||||
// 다른 끝점도 cascade 시드에 추가
|
||||
cascadeQueue.push(m1 ? { x: il.x2, y: il.y2 } : { x: il.x1, y: il.y1 })
|
||||
}
|
||||
}
|
||||
if (trimRecords.length) target.__valleyExtTrims = trimRecords
|
||||
|
||||
for (const vr of valleyExtensions) {
|
||||
@ -2210,9 +2285,14 @@ export function useEavesGableEdit(id) {
|
||||
const rec = target.__valleyExtTrims[i]
|
||||
const il = rec.line
|
||||
if (!il) continue
|
||||
if (rec.end === 1) il.set({ x1: rec.oldPt.x, y1: rec.oldPt.y })
|
||||
else il.set({ x2: rec.oldPt.x, y2: rec.oldPt.y })
|
||||
if (rec.hide) {
|
||||
il.visible = rec.originalVisible !== false
|
||||
} else {
|
||||
if (rec.end === 1) il.set({ x1: rec.oldPt.x, y1: rec.oldPt.y })
|
||||
else il.set({ x2: rec.oldPt.x, y2: rec.oldPt.y })
|
||||
}
|
||||
if (rec.originalAttrs) il.attributes = rec.originalAttrs
|
||||
if (typeof il.setCoords === 'function') il.setCoords()
|
||||
}
|
||||
logger.log('[KERAB-VALLEY-EXT-TRIM] revert restored ' + target.__valleyExtTrims.length + ' trim(s)')
|
||||
delete target.__valleyExtTrims
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user