import { useState } from 'react' import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' import { canvasSettingState, canvasState, checkedModuleState, currentObjectState, isManualModuleSetupState } from '@/store/canvasAtom' import { rectToPolygon, polygonToTurfPolygon, calculateVisibleModuleHeight, getDegreeByChon } from '@/util/canvas-util' import { basicSettingState, roofDisplaySelector } from '@/store/settingAtom' import offsetPolygon, { calculateAngle, createLinesFromPolygon } from '@/util/qpolygon-utils' import { QPolygon } from '@/components/fabric/QPolygon' import { moduleSetupSurfaceState, moduleIsSetupState } from '@/store/canvasAtom' import { useEvent } from '@/hooks/useEvent' import { POLYGON_TYPE, BATCH_TYPE, LINE_TYPE } from '@/common/common' import * as turf from '@turf/turf' import { useSwal } from '@/hooks/useSwal' import { compasDegAtom } from '@/store/orientationAtom' import { QLine } from '@/components/fabric/QLine' import { useRoofFn } from '@/hooks/common/useRoofFn' import { useEffect } from 'react' import { useMessage } from '@/hooks/useMessage' import { moduleStatisticsState } from '@/store/circuitTrestleAtom' import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions' import { useMasterController } from '@/hooks/common/useMasterController' import { v4 as uuidv4 } from 'uuid' import { isObjectNotEmpty } from '@/util/common-utils' import { useCircuitTrestle } from '@/hooks/useCirCuitTrestle' import { useMode } from '@/hooks/useMode' export function useModuleBasicSetting(tabNum) { const canvas = useRecoilValue(canvasState) const { getMessage } = useMessage() const roofDisplay = useRecoilValue(roofDisplaySelector) const [moduleSetupSurface, setModuleSetupSurface] = useRecoilState(moduleSetupSurfaceState) const { addCanvasMouseEventListener, initEvent, removeMouseEvent, addTargetMouseEventListener } = useEvent() const { swalFire } = useSwal() const compasDeg = useRecoilValue(compasDegAtom) const { setSurfaceShapePattern } = useRoofFn() const checkedModule = useRecoilValue(checkedModuleState) const [isManualModuleSetup, setIsManualModuleSetup] = useRecoilState(isManualModuleSetupState) const setModuleStatistics = useSetRecoilState(moduleStatisticsState) const canvasSetting = useRecoilValue(canvasSettingState) const moduleSelectionData = useRecoilValue(moduleSelectionDataState) const [trestleDetailParams, setTrestleDetailParams] = useState([]) const [trestleDetailList, setTrestleDetailList] = useState([]) const selectedModules = useRecoilValue(selectedModuleState) const { getTrestleDetailList } = useMasterController() const [saleStoreNorthFlg, setSaleStoreNorthFlg] = useState(false) const [currentObject, setCurrentObject] = useRecoilState(currentObjectState) const { setModuleStatisticsData } = useCircuitTrestle() const { createRoofPolygon, createMarginPolygon, createPaddingPolygon } = useMode() useEffect(() => { // console.log('basicSetting', basicSetting) if (canvas) { //드래그 여부 // canvas.selection = true // canvas.selectionFullyContained = true } return () => { //수동 설치시 초기화 removeMouseEvent('mouse:up') removeMouseEvent('mouse:move') canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tempModule')) //움직일때 일단 지워가면서 움직임 } }, []) // const { addTargetMouseEventListener, addCanvasMouseEventListener, initEvent } = useContext(EventContext) //모듈 선택에서 선택된 값들 넘어옴 const makeModuleInitArea = () => { if (isObjectNotEmpty(moduleSelectionData) && tabNum === 3) { if (canvasSetting.roofSizeSet !== '3') { const common = moduleSelectionData.common const roofConstructions = moduleSelectionData.roofConstructions if (roofConstructions && roofConstructions.length > 0) { const listParams = roofConstructions.map((item) => { return { ...common, roofMatlCd: item.trestle.roofMatlCd, trestleMkrCd: item.trestle.trestleMkrCd, constMthdCd: item.trestle.constMthdCd, roofBaseCd: item.trestle.roofBaseCd, constTp: item.construction.constTp, mixMatlNo: selectedModules.mixMatlNo, roofPitch: item.addRoof.hajebichi ? item.addRoof.hajebichi : 0, inclCd: String(item.addRoof.pitch), roofIndex: item.addRoof.index, workingWidth: item.addRoof.lenBase, raftBaseCd: item.trestle.raftBaseCd, } }) setTrestleDetailParams(listParams) //북면 설치 가능 판매점 if (moduleSelectionData.common.saleStoreNorthFlg === '1') { setSaleStoreNorthFlg(true) } } } else { //육지붕 일경우에는 바로 배치면 설치LL canvas .getObjects() .filter((roof) => roof.name === 'roof') .forEach((roof) => { makeModuleInstArea(roof, null) }) } } } //가대 상세 데이터 조회 const getTrestleDetailListData = async () => { const trestleDetailList = await getTrestleDetailList(trestleDetailParams) if (trestleDetailList.length > 0) { setTrestleDetailList(trestleDetailList) } } //가대 상세 데이터 파라메터 담기면 실행 useEffect(() => { if (trestleDetailParams.length > 0) { getTrestleDetailListData(trestleDetailParams) } }, [trestleDetailParams]) //가대 상세 데이터 들어오면 실행 useEffect(() => { if (trestleDetailList.length > 0) { console.log('trestleDetailList', trestleDetailList) //지붕을 가져옴 canvas .getObjects() .filter((roof) => roof.name === 'roof') .forEach((roof) => { if (!roof.roofMaterial) return const roofIndex = roof.roofMaterial.index //지붕의 지붕재의 순번 trestleDetailList.forEach((detail) => { if (detail.data !== null) { if (Number(detail.data.roofIndex) === roofIndex) { //roof에 상세 데이터 추가 roof.set({ trestleDetail: detail.data }) roof.lines.forEach((line) => { line.attributes = { ...line.attributes, offset: getOffset(detail.data, line.attributes.type), } }) //배치면 설치 영역 makeModuleInstArea(roof, detail.data) //surface에 상세 데이터 추가 } } }) }) } }, [trestleDetailList]) const getOffset = (data, type) => { switch (type) { case LINE_TYPE.WALLLINE.EAVES: return data.eaveIntvl / 10 case LINE_TYPE.WALLLINE.GABLE: return data.kerabaIntvl / 10 case LINE_TYPE.SUBLINE.RIDGE: case LINE_TYPE.WALLLINE.SHED: return data.ridgeIntvl / 10 default: return 200 / 10 } } //선택 배치면 배열` let selectedModuleInstSurfaceArray = [] //가대 상세 데이터 기준으로 모듈 설치 배치면 생성 const makeModuleInstArea = (roof, trestleDetail) => { //지붕 객체 반환 if (tabNum == 3) { if (!roof) { return } //도머등 오브젝트 객체가 있으면 아웃라인 낸다 const batchObjects = canvas ?.getObjects() .filter( (obj) => obj.name === BATCH_TYPE.OPENING || obj.name === BATCH_TYPE.SHADOW || obj.name === BATCH_TYPE.TRIANGLE_DORMER || obj.name === BATCH_TYPE.PENTAGON_DORMER, ) //도머s 객체 //도머도 외곽을 따야한다 const batchObjectOptions = { stroke: 'red', fill: 'transparent', strokeDashArray: [10, 4], strokeWidth: 1, lockMovementX: true, lockMovementY: true, lockRotation: true, lockScalingX: true, lockScalingY: true, selectable: true, name: POLYGON_TYPE.OBJECT_SURFACE, originX: 'center', originY: 'center', } //도머등 오브젝트 객체가 있으면 아웃라인 낸다 batchObjects.forEach((obj) => { //도머일때 if (obj.name === BATCH_TYPE.TRIANGLE_DORMER || obj.name === BATCH_TYPE.PENTAGON_DORMER) { const groupPoints = obj.groupPoints const offsetObjects = offsetPolygon(groupPoints, 10) const dormerOffset = new QPolygon(offsetObjects, batchObjectOptions) dormerOffset.setViewLengthText(false) canvas.add(dormerOffset) //모듈설치면 만들기 } else { //개구, 그림자일때 const points = obj.points const offsetObjects = offsetPolygon(points, 10) const offset = new QPolygon(offsetObjects, batchObjectOptions) offset.setViewLengthText(false) canvas.add(offset) //모듈설치면 만들기 } }) const isExistSurface = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && obj.parentId === roof.id) if (isExistSurface) { addTargetMouseEventListener('mousedown', isExistSurface, function () { toggleSelection(isExistSurface) }) } else { setSurfaceShapePattern(roof, roofDisplay.column, true, roof.roofMaterial) //패턴 변경 // let offsetPoints = createPaddingPolygon(createRoofPolygon(roof.points), roof.lines).vertices //안쪽 offset let offsetPoints = null console.log(roof, roof.getCurrentPoints()) const polygon = createRoofPolygon(roof.getCurrentPoints()) const originPolygon = new QPolygon(roof.getCurrentPoints(), { fontSize: 0 }) let result = createPaddingPolygon(polygon, roof.lines).vertices //margin polygon 의 point가 기준 polygon의 밖에 있는지 판단한다. const allPointsOutside = result.every((point) => !originPolygon.inPolygon(point)) if (canvasSetting.roofSizeSet == '3') { //육지붕일때는 그냥 하드코딩 offsetPoints = offsetPolygon(roof.points, -30) //육지붕일때 } else { //육지붕이 아닐때 if (allPointsOutside) { offsetPoints = createMarginPolygon(polygon, roof.lines).vertices } else { offsetPoints = createPaddingPolygon(polygon, roof.lines).vertices } } //모듈설치영역?? 생성 const surfaceId = uuidv4() let isNorth = false if (canvasSetting.roofSizeSet != '3') { //북면이 있지만 if (roof.directionText && roof.directionText.indexOf('北') > -1) { //북쪽일때 해당 서북서, 동북동은 제외한다고 한다 if (!(roof.directionText.indexOf('西北西') > -1 || roof.directionText.indexOf('東北東') > -1)) { isNorth = true } } } //모듈설치면 생성 let setupSurface = new QPolygon(offsetPoints, { stroke: 'red', fill: 'rgba(255,255,255,0.1)', strokeDashArray: [10, 4], strokeWidth: 1, lockMovementX: true, lockMovementY: true, lockRotation: true, lockScalingX: true, lockScalingY: true, selectable: true, parentId: roof.id, //가대 폴리곤의 임시 인덱스를 넣어줌 name: POLYGON_TYPE.MODULE_SETUP_SURFACE, flowDirection: roof.direction, direction: roof.direction, flipX: roof.flipX, flipY: roof.flipY, surfaceId: surfaceId, originX: 'center', originY: 'center', modules: [], roofMaterial: roof.roofMaterial, trestleDetail: trestleDetail, isNorth: isNorth, perPixelTargetFind: true, // angle: -compasDeg, }) setupSurface.setViewLengthText(false) canvas.add(setupSurface) //모듈설치면 만들기 //지붕면 선택 금지 roof.set({ selectable: false, //선택 금지 // evented: false, //클릭 이벤트도 금지 }) canvas.renderAll() //바로 들어올때 const setupModules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE) const eaveBars = canvas.getObjects().filter((obj) => obj.name === 'eaveBar' || obj.name === 'halfEaveBar') const racks = canvas.getObjects().filter((obj) => obj.name === 'rack' || obj.name === 'smartRack') const brackets = canvas.getObjects().filter((obj) => obj.name === 'brackets') setupModules.forEach((obj) => { canvas.bringToFront(obj) }) eaveBars.forEach((obj) => { canvas.bringToFront(obj) }) racks.forEach((obj) => { canvas.bringToFront(obj) }) brackets.forEach((obj) => { canvas.bringToFront(obj) }) //모듈설치면 클릭이벤트 addTargetMouseEventListener('mousedown', setupSurface, function () { toggleSelection(setupSurface) }) } } //설치 범위 지정 클릭 이벤트 const toggleSelection = (setupSurface) => { const isExist = selectedModuleInstSurfaceArray.some((obj) => obj.parentId === setupSurface.parentId) //최초 선택일때 if (!isExist) { //설치면이 북면이고 북면설치 허용점이 아니면 if (setupSurface.isNorth && !saleStoreNorthFlg) { swalFire({ text: getMessage('module.not.batch.north'), icon: 'warning' }) return } //기본 선택이랑 스트로크 굵기가 같으면 선택 안됨으로 봄 setupSurface.set({ ...setupSurface, strokeWidth: 3, strokeDashArray: [0], fill: 'rgba(255,255,255,0.1)', }) canvas.discardActiveObject() // 객체의 활성 상태 해제 //중복으로 들어가는걸 방지하기 위한 코드 canvas?.renderAll() selectedModuleInstSurfaceArray.push(setupSurface) setCurrentObject({ name: 'moduleSetupSurface', arrayData: [...selectedModuleInstSurfaceArray] }) } else { //선택후 재선택하면 선택안됨으로 변경 setupSurface.set({ ...setupSurface, fill: 'rgba(255,255,255,0.1)', strokeDashArray: [10, 4], strokeWidth: 1, }) canvas.discardActiveObject() // 객체의 활성 상태 해제 //폴리곤에 커스텀 인덱스를 가지고 해당 배열 인덱스를 찾아 삭제함 const removeIndex = setupSurface.parentId const removeArrayIndex = selectedModuleInstSurfaceArray.findIndex((obj) => obj.parentId === removeIndex) selectedModuleInstSurfaceArray.splice(removeArrayIndex, 1) setCurrentObject({ name: 'moduleSetupSurface', arrayData: [...selectedModuleInstSurfaceArray] }) } canvas?.renderAll() setModuleSetupSurface([...selectedModuleInstSurfaceArray]) } } //모듈,회로에서 다른메뉴 -> 배치면으로 갈 경수 초기화 const restoreModuleInstArea = () => { //설치면 삭제 const setupArea = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE) //모듈 삭제 및 초기화 setupArea.forEach((obj) => { if (obj.modules.length > 0) { obj.modules.forEach((module) => { canvas.remove(module) }) } canvas.remove(obj) obj.modules = [] }) //지붕패턴 변경 const roofs = canvas.getObjects().filter((obj) => obj.name === 'roof') roofs.forEach((roof) => { setSurfaceShapePattern(roof, roofDisplay.column, false) //패턴 변경 }) } useEffect(() => { if (canvasSetting.roofSizeSet !== '3') { if (isObjectNotEmpty(moduleSelectionData) && moduleSelectionData.common.saleStoreNorthFlg === '1') { setSaleStoreNorthFlg(true) } } }, [isManualModuleSetup]) /** * trestle에서 영역을 가져와 mouse:move 이벤트로 해당 영역에 진입했을때 booleanPointInPolygon 로 진입여부를 확인 * 확인 후 셀을 이동시킴 */ const manualModuleSetup = (placementRef) => { const moduleSetupSurfaces = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE) //모듈설치면를 가져옴 if (isManualModuleSetup) { if (checkedModule.length === 0) { swalFire({ text: getMessage('module.place.select.module') }) setIsManualModuleSetup(!isManualModuleSetup) return } if (checkedModule.length > 1) { swalFire({ text: getMessage('module.place.select.one.module') }) setIsManualModuleSetup(!isManualModuleSetup) return } const batchObjects = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.OBJECT_SURFACE) //도머s 객체 //수동모드 모듈 설치면 선택 잠금 moduleSetupSurfaces.forEach((obj) => { obj.set({ selectable: false, evented: false, }) }) //모듈 기본 옵션 const moduleOptions = { fill: checkedModule[0].color, stroke: 'black', strokeWidth: 0.3, selectable: true, // 선택 가능하게 설정 lockMovementX: true, // X 축 이동 잠금 lockMovementY: true, // Y 축 이동 잠금 lockRotation: true, // 회전 잠금 lockScalingX: true, // X 축 크기 조정 잠금 lockScalingY: true, // Y 축 크기 조정 잠금 name: POLYGON_TYPE.MODULE, } if (moduleSetupSurfaces.length !== 0) { let tempModule let manualDrawModules = [] let inside = false let turfPolygon let flowDirection let trestlePolygon addCanvasMouseEventListener('mouse:move', (e) => { //마우스 이벤트 삭제 후 재추가 const mousePoint = canvas.getPointer(e.e) for (let i = 0; i < moduleSetupSurfaces.length; i++) { //배치면이 여러개 일때 옮겨가면서 동작해야함 turfPolygon = polygonToTurfPolygon(moduleSetupSurfaces[i]) trestlePolygon = moduleSetupSurfaces[i] manualDrawModules = moduleSetupSurfaces[i].modules // 앞에서 자동으로 했을때 추가됨 flowDirection = moduleSetupSurfaces[i].direction //도형의 방향 const moduleWidth = Number(checkedModule[0].longAxis) / 10 const moduleHeight = Number(checkedModule[0].shortAxis) / 10 let tmpWidth = flowDirection === 'south' || flowDirection === 'north' ? moduleWidth : moduleHeight let tmpHeight = flowDirection === 'south' || flowDirection === 'north' ? moduleHeight : moduleWidth let { width, height } = canvasSetting.roofSizeSet == '1' ? calculateVisibleModuleHeight(tmpWidth, tmpHeight, getDegreeByChon(moduleSetupSurfaces[i].roofMaterial.pitch), flowDirection) : { width: tmpWidth, height: tmpHeight } const points = [ { x: Number(mousePoint.x.toFixed(1)) + Number((width / 2).toFixed(1)), y: Number(mousePoint.y.toFixed(1)) - Number((height / 2).toFixed(1)), }, { x: Number(mousePoint.x.toFixed(1)) + Number((width / 2).toFixed(1)), y: Number(mousePoint.y.toFixed(1)) + Number((height / 2).toFixed(1)), }, { x: Number(mousePoint.x.toFixed(1)) - Number((width / 2).toFixed(1)), y: Number(mousePoint.y.toFixed(1)) - Number((height / 2).toFixed(1)), }, { x: Number(mousePoint.x.toFixed(1)) - Number((width / 2).toFixed(1)), y: Number(mousePoint.y.toFixed(1)) + Number((height / 2).toFixed(1)), }, ] const turfPoints = coordToTurfPolygon(points) if (turf.booleanWithin(turfPoints, turfPolygon)) { let isDrawing = false if (isDrawing) return canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tempModule')) //움직일때 일단 지워가면서 움직임 tempModule = new fabric.Rect({ fill: 'white', stroke: 'black', strokeWidth: 0.3, width: Number(width.toFixed(1)), height: Number(height.toFixed(1)), left: Number(mousePoint.x.toFixed(1) - Number((width / 2).toFixed(1))), top: Number(mousePoint.y.toFixed(1) - Number((height / 2).toFixed(1))), selectable: false, lockMovementX: true, lockMovementY: true, lockRotation: true, lockScalingX: true, lockScalingY: true, name: 'tempModule', parentId: moduleSetupSurfaces[i].parentId, }) //북면이고 북면설치상점이 아니면 그냥 return if (trestlePolygon.isNorth && !saleStoreNorthFlg) { return } else { canvas?.add(tempModule) //움직여가면서 추가됨 } /** * 스냅기능 */ let snapDistance = flowDirection === 'south' || flowDirection === 'north' ? 70 : 40 let trestleSnapDistance = 15 let intvHor = flowDirection === 'south' || flowDirection === 'north' ? moduleSetupSurfaces[i].trestleDetail.moduleIntvlHor / 10 : moduleSetupSurfaces[i].trestleDetail.moduleIntvlVer / 10 let intvVer = flowDirection === 'south' || flowDirection === 'north' ? moduleSetupSurfaces[i].trestleDetail.moduleIntvlVer / 10 : moduleSetupSurfaces[i].trestleDetail.moduleIntvlHor / 10 const trestleLeft = Number(moduleSetupSurfaces[i].left.toFixed(1)) - Number((moduleSetupSurfaces[i].width / 2).toFixed(1)) const trestleTop = Number(moduleSetupSurfaces[i].top.toFixed(1)) - Number((moduleSetupSurfaces[i].height / 2).toFixed(1)) const trestleRight = Number(moduleSetupSurfaces[i].left.toFixed(1)) + Number((moduleSetupSurfaces[i].width / 2).toFixed(1)) const trestleBottom = Number(moduleSetupSurfaces[i].top.toFixed(1)) + Number((moduleSetupSurfaces[i].height / 2).toFixed(1)) const bigCenterY = (trestleTop + trestleTop + moduleSetupSurfaces[i].height) / 2 // 이동하는 모듈의 경계 좌표 const smallLeft = Number(tempModule.left.toFixed(1)) const smallTop = Number(tempModule.top.toFixed(1)) const smallRight = smallLeft + Number(tempModule.width.toFixed(1)) const smallBottom = smallTop + Number(tempModule.height.toFixed(1)) const smallCenterX = smallLeft + Number((tempModule.width / 2).toFixed(1)) const smallCenterY = smallTop + Number((tempModule.height / 2).toFixed(1)) /** * 미리 깔아놓은 셀이 있을때 셀에 흡착됨 */ if (manualDrawModules) { manualDrawModules.forEach((cell) => { const holdCellLeft = cell.left const holdCellTop = cell.top const holdCellRight = holdCellLeft + Number(cell.width.toFixed(1)) const holdCellBottom = holdCellTop + Number(cell.height.toFixed(1)) const holdCellCenterX = holdCellLeft + Number((cell.width / 2).toFixed(1)) const holdCellCenterY = holdCellTop + Number((cell.height / 2).toFixed(1)) //설치된 셀에 좌측에 스냅 if (Math.abs(smallRight - holdCellLeft) < snapDistance) { tempModule.left = holdCellLeft - width - intvHor } //설치된 셀에 우측에 스냅 if (Math.abs(smallLeft - holdCellRight) < snapDistance) { tempModule.left = holdCellRight + intvHor } //설치된 셀에 위쪽에 스냅 if (Math.abs(smallBottom - holdCellTop) < snapDistance) { tempModule.top = holdCellTop - height - intvVer } //설치된 셀에 밑쪽에 스냅 if (Math.abs(smallTop - holdCellBottom) < snapDistance) { tempModule.top = holdCellBottom + intvVer } //가운데 -> 가운데 if (Math.abs(smallCenterX - holdCellCenterX) < snapDistance) { tempModule.left = holdCellCenterX - Number((width / 2).toFixed(1)) } //왼쪽 -> 가운데 if (Math.abs(smallLeft - holdCellCenterX) < snapDistance) { tempModule.left = holdCellCenterX } // 오른쪽 -> 가운데 if (Math.abs(smallRight - holdCellCenterX) < snapDistance) { tempModule.left = holdCellCenterX - width } //세로 가운데 -> 가운데 if (Math.abs(smallCenterY - holdCellCenterY) < snapDistance) { tempModule.top = holdCellCenterY - Number((height / 2).toFixed(1)) } // //위쪽 -> 가운데 // if (Math.abs(smallTop - holdCellCenterY) < cellSnapDistance) { // tempModule.top = holdCellCenterY // } // //아랫쪽 -> 가운데 // if (Math.abs(smallBottom - holdCellCenterY) < cellSnapDistance) { // tempModule.top = holdCellCenterY - height // } }) } // 위쪽 변에 스냅 if (Math.abs(smallTop - trestleTop) < trestleSnapDistance) { tempModule.top = trestleTop + 1 } // 아래쪽 변에 스냅 if (Math.abs(smallBottom - trestleBottom) < trestleSnapDistance) { tempModule.top = trestleTop + moduleSetupSurfaces[i].height - tempModule.height - 1 } // 왼쪽변에 스냅 if (Math.abs(smallLeft - trestleLeft) < trestleSnapDistance) { tempModule.left = trestleLeft + 1 } //오른쪽 변에 스냅 if (Math.abs(smallRight - trestleRight) < trestleSnapDistance) { tempModule.left = trestleRight - tempModule.width - 1 } if (flowDirection === 'south' || flowDirection === 'north') { // 모듈왼쪽이 세로중앙선에 붙게 스냅 // if (Math.abs(smallLeft - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < snapDistance) { // tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2 // } // 모듈이 가운데가 세로중앙선에 붙게 스냅 if (Math.abs(smallCenterX - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < trestleSnapDistance) { tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2 - tempModule.width / 2 } // 모듈오른쪽이 세로중앙선에 붙게 스냅 // if (Math.abs(smallRight - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < trestleSnapDistance) { // tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2 - tempModule.width * tempModule.scaleX // } } else { // 모듈이 가로중앙선에 스냅 if (Math.abs(smallTop + tempModule.height / 2 - bigCenterY) < trestleSnapDistance) { tempModule.top = bigCenterY - tempModule.height / 2 } // if (Math.abs(smallTop - (trestleTop + moduleSetupSurfaces[i].height / 2)) < trestleSnapDistance) { // tempModule.top = trestleTop + moduleSetupSurfaces[i].height / 2 // } // 모듈 밑면이 가로중앙선에 스냅 // if (Math.abs(smallBottom - (trestleTop + moduleSetupSurfaces[i].height / 2)) < trestleSnapDistance) { // tempModule.top = trestleTop + moduleSetupSurfaces[i].height / 2 - tempModule.height * tempModule.scaleY // } } tempModule.setCoords() canvas?.renderAll() inside = true break } else { inside = false } } if (!inside) { // tempModule.set({ fill: 'red' }) canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tempModule')) canvas?.renderAll() } }) addCanvasMouseEventListener('mouse:up', (e) => { let isIntersection = true if (!inside) return if (tempModule) { const rectPoints = [ { x: Number(tempModule.left.toFixed(1)), y: Number(tempModule.top.toFixed(1)) }, { x: Number(tempModule.left.toFixed(1)) + Number(tempModule.width.toFixed(1)), y: Number(tempModule.top.toFixed(1)) }, { x: Number(tempModule.left.toFixed(1)) + Number(tempModule.width.toFixed(1)), y: Number(tempModule.top.toFixed(1)) + Number(tempModule.height.toFixed(1)), }, { x: Number(tempModule.left.toFixed(1)), y: Number(tempModule.top.toFixed(1)) + Number(tempModule.height.toFixed(1)) }, ] tempModule.set({ points: rectPoints }) const tempTurfModule = polygonToTurfPolygon(tempModule) //도머 객체를 가져옴 if (batchObjects) { batchObjects.forEach((object) => { let dormerTurfPolygon = polygonToTurfPolygon(object, true) const intersection = turf.intersect(turf.featureCollection([dormerTurfPolygon, tempTurfModule])) //겹치는지 확인 //겹치면 안됨 if (intersection) { swalFire({ text: getMessage('module.place.overobject') }) isIntersection = false } }) } if (!isIntersection) return tempModule.setCoords() //좌표 재정렬 if (turf.booleanContains(turfPolygon, tempTurfModule) || turf.booleanWithin(tempTurfModule, turfPolygon)) { //마우스 클릭시 set으로 해당 위치에 셀을 넣음 const isOverlap = manualDrawModules.some((module) => turf.booleanOverlap(tempTurfModule, polygonToTurfPolygon(module))) //겹치는지 확인 if (!isOverlap) { canvas?.remove(tempModule) //안겹치면 넣는다 // tempModule.setCoords() moduleOptions.surfaceId = trestlePolygon.id // console.log('tempModule.points', tempModule.points) let manualModule = new QPolygon(tempModule.points, { ...moduleOptions, moduleInfo: checkedModule[0], left: Number(tempModule.left.toFixed(1)), top: Number(tempModule.top.toFixed(1)), width: Number(tempModule.width.toFixed(1)), height: Number(tempModule.height.toFixed(1)), }) canvas?.add(manualModule) manualDrawModules.push(manualModule) setModuleStatisticsData() // getModuleStatistics() } else { swalFire({ text: getMessage('module.place.overlab') }) } } else { swalFire({ text: getMessage('module.place.out') }) } } }) } } else { if (moduleSetupSurfaces) { //수동모드 해제시 모듈 설치면 선택 잠금 moduleSetupSurfaces.forEach((obj) => { obj.set({ selectable: true, evented: true, }) }) } removeMouseEvent('mouse:up') removeMouseEvent('mouse:move') canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tempModule')) //움직일때 일단 지워가면서 움직임 } } //자동 모듈 설치(그리드 방식) const autoModuleSetup = (placementRef) => { initEvent() //마우스 이벤트 초기화 if (checkedModule.length === 0) { swalFire({ text: getMessage('module.place.select.module') }) return } const isChidori = placementRef.isChidori.current === 'true' ? true : false const setupLocation = placementRef.setupLocation.current const isMaxSetup = placementRef.isMaxSetup.current === 'true' ? true : false const moduleSetupSurfaces = moduleSetupSurface //선택 설치면 const notSelectedTrestlePolygons = canvas ?.getObjects() .filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && !moduleSetupSurfaces.includes(obj)) //설치면이 아닌것 const batchObjects = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.OBJECT_SURFACE) //도머s 객체 if (moduleSetupSurfaces.length === 0) { swalFire({ text: getMessage('module.place.no.surface') }) return } //어짜피 자동으로 누르면 선택안된데도 다 날아간다 canvas.getObjects().forEach((obj) => { if (obj.name === 'module') { canvas.remove(obj) } }) // if (moduleIsSetup.length > 0) { // swalFire({ text: 'alert 아이콘 테스트입니다.', icon: 'error' }) // } // moduleSetupSurfaces.forEach((obj) => { // if (obj.modules) { // obj.modules.forEach((module) => { // canvas?.remove(module) // }) // obj.modules = [] // } // }) notSelectedTrestlePolygons.forEach((obj) => { if (obj.modules) { obj.modules.forEach((module) => { canvas?.remove(module) }) obj.modules = [] } }) let moduleOptions = { stroke: 'black', strokeWidth: 0.3, selectable: true, // 선택 가능하게 설정 lockMovementX: true, // X 축 이동 잠금 lockMovementY: true, // Y 축 이동 잠금 lockRotation: true, // 회전 잠금 lockScalingX: true, // X 축 크기 조정 잠금 lockScalingY: true, // Y 축 크기 조정 잠금 name: 'module', } let leftMargin, bottomMargin, square, chidoriLength //선택된 지붕안에 오브젝트(도머, 개구등)이 있는지 확인하는 로직 포함되면 배열 반환 const objectsIncludeSurface = (turfModuleSetupSurface) => { let containsBatchObjects = [] containsBatchObjects = batchObjects.filter((batchObject) => { let convertBatchObject = polygonToTurfPolygon(batchObject) // 폴리곤 안에 도머 폴리곤이 포함되어있는지 확인해서 반환하는 로직 return ( turf.booleanContains(turfModuleSetupSurface, convertBatchObject) || turf.booleanWithin(convertBatchObject, turfModuleSetupSurface) || turf.booleanOverlap(turfModuleSetupSurface, convertBatchObject) ) }) return containsBatchObjects } // /** // * 도머나 개구가 모듈에 걸치는지 확인하는 로직 // * @param {*} squarePolygon // * @param {*} containsBatchObjects // * @returns // */ // const checkModuleDisjointObjects = (squarePolygon, containsBatchObjects) => { // let isDisjoint = false // // if (containsBatchObjects.length > 0) { // let convertBatchObject // //도머가 있으면 적용되는 로직 // isDisjoint = containsBatchObjects.every((batchObject) => { // if (batchObject.type === 'group') { // convertBatchObject = batchObjectGroupToTurfPolygon(batchObject) // } else { // convertBatchObject = polygonToTurfPolygon(batchObject) // } // /** // * 도머가 여러개일수있으므로 겹치는게 있다면... // * 안겹치는지 확인하는 로직이라 안겹치면 true를 반환 // */ // return turf.booleanDisjoint(squarePolygon, convertBatchObject) // }) // } else { // isDisjoint = true // } // return isDisjoint // } /** * 배치면 안에 있는지 확인 * @param {*} squarePolygon * @param {*} turfModuleSetupSurface * @returns */ const checkModuleDisjointSurface = (squarePolygon, turfModuleSetupSurface) => { return turf.booleanContains(turfModuleSetupSurface, squarePolygon) || turf.booleanWithin(squarePolygon, turfModuleSetupSurface) } //흐름 방향이 남쪽(아래) const downFlowSetupModule = ( surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, isCenter = false, intvHor, intvVer, ) => { let setupModule = [] const trestleDetailData = moduleSetupSurface.trestleDetail const moduleMaxCols = trestleDetailData.moduleMaxCols // 모듈 최대 가로 설치 갯수 let installedLastHeightCoord = 0 //마지막으로 설치된 모듈의 좌표 let installedModuleHeightCount = 0 //마지막으로 설치된 모듈의 카운트 let isChidoriLine = false let flowLines checkedModule.forEach((module, moduleIndex) => { const tmpModuleData = trestleDetailData.module.filter((moduleObj) => module.moduleTpCd === moduleObj.moduleTpCd)[0] //혼합모듈일때는 mixModuleMaxRows 값이 0 이상임 let moduleMaxRows = tmpModuleData.mixModuleMaxRows === 0 ? tmpModuleData.moduleMaxRows : tmpModuleData.mixModuleMaxRows //모듈의 넓이 높이를 가져옴 (복시도 촌수 적용) //1번 깔았던 모듈 기준으로 잡야아함 let { width, height } = getModuleWidthHeight(maxLengthLine, moduleSetupSurface, module) if (moduleIndex === 0) { flowLines = getFlowLines(moduleSetupSurface, height) if (flowLines.bottom.type === 'curve') { flowLines = getFlowLines(moduleSetupSurface, width) } } //육지붕이 아닐때만 넣는다 육지붕일땐 클릭 이벤트에 별도로 넣어놓음 const moduleArray = [] let calcAreaWidth = Math.abs(flowLines.right.x1 - flowLines.left.x1) //오른쪽 x에서 왼쪽 x를 뺀 가운데를 찾는 로직 let calcModuleWidthCount = calcAreaWidth / (width + intvHor) //뺀 공간에서 모듈을 몇개를 넣을수 있는지 확인하는 로직 let calcMaxModuleWidthCount = calcModuleWidthCount > moduleMaxCols ? moduleMaxCols : calcModuleWidthCount //최대 모듈 단수가 있기 때문에 최대 단수보다 카운트가 크면 최대 단수로 씀씀 let totalModuleWidthCount = Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 let calcAreaHeight = flowLines.bottom.y1 - flowLines.top.y1 let calcModuleHeightCount = calcAreaHeight / (height + intvVer) let calcStartPoint = flowLines.right.type === 'flat' ? (calcAreaWidth - totalModuleWidthCount * width) / 2 : 0 //반씩 나눠서 중앙에 맞춤 bottom 기준으로 양변이 직선일때만 가운데 정렬 let startPointX = flowLines.left.x1 + calcStartPoint //시작점을 만든다 //근데 양변이 곡선이면 중앙에 맞추기 위해 아래와 위의 길이를 재서 모듈의 길이를 나눠서 들어갈수 있는 갯수가 동일하면 가운데로 정렬 시킨다 if (flowLines.left.type === 'curve' && flowLines.right.type === 'curve') { startPointX = isChidori ? flowLines.left.x1 + 1 : flowLines.left.x1 + (calcAreaWidth - totalModuleWidthCount * width) / 2 } let heightMargin = 0 let widthMargin = 0 let chidoriLength = 0 //첫번재 모듈 설치 후 두번째 모듈을 몇개까지 설치 할 수 있는지 계산 if (moduleIndex > 0) { // moduleMaxRows = totalModuleMaxRows - installedModuleHeightCount //두번째 모듈일때 isChidoriLine = installedModuleHeightCount % 2 != 0 ? true : false //첫번째에서 짝수에서 끝났으면 홀수는 치도리가 아님 짝수는 치도리 } for (let i = 0; i < calcModuleHeightCount; i++) { let isInstall = false let moduleY = flowLines.bottom.y1 - height * i - 1 //살짝 여유를 준다 //두번째 모듈 -> 혼합일 경우의 설치될 모듈 높이를 계산 if (moduleIndex > 0) { moduleY = installedLastHeightCoord - intvVer } //첫번째는 붙여서 두번째는 마진을 주고 설치 heightMargin = i === 0 ? 0 : intvVer * i for (let j = 0; j < totalModuleWidthCount; j++) { let moduleX = startPointX + width * j + 1 //5정도 마진을 준다 widthMargin = j === 0 ? 0 : intvHor * j // 가로 마진값 chidoriLength = 0 //치도리가 아니여도 기본값을 5정도 준다 if (isChidori && !isMaxSetup) { chidoriLength = installedModuleHeightCount % 2 == 0 ? 0 : width / 2 - intvHor } //치도리 일때 는 짝수(1 기준) 일때만 치도리 라인으로 본다 if (isChidori && isChidoriLine) { chidoriLength = width / 2 - intvHor } let square = [ [moduleX + widthMargin + chidoriLength, moduleY - height - heightMargin], [moduleX + widthMargin + chidoriLength, moduleY - heightMargin], [moduleX + width + widthMargin + chidoriLength, moduleY - heightMargin], [moduleX + width + widthMargin + chidoriLength, moduleY - height - heightMargin], [moduleX + widthMargin + chidoriLength, moduleY - height - heightMargin], ] // console.log('square', square) let squarePolygon = turf.polygon([square]) let turfCoordnates = squarePolygon.geometry.coordinates[0].slice(0, -1) let points = turfCoordnates.map((coord) => ({ x: coord[0], y: coord[1] })) moduleOptions = { ...moduleOptions, fill: module.color, surfaceId: moduleSetupSurface.id, moduleInfo: module } let tempModule = new QPolygon(points, { ...moduleOptions, turfPoints: squarePolygon }) let disjointFromTrestle = checkModuleDisjointSurface(squarePolygon, polygonToTurfPolygon(moduleSetupSurface, true)) let isDisjoint = checkModuleDisjointObjects(squarePolygon, containsBatchObjects) if (disjointFromTrestle && isDisjoint) { //최초 한번은 그냥 그린다 //겹치는지 확인해서 포함된 모듈만 그린다 canvas?.add(tempModule) moduleSetupArray.push(tempModule) moduleArray.push(tempModule) canvas.renderAll() // ++installedModuleHeightCount isInstall = true //마지막에 설치된 모듈의 Y 좌표 installedLastHeightCoord = moduleY - height - heightMargin } else { //디버깅용 // tempModule.set({ fill: 'transparent', stroke: 'red', strokeWidth: 1 }) // canvas?.add(tempModule) // canvas.renderAll() } } if (isInstall) { ++installedModuleHeightCount } } setupModule.push(moduleArray) }) } const topFlowSetupModule = ( surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, isCenter = false, intvHor, intvVer, ) => { let setupModule = [] const trestleDetailData = moduleSetupSurface.trestleDetail const moduleMaxCols = trestleDetailData.moduleMaxCols // 모듈 최대 가로 설치 갯수 let installedLastHeightCoord = 0 //마지막으로 설치된 모듈의 좌표 let installedModuleHeightCount = 0 //마지막으로 설치된 모듈의 카운트 let isChidoriLine = false let flowLines checkedModule.forEach((module, moduleIndex) => { const tmpModuleData = trestleDetailData.module.filter((moduleObj) => module.moduleTpCd === moduleObj.moduleTpCd)[0] //혼합모듈일때는 mixModuleMaxRows 값이 0 이상임 let moduleMaxRows = tmpModuleData.mixModuleMaxRows === 0 ? tmpModuleData.moduleMaxRows : tmpModuleData.mixModuleMaxRows // 혼합모듈 포함 총 모듈 설치 높이 갯수 const totalModuleMaxRows = tmpModuleData.moduleMaxRows const { width, height } = getModuleWidthHeight(maxLengthLine, moduleSetupSurface, module) if (moduleIndex === 0) { flowLines = getFlowLines(moduleSetupSurface, height) if (flowLines.top.type === 'curve') { flowLines = getFlowLines(moduleSetupSurface, width) } } //흐름 방향이 북쪽(위) //육지붕이 아닐때만 넣는다 육지붕일땐 클릭 이벤트에 별도로 넣어놓음 const moduleArray = [] let calcAreaWidth = flowLines.right.x1 - flowLines.left.x1 //오른쪽 x에서 왼쪽 x를 뺀 가운데를 찾는 로직 let calcModuleWidthCount = calcAreaWidth / (width + intvHor) //뺀 공간에서 모듈을 몇개를 넣을수 있는지 확인하는 로직 let calcMaxModuleWidthCount = calcModuleWidthCount > moduleMaxCols ? moduleMaxCols : calcModuleWidthCount //최대 모듈 단수가 있기 때문에 최대 단수보다 카운트가 크면 최대 단수로 씀씀 // let totalModuleWidthCount = isChidori ? Math.abs(calcMaxModuleWidthCount) : Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 //??어쩔때는 붙고 어쩔때는 안붙고 멋대로??? let totalModuleWidthCount = Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 let calcAreaHeight = flowLines.bottom.y1 - flowLines.top.y1 let calcModuleHeightCount = calcAreaHeight / (height + intvVer) let calcStartPoint = flowLines.left.type === 'flat' ? (calcAreaWidth - totalModuleWidthCount * width) / 2 : 0 //반씩 나눠서 중앙에 맞춤 bottom 기준으로 양변이 직선일때만 가운데 정렬 let startPointX = flowLines.right.x1 - calcStartPoint //시작점을 만든다 //근데 양변이 곡선이면 중앙에 맞추기 위해 아래와 위의 길이를 재서 모듈의 길이를 나눠서 들어갈수 있는 갯수가 동일하면 가운데로 정렬 시킨다 if (flowLines.left.type === 'curve' && flowLines.right.type === 'curve') { startPointX = flowLines.right.x1 - (calcAreaWidth - totalModuleWidthCount * width) / 2 } let heightMargin = 0 let widthMargin = 0 let chidoriLength = 0 if (moduleIndex > 0) { moduleMaxRows = totalModuleMaxRows - installedModuleHeightCount //두번째 모듈일때 isChidoriLine = installedModuleHeightCount % 2 != 0 ? true : false //첫번째에서 짝수에서 끝났으면 홀수는 치도리가 아님 짝수는 치도리 } for (let i = 0; i < calcModuleHeightCount; i++) { let isInstall = false let moduleY = flowLines.top.y1 + height * i //탑의 y점에서부터 아래로 그려 내려간다 if (moduleIndex > 0) { moduleY = installedLastHeightCoord + intvVer + 1 } heightMargin = i === 0 ? 0 : intvVer * i //모듈간에 마진이 있어 마진값도 넣음 for (let j = 0; j < totalModuleWidthCount; j++) { //모듈 열수 만큼 반복 let moduleX = startPointX - width * j - 1 //시작점에서 우 -> 좌로 그려 내려간다 widthMargin = j === 0 ? 0 : intvHor * j chidoriLength = 0 if (isChidori && !isMaxSetup) { chidoriLength = installedModuleHeightCount % 2 == 0 ? 0 : width / 2 - intvHor } if (isChidori && isChidoriLine) { chidoriLength = width / 2 + intvHor } let square = [ [moduleX - widthMargin - chidoriLength, moduleY + heightMargin], [moduleX - widthMargin - chidoriLength, moduleY + height + heightMargin], [moduleX - width - widthMargin - chidoriLength, moduleY + height + heightMargin], [moduleX - width - widthMargin - chidoriLength, moduleY + heightMargin], [moduleX - widthMargin - chidoriLength, moduleY + heightMargin], ] let squarePolygon = turf.polygon([square]) let turfCoordnates = squarePolygon.geometry.coordinates[0].slice(0, -1) let points = turfCoordnates.map((coord) => ({ x: coord[0], y: coord[1] })) moduleOptions = { ...moduleOptions, fill: module.color, surfaceId: moduleSetupSurface.id, moduleInfo: module } let tempModule = new QPolygon(points, { ...moduleOptions, turfPoints: squarePolygon }) let disjointFromTrestle = checkModuleDisjointSurface(squarePolygon, polygonToTurfPolygon(moduleSetupSurface, true)) let isDisjoint = checkModuleDisjointObjects(squarePolygon, containsBatchObjects) if (disjointFromTrestle && isDisjoint) { canvas?.add(tempModule) moduleSetupArray.push(tempModule) moduleArray.push(tempModule) canvas.renderAll() isInstall = true //마지막에 설치된 모듈의 Y 좌표 installedLastHeightCoord = moduleY + height + heightMargin } else { //디버깅용 // tempModule.set({ fill: 'transparent', stroke: 'red', strokeWidth: 1 }) // canvas?.add(tempModule) // canvas.renderAll() } } if (isInstall) { ++installedModuleHeightCount } } setupModule.push(moduleArray) }) } //남, 북과 같은 로직으로 적용하려면 좌우는 열 -> 행 으로 그려야함 //변수명은 bottom 기준으로 작성하여 동일한 방향으로 진행한다 const leftFlowSetupModule = ( surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, isCenter = false, intvHor, intvVer, ) => { let setupModule = [] const trestleDetailData = moduleSetupSurface.trestleDetail //가대 상세 데이터 const moduleMaxCols = trestleDetailData.moduleMaxCols // 모듈 최대 가로 설치 갯수 let installedLastHeightCoord = 0 //마지막으로 설치된 모듈의 좌표 let installedModuleHeightCount = 0 //마지막으로 설치된 모듈의 카운트 let isChidoriLine = false let flowLines checkedModule.forEach((module, moduleIndex) => { const tmpModuleData = trestleDetailData.module.filter((moduleObj) => module.moduleTpCd === moduleObj.moduleTpCd)[0] //혼합모듈일때는 mixModuleMaxRows 값이 0 이상임 let moduleMaxRows = tmpModuleData.mixModuleMaxRows === 0 ? tmpModuleData.moduleMaxRows : tmpModuleData.mixModuleMaxRows // 혼합모듈 포함 총 모듈 설치 높이 갯수 const totalModuleMaxRows = tmpModuleData.moduleMaxRows //모듈의 넓이 높이를 가져옴 (복시도 촌수 적용) //1번 깔았던 모듈 기준으로 잡야아함 const { width, height } = getModuleWidthHeight(maxLengthLine, moduleSetupSurface, module) if (moduleIndex === 0) { flowLines = getFlowLines(moduleSetupSurface, width) if (flowLines.left.type === 'curve') { flowLines = getFlowLines(moduleSetupSurface, height) } } //육지붕이 아닐때만 넣는다 육지붕일땐 클릭 이벤트에 별도로 넣어놓음 const moduleArray = [] let calcAreaWidth = flowLines.bottom.y1 - flowLines.top.y1 //아래에서 y에서 위를 y를 뺀 가운데를 찾는 로직 let calcModuleWidthCount = calcAreaWidth / (height + intvHor) //뺀 공간에서 모듈을 몇개를 넣을수 있는지 확인하는 로직 let calcMaxModuleWidthCount = calcModuleWidthCount > moduleMaxCols ? moduleMaxCols : calcModuleWidthCount //최대 모듈 단수가 있기 때문에 최대 단수보다 카운트가 크면 최대 단수로 씀씀 // let totalModuleWidthCount = isChidori ? Math.abs(calcMaxModuleWidthCount) : Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 let totalModuleWidthCount = Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 let calcAreaHeight = flowLines.right.x1 - flowLines.left.x1 let calcModuleHeightCount = calcAreaHeight / (width + intvVer) let calcStartPoint = flowLines.bottom.type === 'flat' ? (calcAreaWidth - totalModuleWidthCount * height) / 2 : 0 //반씩 나눠서 중앙에 맞춤 left 높이 기준으로 양변이 직선일때만 가운데 정렬 let startPointX = flowLines.top.y1 + calcStartPoint //시작점을 만든다 //근데 양변이 곡선이면 중앙에 맞추기 위해 아래와 위의 길이를 재서 모듈의 길이를 나눠서 들어갈수 있는 갯수가 동일하면 가운데로 정렬 시킨다 if (flowLines.top.type === 'curve' && flowLines.bottom.type === 'curve') { startPointX = flowLines.top.y1 + (calcAreaWidth - totalModuleWidthCount * height) / 2 } let heightMargin = 0 let widthMargin = 0 let chidoriLength = 0 //첫번재 모듈 설치 후 두번째 모듈을 몇개까지 설치 할 수 있는지 계산 if (moduleIndex > 0) { moduleMaxRows = totalModuleMaxRows - installedModuleHeightCount //두번째 모듈일때 isChidoriLine = installedModuleHeightCount % 2 != 0 ? true : false //첫번째에서 짝수에서 끝났으면 홀수는 치도리가 아님 짝수는 치도리 } for (let i = 0; i < calcModuleHeightCount; i++) { let isInstall = false let moduleY = flowLines.left.x1 + width * i + 1 //살짝 여유를 준다 //두번째 모듈 -> 혼합일 경우의 설치될 모듈 높이를 계산 if (moduleIndex > 0) { moduleY = installedLastHeightCoord + intvHor } //첫번째는 붙여서 두번째는 마진을 주고 설치 heightMargin = i === 0 ? 0 : intvHor * i for (let j = 0; j < totalModuleWidthCount; j++) { let moduleX = startPointX + height * j + 1 //5정도 마진을 준다 widthMargin = j === 0 ? 0 : intvVer * j // 가로 마진값 chidoriLength = 0 //치도리가 아니여도 기본값을 5정도 준다 if (isChidori && !isMaxSetup) { chidoriLength = installedModuleHeightCount % 2 == 0 ? 0 : height / 2 - intvVer } let square = [ [moduleY + heightMargin, moduleX + height + widthMargin + chidoriLength], [moduleY + heightMargin, moduleX + widthMargin + chidoriLength], [moduleY + width + heightMargin, moduleX + widthMargin + chidoriLength], [moduleY + width + heightMargin, moduleX + height + widthMargin + chidoriLength], [moduleY + heightMargin, moduleX + height + widthMargin + chidoriLength], ] let squarePolygon = turf.polygon([square]) let turfCoordnates = squarePolygon.geometry.coordinates[0].slice(0, -1) let points = turfCoordnates.map((coord) => ({ x: coord[0], y: coord[1] })) moduleOptions = { ...moduleOptions, fill: module.color, surfaceId: moduleSetupSurface.id, moduleInfo: module } let tempModule = new QPolygon(points, { ...moduleOptions, turfPoints: squarePolygon }) let disjointFromTrestle = checkModuleDisjointSurface(squarePolygon, polygonToTurfPolygon(moduleSetupSurface, true)) let isDisjoint = checkModuleDisjointObjects(squarePolygon, containsBatchObjects) if (disjointFromTrestle && isDisjoint) { //최초 한번은 그냥 그린다 //겹치는지 확인해서 포함된 모듈만 그린다 canvas?.add(tempModule) moduleSetupArray.push(tempModule) moduleArray.push(tempModule) canvas.renderAll() // ++installedModuleHeightCount isInstall = true //마지막에 설치된 모듈의 Y 좌표 installedLastHeightCoord = moduleY + width + widthMargin } else { //디버깅용 // tempModule.set({ fill: 'transparent', stroke: 'red', strokeWidth: 1 }) // canvas?.add(tempModule) // canvas.renderAll() } } if (isInstall) { ++installedModuleHeightCount } } setupModule.push(moduleArray) }) } const rightFlowSetupModule = ( surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, isCenter = false, intvHor, intvVer, ) => { let setupModule = [] const trestleDetailData = moduleSetupSurface.trestleDetail //가대 상세 데이터 const moduleMaxCols = trestleDetailData.moduleMaxCols // 모듈 최대 가로 설치 갯수 let installedLastHeightCoord = 0 //마지막으로 설치된 모듈의 좌표 let installedModuleHeightCount = 0 //마지막으로 설치된 모듈의 카운트 let isChidoriLine = false let flowLines checkedModule.forEach((module, moduleIndex) => { const tmpModuleData = trestleDetailData.module.filter((moduleObj) => module.moduleTpCd === moduleObj.moduleTpCd)[0] //혼합모듈일때는 mixModuleMaxRows 값이 0 이상임 let moduleMaxRows = tmpModuleData.mixModuleMaxRows === 0 ? tmpModuleData.moduleMaxRows : tmpModuleData.mixModuleMaxRows // 혼합모듈 포함 총 모듈 설치 높이 갯수 const totalModuleMaxRows = tmpModuleData.moduleMaxRows //모듈의 넓이 높이를 가져옴 (복시도 촌수 적용) //1번 깔았던 모듈 기준으로 잡야아함 const { width, height } = getModuleWidthHeight(maxLengthLine, moduleSetupSurface, module) if (moduleIndex === 0) { flowLines = getFlowLines(moduleSetupSurface, width) if (flowLines.right.type === 'curve') { flowLines = getFlowLines(moduleSetupSurface, height) } } //육지붕이 아닐때만 넣는다 육지붕일땐 클릭 이벤트에 별도로 넣어놓음 const moduleArray = [] let calcAreaWidth = flowLines.bottom.y1 - flowLines.top.y1 //아래에서 y에서 위를 y를 뺀 가운데를 찾는 로직 let calcModuleWidthCount = calcAreaWidth / (height + intvHor) //뺀 공간에서 모듈을 몇개를 넣을수 있는지 확인하는 로직 let calcMaxModuleWidthCount = calcModuleWidthCount > moduleMaxCols ? moduleMaxCols : calcModuleWidthCount //최대 모듈 단수가 있기 때문에 최대 단수보다 카운트가 크면 최대 단수로 씀씀 // let totalModuleWidthCount = isChidori ? Math.abs(calcMaxModuleWidthCount) : Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 let totalModuleWidthCount = Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 let calcAreaHeight = flowLines.right.x1 - flowLines.left.x1 let calcModuleHeightCount = calcAreaHeight / (width + intvVer) let calcStartPoint = flowLines.top.type === 'flat' ? (calcAreaWidth - totalModuleWidthCount * height) / 2 : 0 //반씩 나눠서 중앙에 맞춤 left 높이 기준으로 양변이 직선일때만 가운데 정렬 let startPointX = flowLines.bottom.y2 - calcStartPoint //시작점을 만든다 //근데 양변이 곡선이면 중앙에 맞추기 위해 아래와 위의 길이를 재서 모듈의 길이를 나눠서 들어갈수 있는 갯수가 동일하면 가운데로 정렬 시킨다 if (flowLines.top.type === 'curve' && flowLines.bottom.type === 'curve') { startPointX = flowLines.bottom.y2 - (calcAreaWidth - totalModuleWidthCount * height) / 2 } let heightMargin = 0 let widthMargin = 0 let chidoriLength = 0 //첫번재 모듈 설치 후 두번째 모듈을 몇개까지 설치 할 수 있는지 계산 if (moduleIndex > 0) { moduleMaxRows = totalModuleMaxRows - installedModuleHeightCount //두번째 모듈일때 isChidoriLine = installedModuleHeightCount % 2 != 0 ? true : false //첫번째에서 짝수에서 끝났으면 홀수는 치도리가 아님 짝수는 치도리 } for (let i = 0; i < calcModuleHeightCount; i++) { let isInstall = false let moduleY = flowLines.right.x1 - width * i - 1 //살짝 여유를 준다 //두번째 모듈 -> 혼합일 경우의 설치될 모듈 높이를 계산 if (moduleIndex > 0) { moduleY = installedLastHeightCoord - intvHor } //첫번째는 붙여서 두번째는 마진을 주고 설치 heightMargin = i === 0 ? 0 : intvHor * i for (let j = 0; j < totalModuleWidthCount; j++) { let moduleX = startPointX - height * j - 1 //5정도 마진을 준다 widthMargin = j === 0 ? 0 : intvVer * j // 가로 마진값 chidoriLength = 0 //치도리가 아니여도 기본값을 5정도 준다 if (isChidori && !isMaxSetup) { chidoriLength = installedModuleHeightCount % 2 == 0 ? 0 : height / 2 - intvVer } //치도리 일때 는 짝수(1 기준) 일때만 치도리 라인으로 본다 // if (isChidori && isChidoriLine) { // chidoriLength = width / 2 - height // } let square = [ [moduleY - heightMargin, moduleX - height - widthMargin - chidoriLength], [moduleY - heightMargin, moduleX - widthMargin - chidoriLength], [moduleY - width - heightMargin, moduleX - widthMargin - chidoriLength], [moduleY - width - heightMargin, moduleX - height - widthMargin - chidoriLength], [moduleY - heightMargin, moduleX - height - widthMargin - chidoriLength], ] let squarePolygon = turf.polygon([square]) let turfCoordnates = squarePolygon.geometry.coordinates[0].slice(0, -1) let points = turfCoordnates.map((coord) => ({ x: coord[0], y: coord[1] })) moduleOptions = { ...moduleOptions, fill: module.color, surfaceId: moduleSetupSurface.id, moduleInfo: module } let tempModule = new QPolygon(points, { ...moduleOptions, turfPoints: squarePolygon }) let disjointFromTrestle = checkModuleDisjointSurface(squarePolygon, polygonToTurfPolygon(moduleSetupSurface, true)) let isDisjoint = checkModuleDisjointObjects(squarePolygon, containsBatchObjects) if (disjointFromTrestle && isDisjoint) { //최초 한번은 그냥 그린다 //겹치는지 확인해서 포함된 모듈만 그린다 canvas?.add(tempModule) moduleSetupArray.push(tempModule) moduleArray.push(tempModule) canvas.renderAll() isInstall = true //마지막에 설치된 모듈의 Y 좌표 installedLastHeightCoord = moduleY - width - heightMargin } else { //디버깅용 // tempModule.set({ fill: 'transparent', stroke: 'red', strokeWidth: 1 }) // canvas?.add(tempModule) // canvas.renderAll() } } if (isInstall) { ++installedModuleHeightCount } } setupModule.push(moduleArray) }) } moduleSetupSurfaces.forEach((moduleSetupSurface, index) => { moduleSetupSurface.fire('mousedown') const moduleSetupArray = [] const turfModuleSetupSurface = polygonToTurfPolygon(moduleSetupSurface) //폴리곤을 turf 객체로 변환 const containsBatchObjects = objectsIncludeSurface(turfModuleSetupSurface) //배치면에 오브젝트(도머, 개구등)이 있는지 확인하는 로직 const surfaceMaxLines = findSetupSurfaceMaxLines(moduleSetupSurface) let maxLengthLine = moduleSetupSurface.lines.reduce((acc, cur) => { return acc.length > cur.length ? acc : cur }) const flowDirection = moduleSetupSurface.direction let intvHor = flowDirection === 'south' || flowDirection === 'north' ? moduleSetupSurface.trestleDetail.moduleIntvlHor / 10 : moduleSetupSurface.trestleDetail.moduleIntvlVer / 10 let intvVer = flowDirection === 'south' || flowDirection === 'north' ? moduleSetupSurface.trestleDetail.moduleIntvlVer / 10 : moduleSetupSurface.trestleDetail.moduleIntvlHor / 10 //처마면 배치 if (setupLocation === 'eaves') { // 흐름방향이 남쪽일때 if (moduleSetupSurface.direction === 'south') { downFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) } if (moduleSetupSurface.direction === 'west') { leftFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) } if (moduleSetupSurface.direction === 'east') { rightFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) } if (moduleSetupSurface.direction === 'north') { topFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) } } else if (setupLocation === 'ridge') { //용마루 if (moduleSetupSurface.direction === 'south') { topFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) } if (moduleSetupSurface.direction === 'west') { rightFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) } if (moduleSetupSurface.direction === 'east') { leftFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) } if (moduleSetupSurface.direction === 'north') { downFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, false, intvHor, intvVer) } } else if (setupLocation === 'center') { //중가면 if (moduleSetupSurface.direction === 'south') { downFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, true, intvHor, intvVer) } if (moduleSetupSurface.direction === 'west') { leftFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, true, intvHor, intvVer) } if (moduleSetupSurface.direction === 'east') { rightFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, true, intvHor, intvVer) } if (moduleSetupSurface.direction === 'north') { topFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, containsBatchObjects, true, intvHor, intvVer) } } // const setupedModules = moduleSetupArray.filter((module, index) => { // let disjointFromTrestle = checkModuleDisjointSurface(module.turfPoints, turfModuleSetupSurface) // let isDisjoint = checkModuleDisjointObjects(module.turfPoints, containsBatchObjects) // if (!(disjointFromTrestle && isDisjoint)) { // canvas?.remove(module) // // module.set({ fill: 'rgba(255,190,41, 0.4)', stroke: 'black', strokeWidth: 1 }) // return false // } else { // return module // } // }) // canvas?.renderAll() //나간애들 제외하고 설치된 애들로 겹친애들 삭제 하기 moduleSetupArray.forEach((module, index) => { if (isMaxSetup && index > 0) { const isOverlap = turf.booleanOverlap(polygonToTurfPolygon(moduleSetupArray[index - 1]), polygonToTurfPolygon(module)) //겹치는지 확인 if (isOverlap) { //겹쳐있으면 삭제 canvas?.remove(module) // module.set({ fill: 'rgba(72, 161, 250, 0.4)', stroke: 'black', strokeWidth: 0.1 }) canvas.renderAll() moduleSetupArray.splice(index, 1) return false } } }) moduleSetupSurface.set({ modules: moduleSetupArray }) // getModuleStatistics() setModuleStatisticsData() // const moduleArray = [...moduleIsSetup] // moduleArray.push({ // surfaceId: moduleSetupSurface.surfaceId, // moduleSetupArray: setupedModules, // }) // setModuleIsSetup(moduleArray) }) // calculateForApi() } const coordToTurfPolygon = (points) => { const coordinates = points.map((point) => [point.x, point.y]) coordinates.push(coordinates[0]) return turf.polygon([coordinates]) } const batchObjectGroupToTurfPolygon = (group) => { const polygons = group.getObjects().filter((obj) => obj.type === 'QPolygon') let allPoints = [] polygons.forEach((obj) => allPoints.push(...obj.get('points'))) const points = turf.featureCollection(allPoints.map((point) => turf.point([point.x, point.y]))) const hull = turf.concave(points, { tolerance: 0.1 }) return hull } const bottomTopFlowLine = (surface, length) => { const flowArray = [] const bottomFlow = surface.lines.reduce( (acc, line, index) => { if (line.y1 > acc.y1 || (line.y1 === acc.y1 && line.x1 > acc.x1)) { return { x1: line.x1, y1: line.y1, index: index } } return acc }, { x1: 0, y1: 0, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 ) flowArray.push(bottomFlow) const topFlow = surface.lines.reduce( (acc, line, index) => { if (line.y1 < acc.y1 || (line.y1 === acc.y1 && line.x1 < acc.x1)) { return { x1: line.x1, y1: line.y1, index: index } } return acc }, { x1: Infinity, y1: Infinity, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 ) flowArray.push(topFlow) let rtnObjArray = [] flowArray.forEach((center, index) => { const linesArray = new Array() surface.lines.filter((line) => { if ((center.x1 === line.x1 && center.y1 === line.y1) || (center.x1 === line.x2 && center.y1 === line.y2)) { linesArray.push(line) } }) let coords = [] if (center.index === 0) { coords = [ { x: linesArray[0].x2, y: linesArray[0].y2 }, { x: center.x1, y: center.y1 }, { x: linesArray[1].x1, y: linesArray[1].y1 }, ] } else { coords = [ { x: linesArray[0].x1, y: linesArray[0].y1 }, { x: center.x1, y: center.y1 }, { x: linesArray[1].x2, y: linesArray[1].y2 }, ] } const adjust1 = coords[0].x - coords[1].x const height1 = coords[1].y - coords[0].y const angle1 = Math.abs(Math.round(Math.atan(height1 / adjust1) * (180 / Math.PI) * 1000) / 1000) const adjust2 = coords[2].x - coords[1].x const height2 = coords[2].y - coords[1].y const angle2 = Math.abs(Math.round(Math.atan(height2 / adjust2) * (180 / Math.PI) * 1000) / 1000) const angle3 = 180 - (angle1 + angle2) const charlie = Number(length) + 3 // 평행선길이 약간 여유를 줌 const alpha = (charlie * Math.sin((angle1 * Math.PI) / 180)) / Math.sin((angle3 * Math.PI) / 180) const beta = Math.sqrt(alpha ** 2 + charlie ** 2 - 2 * alpha * charlie * Math.cos((angle2 * Math.PI) / 180)) const h = beta * Math.sin((angle1 * Math.PI) / 180) // 높이 const sign = Math.sign(coords[0].y - coords[1].y) // 진행방향 const top = coords[1].y + sign * h // 변경되는 높이 좌표 값 const pointX1 = coords[0].x + ((coords[0].y - top) / (coords[0].y - coords[1].y)) * (coords[1].x - coords[0].x) const pointY1 = top const pointX2 = coords[2].x + ((coords[2].y - top) / (coords[2].y - coords[1].y)) * (coords[1].x - coords[2].x) const pointY2 = top //디버깅 const finalLine = new QLine([pointX1, pointY1, pointX2, pointY2], { stroke: 'red', strokeWidth: 1, selectable: true, }) // console.log(`index ${index} : finalLine`, pointX1, pointY1, pointX2, pointY2) // canvas?.add(finalLine) // canvas?.renderAll() let rtnObj //평평하면 if (alpha === 0 || beta === 0 || h === 0 || sign === 0) { //꼭지점이 없고 평평할때 ex) 네모 let standardLine if (index === 0) { //bottom standardLine = surface.lines.reduce((acc, line, index) => { if (line.y1 > acc.y1 || (line.y1 === acc.y1 && line.y2 > acc.y2)) { return { x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2, index: index } } return acc }) } else { standardLine = surface.lines.reduce((acc, line, index) => { if (line.y1 < acc.y1 || (line.y1 === acc.y1 && line.y2 < acc.y2)) { return { x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2, index: index } } return acc }) } rtnObj = { target: index === 0 ? 'bottom' : 'top', x1: standardLine.x1, y1: standardLine.y1, x2: standardLine.x2, y2: standardLine.y2, type: 'flat', } } else { rtnObj = { target: index === 0 ? 'bottom' : 'top', x1: pointX1, y1: pointY1, x2: pointX2, y2: pointY2, type: 'curve' } } rtnObjArray.push(rtnObj) }) return rtnObjArray } const leftRightFlowLine = (surface, length) => { const flowArray = [] const leftFlow = surface.lines.reduce( (acc, line, index) => { if (line.x1 < acc.x1 || (line.x1 === acc.x1 && line.y1 < acc.y1)) { return { x1: line.x1, y1: line.y1, index: index } } return acc }, { x1: Infinity, y1: Infinity, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 ) flowArray.push(leftFlow) const rightFlow = surface.lines.reduce( (acc, line, index) => { if (line.x1 > acc.x1 || (line.x1 === acc.x1 && line.y1 > acc.y1)) { return { x1: line.x1, y1: line.y1, index: index } } return acc }, { x1: 0, y1: 0, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 ) flowArray.push(rightFlow) let rtnObjArray = [] flowArray.forEach((center, index) => { const linesArray = surface.lines.filter((line) => { if ((center.x1 === line.x1 && center.y1 === line.y1) || (center.x1 === line.x2 && center.y1 === line.y2)) { return line } }) let coords = [] if (center.index === 0) { coords = [ { x: linesArray[1].x1, y: linesArray[1].y1 }, { x: center.x1, y: center.y1 }, { x: linesArray[0].x2, y: linesArray[0].y2 }, ] } else { coords = [ { x: linesArray[0].x1, y: linesArray[0].y1 }, { x: center.x1, y: center.y1 }, { x: linesArray[1].x2, y: linesArray[1].y2 }, ] } const adjust1 = coords[0].x - coords[1].x const height1 = coords[1].y - coords[0].y const angle1 = Math.abs(Math.round(Math.atan(adjust1 / height1) * (180 / Math.PI) * 1000) / 1000) const adjust2 = coords[2].x - coords[1].x const height2 = coords[2].y - coords[1].y const angle2 = Math.abs(Math.round(Math.atan(adjust2 / height2) * (180 / Math.PI) * 1000) / 1000) const angle3 = 180 - (angle1 + angle2) const charlie = Number(length) + 3 // 평행선길이 약간 여유를 줌 const alpha = (charlie * Math.sin((angle1 * Math.PI) / 180)) / Math.sin((angle3 * Math.PI) / 180) const beta = Math.sqrt(alpha ** 2 + charlie ** 2 - 2 * alpha * charlie * Math.cos((angle2 * Math.PI) / 180)) const h = beta * Math.sin((angle1 * Math.PI) / 180) // 높이 const sign = Math.sign(coords[0].x - coords[1].x) // 진행방향 const top = coords[1].x + sign * h // 변경되는 높이 좌표 값 // const line3 = new QLine([coords[1].x, coords[1].y, top, coords[1].y], { // stroke: 'blue', // strokeWidth: 1, // selectable: true, // }) // canvas?.add(line3) const pointX1 = top const pointY1 = coords[0].y + ((coords[0].x - top) / (coords[0].x - coords[1].x)) * (coords[1].y - coords[0].y) const pointX2 = top const pointY2 = coords[2].y + ((coords[2].x - top) / (coords[2].x - coords[1].x)) * (coords[1].y - coords[2].y) //디버깅용 const finalLine = new QLine([pointX1, pointY1, pointX2, pointY2], { stroke: 'red', strokeWidth: 1, selectable: true, }) // canvas?.add(finalLine) // canvas?.renderAll() let rtnObj //평평하면 if (alpha === 0 || beta === 0 || h === 0 || sign === 0) { //꼭지점이 없고 평평할때 ex) 네모 let standardLine if (index === 0) { //bottom standardLine = surface.lines.reduce((acc, line, index) => { if (line.x1 < acc.x1 || (line.x1 === acc.x1 && line.y1 < acc.y1)) { return { x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2, index: index } } return acc }) } else { standardLine = surface.lines.reduce((acc, line, index) => { if (line.x1 > acc.x1 || (line.x1 === acc.x1 && line.y1 > acc.y1)) { return { x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2, index: index } } return acc }) } rtnObj = { target: index === 0 ? 'left' : 'right', x1: standardLine.x1, y1: standardLine.y1, x2: standardLine.x2, y2: standardLine.y2, type: 'flat', } } else { rtnObj = { target: index === 0 ? 'left' : 'right', x1: pointX1, y1: pointY1, x2: pointX2, y2: pointY2, type: 'curve' } } rtnObjArray.push(rtnObj) }) return rtnObjArray } const findSetupSurfaceMaxLines = (surface) => { const leftFlow = surface.lines.reduce( (acc, line, index) => { if (line.x1 < acc.x1 || (line.x1 === acc.x1 && line.y1 < acc.y1)) { return { x1: line.x1, y1: line.y1 } } return acc }, { x1: Infinity, y1: Infinity, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 ) const rightFlow = surface.lines.reduce( (acc, line, index) => { if (line.x1 > acc.x1 || (line.x1 === acc.x1 && line.y1 > acc.y1)) { return { x1: line.x1, y1: line.y1 } } return acc }, { x1: 0, y1: 0, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 ) const topFlow = surface.lines.reduce( (acc, line, index) => { if (line.y1 < acc.y1 || (line.y1 === acc.y1 && line.x1 < acc.x1)) { return { x1: line.x1, y1: line.y1 } } return acc }, { x1: Infinity, y1: Infinity, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 ) const bottomFlow = surface.lines.reduce( (acc, line, index) => { if (line.y1 > acc.y1 || (line.y1 === acc.y1 && line.x1 > acc.x1)) { return { x1: line.x1, y1: line.y1 } } return acc }, { x1: 0, y1: 0, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 ) const obj = { left: leftFlow, right: rightFlow, top: topFlow, bottom: bottomFlow, } return obj } const manualFlatroofModuleSetup = (placementFlatRef) => { let moduleSetupSurfaces = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE) //모듈설치면를 가져옴 if (isManualModuleSetup) { if (checkedModule.length === 0) { swalFire({ text: getMessage('module.place.select.module') }) setIsManualModuleSetup(!isManualModuleSetup) return } if (checkedModule.length > 1) { swalFire({ text: getMessage('module.place.select.one.module') }) setIsManualModuleSetup(!isManualModuleSetup) return } let flatBatchType = placementFlatRef.setupLocation.current.value const batchObjects = canvas ?.getObjects() .filter( (obj) => obj.name === BATCH_TYPE.OPENING || obj.name === BATCH_TYPE.TRIANGLE_DORMER || obj.name === BATCH_TYPE.PENTAGON_DORMER || obj.name === BATCH_TYPE.SHADOW, ) //도머s 객체 let moduleOptions = { fill: '#BFFD9F', stroke: 'black', strokeWidth: 0.3, selectable: true, // 선택 가능하게 설정 lockMovementX: true, // X 축 이동 잠금 lockMovementY: true, // Y 축 이동 잠금 lockRotation: true, // 회전 잠금 lockScalingX: true, // X 축 크기 조정 잠금 lockScalingY: true, // Y 축 크기 조정 잠금 parentId: moduleSetupSurface.parentId, surfaceId: moduleSetupSurface.id, name: POLYGON_TYPE.MODULE, } if (moduleSetupSurfaces.length !== 0) { let tempModule let manualDrawModules = [] let inside = false let turfPolygon let flowDirection let trestlePolygon //남쪽 선택 if (flatBatchType === 'excreta') { //변별로 선택 const excretaLines = canvas.getObjects().filter((obj) => obj.name === 'flatExcretaLine') excretaLines.forEach((obj) => { if (obj.isSelected === true) { const points1 = { x: obj.x1, y: obj.y1 } const points2 = { x: obj.x2, y: obj.y2 } const angle = calculateAngle(points1, points2) //변별로 선택으로 되어있을때 모듈면을 회전시키기 const targetdSurface = moduleSetupSurfaces.filter((surface) => surface.surfaceId === obj.surfaceId)[0] targetdSurface.angle = -angle //변별로 선택되어있는 지붕도 회전시키기 const targetRoof = canvas.getObjects().filter((roof) => roof.name === POLYGON_TYPE.ROOF && roof.id === targetdSurface.parentId)[0] targetRoof.angle = -angle targetRoof.fire('modified') targetdSurface.fire('modified') } canvas.remove(obj) }) } else { moduleSetupSurfaces.forEach((surface) => { const targetRoof = canvas.getObjects().filter((roof) => roof.name === POLYGON_TYPE.ROOF && roof.id === surface.parentId)[0] if (targetRoof) targetRoof.angle = -compasDeg surface.angle = -compasDeg }) } canvas.renderAll() addCanvasMouseEventListener('mouse:move', (e) => { //마우스 이벤트 삭제 후 재추가 const mousePoint = canvas.getPointer(e.e) for (let i = 0; i < moduleSetupSurfaces.length; i++) { turfPolygon = polygonToTurfPolygon(moduleSetupSurfaces[i], true) trestlePolygon = moduleSetupSurfaces[i] manualDrawModules = moduleSetupSurfaces[i].modules // 앞에서 자동으로 했을때 추가됨 flowDirection = moduleSetupSurfaces[i].direction //도형의 방향 const moduleWidth = Number(checkedModule[0].longAxis) / 10 const moduleHeight = Number(checkedModule[0].shortAxis) / 10 let tmpWidth = flowDirection === 'south' || flowDirection === 'north' ? moduleWidth : moduleHeight let tmpHeight = flowDirection === 'south' || flowDirection === 'north' ? moduleHeight : moduleWidth let { width, height } = canvasSetting.roofSizeSet == '1' ? calculateVisibleModuleHeight(tmpWidth, tmpHeight, getDegreeByChon(moduleSetupSurfaces[i].roofMaterial.pitch), flowDirection) : { width: tmpWidth, height: tmpHeight } const points = [ { x: mousePoint.x - width / 2, y: mousePoint.y - height / 2 }, { x: mousePoint.x + width / 2, y: mousePoint.y - height / 2 }, { x: mousePoint.x + width / 2, y: mousePoint.y + height / 2 }, { x: mousePoint.x - width / 2, y: mousePoint.y + height / 2 }, ] const turfPoints = coordToTurfPolygon(points) if (turf.booleanWithin(turfPoints, turfPolygon)) { let isDrawing = false if (isDrawing) return canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tempModule')) //움직일때 일단 지워가면서 움직임 tempModule = new fabric.Rect({ fill: 'white', stroke: 'black', strokeWidth: 0.3, width: width, height: height, left: mousePoint.x - width / 2, top: mousePoint.y - height / 2, selectable: false, lockMovementX: true, lockMovementY: true, lockRotation: true, lockScalingX: true, lockScalingY: true, name: 'tempModule', parentId: moduleSetupSurfaces[i].parentId, }) canvas?.add(tempModule) //움직여가면서 추가됨 /** * 스냅기능 */ let snapDistance = 10 let cellSnapDistance = 50 let intvHor = flowDirection === 'south' || flowDirection === 'north' ? 1 : 3 let intvVer = flowDirection === 'south' || flowDirection === 'north' ? 3 : 1 const trestleLeft = moduleSetupSurfaces[i].left const trestleTop = moduleSetupSurfaces[i].top const trestleRight = trestleLeft + moduleSetupSurfaces[i].width * moduleSetupSurfaces[i].scaleX const trestleBottom = trestleTop + moduleSetupSurfaces[i].height * moduleSetupSurfaces[i].scaleY const bigCenterY = (trestleTop + trestleTop + moduleSetupSurfaces[i].height) / 2 // 작은 폴리곤의 경계 좌표 계산 const smallLeft = tempModule.left const smallTop = tempModule.top const smallRight = smallLeft + tempModule.width * tempModule.scaleX const smallBottom = smallTop + tempModule.height * tempModule.scaleY const smallCenterX = smallLeft + (tempModule.width * tempModule.scaleX) / 2 const smallCenterY = smallTop + (tempModule.height * tempModule.scaleX) / 2 /** * 미리 깔아놓은 셀이 있을때 셀에 흡착됨 */ if (manualDrawModules) { manualDrawModules.forEach((cell) => { const holdCellLeft = cell.left const holdCellTop = cell.top const holdCellRight = holdCellLeft + cell.width * cell.scaleX const holdCellBottom = holdCellTop + cell.height * cell.scaleY const holdCellCenterX = holdCellLeft + (cell.width * cell.scaleX) / 2 const holdCellCenterY = holdCellTop + (cell.height * cell.scaleY) / 2 //설치된 셀에 좌측에 스냅 if (Math.abs(smallRight - holdCellLeft) < snapDistance) { tempModule.left = holdCellLeft - width - intvHor } //설치된 셀에 우측에 스냅 if (Math.abs(smallLeft - holdCellRight) < snapDistance) { tempModule.left = holdCellRight + intvHor } //설치된 셀에 위쪽에 스냅 if (Math.abs(smallBottom - holdCellTop) < snapDistance) { tempModule.top = holdCellTop - height - intvVer } //설치된 셀에 밑쪽에 스냅 if (Math.abs(smallTop - holdCellBottom) < snapDistance) { tempModule.top = holdCellBottom + intvVer } //가운데 -> 가운데 if (Math.abs(smallCenterX - holdCellCenterX) < cellSnapDistance) { tempModule.left = holdCellCenterX - width / 2 } //왼쪽 -> 가운데 if (Math.abs(smallLeft - holdCellCenterX) < cellSnapDistance) { tempModule.left = holdCellCenterX } // 오른쪽 -> 가운데 if (Math.abs(smallRight - holdCellCenterX) < cellSnapDistance) { tempModule.left = holdCellCenterX - width } //세로 가운데 -> 가운데 if (Math.abs(smallCenterY - holdCellCenterY) < cellSnapDistance) { tempModule.top = holdCellCenterY - height / 2 } // //위쪽 -> 가운데 // if (Math.abs(smallTop - holdCellCenterY) < cellSnapDistance) { // tempModule.top = holdCellCenterY // } // //아랫쪽 -> 가운데 // if (Math.abs(smallBottom - holdCellCenterY) < cellSnapDistance) { // tempModule.top = holdCellCenterY - height // } }) } // 위쪽 변에 스냅 if (Math.abs(smallTop - trestleTop) < snapDistance) { tempModule.top = trestleTop } // 아래쪽 변에 스냅 if (Math.abs(smallTop + tempModule.height * tempModule.scaleY - (trestleTop + moduleSetupSurfaces[i].height)) < snapDistance) { tempModule.top = trestleTop + moduleSetupSurfaces[i].height - tempModule.height * tempModule.scaleY } // 왼쪽변에 스냅 if (Math.abs(smallLeft - trestleLeft) < snapDistance) { tempModule.left = trestleLeft } //오른쪽 변에 스냅 if (Math.abs(smallRight - trestleRight) < snapDistance) { tempModule.left = trestleRight - tempModule.width * tempModule.scaleX } if (flowDirection === 'south' || flowDirection === 'north') { // 모듈왼쪽이 세로중앙선에 붙게 스냅 if (Math.abs(smallLeft - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < snapDistance) { tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2 } // 모듈이 가운데가 세로중앙선에 붙게 스냅 if (Math.abs(smallCenterX - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < snapDistance) { tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2 - (tempModule.width * tempModule.scaleX) / 2 } // 모듈오른쪽이 세로중앙선에 붙게 스냅 if (Math.abs(smallRight - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < snapDistance) { tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2 - tempModule.width * tempModule.scaleX } } else { // 모듈이 가로중앙선에 스냅 if (Math.abs(smallTop + tempModule.height / 2 - bigCenterY) < snapDistance) { tempModule.top = bigCenterY - tempModule.height / 2 } if (Math.abs(smallTop - (trestleTop + moduleSetupSurfaces[i].height / 2)) < snapDistance) { tempModule.top = trestleTop + moduleSetupSurfaces[i].height / 2 } // 모듈 밑면이 가로중앙선에 스냅 if (Math.abs(smallBottom - (trestleTop + moduleSetupSurfaces[i].height / 2)) < snapDistance) { tempModule.top = trestleTop + moduleSetupSurfaces[i].height / 2 - tempModule.height * tempModule.scaleY } } tempModule.setCoords() canvas?.renderAll() inside = true break } else { inside = false } } if (!inside) { // tempModule.set({ fill: 'red' }) canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tempModule')) canvas?.renderAll() } }) addCanvasMouseEventListener('mouse:up', (e) => { let isIntersection = true if (!inside) return if (tempModule) { const rectPoints = [ { x: tempModule.left, y: tempModule.top }, { x: tempModule.left + tempModule.width * tempModule.scaleX, y: tempModule.top }, { x: tempModule.left + tempModule.width * tempModule.scaleX, y: tempModule.top + tempModule.height * tempModule.scaleY, }, { x: tempModule.left, y: tempModule.top + tempModule.height * tempModule.scaleY }, ] tempModule.set({ points: rectPoints }) const tempTurfModule = polygonToTurfPolygon(tempModule) //도머 객체를 가져옴 if (batchObjects) { batchObjects.forEach((object) => { let dormerTurfPolygon if (object.type === 'group') { //도머는 그룹형태임 dormerTurfPolygon = batchObjectGroupToTurfPolygon(object) } else { //개구, 그림자 dormerTurfPolygon = polygonToTurfPolygon(rectToPolygon(object)) } const intersection = turf.intersect(turf.featureCollection([dormerTurfPolygon, tempTurfModule])) //겹치는지 확인 //겹치면 안됨 if (intersection) { swalFire({ text: getMessage('module.place.overobject') }) isIntersection = false } }) } if (!isIntersection) return if (turf.booleanWithin(tempTurfModule, turfPolygon)) { //마우스 클릭시 set으로 해당 위치에 셀을 넣음 const isOverlap = manualDrawModules.some((module) => turf.booleanOverlap(tempTurfModule, polygonToTurfPolygon(module))) //겹치는지 확인 if (!isOverlap) { moduleOptions.surfaceId = trestlePolygon.id let manualModule = new QPolygon(tempModule.points, { ...moduleOptions, moduleInfo: checkedModule[0] }) canvas?.add(manualModule) manualDrawModules.push(tempModule) } else { swalFire({ text: getMessage('module.place.overlab') }) } } else { swalFire({ text: getMessage('module.place.out') }) } } }) } // getModuleStatistics() setModuleStatisticsData() } else { if (moduleSetupSurfaces) { //수동모드 해제시 모듈 설치면 선택 잠금 moduleSetupSurfaces.forEach((obj) => { obj.set({ selectable: true, evented: true, }) }) } removeMouseEvent('mouse:up') removeMouseEvent('mouse:move') canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tempModule')) //움직일때 일단 지워가면서 움직임 } } const autoFlatroofModuleSetup = (placementFlatRef) => { initEvent() //마우스 이벤트 초기화 const moduleSetupSurfaces = moduleSetupSurface //선택 설치면 const notSelectedTrestlePolygons = canvas ?.getObjects() .filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && !moduleSetupSurfaces.includes(obj)) //설치면이 아닌것 const batchObjects = canvas ?.getObjects() .filter( (obj) => obj.name === BATCH_TYPE.OPENING || obj.name === BATCH_TYPE.TRIANGLE_DORMER || obj.name === BATCH_TYPE.PENTAGON_DORMER || obj.name === BATCH_TYPE.SHADOW, ) //도머s 객체 // if (moduleSetupSurfaces.length === 0) { // alert('선택된 모듈 설치면이 없습니다.') // return // } //어짜피 자동으로 누르면 선택안된데도 다 날아간다 canvas.getObjects().forEach((obj) => { if (obj.name === 'module') { canvas.remove(obj) } }) notSelectedTrestlePolygons.forEach((obj) => { if (obj.modules) { obj.modules.forEach((module) => { canvas?.remove(module) }) obj.modules = [] } }) const flatBatchType = placementFlatRef.setupLocation.current.value //남쪽 선택 if (flatBatchType === 'excreta') { //변별로 선택 const excretaLines = canvas.getObjects().filter((obj) => obj.name === 'flatExcretaLine') excretaLines.forEach((obj) => { if (obj.isSelected === true) { const points1 = { x: obj.x1, y: obj.y1 } const points2 = { x: obj.x2, y: obj.y2 } const angle = calculateAngle(points1, points2) // const targetdSurface = moduleSetupSurfaces.filter((surface) => surface.surfaceId === obj.surfaceId)[0] const targetSurface = canvas .getObjects() .filter((surface) => surface.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && surface.surfaceId === obj.surfaceId)[0] const targetRoof = canvas.getObjects().filter((roof) => roof.name === POLYGON_TYPE.ROOF && roof.id === targetSurface.parentId)[0] targetRoof.angle = -angle targetSurface.angle = -angle const newLines = createLinesFromPolygon(targetSurface.getCurrentPoints()) targetSurface.set({ lines: newLines }) targetRoof.fire('modified') targetSurface.fire('modified') targetRoof.setCoords() targetSurface.setCoords() moduleSetupSurfaces.push(targetSurface) } canvas.remove(obj) }) } else { moduleSetupSurfaces.forEach((surface) => { const targetRoof = canvas.getObjects().filter((roof) => roof.name === POLYGON_TYPE.ROOF && roof.id === surface.parentId)[0] if (targetRoof) targetRoof.angle = -compasDeg surface.angle = -compasDeg }) } canvas.renderAll() moduleSetupSurfaces.forEach((surface) => { let currentPoints = surface.getCurrentPoints() let lines = [] for (let i = 0; i < currentPoints.length; i++) { const start = currentPoints[i] const end = currentPoints[(i + 1) % currentPoints.length] const line = new QLine([start.x, start.y, end.x, end.y], {}) lines.push(line) } surface.lines.forEach((targetLine, index) => { targetLine.x1 = lines[index].x1 targetLine.y1 = lines[index].y1 targetLine.x2 = lines[index].x2 targetLine.y2 = lines[index].y2 }) // const flowLines = { // bottom: bottomTopFlowLine(surface).find((obj) => obj.target === 'bottom'), // top: bottomTopFlowLine(surface).find((obj) => obj.target === 'top'), // left: leftRightFlowLine(surface).find((obj) => obj.target === 'left'), // right: leftRightFlowLine(surface).find((obj) => obj.target === 'right'), // } // surface.set({ flowLines: flowLines }) }) let moduleOptions = { fill: '#BFFD9F', stroke: 'black', strokeWidth: 0.3, selectable: true, // 선택 가능하게 설정 lockMovementX: true, // X 축 이동 잠금 lockMovementY: true, // Y 축 이동 잠금 lockRotation: true, // 회전 잠금 lockScalingX: true, // X 축 크기 조정 잠금 lockScalingY: true, // Y 축 크기 조정 잠금 parentId: moduleSetupSurface.parentId, surfaceId: moduleSetupSurface.id, name: POLYGON_TYPE.MODULE, } let leftMargin, bottomMargin, square //선택된 지붕안에 오브젝트(도머, 개구등)이 있는지 확인하는 로직 포함되면 배열 반환 const objectsIncludeSurface = (turfModuleSetupSurface) => { let containsBatchObjects = [] containsBatchObjects = batchObjects.filter((batchObject) => { let convertBatchObject if (batchObject.type === 'group') { //도머는 그룹형태임 convertBatchObject = batchObjectGroupToTurfPolygon(batchObject) } else { //개구, 그림자 batchObject.set({ points: rectToPolygon(batchObject) }) canvas?.renderAll() // set된걸 바로 적용하기 위해 convertBatchObject = polygonToTurfPolygon(batchObject) //rect를 폴리곤으로 변환 -> turf 폴리곤으로 변환 } // 폴리곤 안에 도머 폴리곤이 포함되어있는지 확인해서 반환하는 로직 return turf.booleanContains(turfModuleSetupSurface, convertBatchObject) || turf.booleanWithin(convertBatchObject, turfModuleSetupSurface) }) return containsBatchObjects } /** * 배치면 안에 있는지 확인 * @param {*} squarePolygon * @param {*} turfModuleSetupSurface * @returns */ const checkModuleDisjointSurface = (squarePolygon, turfModuleSetupSurface) => { return turf.booleanContains(turfModuleSetupSurface, squarePolygon) || turf.booleanWithin(squarePolygon, turfModuleSetupSurface) } let moduleGroup = [] const flatRoofDownFlowSetupModule = ( surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, intvHor, intvVer, containsBatchObjects, ) => { let setupModule = [] let installedLastHeightCoord = 0 //마지막으로 설치된 모듈의 좌표 let installedModuleHeightCount = 0 //마지막으로 설치된 모듈의 카운트 let flowLines checkedModule.forEach((module, moduleIndex) => { //모듈의 넓이 높이를 가져옴 (복시도 촌수 적용) //1번 깔았던 모듈 기준으로 잡야아함 let { width, height } = getModuleWidthHeight(maxLengthLine, moduleSetupSurface, module) if (moduleIndex === 0) { flowLines = getFlowLines(moduleSetupSurface, height) if (flowLines.bottom.type === 'curve') { flowLines = getFlowLines(moduleSetupSurface, width) } } //육지붕이 아닐때만 넣는다 육지붕일땐 클릭 이벤트에 별도로 넣어놓음 const moduleArray = [] let calcAreaWidth = flowLines.right.x1 - flowLines.left.x1 //오른쪽 x에서 왼쪽 x를 뺀 가운데를 찾는 로직 let calcModuleWidthCount = calcAreaWidth / (width + intvHor) //뺀 공간에서 모듈을 몇개를 넣을수 있는지 확인하는 로직 let calcMaxModuleWidthCount = calcModuleWidthCount //최대 모듈 단수가 있기 때문에 최대 단수보다 카운트가 크면 최대 단수로 씀씀 let totalModuleWidthCount = Math.floor(calcMaxModuleWidthCount) //치조배치일경우는 한개 더 넣는다 let calcAreaheight = flowLines.bottom.y1 - flowLines.top.y1 //오른쪽 y에서 왼쪽 y를 뺀 가운데를 찾는 로직 let calcModuleHeightCount = calcAreaheight / (height + intvVer) //뺀 공간에서 모듈을 몇개를 넣을수 있는지 확인하는 로직 let calcMaxModuleHeightCount = calcModuleHeightCount //최대 모듈 단수가 있기 때문에 최대 단수보다 카운트가 크면 최대 단수로 씀씀 let totalModuleHeightCount = Math.floor(calcMaxModuleHeightCount) //치조배치일경우는 한개 더 넣는다 let calcStartPoint = flowLines.right.type === 'flat' ? (calcAreaWidth - totalModuleWidthCount * width) / 2 : 0 //반씩 나눠서 중앙에 맞춤 bottom 기준으로 양변이 직선일때만 가운데 정렬 let startPointX = flowLines.left.x1 + calcStartPoint //시작점을 만든다 //근데 양변이 곡선이면 중앙에 맞추기 위해 아래와 위의 길이를 재서 모듈의 길이를 나눠서 들어갈수 있는 갯수가 동일하면 가운데로 정렬 시킨다 if (flowLines.left.type === 'curve' && flowLines.right.type === 'curve') { startPointX = flowLines.left.x1 + (calcAreaWidth - totalModuleWidthCount * width) / 2 if (flowLines.left.x1 < flowLines.bottom.x1) { startPointX = flowLines.left.x1 } } let heightMargin = 0 let widthMargin = 0 let chidoriLength = 0 let moduleMaxRows = totalModuleHeightCount //첫번재 모듈 설치 후 두번째 모듈을 몇개까지 설치 할 수 있는지 계산 if (moduleIndex > 0) { moduleMaxRows = moduleMaxRows - installedModuleHeightCount //두번째 모듈일때 } let isInstall = false for (let i = 0; i < moduleMaxRows; i++) { let moduleY = flowLines.bottom.y1 - height * i - 1 //살짝 여유를 준다 //두번째 모듈 -> 혼합일 경우의 설치될 모듈 높이를 계산 if (moduleIndex > 0) { moduleY = installedLastHeightCoord - intvVer } //첫번째는 붙여서 두번째는 마진을 주고 설치 heightMargin = i === 0 ? 0 : intvVer * i for (let j = 0; j < totalModuleWidthCount; j++) { let moduleX = startPointX + width * j + 1 //5정도 마진을 준다 widthMargin = j === 0 ? 0 : intvHor * j // 가로 마진값 chidoriLength = 0 //치도리가 아니여도 기본값을 5정도 준다 let square = [ [moduleX + widthMargin, moduleY - height - heightMargin], [moduleX + widthMargin, moduleY - heightMargin], [moduleX + width + widthMargin, moduleY - heightMargin], [moduleX + width + widthMargin, moduleY - height - heightMargin], [moduleX + widthMargin, moduleY - height - heightMargin], ] let squarePolygon = turf.polygon([square]) let turfCoordnates = squarePolygon.geometry.coordinates[0].slice(0, -1) let points = turfCoordnates.map((coord) => ({ x: coord[0], y: coord[1] })) moduleOptions = { ...moduleOptions, fill: module.color, surfaceId: moduleSetupSurface.id, moduleInfo: module } let tempModule = new QPolygon(points, { ...moduleOptions, turfPoints: squarePolygon }) let disjointFromTrestle = checkModuleDisjointSurface(squarePolygon, polygonToTurfPolygon(moduleSetupSurface, true)) let isDisjoint = checkModuleDisjointObjects(squarePolygon, containsBatchObjects) if (disjointFromTrestle && isDisjoint) { //최초 한번은 그냥 그린다 //겹치는지 확인해서 포함된 모듈만 그린다 canvas?.add(tempModule) moduleSetupArray.push(tempModule) moduleArray.push(tempModule) canvas.renderAll() // ++installedModuleHeightCount isInstall = true //마지막에 설치된 모듈의 Y 좌표 installedLastHeightCoord = moduleY - height - heightMargin } else { //디버깅용 // tempModule.set({ fill: 'transparent', stroke: 'red', strokeWidth: 1 }) // canvas?.add(tempModule) // canvas.renderAll() } } if (isInstall) { ++installedModuleHeightCount } } setupModule.push(moduleArray) }) } moduleSetupSurfaces.forEach((moduleSetupSurface, index) => { moduleSetupSurface.fire('mousedown') const moduleSetupArray = [] let maxLengthLine = moduleSetupSurface.lines.reduce((acc, cur) => { return acc.length > cur.length ? acc : cur }) const turfModuleSetupSurface = polygonToTurfPolygon(moduleSetupSurface, true) //폴리곤을 turf 객체로 변환 const containsBatchObjects = objectsIncludeSurface(turfModuleSetupSurface) //배치면에 오브젝트(도머, 개구등)이 있는지 확인하는 로직 const surfaceMaxLines = findSetupSurfaceMaxLines(moduleSetupSurface) const intvHor = 30 const intvVer = 10 canvas.renderAll() //육지붕은 왼쪽 기준으로 그려진다 flatRoofDownFlowSetupModule(surfaceMaxLines, maxLengthLine, moduleSetupArray, moduleSetupSurface, intvHor, intvVer, containsBatchObjects) const setupedModules = moduleSetupArray.filter((module, index) => { let disjointFromTrestle = checkModuleDisjointSurface(module.turfPoints, turfModuleSetupSurface) let isDisjoint = checkModuleDisjointObjects(module.turfPoints, containsBatchObjects) if (!(disjointFromTrestle && isDisjoint)) { canvas?.remove(module) // module.set({ fill: 'rgba(255,190,41, 0.4)', stroke: 'black', strokeWidth: 1 }) return false } else { return module } }) canvas?.renderAll() // 나간애들 제외하고 설치된 애들로 겹친애들 삭제 하기 setupedModules.forEach((module, index) => { if (index > 0) { const isOverlap = turf.booleanOverlap(polygonToTurfPolygon(setupedModules[index - 1]), polygonToTurfPolygon(module)) //겹치는지 확인 if (isOverlap) { //겹쳐있으면 삭제 canvas?.remove(module) // module.set({ fill: 'rgba(72, 161, 250, 0.4)', stroke: 'black', strokeWidth: 0.1 }) canvas.renderAll() setupedModules.splice(index, 1) return false } } }) moduleSetupSurface.set({ modules: setupedModules }) setModuleStatisticsData() // getModuleStatistics() // console.log('moduleSetupSurface', moduleSetupSurface) // console.log('setupedModules', setupedModules) // const groupTest = new fabric.Group([moduleSetupSurface, ...setupedModules], { // angle: compasDeg, // }) // canvas.add(groupTest) }) // console.log(calculateForApi()) //드래그 하기위해 기능 활성화 } /** * 도머나 개구가 모듈에 걸치는지 확인하는 로직 * @param {*} squarePolygon * @param {*} containsBatchObjects * @returns */ const checkModuleDisjointObjects = (squarePolygon, containsBatchObjects) => { let isDisjoint = false if (containsBatchObjects.length > 0) { let convertBatchObject //도머가 있으면 적용되는 로직 isDisjoint = containsBatchObjects.every((batchObject) => { if (batchObject.type === 'group') { convertBatchObject = batchObjectGroupToTurfPolygon(batchObject) } else { if (!batchObject.points) { batchObject.set({ points: rectToPolygon(batchObject) }) } convertBatchObject = polygonToTurfPolygon(batchObject) } /** * 도머가 여러개일수있으므로 겹치는게 있다면... * 안겹치는지 확인하는 로직이라 안겹치면 true를 반환 */ return turf.booleanDisjoint(squarePolygon, convertBatchObject) }) } else { isDisjoint = true } return isDisjoint } const getModuleStatistics = () => { const surfaces = canvas.getObjects().filter((obj) => POLYGON_TYPE.MODULE_SETUP_SURFACE === obj.name) // console.log('🚀 ~ getModuleStatistics ~ surfaces:', surfaces) let totalWpout = 0 let moduleInfo = {} const rows = surfaces.map((surface) => { let wpOut = 0 moduleInfo = {} surface.modules.forEach((module) => { if (!moduleInfo[module.moduleInfo.itemId]) { moduleInfo[module.moduleInfo.itemId] = { name: module.moduleInfo.itemNm, amount: 0, id: module.moduleInfo.itemId } } wpOut += +module.moduleInfo.wpOut moduleInfo[module.moduleInfo.itemId].amount++ }) totalWpout += wpOut // console.log('🚀 ~ moduleData.rows=surfaces.map ~ module:', module) const rowObject = {} Object.keys(moduleInfo).forEach((key) => { rowObject[key] = moduleInfo[key].amount }) return { ...rowObject, // 총 발전량 = 발전량 * 모듈 개수 ...surface, name: canvas.getObjects().filter((obj) => obj.id === surface.parentId)[0].directionText, // 지붕면 // powerGeneration: wpOut.toLocaleString('ko-KR', { maximumFractionDigits: 4 }), wpOut: (wpOut / 1000).toFixed(3), } }) // console.log('🚀 ~ getModuleStatistics ~ rows:', rows) // console.log('🚀 ~ getModuleStatistics ~ moduleInfo:', moduleInfo) const header = [ { name: getMessage('modal.panel.batch.statistic.roof.shape'), prop: 'name' }, ...Object.keys(moduleInfo).map((key) => { return { name: moduleInfo[key].name, prop: key } }), { name: `${getMessage('modal.panel.batch.statistic.power.generation.amount')}(kW)`, prop: 'wpOut' }, ] let footer = [getMessage('modal.panel.batch.statistic.total')] let footerData = {} rows.forEach((row) => { Object.keys(moduleInfo).map((key) => { if (!footerData[key]) footerData[key] = 0 footerData[key] += row[key] }) }) Object.keys(footerData).forEach((key) => { footer.push(footerData[key]) }) footer.push((totalWpout / 1000).toFixed(3)) // console.log({ header: header, rows, footer: footer }) setModuleStatistics({ header: header, rows, footer: footer }) } /** * 모듈의 너비와 높이를 계산하는 함수 * @param {object} maxLengthLine 최대 길이 라인 * @param {object} moduleSetupSurface 모듈 설치면 * @param {object} module 모듈 * @returns {object} 모듈의 너비와 높이 */ const getModuleWidthHeight = (maxLengthLine, moduleSetupSurface, module) => { let tmpWidth = (maxLengthLine.flowDirection === 'east' || maxLengthLine.flowDirection === 'west' ? Number(module.longAxis) : Number(module.shortAxis)) / 10 let tmpHeight = (maxLengthLine.flowDirection === 'east' || maxLengthLine.flowDirection === 'west' ? Number(module.shortAxis) : Number(module.longAxis)) / 10 //배치면때는 방향쪽으로 패널이 넓게 누워져야함 if (moduleSetupSurface.direction !== undefined) { tmpWidth = (moduleSetupSurface.direction === 'south' || moduleSetupSurface.direction === 'north' ? Number(module.longAxis) : Number(module.shortAxis)) / 10 tmpHeight = (moduleSetupSurface.direction === 'south' || moduleSetupSurface.direction === 'north' ? Number(module.shortAxis) : Number(module.longAxis)) / 10 } return canvasSetting.roofSizeSet == '1' ? calculateVisibleModuleHeight(tmpWidth, tmpHeight, getDegreeByChon(moduleSetupSurface.roofMaterial.pitch), moduleSetupSurface.direction) : { width: tmpWidth, height: tmpHeight } } const getFlowLines = (moduleSetupSurface, length) => { let flowLines = {} flowLines = { bottom: bottomTopFlowLine(moduleSetupSurface, length).find((obj) => obj.target === 'bottom'), top: bottomTopFlowLine(moduleSetupSurface, length).find((obj) => obj.target === 'top'), left: leftRightFlowLine(moduleSetupSurface, length).find((obj) => obj.target === 'left'), right: leftRightFlowLine(moduleSetupSurface, length).find((obj) => obj.target === 'right'), } return flowLines } return { selectedModules, makeModuleInstArea, manualModuleSetup, autoModuleSetup, restoreModuleInstArea, manualFlatroofModuleSetup, autoFlatroofModuleSetup, checkModuleDisjointObjects, makeModuleInitArea, } }