Skip to content

Commit

Permalink
feat: grid lines a39cfc6
Browse files Browse the repository at this point in the history
  • Loading branch information
plantain-00 committed Apr 21, 2024
1 parent a1dbfc5 commit df964b5
Show file tree
Hide file tree
Showing 20 changed files with 500 additions and 80 deletions.
100 changes: 64 additions & 36 deletions dev/cad-editor/cad-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type { LineContent } from './plugins/line-polyline.plugin'
import type { PluginContext } from './plugins/types'
import type { PointContent } from './plugins/point.plugin'
import type { EllipseContent } from './plugins/ellipse.plugin'
import type { RayContent } from './plugins/ray.plugin'

enablePatches()

Expand Down Expand Up @@ -346,13 +347,15 @@ export const CADEditor = React.forwardRef((props: {

// snap point
const { snapOffset, snapOffsetActive, snapOffsetInput, setSnapOffset, onSnapOffsetKeyDown } = useSnapOffset((operations.type === 'operate' && operations.operate.type === 'command') || (operations.type !== 'operate' && editPoint !== undefined))
const gridSize = 10
const { getSnapAssistentContents, getSnapPoint } = usePointSnap(
(operations.type === 'operate' && !getCommand(operations.operate.name)?.pointSnapDisabled) || editPoint !== undefined || acquirePointHandler.current !== undefined,
getIntersectionPoints,
snapTypes,
getContentModel,
snapOffset,
5 / scaleWithViewport,
p => ({ x: core.formatNumber(p.x, 1 / gridSize), y: core.formatNumber(p.y, 1 / gridSize) }),
)

const getContentsInRange = (region?: TwoPointsFormRegion): readonly Nullable<BaseContent>[] => {
Expand Down Expand Up @@ -528,14 +531,21 @@ export const CADEditor = React.forwardRef((props: {
})

debug.mark('before assistent contents')
const assistentContents: BaseContent[] = [
const assistentContents: BaseContent[] = []
const gridLinesCache = React.useRef(new core.ValueChangedCache<TwoPointsFormRegion, [Position, Position][]>())
if (snapTypes.includes('grid')) {
const lines = gridLinesCache.current.get(bounding, () => core.getGridLines(bounding, gridSize, gridSize, 200))
assistentContents.push(...lines.map(n => ({ type: 'line', points: n, strokeOpacity: 0.2 } as LineContent)))
}
assistentContents.push(
...getSnapAssistentContents(
(circle) => ({ type: 'circle', ...circle, strokeColor: 0x00ff00 } as CircleContent),
(rect) => ({ type: 'rect', ...rect, angle: 0, strokeColor: 0x00ff00 } as RectContent),
(points) => ({ type: 'polyline', points, strokeColor: 0x00ff00 } as LineContent),
(ray) => ({ type: 'ray', ...ray, strokeColor: 0x00ff00 } as RayContent)
),
...(commandAssistentContents || []),
]
)

const result = updateEditPreview()
previewPatches.push(...result?.patches ?? [])
Expand Down Expand Up @@ -902,44 +912,51 @@ export const CADEditor = React.forwardRef((props: {
setContextMenu(undefined)
}
})
try {
const text = await navigator.clipboard.readText()
if (text) {
let marker: BaseContent | undefined
const parts = text.split(',')
if (parts.length === 2) {
const nums = parts.map(p => +p)
if (nums.every(n => !isNaN(n))) {
marker = { type: 'point', x: nums[0], y: nums[1] } as PointContent
}
}
if (!marker) {
const content: unknown = JSON.parse(text)
if (core.is<BaseContent>(content, Content)) {
marker = content
} else if (core.is<Position>(content, Position)) {
marker = { ...content, type: 'point' } as PointContent
} else if (core.is<core.Circle>(content, core.Circle)) {
marker = { ...content, type: 'circle' } as CircleContent
} else if (core.is<core.Ellipse>(content, core.Ellipse)) {
marker = { ...content, type: 'ellipse' } as EllipseContent
}
if (markers.length > 0) {
items.push({
title: 'Clear markers',
onClick: () => {
setMarkers([])
setContextMenu(undefined)
}
if (marker) {
items.push({
title: `Mark ${marker.type}`,
onClick: () => {
if (marker) {
setMarkers([...markers, marker])
})
}
items.push({
title: 'Mark',
onClick: async () => {
try {
const text = await navigator.clipboard.readText()
if (text) {
let marker: BaseContent | undefined
const parts = text.split(',')
if (parts.length === 2) {
const nums = parts.map(p => +p)
if (nums.every(n => !isNaN(n))) {
marker = { type: 'point', x: nums[0], y: nums[1] } as PointContent
}
setContextMenu(undefined)
}
})
if (!marker) {
const content: unknown = JSON.parse(text)
if (core.is<BaseContent>(content, Content)) {
marker = content
} else if (core.is<Position>(content, Position)) {
marker = { ...content, type: 'point' } as PointContent
} else if (core.is<core.Circle>(content, core.Circle)) {
marker = { ...content, type: 'circle' } as CircleContent
} else if (core.is<core.Ellipse>(content, core.Ellipse)) {
marker = { ...content, type: 'ellipse' } as EllipseContent
}
}
if (marker) {
setMarkers([...markers, marker])
}
}
} catch (error) {
console.info(error)
}
setContextMenu(undefined)
}
} catch (error) {
console.info(error)
}
})
items.push({ type: 'divider' })
items.push({
title: 'Select All',
Expand Down Expand Up @@ -1010,7 +1027,7 @@ export const CADEditor = React.forwardRef((props: {
setContextMenu(undefined)
}
})
setContextMenu(<Menu items={items} style={{ left: viewportPosition.x + 'px', top: viewportPosition.y + 'px' }} />)
setContextMenu(<Menu items={items} style={{ left: viewportPosition.x + 'px', top: Math.min(viewportPosition.y, height - core.getMenuHeight(items, 16)) + 'px' }} />)
})

const rebuildRTree = (contents: readonly Nullable<BaseContent>[]) => {
Expand Down Expand Up @@ -1095,6 +1112,7 @@ export const CADEditor = React.forwardRef((props: {
const visiblePanel: JSX.Element[] = []
const readonlyPanel: JSX.Element[] = []
let areas = 0
let lengths = 0
selectedContents.forEach(target => {
types.add(target.content.type)
const id = target.path[0]
Expand Down Expand Up @@ -1144,13 +1162,23 @@ export const CADEditor = React.forwardRef((props: {
if (area) {
areas += area
}
const length = model.lengthCache.get(target.content, () => {
const lines = getContentModel(target.content)?.getGeometries?.(target.content, state)?.lines
return lines ? (core.getGeometryLinesLength(lines) ?? 0) : 0
})
if (length) {
lengths += length
}
})
propertyPanels.z = zPanel
propertyPanels.visible = visiblePanel
propertyPanels.readonly = readonlyPanel
if (areas) {
propertyPanels.areas = <NumberEditor value={areas} />
}
if (lengths) {
propertyPanels.lengths = <NumberEditor value={lengths} />
}
propertyPanels.debug = <core.Button onClick={() => console.info(selectedContents.map(s => [s.content, getContentModel(s.content)?.getGeometries?.(s.content, state)]))}>log to console</core.Button>
panel = (
<div style={{ position: 'absolute', right: '0px', top: '100px', bottom: '0px', width: '400px', overflowY: 'auto', background: 'white', zIndex: 11 }}>
Expand Down
1 change: 1 addition & 0 deletions dev/cad-editor/model.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ const geometriesCache = new WeakmapValuesCache<object, BaseContent, Geometries>(
const snapPointsCache = new WeakmapCache<object, SnapPoint[]>()
const editPointsCache = new WeakmapCache<object, { editPoints: (EditPoint<BaseContent> & { type?: 'move' })[], angleSnapStartPoint?: Position } | undefined>()
export const allContentsCache = new WeakmapCache<object, Nullable<BaseContent>[]>()
export const lengthCache = new WeakmapCache<BaseContent, number>()

export const getGeometriesFromCache = geometriesCache.get.bind(geometriesCache)
export const getSnapPointsFromCache = snapPointsCache.get.bind(snapPointsCache)
Expand Down
2 changes: 1 addition & 1 deletion dev/cad-editor/plugins/circle-arc.plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ export function getModel(ctx: PluginContext) {
render(content, renderCtx) {
const { options, dashed, target } = ctx.getStrokeFillRenderOptionsFromRenderContext(content, renderCtx)
if (dashed) {
return target.renderPolyline(getCircleGeometries(content, renderCtx.contents).points, options)
return target.renderPolyline(getArcGeometries(content, renderCtx.contents).points, options)
}
return target.renderArc(content.x, content.y, content.r, content.startAngle, content.endAngle, { ...options, counterclockwise: content.counterclockwise })
},
Expand Down
4 changes: 2 additions & 2 deletions dev/cad-editor/plugins/geometry-lines.plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ export function getModel(ctx: PluginContext): model.Model<GeometryLinesContent>
}
},
offset(content, point, distance, _, lineJoin) {
const newLines = ctx.trimGeometryLines(ctx.getParallelGeometryLinesByDistancePoint(point, content.lines, distance, lineJoin))
return { ...content, lines: newLines } as GeometryLinesContent
const newLines = ctx.trimGeometryLinesOffsetResult(ctx.getParallelGeometryLinesByDistancePoint(point, content.lines, distance, lineJoin), point)
return newLines.map(n => ctx.geometryLinesToPline(n))
},
join(content, target, contents) {
const line2 = ctx.getContentModel(target)?.getGeometries?.(target, contents)?.lines
Expand Down
2 changes: 1 addition & 1 deletion dev/cad-editor/plugins/nurbs.plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export function getModel(ctx: PluginContext): model.Model<NurbsContent>[] {
},
offset(content, point, distance, contents) {
const lines = getNurbsGeometries(content, contents).lines
return ctx.trimGeometryLines(ctx.getParallelGeometryLinesByDistancePoint(point, lines, distance)).map(r => ctx.geometryLineToContent(r))
return ctx.trimGeometryLinesOffsetResult(ctx.getParallelGeometryLinesByDistancePoint(point, lines, distance), point).map(r => r.map(n => ctx.geometryLineToContent(n))).flat()
},
render(content, renderCtx) {
const { points } = getNurbsGeometries(content, renderCtx.contents)
Expand Down
1 change: 1 addition & 0 deletions dev/cad-editor/plugins/offset.plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,5 +96,6 @@ export function getCommand(ctx: PluginContext): Command {
contentSelectable,
selectCount: 1,
icon,
pointSnapDisabled: true,
}
}
7 changes: 4 additions & 3 deletions dev/cad-editor/plugins/path.plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,11 @@ export function getModel(ctx: PluginContext): model.Model<PathContent> {
},
offset(content, point, distance, contents, lineJoin) {
const lines = getPathGeometriesFromCache(content, contents).lines
return {
const newLines = ctx.trimGeometryLinesOffsetResult(ctx.getParallelGeometryLinesByDistancePoint(point, lines, distance, lineJoin), point)
return newLines.map(n => ({
...content,
commands: ctx.geometryLineToPathCommands(ctx.trimGeometryLines(ctx.getParallelGeometryLinesByDistancePoint(point, lines, distance, lineJoin))),
}
commands: ctx.geometryLineToPathCommands(n),
}))
},
extend(content, point, contents) {
const lines = getPathGeometriesFromCache(content, contents).lines
Expand Down
4 changes: 2 additions & 2 deletions dev/cad-editor/plugins/pline.plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ export function getModel(ctx: PluginContext) {
},
offset(content, point, distance, contents, lineJoin) {
const { lines } = getPlineGeometries(content, contents)
const newLines = ctx.trimGeometryLines(ctx.getParallelGeometryLinesByDistancePoint(point, lines, distance, lineJoin))
return ctx.geometryLinesToPline(newLines)
const newLines = ctx.trimGeometryLinesOffsetResult(ctx.getParallelGeometryLinesByDistancePoint(point, lines, distance, lineJoin), point)
return newLines.map(n => ctx.geometryLinesToPline(n))
},
join(content, target, contents) {
const { lines } = getPlineGeometries(content, contents)
Expand Down
22 changes: 12 additions & 10 deletions dev/cad-editor/plugins/variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1529,7 +1529,7 @@ function getModel(ctx) {
render(content, renderCtx) {
const { options, dashed, target } = ctx.getStrokeFillRenderOptionsFromRenderContext(content, renderCtx);
if (dashed) {
return target.renderPolyline(getCircleGeometries(content, renderCtx.contents).points, options);
return target.renderPolyline(getArcGeometries(content, renderCtx.contents).points, options);
}
return target.renderArc(content.x, content.y, content.r, content.startAngle, content.endAngle, { ...options, counterclockwise: content.counterclockwise });
},
Expand Down Expand Up @@ -4751,8 +4751,8 @@ function getModel(ctx) {
}
},
offset(content, point, distance, _, lineJoin) {
const newLines = ctx.trimGeometryLines(ctx.getParallelGeometryLinesByDistancePoint(point, content.lines, distance, lineJoin));
return { ...content, lines: newLines };
const newLines = ctx.trimGeometryLinesOffsetResult(ctx.getParallelGeometryLinesByDistancePoint(point, content.lines, distance, lineJoin), point);
return newLines.map((n) => ctx.geometryLinesToPline(n));
},
join(content, target, contents) {
var _a, _b, _c;
Expand Down Expand Up @@ -7079,7 +7079,7 @@ function getModel(ctx) {
},
offset(content, point, distance, contents) {
const lines = getNurbsGeometries(content, contents).lines;
return ctx.trimGeometryLines(ctx.getParallelGeometryLinesByDistancePoint(point, lines, distance)).map((r) => ctx.geometryLineToContent(r));
return ctx.trimGeometryLinesOffsetResult(ctx.getParallelGeometryLinesByDistancePoint(point, lines, distance), point).map((r) => r.map((n) => ctx.geometryLineToContent(n))).flat();
},
render(content, renderCtx) {
const { points } = getNurbsGeometries(content, renderCtx.contents);
Expand Down Expand Up @@ -7335,7 +7335,8 @@ function getCommand(ctx) {
},
contentSelectable,
selectCount: 1,
icon
icon,
pointSnapDisabled: true
};
}
export {
Expand Down Expand Up @@ -7787,10 +7788,11 @@ function getModel(ctx) {
},
offset(content, point, distance, contents, lineJoin) {
const lines = getPathGeometriesFromCache(content, contents).lines;
return {
const newLines = ctx.trimGeometryLinesOffsetResult(ctx.getParallelGeometryLinesByDistancePoint(point, lines, distance, lineJoin), point);
return newLines.map((n) => ({
...content,
commands: ctx.geometryLineToPathCommands(ctx.trimGeometryLines(ctx.getParallelGeometryLinesByDistancePoint(point, lines, distance, lineJoin)))
};
commands: ctx.geometryLineToPathCommands(n)
}));
},
extend(content, point, contents) {
const lines = getPathGeometriesFromCache(content, contents).lines;
Expand Down Expand Up @@ -8442,8 +8444,8 @@ function getModel(ctx) {
},
offset(content, point, distance, contents, lineJoin) {
const { lines } = getPlineGeometries(content, contents);
const newLines = ctx.trimGeometryLines(ctx.getParallelGeometryLinesByDistancePoint(point, lines, distance, lineJoin));
return ctx.geometryLinesToPline(newLines);
const newLines = ctx.trimGeometryLinesOffsetResult(ctx.getParallelGeometryLinesByDistancePoint(point, lines, distance, lineJoin), point);
return newLines.map((n) => ctx.geometryLinesToPline(n));
},
join(content, target, contents) {
var _a, _b, _c;
Expand Down
25 changes: 16 additions & 9 deletions dev/point-snap.story.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React from "react"
import { allSnapTypes, Circle, circleToArc, iterateIntersectionPoints, Position, usePointSnap, WeakmapCache2 } from "../src"
import { allSnapTypes, Circle, circleToArc, formatNumber, getGridLines, iterateIntersectionPoints, Position, usePointSnap, useWindowSize, WeakmapCache2 } from "../src"

const intersectionPointsCache = new WeakmapCache2<Circle, Circle, Position[]>()
const contents: Circle[] = [{ x: 300, y: 200, r: 100 }, { x: 450, y: 350, r: 130 }]

export default () => {
const [position, setPosition] = React.useState<Position>()
const { width, height } = useWindowSize()
const startPosition = { x: 500, y: 100 }
const gridSize = 20
const { getSnapAssistentContents, getSnapPoint } = usePointSnap<Circle>(
true,
(c1, c2) => intersectionPointsCache.get(c1, c2, () => Array.from(iterateIntersectionPoints(c1, c2, contents, () => ({ getGeometries: (c) => ({ lines: [{ type: 'arc', curve: circleToArc(c) }] }) })))),
Expand All @@ -31,35 +33,40 @@ export default () => {
}
},
}),
undefined, undefined,
p => ({ x: formatNumber(p.x, 1 / gridSize), y: formatNumber(p.y, 1 / gridSize) }),
)
const assistentContents = getSnapAssistentContents(
(circle) => ({ type: 'circle' as const, ...circle }),
(rect) => ({ type: 'rect' as const, ...rect }),
(points) => ({ type: 'polyline' as const, points }),
(ray) => ({ type: 'ray' as const, ...ray }),
)
if (position) {
assistentContents.push({ type: 'polyline', points: [startPosition, position] })
}

const lines = getGridLines({ start: { x: 0, y: 0 }, end: { x: width / 2, y: height } }, gridSize)
return (
<>
<svg
viewBox="0 0 800 600"
width={800}
height={600}
viewBox={`0 0 ${width / 2} ${height}`}
width={width / 2}
height={height}
xmlns="http://www.w3.org/2000/svg"
fill='none'
style={{ position: 'absolute', left: 0, top: 0 }}
onMouseMove={(e) => setPosition(getSnapPoint({ x: e.clientX, y: e.clientY }, contents, undefined, startPosition).position)}
>
{contents.map((c, i) => <circle key={i} cx={c.x} cy={c.y} r={c.r} stroke='#00ff00' />)}
{contents.map((c, i) => <circle key={i} cx={c.x} cy={c.y} r={c.r} stroke='#000000' />)}
{lines.map((c, i) => <polyline key={i} points={c.map((p) => `${p.x},${p.y}`).join(' ')} stroke="black" strokeOpacity='0.2' />)}
{position && <polyline points={`${startPosition.x},${startPosition.y} ${position.x},${position.y}`} strokeDasharray={4} stroke='#ff0000' />}
{assistentContents.map((c, i) => {
if (c.type === 'rect') {
return <rect key={i} x={c.x - c.width / 2} y={c.y - c.height / 2} width={c.width} height={c.height} stroke='#00ff00' />
}
if (c.type === 'circle') {
return <circle key={i} cx={c.x} cy={c.y} r={c.r} stroke='#00ff00' />
}
if (c.type === 'ray') {
return null
}
return <polyline key={i} points={c.points.map((p) => `${p.x},${p.y}`).join(' ')} stroke='#00ff00' />
})}
{position && <circle cx={position.x} cy={position.y} r={5} stroke='#ff0000' />}
Expand Down
2 changes: 1 addition & 1 deletion main.bundle.js

Large diffs are not rendered by default.

Loading

0 comments on commit df964b5

Please sign in to comment.