Merge pull request '[2296] 수동배치 모듈 간 이격 미검증 수정 — 배치 게이트에 isTooCloseToOtherModules 추가' (#941) from dev into prd-deploy

Reviewed-on: #941
This commit is contained in:
ysCha 2026-07-01 10:45:39 +09:00
commit 281ae3e027

View File

@ -34,6 +34,7 @@ import { useCircuitTrestle } from '@/hooks/useCirCuitTrestle'
import { useMode } from '@/hooks/useMode'
import { useTurf } from '@/hooks/common/useTurf'
import { useUndoRedo } from '@/hooks/useUndoRedo'
// import { debugCapture } from '@/util/debugCapture' // [MODULE-PLACE-DIAG 2026-07-01] 수동배치 경계 초과 조사용 (재활성 시 주석 해제)
export function useModuleBasicSetting(tabNum) {
const canvas = useRecoilValue(canvasState)
@ -296,6 +297,44 @@ export function useModuleBasicSetting(tabNum) {
}
}
// [PLACE-CLEARANCE 2026-07-01] 手動配置 시 モジュール間離隔(縦 moduleIntvlVer / 横 moduleIntvlHor) 미검증 보완.
// 겹침(booleanOverlap)·外周離隔(setupSurface)만 검사해, 인접 모듈에 0간격 밀착 스냅해도 통과하던 버그.
// [2296-2] dfa90812 의 移動 수정(useModule.js:isTooCloseToOtherModules)과 동일 로직 — 진입점만 배치.
// ⚠ 간격은 stroke 를 뺀 실제 정점(getCurrentPoints)으로 잰다. getBoundingRect 는 strokeWidth 팽창 → 정상 격자도 위반 오판.
const isTooCloseToOtherModules = (module, otherModules, surface) => {
if (!otherModules || otherModules.length === 0) return false
const gapVer = Number(surface?.trestleDetail?.moduleIntvlVer ?? 0) / 10 // mm → canvas unit
const gapHor = Number(surface?.trestleDetail?.moduleIntvlHor ?? 0) / 10
if (gapVer <= 0 && gapHor <= 0) return false
const EPS = 0.05 // 0.5mm — 좌표 반올림 잔차(±0.02) 흡수
const bboxOf = (o) => {
const pts = o.getCurrentPoints ? o.getCurrentPoints() : null
if (pts && pts.length) {
const xs = pts.map((p) => p.x)
const ys = pts.map((p) => p.y)
return { l: Math.min(...xs), r: Math.max(...xs), t: Math.min(...ys), b: Math.max(...ys) }
}
const r = o.getBoundingRect(true, true)
return { l: r.left, r: r.left + r.width, t: r.top, b: r.top + r.height }
}
const mb = bboxOf(module)
return otherModules.some((o) => {
if (o.id === module.id) return false
const nb = bboxOf(o)
const overlapX = mb.l < nb.r - EPS && nb.l < mb.r - EPS
const overlapY = mb.t < nb.b - EPS && nb.t < mb.b - EPS
if (overlapX) {
const vGap = mb.t >= nb.b ? mb.t - nb.b : nb.t >= mb.b ? nb.t - mb.b : 0
if (vGap < gapVer - EPS) return true
}
if (overlapY) {
const hGap = mb.l >= nb.r ? mb.l - nb.r : nb.l >= mb.r ? nb.l - mb.r : 0
if (hGap < gapHor - EPS) return true
}
return false
})
}
//선택 배치면 배열`
let selectedModuleInstSurfaceArray = []
@ -477,6 +516,25 @@ export function useModuleBasicSetting(tabNum) {
})
setupSurface.setViewLengthText(false)
// [MODULE-PLACE-DIAG 2026-07-01] setupSurface 인셋 확인 로그 (재활성 시 주석 해제 + import 복원)
// {
// const _bbox = (pts) => {
// const xs = pts.map((p) => p.x)
// const ys = pts.map((p) => p.y)
// return { minX: Math.min(...xs), maxX: Math.max(...xs), minY: Math.min(...ys), maxY: Math.max(...ys) }
// }
// const _roofPts = roof.getCurrentPoints()
// const _rb = _bbox(_roofPts)
// const _sb = _bbox(offsetPoints)
// debugCapture.log('[MODULE-PLACE-DIAG] setupSurface 생성', {
// roofId: roof.id, roofSizeSet: canvasSetting.roofSizeSet, pitch: roof.roofMaterial?.pitch, direction: roof.direction,
// roofBboxCanvas: { w: +(_rb.maxX - _rb.minX).toFixed(2), h: +(_rb.maxY - _rb.minY).toFixed(2) },
// setupBboxCanvas: { w: +(_sb.maxX - _sb.minX).toFixed(2), h: +(_sb.maxY - _sb.minY).toFixed(2) },
// insetTopCanvas: +(_sb.minY - _rb.minY).toFixed(2), insetBottomCanvas: +(_rb.maxY - _sb.maxY).toFixed(2),
// insetLeftCanvas: +(_sb.minX - _rb.minX).toFixed(2), insetRightCanvas: +(_rb.maxX - _sb.maxX).toFixed(2),
// lineOffsets: roof.lines.map((l) => ({ type: l.attributes?.type, offset: l.attributes?.offset })),
// })
// }
canvas.add(setupSurface) //모듈설치면 만들기
//지붕면 선택 금지
@ -998,13 +1056,35 @@ export function useModuleBasicSetting(tabNum) {
if (!isIntersection) return
tempModule.setCoords() //좌표 재정렬
// [MODULE-PLACE-DIAG 2026-07-01] 수동배치 게이트 진단 로그 (재활성 시 주석 해제 + import 복원)
// {
// const _sc = turfPolygon?.geometry?.coordinates?.[0] || []
// const _mc = tempTurfModule?.geometry?.coordinates?.[0] || []
// const _bb = (c) => c.length ? { minX: Math.min(...c.map((p) => p[0])), maxX: Math.max(...c.map((p) => p[0])), minY: Math.min(...c.map((p) => p[1])), maxY: Math.max(...c.map((p) => p[1])) } : null
// const _sb = _bb(_sc), _mb = _bb(_mc)
// debugCapture.log('[MODULE-PLACE-DIAG] 배치 게이트', {
// surfaceId: trestlePolygon?.id,
// moduleBbox: _mb && { minX: +_mb.minX.toFixed(2), maxX: +_mb.maxX.toFixed(2), minY: +_mb.minY.toFixed(2), maxY: +_mb.maxY.toFixed(2) },
// surfaceBbox: _sb && { minX: +_sb.minX.toFixed(2), maxX: +_sb.maxX.toFixed(2), minY: +_sb.minY.toFixed(2), maxY: +_sb.maxY.toFixed(2) },
// overTopCanvas: _mb && _sb ? +(_sb.minY - _mb.minY).toFixed(2) : null,
// overBottomCanvas: _mb && _sb ? +(_mb.maxY - _sb.maxY).toFixed(2) : null,
// gateSpare0: checkModuleDisjointSurface(tempTurfModule, turfPolygon, 0),
// gateSpare05: checkModuleDisjointSurface(tempTurfModule, turfPolygon, 0.5),
// isTooClose: isTooCloseToOtherModules(tempModule, manualDrawModules, trestlePolygon),
// gapVerReq: Number(trestlePolygon?.trestleDetail?.moduleIntvlVer ?? 0) / 10,
// gapHorReq: Number(trestlePolygon?.trestleDetail?.moduleIntvlHor ?? 0) / 10,
// existingModuleCount: manualDrawModules?.length,
// })
// }
// [EDGE-PLACE-TOLERANCE 2026-06-09] 手動 1枚 배치: 端部에 딱 붙인 모듈이 소수 절단 잔차(±0.02)로 屋根 경계를 살짝 넘으면
// spare=0 판정이 屋根外로 오인 → 「取り付け面からモジュールを取り付けることはできません」 경고. 屋根 절대좌표에 따라 2번째/3번째가 들쭉날쭉.
// 이동/삽입/정렬(isOutsideSurface)과 동일한 0.5 허용오차로 잔차 흡수.
if (checkModuleDisjointSurface(tempTurfModule, turfPolygon, 0.5)) {
//마우스 클릭시 set으로 해당 위치에 셀을 넣음
const isOverlap = manualDrawModules.some((module) => turf.booleanOverlap(tempTurfModule, polygonToTurfPolygon(module, true))) //겹치는지 확인
if (!isOverlap) {
// [PLACE-CLEARANCE 2026-07-01] 인접 모듈과 モジュール間離隔(縦30/横3 등) 미만이면 배치 거부
const isTooClose = isTooCloseToOtherModules(tempModule, manualDrawModules, trestlePolygon)
if (!isOverlap && !isTooClose) {
canvas?.remove(tempModule)
//안겹치면 넣는다
@ -1036,8 +1116,11 @@ export function useModuleBasicSetting(tabNum) {
// }
// getModuleStatistics()
} else {
} else if (isOverlap) {
swalFire({ text: getMessage('module.place.overlab'), icon: 'warning' })
} else {
// [PLACE-CLEARANCE 2026-07-01] 間離隔 미달 — 설치면 밖 배치와 동일하게 취급(取り付け不可)
swalFire({ text: getMessage('module.place.out'), icon: 'warning' })
}
} else {
swalFire({ text: getMessage('module.place.out'), icon: 'warning' })