diff --git a/package.json b/package.json index cb0e00c05..622957d59 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "@blueprintjs/popover2": "^0.12.9", "@blueprintjs/table": "^3.10.0", "@epicgames-ps/lib-pixelstreamingfrontend-ue5.2": "^0.5.1", - "@jscad/modeling": "^2.11.0", + "@jscad/modeling": "^2.12.0", "blueimp-md5": "^2.19.0", "detect-browser": "^5.3.0", "dwg2dxf": "^1.0.0", diff --git a/src/Add-on/testEntity/TestDrawGeom3s.ts b/src/Add-on/testEntity/TestDrawGeom3s.ts new file mode 100644 index 000000000..8ebc7f1f0 --- /dev/null +++ b/src/Add-on/testEntity/TestDrawGeom3s.ts @@ -0,0 +1,77 @@ +import geom3 from "@jscad/modeling/src/geometries/geom3"; +import poly3 from "@jscad/modeling/src/geometries/poly3"; +import { Matrix4, Vector3 } from "three"; +import { app } from "../../ApplicationServices/Application"; +import { Log } from "../../Common/Log"; +import { Polyline } from "../../DatabaseServices/Entity/Polyline"; +import { Region } from "../../DatabaseServices/Entity/Region"; +import { GroupRecord } from "../../DatabaseServices/GroupTableRecord"; +import { ObjectId } from "../../DatabaseServices/ObjectId"; +import { AsVector2 } from "../../Geometry/GeUtils"; +import { Orbit } from "../../Geometry/Orbit"; + +/** + * 测试绘制geom3的多边形(用面域绘制) + * 功能测试了多边形是不是自交,如果自交则绘制多段线 + * 绘制完成后编组 + */ +export function TestDrawGeom3s(geoms: geom3.Geom3[]) +{ + for (let i = 0; i < geoms.length; i++) + { + let geom = geoms[i]; + let polygons = geom.polygons; + let entitys: ObjectId[] = []; + for (let polygon of polygons) + { + let plane = poly3.plane(polygon); + let z = new Vector3().fromArray(plane); + + + let x = new Vector3(); + let y = new Vector3(); + + Orbit.ComputUpDirection(z, y, x); + + let mtx = new Matrix4().makeBasis(x, y, z).setPosition(z.multiplyScalar(plane[3])); + let mtxInv = new Matrix4().getInverse(mtx); + + if (geom.transforms) + mtx.multiplyMatrices(new Matrix4().fromArray(geom.transforms), mtx); + + let pts = polygon.vertices.map(v => new Vector3().fromArray(v).applyMatrix4(mtxInv)); + + let polyline = new Polyline(pts.map(p => + { + return { pt: AsVector2(p), bul: 0 }; + })); + polyline.CloseMark = true; + + if (polyline.IsIntersectSelf()) + { + polyline.ColorIndex = i + 1; + app.Database.ModelSpace.Append(polyline); + Log("存在无法自交多边形"); + } + else + { + + let reg = Region.CreateFromCurves(polyline.Explode()); + if (reg) + { + reg.ApplyMatrix(mtx); + reg.ColorIndex = i + 1; + app.Database.ModelSpace.Append(reg); + entitys.push(reg.Id); + } + + else + console.error(); + } + } + + let group = new GroupRecord; + app.Database.GroupTable.Add(group); + group.Entitys = entitys; + } +} diff --git a/src/DatabaseServices/Entity/Board.ts b/src/DatabaseServices/Entity/Board.ts index badfd67d5..2cc5cdcc8 100644 --- a/src/DatabaseServices/Entity/Board.ts +++ b/src/DatabaseServices/Entity/Board.ts @@ -1,7 +1,14 @@ +import geom2, { Geom2 } from '@jscad/modeling/src/geometries/geom2'; +import geom3, { transform } from '@jscad/modeling/src/geometries/geom3'; import Geom3 from '@jscad/modeling/src/geometries/geom3/type'; +import poly3, { measureArea } from '@jscad/modeling/src/geometries/poly3'; import { Mat4 } from '@jscad/modeling/src/maths/mat4'; -import subtract from '@jscad/modeling/src/operations/booleans/subtract'; -import union from '@jscad/modeling/src/operations/booleans/union'; +import { Vec2 } from '@jscad/modeling/src/maths/vec2'; +import { Vec3 } from '@jscad/modeling/src/maths/vec3'; +import { intersect, subtract, union } from '@jscad/modeling/src/operations/booleans'; +import extrudeLinear from '@jscad/modeling/src/operations/extrusions/extrudeLinear'; +import extrudeRotate from '@jscad/modeling/src/operations/extrusions/extrudeRotate'; +import retessellate from '@jscad/modeling/src/operations/modifiers/retessellate'; import { BufferGeometry, Euler, FrontSide, Frustum, Geometry, LineSegments, Matrix3, Matrix4, Mesh, Object3D, ShapeGeometry, Line as TLine, UVGenerator, Vector3 } from 'three'; import { Board2Regions } from '../../Add-on/BoardEditor/Board2Regions'; import { DeserializationBoard2DModeingData, DeserializationBoard3DModeingData, SerializeBoard2DModeingData, SerializeBoard3DModeingData, deserializationBoardData, serializeBoardData } from '../../Add-on/BoardEditor/SerializeBoardData'; @@ -14,19 +21,23 @@ import { EBoardKeyList } from '../../Common/BoardKeyList'; import { Geom3Res } from '../../Common/CSGIntersect'; import { ColorMaterial } from '../../Common/ColorPalette'; import { DisposeThreeObj } from '../../Common/Dispose'; -import { MakeMirrorMtx, TransformVector, ZMirrorMatrix, tempMatrix1 } from '../../Common/Matrix4Utils'; +import { MakeMirrorMtx, TransformVector, Vector2ApplyMatrix4, ZMirrorMatrix, tempMatrix1 } from '../../Common/Matrix4Utils'; import { UpdateDraw } from '../../Common/Status'; +import { FixIndex } from '../../Common/Utils'; import { ObjectSnapMode } from '../../Editor/ObjectSnapMode'; import { boardUVGenerator, boardUVGenerator2 } from '../../Geometry/BoardUVGenerator'; import { BufferGeometryUtils } from '../../Geometry/BufferGeometryUtils'; import { EdgesGeometry } from '../../Geometry/EdgeGeometry'; +import { GetArcDrawCount } from '../../Geometry/ExtrudeMeshGeomBuilder/SplitCurveParams'; import { IdentityMtx4, XAxis, XAxisN, YAxis, YAxisN, ZAxis, ZeroVec, equaln, equalv3 } from '../../Geometry/GeUtils'; import { PointShapeUtils } from '../../Geometry/PointShapeUtils'; -import { SweepGeometry } from '../../Geometry/SweepGeometry'; +import { SweepGeometrySimple } from '../../Geometry/SweepGeometry'; import { GetBoardHighSeal, GetBoardSealingCurves, SetBoardTopDownLeftRightSealData } from '../../GraphicsSystem/CalcEdgeSealing'; import { RenderType } from '../../GraphicsSystem/RenderType'; import { VData2Curve, VKnifToolPath } from '../../GraphicsSystem/ToolPath/VKnifToolPath'; +import { Max } from '../../Nest/Common/Util'; import { BoardProcessOption } from "../../UI/Store/OptionInterface/BoardProcessOption"; +import { FuzzyFactory } from '../../csg/core/FuzzyFactory'; import { CSG2Geometry2, Geometry2CSG2 } from '../../csg/core/Geometry2CSG'; import { CylinderHole } from '../3DSolid/CylinderHole'; import { ExtrudeHole } from '../3DSolid/ExtrudeHole'; @@ -45,6 +56,7 @@ import { DragPointType } from './DragPointType'; import { Entity } from './Entity'; import { ExtrudeContourCurve, ExtrudeSolid } from './Extrude'; import { GenLocalUv } from './GenLocalUv'; +import { Line } from './Line'; import { Polyline } from './Polyline'; //排钻配置名是合法的 可用的 @@ -1301,8 +1313,11 @@ export class Board extends ExtrudeSolid } private _2DPathCsgs: Geom3[];//二维刀路的csg数组 - private _2DPathDrawObject: Object3D; - //获取二维刀路的csg数组 + private _2DPathDrawObject: Object3D;//二维刀路提刀线框显示对象 + + /** + * 这个函数生成了二维刀路的csg数组,并且同时生成了_2DPathDrawObject(二维刀路提刀线框显示对象) + */ private Get2DPathCsgs(): Geom3[] { if (this._2DPathCsgs) @@ -1314,63 +1329,240 @@ export class Board extends ExtrudeSolid if (this._2DModelingList.length === 0) return this._2DPathCsgs; + let modelKnifePtsCache: Map = new Map(); + + let tempVec = new Vector3; + for (let vm of this._2DModelingList) { let path = vm.path; + let pathGripsSet = new Set(); + let fuzz = new FuzzyFactory; + + //获取端点部分的csg(使用旋转建模) + const GetGripsCsgs = (pts: Vector3[], geom3s: Geom3[], knifeGeom: Geom2, knifeGeomRadius: number) => + { + for (let pt of pts) + { + let key = fuzz.lookupOrCreate([pt.x, pt.y], pt); + if (!pathGripsSet.has(key)) + { + let rotateGeom = extrudeRotate({ segments: GetArcDrawCount(knifeGeomRadius), angle: 2 * Math.PI }, knifeGeom); + rotateGeom = retessellate(rotateGeom); + // TestDrawGeom3s([rotateGeom]); + let mat = new Matrix4().setPosition(pt); + rotateGeom = transform(mat.elements as Mat4, rotateGeom); + geom3s.push(rotateGeom); + pathGripsSet.add(key); + } + } + }; + for (let item of vm.items) { let tempPath = this.GetOffsetPath(path, item); if (tempPath) { let sweepContour = this._KnifePolylineMap.get(item.knife.id); - if (true || !sweepContour)//如果没有轮廓的时候,我们还是依然绘制线段 (因为现在我们没有线框生成 所以强制true) { if (!this._2DPathDrawObject) this._2DPathDrawObject = new Object3D; - let curves = VData2Curve(VKnifToolPath(tempPath, item.depth, item.knife.angle / 2)); - let o = new Object3D(); + let curves = VData2Curve(VKnifToolPath(tempPath, item.depth, item.knife.angle / 2));//走刀+提刀曲线 + let pathObject = new Object3D();//走刀路径绘制线 for (let c of curves) { c.ColorIndex = tempPath.ColorIndex; - o.add(c.GetDrawObjectFromRenderType(RenderType.Wireframe)); + pathObject.add(c.GetDrawObjectFromRenderType(RenderType.Wireframe)); } if (vm.dir === FaceDirection.Back) { - o.applyMatrix4(ZMirrorMatrix); - o.position.z = item.depth; + pathObject.applyMatrix4(ZMirrorMatrix); + pathObject.position.z = item.depth; } else - o.position.z = this.thickness - item.depth; - o.updateMatrix(); - this._2DPathDrawObject.add(o); + pathObject.position.z = this.thickness - item.depth; + pathObject.updateMatrix(); + this._2DPathDrawObject.add(pathObject); + } + if (!sweepContour) continue; + + let geom3s: Geom3[] = []; + + //刀截面轮廓Pts + let knifeGeomPts: Vec2[] = modelKnifePtsCache.get(item.knife.id); + + if (!knifeGeomPts?.length) + { + + let shapePts = sweepContour.Shape.getPoints(); + for (let p of shapePts) + Vector2ApplyMatrix4(sweepContour.OCSNoClone, p); + + knifeGeomPts = shapePts.map(p => [p.x, p.y]); - // continue; + if (sweepContour.IsClockWise) knifeGeomPts.reverse(); } - if (!sweepContour) continue; + //刀截面geom + let knifeGeom = geom2.fromPoints(knifeGeomPts); + let knifeGeomRadius = sweepContour.BoundingBox.getSize(tempVec).y / 2; - const geometry = new SweepGeometry(sweepContour, tempPath); - let csg = Geometry2CSG2(geometry); - if (vm.dir === FaceDirection.Front) + //分解路径 + let cus = tempPath.Explode(); + + if (item.knife.angle) { - let mtx = new Matrix4().setPosition(0, 0, this.thickness - item.depth); + let sweepGeom = new SweepGeometrySimple(sweepContour, cus); + + //这里我们对盖子进行精简 + let lidPolys: poly3.Poly3[] = []; + + let fuzz = new FuzzyFactory; + let lidVerts = sweepGeom.shapePts2d.map((p, i) => + { + fuzz.lookupOrCreate([p.x, p.y], i); + return [p.x, p.y, 0] as Vec3; + }); + + for (let face of sweepGeom.TriFaces) + lidPolys.push(poly3.create(face.map(index => lidVerts[index]))); + + let lidGeom3 = retessellate(geom3.create(lidPolys)); + let lidPolygons = lidGeom3.polygons.map(p => p.vertices.map(v => fuzz.lookupOrCreate([v[0], v[1]], -1))); + //精简结束 + + //遍历每个水管 + for (let i = 0; i < sweepGeom.SidePolygons.length; i++) + { + let polygons = sweepGeom.SidePolygons[i]; + let polys: poly3.Poly3[] = []; + for (let polygon of polygons) + { + let pts = polygon.map(i => sweepGeom.vertices[i]);//这是水管盖子的点 + + //水管前进的方向 + let dir = polygon["dir"] as Vector3; + + //如果水管的侧面自交了,我们选择性的删掉一个无用面 + let dir1 = tempVec.subVectors(pts[3], pts[0]).normalize(); + if (!equalv3(dir, dir1))//如果反向1 + { + let l1 = new Line(pts[0], pts[1]); + let l2 = new Line(pts[2], pts[3]); + let p = l1.IntersectWith(l2, 0)[0]; + + if (!p) continue;//弃用了这个多边形 + + // polys.push(poly3.create([vertices[3], p.toArray() as Vec3, vertices[0]]));//弃用了无效的自交三角形 + polys.push(poly3.create([p.toArray() as Vec3, pts[1].toArray() as Vec3, pts[2].toArray() as Vec3])); + continue; + } + + let dir2 = tempVec.subVectors(pts[2], pts[1]).normalize(); + if (!equalv3(dir, dir2))//如果反向2 + { + let l1 = new Line(pts[0], pts[1]); + let l2 = new Line(pts[2], pts[3]); + let p = l1.IntersectWith(l2, 0)[0]; + + if (!p) continue;//弃用了这个多边形 + + polys.push(poly3.create([pts[0].toArray() as Vec3, p.toArray() as Vec3, pts[3].toArray() as Vec3])); + // polys.push(poly3.create([p.toArray() as Vec3, vertices[2], vertices[1]]));//弃用了无效的自交三角形 + continue; + } + + polys.push(poly3.create(pts.map(p => p.toArray() as Vec3)));//如果没有反向,那么我们直接返回多边形 + } + + //构建水管的盖子 + let curVerts = sweepGeom.shapeVerts[FixIndex(i, sweepGeom.shapeVerts)]; + let nextVerts = sweepGeom.shapeVerts[FixIndex(i + 1, sweepGeom.shapeVerts)]; + let lid1: poly3.Poly3[] = []; + let lid2: poly3.Poly3[] = []; + for (let lidPolygon of lidPolygons)//我们使用已经精简过的盖子索引 + { + lid1.push(poly3.create(lidPolygon.map(index => curVerts[index].toArray() as Vec3))); + lid2.push(poly3.create(lidPolygon.reverse().map(index => nextVerts[index].toArray() as Vec3))); + lidPolygon.reverse();//这里偷懒了 + } - // let edge = geometry.EdgeGeom.applyMatrix4(mtx); - // this._2DPathDrawObject.add(new LineSegments(edge, ColorMaterial.GetLineMaterial(this.ColorIndex))); + let lidGeom1 = geom3.create(lid1); + let lidGeom2 = geom3.create(lid2); - csg.transforms = mtx.toArray() as Mat4; + let intGeom = intersect(lidGeom1, lidGeom2); + if (intGeom.polygons.length) + polys.push(...intGeom.polygons); + else + polys.push(...lidGeom1.polygons, ...lidGeom2.polygons); + + geom3s.push(geom3.create(polys)); + } + + //提刀轮廓 未闭合路径增加圆角 端点部分 + if (!tempPath.IsClose) + GetGripsCsgs([tempPath.StartPoint, tempPath.EndPoint], geom3s, knifeGeom, knifeGeomRadius); } else { - let mtx = MakeMirrorMtx(ZAxis, new Vector3(0, 0, item.depth * 0.5)); + let mt4Z = new Matrix4().makeRotationZ(Math.PI / 2); + let mt4Y = new Matrix4().makeRotationY(Math.PI / 2); + + for (let cu of cus) + { + if (cu instanceof Line) + { + //直线部分 + //旋转至 X为正方向 + let lineShape = extrudeLinear({ height: cu.Length }, knifeGeom); + lineShape = retessellate(lineShape); + lineShape = transform(mt4Z.elements as Mat4, lineShape); + lineShape = transform(mt4Y.elements as Mat4, lineShape); + //平移到原点为中心 + let mtx = new Matrix4().setPosition((new Vector3(-cu.Length / 2, 0, 0))); + lineShape = transform(mtx.elements as Mat4, lineShape); + //旋转直线正确方向 + let pt = cu.StartPoint.sub(cu.EndPoint); + mtx = new Matrix4().makeRotationZ(Math.atan2(pt.y, pt.x)); + lineShape = transform(mtx.elements as Mat4, lineShape); + //平移到线段中点 + mtx = new Matrix4().setPosition(cu.Midpoint); + lineShape = transform(mtx.elements as Mat4, lineShape); + geom3s.push(lineShape); + + //端点部分 + GetGripsCsgs([cu.StartPoint, cu.EndPoint], geom3s, knifeGeom, knifeGeomRadius); + } + else if (cu instanceof Arc) + { + //弧线部分 + //获取针对弧形扫掠Geom + let mt4 = new Matrix4().setPosition(cu.Radius, 0, 0); + let arcKnifeGeom = geom2.transform(mt4.elements as Mat4, knifeGeom); + let rotateGeom = extrudeRotate({ segments: GetArcDrawCount(cu.Radius), angle: cu.AllAngle }, arcKnifeGeom); + rotateGeom = retessellate(rotateGeom); + //放置Geom + let rotateMat = new Matrix4().makeRotationZ(cu.IsClockWise ? cu.EndAngle : cu.StartAngle).setPosition(cu.Position); + rotateGeom = transform(rotateMat.elements as Mat4, rotateGeom); + geom3s.push(rotateGeom); + //端点部分 + GetGripsCsgs([cu.StartPoint, cu.EndPoint], geom3s, knifeGeom, knifeGeomRadius); + } + } + } - // let edge = geometry.EdgeGeom.applyMatrix4(mtx); - // this._2DPathDrawObject.add(new LineSegments(edge, ColorMaterial.GetLineMaterial(this.ColorIndex))); + let mtx: Matrix4; + if (vm.dir === FaceDirection.Front) + mtx = new Matrix4().setPosition(0, 0, this.thickness - item.depth); + else + mtx = MakeMirrorMtx(ZAxis, new Vector3(0, 0, item.depth * 0.5)); - csg.transforms = mtx.toArray() as Mat4; + for (let geom of geom3s) + { + geom = transform(mtx.elements as Mat4, geom); + this._2DPathCsgs.push(geom); } - this._2DPathCsgs.push(csg); } } } @@ -1387,19 +1579,89 @@ export class Board extends ExtrudeSolid override UpdateMeshGeom(geo: Geometry): BufferGeometry { let path2dCsgs = this.Get2DPathCsgs(); - if (path2dCsgs.length === 0 || !HostApplicationServices.show2DPathObject) + if (!path2dCsgs.length || !HostApplicationServices.show2DPathObject) { if (geo instanceof Geometry) return new BufferGeometry().fromGeometry(geo); return geo; } + // TestDrawGeom3s(path2dCsgs); + + let gemUnion = path2dCsgs[0]; + for (let i = 1; i < path2dCsgs.length; i++) + gemUnion = union(gemUnion, path2dCsgs[i]); - let geomSub = union(path2dCsgs); let geom = Geometry2CSG2(geo); - let newGeom = subtract(geom, geomSub); - this._EdgeGeometry = new EdgesGeometry().FromCSG(newGeom as unknown as Geom3Res); + let newGeom = subtract(geom, gemUnion); + + //删除小面积(只留一个) + { + let fuzz = new FuzzyFactory; + let vmap = new Map; + for (let poly of newGeom.polygons) + { + for (let v of poly.vertices) + { + let key = fuzz.lookupOrCreate(v, v); + let arr = vmap.get(key); + if (!arr) + { + arr = []; + vmap.set(key, arr); + } + arr.push(poly); + v["__key__"] = key; + } + } + + let polys = newGeom.polygons.concat(); + let polyGroups = []; + + let calcs = new Set; + while (polys.length) + { + let poly1 = polys.pop(); + calcs.add(poly1); + let polyGroup = [poly1]; + polyGroups.push(polyGroup); + + for (let i = 0; i < polyGroup.length; i++) + { + let poly = polyGroup[i]; + + for (let v of poly.vertices) + { + let key = v["__key__"]; + let arr = vmap.get(key); + + for (let vpoly of arr) + { + if (calcs.has(vpoly)) continue; + + calcs.add(vpoly); + + polyGroup.push(vpoly); + } + } + } + // arrayRemoveIf(polys, poly => !calcs.has(poly)); //加上这个无法提高性能 + } + + let areas = polyGroups.map(polys => + { + let area = 0; + for (let poly of polys) + area += measureArea(poly); + }); + + let maxIndex = Max(areas, (t1, t2) => t2 > t1); + + newGeom.polygons = polyGroups[maxIndex]; + } + + this._EdgeGeometry = new EdgesGeometry().FromCSG(newGeom as unknown as Geom3Res); const geometry = CSG2Geometry2(newGeom); const bufferGeometry = new BufferGeometry().fromGeometry(geometry); diff --git a/src/Geometry/ExtrudeMeshGeomBuilder/SplitCurveParams.ts b/src/Geometry/ExtrudeMeshGeomBuilder/SplitCurveParams.ts index 45ab85356..4f61da4dd 100644 --- a/src/Geometry/ExtrudeMeshGeomBuilder/SplitCurveParams.ts +++ b/src/Geometry/ExtrudeMeshGeomBuilder/SplitCurveParams.ts @@ -29,12 +29,13 @@ export const ARC_DRAW_CONFIG = { */ //del_exp_end -export function GetArcDrawCount(arc: Arc | Circle): number +export function GetArcDrawCount(arc: Arc | Circle | number): number { - let splitCount = arc.Radius / ARC_DRAW_CONFIG.ARC_SplitLength; + let radius = typeof arc === "number" ? arc : arc.Radius; + let splitCount = radius / ARC_DRAW_CONFIG.ARC_SplitLength; //保证是偶数(避免奇数和Shape2计算方式一致导致的干涉) splitCount = clamp(Math.floor(splitCount * 0.5) * 2, ARC_DRAW_CONFIG.Arc_MinSplitCount, ARC_DRAW_CONFIG.ARC_MaxSplitCount); - if (arc.Radius > ARC_DRAW_CONFIG.ARC_RADIUS_MIN) + if (radius > ARC_DRAW_CONFIG.ARC_RADIUS_MIN) splitCount = Math.max(36, splitCount); return splitCount; } diff --git a/src/Geometry/SweepGeometry.ts b/src/Geometry/SweepGeometry.ts index fa253b31c..c9a0efe31 100644 --- a/src/Geometry/SweepGeometry.ts +++ b/src/Geometry/SweepGeometry.ts @@ -14,6 +14,7 @@ import { PlaneExt } from "./Plane"; export class SweepGeometry extends Geometry { edgePts: number[] = []; + polygonIndexes = []; ShapeMaterialSlotData: number[];//[0,0,0,1,2,3,0] 指定多段线轮廓的材质槽索引 每个顶点指定一个材质槽位置 constructor(contour: Polyline, path: Curve[] | Curve, ShapeMaterialSlotData?: number[]) { @@ -167,7 +168,30 @@ export class SweepGeometry extends Geometry if (!isClosePath) this.BuildLid(shapePts2d, verts); } - private BuildSideFaces(shapePts2d: Vector2[], pathPts2d: Vector2[], pathPts: Vector3[], verts: Vector3[][]) + /** + * 使用4点构建面 + * @param a 左下 + * @param b 右下 + * @param c 左上 + * @param d 右上 + * @param uvs + * @param [materialIndex] + */ + protected BuildFace4(a: number, b: number, c: number, d: number, uvs: Vector2[], materialIndex?: number) + { + let f1 = new Face3(a, b, c, undefined, undefined, materialIndex); + let f2 = new Face3(b, d, c, undefined, undefined, materialIndex); + this.faces.push(f1, f2); + this.faceVertexUvs[0].push([uvs[0].clone(), uvs[1].clone(), uvs[2].clone()], [uvs[1].clone(), uvs[3].clone(), uvs[2].clone()]); + } + + /** + * 构造边缘面开始标记 + * @param dir 前进方向(单位向量) + */ + protected SideStartMark(dir: Vector3) { } + + protected BuildSideFaces(shapePts2d: Vector2[], pathPts2d: Vector2[], pathPts: Vector3[], verts: Vector3[][]) { let addCount = 0; //补充个数 shapePts2d[0]["_mask_"] = true; @@ -181,13 +205,6 @@ export class SweepGeometry extends Geometry } let sumCount = addCount + shapePts2d.length; //实际个数 - const f4 = (a: number, b: number, c: number, d: number, uvs: Vector2[], materialIndex?: number) => - { - let f1 = new Face3(a, b, c, undefined, undefined, materialIndex); - let f2 = new Face3(b, d, c, undefined, undefined, materialIndex); - this.faces.push(f1, f2); - this.faceVertexUvs[0].push([uvs[0].clone(), uvs[1].clone(), uvs[2].clone()], [uvs[1].clone(), uvs[3].clone(), uvs[2].clone()]); - }; let vs: number[] = [0]; //vs 对应 y轴 for (let i = 1; i < shapePts2d.length; i++) vs.push((vs[i - 1] + shapePts2d[i].distanceTo(shapePts2d[i - 1]) * 1e-3)); @@ -203,6 +220,7 @@ export class SweepGeometry extends Geometry let p1 = pathPts[pathIndex]; let p2 = pathPts[FixIndex(pathIndex + 1, pathPts.length)]; let p1Dir = p2.clone().sub(p1).normalize(); + this.SideStartMark(p1Dir); let tempStartX = 0; @@ -247,7 +265,8 @@ export class SweepGeometry extends Geometry new Vector2(v1, x3), new Vector2(v2, x4), ]; - f4(curIndex, nextIndex, curIndex2, nextIndex2, uvs, lastMaterialIndex); + + this.BuildFace4(curIndex, nextIndex, curIndex2, nextIndex2, uvs, lastMaterialIndex); } this.vertices.push(p1); } @@ -270,7 +289,7 @@ export class SweepGeometry extends Geometry } } - private BuildLid(shapePts2d: Vector2[], verts: Vector3[][]) + protected BuildLid(shapePts2d: Vector2[], verts: Vector3[][]) { //轮廓三角网格索引 let faces = ShapeUtils.triangulateShape(shapePts2d, []); @@ -321,13 +340,13 @@ export function ProjectionToPlane(contourPts: Vector3[], normal: Vector3, curP: let pts: Vector3[]; if (!preP && nextP) { - let mat = ContourTransfromToPath(curP, normal, nextP.clone().sub(curP)); - pts = contourPts.map(p => p.clone().applyMatrix4(mat)); + let mtx = ContourTransfromToPath(curP, normal, nextP.clone().sub(curP)); + pts = contourPts.map(p => p.clone().applyMatrix4(mtx)); } else if (!nextP && preP) { - let mat = ContourTransfromToPath(curP, normal, curP.clone().sub(preP)); - pts = contourPts.map(p => p.clone().applyMatrix4(mat)); + let mtx = ContourTransfromToPath(curP, normal, curP.clone().sub(preP)); + pts = contourPts.map(p => p.clone().applyMatrix4(mtx)); } else if (nextP && preP) { @@ -344,8 +363,8 @@ export function ProjectionToPlane(contourPts: Vector3[], normal: Vector3, curP: //角平分线的平面 let plane = new PlaneExt(norm, curP); - let mat = ContourTransfromToPath(preP, normal, dir); - pts = contourPts.map(p => p.clone().applyMatrix4(mat)); + let mtx = ContourTransfromToPath(preP, normal, dir); + pts = contourPts.map(p => p.clone().applyMatrix4(mtx)); pts = pts.map(p => plane.intersectLine(new Line3(p, p.clone().add(dir)), new Vector3(), true)); } return pts; @@ -370,3 +389,54 @@ function ContourTransfromToPath(pt: Vector3, norm: Vector3, dir: Vector3): Matri mat.setPosition(pt); return mat; } + +//用索引来定义 +type Polygon = number[]; + +export class SweepGeometrySimple extends SweepGeometry +{ + SidePolygons: (Polygon[])[];//所有侧面的多边形 + private _curSidePolygons: Polygon[];// + private _curDir: Vector3; + + TriFaces: (number[])[];//截面的三角面索引 + shapeVerts: Vector3[][];//所有的截面点在节点位置 + shapePts2d: Vector2[]; + override computeVertexNormals() { } + override computeFaceNormals() { } + + override BuildFace4(a: number, b: number, c: number, d: number, uvs: Vector2[], materialIndex?: number) + { + let polygon = [a, b, d, c]; + polygon["dir"] = this._curDir; + this._curSidePolygons.push(polygon); + } + + protected override SideStartMark(dir: Vector3) + { + this._curDir = dir; + if (this._curSidePolygons?.length) + this.SidePolygons.push(this._curSidePolygons); + + this._curSidePolygons = []; + } + + protected override BuildSideFaces(shapePts2d: Vector2[], pathPts2d: Vector2[], pathPts: Vector3[], verts: Vector3[][]) + { + this.shapeVerts = verts; + this.shapePts2d = shapePts2d; + pathPts2d[0]["_mask_"] = true; + + if (!this.TriFaces) + this.TriFaces = ShapeUtils.triangulateShape(shapePts2d, []); + + + if (!this.SidePolygons) this.SidePolygons = []; + super.BuildSideFaces(shapePts2d, pathPts2d, pathPts, verts); + + if (this._curSidePolygons?.length) + this.SidePolygons.push(this._curSidePolygons); + } + + protected override BuildLid(shapePts2d: Vector2[], verts: Vector3[][]) { } +} diff --git a/src/UI/Components/RightPanel/Modeling/KnifeList.tsx b/src/UI/Components/RightPanel/Modeling/KnifeList.tsx index 74d93579e..20e726c57 100644 --- a/src/UI/Components/RightPanel/Modeling/KnifeList.tsx +++ b/src/UI/Components/RightPanel/Modeling/KnifeList.tsx @@ -1,7 +1,8 @@ import { Button, ContextMenu, Intent, Menu, MenuItem } from '@blueprintjs/core'; -import { IObservableValue, observable } from 'mobx'; +import { IObservableValue, observable, toJS } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { Vector3 } from 'three'; import { app } from '../../../../ApplicationServices/Application'; import { DataAdapter } from '../../../../Common/DataAdapter'; import { ToplineUrls } from '../../../../Common/HostUrl'; @@ -143,6 +144,9 @@ export class KnifeList extends React.Component @observable private currentInfo = { id: "", name: "", - props: { radius: 3, angle: 45 } + props: { radius: 3, angle: 0 } }; constructor(props) { @@ -191,6 +191,8 @@ export class KnifeManage extends Component pl.ApplyMatrix(MoveMatrix(ptRes.Point.negate())) .ApplyMatrix(new Matrix4().extractRotation(pl.OCSInv)); + this.currentInfo.props.radius = parseFloat((pl.BoundingBox.getSize(new Vector3).x / 2).toFixed(2)); + let vf = new CADFiler(); pl.WriteFile(vf); pl.Erase(); @@ -218,13 +220,14 @@ export class KnifeManage extends Component logo, file: deflate(fileJson), zip_type: "gzip", + props: JSON.stringify(toJS(this.currentInfo.props)) }); } if (data.err_code === RequestStatus.Ok) { AppToaster.show({ - message: "刀具创建成功,默认半径3,夹角45°", + message: `刀具创建成功,半径${this.currentInfo.props.radius},默认夹角${this.currentInfo.props.angle}°`, timeout: 3000, intent: Intent.SUCCESS });