1100 lines
33 KiB
JavaScript
1100 lines
33 KiB
JavaScript
'use client'
|
|
|
|
import { useCanvas } from '@/hooks/useCanvas'
|
|
import { useEffect, useRef, useState } from 'react'
|
|
import { v4 as uuidv4 } from 'uuid'
|
|
import { useMode } from '@/hooks/useMode'
|
|
import { LINE_TYPE, Mode } from '@/common/common'
|
|
import { Button } from '@nextui-org/react'
|
|
import RangeSlider from './ui/RangeSlider'
|
|
import { useRecoilState, useRecoilValue } from 'recoil'
|
|
import {
|
|
cadFileCompleteState,
|
|
cadFileNameState,
|
|
canvasSizeState,
|
|
compassState,
|
|
currentObjectState,
|
|
fontSizeState,
|
|
globalCompassState,
|
|
googleMapFileNameState,
|
|
roofMaterialState,
|
|
roofState,
|
|
sortedPolygonArray,
|
|
templateTypeState,
|
|
useCadFileState,
|
|
useGoogleMapFileState,
|
|
wallState,
|
|
} from '@/store/canvasAtom'
|
|
import { QLine } from '@/components/fabric/QLine'
|
|
import { getCanvasState, insertCanvasState } from '@/lib/canvas'
|
|
import { calculateIntersection } from '@/util/canvas-util'
|
|
import { QPolygon } from '@/components/fabric/QPolygon'
|
|
import QContextMenu from './common/context-menu/QContextMenu'
|
|
import { modalContent, modalState } from '@/store/modalAtom'
|
|
import { useAxios } from '@/hooks/useAxios'
|
|
import QPolygonContextMenu from '@/components/common/context-menu/QPolygonContextMenu'
|
|
import QLineContextMenu from '@/components/common/context-menu/QLineContextMenu'
|
|
import QEmptyContextMenu from '@/components/common/context-menu/QEmptyContextMenu'
|
|
|
|
import InitSettingsModal from './InitSettingsModal'
|
|
import GridSettingsModal from './GridSettingsModal'
|
|
import { SurfaceShapeModal } from '@/components/ui/SurfaceShape'
|
|
import { changeCurrentRoof, drawDirectionStringToArrow } from '@/util/qpolygon-utils'
|
|
import ThumbnailList from '@/components/ui/ThumbnailLIst'
|
|
import ObjectPlacement from '@/components/ui/ObjectPlacement'
|
|
import { globalLocaleStore } from '@/store/localeAtom'
|
|
|
|
export default function Roof2(props) {
|
|
const { name, userId, email, isLoggedIn } = props
|
|
const {
|
|
canvas,
|
|
handleRedo,
|
|
handleUndo,
|
|
setCanvasBackgroundWithDots,
|
|
saveImage,
|
|
addCanvas,
|
|
handleBackImageLoadToCanvas,
|
|
handleCadImageInit,
|
|
backImg,
|
|
setBackImg,
|
|
} = useCanvas('canvas')
|
|
|
|
const globalLocaleState = useRecoilValue(globalLocaleStore)
|
|
|
|
const { get } = useAxios(globalLocaleState)
|
|
|
|
const canvasRef = useRef(null)
|
|
|
|
//canvas 기본 사이즈
|
|
const [canvasSize, setCanvasSize] = useRecoilState(canvasSizeState)
|
|
|
|
//canvas 가로 사이즈
|
|
const [verticalSize, setVerticalSize] = useState(canvasSize.vertical)
|
|
//canvas 세로 사이즈
|
|
const [horizontalSize, setHorizontalSize] = useState(canvasSize.horizontal)
|
|
// 글자크기
|
|
const [fontSize, setFontSize] = useRecoilState(fontSizeState)
|
|
|
|
const [sortedArray] = useRecoilState(sortedPolygonArray)
|
|
|
|
const [angle, setAngle] = useState(0)
|
|
|
|
const [showControl, setShowControl] = useState(false)
|
|
|
|
//지붕재
|
|
const roofMaterial = useRecoilValue(roofMaterialState)
|
|
|
|
const [templateType, setTemplateType] = useRecoilState(templateTypeState)
|
|
|
|
const [compass, setCompass] = useRecoilState(compassState)
|
|
|
|
const roof = useRecoilValue(roofState)
|
|
|
|
const wall = useRecoilValue(wallState)
|
|
|
|
const [open, setOpen] = useRecoilState(modalState)
|
|
const [contents, setContent] = useRecoilState(modalContent)
|
|
|
|
const [scale, setScale] = useState(1)
|
|
const currentObject = useRecoilValue(currentObjectState)
|
|
|
|
//canvas 썸네일
|
|
const [thumbnails, setThumbnails] = useState([])
|
|
const thumbnailProps = {
|
|
thumbnails,
|
|
canvas,
|
|
}
|
|
|
|
let imgPath
|
|
// cad 파일 업로드
|
|
const [useCadFile, setUseCadFile] = useRecoilState(useCadFileState)
|
|
const [cadFileName, setCadFileName] = useRecoilState(cadFileNameState)
|
|
const [cadFileComplete, setCadFileComplete] = useRecoilState(cadFileCompleteState)
|
|
useCadFile && (imgPath = `/cadImages/${cadFileName}`)
|
|
|
|
// 구글맵 이미지 업로드
|
|
const [useGoogleMapFile, setUseGoogleMapFile] = useRecoilState(useGoogleMapFileState)
|
|
const [googleMapFileName, setGoogleMapFileName] = useRecoilState(googleMapFileNameState)
|
|
useGoogleMapFile && (imgPath = `/mapImages/${googleMapFileName}`)
|
|
|
|
const [globalCampass, setGlobalCampass] = useRecoilState(globalCompassState)
|
|
|
|
const {
|
|
mode,
|
|
setMode,
|
|
changeMode,
|
|
handleClear,
|
|
zoomIn,
|
|
zoomOut,
|
|
zoom,
|
|
togglePolygonLine,
|
|
handleOuterlinesTest,
|
|
handleOuterlinesTest2,
|
|
applyTemplateB,
|
|
makeRoofPatternPolygon,
|
|
createRoofRack,
|
|
drawRoofPolygon,
|
|
drawCellInTrestle,
|
|
drawCellManualInTrestle,
|
|
setDirectionTrestles,
|
|
cutHelpLines,
|
|
} = useMode()
|
|
|
|
// const [canvasState, setCanvasState] = useRecoilState(canvasAtom)
|
|
|
|
useEffect(() => {
|
|
get({ url: `/api/canvas-management/canvas-statuses/by-object/test123240822001/${userId}` }).then((res) => {
|
|
// console.log(res)
|
|
|
|
const arrangeData = res.map((item) => {
|
|
// console.log(item.canvasStatus.replace(/##/g, '"').replace(/\\/g, ''))
|
|
const test = item.canvasStatus.replace(/##/g, '"').replace(/\\/g, '')
|
|
const test2 = test.substring(1, test.length - 1)
|
|
return {
|
|
id: item.id,
|
|
userId: item.userId,
|
|
imageName: `/canvasState/${item.imageName}.png`,
|
|
canvasStatus: JSON.stringify(test2),
|
|
}
|
|
})
|
|
setThumbnails(arrangeData)
|
|
})
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
if (!canvas) {
|
|
return
|
|
}
|
|
changeMode(canvas, mode)
|
|
|
|
if (!cadFileComplete && useCadFile) {
|
|
// cad 파일 로드
|
|
useCadFile && handleBackImageLoadToCanvas(imgPath, canvas)
|
|
}
|
|
|
|
if (useGoogleMapFile) {
|
|
handleBackImageLoadToCanvas(imgPath, canvas)
|
|
}
|
|
}, [canvas, mode])
|
|
|
|
const makeLine = () => {
|
|
if (canvas) {
|
|
const line = new QLine([50, 50, 200, 50], {
|
|
stroke: 'black',
|
|
selectable: true,
|
|
strokeWidth: 2,
|
|
fontSize: fontSize,
|
|
})
|
|
|
|
canvas?.add(line)
|
|
}
|
|
}
|
|
|
|
const makePolygon = () => {
|
|
if (canvas) {
|
|
const polygon = new QPolygon(
|
|
[
|
|
{ x: 100, y: 100 },
|
|
{ x: 600, y: 200 },
|
|
{ x: 700, y: 800 },
|
|
{ x: 100, y: 800 },
|
|
],
|
|
{
|
|
fill: 'transparent',
|
|
stroke: 'black',
|
|
strokeWidth: 2,
|
|
selectable: true,
|
|
fontSize: fontSize,
|
|
},
|
|
)
|
|
canvas?.add(polygon)
|
|
|
|
// polygon.fillCell({ width: 50, height: 30, padding: 10 })
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
setCanvasSize({ ...canvasSize, vertical: parseInt(verticalSize), horizontal: parseInt(horizontalSize) })
|
|
}, [verticalSize, horizontalSize])
|
|
|
|
/**
|
|
* 값 변경시
|
|
*/
|
|
// useEffect(() => {
|
|
// canvasSizeMode()
|
|
// }, [verticalSize, horizontalSize])
|
|
useEffect(() => {
|
|
const { vertical, horizontal } = canvasSize
|
|
if (vertical !== verticalSize || horizontal !== horizontalSize) {
|
|
canvas?.setWidth(horizontalSize)
|
|
canvas?.setHeight(verticalSize)
|
|
canvas?.renderAll()
|
|
}
|
|
}, [canvasSize, canvas])
|
|
|
|
const makeQPolygon = () => {
|
|
const type1 = [
|
|
{ x: 100, y: 100 },
|
|
{ x: 850, y: 100 },
|
|
{ x: 850, y: 800 },
|
|
{ x: 500, y: 800 },
|
|
{ x: 500, y: 400 },
|
|
{ x: 100, y: 400 },
|
|
]
|
|
const type2 = [
|
|
{ x: 200, y: 100 },
|
|
{ x: 200, y: 1000 },
|
|
{ x: 1100, y: 1000 },
|
|
{ x: 1100, y: 600 },
|
|
{ x: 650, y: 600 },
|
|
{ x: 650, y: 100 },
|
|
]
|
|
|
|
const type3 = [
|
|
{ x: 200, y: 100 },
|
|
{ x: 200, y: 800 },
|
|
{ x: 500, y: 800 },
|
|
{ x: 500, y: 300 },
|
|
{ x: 800, y: 300 },
|
|
{ x: 800, y: 100 },
|
|
]
|
|
|
|
const type4 = [
|
|
{ x: 150, y: 450 },
|
|
{ x: 150, y: 800 },
|
|
{ x: 750, y: 800 },
|
|
{ x: 750, y: 300 },
|
|
{ x: 550, y: 300 },
|
|
{ x: 550, y: 450 },
|
|
]
|
|
|
|
const type1A = [
|
|
{ x: 67, y: 81 },
|
|
{ x: 67, y: 660 },
|
|
{ x: 437, y: 660 },
|
|
{ x: 437, y: 1190 },
|
|
{ x: 858, y: 1190 },
|
|
{ x: 858, y: 81 },
|
|
]
|
|
|
|
const type1B = [
|
|
{ x: 137, y: 42 },
|
|
{ x: 137, y: 621 },
|
|
{ x: 667, y: 621 },
|
|
{ x: 667, y: 991 },
|
|
{ x: 1088, y: 991 },
|
|
{ x: 1088, y: 42 },
|
|
]
|
|
|
|
const eightPoint = [
|
|
{ x: 240.1111, y: 130.1111 },
|
|
{ x: 240.1111, y: 630.1111 },
|
|
{ x: 640.1111, y: 630.1111 },
|
|
{ x: 640.1111, y: 480.1111 },
|
|
{ x: 440.1111, y: 480.1111 },
|
|
{ x: 440.1111, y: 280.1111 },
|
|
{ x: 740.1111, y: 280.1111 },
|
|
{ x: 740.1111, y: 130.1111 },
|
|
]
|
|
|
|
const eightPoint2 = [
|
|
{ x: 197, y: 215 },
|
|
{ x: 197, y: 815 },
|
|
{ x: 397, y: 815 },
|
|
{ x: 397, y: 1115 },
|
|
{ x: 697, y: 1115 },
|
|
{ x: 697, y: 815 },
|
|
{ x: 897, y: 815 },
|
|
{ x: 897, y: 215 },
|
|
]
|
|
|
|
const eightPoint3 = [
|
|
{ x: 190, y: 147 },
|
|
{ x: 190, y: 747 },
|
|
{ x: 490, y: 747 },
|
|
{ x: 490, y: 497 },
|
|
{ x: 640, y: 497 },
|
|
{ x: 640, y: 747 },
|
|
{ x: 1090, y: 747 },
|
|
{ x: 1090, y: 147 },
|
|
]
|
|
|
|
const eightPoint4 = [
|
|
{ x: 200, y: 200 },
|
|
{ x: 200, y: 400 },
|
|
{ x: 500, y: 400 },
|
|
{ x: 500, y: 700 },
|
|
{ x: 800, y: 700 },
|
|
{ x: 800, y: 400 },
|
|
{ x: 1100, y: 400 },
|
|
{ x: 1100, y: 200 },
|
|
]
|
|
|
|
const eightPoint5 = [
|
|
{ x: 140, y: 101 },
|
|
{ x: 140, y: 601 },
|
|
{ x: 440, y: 601 },
|
|
{ x: 440, y: 801 },
|
|
{ x: 840, y: 801 },
|
|
{ x: 840, y: 601 },
|
|
{ x: 1140, y: 601 },
|
|
{ x: 1140, y: 101 },
|
|
]
|
|
|
|
const twelvePoint = [
|
|
{ x: 195, y: 166 },
|
|
{ x: 195, y: 466 },
|
|
{ x: 395, y: 466 },
|
|
{ x: 395, y: 766 },
|
|
{ x: 545, y: 766 },
|
|
{ x: 545, y: 466 },
|
|
{ x: 695, y: 466 },
|
|
{ x: 695, y: 666 },
|
|
{ x: 845, y: 666 },
|
|
{ x: 845, y: 466 },
|
|
{ x: 995, y: 466 },
|
|
{ x: 995, y: 166 },
|
|
]
|
|
|
|
const twelvePoint2 = [
|
|
{ x: 165, y: 81 },
|
|
{ x: 165, y: 1081 },
|
|
{ x: 465, y: 1081 },
|
|
{ x: 465, y: 781 },
|
|
{ x: 765, y: 781 },
|
|
{ x: 765, y: 1081 },
|
|
{ x: 1065, y: 1081 },
|
|
{ x: 1065, y: 581 },
|
|
{ x: 765, y: 581 },
|
|
{ x: 765, y: 281 },
|
|
{ x: 1065, y: 281 },
|
|
{ x: 1065, y: 81 },
|
|
]
|
|
|
|
const complicatedType = [
|
|
{ x: 100, y: 100 },
|
|
{ x: 100, y: 1100 },
|
|
{ x: 400, y: 1100 },
|
|
{ x: 400, y: 800 },
|
|
{ x: 700, y: 800 },
|
|
{ x: 700, y: 1100 },
|
|
{ x: 1000, y: 1100 },
|
|
{ x: 1000, y: 600 },
|
|
{ x: 700, y: 600 },
|
|
{ x: 700, y: 300 },
|
|
{ x: 1000, y: 300 },
|
|
{ x: 1000, y: 100 },
|
|
]
|
|
|
|
const testType = [
|
|
{ x: 500, y: 400 },
|
|
{ x: 650, y: 550 },
|
|
{ x: 575, y: 625 },
|
|
{ x: 325, y: 625 },
|
|
{ x: 100, y: 400 },
|
|
]
|
|
|
|
const testType2 = [
|
|
{ x: 100, y: 400 },
|
|
{ x: 325, y: 625 },
|
|
{ x: 575, y: 625 },
|
|
{ x: 650, y: 550 },
|
|
{ x: 500, y: 400 },
|
|
]
|
|
|
|
const triangleType = [
|
|
{ x: 100, y: 100 },
|
|
{ x: 100, y: 600 },
|
|
{ x: 600, y: 600 },
|
|
{ x: 600, y: 100 },
|
|
]
|
|
|
|
const rectangleType1 = [
|
|
{ x: 500, y: 100 },
|
|
{ x: 500, y: 800 },
|
|
{ x: 900, y: 800 },
|
|
{ x: 900, y: 100 },
|
|
]
|
|
|
|
const rectangleType2 = [
|
|
{ x: 100, y: 100 },
|
|
{ x: 100, y: 300 },
|
|
{ x: 600, y: 300 },
|
|
{ x: 600, y: 100 },
|
|
]
|
|
|
|
const types = [type1, type2, type3, type4, type1A, type1B, eightPoint, eightPoint2, eightPoint3, eightPoint4, twelvePoint]
|
|
const newP = [
|
|
{ x: 450, y: 450 },
|
|
{ x: 650, y: 250 },
|
|
{ x: 675, y: 275 },
|
|
{ x: 450, y: 850 },
|
|
]
|
|
|
|
const test1 = [
|
|
{ x: 381, y: 178 },
|
|
{ x: 381, y: 659.3 },
|
|
{ x: 773.3, y: 659.3 },
|
|
{ x: 773.3, y: 497.9 },
|
|
{ x: 1457, y: 497.9 },
|
|
{ x: 1457, y: 178 },
|
|
]
|
|
|
|
const test2 = [
|
|
{ x: 113, y: 114.9 },
|
|
{ x: 113, y: 371.9 },
|
|
{ x: 762, y: 371.9 },
|
|
{ x: 762, y: 818.7 },
|
|
{ x: 1478.6, y: 818.7 },
|
|
{ x: 1478.6, y: 114.9 },
|
|
]
|
|
|
|
const test3 = [
|
|
{ x: 100, y: 100 },
|
|
{ x: 100, y: 600 },
|
|
{ x: 600, y: 600 },
|
|
{ x: 600, y: 100 },
|
|
{ x: 500, y: 100 },
|
|
{ x: 500, y: 200 },
|
|
{ x: 200, y: 200 },
|
|
{ x: 200, y: 100 },
|
|
]
|
|
|
|
const test4 = [
|
|
{ x: 100, y: 100 },
|
|
{ x: 100, y: 1000 },
|
|
{ x: 1100, y: 1000 },
|
|
{ x: 1100, y: 550 },
|
|
{ x: 500, y: 550 },
|
|
{ x: 500, y: 100 },
|
|
]
|
|
|
|
const polygon = new QPolygon(test4, {
|
|
fill: 'transparent',
|
|
stroke: 'green',
|
|
strokeWidth: 1,
|
|
selectable: false,
|
|
fontSize: fontSize,
|
|
name: 'wall',
|
|
})
|
|
|
|
canvas?.add(polygon)
|
|
handleOuterlinesTest2(polygon, 50)
|
|
setTemplateType(1)
|
|
}
|
|
|
|
const rotateShape = () => {
|
|
if (canvas) {
|
|
const activeObject = canvas?.getActiveObject()
|
|
|
|
if (activeObject) {
|
|
activeObject.rotate(angle)
|
|
canvas?.renderAll()
|
|
}
|
|
}
|
|
}
|
|
|
|
const makeQLine = () => {
|
|
if (canvas) {
|
|
const line = new QLine([50, 250, 900, 250], {
|
|
stroke: 'black',
|
|
strokeWidth: 5,
|
|
fontSize: fontSize,
|
|
selectable: true,
|
|
})
|
|
|
|
const line2 = new QLine([450, 450, 821, 78], {
|
|
stroke: 'black',
|
|
strokeWidth: 5,
|
|
fontSize: fontSize,
|
|
selectable: true,
|
|
})
|
|
|
|
canvas?.add(line)
|
|
canvas?.add(line2)
|
|
|
|
const interSectionPoint = calculateIntersection(line, line2)
|
|
|
|
if (interSectionPoint) {
|
|
const circle = new fabric.Circle({
|
|
radius: 5,
|
|
fill: 'red',
|
|
left: interSectionPoint.x - 5,
|
|
top: interSectionPoint.y - 5,
|
|
})
|
|
|
|
canvas?.add(circle)
|
|
}
|
|
}
|
|
}
|
|
|
|
const addBackgroundInPolygon = (polygon) => {
|
|
fabric.Image.fromURL('assets/img/check2.png', function (img) {
|
|
// 패턴 객체를 생성합니다.
|
|
const pattern = new fabric.Pattern({
|
|
source: img.getElement(),
|
|
repeat: 'repeat',
|
|
})
|
|
|
|
polygon.fillBackground(pattern)
|
|
})
|
|
}
|
|
|
|
function PolygonToLine() {
|
|
const polygon = canvas?.getActiveObject()
|
|
|
|
if (polygon.type !== 'QPolygon') {
|
|
return
|
|
}
|
|
|
|
const lines = togglePolygonLine(polygon)
|
|
}
|
|
|
|
/**
|
|
* canvas 내용 저장하기
|
|
*/
|
|
const handleSaveCanvas = async () => {
|
|
// const jsonStr = JSON.stringify(canvas?.toDatalessJSON(['type', 'fontSize']))
|
|
const jsonObj = JSON.stringify(canvas?.toDatalessJSON(['type', 'fontSize', 'lines']))
|
|
console.log(jsonObj)
|
|
|
|
const param = {
|
|
loginId: 'test',
|
|
canvas: jsonObj,
|
|
}
|
|
console.log(param)
|
|
|
|
await insertCanvasState(param)
|
|
handleClear()
|
|
}
|
|
|
|
const drawRoofMaterial = () => {
|
|
const { width, height, roofStyle } = roofMaterial
|
|
|
|
const wallPolygon = canvas?.getObjects().find((obj) => obj.name === 'wall')
|
|
|
|
wallPolygon.set('strokeDashArray', [10, 5, 2, 5])
|
|
wallPolygon.set('stroke', 'blue')
|
|
wallPolygon.set('strokeWidth', 1)
|
|
|
|
const roofs = canvas?.getObjects().filter((obj) => obj.name === 'roof')
|
|
|
|
roofs.forEach((roof) => {
|
|
let maxLengthLine = roof.lines.reduce((acc, cur) => {
|
|
return acc.length > cur.length ? acc : cur
|
|
})
|
|
|
|
const roofRatio = window.devicePixelRatio || 1
|
|
|
|
// 패턴 소스를 위한 임시 캔버스 생성
|
|
const patternSourceCanvas = document.createElement('canvas')
|
|
if (roofStyle === 1) {
|
|
if (maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left') {
|
|
patternSourceCanvas.width = width * roofRatio
|
|
patternSourceCanvas.height = height * roofRatio
|
|
} else {
|
|
patternSourceCanvas.width = height * roofRatio
|
|
patternSourceCanvas.height = width * roofRatio
|
|
}
|
|
} else if (roofStyle === 2) {
|
|
if (maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left') {
|
|
patternSourceCanvas.width = width * 2
|
|
patternSourceCanvas.height = height * 2
|
|
} else {
|
|
patternSourceCanvas.width = height * 2
|
|
patternSourceCanvas.height = width * 2
|
|
}
|
|
}
|
|
|
|
const ctx = patternSourceCanvas.getContext('2d')
|
|
|
|
ctx.scale(roofRatio, roofRatio)
|
|
ctx.strokeStyle = 'green'
|
|
ctx.lineWidth = 0.4
|
|
// 벽돌 패턴 그리기
|
|
if (roofStyle === 1) {
|
|
ctx.strokeRect(0, 0, 50, 30)
|
|
} else if (roofStyle === 2) {
|
|
// 지그재그
|
|
ctx.strokeRect(0, 0, 200, 100)
|
|
ctx.strokeRect(100, 100, 200, 100)
|
|
}
|
|
|
|
// 패턴 생성
|
|
const pattern = new fabric.Pattern({
|
|
source: patternSourceCanvas,
|
|
repeat: 'repeat',
|
|
})
|
|
roof.set('fill', null)
|
|
|
|
roof.set('fill', pattern)
|
|
canvas?.renderAll()
|
|
})
|
|
}
|
|
|
|
/**
|
|
* canvas 내용 불러오기
|
|
*/
|
|
const handleLoadCanvas = async () => {
|
|
const canvasStates = await getCanvasState()
|
|
console.log(JSON.parse(canvasStates.canvas))
|
|
canvas?.loadFromJSON(JSON.parse(canvasStates.canvas))
|
|
}
|
|
|
|
/**
|
|
* 컨트롤러 보이기/숨기기
|
|
*/
|
|
const handleShowController = () => {
|
|
setShowControl(!showControl)
|
|
}
|
|
|
|
const drawRoofPatterns = (roofStyle) => {
|
|
makeRoofPatternPolygon(roofStyle)
|
|
}
|
|
|
|
const deleteCell = () => {
|
|
const selectedCells = canvas?.getObjects().filter((obj) => obj.name === 'cell' && obj.selected)
|
|
|
|
selectedCells.forEach((cell) => {
|
|
canvas?.remove(cell)
|
|
})
|
|
}
|
|
|
|
const setCompassState = (degree) => {
|
|
setCompass(degree)
|
|
}
|
|
|
|
const changeLength = (e) => {
|
|
const polygon = canvas?.getActiveObject()
|
|
|
|
if (polygon?.type !== 'QPolygon') {
|
|
return
|
|
}
|
|
setScale(e)
|
|
polygon.setScaleX(e)
|
|
|
|
canvas?.renderAll()
|
|
}
|
|
|
|
const moduleConfiguration = () => {
|
|
createRoofRack()
|
|
}
|
|
|
|
const setDirectionStringToArrow = () => {
|
|
drawDirectionStringToArrow(canvas, globalCampass)
|
|
|
|
/**
|
|
* 나중에 유틸로 다시 구현
|
|
*/
|
|
// const groupShapes = canvas?.getObjects().filter((obj) => obj.name === 'cellGroup')
|
|
|
|
// console.log('groupShapes', groupShapes)
|
|
|
|
// groupShapes.forEach((obj) => {
|
|
// let originAngle = obj._objects.find((array) => array.originAngle !== undefined).originAngle
|
|
|
|
// console.log('originAngle', originAngle)
|
|
|
|
// let rotateAngle = globalCampass
|
|
|
|
// // let rotateAngle = originAngle + globalCampass
|
|
// // if (rotateAngle > 360) {
|
|
// // rotateAngle -= 360
|
|
// // }
|
|
|
|
// console.log('rotateAngle', rotateAngle)
|
|
|
|
// obj.set({ angle: rotateAngle, originX: 'center', originY: 'center' })
|
|
// obj.setCoords()
|
|
// })
|
|
canvas?.renderAll()
|
|
}
|
|
|
|
const setHipRoof = () => {
|
|
const polygon = canvas?.getObjects().find((obj) => obj.name === 'roof')
|
|
const currentRoof = polygon.lines[2]
|
|
currentRoof.attributes.type = LINE_TYPE.WALLLINE.EAVES
|
|
currentRoof.attributes.offset = 50
|
|
changeCurrentRoof(currentRoof, canvas)
|
|
}
|
|
const setGableRoof = () => {
|
|
const polygon = canvas?.getObjects().find((obj) => obj.name === 'roof')
|
|
const currentRoof = polygon.lines[2]
|
|
currentRoof.attributes.type = LINE_TYPE.WALLLINE.GABLE
|
|
currentRoof.attributes.offset = 30
|
|
changeCurrentRoof(currentRoof, canvas)
|
|
}
|
|
const setHipAndGableRoof = () => {
|
|
let offset = Number(prompt('팔작지붕 폭', '50'))
|
|
if (!isNaN(offset) && offset > 0) {
|
|
const polygon = canvas?.getObjects().find((obj) => obj.name === 'roof')
|
|
const currentRoof = polygon.lines[2]
|
|
currentRoof.attributes.type = LINE_TYPE.WALLLINE.HIPANDGABLE
|
|
currentRoof.attributes.width = offset
|
|
changeCurrentRoof(currentRoof, canvas)
|
|
} else {
|
|
alert('폭은 0 보다 커야 함')
|
|
}
|
|
}
|
|
const setJerkInHeadRoof = () => {
|
|
let offset = Number(prompt('팔작지붕 폭', '50'))
|
|
if (!isNaN(offset) && offset > 0) {
|
|
const polygon = canvas?.getObjects().find((obj) => obj.name === 'roof')
|
|
const currentRoof = polygon.lines[2]
|
|
currentRoof.attributes.type = LINE_TYPE.WALLLINE.JERKINHEAD
|
|
currentRoof.attributes.width = offset
|
|
changeCurrentRoof(currentRoof, canvas)
|
|
} else {
|
|
alert('폭은 0 보다 커야 함')
|
|
}
|
|
}
|
|
const setWallRoof = () => {
|
|
let offset = Number(prompt('소매 폭', '0'))
|
|
const polygon = canvas?.getObjects().find((obj) => obj.name === 'roof')
|
|
const currentRoof = polygon.lines[2]
|
|
currentRoof.attributes.type = LINE_TYPE.WALLLINE.WALL
|
|
currentRoof.attributes.width = offset
|
|
changeCurrentRoof(currentRoof, canvas)
|
|
}
|
|
return (
|
|
<>
|
|
{canvas && (
|
|
<>
|
|
<div className=" my-8 w-full text:pretty">
|
|
<Button
|
|
className="m-1 p-2"
|
|
onClick={() => {
|
|
setContent(<InitSettingsModal canvasProps={canvas} />)
|
|
setOpen(true)
|
|
}}
|
|
>
|
|
배치면 초기설정
|
|
</Button>
|
|
<Button
|
|
className="m-1 p-2"
|
|
onClick={() => {
|
|
setContent(<GridSettingsModal canvasProps={canvas} />)
|
|
setOpen(true)
|
|
}}
|
|
>
|
|
그리드 설정
|
|
</Button>
|
|
<Button className="m-1 p-2" color={`${mode === Mode.DEFAULT ? 'primary' : 'default'}`} onClick={() => setMode(Mode.DEFAULT)}>
|
|
모드 DEFAULT
|
|
</Button>
|
|
<Button className="m-1 p-2" color={`${mode === Mode.DRAW_LINE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.DRAW_LINE)}>
|
|
임의 그리드 모드
|
|
</Button>
|
|
<Button className="m-1 p-2" color={`${mode === Mode.EDIT ? 'primary' : 'default'}`} onClick={() => setMode(Mode.EDIT)}>
|
|
에디팅모드
|
|
</Button>
|
|
<Button
|
|
className="m-1 p-2"
|
|
color={`${mode === Mode.MOUSE_DISTANCE ? 'primary' : 'default'}`}
|
|
onClick={() => setMode(Mode.MOUSE_DISTANCE)}
|
|
>
|
|
마우스거리 모드
|
|
</Button>
|
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.TEMPLATE)}>
|
|
템플릿(기둥)
|
|
</Button>
|
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.PATTERNA)}>
|
|
템플릿(A 패턴)
|
|
</Button>
|
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.PATTERNB)}>
|
|
템플릿(B 패턴)
|
|
</Button>
|
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => drawRoofPatterns(1)}>
|
|
지붕패턴1
|
|
</Button>
|
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => drawRoofPatterns(2)}>
|
|
지붕패턴2
|
|
</Button>
|
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setCompassState(90)}>
|
|
방위표◀
|
|
</Button>
|
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setCompassState(270)}>
|
|
방위표▶
|
|
</Button>
|
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setCompassState(180)}>
|
|
방위표▲
|
|
</Button>
|
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setCompassState(0)}>
|
|
방위표▼
|
|
</Button>
|
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.ROOF_TRESTLE)}>
|
|
지붕가대생성
|
|
</Button>
|
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.FILL_CELLS)}>
|
|
태양광셀생성
|
|
</Button>
|
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.CELL_POWERCON)}>
|
|
파워콘설치
|
|
</Button>
|
|
<Button className="m-1 p-2" color={`${mode === Mode.TEXTBOX ? 'primary' : 'default'}`} onClick={() => setMode(Mode.TEXTBOX)}>
|
|
텍스트박스 모드
|
|
</Button>
|
|
<Button className="m-1 p-2" color={`${mode === Mode.DRAW_RECT ? 'primary' : 'default'}`} onClick={() => setMode(Mode.DRAW_RECT)}>
|
|
도머 추가 모드
|
|
</Button>
|
|
<Button
|
|
className="m-1 p-2"
|
|
color={`${mode === Mode.ADSORPTION_POINT ? 'primary' : 'default'}`}
|
|
onClick={() => setMode(Mode.ADSORPTION_POINT)}
|
|
>
|
|
흡착점 모드
|
|
</Button>
|
|
<Button
|
|
className="m-1 p-2"
|
|
color={`${mode === Mode.DRAW_HELP_LINE ? 'primary' : 'default'}`}
|
|
onClick={() => setMode(Mode.DRAW_HELP_LINE)}
|
|
>
|
|
보조선 연결 모드
|
|
</Button>
|
|
<Button className="m-1 p-2" onClick={handleUndo}>
|
|
Undo
|
|
</Button>
|
|
<Button className="m-1 p-2" onClick={handleRedo}>
|
|
Redo
|
|
</Button>
|
|
<Button className="m-1 p-2" onClick={handleClear}>
|
|
clear
|
|
</Button>
|
|
{
|
|
<Button className="m-1 p-2" onClick={zoomIn}>
|
|
확대
|
|
</Button>
|
|
}
|
|
<Button className="m-1 p-2" onClick={zoomOut}>
|
|
축소
|
|
</Button>
|
|
현재 줌 : {zoom}%
|
|
<Button className="m-1 p-2" onClick={makeLine}>
|
|
선 추가
|
|
</Button>
|
|
<Button className="m-1 p-2" onClick={makePolygon}>
|
|
다각형 추가
|
|
</Button>
|
|
{/*{templateType === 0 && (*/}
|
|
{/* <>*/}
|
|
<Button className="m-1 p-2" onClick={makeQPolygon}>
|
|
QPolygon
|
|
</Button>
|
|
{/* </>*/}
|
|
{/*)}*/}
|
|
<Button className={'m-1 p-2'} onClick={setHipRoof}>
|
|
용마루지붕
|
|
</Button>
|
|
<Button className={'m-1 p-2'} onClick={setHipAndGableRoof}>
|
|
팔작지붕
|
|
</Button>
|
|
<Button className={'m-1 p-2'} onClick={setGableRoof}>
|
|
박공지붕
|
|
</Button>
|
|
<Button className={'m-1 p-2'} onClick={setJerkInHeadRoof}>
|
|
반절처지붕
|
|
</Button>
|
|
<Button className={'m-1 p-2'} onClick={setWallRoof}>
|
|
벽지붕
|
|
</Button>
|
|
<Button className="m-1 p-2" onClick={() => saveImage(uuidv4(), userId, setThumbnails)}>
|
|
저장
|
|
</Button>
|
|
<Button
|
|
className="m-1 p-2"
|
|
onClick={() => {
|
|
setContent(<SurfaceShapeModal canvas={canvas} />)
|
|
setOpen(true)
|
|
}}
|
|
>
|
|
면형상
|
|
</Button>
|
|
<Button
|
|
className="m-1 p-2"
|
|
onClick={() => {
|
|
setContent(<ObjectPlacement canvas={canvas} />)
|
|
setOpen(true)
|
|
}}
|
|
>
|
|
오브젝트 배치
|
|
</Button>
|
|
{/*<Button className="m-1 p-2" onClick={rotateShape}>
|
|
회전
|
|
</Button>*/}
|
|
{/*<Button className="m-1 p-2" onClick={makeQLine}>
|
|
QLine
|
|
</Button>
|
|
<Button className="m-1 p-2" onClick={PolygonToLine}>
|
|
PolygonToLine
|
|
</Button>
|
|
<Button className="m-1 p-2" onClick={addCanvas}>
|
|
캔버스 추가
|
|
</Button>*/}
|
|
<Button className="m-1 p-2" onClick={cutHelpLines}>
|
|
보조선 절삭
|
|
</Button>
|
|
{templateType === 1 && (
|
|
<>
|
|
<Button className="m-1 p-2" onClick={drawRoofMaterial}>
|
|
지붕타입 지붕재
|
|
</Button>
|
|
<Button className="m-1 p-2" onClick={createRoofRack}>
|
|
지붕가대설치
|
|
</Button>
|
|
<Button className="m-1 p-2" onClick={setDirectionTrestles}>
|
|
가대 방향 설정
|
|
</Button>
|
|
<Button className="m-1 p-2" onClick={drawCellInTrestle}>
|
|
선택한 가대 셀채우기
|
|
</Button>
|
|
</>
|
|
)}
|
|
<Button className="m-1 p-2" onClick={deleteCell}>
|
|
선택 셀 지우기
|
|
</Button>
|
|
<Button className="m-1 p-2" onClick={moduleConfiguration}>
|
|
모듈,회로구성
|
|
</Button>
|
|
<Button className="m-1 p-2" onClick={drawCellInTrestle}>
|
|
모듈 채우기
|
|
</Button>
|
|
<Button className="m-1 p-2" onClick={drawCellManualInTrestle}>
|
|
수동 모듈 채우기
|
|
</Button>
|
|
<Button
|
|
className="m-1 p-2"
|
|
onClick={() => {
|
|
if (useCadFile) {
|
|
setUseCadFile(false)
|
|
setCadFileName('')
|
|
handleCadImageInit()
|
|
}
|
|
}}
|
|
>
|
|
cad 파일 초기화
|
|
</Button>
|
|
<Button
|
|
className="m-1 p-2"
|
|
onClick={() => {
|
|
if (useCadFile) {
|
|
backImg
|
|
.set({
|
|
selectable: false,
|
|
opacity: 0.7,
|
|
})
|
|
.sendToBack()
|
|
canvas.clear()
|
|
canvas.add(backImg)
|
|
canvas.renderAll()
|
|
setCadFileComplete(true)
|
|
}
|
|
}}
|
|
>
|
|
배경 이미지 조정 완료
|
|
</Button>
|
|
<Button className="m-1 p-2" color={`${showControl ? 'primary' : 'default'}`} onClick={handleShowController}>
|
|
canvas 컨트롤러 {`${showControl ? '숨기기' : '보이기'}`}
|
|
</Button>
|
|
</div>
|
|
<div className={showControl ? `flex justify-center flex-col items-center` : `hidden`}>
|
|
<div className="m-2 p-2 w-80">
|
|
<RangeSlider title={`각도${angle}`} initValue={angle} min="0" step="1" max="360" onchange={setAngle} />
|
|
</div>
|
|
<div className="m-2 p-2 w-80">
|
|
<RangeSlider
|
|
title={`canvas 가로 사이즈${horizontalSize}`}
|
|
initValue={horizontalSize}
|
|
min="500"
|
|
step="100"
|
|
max="2000"
|
|
onchange={setHorizontalSize}
|
|
/>
|
|
</div>
|
|
<div className="m-2 p-2 w-80">
|
|
<RangeSlider
|
|
title={`canvas 세로 사이즈${verticalSize}`}
|
|
initValue={verticalSize}
|
|
min="500"
|
|
step="100"
|
|
max="2000"
|
|
onchange={setVerticalSize}
|
|
/>
|
|
</div>
|
|
<div className="m-2 p-2 w-80">
|
|
<RangeSlider title={`글자 크기${fontSize}`} initValue={fontSize} onchange={setFontSize} />
|
|
</div>
|
|
<div className="m-2 p-2 w-80">
|
|
<RangeSlider title={`선택한 obj 가로 늘리기${scale}`} initValue={scale} min={0.01} max={2.0} step={0.01} onchange={changeLength} />
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
<ThumbnailList {...thumbnailProps} />
|
|
{/* <div className={'flex'}>
|
|
<p className={'m-1 p-3'}>각도 입력(0~360) 후 방향설정 클릭</p>
|
|
<Input
|
|
className="m-1 p-3"
|
|
type={'text'}
|
|
value={globalCampass}
|
|
onChange={(e) => {
|
|
const val = e.target.value.replace(/[^-0-9]/g, '')
|
|
if (val < 0 || val > 360) {
|
|
setGlobalCampass(0)
|
|
} else {
|
|
setGlobalCampass(Number(val))
|
|
}
|
|
}}
|
|
/>
|
|
<Button className="m-1 p-3" onClick={setDirectionStringToArrow}>
|
|
방향 설정
|
|
</Button>
|
|
</div>*/}
|
|
{/* <div className="relative w-48 h-48 flex justify-center items-center border-2 border-gray-300 rounded-full">
|
|
Compass Circle
|
|
<div
|
|
className="absolute w-full h-full border-2 border-gray-300 rounded-full flex justify-center items-center"
|
|
style={{ rotate: `${globalCampass}deg` }}
|
|
>
|
|
N, S, E, W Labels
|
|
<div className="absolute top-2 text-lg font-bold">N</div>
|
|
<div className="absolute bottom-2 text-lg font-bold">S</div>
|
|
<div className="absolute right-2 text-lg font-bold" style={{ rotate: '90deg' }}>
|
|
E
|
|
</div>
|
|
<div className="absolute left-2 text-lg font-bold" style={{ rotate: '-90deg' }}>
|
|
W
|
|
</div>
|
|
</div>
|
|
|
|
Compass Pointer
|
|
<div className="relative w-10 h-10">
|
|
Red Upper Triangle
|
|
<div
|
|
className="absolute top-0"
|
|
style={{
|
|
width: 0,
|
|
height: 0,
|
|
rotate: `${globalCampass - 180}deg`,
|
|
borderLeft: '15px solid transparent',
|
|
borderRight: '15px solid transparent',
|
|
borderTop: '60px solid red',
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>*/}
|
|
<div className="flex justify-start my-8 mx-2 w-full">
|
|
<canvas ref={canvasRef} id="canvas" style={{ border: '1px solid black' }} />
|
|
{!canvas ? null : mode === Mode.DRAW_LINE ? (
|
|
<QEmptyContextMenu contextRef={canvasRef} canvasProps={canvas} />
|
|
) : currentObject?.type === 'QPolygon' ? (
|
|
<QPolygonContextMenu contextRef={canvasRef} canvasProps={canvas} />
|
|
) : currentObject?.type === 'QLine' ? (
|
|
<QLineContextMenu contextRef={canvasRef} canvasProps={canvas} />
|
|
) : (
|
|
<QContextMenu contextRef={canvasRef} canvasProps={canvas} />
|
|
)}
|
|
</div>
|
|
</>
|
|
)
|
|
}
|