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(v: T, an: number, dis: number): T { v.x += Math.cos(an) * dis v.y += Math.sin(an) * dis return v }