更新排料优化引擎2.0

yh2
ChenX 5 years ago
parent 932b36437f
commit bbda2d0a14

@ -16,7 +16,6 @@ const vendors = [
"xaop", "xaop",
"react-rnd2", "react-rnd2",
"dxf-parser", "dxf-parser",
"clipper-js-fpoint",
"pako", "pako",
"monotone-convex-hull-2d", "monotone-convex-hull-2d",
//如果你想调试threejs的代码,那么你应该注释掉下面的代码,然后重新构建dll //如果你想调试threejs的代码,那么你应该注释掉下面的代码,然后重新构建dll

@ -74,7 +74,6 @@
}, },
"dependencies": { "dependencies": {
"@blueprintjs/core": "^3.22.1", "@blueprintjs/core": "^3.22.1",
"clipper-js-fpoint": "^6.4.224",
"detect-browser": "^4.8.0", "detect-browser": "^4.8.0",
"dxf-parser": "^1.0.0-alpha.1", "dxf-parser": "^1.0.0-alpha.1",
"golden-layout": "^1.5.9", "golden-layout": "^1.5.9",

@ -142,12 +142,11 @@ import { ZoomE } from "../Add-on/ZoomE";
import { CommandServer } from '../DatabaseServices/CommandServer'; import { CommandServer } from '../DatabaseServices/CommandServer';
import { Command_TestNFP } from "../Nest/Test/TestNFP"; import { Command_TestNFP } from "../Nest/Test/TestNFP";
import { Command_TestPlace } from "../Nest/Test/TestPlace"; import { Command_TestPlace } from "../Nest/Test/TestPlace";
import { Command_Polyline2Path } from "../Nest/Test/TestPolyline2Path";
import { Command_TestSimply } from "../Nest/Test/TestSimply"; import { Command_TestSimply } from "../Nest/Test/TestSimply";
import { Command_TestYH } from "../Nest/Test/TestYH";
import { ICommand } from '../UI/Components/CommandPanel/CommandList'; import { ICommand } from '../UI/Components/CommandPanel/CommandList';
import { commandMachine } from './CommandMachine'; import { commandMachine } from './CommandMachine';
import { Command_ExportData } from "../Add-on/ExportData"; import { Command_ExportData } from "../Add-on/ExportData";
import { Command_TestYHWorker } from "../Nest/Test/TestYH3";
export function registerCommand() export function registerCommand()
{ {
@ -390,8 +389,7 @@ export function registerCommand()
commandMachine.RegisterCommand("testNFP", new Command_TestNFP()); commandMachine.RegisterCommand("testNFP", new Command_TestNFP());
commandMachine.RegisterCommand("testSimply", new Command_TestSimply()); commandMachine.RegisterCommand("testSimply", new Command_TestSimply());
commandMachine.RegisterCommand("testPlace", new Command_TestPlace()); commandMachine.RegisterCommand("testPlace", new Command_TestPlace());
commandMachine.RegisterCommand("testYH", new Command_TestYH()); commandMachine.RegisterCommand("testYH", new Command_TestYHWorker());
commandMachine.RegisterCommand("testPl2Pts", new Command_Polyline2Path());
commandMachine.RegisterCommand("editorlattice", new EditorLattice()); commandMachine.RegisterCommand("editorlattice", new EditorLattice());
commandMachine.RegisterCommand("print", new Print()); commandMachine.RegisterCommand("print", new Print());
commandMachine.RegisterCommand("extrude", new DrawExtrude()); commandMachine.RegisterCommand("extrude", new DrawExtrude());

@ -64,11 +64,16 @@ export function equalnn(dis = 5)
}; };
} }
interface P2
{
x: number; y: number;
}
export function equalv3(v1: Vector3, v2: Vector3, fuzz = 1e-8) export function equalv3(v1: Vector3, v2: Vector3, fuzz = 1e-8)
{ {
return equaln(v1.x, v2.x, fuzz) && equaln(v1.y, v2.y, fuzz) && equaln(v1.z, v2.z, fuzz); return equaln(v1.x, v2.x, fuzz) && equaln(v1.y, v2.y, fuzz) && equaln(v1.z, v2.z, fuzz);
} }
export function equalv2(v1: Vector2, v2: Vector2, fuzz = 1e-8) export function equalv2(v1: P2, v2: P2, fuzz = 1e-8)
{ {
return equaln(v1.x, v2.x, fuzz) && equaln(v1.y, v2.y, fuzz); return equaln(v1.x, v2.x, fuzz) && equaln(v1.y, v2.y, fuzz);
} }

@ -0,0 +1,154 @@
import { Vector2 } from "./Vector2";
import { Point } from "./Point";
export class Box2
{
min: Vector2;
max: Vector2;
constructor(min = new Vector2(+ Infinity, + Infinity), max = new Vector2(- Infinity, - Infinity))
{
this.min = min;
this.max = max;
}
set(min: Vector2, max: Vector2): Box2
{
this.min.copy(min);
this.max.copy(max);
return this;
}
setFromPoints(points: Vector2[]): Box2
{
this.makeEmpty();
for (let i = 0, il = points.length; i < il; i++)
{
this.expandByPoint(points[i]);
}
return this;
}
private static _setFromCenterAndSize_v1 = new Vector2();
setFromCenterAndSize(center: Vector2, size: Vector2): Box2
{
const v1 = Box2._setFromCenterAndSize_v1;
const halfSize = v1.copy(size).multiplyScalar(0.5);
this.min.copy(center).sub(halfSize);
this.max.copy(center).add(halfSize);
return this;
}
clone(): Box2
{
return new (this.constructor as any)().copy(this);
}
copy(box: Box2): Box2
{
this.min.copy(box.min);
this.max.copy(box.max);
return this;
}
makeEmpty(): Box2
{
this.min.x = this.min.y = + Infinity;
this.max.x = this.max.y = - Infinity;
return this;
}
isEmpty(): boolean
{
// this is a more robust check for empty than (volume <= 0) because volume can get positive with two negative axes
return (this.max.x < this.min.x) || (this.max.y < this.min.y);
}
getCenter(result: Vector2 = new Vector2()): Vector2
{
return this.isEmpty() ? result.set(0, 0) : result.addVectors(this.min, this.max).multiplyScalar(0.5);
}
getSize(result: Vector2 = new Vector2()): Vector2
{
return this.isEmpty() ? result.set(0, 0) : result.subVectors(this.max, this.min);
}
expandByPoint(point: Vector2): Box2
{
this.min.min(point);
this.max.max(point);
return this;
}
expandByVector(vector: Vector2): Box2
{
this.min.sub(vector);
this.max.add(vector);
return this;
}
expandByScalar(scalar: number): Box2
{
this.min.addScalar(- scalar);
this.max.addScalar(scalar);
return this;
}
containsPoint(point: Vector2): boolean
{
if (point.x < this.min.x || point.x > this.max.x ||
point.y < this.min.y || point.y > this.max.y)
{
return false;
}
return true;
}
containsBox(box: Box2): boolean
{
if ((this.min.x <= box.min.x) && (box.max.x <= this.max.x) &&
(this.min.y <= box.min.y) && (box.max.y <= this.max.y))
{
return true;
}
return false;
}
getParameter(point: Vector2, result: Vector2 = new Vector2()): Vector2
{
// This can potentially have a divide by zero if the box
// has a size dimension of 0.
return result.set(
(point.x - this.min.x) / (this.max.x - this.min.x),
(point.y - this.min.y) / (this.max.y - this.min.y)
);
}
intersectsBox(box: Box2): boolean
{
// using 6 splitting planes to rule out intersections.
if (box.max.x < this.min.x || box.min.x > this.max.x ||
box.max.y < this.min.y || box.min.y > this.max.y)
{
return false;
}
return true;
}
clampPoint(point: Vector2, result: Vector2 = new Vector2()): Vector2
{
return result.copy(point).clamp(this.min, this.max);
}
private static _distanceToPoint_v1 = new Vector2();
distanceToPoint(point: Vector2): number
{
const v1 = Box2._distanceToPoint_v1;
const clampedPoint = v1.copy(point).clamp(this.min, this.max);
return clampedPoint.sub(point).length();
}
intersect(box: Box2): Box2
{
this.min.max(box.min);
this.max.min(box.max);
return this;
}
union(box: Box2): Box2
{
this.min.min(box.min);
this.max.max(box.max);
return this;
}
translate(offset: Point): Box2
{
this.min.add(offset);
this.max.add(offset);
return this;
}
equals(box: Box2): boolean
{
return box.min.equals(this.min) && box.max.equals(this.max);
}
};

@ -0,0 +1,21 @@
import * as clipperLib from "js-angusj-clipper/web"; // nodejs style require
export let clipperCpp: { lib?: clipperLib.ClipperLibWrapper; } = {};
export function InitClipperCpp(): Promise<void>
{
if (clipperCpp.lib) return;
if (!globalThis.document)
globalThis.document = {} as any;
return new Promise((res, rej) =>
{
clipperLib.loadNativeClipperLibInstanceAsync(
// let it autodetect which one to use, but also available WasmOnly and AsmJsOnly
clipperLib.NativeClipperLibRequestedFormat.WasmWithAsmJsFallback
).then(c =>
{
clipperCpp.lib = c;
res();
console.log("载入成功!");
});
});
}

@ -1,58 +1,59 @@
import { Clipper, ClipType, Paths, PolyFillType, PolyType } from "clipper-js-fpoint"; import { ClipType, PolyFillType } from "js-angusj-clipper/web";
import { Box3, Matrix4, Vector3 } from "three"; import { SubjectInput } from "js-angusj-clipper/web/clipFunctions";
import { arrayRemoveIf } from "../Common/ArrayExt"; import { Box2 } from "./Box2";
import { AsVector3, equaln } from "../Geometry/GeUtils"; import { clipperCpp } from "./ClipperCpp";
import { ConvexHull2D } from "./ConvexHull2D"; import { ConvexHull2D } from "./ConvexHull2D";
import { CADFiler } from "./Filer";
import { NestCache } from "./NestCache"; import { NestCache } from "./NestCache";
import { Part } from "./Part"; import { Part } from "./Part";
import { Path } from "./Path"; import { Area, Path, TranslatePath } from "./Path";
import { PlaceType } from "./PlaceType"; import { PlaceType } from "./PlaceType";
import { Point } from "./Point"; import { Point } from "./Point";
import { equaln } from "./Util";
import { Vector2 } from "./Vector2";
export class Container export class Container
{ {
Holes: Container[] = []; ParentId: number = -1;//父亲id,-1表示默认的bin,-2开始表示余料,大于等于0表示板件
ChildrenIndex: number = 0;//网洞的索引位置
ParentM: Point;
Placed: Part[] = [];
PlaceType: PlaceType = PlaceType.Box;
//放置状态 //放置状态
PlacedArea = 0; PlacedArea = 0;
PlacedBox: Box3 = new Box3(); PlacedBox: Box2;
Placed: Part[] = [];
PlacedHull: Point[]; PlacedHull: Point[];
PlaceType: PlaceType = PlaceType.Box;
OCS: Matrix4 = new Matrix4();
StatusKey: string; StatusKey: string;
constructor(protected BinPath: Path) constructor(protected BinPath?: Path)
{ {
if (BinPath)
this.StatusKey = this.BinPath.Id.toString() + "," + this.PlaceType; this.StatusKey = this.BinPath.Id.toString() + "," + this.PlaceType;
} }
get PlacedAll() get PlacedAll()
{ {
let ps: Part[] = [...this.Placed]; return this.Placed;
for (let c of this.Holes)
{
ps.push(...c.PlacedAll);
}
return ps;
} }
get UseRatio() get UseRatio()
{ {
let size = this.PlacedBox.getSize(new Vector3); let size = this.PlacedBox.getSize(new Vector2);
return size.x * size.y / this.BinPath.Area; return size.x * size.y / this.BinPath.Area;
} }
notPuts: Set<number>[] = [];
PutPart(part: Part): boolean PutPart(part: Part): boolean
{ {
//------------先放置在孔洞内------------ //------------无法容纳------------
for (let c of this.Holes) if (this.BinPath.Area - this.PlacedArea < part.State.Contour.Area)
{ return false;
if (c.PutPart(part))
return true;
}
let cacheKey = this.StatusKey + "," + part.State.Contour.Id; let cacheKey = this.StatusKey + "," + part.State.Contour.Id;
let cacheP = NestCache.PositionCache.get(cacheKey); let cacheP = NestCache.PositionCache[cacheKey];
//读取缓存位置 //读取缓存位置
if (cacheP) if (cacheP)
{ {
@ -62,10 +63,6 @@ export class Container
return true; return true;
} }
//------------无法容纳------------
if (this.BinPath.Area - this.PlacedArea < part.State.Contour.Area)
return false;
let binNfp = this.BinPath.GetInsideNFP(part.State.Contour); let binNfp = this.BinPath.GetInsideNFP(part.State.Contour);
if (!binNfp || binNfp.length === 0) return false; if (!binNfp || binNfp.length === 0) return false;
@ -74,95 +71,86 @@ export class Container
{ {
part.PlacePosition = this.GetFarLeftP(binNfp); part.PlacePosition = this.GetFarLeftP(binNfp);
this.AppendPartHoles(part); this.AppendPartHoles(part);
NestCache.PositionCache[cacheKey] = part.PlacePosition;
return true; return true;
} }
//如果已经缓存过,证明已经无法放置(否则应该已经缓存了放置点) let noSet = NestCache.NoPutCache[this.StatusKey];
// if (NestCache.PartCaled.has(cacheKey)) return false; if (noSet) this.notPuts.push(noSet);
// NestCache.PartCaled.add(cacheKey); for (let set of this.notPuts)
//------------计算nfp------------
//合并(零件和所有已经放置零件的NFP)
let clipper = new Clipper();
for (let placedPart of this.Placed)
{ {
let nfp = placedPart.State.Contour.GetOutsideNFP(part.State.Contour); if (set.has(part.State.Contour.Id))
if (!nfp)
return false;
let x = placedPart.PlacePosition.x;
let y = placedPart.PlacePosition.y;
for (let n of nfp)
{ {
let f = n.map(v => { return { x: v.x + x, y: v.y + y }; }); NestCache.count2++;
clipper.AddPath(f, PolyType.ptSubject, true); return false;
} }
} }
let combinedNfp: Point[][] = []; let finalNfp = this.GetNFPs(part, binNfp);
if (!clipper.Execute(ClipType.ctUnion, combinedNfp, PolyFillType.pftNonZero, PolyFillType.pftNonZero)) if (!finalNfp || finalNfp.length === 0)
return false; {
if (noSet) noSet.add(part.State.Contour.Id);
combinedNfp = Clipper.CleanPolygons(combinedNfp, 0.1); else
combinedNfp = combinedNfp.filter(nfp => { return nfp.length > 2; }); {
noSet = new Set([part.State.Contour.Id]);
if (combinedNfp.length === 0) return false; NestCache.NoPutCache[this.StatusKey] = noSet;
}
//binNfp 减去 combinedNfp 得到最终的nfp
clipper = new Clipper();
clipper.AddPaths(binNfp, PolyType.ptSubject, true);
clipper.AddPaths(combinedNfp, PolyType.ptClip, true);
let finalNfp: Paths = [];
if (!clipper.Execute(ClipType.ctDifference, finalNfp, PolyFillType.pftNonZero, PolyFillType.pftNonZero))
return false; return false;
};
//清理NFP //------------选择合适的放置点------------
finalNfp = Clipper.CleanPolygons(finalNfp, 0.1);
arrayRemoveIf(finalNfp, nfp => nfp.length < 2);
if (finalNfp.length === 0)
return false;
let minArea: number = Infinity; let minArea: number = Infinity;
let translate: Point; let translate: Point;
let bestBox: Box2;
let bestHull: Point[];
let tempVec = new Vector3; let tempVec = new Vector2;
for (let nfp of finalNfp) for (let nfp of finalNfp)
{ {
for (let p of nfp) for (let p of nfp)
{ {
tempVec.set(p.x, p.y, 0); tempVec.set(p.x * 1e-4, p.y * 1e-4);
let box2 = part.State.Contour.BoundingBox.clone();
box2.translate(tempVec);
let rectBox = this.PlacedBox.clone().union(box2);
let size = rectBox.getSize(new Vector3());
switch (this.PlaceType) switch (this.PlaceType)
{ {
case PlaceType.Box: case PlaceType.Box:
{ {
let box2 = part.State.Contour.BoundingBox.clone();
box2.translate(tempVec);
let rectBox = this.PlacedBox.clone().union(box2);
let size = rectBox.getSize();
let area = size.x * size.y; let area = size.x * size.y;
if (area < minArea || if (area < minArea ||
((equaln(area, minArea, 1)) ((equaln(area, minArea, 1))
&& (this.BinPath.Size.x > this.BinPath.Size.y ? p.x < translate.x : p.y < translate.y))) && (p.x < translate.x || (p.x === translate.x && p.y < translate.y))))
// && (this.BinPath.Size.x > this.BinPath.Size.y ? p.x < translate.x : p.y < translate.y)))
{ {
translate = p; translate = p;
minArea = area; minArea = area;
bestBox = rectBox;
} }
break; break;
} }
case PlaceType.Hull: case PlaceType.Hull:
{ {
let pts = Clipper.TranslatePath(part.State.Contour.Points, p); let pts = TranslatePath(part.State.Contour.Points, tempVec);
let nhull = ConvexHull2D([...pts, ...this.PlacedHull]); let nhull = ConvexHull2D([...pts, ...this.PlacedHull]);
let area = Math.abs(Clipper.Area(nhull)); let area = Math.abs(Area(nhull));
if (area < minArea || if (area < minArea ||
((equaln(area, minArea, 1)) ((equaln(area, minArea, 1))
&& (this.BinPath.Size.x > this.BinPath.Size.y ? p.x < translate.x : p.y < translate.y))) && (p.x < translate.x || (p.x === translate.x && p.y < translate.y))))
// && (this.BinPath.Size.x > this.BinPath.Size.y ? p.x < translate.x : p.y < translate.y)))
{ {
translate = p; translate = p;
minArea = area; minArea = area;
bestHull = nhull;
} }
break; break;
} }
case PlaceType.Gravity:
{
if (!translate || p.x < translate.x || (p.x === translate.x && p.y < translate.y))
translate = p;
}
default: default:
break; break;
} }
@ -172,45 +160,78 @@ export class Container
if (translate) if (translate)
{ {
part.PlacePosition = translate; part.PlacePosition = translate;
this.AppendPartHoles(part); this.AppendPartHoles(part, false);
NestCache.PositionCache[cacheKey] = part.PlacePosition;
this.PlacedBox = bestBox ?? this.PlacedBox;
this.PlacedHull = bestHull;
return true; return true;
} }
else else
return false; return false;
} }
protected AppendPartHoles(part: Part) protected GetNFPs(part: Part, binNfp: Point[][])
{ {
this.Placed.push(part); //------------计算nfp------------
this.PlacedArea += part.State.Contour.Area; //合并(零件和所有已经放置零件的NFP)
this.PlacedBox.union(part.State.Contour.BoundingBox.clone().translate(AsVector3(part.PlacePosition))); let nfps: SubjectInput[] = [];
for (let placedPart of this.Placed)
{
let nfp = placedPart.State.Contour.GetOutsideNFP(part.State.Contour);
if (!nfp)
return;
for (let n of nfp)
{
let nnfp = TranslatePath(n, placedPart.PlacePosition);
nfps.push({ data: nnfp, closed: true });
}
}
//合并nfp
let combinedNfp = clipperCpp.lib.clipToPaths({
subjectInputs: nfps,
clipType: ClipType.Union,
subjectFillType: PolyFillType.NonZero
});
//位置缓存 if (combinedNfp.length === 0)
this.StatusKey += "," + part.State.Contour.Id; return;
NestCache.PositionCache.set(this.StatusKey, part.PlacePosition);
//减去nfp
let finalNfp = clipperCpp.lib.clipToPaths({
subjectInputs: [{ data: binNfp, closed: true }],
clipInputs: [{ data: combinedNfp }],
clipType: ClipType.Difference,
subjectFillType: PolyFillType.NonZero
});
return finalNfp;
}
protected AppendPartHoles(part: Part, calc = true)
{
part.Container = this.BinPath.Id;
this.StatusKey += "," + part.State.Contour.Id;
this.Placed.push(part);
this.PlacedArea += part.State.Contour.Area;
let m = { x: part.PlacePosition.x * 1e-4, y: part.PlacePosition.y * 1e-4 };
if (calc)
{
//凸包 //凸包
if (this.PlaceType === PlaceType.Hull) if (this.PlaceType === PlaceType.Hull)
{ {
if (!this.PlacedHull) if (!this.PlacedHull)
this.PlacedHull = part.State.Contour.Points; this.PlacedHull = part.State.Contour.Points;
else else
this.PlacedHull = ConvexHull2D([...this.PlacedHull, ...part.State.Contour.Points]); this.PlacedHull = ConvexHull2D([...this.PlacedHull, ...TranslatePath(part.State.Contour.Points, m)]);
} }
}
part.Container = this; if (calc || this.PlaceType !== PlaceType.Box)
let holes: Container[] = [];
for (let h of part.Holes)
{ {
let c = new Container(h.Contour); if (this.PlacedBox)
c.OCS = this.OCS.clone() this.PlacedBox.union(part.State.Contour.BoundingBox.clone().translate(m));
.multiply(part.PlaceCS) else
.multiply(new Matrix4().setPosition(h.OrigionMinPoint.x, h.OrigionMinPoint.y, 0)); this.PlacedBox = part.State.Contour.BoundingBox.clone();
holes.push(c);
} }
if (holes.length > 0)
this.Holes.push(...holes);
} }
/** /**
@ -232,4 +253,39 @@ export class Container
return leftP; return leftP;
} }
//#region -------------------------File-------------------------
//对象从文件中读取数据,初始化自身
ReadFile(file: CADFiler, parts: Part[])
{
this.ParentId = file.Read();
this.ChildrenIndex = file.Read();
this.ParentM = file.Read();
let count = file.Read() as number;
this.Placed = [];
for (let i = 0; i < count; i++)
{
let index = file.Read() as number;
let part = parts[index];
part.StateIndex = file.Read();
part.PlacePosition = file.Read();
this.Placed.push(part);
}
return this;
}
//对象将自身数据写入到文件.
WriteFile(file: CADFiler)
{
file.Write(this.ParentId);
file.Write(this.ChildrenIndex);
file.Write(this.ParentM);
file.Write(this.Placed.length);
for (let p of this.Placed)
{
file.Write(p.Id);
file.Write(p.StateIndex);
file.Write(p.PlacePosition);
}
}
//#endregion
} }

@ -1,17 +1,19 @@
import { EndType, JoinType } from "js-angusj-clipper/web";
import { Matrix4 } from "three";
import { TestDraw } from "../../Add-on/test/TestUtil"; import { TestDraw } from "../../Add-on/test/TestUtil";
import { arrayRemoveDuplicateBySort } from "../../Common/ArrayExt";
import { Arc } from "../../DatabaseServices/Entity/Arc"; import { Arc } from "../../DatabaseServices/Entity/Arc";
import { Board } from "../../DatabaseServices/Entity/Board"; import { Board } from "../../DatabaseServices/Entity/Board";
import { Circle } from "../../DatabaseServices/Entity/Circle"; import { Circle } from "../../DatabaseServices/Entity/Circle";
import { Polyline } from "../../DatabaseServices/Entity/Polyline"; import { Polyline } from "../../DatabaseServices/Entity/Polyline";
import { equaln, polar } from "../../Geometry/GeUtils"; import { equaln, equalv2, equalv3, polar, ZeroVec } from "../../Geometry/GeUtils";
import { clipperCpp } from "../ClipperCpp";
import { DefaultBin } from "../DefaultBin";
import { NestCache } from "../NestCache";
import { Part } from "../Part"; import { Part } from "../Part";
import { Path } from "../Path"; import { Path, PathScale } from "../Path";
import { Point } from "../Point"; import { Point } from "../Point";
import { Circle2Points } from "./Curves2Parts"; import { Circle2Points } from "./Curves2Parts";
import { NestCache } from "../NestCache";
export let DefaultBin = new Path([{ x: 0, y: 0 }, { x: 1220, y: 0 }, { x: 1220, y: 2440 }, { x: 0, y: 2440 }]);
DefaultBin.Id = -2;
let Rotations = [ let Rotations = [
[0, Math.PI], [0, Math.PI],
@ -19,7 +21,10 @@ let Rotations = [
[0, Math.PI, Math.PI / 2, Math.PI * 1.5], [0, Math.PI, Math.PI / 2, Math.PI * 1.5],
]; ];
function Curves2Points(cu: Circle | Polyline, outside: boolean, knifRadius: number): [(Circle | Polyline), Point[]] /**
* 线
*/
export function Curves2Points(cu: Circle | Polyline, outside: boolean, knifRadius: number): [(Circle | Polyline), Point[]]
{ {
if (cu instanceof Circle) if (cu instanceof Circle)
return [cu.Clone(), Circle2Points(cu, knifRadius, 8, outside)]; return [cu.Clone(), Circle2Points(cu, knifRadius, 8, outside)];
@ -32,11 +37,7 @@ export function Polylin2Points(pl: Polyline, outside: boolean, knifRadius: numbe
let pts: Point[] = []; let pts: Point[] = [];
if (!outside) knifRadius = -knifRadius; if (!outside) knifRadius = -knifRadius;
pl = pl.GetOffsetCurves(knifRadius * Math.sign(pl.Area2))[0] as Polyline;
if (pl.IsClockWise) pl.Reverse(); if (pl.IsClockWise) pl.Reverse();
for (let i = 0; i < pl.EndParam; i++) for (let i = 0; i < pl.EndParam; i++)
{ {
pts.push(pl.GetPointAtParam(i)); pts.push(pl.GetPointAtParam(i));
@ -69,20 +70,33 @@ export function Polylin2Points(pl: Polyline, outside: boolean, knifRadius: numbe
} }
} }
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]; return [pl, pts];
} }
export function ConverBoard2Part(board: Board, knifRadius = 3.5): Part
export function ConverBoard2Part(board: Board): Part
{ {
let part = new Part(); let part = new Part();
let path: Path; let path: Path;
part.UserData = [board.ContourCurve.Clone()];
let c = board.ContourCurve.Clone();
let box = c.BoundingBox;
if (!equalv3(box.min, ZeroVec, 1e-4))
c.ApplyMatrix(new Matrix4().setPosition(box.min.negate()));
part.UserData = [c];
if (!board.IsSpecialShape) if (!board.IsSpecialShape)
path = NestCache.CreatePath(board.Width, board.Height); path = NestCache.CreatePath(board.Width, board.Height, knifRadius);
else else
{ {
let [, pts] = Curves2Points(board.ContourCurve as Polyline, true, 3); let [, pts] = Curves2Points(board.ContourCurve as Polyline, true, knifRadius);
arrayRemoveDuplicateBySort(pts, (p1, p2) => equalv2(p1, p2, 1e-2));
path = new Path(pts); path = new Path(pts);
} }
part.Init2(path, DefaultBin, Rotations[board.BoardProcessOption.lines]); part.Init2(path, DefaultBin, Rotations[board.BoardProcessOption.lines]);
@ -90,7 +104,7 @@ export function ConverBoard2Part(board: Board): Part
{ {
if (equaln(m.thickness, board.Thickness)) if (equaln(m.thickness, board.Thickness))
{ {
let [, pts] = Curves2Points(m.shape.Outline.Curve, false, 3); let [, pts] = Curves2Points(m.shape.Outline.Curve, false, knifRadius);
part.UserData.push(m.shape.Outline.Curve.Clone()); part.UserData.push(m.shape.Outline.Curve.Clone());
part.AppendHole(new Path(pts)); part.AppendHole(new Path(pts));
} }

@ -1,11 +1,14 @@
import { EndType, JoinType } from "js-angusj-clipper/web";
import { Box3, Vector3 } from "three"; import { Box3, Vector3 } from "three";
import { Circle } from "../../DatabaseServices/Entity/Circle"; import { Circle } from "../../DatabaseServices/Entity/Circle";
import { Curve } from "../../DatabaseServices/Entity/Curve"; import { Curve } from "../../DatabaseServices/Entity/Curve";
import { Polyline } from "../../DatabaseServices/Entity/Polyline"; import { Polyline } from "../../DatabaseServices/Entity/Polyline";
import { polar } from "../../Geometry/GeUtils"; import { polar } from "../../Geometry/GeUtils";
import { clipperCpp } from "../ClipperCpp";
import { Part } from "../Part"; import { Part } from "../Part";
import { Path } from "../Path"; import { Path, PathScale } from "../Path";
import { Point } from "../Point"; import { Point } from "../Point";
import { Polylin2Points } from "./ConverBoard2Part";
import { Path2Polyline } from "./Path2Polyline"; import { Path2Polyline } from "./Path2Polyline";
import { IOffset, SimplifyDouglasPeucker } from "./Simplify2"; import { IOffset, SimplifyDouglasPeucker } from "./Simplify2";
@ -33,12 +36,12 @@ export function Curve2Path(curve: Circle | Polyline, outside = false): Path
curve.Reverse(); curve.Reverse();
if (curve instanceof Circle) if (curve instanceof Circle)
{ {
return new Path(Circle2Points(curve, 0, 10, outside), 0); return new Path(Circle2Points(curve, 0, 10, outside), 3);
} }
else else
{ {
let w = new CurveWrap(curve, 0); let w = new CurveWrap(curve, 3);
return new Path(outside ? w.GetOutsidePoints() : w.GetInsidePoints(), 0); return new Path(outside ? w.GetOutsidePoints() : w.GetInsidePoints());
} }
} }
@ -61,7 +64,7 @@ export class CurveWrap
if (Curve instanceof Polyline) if (Curve instanceof Polyline)
{ {
let pts = Curve.GetStretchPoints(); let pts = Polylin2Points(Curve, true, 0)[1];
let [spts, offset] = SimplifyDouglasPeucker(pts, KnifRadius ** 2 + KnifRadius); let [spts, offset] = SimplifyDouglasPeucker(pts, KnifRadius ** 2 + KnifRadius);
if (spts.length !== pts.length) if (spts.length !== pts.length)
{ {
@ -101,8 +104,13 @@ export class CurveWrap
} }
if (offset > 0) if (offset > 0)
{ {
let pls = pl.GetOffsetCurves(offset); let pts = pl.GetStretchPoints() as Point[];
return pls[0].GetStretchPoints(); pts = clipperCpp.lib.offsetToPaths({
delta: this.KnifRadius * 1e4,
offsetInputs: [{ data: PathScale(pts, 1e4), joinType: JoinType.Miter, endType: EndType.ClosedPolygon }]
})[0];
PathScale(pts, 1e-4);
return pts;
} }
else else
return this.Points; return this.Points;
@ -140,13 +148,14 @@ export class CurveWrap
* 线, * 线,
* 线,便 * 线,便
*/ */
export function Curves2Parts(curves: (Polyline | Circle)[], binPath: Path, KnifRadius = 3): Part[] export function Curves2Parts(curves: (Polyline | Circle)[], binPath: Path, KnifRadius = 3.5): Part[]
{ {
let warps: CurveWrap[] = []; let warps: CurveWrap[] = [];
for (let c of curves) for (let c of curves)
{ {
if (c.IsClockWise) c.Reverse(); if (c.IsClockWise) c.Reverse();
warps.push(new CurveWrap(c)); if (c.Length > 1)
warps.push(new CurveWrap(c, KnifRadius));
} }
warps.sort((c1, c2) => c2.Area - c1.Area); warps.sort((c1, c2) => c2.Area - c1.Area);

@ -0,0 +1,4 @@
import { Path } from "./Path";
let width = 1221;
let height = 2441;
export let DefaultBin = new Path([{ x: 0, y: 0 }, { x: width, y: 0 }, { x: width, y: height }, { x: 0, y: height }]);

@ -0,0 +1,30 @@
/**
* CAD
*/
export class CADFiler
{
private readIndex: number = 0;
constructor(public _datas: any[] = []) { }
Clear()
{
this._datas.length = 0;
return this.Reset();
}
Reset()
{
this.readIndex = 0;
return this;
}
Write(data: any)
{
this._datas.push(data);
return this;
}
Read(): any
{
return this._datas[this.readIndex++];
}
}

@ -4,6 +4,7 @@ import { Part } from "./Part";
import { Path } from "./Path"; import { Path } from "./Path";
import { RandomIndex } from "./Random"; import { RandomIndex } from "./Random";
import { PlaceType } from "./PlaceType"; import { PlaceType } from "./PlaceType";
import { CADFiler } from "./Filer";
/** /**
* *
@ -20,10 +21,8 @@ export class Individual
{ {
//强壮程度 越低越好 //强壮程度 越低越好
Fitness: number; Fitness: number;
constructor(public Parts?: Part[], public mutationRate = 0.5) constructor(public Parts?: Part[], public mutationRate = 0.5) { }
{
}
Clone() Clone()
{ {
let p = new Individual(this.Parts.map(p => p.Clone(), this.mutationRate)); let p = new Individual(this.Parts.map(p => p.Clone(), this.mutationRate));
@ -73,6 +72,7 @@ export class Individual
} }
Containers: Container[]; Containers: Container[];
HoleContainers: Container[];
/** /**
* *
*/ */
@ -80,6 +80,31 @@ export class Individual
{ {
let parts = this.Parts; let parts = this.Parts;
this.Containers = []; this.Containers = [];
this.HoleContainers = [];
for (let part of parts)
{
for (let i = 0; i < part.Holes.length; i++)
{
let hole = part.Holes[i];
let container = new Container(hole.Contour);
container.ParentId = part.Id;
container.ChildrenIndex = i;
container.ParentM = hole.OrigionMinPoint;
this.HoleContainers.push(container);
}
}
//网洞优先
parts = parts.filter(p =>
{
for (let hole of this.HoleContainers)
{
if (hole.PutPart(p))
return false;
}
return true;
});
while (parts.length > 0) while (parts.length > 0)
{ {
if (this.Containers.length > bestCount) if (this.Containers.length > bestCount)
@ -88,7 +113,9 @@ export class Individual
return; return;
}; };
let container = new Container(bin); let container = new Container(bin);
container.PlaceType = PlaceType.Box; container.ParentId = -1;
container.PlaceType = RandomIndex(3);
// container.PlaceType = PlaceType.Box;
if (this.mutationRate > 0.5) if (this.mutationRate > 0.5)
{ {
let area = 0; let area = 0;
@ -113,4 +140,35 @@ export class Individual
} }
this.Fitness = this.Containers.length - 1 + arrayLast(this.Containers).UseRatio; this.Fitness = this.Containers.length - 1 + arrayLast(this.Containers).UseRatio;
} }
//#region -------------------------File-------------------------
//对象从文件中读取数据,初始化自身
ReadFile(file: CADFiler)
{
this.Fitness = file.Read();
let count = file.Read() as number;
this.Containers = [];
for (let i = 0; i < count; i++)
this.Containers.push(new Container().ReadFile(file, this.Parts));
count = file.Read();
this.HoleContainers = [];
for (let i = 0; i < count; i++)
this.HoleContainers.push(new Container().ReadFile(file, this.Parts));
}
//对象将自身数据写入到文件.
WriteFile(f: CADFiler)
{
f.Write(this.Fitness);
f.Write(this.Containers.length);
for (let c of this.Containers)
c.WriteFile(f);
f.Write(this.HoleContainers.length);
for (let c of this.HoleContainers)
c.WriteFile(f);
}
//#endregion
} }

@ -4,22 +4,16 @@ import { Point } from "./Point";
export class NestCache export class NestCache
{ {
static count1 = 0; static count1 = 0;
static count2 = 0; static count2 = 0;//noset
static count3 = 0;
private static _pathId = -1; static PositionCache: { [key: string]: Point; } = {};
static get PathId() static NoPutCache: { [key: string]: Set<number>; } = {};
{
return this._pathId++;
}
static PartCaled = new Set<string>();
static PositionCache = new Map<string, Point>();
private static CacheRect = new Map<string, Path>(); private static CacheRect = new Map<string, Path>();
/** /**
* 0 * 0
*/ */
static CreatePath(x: number, y: number, knifRadius = 3): Path static CreatePath(x: number, y: number, knifRadius = 3.5): Path
{ {
let key = `${x.toFixed(2)},${y.toFixed(2)}`; let key = `${x.toFixed(2)},${y.toFixed(2)}`;
let path = this.CacheRect.get(key); let path = this.CacheRect.get(key);
@ -43,10 +37,7 @@ export class NestCache
{ {
this.count1 = 0; this.count1 = 0;
this.count2 = 0; this.count2 = 0;
this.count3 = 0;
this._pathId = -1;
this.CacheRect.clear(); this.CacheRect.clear();
this.PositionCache.clear(); this.PositionCache = {};
this.PartCaled.clear();
} }
} }

@ -1,6 +1,7 @@
import { CADFiler } from "../DatabaseServices/CADFiler";
import { Part } from "./Part"; import { Part } from "./Part";
import { Path } from "./Path"; import { Path } from "./Path";
import { CADFiler } from "./Filer";
import { PathGeneratorSingle } from "./PathGenerator";
export class NestDatabase export class NestDatabase
{ {
@ -23,6 +24,7 @@ export class NestDatabase
this.Paths.push(path); this.Paths.push(path);
} }
this.Bin = this.Paths[file.Read()]; this.Bin = this.Paths[file.Read()];
PathGeneratorSingle.paths = this.Paths;
count = file.Read(); count = file.Read();
this.Parts = []; this.Parts = [];
for (let i = 0; i < count; i++) for (let i = 0; i < count; i++)

@ -1,12 +1,11 @@
import { Vector3 } from "three";
import { arrayRemoveIf } from "../Common/ArrayExt"; import { arrayRemoveIf } from "../Common/ArrayExt";
import { Sleep } from "../Common/Sleep"; import { Sleep } from "../Common/Sleep";
import { MoveMatrix } from "../Geometry/GeUtils";
import { Individual } from "./Individual"; import { Individual } from "./Individual";
import { NestCache } from "./NestCache"; import { NestCache } from "./NestCache";
import { Part } from "./Part"; import { Part } from "./Part";
import { Path } from "./Path"; import { Path } from "./Path";
import { ShuffleArray } from "./Shuffle"; import { ShuffleArray } from "./Shuffle";
import { clipperCpp } from "./ClipperCpp";
/** /**
* *
@ -38,19 +37,8 @@ export class OptimizeMachine
//放入零件 //放入零件
PutParts(parts: Part[]) PutParts(parts: Part[])
{ {
let m = MoveMatrix(new Vector3(0, 3000, 0)); if (globalThis.document) parts = parts.slice();
arrayRemoveIf(parts, p => arrayRemoveIf(parts, p => p.RotatedStates.length === 0);
{
if (p.RotatedStates.length === 0)
{
for (let c of p.UserData)
{
c.ColorIndex = 1;
c.ApplyMatrix(m);
}
return true;
}
});
this.Parts = parts; this.Parts = parts;
} }
@ -88,19 +76,24 @@ export class OptimizeMachine
{ {
this.calcCount += this._Individuals.length; this.calcCount += this._Individuals.length;
let goBack = this.calcCount - this.bestCount > 8000; let goBack = this.calcCount - this.bestCount > 8000;
// console.time();
//1.适应环境(放置零件) //1.适应环境(放置零件)
for (let i = 0; i < this._Individuals.length; i++) for (let i = 0; i < this._Individuals.length; i++)
{ {
if (globalThis.document || !clipperCpp.lib)
await Sleep(0);
let p = this._Individuals[i]; let p = this._Individuals[i];
p.Evaluate(this.Bin, this.best); p.Evaluate(this.Bin, this.best);
if (!this.bestP)
{
this.bestP = p;
await this.callBack(p);
}
if (goBack) if (goBack)
p.mutationRate = 0.5; p.mutationRate = 0.5;
} }
// console.timeEnd();
await Sleep(0);
if (this.calcCount % 5000 === 0) await Sleep(0);
if (this.calcCount % 50 === 0)
console.timeLog("1", this.best, this.calcCount, NestCache.count1, NestCache.count2); console.timeLog("1", this.best, this.calcCount, NestCache.count1, NestCache.count2);
this._Individuals.sort((i1, i2) => i1.Fitness - i2.Fitness); this._Individuals.sort((i1, i2) => i1.Fitness - i2.Fitness);
@ -110,12 +103,10 @@ export class OptimizeMachine
{ {
this.best = bestP.Fitness; this.best = bestP.Fitness;
this.bestP = bestP; this.bestP = bestP;
console.timeLog("1", this.best, this.calcCount, NestCache.count1, NestCache.count2); // console.timeLog("1", this.best, this.calcCount, NestCache.count1, NestCache.count2);
if (this.callBack) if (this.callBack)
{
await this.callBack(bestP); await this.callBack(bestP);
} }
}
//自然选择 //自然选择
this._Individuals.splice(-10);//杀死它 this._Individuals.splice(-10);//杀死它
@ -137,7 +128,7 @@ export class OptimizeMachine
Suspend() Suspend()
{ {
console.timeEnd("1"); console.timeEnd("1");
console.log(this.best, this.calcCount, NestCache.count1, NestCache.count2); console.log(this.best, this.calcCount, NestCache.count1);
this._IsSuspend = true; this._IsSuspend = true;
console.log("暂停"); console.log("暂停");
} }

@ -1,6 +1,27 @@
const ctx: Worker = self as any; const ctx: Worker = self as any;
ctx.addEventListener("message", (event) => import { InitClipperCpp } from "./ClipperCpp";
import { CADFiler } from "./Filer";
import { NestDatabase } from "./NestDatabase";
import { OptimizeMachine } from "./OptimizeMachine";
ctx.addEventListener("message", async (event) =>
{ {
await InitClipperCpp();
let f = new CADFiler(event.data);
let db = new NestDatabase;
db.ReadFile(f);
let m = new OptimizeMachine;
m.Bin = db.Bin;
m.PutParts(db.Parts);
m.callBack = async (inv) =>
{
let f = new CADFiler();
inv.WriteFile(f);
ctx.postMessage(f._datas);
};
await m.Start();
}); });
export default {} as typeof Worker & (new () => Worker); export default {} as typeof Worker & (new () => Worker);

@ -1,13 +1,10 @@
import { Matrix4, Vector2 } from "three"; import { CADFiler } from "./Filer";
import { Curve } from "../DatabaseServices/Entity/Curve";
import { AsVector3 } from "../Geometry/GeUtils";
import { Container } from "./Container";
import { PartState } from "./PartState"; import { PartState } from "./PartState";
import { Path } from "./Path"; import { Path } from "./Path";
import { PathGeneratorSingle } from "./PathGenerator"; import { PathGeneratorSingle } from "./PathGenerator";
import { Point } from "./Point"; import { Point } from "./Point";
import { RandomIndex } from "./Random"; import { RandomIndex } from "./Random";
import { CADFiler } from "../DatabaseServices/CADFiler"; import { Vector2 } from "./Vector2";
/** /**
* *
@ -33,9 +30,11 @@ import { CADFiler } from "../DatabaseServices/CADFiler";
export class Part export class Part
{ {
Id: number; Id: number;
Container: Container; Container: number;
//应该也是可序列化的实体 //应该也是可序列化的实体
UserData: Curve[]; UserData: any[];
PlaceCS: any;//放置矩阵
Parent: Part;
Holes: PartState[] = []; Holes: PartState[] = [];
//零件的不同旋转状态,这个数组会在所有的状态间共享,以便快速切换状态 //零件的不同旋转状态,这个数组会在所有的状态间共享,以便快速切换状态
@ -55,24 +54,6 @@ export class Part
{ {
return this.RotatedStates[this.StateIndex]; return this.RotatedStates[this.StateIndex];
} }
//旋转->放置 (网洞内的实体需要这个)
get OCS(): Matrix4
{
let p = new Matrix4().setPosition(AsVector3(this.PlacePosition));
let r = new Matrix4().makeRotationZ(this.State.Rotation);
return p.multiply(r);
}
//旋转->归0->放置 (零件需要这个)
get PlaceCS(): Matrix4
{
let r = new Matrix4().makeRotationZ(this.State.Rotation);
let m = new Matrix4().setPosition(-this.State.OrigionMinPoint.x, -this.State.OrigionMinPoint.y, 0);
let p = new Matrix4().setPosition(AsVector3(this.PlacePosition));
return p.multiply(r).multiply(m);
}
Init(path: Path, bin?: Path, rotateCount = 4) Init(path: Path, bin?: Path, rotateCount = 4)
{ {
let rotations: number[] = []; let rotations: number[] = [];
@ -105,6 +86,7 @@ export class Part
{ {
let path_r = new Path(path.Points, pa); let path_r = new Path(path.Points, pa);
partState.Contour = PathGeneratorSingle.Allocate(path_r); partState.Contour = PathGeneratorSingle.Allocate(path_r);
partState.Contour.Area = path_0.Area;
//避免重复的Path进入State //避免重复的Path进入State
if (pathSet.has(partState.Contour)) continue; if (pathSet.has(partState.Contour)) continue;
let p0 = path_r.OrigionMinPoint; let p0 = path_r.OrigionMinPoint;
@ -149,6 +131,7 @@ export class Part
Clone() Clone()
{ {
let part = new Part(); let part = new Part();
part.Id = this.Id;
part.UserData = this.UserData; part.UserData = this.UserData;
part.RotatedStates = this.RotatedStates; part.RotatedStates = this.RotatedStates;
part.StateIndex = this.StateIndex; part.StateIndex = this.StateIndex;

@ -1,7 +1,7 @@
import { Path } from "./Path"; import { Path } from "./Path";
import { Point } from "./Point"; import { Point } from "./Point";
import { CADFiler } from "../DatabaseServices/CADFiler";
import { PathGeneratorSingle } from "./PathGenerator"; import { PathGeneratorSingle } from "./PathGenerator";
import { CADFiler } from "./Filer";
/** /**
* *
@ -18,7 +18,14 @@ export class PartState
{ {
this.Rotation = file.Read(); this.Rotation = file.Read();
this.OrigionMinPoint = file.Read(); this.OrigionMinPoint = file.Read();
this.Contour = PathGeneratorSingle.paths[file.Read()];
let index = file.Read() as number;
this.Contour = PathGeneratorSingle.paths[index];
if (!this.Contour)
{
console.log(index);
}
} }
WriteFile(file: CADFiler) WriteFile(file: CADFiler)
{ {

@ -1,24 +1,21 @@
import { Clipper } from "clipper-js-fpoint"; import { Box2 } from "./Box2";
import * as clipperLib from "js-angusj-clipper/web"; // nodejs style require import { clipperCpp } from "./ClipperCpp";
import { Box2, Box3, Vector2, Vector3 } from "three"; import { CADFiler } from "./Filer";
import { CADFiler } from "../DatabaseServices/CADFiler";
import { Polyline } from "../DatabaseServices/Entity/Polyline";
import { ISerialize } from "../DatabaseServices/ISerialize";
import { AsVector2, AsVector3, equaln } from "../Geometry/GeUtils";
import { OffsetPolyline } from "../GraphicsSystem/OffsetPolyline";
import { Point } from "./Point"; import { Point } from "./Point";
import { equaln } from "./Util";
import { Vector2 } from "./Vector2";
/** /**
* *
* NFPNFPCahce * NFPNFPCahce
* NFP,,0. * NFP,,0.
*/ */
export class Path implements ISerialize export class Path
{ {
Id: number; Id: number;
Points: Point[]; Points: Point[];
private OutsideNFPCache: Map<Path, Point[][]> = new Map(); OutsideNFPCache: { [key: number]: Point[][]; } = {};
private InsideNFPCache: Map<Path, Point[][]> = new Map(); InsideNFPCache: { [key: number]: Point[][]; } = {};
constructor(origionPoints?: Point[], rotation: number = 0) constructor(origionPoints?: Point[], rotation: number = 0)
{ {
@ -26,7 +23,6 @@ export class Path implements ISerialize
this.Init(origionPoints, rotation); this.Init(origionPoints, rotation);
} }
Origion: Path; Origion: Path;
//点表在旋转后的原始最小点.使用这个点将轮廓移动到0点 //点表在旋转后的原始最小点.使用这个点将轮廓移动到0点
OrigionMinPoint: Vector2; OrigionMinPoint: Vector2;
@ -76,66 +72,42 @@ export class Path implements ISerialize
GetNFPs(path: Path, outside: boolean): Point[][] GetNFPs(path: Path, outside: boolean): Point[][]
{ {
let nfps: Point[][]; // 寻找内轮廓时,面积应该比本path小,这个判断移交给使用者自己判断
if (path.Points.length + this.Points.length > 18) // if (!outside && this.Area < path.Area) return [];
nfps = clipperCpp.minkowskiSumPath(this.Points, path.MirrorPoints, true); let nfps = clipperCpp.lib.minkowskiSumPath(this.BigIntPoints, path.MirrorPoints, true);
else
nfps = Clipper.MinkowskiSum(this.Points, path.MirrorPoints, true);
nfps = Clipper.CleanPolygons(nfps, 0.1);
nfps = nfps.filter((nfp) => nfps = nfps.filter((nfp) =>
{ {
let area = Clipper.Area(nfp); let area = Area(nfp);
if (area > 1) return outside;//第一个不一定是外轮廓,但是面积为正时肯定为外轮廓 if (area > 1) return outside;//第一个不一定是外轮廓,但是面积为正时肯定为外轮廓
if (equaln(area, 0, 1)) return false;//应该不用在移除这个了 if (Math.abs(area) < 10) return false;//应该不用在移除这个了
let tp = new Vector3(); let { x, y } = nfp[0];
if (outside) if (outside)
{ {
for (let np of nfp) if (this.Area > path.Area)
{
let { x, y } = np;
let isOut = false;
for (let p of path.Points)
{
tp.set(p.x + x, p.y + y, 0);
let dir = this.Check.GetPointAtCurveDir(tp);
if (dir === 0) continue;
if (dir === -1) return false;
else
{ {
if (path.Area < this.Area) return true; let p = { x: path.InPoint.x + x, y: path.InPoint.y + y };
isOut = true; if (p.x < 0 || p.y < 0 || p.x > this.BigSize.x || p.y > this.BigSize.y)
break; return true;
let dir = clipperCpp.lib.pointInPolygon(p, this.BigIntPoints);
return dir === 0;
} }
}
for (let p of this.Points)
{
tp.set(p.x - x, p.y - y, 0);
let dir = path.Check.GetPointAtCurveDir(tp);
if (dir === 0) continue;
if (dir === -1) return false;
else else
{ {
if (isOut || this.Area < path.Area) return true; let p = { x: this.InPoint.x - x, y: this.InPoint.y - y };
break; if (p.x < 0 || p.y < 0 || p.x > path.BigSize.x || p.y > path.BigSize.y)
} return true;
} let dir = clipperCpp.lib.pointInPolygon(p, path.BigIntPoints);
return dir === 0;
} }
} }
else else
{ {
for (let np of nfp) let p = { x: path.InPoint.x + x, y: path.InPoint.y + y };
{ if (p.x < 0 || p.y < 0 || p.x > this.BigSize.x || p.y > this.BigSize.y)
let { x, y } = np; return false;
for (let p of path.Points) let dir = clipperCpp.lib.pointInPolygon(p, this.BigIntPoints);
{ return dir === 1;
tp.set(p.x + x, p.y + y, 0);
let dir = this.Check.GetPointAtCurveDir(tp);
if (dir === 0) continue;
return dir === -1;
}
}
} }
}); });
return nfps; return nfps;
@ -143,13 +115,13 @@ export class Path implements ISerialize
GetOutsideNFP(path: Path): Point[][] GetOutsideNFP(path: Path): Point[][]
{ {
let nfps = this.OutsideNFPCache.get(path); let nfps = this.OutsideNFPCache[path.Id];
if (nfps) return nfps; if (nfps) return nfps;
if (this.IsRect && path.IsRect) if (this.IsRect && path.IsRect)
{ {
let [ax, ay] = this.Size.toArray(); let [ax, ay] = [this.Size.x * 1e4, this.Size.y * 1e4];
let [bx, by] = path.Size.toArray(); let [bx, by] = [path.Size.x * 1e4, path.Size.y * 1e4];
nfps = [[ nfps = [[
{ x: -bx, y: -by }, { x: -bx, y: -by },
{ x: ax, y: -by }, { x: ax, y: -by },
@ -159,7 +131,7 @@ export class Path implements ISerialize
} }
else else
nfps = this.GetNFPs(path, true); nfps = this.GetNFPs(path, true);
this.OutsideNFPCache.set(path, nfps); this.OutsideNFPCache[path.Id] = nfps;
//虽然有这种神奇的特性,但是好像并不会提高性能。 //虽然有这种神奇的特性,但是好像并不会提高性能。
// path.OutsideNFPCache[this.id] = (this, nfps.map(nfp => // path.OutsideNFPCache[this.id] = (this, nfps.map(nfp =>
// { // {
@ -173,14 +145,14 @@ export class Path implements ISerialize
GetInsideNFP(path: Path): Point[][] GetInsideNFP(path: Path): Point[][]
{ {
if (path.Area > this.Area) return; if (path.Area > this.Area) return;
let nfp = this.InsideNFPCache.get(path); let nfp = this.InsideNFPCache[path.Id];
if (nfp) return nfp; if (nfp) return nfp;
let nfps: Point[][]; let nfps: Point[][];
if (this.IsRect) if (this.IsRect)
{ {
let [ax, ay] = this.Size.toArray(); let [ax, ay] = [this.Size.x * 1e4, this.Size.y * 1e4];
let [bx, by] = path.Size.toArray(); let [bx, by] = [path.Size.x * 1e4, path.Size.y * 1e4];
if (bx > ax || by > ay) if (bx > ax || by > ay)
return; return;
@ -193,44 +165,55 @@ export class Path implements ISerialize
} }
else else
nfps = this.GetNFPs(path, false); nfps = this.GetNFPs(path, false);
this.InsideNFPCache.set(path, nfps);
if (path.Id !== undefined)
this.InsideNFPCache[path.Id] = nfps;
return nfps; return nfps;
} }
protected _Polyline: Polyline; private _InPoint: Point;
get Polyline(): Polyline get InPoint()
{ {
if (!this._Polyline) if (this._InPoint) return this._InPoint;
let mp = { x: (this.Points[0].x + this.Points[1].x) / 2, y: (this.Points[0].y + this.Points[1].y) / 2 };
let normal = new Vector2(this.Points[1].x - this.Points[0].x, this.Points[1].y - this.Points[0].y).normalize();
// [normal.x, normal.y] = [normal.y, -normal.x];
mp.x -= normal.y;
mp.y += normal.x;
mp.x *= 1e4;
mp.y *= 1e4;
this._InPoint = mp;
return mp;
}
protected _BigIntPoints: Point[];
get BigIntPoints()
{ {
this._Polyline = new Polyline(); if (this._BigIntPoints) return this._BigIntPoints;
this._Polyline.LineData = this.Points.map(p => this._BigIntPoints = this.Points.map(p =>
{ {
return { return {
pt: AsVector2(p), x: Math.round(p.x * 1e4),
bul: 0 y: Math.round(p.y * 1e4),
}; };
}); });
this._Polyline.CloseMark = true; return this._BigIntPoints;
}
return this._Polyline;
} }
protected _OffsetUtil: OffsetPolyline; private _BigSize: Vector2;
get Check() get BigSize()
{
if (!this._OffsetUtil)
{ {
this._OffsetUtil = new OffsetPolyline(this.Polyline, 1); if (this._BigSize) return this._BigSize;
this._OffsetUtil.InitSubCurves(); this._BigSize = new Vector2(this.Size.x * 1e4, this.Size.y * 1e4);
} return this._BigSize;
return this._OffsetUtil;
} }
protected _MirrorPoints: Point[]; protected _MirrorPoints: Point[];
get MirrorPoints() get MirrorPoints()
{ {
if (!this._MirrorPoints) if (!this._MirrorPoints)
this._MirrorPoints = this.Points.map(p => this._MirrorPoints = this.BigIntPoints.map(p =>
{ {
return { x: -p.x, y: -p.y }; return { x: -p.x, y: -p.y };
}); });
@ -238,11 +221,11 @@ export class Path implements ISerialize
return this._MirrorPoints; return this._MirrorPoints;
} }
protected _BoundingBox: Box3; protected _BoundingBox: Box2;
get BoundingBox() get BoundingBox()
{ {
if (!this._BoundingBox) if (!this._BoundingBox)
this._BoundingBox = new Box3(new Vector3, AsVector3(this.Size)); this._BoundingBox = new Box2(new Vector2, this.Size);
return this._BoundingBox; return this._BoundingBox;
} }
@ -250,17 +233,20 @@ export class Path implements ISerialize
get Area() get Area()
{ {
if (this._Area === undefined) if (this._Area === undefined)
this._Area = Clipper.Area(this.Points); this._Area = Area(this.Points);
return this._Area; return this._Area;
} }
set Area(a: number)
{
this._Area = a;
}
private _IsRect: boolean; private _IsRect: boolean;
get IsRect() get IsRect()
{ {
if (this._IsRect === undefined) if (this._IsRect === undefined)
{ {
let s = this.BoundingBox.getSize(new Vector3); let s = this.BoundingBox.getSize(new Vector2);
this._IsRect = equaln(this.Area, s.x * s.y, 1); this._IsRect = equaln(this.Area, s.x * s.y, 1);
} }
return this._IsRect; return this._IsRect;
@ -272,19 +258,20 @@ export class Path implements ISerialize
this.Id = file.Read(); this.Id = file.Read();
let arr = file.Read(); let arr = file.Read();
this.Points = []; this.Points = [];
for (let i = 0; i < arr.length; i++) for (let i = 0; i < arr.length; i += 2)
{ {
let p = { x: arr[i], y: arr[i + 1] }; let p = { x: arr[i], y: arr[i + 1] };
this.Points.push(p); this.Points.push(p);
} }
this.Size = new Vector2(file.Read(), file.Read());
this._Area = file.Read();
let id = file.Read(); let id = file.Read();
if (id !== -1) if (id !== -1)
{ {
this.Origion = id; this.Origion = id;
this.Rotation = file.Read(); this.Rotation = file.Read();
this.OrigionMinPoint = new Vector2(file.Read(), file.Read()); this.OrigionMinPoint = new Vector2(file.Read(), file.Read());
this.Size = new Vector2(file.Read(), file.Read());
} }
} }
WriteFile(file: CADFiler): void WriteFile(file: CADFiler): void
@ -296,6 +283,9 @@ export class Path implements ISerialize
arr.push(p.x, p.y); arr.push(p.x, p.y);
file.Write(arr); file.Write(arr);
file.Write(this.Size.x);
file.Write(this.Size.y);
file.Write(this._Area);
if (this.Origion && this.Origion.Id) if (this.Origion && this.Origion.Id)
{ {
//如果有原始的id,则传递它,以便后续进行NFP复用. //如果有原始的id,则传递它,以便后续进行NFP复用.
@ -303,19 +293,40 @@ export class Path implements ISerialize
file.Write(this.Rotation); file.Write(this.Rotation);
file.Write(this.OrigionMinPoint.x); file.Write(this.OrigionMinPoint.x);
file.Write(this.OrigionMinPoint.y); file.Write(this.OrigionMinPoint.y);
file.Write(this.Size.x);
file.Write(this.Size.y);
} }
else else
file.Write(-1); file.Write(-1);
} }
} }
export let clipperCpp: clipperLib.ClipperLibWrapper; export function Area(pts: Point[]): number
clipperLib.loadNativeClipperLibInstanceAsync(
// let it autodetect which one to use, but also available WasmOnly and AsmJsOnly
clipperLib.NativeClipperLibRequestedFormat.WasmWithAsmJsFallback
).then(c =>
{ {
clipperCpp = c; let cnt = pts.length;
}); if (cnt < 3)
return 0;
let a = 0;
for (let i = 0, j = cnt - 1; i < cnt; ++i)
{
a += (pts[j].x + pts[i].x) * (pts[j].y - pts[i].y);
j = i;
}
return -a * 0.5;
}
export function TranslatePath(pts: Point[], p: Point)
{
return pts.map(px =>
{
return { x: p.x + px.x, y: p.y + px.y };
});
}
export function PathScale(pts: Point[], scale: number)
{
for (let p of pts)
{
p.x *= scale;
p.y *= scale;
}
return pts;
}

@ -1,6 +1,5 @@
import { Path } from "./Path"; import { Path } from "./Path";
import { equaln } from "../Geometry/GeUtils"; import { equaln, FixIndex } from "./Util";
import { FixIndex } from "../Common/Utils";
/** /**
* *

@ -3,4 +3,5 @@ export enum PlaceType
{ {
Box = 0, Box = 0,
Hull = 1, Hull = 1,
Gravity = 2,
} }

@ -1,3 +1,15 @@
特性列表:
余料利用
自动余料分析
异形嵌套
异形网洞利用
代码层:
多线程并行运算,发挥所有cpu核心潜力,可同时优化多个优化实例.
c++ wasm加速,使用c++进行计算,加速运行效率.
更多缓存进行快速过滤重复放置计算.
路径复用,更少的内存占用,更多缓存命中.
#路径复用 #路径复用
为了保证`Path`被复用,`Part`初始化 `Path` 时,先将 `Path` 移动到 0 点,同时记录移动的向量 `OriginMinPoint`,保存在`Part`中. 为了保证`Path`被复用,`Part`初始化 `Path` 时,先将 `Path` 移动到 0 点,同时记录移动的向量 `OriginMinPoint`,保存在`Part`中.
这样保证了不同`Part`使用相同 `Path` 时,可以有不同的初始化位置. 这样保证了不同`Part`使用相同 `Path` 时,可以有不同的初始化位置.
@ -36,3 +48,10 @@ Path: {pts, origin:{id,ro,m},}
containerid 负数表示构造,大于等于 0 表示在网洞中? containerid 负数表示构造,大于等于 0 表示在网洞中?
保存 Path IsHole IsOut 以便保存 NFP 保存 Path IsHole IsOut 以便保存 NFP
性能测试结果:
布尔运算,c++更快
NFP运算,C++更快
点在曲线内,c++更快
清理:js更快

@ -1,4 +1,4 @@
import { FixIndex } from "../Common/Utils"; import { FixIndex } from "./Util";
export function RandomIndex(count: number, exclude?: number): number export function RandomIndex(count: number, exclude?: number): number
{ {

@ -1,179 +0,0 @@
import { Clipper, ClipType, Paths, PolyFillType, PolyType } from "clipper-js-fpoint";
import { Box3, Vector3 } from "three";
import { TestDraw } from "../../Add-on/test/TestUtil";
import { app } from "../../ApplicationServices/Application";
import { arrayRemoveIf } from "../../Common/ArrayExt";
import { JigUtils } from "../../Editor/JigUtils";
import { AsVector3, equaln } from "../../Geometry/GeUtils";
import { Container } from "../Container";
import { Path2Polyline } from "../Converter/Path2Polyline";
import { NestCache } from "../NestCache";
import { Part } from "../Part";
export class TestContainer extends Container
{
async PutPart2(part: Part): Promise<boolean>
{
//------------先放置在孔洞内------------
if (this.Holes)
{
for (let c of this.Holes)
{
if (c.PutPart(part))
return true;
}
}
let cacheKey = this.StatusKey + "," + part.State.Contour.Id;
//读取缓存位置
if (NestCache.PositionCache.has(cacheKey))
{
NestCache.count1++;
let p = NestCache.PositionCache.get(cacheKey);
part.PlacePosition = p;
this.AppendPartHoles(part);
return true;
}
//------------无法容纳------------
if (this.BinPath.Area - this.PlacedArea < part.State.Contour.Area)
return false;
let binNfp = this.BinPath.GetInsideNFP(part.State.Contour);
if (!binNfp || binNfp.length === 0) return false;
//------------首个------------
if (this.Placed.length === 0)
{
part.PlacePosition = this.GetFarLeftP(binNfp);
this.AppendPartHoles(part);
return true;
}
//如果已经缓存过,证明已经无法放置(否则应该已经缓存了放置点)
// if (NestCache.PartCaled.has(cacheKey)) return false;
// NestCache.PartCaled.add(cacheKey);
//------------计算nfp------------
//合并(零件和所有已经放置零件的NFP)
let clipper = new Clipper();
let i = 1;
for (let placedPart of this.Placed)
{
let nfp = placedPart.State.Contour.GetOutsideNFP(part.State.Contour);
if (!nfp)
return false;
let x = placedPart.PlacePosition.x;
let y = placedPart.PlacePosition.y;
if (i === 2 && this.Placed.length === 4)
{
let pl = placedPart.State.Contour.Polyline.Clone();
let pl2 = part.State.Contour.Polyline.Clone();
pl.Position = pl.Position.add(new Vector3(0, -5000));
pl2.Position = pl2.Position.add(new Vector3(0, -5000));
TestDraw(pl, 1);
TestDraw(pl2, 1);
}
for (let n of nfp)
{
let f = n.map(v => { return { x: v.x + x, y: v.y + y }; });
//绘制所有的NFP
let pl = Path2Polyline(f);
pl.ColorIndex = i;
JigUtils.Draw(pl);
clipper.AddPath(f, PolyType.ptSubject, true);
}
i++;
}
app.Editor.UpdateScreen();
await app.Editor.GetPoint();
let combinedNfp = [];
if (!clipper.Execute(ClipType.ctUnion, combinedNfp, PolyFillType.pftNonZero, PolyFillType.pftNonZero))
return false;
if (combinedNfp.length === 0) return false;
//binNfp 减去 combinedNfp 得到最终的nfp
clipper = new Clipper();
clipper.AddPaths(binNfp, PolyType.ptSubject, true);
clipper.AddPaths(combinedNfp, PolyType.ptClip, true);
let finalNfp: Paths = [];
if (!clipper.Execute(ClipType.ctDifference, finalNfp, PolyFillType.pftNonZero, PolyFillType.pftNonZero))
return false;
//清理NFP
finalNfp = Clipper.CleanPolygons(finalNfp, 1);
arrayRemoveIf(finalNfp, nfp =>
{
return (nfp.length < 3 || Math.abs(Clipper.Area(nfp)) < 2);
});
if (finalNfp.length === 0)
return false;
//绘制结果nfp
JigUtils.End();
for (let n of finalNfp)
{
let pl = Path2Polyline(n);
pl.ColorIndex = 6;
JigUtils.Draw(pl);
}
app.Editor.UpdateScreen();
await app.Editor.GetPoint();
/**
* (box).
* 使
* TODO:使
*/
let minWidth = Infinity;
let minArea: number = Infinity;
let minX: number = Infinity;
let minY: number = Infinity;
let minBox: Box3;
let translate: Vector3;
for (let nfp of finalNfp)
{
for (let p of nfp)
{
let translateTemp = AsVector3(p);
let box2 = part.State.Contour.BoundingBox.clone();
box2.translate(translateTemp);
let rectBox = this.PlacedBox.clone().union(box2);
let size = rectBox.getSize(new Vector3());
//宽度的权重更大,使得零件的放置优先宽度较小
let area = (size.x * 1.1) * size.y;
if (area < minArea
|| (equaln(minArea, area) && translateTemp.x < minX)
|| (equaln(minArea, area) && translateTemp.y < minY))
{
minArea = area;
minWidth = size.x;
translate = translateTemp;
minX = translateTemp.x;
minY = translateTemp.y;
minBox = rectBox;
}
}
}
if (translate)
{
part.PlacePosition = translate;
this.AppendPartHoles(part);
return true;
}
else
return false;
}
}

@ -1,15 +1,15 @@
import { TestDraw } from "../../Add-on/test/TestUtil"; import { TestDraw } from "../../Add-on/test/TestUtil";
import { app } from "../../ApplicationServices/Application"; import { app } from "../../ApplicationServices/Application";
import { Circle } from "../../DatabaseServices/Entity/Circle"; import { Circle } from "../../DatabaseServices/Entity/Circle";
import { Point } from "../../DatabaseServices/Entity/Point";
import { Polyline } from "../../DatabaseServices/Entity/Polyline"; import { Polyline } from "../../DatabaseServices/Entity/Polyline";
import { Command } from "../../Editor/CommandMachine"; import { Command } from "../../Editor/CommandMachine";
import { PromptStatus } from "../../Editor/PromptResult"; import { PromptStatus } from "../../Editor/PromptResult";
import { AsVector3 } from "../../Geometry/GeUtils";
import { HotCMD } from "../../Hot/HotCommand"; import { HotCMD } from "../../Hot/HotCommand";
import { Curve2Path } from "../Converter/Curves2Parts"; import { Curves2Points } from "../Converter/ConverBoard2Part";
import { Path2Polyline } from "../Converter/Path2Polyline"; import { Path2Polyline } from "../Converter/Path2Polyline";
import { Path } from "../Path"; import { Path, PathScale } from "../Path";
const TesetPerformance = true;
@HotCMD @HotCMD
export class Command_TestNFP implements Command export class Command_TestNFP implements Command
@ -27,21 +27,42 @@ export class Command_TestNFP implements Command
let pls = plsRes.SelectSet.SelectEntityList as (Polyline | Circle)[]; let pls = plsRes.SelectSet.SelectEntityList as (Polyline | Circle)[];
if (pls.length < 2) return; if (pls.length < 2) return;
let binpl = pls.shift(); let binpl = pls.shift();
let binPath = Curve2Path(binpl, false); let binPath = new Path(Curves2Points(binpl, false, 0)[1]);
console.log(binPath.Points.length);
let paths: Path[] = pls.map(pl => { return Curve2Path(pl, true); }); let paths: Path[] = pls.map(pl => { return new Path(Curves2Points(pl, true, 0)[1]); });
for (let p of paths)
console.log(p.Points.length);
if (TesetPerformance)
{
console.time(); console.time();
// for (let j = 0; j < 1000; j++) for (let j = 0; j < 1000; j++)
for (let i = 0; i < paths.length; i++) for (let i = 0; i < paths.length; i++)
{ {
let path = paths[i]; let path = paths[i];
TestDraw(new Point(AsVector3(path.OrigionMinPoint))); binPath.GetNFPs(path, true);
TestDraw(Path2Polyline(path.MirrorPoints)); }
console.timeEnd();
console.time();
for (let j = 0; j < 1000; j++)
for (let i = 0; i < paths.length; i++)
{
let path = paths[i];
binPath.GetNFPs(path, true);
}
console.timeEnd();
}
else
for (let i = 0; i < paths.length; i++)
{
let path = paths[i];
if (binPath.Area > path.Area)
for (let nfp of binPath.GetNFPs(path, false)) for (let nfp of binPath.GetNFPs(path, false))
{ {
PathScale(nfp, 1e-4);
for (let p of nfp) for (let p of nfp)
{ {
p.x += binPath.OrigionMinPoint.x; p.x += binPath.OrigionMinPoint.x;
@ -51,6 +72,7 @@ export class Command_TestNFP implements Command
} }
for (let nfp of binPath.GetNFPs(path, true)) for (let nfp of binPath.GetNFPs(path, true))
{ {
PathScale(nfp, 1e-4);
for (let p of nfp) for (let p of nfp)
{ {
p.x += binPath.OrigionMinPoint.x; p.x += binPath.OrigionMinPoint.x;
@ -59,6 +81,5 @@ export class Command_TestNFP implements Command
TestDraw(Path2Polyline(nfp), 2); TestDraw(Path2Polyline(nfp), 2);
} }
} }
console.timeEnd();
} }
} }

@ -1,48 +1,45 @@
import { EndType, JoinType } from "js-angusj-clipper/web/enums"; import { EndType as ET, JoinType as JT } from "js-angusj-clipper/web";
import { TestDraw } from "../../Add-on/test/TestUtil";
import { app } from "../../ApplicationServices/Application"; import { app } from "../../ApplicationServices/Application";
import { Polyline } from "../../DatabaseServices/Entity/Polyline"; import { Polyline } from "../../DatabaseServices/Entity/Polyline";
import { PromptStatus } from "../../Editor/PromptResult"; import { PromptStatus } from "../../Editor/PromptResult";
import { HotCMD } from "../../Hot/HotCommand"; import { HotCMD } from "../../Hot/HotCommand";
import { Path2Polyline } from "../Converter/Path2Polyline"; import { clipperCpp } from "../ClipperCpp";
import { clipperCpp } from "../Path";
/**
* 19-20使c++
*/
@HotCMD @HotCMD
export class Command_TestOffset export class Command_TestOffset
{ {
async exec() async exec()
{ {
let enRes = await app.Editor.GetEntity({ Filter: { filterTypes: [Polyline] } }); let ssRes = await app.Editor.GetSelection({ Filter: { filterTypes: [Polyline] } });
if (enRes.Status !== PromptStatus.OK) return; if (ssRes.Status !== PromptStatus.OK) return;
let pl = enRes.Entity as Polyline; let ents = ssRes.SelectSet.SelectEntityList;
for (let pl of ents)
{
let pts = pl.GetStretchPoints(); let pts = pl.GetStretchPoints();
pts.forEach(p => console.log(pts.length);
console.time();
for (let i = 0; i < 1e4; i++)
{
let npts = pts.map(p =>
{ {
p.x *= 1e5; return {
p.y *= 1e5; x: p.x * 1e5,
y: p.y * 1e5,
};
}); });
let paths = clipperCpp.offsetToPaths({
offsetInputs: [{ joinType: JoinType.Miter, endType: EndType.ClosedPolygon, data: pts }], let paths = clipperCpp.lib.offsetToPaths({
offsetInputs: [{ joinType: JT.Miter, endType: ET.ClosedPolygon, data: npts }],
delta: 3 * 1e5 delta: 3 * 1e5
}); });
for (let p of paths)
{
p.forEach(px =>
{
px.x *= 1e-5;
px.y *= 1e-5;
});
TestDraw(Path2Polyline(p));
} }
console.timeEnd();
// let c = new ClipperOffset(); }
// c.AddPath(pts, JoinType.jtSquare, EndType.etClosedPolygon);
// c.DoOffset(3 * Math.sign(Clipper.Area(pts)));
// console.log('Clipper.Area(pts): ', Clipper.Area(pts));
// TestDraw(Path2Polyline(c.m_destPoly));
} }
} }

@ -11,12 +11,16 @@ import { Container } from "../Container";
import { Curve2Path, Curves2Parts } from "../Converter/Curves2Parts"; import { Curve2Path, Curves2Parts } from "../Converter/Curves2Parts";
import { Part } from "../Part"; import { Part } from "../Part";
import { PathGeneratorSingle } from "../PathGenerator"; import { PathGeneratorSingle } from "../PathGenerator";
import { InitClipperCpp } from "../ClipperCpp";
@HotCMD @HotCMD
export class Command_TestPlace implements Command export class Command_TestPlace implements Command
{ {
async exec() async exec()
{ {
InitClipperCpp();
PathGeneratorSingle.Clear();
let plsRes = await app.Editor.GetSelection({ let plsRes = await app.Editor.GetSelection({
Filter: { Filter: {
filterTypes: [Polyline, Circle] filterTypes: [Polyline, Circle]
@ -35,7 +39,7 @@ export class Command_TestPlace implements Command
let parts: Part[]; let parts: Part[];
if (true)//使用网洞 if (false)//使用网洞
{ {
parts = Curves2Parts(pls, binPath, 3); parts = Curves2Parts(pls, binPath, 3);
} }
@ -44,12 +48,17 @@ export class Command_TestPlace implements Command
parts = []; parts = [];
for (let pl of pls) for (let pl of pls)
{ {
let path = Curve2Path(pl); let path = Curve2Path(pl, true);
let part = new Part().Init(path, binPath); let part = new Part().Init(path, binPath);
part.UserData = [pl]; part.UserData = [pl];
parts.push(part); parts.push(part);
} }
parts.sort((p1, p2) =>
{
return p1.UserData[0].ColorIndex - p2.UserData[0].ColorIndex;
});
} }
let map: Map<Curve, Matrix4> = new Map(); let map: Map<Curve, Matrix4> = new Map();
for (let pl of pls) for (let pl of pls)
{ {
@ -57,7 +66,7 @@ export class Command_TestPlace implements Command
} }
let container = new Container(binPath); let container = new Container(binPath);
container.OCS = new Matrix4().setPosition(bp); let OCS = new Matrix4().setPosition(bp);
for (let i = 0; i < parts.length; i++) for (let i = 0; i < parts.length; i++)
{ {
let p = parts[i]; let p = parts[i];
@ -68,8 +77,16 @@ export class Command_TestPlace implements Command
{ {
c.ColorIndex = i + 1; c.ColorIndex = i + 1;
c.OCS = map.get(c); c.OCS = map.get(c);
c.ApplyMatrix(p.PlaceCS);
c.ApplyMatrix(p.Container.OCS); let r = new Matrix4().makeRotationZ(p.State.Rotation);
let m = new Matrix4().setPosition(-p.State.OrigionMinPoint.x, -p.State.OrigionMinPoint.y, 0);
let px = new Matrix4().setPosition(AsVector3(p.PlacePosition));
c.ApplyMatrix(m);
c.ApplyMatrix(r);
c.ApplyMatrix(px);
c.ApplyMatrix(OCS);
} }
} }
app.Editor.UpdateScreen(); app.Editor.UpdateScreen();

@ -1,23 +0,0 @@
import { Command } from "../../Editor/CommandMachine";
import { app } from "../../ApplicationServices/Application";
import { Polyline } from "../../DatabaseServices/Entity/Polyline";
import { PromptStatus } from "../../Editor/PromptResult";
import { Polylin2Points } from "../Converter/ConverBoard2Part";
import { TestDraw } from "../../Add-on/test/TestUtil";
import { Path2Polyline } from "../Converter/Path2Polyline";
import { HotCMD } from "../../Hot/HotCommand";
@HotCMD
export class Command_Polyline2Path implements Command
{
async exec()
{
let plRes = await app.Editor.GetEntity({ Filter: { filterTypes: [Polyline] } });
if (plRes.Status !== PromptStatus.OK) return;
let pl = plRes.Entity as Polyline;
// TestDraw(Path2Polyline(Polylin2Points(pl, true, 3)), 1);
// TestDraw(Path2Polyline(Polylin2Points(pl, false, 3)), 2);
}
}

@ -7,10 +7,13 @@ import { Path2Polyline } from "../Converter/Path2Polyline";
import { HotCMD } from "../../Hot/HotCommand"; import { HotCMD } from "../../Hot/HotCommand";
import { Circle } from "../../DatabaseServices/Entity/Circle"; import { Circle } from "../../DatabaseServices/Entity/Circle";
import { SimplifyDouglasPeucker } from "../Converter/Simplify2"; import { SimplifyDouglasPeucker } from "../Converter/Simplify2";
import { Polylin2Points } from "../Converter/ConverBoard2Part";
@HotCMD @HotCMD
export class Command_TestSimply implements Command { export class Command_TestSimply implements Command
async exec() { {
async exec()
{
let plsRes = await app.Editor.GetSelection({ let plsRes = await app.Editor.GetSelection({
Filter: { Filter: {
filterTypes: [Polyline, Circle] filterTypes: [Polyline, Circle]
@ -22,7 +25,16 @@ export class Command_TestSimply implements Command {
let pls = plsRes.SelectSet.SelectEntityList as Polyline[]; let pls = plsRes.SelectSet.SelectEntityList as Polyline[];
for (let polyline of pls) { for (let polyline of pls)
{
if (1)
{
let pts = Polylin2Points(polyline, true, 3)[1];
let pl = Path2Polyline(pts);
TestDraw(pl, 3);
}
else
{
if (polyline.Area2 < 0) polyline.Reverse(); if (polyline.Area2 < 0) polyline.Reverse();
let pts = polyline.GetStretchPoints(); let pts = polyline.GetStretchPoints();
let [spts, offset] = SimplifyDouglasPeucker(pts, 12); let [spts, offset] = SimplifyDouglasPeucker(pts, 12);
@ -35,4 +47,5 @@ export class Command_TestSimply implements Command {
TestDraw(pl2, 2); TestDraw(pl2, 2);
} }
} }
}
} }

@ -1,41 +0,0 @@
import { Matrix4 } from "three";
import { TestDraw } from "../../Add-on/test/TestUtil";
import { app } from "../../ApplicationServices/Application";
import { Polyline } from "../../DatabaseServices/Entity/Polyline";
import { PromptStatus } from "../../Editor/PromptResult";
import { HotCMD } from "../../Hot/HotCommand";
import { DefaultBin } from "../Converter/ConverBoard2Part";
import { Curve2Path } from "../Converter/Curves2Parts";
import { Part } from "../Part";
import { PathGeneratorSingle } from "../PathGenerator";
@HotCMD
export class Test_Translate
{
async exec()
{
PathGeneratorSingle.Clear();
let eRes = await app.Editor.GetEntity({ Filter: { filterTypes: [Polyline] } });
if (eRes.Status !== PromptStatus.OK) return;
let pl = eRes.Entity as Polyline;
let path = Curve2Path(pl, true);
let part = new Part();
part.Init(path, DefaultBin, 36);
for (let i = 0; i < part.RotatedStates.length; i++)
{
let p = part.RotatedStates[i];
let plx = pl.Clone();
let m = new Matrix4().setPosition(-p.OrigionMinPoint.x, -p.OrigionMinPoint.y, 0);
plx.ApplyMatrix(m);
let r = new Matrix4().makeRotationZ(p.Rotation);
plx.ApplyMatrix(r);
TestDraw(plx, i + 1);
}
}
}

@ -1,179 +0,0 @@
import React from "react";
import { Matrix4, Vector3 } from "three";
import { TestDraw } from "../../Add-on/test/TestUtil";
import { app } from "../../ApplicationServices/Application";
import { Board } from "../../DatabaseServices/Entity/Board";
import { Circle } from "../../DatabaseServices/Entity/Circle";
import { Curve } from "../../DatabaseServices/Entity/Curve";
import { Polyline } from "../../DatabaseServices/Entity/Polyline";
import { Command, commandMachine } from "../../Editor/CommandMachine";
import { JigUtils } from "../../Editor/JigUtils";
import { PromptStatus } from "../../Editor/PromptResult";
import { AsVector3, IdentityMtx4 } from "../../Geometry/GeUtils";
import { HotCMD } from "../../Hot/HotCommand";
import { AppToaster } from "../../UI/Components/Toaster";
import { ConverBoard2Part, DefaultBin } from "../Converter/ConverBoard2Part";
import { Curves2Parts } from "../Converter/Curves2Parts";
import { NestCache } from "../NestCache";
import { OptimizeMachine } from "../OptimizeMachine";
import { TestContainer } from "./TestContainer";
import { NestVariant } from "./TestVariant";
@HotCMD
export class Command_TestYH implements Command
{
async exec()
{
NestVariant.curveMap.clear();
let plsRes = await app.Editor.GetSelection({
Filter: {
filterTypes: [Polyline, Circle, Board]
}
});
if (plsRes.Status !== PromptStatus.OK)
return;
let pls: (Polyline | Circle)[] = [];
let brs: Board[] = [];
for (let e of plsRes.SelectSet.SelectEntityList)
{
if (e instanceof Board)
brs.push(e);
else
pls.push(e as Polyline);
}
let binPath = DefaultBin;
let parts = Curves2Parts(pls, binPath, 3);
for (let br of brs)
{
parts.push(ConverBoard2Part(br));
}
//购买机器
let machie = new OptimizeMachine();
machie.Bin = binPath;
machie.PutParts(parts);
let key = AppToaster.show({
message: <>
....
<button onClick={e =>
{
machie.Suspend();
AppToaster.dismiss(key);
}}></button>
</>,
timeout: 0,
});
let binCurves: Polyline[] = [];
const DrawBin = (count: number) =>
{
for (let i = binCurves.length; i < count; i++)
{
let pl = binPath.Polyline.Clone();
pl.Position = new Vector3(1.1 * i * 1220);
TestDraw(pl, 6);
binCurves.push(pl);
}
for (let i = 0; i < binCurves.length; i++)
{
binCurves[i].Visible = i < count;
}
};
for (let part of parts)
{
for (let c of part.UserData)
NestVariant.curveMap.set(c, c.OCS);
}
machie.callBack = async (i) =>
{
let size = binPath.BoundingBox.getSize(new Vector3).setY(0);
size.x *= 1.1;
let p = AsVector3(binPath.OrigionMinPoint);
DrawBin(i.Containers.length);
for (let c of i.Containers)
{
let ocs = new Matrix4().setPosition(p);
let i = 1;
for (let p of c.PlacedAll)
{
let curves = p.UserData as Curve[];
for (let c of curves)
{
c.ColorIndex = i;
c.OCS = NestVariant.curveMap.get(c) || IdentityMtx4;
c.ApplyMatrix(p.PlaceCS)
.ApplyMatrix(p.Container.OCS)
.ApplyMatrix(ocs);
}
i++;
}
p.add(size);
}
app.Editor.UpdateScreen();
};
await machie.Start();
}
}
@HotCMD
class RePlace
{
async exec()
{
if (!NestVariant.best) return;
NestCache.Clear();
let binPath = DefaultBin;
let size = binPath.BoundingBox.getSize(new Vector3).setY(0);
size.x *= 1.1;
let p = new Vector3;
// for (let c of NestVariant.best.Containers)
let c = NestVariant.best.Containers[1];
{
let ocs = new Matrix4().setPosition(p);
let nc = new TestContainer(binPath);
let parts = c.PlacedAll.map(p => p.Clone());
let i = 1;
for (let p of parts)
{
JigUtils.End();
await nc.PutPart2(p);
let curves = p.UserData as Curve[];
if (curves[0].IsErase)
{
curves = curves.map(c =>
{
let nc = c.Clone();
TestDraw(nc);
return nc;
});
}
for (let c of curves)
{
c.ColorIndex = i;
c.OCS = NestVariant.curveMap.get(c) || IdentityMtx4;
c.ApplyMatrix(p.PlaceCS)
.ApplyMatrix(p.Container.OCS)
.ApplyMatrix(ocs);
}
JigUtils.End();
await app.Editor.GetPoint({ Msg: "放置完毕" });
i++;
}
p.add(size);
}
app.Editor.UpdateScreen();
}
}
commandMachine.RegisterCommand("reyh", new RePlace);

@ -1,57 +0,0 @@
import { Command } from "../../Editor/CommandMachine";
import { app } from "../../ApplicationServices/Application";
import { Polyline } from "../../DatabaseServices/Entity/Polyline";
import { Circle } from "../../DatabaseServices/Entity/Circle";
import { Board } from "../../DatabaseServices/Entity/Board";
import { PromptStatus } from "../../Editor/PromptResult";
import { DefaultBin, ConverBoard2Part } from "../Converter/ConverBoard2Part";
import { Curves2Parts } from "../Converter/Curves2Parts";
import { PathGeneratorSingle } from "../PathGenerator";
import { NestDatabase } from "../NestDatabase";
import { CADFiler } from "../../DatabaseServices/CADFiler";
import { HotCMD } from "../../Hot/HotCommand";
@HotCMD
export class Command_TestYH2 implements Command
{
async exec()
{
PathGeneratorSingle.Clear();
PathGeneratorSingle.RegisterId(DefaultBin);
let plsRes = await app.Editor.GetSelection({
Filter: {
filterTypes: [Polyline, Circle, Board]
}
});
if (plsRes.Status !== PromptStatus.OK)
return;
let pls: (Polyline | Circle)[] = [];
let brs: Board[] = [];
for (let e of plsRes.SelectSet.SelectEntityList)
{
if (e instanceof Board)
brs.push(e);
else
pls.push(e as Polyline);
}
let binPath = DefaultBin;
let parts = Curves2Parts(pls, binPath, 3);
for (let br of brs)
parts.push(ConverBoard2Part(br));
let db = new NestDatabase();
db.Paths = PathGeneratorSingle.paths;
db.Parts = parts;
db.Bin = DefaultBin;
let f = new CADFiler();
db.WriteFile(f);
}
}

@ -0,0 +1,182 @@
import React from "react";
import { Matrix4, Vector3 } from "three";
import { TestDraw } from "../../Add-on/test/TestUtil";
import { app } from "../../ApplicationServices/Application";
import { Board } from "../../DatabaseServices/Entity/Board";
import { Circle } from "../../DatabaseServices/Entity/Circle";
import { Curve } from "../../DatabaseServices/Entity/Curve";
import { Polyline } from "../../DatabaseServices/Entity/Polyline";
import { Command } from "../../Editor/CommandMachine";
import { PromptStatus } from "../../Editor/PromptResult";
import { AsVector3, IdentityMtx4 } from "../../Geometry/GeUtils";
import { HotCMD } from "../../Hot/HotCommand";
import { AppToaster } from "../../UI/Components/Toaster";
import { ConverBoard2Part } from "../Converter/ConverBoard2Part";
import { Curves2Parts } from "../Converter/Curves2Parts";
import { Path2Polyline } from "../Converter/Path2Polyline";
import { DefaultBin } from "../DefaultBin";
import { NestCache } from "../NestCache";
import { NestDatabase } from "../NestDatabase";
import { OptimizeMachine } from "../OptimizeMachine";
import { PathGeneratorSingle } from "../PathGenerator";
import { NestVariant } from "./TestVariant";
@HotCMD
export class Command_TestYH2 implements Command
{
async exec()
{
PathGeneratorSingle.Clear();
NestCache.Clear();
DefaultBin.InsideNFPCache = {};
let plsRes = await app.Editor.GetSelection({
Filter: {
filterTypes: [Polyline, Circle, Board]
}
});
if (plsRes.Status !== PromptStatus.OK)
return;
let pls: (Polyline | Circle)[] = [];
let brs: Board[] = [];
for (let e of plsRes.SelectSet.SelectEntityList)
{
if (e instanceof Board)
brs.push(e);
else
pls.push(e as Polyline);
}
let binPath = DefaultBin;
binPath.Id = undefined;
PathGeneratorSingle.RegisterId(binPath);
let parts = Curves2Parts(pls, binPath, 3);
for (let br of brs)
parts.push(ConverBoard2Part(br));
parts = parts.filter(p => p.RotatedStates.length > 0);
if (parts.length === 0)
{
app.Editor.Prompt("没有可以优化的板件!");
return;
}
for (let i = 0; i < parts.length; i++)
parts[i].Id = i;
let db = new NestDatabase();
db.Paths = PathGeneratorSingle.paths;
db.Parts = parts;
db.Bin = DefaultBin;
let m = new OptimizeMachine;
m.Bin = db.Bin;
m.PutParts(db.Parts);
let binCurves: Polyline[] = [];
const DrawBin = (count: number) =>
{
for (let i = binCurves.length; i < count; i++)
{
let pl = Path2Polyline(binPath.Points);
pl.Position = new Vector3(1.1 * i * 1220);
TestDraw(pl, 6);
binCurves.push(pl);
}
for (let i = 0; i < binCurves.length; i++)
{
binCurves[i].Visible = i < count;
}
};
for (let part of parts)
{
for (let c of part.UserData)
NestVariant.curveMap.set(c, c.OCS);
}
m.callBack = async (inv) =>
{
for (let part of parts)
{
part.Parent = undefined;
for (let c of part.UserData)
{
c.OCS = NestVariant.curveMap.get(c) || IdentityMtx4;
}
}
DrawBin(inv.Containers.length);
for (let i = 0; i < inv.Containers.length; i++)
{
for (let part of inv.Containers[i].Placed)
{
let r = new Matrix4().makeRotationZ(part.State.Rotation);
let m = new Matrix4().setPosition(-part.State.OrigionMinPoint.x - 0.5, -part.State.OrigionMinPoint.y - 0.5, 0);
let p = new Matrix4().setPosition(AsVector3(part.PlacePosition).multiplyScalar(1e-4));
let partOld = parts[part.Id];
partOld.PlaceCS = p.multiply(r).multiply(m);
if (i !== 0)
partOld.PlaceCS = new Matrix4().setPosition(new Vector3(1.1 * i * 1220)).multiply(partOld.PlaceCS);
for (let c of partOld.UserData)
{
let cu = c as Curve;
cu.ApplyMatrix(partOld.PlaceCS);
}
}
}
for (let hole of inv.HoleContainers)
{
let pm = new Matrix4().setPosition(hole.ParentM.x, hole.ParentM.y, 0);
for (let part of hole.Placed)
{
let r = new Matrix4().makeRotationZ(part.State.Rotation);
let m = new Matrix4().setPosition(-part.State.OrigionMinPoint.x, -part.State.OrigionMinPoint.y, 0);
let p = new Matrix4().setPosition(AsVector3(part.PlacePosition).multiplyScalar(1e-4));
part.PlaceCS = pm.clone().multiply(p).multiply(r).multiply(m);//pm.multiply (p).multiply pm.multiply(p).multiply
part.Parent = parts[hole.ParentId];
}
}
for (let hole of inv.HoleContainers)
{
for (let part of hole.Placed)
{
for (let c of part.UserData)
{
let cu = c as Curve;
cu.Visible = true;
cu.ApplyMatrix(part.PlaceCS);
let p = part.Parent;
while (p)
{
cu.ApplyMatrix(p.PlaceCS);
p = p.Parent;
}
}
}
}
};
let key = AppToaster.show({
message: <>
....
<button onClick={e =>
{
m.Suspend();
AppToaster.dismiss(key);
}}></button>
</>,
timeout: 0,
});
await m.Start();
}
}

@ -0,0 +1,212 @@
import React from "react";
import { Matrix4, Vector3 } from "three";
import { TestDraw } from "../../Add-on/test/TestUtil";
import { app } from "../../ApplicationServices/Application";
import { Board } from "../../DatabaseServices/Entity/Board";
import { Circle } from "../../DatabaseServices/Entity/Circle";
import { Curve } from "../../DatabaseServices/Entity/Curve";
import { Polyline } from "../../DatabaseServices/Entity/Polyline";
import { Command, CommandWrap } from "../../Editor/CommandMachine";
import { PromptStatus } from "../../Editor/PromptResult";
import { AsVector3, IdentityMtx4 } from "../../Geometry/GeUtils";
import { HotCMD } from "../../Hot/HotCommand";
import { AppToaster } from "../../UI/Components/Toaster";
import { InitClipperCpp } from "../ClipperCpp";
import { ConverBoard2Part } from "../Converter/ConverBoard2Part";
import { Curves2Parts } from "../Converter/Curves2Parts";
import { Path2Polyline } from "../Converter/Path2Polyline";
import { DefaultBin } from "../DefaultBin";
import { CADFiler } from "../Filer";
import { Individual } from "../Individual";
import { NestCache } from "../NestCache";
import { NestDatabase } from "../NestDatabase";
import Worker from "../OptimizeWorker.worker";
import { PathGeneratorSingle } from "../PathGenerator";
import { NestVariant } from "./TestVariant";
@HotCMD
export class Command_TestYHWorker implements Command
{
async exec()
{
PathGeneratorSingle.Clear();
NestCache.Clear();
DefaultBin.InsideNFPCache = {};
let plsRes = await app.Editor.GetSelection({
Filter: { filterTypes: [Polyline, Circle, Board] }
});
await InitClipperCpp();
if (plsRes.Status !== PromptStatus.OK)
return;
let pls: (Polyline | Circle)[] = [];
let brs: Board[] = [];
for (let e of plsRes.SelectSet.SelectEntityList)
{
if (e instanceof Board)
brs.push(e);
else
pls.push(e as Polyline);
}
let binPath = DefaultBin;
binPath.Id = undefined;
PathGeneratorSingle.RegisterId(binPath);
let parts = Curves2Parts(pls, binPath, 3.5);
for (let br of brs)
parts.push(ConverBoard2Part(br));
parts = parts.filter(p => p.RotatedStates.length > 0);
if (parts.length === 0)
{
app.Editor.Prompt("没有可以优化的板件!");
return;
}
for (let i = 0; i < parts.length; i++)
parts[i].Id = i;
let db = new NestDatabase();
db.Paths = PathGeneratorSingle.paths;
db.Parts = parts;
db.Bin = DefaultBin;
let f = new CADFiler();
db.WriteFile(f);
let db2 = new NestDatabase();
f.Reset();
db2.ReadFile(f);
let binCurves: Polyline[] = [];
const DrawBin = (count: number) =>
{
for (let i = binCurves.length; i < count; i++)
{
let pl = Path2Polyline(binPath.Points);
pl.Position = new Vector3(1.1 * i * 1220);
TestDraw(pl, 6);
binCurves.push(pl);
}
for (let i = 0; i < binCurves.length; i++)
{
binCurves[i].Visible = i < count;
}
app.Editor.UpdateScreen();
};
for (let part of parts)
{
for (let c of part.UserData)
NestVariant.curveMap.set(c, c.OCS);
}
let workers: Worker[] = [];
let key = AppToaster.show({
message: <>
....
<button onClick={e =>
{
for (let w of workers)
w.terminate();
AppToaster.dismiss(key);
}}></button>
</>,
timeout: 0,
});
let best = Infinity;
const fff = (e) =>
{
let f = new CADFiler(e.data);
let inv = new Individual(db.Parts);
inv.ReadFile(f);
if (best <= inv.Fitness)
return;
else
best = inv.Fitness;
CommandWrap(() =>
{
app.Editor.Prompt(`优化率:${inv.Fitness}`);
for (let part of parts)
{
part.Parent = undefined;
for (let c of part.UserData)
{
c.OCS = NestVariant.curveMap.get(c) || IdentityMtx4;
}
}
DrawBin(inv.Containers.length);
for (let i = 0; i < inv.Containers.length; i++)
{
let j = 1;
for (let part of inv.Containers[i].Placed)
{
let r = new Matrix4().makeRotationZ(part.State.Rotation);
let m = new Matrix4().setPosition(-part.State.OrigionMinPoint.x - 0.5, -part.State.OrigionMinPoint.y - 0.5, 0);
let p = new Matrix4().setPosition(AsVector3(part.PlacePosition).multiplyScalar(1e-4));
let partOld = parts[part.Id];
partOld.PlaceCS = p.multiply(r).multiply(m);
if (i !== 0)
partOld.PlaceCS = new Matrix4().setPosition(new Vector3(1.1 * i * 1220)).multiply(partOld.PlaceCS);
for (let c of partOld.UserData)
{
let cu = c as Curve;
cu.ColorIndex = j;
cu.ApplyMatrix(partOld.PlaceCS);
}
j++;
}
}
for (let hole of inv.HoleContainers)
{
let pm = new Matrix4().setPosition(hole.ParentM.x, hole.ParentM.y, 0);
for (let part of hole.Placed)
{
let r = new Matrix4().makeRotationZ(part.State.Rotation);
let m = new Matrix4().setPosition(-part.State.OrigionMinPoint.x, -part.State.OrigionMinPoint.y, 0);
let p = new Matrix4().setPosition(AsVector3(part.PlacePosition).multiplyScalar(1e-4));
part.PlaceCS = pm.clone().multiply(p).multiply(r).multiply(m);//pm.multiply (p).multiply pm.multiply(p).multiply
part.Parent = parts[hole.ParentId];
}
}
for (let hole of inv.HoleContainers)
{
for (let part of hole.Placed)
{
for (let c of part.UserData)
{
let cu = c as Curve;
cu.Visible = true;
cu.ApplyMatrix(part.PlaceCS);
let p = part.Parent;
while (p)
{
cu.ApplyMatrix(p.PlaceCS);
p = p.Parent;
}
}
}
}
}, "应用优化");
};
for (let i = 0; i < navigator.hardwareConcurrency - 2; i++)// ||
{
let w = new Worker;
workers.push(w);
w.postMessage(f._datas);
w.onmessage = fff;
}
}
}

@ -0,0 +1,15 @@
export function equaln(v1: number, v2: number, fuzz = 1e-5)
{
return Math.abs(v1 - v2) <= fuzz;
}
export function FixIndex(index: number, arr: Array<any> | number)
{
let count = (arr instanceof Array) ? arr.length : arr;
if (index < 0)
return count + index;
else if (index >= count)
return index - count;
else
return index;
}

@ -0,0 +1,292 @@
import { Point } from "./Point";
export class Vector2
{
x: number;
y: number;
readonly isVector2: boolean = true;
constructor(x: number = 0, y: number = 0)
{
this.x = x;
this.y = y;
}
get width(): number { return this.x; }
set width(value: number) { this.x = value; }
get height(): number { return this.y; }
set height(value: number) { this.y = value; }
set(x: number, y: number): Vector2
{
this.x = x;
this.y = y;
return this;
}
setScalar(scalar: number): Vector2
{
this.x = scalar;
this.y = scalar;
return this;
}
setX(x: number): Vector2
{
this.x = x;
return this;
}
setY(y: number): Vector2
{
this.y = y;
return this;
}
setComponent(index: number, value: number): Vector2
{
switch (index)
{
case 0: this.x = value; break;
case 1: this.y = value; break;
default: throw new Error('index is out of range: ' + index);
}
return this;
}
getComponent(index: number): number
{
switch (index)
{
case 0: return this.x;
case 1: return this.y;
default: throw new Error('index is out of range: ' + index);
}
}
clone(): Vector2
{
return new (this.constructor as any)().copy(this);
}
copy(v: Vector2): Vector2
{
this.x = v.x;
this.y = v.y;
return this;
}
add(v: Point): Vector2
{
this.x += v.x;
this.y += v.y;
return this;
}
addScalar(s: number): Vector2
{
this.x += s;
this.y += s;
return this;
}
addVectors(a: Vector2, b: Vector2): Vector2
{
this.x = a.x + b.x;
this.y = a.y + b.y;
return this;
}
addScaledVector(v: Vector2, s: number): Vector2
{
this.x += v.x * s;
this.y += v.y * s;
return this;
}
sub(v: Vector2, w?: Vector2): Vector2
{
if (w !== undefined)
{
console.warn('THREE.Vector2: .sub() now only accepts one argument. Use .subVectors(a, b) instead.');
return this.subVectors(v, w);
}
this.x -= v.x;
this.y -= v.y;
return this;
}
subScalar(s: number): Vector2
{
this.x -= s;
this.y -= s;
return this;
}
subVectors(a: Vector2, b: Vector2): Vector2
{
this.x = a.x - b.x;
this.y = a.y - b.y;
return this;
}
multiply(v: Vector2): Vector2
{
this.x *= v.x;
this.y *= v.y;
return this;
}
multiplyScalar(scalar: number): Vector2
{
if (isFinite(scalar))
{
this.x *= scalar;
this.y *= scalar;
} else
{
this.x = 0;
this.y = 0;
}
return this;
}
divide(v: Vector2): Vector2
{
this.x /= v.x;
this.y /= v.y;
return this;
}
divideScalar(scalar: number): Vector2
{
return this.multiplyScalar(1 / scalar);
}
min(v: Vector2): Vector2
{
this.x = Math.min(this.x, v.x);
this.y = Math.min(this.y, v.y);
return this;
}
max(v: Vector2): Vector2
{
this.x = Math.max(this.x, v.x);
this.y = Math.max(this.y, v.y);
return this;
}
clamp(min: Vector2, max: Vector2): Vector2
{
// This function assumes min < max, if this assumption isn't true it will not operate correctly
this.x = Math.max(min.x, Math.min(max.x, this.x));
this.y = Math.max(min.y, Math.min(max.y, this.y));
return this;
}
private static clampScalar_min = new Vector2();
private static clampScalar_max = new Vector2();
clampScalar(minVal: number, maxVal: number): Vector2
{
const min: Vector2 = Vector2.clampScalar_min.set(minVal, minVal);
const max: Vector2 = Vector2.clampScalar_max.set(maxVal, maxVal);
return this.clamp(min, max);
}
clampLength(min: number, max: number): Vector2
{
const length: number = this.length();
return this.multiplyScalar(Math.max(min, Math.min(max, length)) / length);
}
floor(): Vector2
{
this.x = Math.floor(this.x);
this.y = Math.floor(this.y);
return this;
}
ceil(): Vector2
{
this.x = Math.ceil(this.x);
this.y = Math.ceil(this.y);
return this;
}
round(): Vector2
{
this.x = Math.round(this.x);
this.y = Math.round(this.y);
return this;
}
roundToZero(): Vector2
{
this.x = (this.x < 0) ? Math.ceil(this.x) : Math.floor(this.x);
this.y = (this.y < 0) ? Math.ceil(this.y) : Math.floor(this.y);
return this;
}
negate(): Vector2
{
this.x = - this.x;
this.y = - this.y;
return this;
}
dot(v: Vector2): number
{
return this.x * v.x + this.y * v.y;
}
lengthSq(): number
{
return this.x * this.x + this.y * this.y;
}
length(): number
{
return Math.sqrt(this.x * this.x + this.y * this.y);
}
lengthManhattan(): number
{
return Math.abs(this.x) + Math.abs(this.y);
}
normalize(): Vector2
{
return this.divideScalar(this.length());
}
angle(): number
{
// computes the angle in radians with respect to the positive x-axis
let angle: number = Math.atan2(this.y, this.x);
if (angle < 0) angle += 2 * Math.PI;
return angle;
}
distanceTo(v: Vector2): number
{
return Math.sqrt(this.distanceToSquared(v));
}
distanceToSquared(v: Vector2): number
{
const dx: number = this.x - v.x, dy: number = this.y - v.y;
return dx * dx + dy * dy;
}
distanceToManhattan(v: Vector2): number
{
return Math.abs(this.x - v.x) + Math.abs(this.y - v.y);
}
setLength(length: number): Vector2
{
return this.multiplyScalar(length / this.length());
}
lerp(v: Vector2, alpha: number): Vector2
{
this.x += (v.x - this.x) * alpha;
this.y += (v.y - this.y) * alpha;
return this;
}
lerpVectors(v1: Vector2, v2: Vector2, alpha: number): Vector2
{
return this.subVectors(v2, v1).multiplyScalar(alpha).add(v1);
}
equals(v: Vector2): boolean
{
return ((v.x === this.x) && (v.y === this.y));
}
fromArray(array: Float32Array | number[], offset: number = 0): Vector2
{
this.x = array[offset];
this.y = array[offset + 1];
return this;
}
toArray(array: Float32Array | number[] = [], offset: number = 0): Float32Array | number[]
{
array[offset] = this.x;
array[offset + 1] = this.y;
return array;
}
fromAttribute(attribute: any, index: number, offset: number = 0): Vector2
{
index = index * attribute.itemSize + offset;
this.x = attribute.array[index];
this.y = attribute.array[index + 1];
return this;
}
rotateAround(center: Vector2, angle: number): Vector2
{
const c: number = Math.cos(angle), s: number = Math.sin(angle);
const x: number = this.x - center.x;
const y: number = this.y - center.y;
this.x = x * c - y * s + center.x;
this.y = x * s + y * c + center.y;
return this;
}
}
Loading…
Cancel
Save