diff --git a/Nextjs 14 컴포넌트에 대해서....pdf b/Nextjs 14 컴포넌트에 대해서....pdf
new file mode 100644
index 00000000..fe9072f1
Binary files /dev/null and b/Nextjs 14 컴포넌트에 대해서....pdf differ
diff --git a/Qcast coding convention.pdf b/Qcast coding convention.pdf
new file mode 100644
index 00000000..0ab0e190
Binary files /dev/null and b/Qcast coding convention.pdf differ
diff --git a/Qcast development guilde.pdf b/Qcast development guilde.pdf
new file mode 100644
index 00000000..d991663d
Binary files /dev/null and b/Qcast development guilde.pdf differ
diff --git a/README.md b/README.md
index 24eddc59..c4033664 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,36 @@
-# 점 갯수 별 타입
+This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
-## 점 6개
+## Getting Started
-### type1
+First, run the development server:
-
+```bash
+npm run dev
+# or
+yarn dev
+# or
+pnpm dev
+# or
+bun dev
+```
-### type2
+Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
-
+You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
-### type3
+This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
-
+## Learn More
-### type4
+To learn more about Next.js, take a look at the following resources:
-
+- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
+- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+
+You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
+
+## Deploy on Vercel
+
+The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+
+Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
diff --git a/next.config.mjs b/next.config.mjs
index 15466bd2..80649ef9 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -10,6 +10,9 @@ const nextConfig = {
// config.infrastructureLogging = { debug: /PackFileCache/ };
return config
},
+ sassOptions: {
+ includePaths: ['./src/styles'],
+ },
}
export default nextConfig
diff --git a/package.json b/package.json
index d4cdd9bb..28845ef0 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
"postcss": "^8",
"prettier": "^3.3.3",
"prisma": "^5.17.0",
+ "sass": "^1.77.8",
"tailwindcss": "^3.4.1"
}
}
diff --git a/shape-type.md b/shape-type.md
new file mode 100644
index 00000000..24eddc59
--- /dev/null
+++ b/shape-type.md
@@ -0,0 +1,19 @@
+# 점 갯수 별 타입
+
+## 점 6개
+
+### type1
+
+
+
+### type2
+
+
+
+### type3
+
+
+
+### type4
+
+
diff --git a/src/app/changelog/page.jsx b/src/app/changelog/page.jsx
index 1dcc8d0e..52c207b8 100644
--- a/src/app/changelog/page.jsx
+++ b/src/app/changelog/page.jsx
@@ -47,6 +47,9 @@ export default function changelogPage() {
Button
+
>
)
}
diff --git a/src/app/layout.js b/src/app/layout.js
index 55cb11f3..d9dd77d0 100644
--- a/src/app/layout.js
+++ b/src/app/layout.js
@@ -1,5 +1,6 @@
import { Inter } from 'next/font/google'
import './globals.css'
+import '../styles/style.scss'
import Headers from '@/components/Headers'
import RecoilRootWrapper from './RecoilWrapper'
import UIProvider from './UIProvider'
diff --git a/src/components/Roof2.jsx b/src/components/Roof2.jsx
index da41280b..be84a135 100644
--- a/src/components/Roof2.jsx
+++ b/src/components/Roof2.jsx
@@ -2,15 +2,15 @@ import { useCanvas } from '@/hooks/useCanvas'
import { useEffect, useState } from 'react'
import { Mode, useMode } from '@/hooks/useMode'
import { Button } from '@nextui-org/react'
-import QRect from '@/components/fabric/QRect'
import RangeSlider from './ui/RangeSlider'
import { useRecoilState, useRecoilValue } from 'recoil'
-import { canvasSizeState, fontSizeState, roofMaterialState, roofState, sortedPolygonArray } from '@/store/canvasAtom'
+import { canvasSizeState, fontSizeState, roofMaterialState, sortedPolygonArray } 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 offsetPolygon from '@/util/qpolygon-utils'
export default function Roof2() {
const { canvas, handleRedo, handleUndo, setCanvasBackgroundWithDots, saveImage, addCanvas } = useCanvas('canvas')
@@ -48,6 +48,7 @@ export default function Roof2() {
handleOuterlinesTest2,
applyTemplateB,
makeRoofPatternPolygon,
+ createRoofRack,
} = useMode()
// const [canvasState, setCanvasState] = useRecoilState(canvasAtom)
@@ -59,22 +60,6 @@ export default function Roof2() {
changeMode(canvas, mode)
}, [canvas, mode])
- const makeRect = () => {
- if (canvas) {
- const rect = new QRect({
- left: 100,
- top: 100,
- fill: 'transparent',
- stroke: 'black',
- width: 400,
- height: 100,
- fontSize: fontSize,
- })
-
- canvas?.add(rect)
- }
- }
-
const makeLine = () => {
if (canvas) {
const line = new QLine([50, 50, 200, 50], {
@@ -264,7 +249,7 @@ export default function Roof2() {
{ x: 675, y: 275 },
{ x: 450, y: 850 },
]
- const polygon = new QPolygon(type2, {
+ const polygon = new QPolygon(type1, {
fill: 'transparent',
stroke: 'black',
strokeWidth: 1,
@@ -448,21 +433,6 @@ export default function Roof2() {
makeRoofPatternPolygon(roofStyle)
}
- const createRoofRack = () => {
- 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
- })
-
- if (maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left') {
- roof.fillCell({ width: 50, height: 100, padding: 0 })
- } else {
- roof.fillCell({ width: 100, height: 50, padding: 0 })
- }
- })
- }
-
return (
<>
{canvas && (
@@ -501,9 +471,6 @@ export default function Roof2() {
setMode(Mode.TEXTBOX)}>
텍스트박스 모드
- setMode(Mode.DRAW_RECT)}>
- 사각형 생성 모드
-
Undo
@@ -520,9 +487,6 @@ export default function Roof2() {
축소
현재 줌 : {zoom}%
-
- 사각형만들기
-
선 추가
diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js
index 685d83ee..61a7928c 100644
--- a/src/components/fabric/QPolygon.js
+++ b/src/components/fabric/QPolygon.js
@@ -13,6 +13,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
hips: [],
ridges: [],
connectRidges: [],
+ cells: [],
initialize: function (points, options, canvas) {
// 소수점 전부 제거
points.forEach((point) => {
@@ -223,6 +224,8 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
const drawCellsArray = [] //그려진 셀의 배열
+ let idx = 1
+
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
const rectLeft = minX + i * (rectWidth + cell.padding) + tmpWidth
@@ -251,13 +254,18 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
lockScalingX: true, // X 축 크기 조정 잠금
lockScalingY: true, // Y 축 크기 조정 잠금
opacity: 0.8,
+ name: 'cell',
+ idx: idx,
})
+
+ idx++
drawCellsArray.push(rect) //배열에 넣어서 반환한다
this.canvas.add(rect)
}
}
}
this.canvas?.renderAll()
+ this.cells = drawCellsArray
return drawCellsArray
},
inPolygon(point) {
diff --git a/src/components/fabric/QRect.js b/src/components/fabric/QRect.js
deleted file mode 100644
index ad6c8ae9..00000000
--- a/src/components/fabric/QRect.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import { fabric } from 'fabric'
-export default class QRect extends fabric.Rect {
- #text = []
- #viewLengthText
- #fontSize
- type = 'QRect'
- constructor(option) {
- if (!option.fontSize) {
- throw new Error('Font size is required.')
- }
- super(option)
- this.#fontSize = option.fontSize
- this.#init(option)
- this.#addControl()
- }
-
- #init(option) {
- this.#viewLengthText = option.viewLengthText ?? true
- }
-
- setViewLengthText(bool) {
- this.#viewLengthText = bool
- this.#addLengthText()
- }
-
- setFontSize(fontSize) {
- this.#fontSize = fontSize
- this.#addLengthText()
- }
-
- #addControl() {
- this.on('removed', () => {
- if (this.#text.length > 0) {
- this.#text.forEach((text) => {
- this.canvas.remove(text)
- })
- this.#text = []
- }
- })
-
- this.on('added', () => {
- this.#addLengthText()
- })
-
- this.on('modified', (e) => {
- this.#addLengthText()
- })
-
- this.on('scaling', (e) => {
- this.#addLengthText()
- })
-
- this.on('moving', () => {
- this.#addLengthText()
- })
- }
- #addLengthText() {
- if (this.#text.length > 0) {
- this.#text.forEach((text) => {
- this.canvas.remove(text)
- })
- this.#text = []
- }
-
- if (!this.#viewLengthText) {
- return
- }
-
- const scaleX = this.scaleX
- const scaleY = this.scaleY
-
- const lines = [
- {
- start: { x: this.left, y: this.top },
- end: { x: this.left + this.width * scaleX, y: this.top },
- },
- {
- start: { x: this.left, y: this.top + this.height * scaleY },
- end: { x: this.left, y: this.top },
- },
- ]
-
- lines.forEach((line) => {
- const dx = line.end.x - line.start.x
- const dy = line.end.y - line.start.y
- const length = Math.sqrt(dx * dx + dy * dy)
-
- const text = new fabric.Text(length.toFixed(0), {
- left: (line.start.x + line.end.x) / 2,
- top: (line.start.y + line.end.y) / 2,
- fontSize: this.#fontSize,
- selectable: false,
- })
- this.#text.push(text)
- this.canvas.add(text)
- })
- }
-}
diff --git a/src/hooks/useCanvas.js b/src/hooks/useCanvas.js
index 31b223bf..77f261cc 100644
--- a/src/hooks/useCanvas.js
+++ b/src/hooks/useCanvas.js
@@ -5,7 +5,6 @@ import { actionHandler, anchorWrapper, polygonPositionHandler } from '@/util/can
import { useRecoilState } from 'recoil'
import { canvasSizeState, fontSizeState } from '@/store/canvasAtom'
import { QLine } from '@/components/fabric/QLine'
-import QRect from '@/components/fabric/QRect'
import { QPolygon } from '@/components/fabric/QPolygon'
import { defineQLine } from '@/util/qline-utils'
import { defineQPloygon } from '@/util/qpolygon-utils'
@@ -65,6 +64,7 @@ export function useCanvas(id) {
if (canvas) {
initialize()
canvas?.on('object:added', onChange)
+ canvas?.on('object:added', addEventOnObject)
canvas?.on('object:modified', onChange)
canvas?.on('object:removed', onChange)
canvas?.on('mouse:move', drawMouseLines)
@@ -93,6 +93,21 @@ export function useCanvas(id) {
canvas?.off('mouse:down', handleMouseDown)
}
+ const addEventOnObject = (e) => {
+ const target = e.target
+ if (target.name === 'cell') {
+ target.on('mousedown', () => {
+ target.set({ fill: 'red' })
+ })
+ }
+
+ if (target.name === 'trestle') {
+ target.on('mousedown', () => {
+ target.set({ strokeWidth: 5 })
+ })
+ }
+ }
+
/**
* 마우스 포인터의 가이드라인을 제거합니다.
*/
@@ -116,7 +131,6 @@ export function useCanvas(id) {
fabric.QPolygon = QPolygon
QPolygon.prototype.canvas = canvas
QLine.prototype.canvas = canvas
- QRect.prototype.canvas = canvas
defineQLine()
defineQPloygon()
}
@@ -542,7 +556,24 @@ export function useCanvas(id) {
const addCanvas = () => {
// const canvasState = canvas
- const objs = canvas?.toJSON(['selectable', 'name', 'parentId', 'id', 'length', 'idx', 'direction', 'lines', 'points'])
+ const objs = canvas?.toJSON([
+ 'selectable',
+ 'name',
+ 'parentId',
+ 'id',
+ 'length',
+ 'idx',
+ 'direction',
+ 'lines',
+ 'points',
+ 'lockMovementX',
+ 'lockMovementY',
+ 'lockRotation',
+ 'lockScalingX',
+ 'lockScalingY',
+ 'opacity',
+ 'cells',
+ ])
const str = JSON.stringify(objs)
@@ -552,16 +583,12 @@ export function useCanvas(id) {
// 역직렬화하여 캔버스에 객체를 다시 추가합니다.
canvas?.loadFromJSON(JSON.parse(str), function () {
// 모든 객체가 로드되고 캔버스에 추가된 후 호출됩니다.
+ console.log(canvas?.getObjects().filter((obj) => obj.name === 'roof'))
canvas?.renderAll() // 캔버스를 다시 그립니다.
})
}, 1000)
}
- const changeCanvas = (idx) => {
- canvas?.clear()
- const canvasState = JSON.parse(canvasList[idx])
- }
-
return {
canvas,
addShape,
@@ -577,6 +604,5 @@ export function useCanvas(id) {
handleFlip,
setCanvasBackgroundWithDots,
addCanvas,
- changeCanvas,
}
}
diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js
index c60fba2b..efa933e2 100644
--- a/src/hooks/useMode.js
+++ b/src/hooks/useMode.js
@@ -1,5 +1,4 @@
import { useEffect, useRef, useState } from 'react'
-import QRect from '@/components/fabric/QRect'
import { findTopTwoIndexesByDistance, getCenterPoint, getDirection, getStartIndex, rearrangeArray } from '@/util/canvas-util'
import { useRecoilState } from 'recoil'
@@ -17,6 +16,7 @@ import {
import { QLine } from '@/components/fabric/QLine'
import { fabric } from 'fabric'
import { QPolygon } from '@/components/fabric/QPolygon'
+import offsetPolygon from '@/util/qpolygon-utils'
export const Mode = {
DRAW_LINE: 'drawLine', // 기준선 긋기모드`
@@ -58,6 +58,7 @@ export function useMode() {
const [selectedCellRoofArray, setSelectedCellRoofArray] = useState([])
const [drewRoofCells, setDrewRoofCells] = useRecoilState(drewRoofCellsState)
+ const [roofStyle, setRoofStyle] = useState(1) //기본 지붕 패턴
useEffect(() => {
// 이벤트 리스너 추가
@@ -521,6 +522,7 @@ export function useMode() {
// handleOuterlines()
const wall = makePolygon()
+ wall.set({ name: 'wall' })
setWall(wall)
return wall
@@ -614,27 +616,6 @@ export function useMode() {
rect.set({ width: Math.abs(origX - pointer.x) })
rect.set({ height: Math.abs(origY - pointer.y) })
})
-
- canvas.on('mouse:up', function (o) {
- const pointer = canvas.getPointer(o.e)
- const qRect = new QRect({
- left: origX,
- top: origY,
- originX: 'left',
- originY: 'top',
- width: pointer.x - origX,
- height: pointer.y - origY,
- angle: 0,
- viewLengthText: true,
- fill: 'transparent',
- stroke: 'black',
- transparentCorners: false,
- fontSize: fontSize,
- })
- canvas.remove(rect)
- canvas.add(qRect)
- isDown = false
- })
}
/**
@@ -1112,67 +1093,14 @@ export function useMode() {
/**
*벽 지붕 외곽선 생성 polygon을 입력받아 만들기
*/
- const handleOuterlinesTest2 = (polygon, offset = 71) => {
- const offsetPoints = []
- const sortedIndex = getStartIndex(polygon.lines)
- let tmpArraySorted = rearrangeArray(polygon.lines, sortedIndex)
+ const handleOuterlinesTest2 = (polygon, offset = 50) => {
+ const offsetPoints = offsetPolygon(polygon.points, offset)
- if (tmpArraySorted[0].direction === 'right') {
- //시계방향
- tmpArraySorted = tmpArraySorted.reverse() //그럼 배열을 거꾸로 만들어서 무조건 반시계방향으로 배열 보정
- }
-
- setSortedArray(tmpArraySorted) //recoil에 넣음
-
- const points = tmpArraySorted.map((line) => ({
- x: line.x1,
- y: line.y1,
- }))
-
- for (let i = 0; i < points.length; i++) {
- const prev = points[(i - 1 + points.length) % points.length]
- const current = points[i]
- const next = points[(i + 1) % points.length]
-
- // 두 벡터 계산 (prev -> current, current -> next)
- const vector1 = { x: current.x - prev.x, y: current.y - prev.y }
- const vector2 = { x: next.x - current.x, y: next.y - current.y }
-
- // 벡터의 길이 계산
- const length1 = Math.sqrt(vector1.x * vector1.x + vector1.y * vector1.y)
- const length2 = Math.sqrt(vector2.x * vector2.x + vector2.y * vector2.y)
-
- // 벡터를 단위 벡터로 정규화
- const unitVector1 = { x: vector1.x / length1, y: vector1.y / length1 }
- const unitVector2 = { x: vector2.x / length2, y: vector2.y / length2 }
-
- // 법선 벡터 계산 (왼쪽 방향)
- const normal1 = { x: -unitVector1.y, y: unitVector1.x }
- const normal2 = { x: -unitVector2.y, y: unitVector2.x }
-
- // 법선 벡터 평균 계산
- const averageNormal = {
- x: (normal1.x + normal2.x) / 2,
- y: (normal1.y + normal2.y) / 2,
- }
-
- // 평균 법선 벡터를 단위 벡터로 정규화
- const lengthNormal = Math.sqrt(averageNormal.x * averageNormal.x + averageNormal.y * averageNormal.y)
- const unitNormal = {
- x: averageNormal.x / lengthNormal,
- y: averageNormal.y / lengthNormal,
- }
-
- // 오프셋 적용
- const offsetPoint = {
- x1: current.x + unitNormal.x * offset,
- y1: current.y + unitNormal.y * offset,
- }
-
- offsetPoints.push(offsetPoint)
- }
-
- const roof = makePolygon(offsetPoints)
+ const roof = makePolygon(
+ offsetPoints.map((point) => {
+ return { x1: point.x, y1: point.y }
+ }),
+ )
roof.setWall(polygon)
setRoof(roof)
@@ -1233,15 +1161,15 @@ export function useMode() {
setTemplateType(2)
}
- const handleOuterLineTemplateA4Points = (polygon) => {
- const edge = 20
- const eaves = 50
+ const handleOuterLineTemplateA4Points = (polygon, offsetInputX = 20, offsetInputY = 50) => {
+ const edge = offsetInputX
+ const eaves = offsetInputY
// 폴리곤의 각 변을 선으로 생성
const createLine = (start, end, stroke, property) =>
new QLine([start.x, start.y, end.x, end.y], {
stroke,
- strokeWidth: 5,
+ strokeWidth: 1,
property,
fontSize: 14,
})
@@ -1279,7 +1207,7 @@ export function useMode() {
strokeDashArray: dashArray,
})
- const vertCenterLine = createCenterLine(centerPointX, lines[0].y1 - edge, centerPointX, lines[0].y2 + edge, 'blue', 4, 'center')
+ const vertCenterLine = createCenterLine(centerPointX, lines[0].y1 - edge, centerPointX, lines[0].y2 + edge, 'blue', 1, 'center')
canvas.add(vertCenterLine)
const horiCenterLineLeft = createCenterLine(
@@ -1341,6 +1269,31 @@ export function useMode() {
const outLine = createLine({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 }, 'blue', 'normal')
canvas.add(outLine)
})
+
+ const roofPatternPolygonArray = []
+
+ const leftLine = drawArray[0]
+ const rightLine = drawArray[3]
+
+ //사각형 왼쪽 지붕 패턴 생성 배열
+ const leftPolygon = [
+ { x: leftLine.x1, y: leftLine.y1 },
+ { x: leftLine.x2, y: leftLine.y2 },
+ { x: vertCenterLine.x1, y: vertCenterLine.y1 },
+ { x: vertCenterLine.x2, y: vertCenterLine.y2 },
+ ]
+ roofPatternPolygonArray.push(leftPolygon)
+
+ //사각형 오른쪽 지붕 패턴 생성 배열
+ const rightPolygon = [
+ { x: vertCenterLine.x1, y: vertCenterLine.y1 },
+ { x: vertCenterLine.x2, y: vertCenterLine.y2 },
+ { x: rightLine.x1, y: rightLine.y1 },
+ { x: rightLine.x2, y: rightLine.y2 },
+ ]
+ roofPatternPolygonArray.push(rightPolygon)
+
+ setRoofPolygonPattern({ roofPatternPolygonArray, lines }) //모든 행을 저장
}
//탬플릿A 적용
@@ -2379,8 +2332,6 @@ export function useMode() {
let tmpArray = []
let tmpBigArray = []
- console.log('tmpVertCenterLine', tmpVertCenterLine)
-
const lastCenterLine = tmpVertCenterLine[tmpVertCenterLine.length - 1] //마지막 센터라인을 정의
for (let i = 0; i < tmpVertCenterLine.length - 1; i++) {
@@ -2455,8 +2406,6 @@ export function useMode() {
}
}
- console.log('roofPatternPolygonArray', roofPatternPolygonArray)
-
setRoofPolygonPattern({ roofPatternPolygonArray, lines })
} else {
// 오목한 부분이 세로선일때 아래ㄷ, 위ㄷ
@@ -3179,23 +3128,7 @@ export function useMode() {
canvas?.renderAll()
}
- /**
- * 지붕 패턴 생성 로직
- * @param roofStyle
- */
- const makeRoofPatternPolygon = (roofStyle) => {
- if (Object.keys(roofPolygonPattern).length === 0 && roofPolygonPattern.constructor === Object) {
- alert('객체가 비어있습니다.')
- return
- }
-
- //내부 선 점선으로 변경 추후에 다시 되돌리는 로직 필요
- roofPolygonPattern.lines.forEach((line, index) => {
- line.line.set('strokeDashArray', [10, 5, 2, 5])
- line.line.set('stroke', 'blue')
- line.line.set('strokeWidth', 1)
- })
-
+ const getRoofPattern = (roofStyle, mode = 'normal') => {
const ratio = window.devicePixelRatio || 1
const inputPatternSize = { width: 30, height: 20 } //임시 사이즈
@@ -3214,6 +3147,12 @@ export function useMode() {
// 벽돌 패턴 그리기
ctx.scale(ratio, ratio)
+
+ if (mode === 'cell') {
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'
+ ctx.fillRect(0, 0, patternSize.width * 2, patternSize.height * 2)
+ }
+
ctx.strokeStyle = 'green'
ctx.lineWidth = 0.4
@@ -3241,6 +3180,30 @@ export function useMode() {
repeat: 'repeat',
})
+ return pattern
+ }
+
+ /**
+ * 지붕 패턴 생성 로직
+ * @param roofStyle
+ */
+ const makeRoofPatternPolygon = (roofStyle) => {
+ if (Object.keys(roofPolygonPattern).length === 0 && roofPolygonPattern.constructor === Object) {
+ alert('객체가 비어있습니다.')
+ return
+ }
+
+ setRoofStyle(roofStyle) //클릭한 지붕패턴을 저장
+
+ //내부 선 점선으로 변경 추후에 다시 되돌리는 로직 필요
+ roofPolygonPattern.lines.forEach((line, index) => {
+ line.line.set('strokeDashArray', [10, 5, 2, 5])
+ line.line.set('stroke', 'blue')
+ line.line.set('strokeWidth', 1)
+ })
+
+ const pattern = getRoofPattern(roofStyle)
+
const commonOption = {
fill: pattern,
selectable: false,
@@ -3316,14 +3279,16 @@ export function useMode() {
canvas.discardActiveObject() // 객체의 활성 상태 해제
//폴리곤에 커스텀 인덱스를 가지고 해당 배열 인덱스를 찾아 삭제함
- const removeIndex = polygon.customIndex
- const removeArrayIndex = selectedAreaArray.findIndex((x) => x.customIndex === removeIndex)
+ const removeIndex = polygon.idx
+ const removeArrayIndex = selectedAreaArray.findIndex((x) => x.idx === removeIndex)
selectedAreaArray.splice(removeArrayIndex, 1)
}
canvas?.renderAll()
}
- //외각선을 안쪽으로 그려 가대선을 그린다.
+ const pattern = getRoofPattern(roofStyle, 'cell')
+
+ // 외각선을 안쪽으로 그려 가대선을 그린다.
polygons.forEach((polygon, index) => {
const trestlePolygon = handleOuterlinesTest(polygon, -12)
trestlePolygon.setViewLengthText(false) //얘는 set으로 안먹는다...
@@ -3337,7 +3302,8 @@ export function useMode() {
lockScalingX: true,
lockScalingY: true,
bringToFront: true,
- customIndex: polygon.customIndex, //가대 폴리곤의 임시 인덱스를 넣어줌
+ idx: polygon.customIndex, //가대 폴리곤의 임시 인덱스를 넣어줌
+ name: 'trestlePolygon',
})
/**
@@ -3346,8 +3312,15 @@ export function useMode() {
trestlePolygon.on('mousedown', function () {
toggleSelection(trestlePolygon)
})
+
+ console.log('polygon', polygon)
+
+ polygon.set({ fill: pattern })
})
+
setSelectedCellRoofArray(selectedAreaArray)
+ canvas?.renderAll()
+ setMode(Mode.DEFAULT) //default 모드로 변경
}
/**
@@ -3392,6 +3365,51 @@ export function useMode() {
setMode(Mode.DEFAULT) //default 모드로 변경
}
+ const createRoofRack = () => {
+ const roofs = canvas?.getObjects().filter((obj) => obj.name === 'roof')
+ let roofCells = [] // roof에 적재된 cell들
+ roofs.forEach((roof, index) => {
+ let maxLengthLine = roof.lines.reduce((acc, cur) => {
+ return acc.length > cur.length ? acc : cur
+ })
+
+ const offsetPolygonPoint = offsetPolygon(roof.points, -20, 0)
+
+ const trestlePoly = new QPolygon(offsetPolygonPoint, {
+ fill: 'transparent',
+ stroke: 'red',
+ strokeDashArray: [5, 5],
+ strokeWidth: 1,
+ selectable: true,
+ fontSize: fontSize,
+ name: 'trestle',
+ lockMovementX: true, // X 축 이동 잠금
+ lockMovementY: true, // Y 축 이동 잠금
+ lockRotation: true, // 회전 잠금
+ lockScalingX: true, // X 축 크기 조정 잠금
+ lockScalingY: true, // Y 축 크기 조정 잠금
+ idx: index,
+ })
+
+ canvas?.add(trestlePoly)
+
+ let drawRoofCells
+ if (maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left') {
+ drawRoofCells = trestlePoly.fillCell({ width: 100, height: 50, padding: 10 })
+ trestlePoly.direction = 'south'
+ } else {
+ drawRoofCells = trestlePoly.fillCell({ width: 50, height: 100, padding: 10 })
+ trestlePoly.direction = 'east'
+ }
+
+ drawRoofCells.forEach((cell) => {
+ roofCells.push(cell)
+ })
+ })
+
+ setDrewRoofCells(roofCells)
+ }
+
return {
mode,
setMode,
@@ -3406,5 +3424,6 @@ export function useMode() {
handleOuterlinesTest2,
makeRoofPatternPolygon,
makeRoofTrestle,
+ createRoofRack,
}
}
diff --git a/src/styles/_test.scss b/src/styles/_test.scss
new file mode 100644
index 00000000..babec92c
--- /dev/null
+++ b/src/styles/_test.scss
@@ -0,0 +1,3 @@
+.test {
+ background-color: #121212;
+}
diff --git a/src/styles/style.scss b/src/styles/style.scss
new file mode 100644
index 00000000..e7f56590
--- /dev/null
+++ b/src/styles/style.scss
@@ -0,0 +1 @@
+@import '_test.scss';
\ No newline at end of file
diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js
index 36eadd4d..80693e04 100644
--- a/src/util/qpolygon-utils.js
+++ b/src/util/qpolygon-utils.js
@@ -3,6 +3,8 @@ import { QLine } from '@/components/fabric/QLine'
import { calculateIntersection, distanceBetweenPoints, findClosestPoint, getDirectionByPoint } from '@/util/canvas-util'
import { QPolygon } from '@/components/fabric/QPolygon'
+const TWO_PI = Math.PI * 2
+
export const defineQPloygon = () => {
fabric.QPolygon.fromObject = function (object, callback) {
fabric.Object._fromObject('QPolygon', object, callback, 'points')
@@ -251,6 +253,10 @@ export const drawHelpLineInHexagon = (polygon, chon) => {
name: 'hip',
})
+ if (line.length === 0) {
+ return
+ }
+
line.startPoint = ridgePoint
line.endPoint = filteredCenterInterSectionPoints
@@ -281,54 +287,74 @@ export const drawHelpLineInHexagon = (polygon, chon) => {
const startPoint = remainingPoints.shift()
const endPoint = remainingPoints.shift()
- const line = new QLine([startPoint.x, startPoint.y, endPoint.x, endPoint.y], {
- stroke: 'purple',
- fontSize: polygon.fontSize,
- name: 'connectRidge',
- })
+ if (!(startPoint.x === endPoint.x && startPoint.y === endPoint.y)) {
+ const line = new QLine([startPoint.x, startPoint.y, endPoint.x, endPoint.y], {
+ stroke: 'purple',
+ fontSize: polygon.fontSize,
+ name: 'connectRidge',
+ })
- line.startPoint = startPoint
- line.endPoint = endPoint
+ line.startPoint = startPoint
+ line.endPoint = endPoint
- polygon.connectRidges.push(line)
+ polygon.connectRidges.push(line)
- polygon.points.forEach((point) => {
- const degree = calculateAngle(startPoint, point)
+ polygon.points.forEach((point) => {
+ const degree = calculateAngle(startPoint, point)
- if (Math.abs(degree) === 45 || Math.abs(degree) === 135) {
- const line = new QLine([startPoint.x, startPoint.y, point.x, point.y], {
- stroke: 'purple',
- fontSize: polygon.fontSize,
- name: 'hip',
- })
+ if (Math.abs(degree) === 45 || Math.abs(degree) === 135) {
+ const line = new QLine([startPoint.x, startPoint.y, point.x, point.y], {
+ stroke: 'purple',
+ fontSize: polygon.fontSize,
+ name: 'hip',
+ })
- line.startPoint = startPoint
- line.endPoint = point
+ line.startPoint = startPoint
+ line.endPoint = point
- polygon.hips.push(line)
- polygon.canvas.add(line)
- }
- })
+ polygon.hips.push(line)
+ polygon.canvas.add(line)
+ }
+ })
- polygon.points.forEach((point) => {
- const degree = calculateAngle(endPoint, point)
+ polygon.points.forEach((point) => {
+ const degree = calculateAngle(endPoint, point)
- if (Math.abs(degree) === 45 || Math.abs(degree) === 135) {
- const line = new QLine([endPoint.x, endPoint.y, point.x, point.y], {
- stroke: 'purple',
- fontSize: polygon.fontSize,
- name: 'hip',
- })
+ if (Math.abs(degree) === 45 || Math.abs(degree) === 135) {
+ const line = new QLine([endPoint.x, endPoint.y, point.x, point.y], {
+ stroke: 'purple',
+ fontSize: polygon.fontSize,
+ name: 'hip',
+ })
- line.startPoint = endPoint
- line.endPoint = point
+ line.startPoint = endPoint
+ line.endPoint = point
- polygon.hips.push(line)
- polygon.canvas.add(line)
- }
- })
+ polygon.hips.push(line)
+ polygon.canvas.add(line)
+ }
+ })
- polygon.canvas.add(line)
+ polygon.canvas.add(line)
+ } else {
+ polygon.points.forEach((point) => {
+ const degree = calculateAngle(startPoint, point)
+
+ if (Math.abs(degree) === 45 || Math.abs(degree) === 135) {
+ const line = new QLine([startPoint.x, startPoint.y, point.x, point.y], {
+ stroke: 'purple',
+ fontSize: polygon.fontSize,
+ name: 'hip',
+ })
+
+ line.startPoint = startPoint
+ line.endPoint = point
+
+ polygon.hips.push(line)
+ polygon.canvas.add(line)
+ }
+ })
+ }
}
}
@@ -483,9 +509,8 @@ export const dividePolygon = (polygon) => {
}
})
hips = [...hips, ...connectRidges]
- polygon.setViewLengthText(false)
- polygonLines.forEach((line) => {
+ polygonLines.forEach((line, index) => {
let ridge
const startPoint = line.startPoint
@@ -520,15 +545,31 @@ export const dividePolygon = (polygon) => {
return
}
- let connectedRidge = ridges.find(
- (ridge) =>
- (ridge.startPoint.x === startHip.endPoint.x && ridge.startPoint.y === startHip.endPoint.y) ||
- (ridge.endPoint.x === startHip.endPoint.x && ridge.endPoint.y === startHip.endPoint.y),
- )
+ let connectedRidge
+ const restRidgeConnection = connectRidges[0]
+
+ if (!restRidgeConnection || restRidgeConnection.length === 0) {
+ connectedRidge = ridges.find(
+ (ridge) =>
+ (ridge.startPoint.x === startHip.endPoint.x &&
+ ridge.startPoint.y === startHip.endPoint.y &&
+ ridge.endPoint.x === endHip.endPoint.x &&
+ ridge.endPoint.y === endHip.endPoint.y) ||
+ (ridge.startPoint.x === endHip.endPoint.x &&
+ ridge.startPoint.y === endHip.endPoint.y &&
+ ridge.endPoint.x === startHip.endPoint.x &&
+ ridge.endPoint.y === startHip.endPoint.y),
+ )
+ } else {
+ connectedRidge = ridges.find(
+ (ridge) =>
+ (ridge.startPoint.x === startHip.endPoint.x && ridge.startPoint.y === startHip.endPoint.y) ||
+ (ridge.endPoint.x === startHip.endPoint.x && ridge.endPoint.y === startHip.endPoint.y),
+ )
+ }
const hipStartPoint = startHip.endPoint
const hipEndPoint = endHip.endPoint
- const restRidgeConnection = connectRidges[0]
if (connectedRidge.startPoint.x === hipStartPoint.x && connectedRidge.startPoint.y === hipStartPoint.y) {
if (connectedRidge.endPoint.x === hipEndPoint.x && connectedRidge.endPoint.y === hipEndPoint.y) {
@@ -676,3 +717,239 @@ const getOneSideLine = (line) => {
return newLine
}
+
+function inwardEdgeNormal(vertex1, vertex2) {
+ // Assuming that polygon vertices are in clockwise order
+ const dx = vertex2.x - vertex1.x
+ const dy = vertex2.y - vertex1.y
+ const edgeLength = Math.sqrt(dx * dx + dy * dy)
+
+ return {
+ x: -dy / edgeLength,
+ y: dx / edgeLength,
+ }
+}
+
+function outwardEdgeNormal(vertex1, vertex2) {
+ var n = inwardEdgeNormal(vertex1, vertex2)
+
+ return {
+ x: -n.x,
+ y: -n.y,
+ }
+}
+
+function createPolygon(vertices) {
+ const edges = []
+ let minX = vertices.length > 0 ? vertices[0].x : undefined
+ let minY = vertices.length > 0 ? vertices[0].y : undefined
+ let maxX = minX
+ let maxY = minY
+
+ for (let i = 0; i < vertices.length; i++) {
+ const vertex1 = vertices[i]
+ const vertex2 = vertices[(i + 1) % vertices.length]
+
+ const outwardNormal = outwardEdgeNormal(vertex1, vertex2)
+
+ const inwardNormal = inwardEdgeNormal(vertex1, vertex2)
+
+ const edge = {
+ vertex1,
+ vertex2,
+ index: i,
+ outwardNormal,
+ inwardNormal,
+ }
+
+ edges.push(edge)
+
+ const x = vertices[i].x
+ const y = vertices[i].y
+ minX = Math.min(x, minX)
+ minY = Math.min(y, minY)
+ maxX = Math.max(x, maxX)
+ maxY = Math.max(y, maxY)
+ }
+
+ return {
+ vertices,
+ edges,
+ minX,
+ minY,
+ maxX,
+ maxY,
+ }
+}
+
+// based on http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/, edgeA => "line a", edgeB => "line b"
+
+function edgesIntersection(edgeA, edgeB) {
+ const den =
+ (edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex2.x - edgeA.vertex1.x) -
+ (edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex2.y - edgeA.vertex1.y)
+
+ if (den == 0) {
+ return null // lines are parallel or coincident
+ }
+
+ const ua =
+ ((edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) -
+ (edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) /
+ den
+
+ const ub =
+ ((edgeA.vertex2.x - edgeA.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) -
+ (edgeA.vertex2.y - edgeA.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) /
+ den
+
+ // Edges are not intersecting but the lines defined by them are
+ const isIntersectionOutside = ua < 0 || ub < 0 || ua > 1 || ub > 1
+
+ return {
+ x: edgeA.vertex1.x + ua * (edgeA.vertex2.x - edgeA.vertex1.x),
+ y: edgeA.vertex1.y + ua * (edgeA.vertex2.y - edgeA.vertex1.y),
+ isIntersectionOutside,
+ }
+}
+
+function appendArc(arcSegments, vertices, center, radius, startVertex, endVertex, isPaddingBoundary) {
+ var startAngle = Math.atan2(startVertex.y - center.y, startVertex.x - center.x)
+ var endAngle = Math.atan2(endVertex.y - center.y, endVertex.x - center.x)
+
+ if (startAngle < 0) {
+ startAngle += TWO_PI
+ }
+
+ if (endAngle < 0) {
+ endAngle += TWO_PI
+ }
+
+ const angle = startAngle > endAngle ? startAngle - endAngle : startAngle + TWO_PI - endAngle
+ const angleStep = (isPaddingBoundary ? -angle : TWO_PI - angle) / arcSegments
+
+ vertices.push(startVertex)
+
+ for (let i = 1; i < arcSegments; ++i) {
+ const angle = startAngle + angleStep * i
+
+ const vertex = {
+ x: center.x + Math.cos(angle) * radius,
+ y: center.y + Math.sin(angle) * radius,
+ }
+
+ vertices.push(vertex)
+ }
+
+ vertices.push(endVertex)
+}
+
+function createOffsetEdge(edge, dx, dy) {
+ return {
+ vertex1: {
+ x: edge.vertex1.x + dx,
+ y: edge.vertex1.y + dy,
+ },
+ vertex2: {
+ x: edge.vertex2.x + dx,
+ y: edge.vertex2.y + dy,
+ },
+ }
+}
+
+function createMarginPolygon(polygon, offset, arcSegments = 0) {
+ const offsetEdges = []
+
+ for (let i = 0; i < polygon.edges.length; i++) {
+ const edge = polygon.edges[i]
+ const dx = edge.outwardNormal.x * offset
+ const dy = edge.outwardNormal.y * offset
+ offsetEdges.push(createOffsetEdge(edge, dx, dy))
+ }
+
+ const vertices = []
+
+ for (let i = 0; i < offsetEdges.length; i++) {
+ const thisEdge = offsetEdges[i]
+ const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]
+ const vertex = edgesIntersection(prevEdge, thisEdge)
+
+ if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) {
+ vertices.push({
+ x: vertex.x,
+ y: vertex.y,
+ })
+ } else {
+ const arcCenter = polygon.edges[i].vertex1
+
+ appendArc(arcSegments, vertices, arcCenter, offset, prevEdge.vertex2, thisEdge.vertex1, false)
+ }
+ }
+
+ const marginPolygon = createPolygon(vertices)
+
+ marginPolygon.offsetEdges = offsetEdges
+
+ return marginPolygon
+}
+
+function createPaddingPolygon(polygon, offset, arcSegments = 0) {
+ const offsetEdges = []
+
+ for (let i = 0; i < polygon.edges.length; i++) {
+ const edge = polygon.edges[i]
+ const dx = edge.inwardNormal.x * offset
+ const dy = edge.inwardNormal.y * offset
+ offsetEdges.push(createOffsetEdge(edge, dx, dy))
+ }
+
+ const vertices = []
+
+ for (let i = 0; i < offsetEdges.length; i++) {
+ const thisEdge = offsetEdges[i]
+ const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]
+ const vertex = edgesIntersection(prevEdge, thisEdge)
+ if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) {
+ vertices.push({
+ x: vertex.x,
+ y: vertex.y,
+ })
+ } else {
+ const arcCenter = polygon.edges[i].vertex1
+
+ appendArc(arcSegments, vertices, arcCenter, offset, prevEdge.vertex2, thisEdge.vertex1, true)
+ }
+ }
+
+ const paddingPolygon = createPolygon(vertices)
+
+ paddingPolygon.offsetEdges = offsetEdges
+
+ return paddingPolygon
+}
+
+export default function offsetPolygon(vertices, offset, arcSegments = 0) {
+ const polygon = createPolygon(vertices)
+
+ const originPolygon = new QPolygon(vertices, { fontSize: 0 })
+
+ if (offset > 0) {
+ let result = createMarginPolygon(polygon, offset, arcSegments).vertices
+ const allPointsOutside = result.every((point) => !originPolygon.inPolygon(point))
+
+ if (allPointsOutside) {
+ return createMarginPolygon(polygon, offset, arcSegments).vertices
+ } else {
+ return createPaddingPolygon(polygon, offset, arcSegments).vertices
+ }
+ } else {
+ let result = createPaddingPolygon(polygon, offset, arcSegments).vertices
+ const allPointsInside = result.every((point) => originPolygon.inPolygon(point))
+
+ if (allPointsInside) {
+ return createPaddingPolygon(polygon, offset, arcSegments).vertices
+ } else {
+ return createMarginPolygon(polygon, offset, arcSegments).vertices
+ }
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index 5eec9ca2..7c678f91 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2258,7 +2258,7 @@ canvas@^2.8.0:
nan "^2.17.0"
simple-get "^3.0.3"
-chokidar@^3.5.3:
+"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.3:
version "3.6.0"
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz"
integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
@@ -2714,6 +2714,11 @@ iconv-lite@0.6.3:
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
+immutable@^4.0.0:
+ version "4.3.7"
+ resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.7.tgz#c70145fc90d89fb02021e65c84eb0226e4e5a381"
+ integrity sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==
+
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@@ -3402,6 +3407,15 @@ safe-buffer@~5.2.0:
resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+sass@^1.77.8:
+ version "1.77.8"
+ resolved "https://registry.yarnpkg.com/sass/-/sass-1.77.8.tgz#9f18b449ea401759ef7ec1752a16373e296b52bd"
+ integrity sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==
+ dependencies:
+ chokidar ">=3.0.0 <4.0.0"
+ immutable "^4.0.0"
+ source-map-js ">=0.6.2 <2.0.0"
+
saxes@^5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz"
@@ -3486,7 +3500,7 @@ simple-swizzle@^0.2.2:
dependencies:
is-arrayish "^0.3.1"
-source-map-js@^1.0.2, source-map-js@^1.2.0:
+"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2, source-map-js@^1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz"
integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==