feat:处理器初步实现---有接上了新优化,回显需要再看下

This commit is contained in:
2025-07-09 16:36:26 +08:00
parent 92b49c7035
commit 595675a08a
42 changed files with 70546 additions and 1993 deletions

View File

@@ -0,0 +1,255 @@
import type { PolylineProps } from 'cadapi'
import { Circle, Polyline, Polyline2Points } from 'cadapi'
import { EndType, JoinType } from 'js-angusj-clipper/web'
import type { Box3, Vector3 } from 'three'
import { Vector2 } from 'three'
import { clipperCpp } from '../ClipperCpp'
import type { Point } from '../Point'
import { Path, PathScale } from '../core/Path'
import type { IOffset } from './Simplify2'
import { SimplifyDouglasPeucker } from './Simplify2'
/** 内外接多边形 */
export function Circle2Points(circle: Circle, knifRadius: number, splitSize = 10, outside = false): Point[] {
let radius = circle.Radius
const an = Math.PI * 2 / splitSize
if (outside)
radius = radius / Math.cos(an / 2) + knifRadius
else
radius -= knifRadius
const cenP = circle.Center
const pts: Vector3[] = []
for (let i = 0; i < splitSize; i++)
pts.push(polar(cenP.clone(), an * i, radius))
return pts as Point[]
}
export function Curve2Path(curve: Polyline, outside = false): Path {
if (curve.IsClockWise)
curve.Reverse()
const w = new CurveWrap(curve, 3, outside)
return new Path(outside ? w.GetOutsidePoints() : w.GetInsidePoints())
}
export class CurveWrap {
BoundingBox: Box3
Area: number
SimplyPolyline: Polyline
SimplyOffset: IOffset
Used = false
Holes: CurveWrap[] = []
Points: Point[]
constructor(public Curve: Polyline | Circle, public KnifRadius: number, public IsOutside: boolean) {
this.BoundingBox = Curve.BoundingBox
if (Curve instanceof Polyline) {
const pts = Polyline2Points(Curve, IsOutside, 0)[1]
/**
* 精简算法SimplifyDouglasPeucker 会导致轮廓变大,
* 修改成直接取点 陈雄 QQ聊天记录 23.9.18
* 23.10.9 by lrx
*/
const [spts, offset] = SimplifyDouglasPeucker(pts, KnifRadius ** 2 + KnifRadius)
if (spts.length !== pts.length && spts.length > 2) {
this.SimplyOffset = offset
this.SimplyPolyline = Path2Polyline(spts)
this.Curve = this.SimplyPolyline// 保险起见,也更新它
this.Area = this.SimplyPolyline.Area
}
else// 此处更新多段线
{ this.Curve = Path2Polyline(pts) }
this.Points = spts
// 以下修改后的
// this.Curve = Path2Polyline(pts);
// this.Points = pts;
}
if (this.Area === undefined)
this.Area = this.Curve.Area
}
ContainsCurve(curve: CurveWrap): boolean {
if (this.SimplyPolyline)
return this.SimplyPolyline.PtInCurve(curve.Curve.StartPoint)
return this.Curve.PtInCurve(curve.Curve.StartPoint)
}
GetOutsidePoints(): Point[] {
if (this.Curve instanceof Circle) {
const pts = Circle2Points(this.Curve, this.KnifRadius, 10, true)
return pts
}
else {
const pl = this.SimplyPolyline || this.Curve
let offset = this.KnifRadius
if (this.SimplyOffset)
offset += this.SimplyOffset.positiveOffset
if (offset > 0) {
let pts = pl.GetStretchPoints() as Point[]
pts = clipperCpp.lib.offsetToPaths({
delta: offset * 1e4,
offsetInputs: [{ data: PathScale(pts, 1e4), joinType: JoinType.Miter, endType: EndType.ClosedPolygon }],
})[0]
try {
PathScale(pts, 1e-4)
}
catch {
console.log('err')
}
return pts
}
else { return this.Points }
}
}
GetInsidePoints(): Point[] {
return this.GetInsidePoints2(this.KnifRadius)
}
GetInsidePoints2(d: number): Point[] {
if (this.Curve instanceof Circle) {
const pts = Circle2Points(this.Curve, d, 10, false)
return pts
}
else {
const pl = this.SimplyPolyline || this.Curve
let offset = -d
if (this.SimplyOffset)
offset += this.SimplyOffset.negativeOffset
if (offset < -0.01) {
const pls = pl.GetOffsetCurves(offset)
if (pls.length)
return pls[0].GetStretchPoints()
}
else { return this.Points }
}
}
/** 引入Polyline 已经含刀半径, 获得精简后的点阵 */
GetOrgPoints(outside = true): Point[] {
if (this.Curve instanceof Circle) {
const pts = Circle2Points(this.Curve, 0, 10, outside)
return pts
}
else {
const pl = this.SimplyPolyline || this.Curve
let offset = 0
if (this.SimplyOffset)
offset += this.SimplyOffset.positiveOffset
if (offset > 0) {
let pts = pl.GetStretchPoints() as Point[]
pts = clipperCpp.lib.offsetToPaths({
delta: offset * 1e4,
offsetInputs: [{ data: PathScale(pts, 1e4), joinType: JoinType.Miter, endType: EndType.ClosedPolygon }],
})[0]
try {
PathScale(pts, 1e-4)
}
catch {
console.log('err')
}
return pts
}
else {
return this.Points
}
}
}
}
/** 多段线 转点整 已弃用整合到CAD api 23.11.2 */
// export function Polylin2Points(pl: Polyline, outside: boolean, knifRadius: number): [Polyline, Point[]]
// {
// let pts: Point[] = [];
// if (!outside) knifRadius = -knifRadius;
// if (pl.IsClockWise) pl.Reverse();
// for (let i = 0; i < pl.EndParam; i++)
// {
// pts.push(pl.GetPointAtParam(i));
// let bul = pl.GetBulgeAt(i);
// if (bul !== 0)
// {
// let arc = pl.GetCurveAtIndex(i) as Arc;
// let allAngle = arc.AllAngle;
// let arcLength = arc.Length;
// // let splitCount = Math.round(allAngle / 0.4);
// // if (arcLength < 300)
// // splitCount = 2;
// // else
// // splitCount = Math.max(arcLength / 200, splitCount,2);
// let minCount = Math.floor(allAngle * 4 / Math.PI);
// let splitCount = Math.round(allAngle / 0.4);
// if (arcLength < 300)
// splitCount = Math.max(2, minCount);
// else
// splitCount = Math.max(Math.floor(arcLength / 200), splitCount,2, minCount);
// let radius = arc.Radius;
// if (outside === bul > 0)
// radius = radius / Math.cos(allAngle / (splitCount * 2));
// let cp = arc.Center;
// for (let j = outside ? 0.5 : 0; j < splitCount; j++)
// {
// let a = arc.GetAngleAtParam(j * (1 / splitCount));
// let p = polar(cp.clone(), a, radius);
// pts.push(p);
// }
// }
// }
// if (knifRadius !== 0)
// {
// pts = clipperCpp.lib.offsetToPaths({
// delta: knifRadius * 1e4,
// offsetInputs: [{ data: PathScale(pts, 1e4), joinType: JoinType.Miter, endType: EndType.ClosedPolygon }]
// })[0];
// PathScale(pts, 1e-4);
// }
// return [pl, pts];
// }
export function Path2Polyline(path: Point[]): Polyline {
const pl = new Polyline()
pl.LineData = path.map((p) => {
return { pt: new Vector2(p.x, p.y), bul: 0 }
})
pl.CloseMark = true
return pl
}
export function Points2Polyline(pts: any[]): Polyline {
const lined: PolylineProps[] = []
const count = pts.length
for (let i = 0; i < count; i++) {
const p0 = pts[i]
lined.push({ pt: new Vector2(p0.x, p0.y), bul: p0.bul })
}
const pls = new Polyline(lined)
pls.CloseMark = true
return pls
}
export function polar<T extends Vector2 | Vector3>(v: T, an: number, dis: number): T {
v.x += Math.cos(an) * dis
v.y += Math.sin(an) * dis
return v
}

View File

@@ -0,0 +1,369 @@
import type { PolylineProps } from 'cadapi'
import { CADFiler, Circle, Polyline, Status, VKnifToolPath, isTargetCurInOrOnSourceCur } from 'cadapi'
import type { Box3 } from 'three'
import { Vector2, Vector3 } from 'three'
import { arrayRemoveDuplicateBySort } from '../ArrayExt'
import type { Curve2d } from '../base/CAD'
import { Arc2d, Point2d, copyTextToClipboard } from '../base/CAD'
import { CurveWrap } from './Curves2Parts'
// import type { Curve2d } from '../../common/base/CAD'
export class PolylineHelper {
/** 创建闭合多段线 */
static create(pts: any[], closeMark = false): Polyline {
let lined: PolylineProps[] = []
let count = pts.length
for (let i = 0; i < count; i++) {
let p0 = pts[i]
lined.push({ pt: new Vector2(p0.x, p0.y), bul: p0.bul || 0 })
}
let pls = new Polyline(lined)
pls.CloseMark = closeMark
return pls
}
static createByCurve2d(curs: Curve2d[], closeMark = true): Polyline {
let lined: PolylineProps[] = []
for (let cur of curs) {
let x = cur.StartPoint.m_X
let y = cur.StartPoint.m_Y
let bul = 0
if (cur instanceof Arc2d)
bul = cur.Bul || 0
lined.push({ pt: new Vector2(x, y), bul })
}
let pls = new Polyline(lined)
pls.CloseMark = true
return pls
}
static createByPts(pts: any[], buls: number[], closeMark = false): Polyline {
let plps: PolylineProps[] = []
let count = pts.length
for (let i = 0; i < count; i++) {
let p0 = pts[i]
plps.push({ pt: new Vector2(p0.x, p0.y), bul: buls[i] })
}
let pls = new Polyline(plps)
pls.CloseMark = closeMark
return pls
}
static getSimplePoints(pts: any[], offset: number): any[] {
let pl = PolylineHelper.create(pts)
pl.CloseMark = true
let cureW = new CurveWrap(pl, offset, true)
let pts2 = cureW.GetOutsidePoints()
arrayRemoveDuplicateBySort(pts2, (p1, p2) => (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y) < 1e-2)
return pts2
}
static createByWidthLength(w: number, l: number): Polyline {
let plps: PolylineProps[] = []
plps.push({ pt: new Vector2(0, 0), bul: 0 })
plps.push({ pt: new Vector2(w, 0), bul: 0 })
plps.push({ pt: new Vector2(w, l), bul: 0 })
plps.push({ pt: new Vector2(0, l), bul: 0 })
let pls = new Polyline(plps)
pls.CloseMark = true
return pls
}
/** 多段线,添加位置移动 返回新多段线 */
static moveTo(pl: Polyline, x: number, y: number): Polyline {
let lindData = pl.LineData
let pos = pl.Position
let newPts: PolylineProps[] = []
for (let p of lindData) {
let nx = p.pt.x + pos.x + x
let ny = p.pt.y + pos.y + y
if (ny < 7.9) {
// console.log('修边小于 7.9????', ny)
}
let bul = p.bul
newPts.push({ pt: new Vector2(nx, ny), bul })
}
let npl = new Polyline(newPts)
npl.CloseMark = pl.CloseMark
return npl
}
/** 重设 多段线的几点 */
static resetPosition(pl: Polyline): Polyline {
let lindData = pl.LineData
let pos = pl.Position
let newPts: PolylineProps[] = []
for (let p of lindData) {
let nx = p.pt.x + pos.x
let ny = p.pt.y + pos.y
let bul = p.bul
newPts.push({ pt: new Vector2(nx, ny), bul })
}
let npl = new Polyline(newPts)
npl.CloseMark = pl.CloseMark
return npl
}
/** 获得v型刀走刀路径 */o
static getVModelPoints(pl: Polyline, depth: number, ang: number): any[] {
// let ang = Math.PI * (0.5 * angle) / 180 ;
let ps = []
let bx = pl.Position.x
let by = pl.Position.y
if (ang > 0.01) {
let rt = VKnifToolPath(pl, depth, ang / 2)
ps = rt.map((t) => { return { x: t.pt.x + bx, y: t.pt.y + by, z: t.pt.z, bul: t.bul, r: 0 } })
}
else {
ps = pl.LineData.map((t) => { return { x: t.pt.x + bx, y: t.pt.y + by, z: 0, bul: t.bul, r: 0 } })
}
for (let i = 0; i < ps.length; i++) {
let p = ps[i]
if (p.bul == 0)
continue
let p2 = (i == ps.length - 1 ? ps[0] : ps[i + 1])
let r = this.getArcRadius(p.x, p.y, p2.x, p2.y, p.bul)
p.r = r
}
return ps
}
static ConverPolyLin2Circle(polyline: Polyline, fuzz = 0.1): Circle | undefined {
let box = polyline.BoundingBox
let size = box.getSize(new Vector3())
if (!this.equaln(size.x, size.y, fuzz))// 盒子四方
return undefined
let circleLength = 2 * Math.PI * size.x
if (!this.equaln(circleLength, polyline.Length, fuzz * 2))
return undefined
let circleArea = Math.PI * size.x * size.x
if (!this.equaln(circleArea, polyline.Area, fuzz * 2))
return undefined
let r = size.x// 必须备份(因为我们要复用这个vector变量)
return new Circle(box.getCenter(size), r)
}
// 有问题
static getVModelPoints_offset(pl: Polyline, offset: number, depth: number, angle: number) {
let npl = offset == 0 ? pl : pl.GetOffsetCurves(offset)[0]
// if(offset != 0)
// {
// ClipboardTest.write2PolyLine(pl,npl);
// }
return PolylineHelper.getVModelPoints(npl, depth, angle)
}
static getArcRadius(x1: number, y1: number, x2: number, y2: number, bul: number): number {
let bul2 = Math.abs(bul)
let d = Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2) / 2
return 0.5 * d * (1 + bul2 ** 2) / bul2
}
// 圆 转 多段线
static cicleToPolyline(c: Circle): Polyline {
let arcs = c.GetSplitCurves([0, 0.5])
let pl = Polyline.FastCombine(arcs)
return pl
}
/** 判断多段线是否 重叠 */
static isIntersect(pl1: Polyline, pl2: Polyline): boolean {
let box1 = this.getBox(pl1)
let box2 = this.getBox(pl2)
if (!box1.intersectsBox(box2))
return false // 肯定不相交
let ipts = pl1.IntersectWith(pl2, 0)
if (ipts.length === 0) {
if (pl1.Area > pl2.Area)// 缓存面积
{
if (isTargetCurInOrOnSourceCur(pl1, pl2))
return true // 包含
}
else {
if (isTargetCurInOrOnSourceCur(pl2, pl1))
return true // 包含
}
return false
}
else {
return true // 有交点 一定有交集
}
}
// 多段线 圆弧合并
static mergeCurve(pl2: Polyline): Polyline {
const curves = pl2.Explode()
arrayRemoveDuplicateBySort(curves, (c1, c2) => {
return c1.Join(c2) === Status.True
})
return Polyline.FastCombine(curves)
}
/**
* pl2 包含在pl1 内
*
*/
// static isInside(pl1:Polyline,pl2:Polyline):boolean
// {
// let box1 = this.getBox(pl1);
// let box2 = this.getBox(pl2);
// if (!box1.intersectsBox(box2)) return false; //肯定不相交
// let ipts = pl1.IntersectWith(pl2, 0);
// if (ipts.length > 0) return true; //有交点 一定有交集
// }
/**
* 两片板的干涉检查
*
* @param pl1
* @param pls1_inner
* @param pls1_model
* @param pl2
* @param pls2_inner
* @param pls2_model
* @returns
*/
static isOverLap(pl1: Polyline, pls1_inner: Polyline[], pls1_model: Polyline[], pl2: Polyline, pls2_inner: Polyline[], pls2_model: Polyline[]) {
// 是否干涉, 被包含在造型洞,不算干涉
let isOverlap = this.boxIsOverlap(pl1, pls1_inner, pl2, pls2_inner)
if (isOverlap)
return true
// 造型 2v 刀路 是否干涉
for (let pl1_model of pls1_model) {
if (pl1_model.IntersectWith(pl2, 0).length > 0)
return true
for (let pl2_inner of pls2_inner) {
if (pl1_model.IntersectWith(pl2_inner, 0).length > 0)
return true
}
}
for (let pl2_model of pls2_model) {
if (pl2_model.IntersectWith(pl1, 0).length > 0)
return true
for (let pl1_inner of pls1_inner) {
if (pl2_model.IntersectWith(pl1_inner, 0).length > 0)
return true
}
}
return false
}
private static boxIsOverlap(pl1: Polyline, pls1_inner: Polyline[], pl2: Polyline, pls2_inner: Polyline[]) {
let box1 = this.getBox(pl1)
let box2 = this.getBox(pl2)
if (!box1.intersectsBox(box2))
return false // 肯定不相交
let ipts = pl1.IntersectWith(pl2, 0)
if (ipts.length > 0)
return true // 有交点 一定有交集
if (pl1.Area > pl2.Area)// 缓存面积
{
if (isTargetCurInOrOnSourceCur(pl1, pl2)) // pl1包含 pl2
{
for (let mpl of pls1_inner) // 如果pl1有造型洞包含pl2 则表示不干涉返回false
{
if (isTargetCurInOrOnSourceCur(mpl, pl2) == true)
return false
}
return true
}
}
else {
if (isTargetCurInOrOnSourceCur(pl2, pl1)) // pl2包含 pl1
{
for (let mpl of pls2_inner) // 如果pl2有造型洞包含pl1 则表示不干涉返回false
{
if (isTargetCurInOrOnSourceCur(mpl, pl1) == true)
return false
}
return true
}
}
return false
}
/** 判断 点是否在多段线内 */
static isPointInPolyline(pl1: Polyline, x: number, y: number): boolean {
return pl1.PtInCurve(new Vector3(x, y, 0))
}
static getBox(pl1: Polyline): Box3 {
if (!pl1.box_tp)
pl1.box_tp = pl1.BoundingBox
return pl1.box_tp as Box3
}
static getArea(pl1: Polyline): number {
if (!pl1.area_tp)
pl1.area_tp = pl1.Area
return pl1.area_tp as number
}
static getPath(pl: Polyline): Path2D {
let path = new Path2D()
let p0 = pl.LineData[0].pt
path.moveTo(p0.x, p0.y)
for (let i = 0; i < pl.LineData.length; i++) {
let p0 = pl.LineData[i].pt
let p1 = (i == pl.LineData.length - 1) ? pl.LineData[0].pt : pl.LineData[i + 1].pt
let bul = pl.LineData[i].bul
if (bul == 0) {
path.lineTo(p1.x, p1.y)
}
else {
let arc = new Arc2d(new Point2d(p0.x, p0.y), new Point2d(p1.x, p1.y), bul)
path.arc(arc.m_Center.m_X, arc.m_Center.m_Y, arc.m_Radius, arc.m_StartAngle, arc.m_EndAngle, bul < 0)
}
}
path.closePath()
return path
}
static equaln(v1: number, v2: number, fuzz = 1e-5) {
return Math.abs(v1 - v2) <= fuzz
}
static toClipboard(en: Polyline | any) {
let f = new CADFiler()
f.Write(1)// 实体个数
f.WriteObject(en)
copyTextToClipboard(f.ToString())
}
static getStrPLs(ens: any[]) {
if (ens.length == 0)
return ''
let f = new CADFiler()
f.Write(ens.length)// 实体个数
for (let en of ens)
f.WriteObject(en)
return f.ToString()
}
}

View File

@@ -0,0 +1,85 @@
import { Vector2 } from "../Vector2"
interface P {
x: number
y: number
}
export interface IOffset {
negativeOffset: number
positiveOffset: number
}
/** 点p到线段P1P2 的最短距离的平方,线段不延伸 */
function GetSqSegDist(p: P, p1: P, p2: P): number {
let x = p1.x
let y = p1.y
let dx = p2.x - x
let dy = p2.y - y
if (dx !== 0 || dy !== 0)// 不是0长度线
{
const t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy)
if (t > 1) {
x = p2.x
y = p2.y
}
else if (t > 0) {
x += dx * t
y += dy * t
}
}
dx = p.x - x
dy = p.y - y
return dx * dx + dy * dy
}
function CrossVector2(a: P, b: P)
{
return a.x * b.y - a.y * b.x
}
// Ramer-Douglas-Peucker algorithm
function SimplifyDPStep(points: P[], first: number, last: number, sqTolerance: number, simplified: P[], offset: IOffset): void {
let maxSqDist = 0
let index: number
const fp = points[first]
const lp = points[last]
for (let i = first + 1; i < last; i++) {
const p = points[i]
const sqDist = GetSqSegDist(p, fp, lp)
if (sqDist > maxSqDist) {
index = i
maxSqDist = sqDist
}
}
if (maxSqDist > sqTolerance) {
if (index - first > 1)
SimplifyDPStep(points, first, index, sqTolerance, simplified, offset)
simplified.push(points[index])
if (last - index > 1)
SimplifyDPStep(points, index, last, sqTolerance, simplified, offset)
}
else {
// 记录偏移
const v = new Vector2(lp.x - fp.x, lp.y - fp.y).normalize()
for (let i = first + 1; i < last; i++) {
const p = points[i]
const offsetDist = -CrossVector2(v, { x: p.x - fp.x, y: p.y - fp.y })
offset.positiveOffset = Math.max(offset.positiveOffset, offsetDist)
offset.negativeOffset = Math.min(offset.negativeOffset, offsetDist)
}
}
}
// Ramer-Douglas-Peucker 算法
export function SimplifyDouglasPeucker(points: P[], sqTolerance: number): [P[], IOffset] {
const last = points.length - 1
const simplified: P[] = [points[0]]
const offset: IOffset = { negativeOffset: 0, positiveOffset: 0 }
SimplifyDPStep(points, 0, last, sqTolerance, simplified, offset)
simplified.push(points[last])
return [simplified, offset]
}