Skip to content

Commit

Permalink
feat: trim hatch 2090593
Browse files Browse the repository at this point in the history
  • Loading branch information
plantain-00 committed Mar 24, 2024
1 parent aec6ad0 commit 484fd9c
Show file tree
Hide file tree
Showing 10 changed files with 286 additions and 86 deletions.
5 changes: 5 additions & 0 deletions dev/cad-editor/cad-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,11 @@ export const CADEditor = React.forwardRef((props: {
inputFixed,
operations.type === 'operate' && operations.operate.type === 'command' ? operations.operate.name : undefined,
selectedContents,
width,
height,
x,
y,
rotate,
scaleWithViewport,
strokeStyleId,
fillStyleId,
Expand Down
19 changes: 17 additions & 2 deletions dev/cad-editor/command.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ interface CommandProps {
type: string | undefined,
selected: { content: BaseContent, path: ContentPath }[],
setSelected: (...value: readonly Nullable<ContentPath>[]) => void
width: number,
height: number,
x: number,
y: number,
rotate: number,
scale: number,
strokeStyleId: number | undefined,
fillStyleId: number | undefined,
Expand Down Expand Up @@ -66,7 +71,7 @@ interface CommandResult {
}
assistentContents?: BaseContent[]
selected?: ContentPath[]
hovering?:ContentPath[]
hovering?: ContentPath[]
lastPosition?: Position
reset?(saveCurrent?: boolean): void
}
Expand All @@ -92,6 +97,11 @@ export function useCommands(
inputFixed: boolean | undefined,
operation: string | undefined,
selected: { content: BaseContent, path: ContentPath }[],
width: number,
height: number,
x: number,
y: number,
rotate: number,
scale: number,
strokeStyleId: number | undefined,
fillStyleId: number | undefined,
Expand All @@ -115,6 +125,11 @@ export function useCommands(
type,
selected,
setSelected,
width,
height,
x,
y,
rotate,
scale,
strokeStyleId,
fillStyleId,
Expand Down Expand Up @@ -157,7 +172,7 @@ export function useCommands(
}
}
const idMap: Record<number, number> = {}
for(let i = 0; i < selected.length;i++) {
for (let i = 0; i < selected.length; i++) {
idMap[selected[i].path[0]] = contents.length + i
}
newContents = newContents.map(c => {
Expand Down
3 changes: 3 additions & 0 deletions dev/cad-editor/model.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export interface FillFields {
strokeOpacity?: number
}
fillStyleId?: ContentRef
trueFillColor?: boolean
fillOpacity?: number
}

Expand All @@ -84,6 +85,7 @@ export const FillFields = {
strokeOpacity: optional(maximum(1, minimum(0, number))),
})),
fillStyleId: optional(or(number, Content)),
trueFillColor: optional(boolean),
fillOpacity: optional(maximum(1, minimum(0, number))),
}

Expand Down Expand Up @@ -684,6 +686,7 @@ export function getFillContentPropertyPanel(
<BooleanEditor value={content.fillColor !== undefined} setValue={(v) => update(c => { if (isFillContent(c)) { c.fillColor = v ? 0 : undefined } })} />,
content.fillColor !== undefined ? <NumberEditor type='color' value={content.fillColor} setValue={(v) => update(c => { if (isFillContent(c)) { c.fillColor = v } })} /> : undefined,
],
trueFillColor: <BooleanEditor value={content.trueFillColor !== undefined} setValue={(v) => update(c => { if (isFillContent(c)) { c.trueFillColor = v ? true : undefined } })} />,
fillPattern: [
<BooleanEditor value={content.fillPattern !== undefined} setValue={(v) => update(c => { if (isFillContent(c)) { c.fillPattern = v ? { width: 10, height: 10, lines: [[{ x: 0, y: 5 }, { x: 5, y: 0 }], [{ x: 10, y: 5 }, { x: 5, y: 10 }]] } : undefined } })} />,
content.fillPattern !== undefined
Expand Down
2 changes: 1 addition & 1 deletion dev/cad-editor/plugins/extend.plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export function getCommand(ctx: PluginContext): Command {
if (shift) {
points.push(...ctx.getTwoGeometryLinesIntersectionPoint(lines[i], line))
} else {
points.push(...ctx.getTwoGeometryLinesIntersectionPoint(lines[i], line, extend).filter(p => lines.every(n => !ctx.pointIsOnGeometryLine(p, n))))
points.push(...ctx.getTwoGeometryLinesIntersectionPoint(lines[i], line, extend).filter(p => !ctx.pointIsOnGeometryLines(p, lines)))
}
}
}
Expand Down
46 changes: 27 additions & 19 deletions dev/cad-editor/plugins/hatch.plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export type HatchContent = model.BaseContent<'hatch'> & model.FillFields & {
holes?: core.GeometryLine[][]
ref?: {
point: core.Position
end: core.Position
ids: model.ContentRef[]
}
}
Expand All @@ -19,7 +18,6 @@ export function getModel(ctx: PluginContext): model.Model<HatchContent> {
holes: ctx.optional([[ctx.GeometryLine]]),
ref: ctx.optional({
point: ctx.Position,
end: ctx.Position,
ids: [ctx.ContentRef],
}),
})
Expand All @@ -31,11 +29,11 @@ export function getModel(ctx: PluginContext): model.Model<HatchContent> {
let hatch = content
if (content.ref && content.ref.ids.length > 0) {
const refContents = content.ref.ids.map(id => ctx.getReference(id, contents)).filter((d): d is model.BaseContent => !!d && !ctx.shallowEquals(d, content))
if (refContents.length > 0) {
const bounding = ctx.mergeBoundings(refContents.map(ref => ctx.getContentModel(ref)?.getGeometries?.(ref, contents)?.bounding))
if (refContents.length > 0 && bounding) {
const p = content.ref.point
const end = content.ref.end
const getGeometriesInRange = () => refContents.map(c => ctx.getContentHatchGeometries(c, contents))
const border = ctx.getHatchByPosition(p, end, getGeometriesInRange)
const border = ctx.getHatchByPosition(p, { x: bounding.end.x, y: p.y }, getGeometriesInRange)
if (border) {
const holes = ctx.getHatchHoles(border.lines, getGeometriesInRange)
hatch = {
Expand Down Expand Up @@ -69,7 +67,6 @@ export function getModel(ctx: PluginContext): model.Model<HatchContent> {
move(content, offset) {
if (content.ref) {
ctx.movePoint(content.ref.point, offset)
ctx.movePoint(content.ref.end, offset)
}
for (const line of content.border) {
ctx.moveGeometryLine(line, offset)
Expand All @@ -85,7 +82,6 @@ export function getModel(ctx: PluginContext): model.Model<HatchContent> {
rotate(content, center, angle) {
if (content.ref) {
ctx.rotatePoint(content.ref.point, center, angle)
ctx.rotatePoint(content.ref.end, center, angle)
}
for (const line of content.border) {
ctx.rotateGeometryLine(line, center, angle)
Expand All @@ -101,7 +97,6 @@ export function getModel(ctx: PluginContext): model.Model<HatchContent> {
scale(content, center, sx, sy) {
if (content.ref) {
ctx.scalePoint(content.ref.point, center, sx, sy)
ctx.scalePoint(content.ref.end, center, sx, sy)
}
ctx.scaleGeometryLines(content.border, center, sx, sy)
if (content.holes) {
Expand All @@ -113,7 +108,6 @@ export function getModel(ctx: PluginContext): model.Model<HatchContent> {
skew(content, center, sx, sy) {
if (content.ref) {
ctx.skewPoint(content.ref.point, center, sx, sy)
ctx.skewPoint(content.ref.end, center, sx, sy)
}
ctx.skewGeometryLines(content.border, center, sx, sy)
if (content.holes) {
Expand All @@ -125,7 +119,6 @@ export function getModel(ctx: PluginContext): model.Model<HatchContent> {
mirror(content, line, angle) {
if (content.ref) {
ctx.mirrorPoint(content.ref.point, line)
ctx.mirrorPoint(content.ref.end, line)
}
for (const b of content.border) {
ctx.mirrorGeometryLine(b, line, angle)
Expand All @@ -144,6 +137,26 @@ export function getModel(ctx: PluginContext): model.Model<HatchContent> {
return target.renderPath([border, ...holes], options)
},
getGeometries: getHatchGeometries,
getEditPoints(content) {
return ctx.getEditPointsFromCache(content, () => {
const editPoints: core.EditPoint<model.BaseContent>[] = []
if (content.ref) {
editPoints.push({
x: content.ref.point.x,
y: content.ref.point.y,
cursor: 'move',
update(c, { cursor, start }) {
if (!isHatchContent(c) || !c.ref) {
return
}
c.ref.point.x += cursor.x - start.x
c.ref.point.y += cursor.y - start.y
},
})
}
return { editPoints }
})
},
propertyPanel(content, update, contents) {
return {
...ctx.getFillContentPropertyPanel(content, update, contents),
Expand Down Expand Up @@ -185,7 +198,7 @@ export function getCommand(ctx: PluginContext): Command[] {
{
name: 'create hatch',
icon,
useCommand({ onEnd, contents, getContentsInRange }) {
useCommand({ onEnd, contents, getContentsInRange, width, height, x, y, rotate, scale }) {
const [hatch, setHatch] = React.useState<HatchContent>()
const reset = () => {
setHatch(undefined)
Expand All @@ -201,14 +214,10 @@ export function getCommand(ctx: PluginContext): Command[] {
})
},
onMove(p) {
if (contents.length === 0) return
const bounding = ctx.contentsBoundingCache.get(contents, () => {
const points = ctx.getContentsPoints(contents, contents)
return ctx.getPointsBoundingUnsafe(points)
})
const lineSegment = ctx.getRayTransformedLineSegment({ x: p.x, y: p.y, angle: 0 }, width, height, { x, y, scale, rotate })
if (!lineSegment) return
const getGeometriesInRange = (region: core.TwoPointsFormRegion | undefined) => getContentsInRange(region).map(c => ctx.getContentHatchGeometries(c, contents))
const end = { x: bounding.end.x, y: p.y }
const border = ctx.getHatchByPosition(p, end, line => getGeometriesInRange(ctx.getGeometryLineBoundingFromCache(line)))
const border = ctx.getHatchByPosition(...lineSegment, line => getGeometriesInRange(ctx.getGeometryLineBoundingFromCache(line)))
if (border) {
const holes = ctx.getHatchHoles(border.lines, getGeometriesInRange)
setHatch({
Expand All @@ -217,7 +226,6 @@ export function getCommand(ctx: PluginContext): Command[] {
holes: holes?.holes,
ref: {
point: p,
end,
ids: [...border.ids, ...(holes?.ids || [])],
},
})
Expand Down
112 changes: 94 additions & 18 deletions dev/cad-editor/plugins/trim.plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type * as core from '../../../src'
import type { Command } from '../command'
import type * as model from '../model'
import type { LineContent } from './line-polyline.plugin'
import { HatchContent, isHatchContent } from './hatch.plugin'

export function getCommand(ctx: PluginContext): Command {
const React = ctx.React
Expand All @@ -13,7 +14,7 @@ export function getCommand(ctx: PluginContext): Command {
)
return {
name: 'trim',
useCommand({ onEnd, type, selected, backgroundColor, contents }) {
useCommand({ onEnd, type, selected, backgroundColor, contents, getContentsInRange }) {
const [candidates, setCandidates] = React.useState<{ content: model.BaseContent, children: model.BaseContent[] }[]>([])
const [currents, setCurrents] = React.useState<{ content: model.BaseContent, children: model.BaseContent[] }[]>([])
const [trackPoints, setTrackPoints] = React.useState<core.Position[]>([])
Expand Down Expand Up @@ -46,32 +47,35 @@ export function getCommand(ctx: PluginContext): Command {
}
}, [type])

const assistentContents: (model.BaseContent & model.StrokeFields)[] = []
const assistentContents: ((model.BaseContent & model.StrokeFields) | HatchContent)[] = []
const collectAssistentContent = (child: model.BaseContent) => {
if (ctx.isStrokeContent(child)) {
assistentContents.push({
...child,
strokeWidth: (child.strokeWidth ?? ctx.getDefaultStrokeWidth(child)) + 2,
strokeColor: backgroundColor,
trueStrokeColor: true,
})
} else if (isHatchContent(child)) {
assistentContents.push({
...child,
fillPattern: undefined,
fillColor: backgroundColor,
trueFillColor: true,
})
}
}
for (const current of currents) {
for (const child of current.children) {
if (ctx.isStrokeContent(child)) {
assistentContents.push({
...child,
strokeWidth: (child.strokeWidth ?? ctx.getDefaultStrokeWidth(child)) + 2,
strokeColor: backgroundColor,
trueStrokeColor: true,
})
}
collectAssistentContent(child)
}
}
if (trackPoints.length > 1) {
assistentContents.push({ points: trackPoints, type: 'polyline' } as LineContent)
}
for (const { children } of state) {
for (const child of children) {
if (ctx.isStrokeContent(child)) {
assistentContents.push({
...child,
strokeWidth: (child.strokeWidth ?? ctx.getDefaultStrokeWidth(child)) + 2,
strokeColor: backgroundColor,
trueStrokeColor: true,
})
}
collectAssistentContent(child)
}
}
const reset = () => {
Expand Down Expand Up @@ -135,6 +139,33 @@ export function getCommand(ctx: PluginContext): Command {
for (const child of candidate.children) {
const geometries = ctx.getContentModel(child)?.getGeometries?.(child, contents)
if (geometries) {
if (isHatchContent(child) && geometries.regions && geometries.bounding) {
for (const region of geometries.regions) {
if (region.holes && region.holes.some(h => ctx.pointInPolygon(p, h))) {
continue
}
if (ctx.pointInPolygon(p, region.points)) {
const getGeometriesInRange = (region: core.TwoPointsFormRegion | undefined) => getContentsInRange(region).map(c => ctx.getContentHatchGeometries(c, contents))
const border = ctx.getHatchByPosition(p, { x: geometries.bounding.end.x, y: p.y }, line => getGeometriesInRange(ctx.getGeometryLineBoundingFromCache(line)))
if (border) {
const holes = ctx.getHatchHoles(border.lines, getGeometriesInRange)
setCurrents([{
children: [{
type: 'hatch',
border: border.lines,
holes: holes?.holes,
ref: {
point: p,
ids: [...border.ids, ...(holes?.ids || [])],
},
} as HatchContent],
content: candidate.content
}])
}
return
}
}
}
for (const line of geometries.lines) {
if (ctx.getPointAndGeometryLineMinimumDistance(p, line) < 5) {
setCurrents([{ children: [child], content: candidate.content }])
Expand Down Expand Up @@ -182,6 +213,51 @@ export function getCommand(ctx: PluginContext): Command {
removedIndexes.push(ctx.getContentIndex(content, contents))
newContents.push(...r.filter(c => children.every(f => !ctx.deepEquals(f, c))))
}
} else if (isHatchContent(content)) {
const holes: core.GeometryLine[][] = []
const ids: model.ContentRef[] = []
if (content.ref) {
ids.push(...content.ref.ids)
}
const borders = [content.border]
if (content.holes) {
holes.push(...content.holes)
}
for (const child of children) {
if (isHatchContent(child)) {
holes.push(child.border)
if (child.holes) {
borders.push(...child.holes)
}
if (child.ref) {
ids.push(...child.ref.ids)
}
}
}
removedIndexes.push(ctx.getContentIndex(content, contents))
const result = borders.map(b => {
const polygon = ctx.getGeometryLinesPoints(b)
return ctx.optimizeHatch(b, holes.filter(h => {
const start = ctx.getGeometryLineStartAndEnd(h[0]).start
return start && (ctx.pointIsOnGeometryLines(start, b) || ctx.pointInPolygon(start, polygon))
}))
}).flat()
newContents.push(...result.map(r => {
let ref: { point: core.Position, ids: model.ContentRef[] } | undefined
if (content.ref) {
const p = content.ref.point
if (
ctx.pointInPolygon(p, ctx.getGeometryLinesPoints(r.border)) &&
r.holes.every(h => !ctx.pointInPolygon(p, ctx.getGeometryLinesPoints(h)))
) {
ref = {
point: p,
ids: Array.from(new Set(ids)),
}
}
}
return { ...content, border: r.border, holes: r.holes, ref }
}))
}
}
onEnd({
Expand Down
Loading

0 comments on commit 484fd9c

Please sign in to comment.