mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-08 01:13:27 +01:00
introducing sketch model / change reading 2d geometry / change extruding API
This commit is contained in:
parent
721b2fc137
commit
84de8be661
20 changed files with 827 additions and 501 deletions
|
|
@ -225,96 +225,6 @@ export function fixCCW(path, normal) {
|
|||
|
||||
export const isPointInsidePolygon = math.isPointInsidePolygon;
|
||||
|
||||
export function sketchToPolygons(geom) {
|
||||
|
||||
var dict = HashTable.forVector2d();
|
||||
var edges = HashTable.forDoubleArray();
|
||||
|
||||
var lines = geom.connections;
|
||||
|
||||
function edgeKey(a, b) {
|
||||
return [a.x, a.y, b.x, b.y];
|
||||
}
|
||||
|
||||
var size = 0;
|
||||
var points = [];
|
||||
function memDir(a, b) {
|
||||
var dirs = dict.get(a);
|
||||
if (dirs === null) {
|
||||
dirs = [];
|
||||
dict.put(a, dirs);
|
||||
points.push(a);
|
||||
}
|
||||
dirs.push(b);
|
||||
}
|
||||
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var a = lines[i].a;
|
||||
var b = lines[i].b;
|
||||
|
||||
memDir(a, b);
|
||||
memDir(b, a);
|
||||
edges.put(edgeKey(a, b), lines[i]);
|
||||
}
|
||||
|
||||
var graph = {
|
||||
|
||||
connections : function(e) {
|
||||
var dirs = dict.get(e);
|
||||
return dirs === null ? [] : dirs;
|
||||
},
|
||||
|
||||
at : function(index) {
|
||||
return points[index];
|
||||
},
|
||||
|
||||
size : function() {
|
||||
return points.length;
|
||||
}
|
||||
};
|
||||
|
||||
var loops = Graph.findAllLoops(graph, dict.hashCodeF, dict.equalsF);
|
||||
var polygons = [];
|
||||
var li, loop, polyPoints;
|
||||
for (li = 0; li < loops.length; ++li) {
|
||||
loop = loops[li];
|
||||
if (!isCCW(loop)) loop.reverse();
|
||||
polyPoints = [];
|
||||
for (var pi = 0; pi < loop.length; ++pi) {
|
||||
var point = loop[pi];
|
||||
var next = loop[(pi + 1) % loop.length];
|
||||
|
||||
var edge = edges.get(edgeKey(point, next));
|
||||
if (edge === null) {
|
||||
edge = edges.get(edgeKey(next, point));
|
||||
}
|
||||
polyPoints.push(point);
|
||||
point.sketchConnectionObject = edge.sketchObject;
|
||||
}
|
||||
if (polyPoints.length >= 3) {
|
||||
polygons.push(polyPoints);
|
||||
} else {
|
||||
console.warn("Points count < 3!");
|
||||
}
|
||||
}
|
||||
for (li = 0; li < geom.loops.length; ++li) {
|
||||
loop = geom.loops[li];
|
||||
polyPoints = loop.slice(0);
|
||||
for (var si = 0; si < polyPoints.length; si++) {
|
||||
var conn = polyPoints[si];
|
||||
//reuse a point and ignore b point since it's a guaranteed loop
|
||||
conn.a.sketchConnectionObject = conn.sketchObject;
|
||||
polyPoints[si] = conn.a;
|
||||
}
|
||||
// we assume that connection object is the same al other the loop. That's why reverse is safe.
|
||||
if (!isCCW(polyPoints)) polyPoints.reverse();
|
||||
if (polyPoints.length >= 3) {
|
||||
polygons.push(polyPoints);
|
||||
}
|
||||
}
|
||||
return polygons;
|
||||
}
|
||||
|
||||
export function someBasis2(normal) {
|
||||
var x = normal.cross(normal.randomNonParallelVector());
|
||||
var y = normal.cross(x).unit();
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
import {Matrix3, BasisForPlane, ORIGIN} from '../../../math/l3space'
|
||||
import * as math from '../../../math/math'
|
||||
import Vector from '../../../math/vector'
|
||||
import {Extruder} from '../../../brep/brep-builder'
|
||||
import {enclose, iterateSegments} from '../../../brep/brep-builder'
|
||||
import {BREPValidator} from '../../../brep/brep-validator'
|
||||
import * as stitching from '../../../brep/stitching'
|
||||
import {subtract, union} from '../../../brep/operations/boolean'
|
||||
import {Loop} from '../../../brep/topo/loop'
|
||||
import {Shell} from '../../../brep/topo/shell'
|
||||
import {ReadSketchFromFace} from './sketch-reader'
|
||||
import {CompositeCurve} from '../../../brep/geom/curve'
|
||||
import {ReadSketchContoursFromFace} from '../sketch/sketch-reader'
|
||||
import {Segment} from '../sketch/sketch-model'
|
||||
import {isCurveClass} from '../../cad-utils'
|
||||
|
||||
import {BREPSceneSolid} from '../../scene/brep-scene-object'
|
||||
|
|
@ -23,19 +25,11 @@ export function Cut(app, params) {
|
|||
export function doOperation(app, params, cut) {
|
||||
const face = app.findFace(params.face);
|
||||
const solid = face.solid;
|
||||
let reverseNormal = !cut;
|
||||
|
||||
let normal = face.normal();
|
||||
if (params.value < 0) {
|
||||
params = fixNegativeValue(params);
|
||||
reverseNormal = !reverseNormal;
|
||||
}
|
||||
|
||||
if (reverseNormal) normal = normal.negate();
|
||||
const sketch = ReadSketchFromFace(app, face, reverseNormal);
|
||||
const sketch = ReadSketchContoursFromFace(app, face);
|
||||
|
||||
const extruder = new ParametricExtruder(params);
|
||||
const operand = combineShells(sketch.map(s => extruder.extrude(s, normal)));
|
||||
const details = getEncloseDetails(params, sketch, face.brepFace.surface, !cut);
|
||||
const operand = combineShells(details.map(d => enclose(d.basePath, d.lidPath, d.baseSurface, d.lidSurface, wallJoiner)));
|
||||
BREPValidator.validateToConsole(operand);
|
||||
|
||||
let result;
|
||||
|
|
@ -59,14 +53,6 @@ export function doOperation(app, params, cut) {
|
|||
}
|
||||
}
|
||||
|
||||
export function fixNegativeValue(params) {
|
||||
if (params.value < 0) {
|
||||
params = Object.assign({}, params);
|
||||
params.value *= -1;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
function combineShells(shells) {
|
||||
if (shells.length == 1) {
|
||||
return shells[0];
|
||||
|
|
@ -76,47 +62,61 @@ function combineShells(shells) {
|
|||
return cutter;
|
||||
}
|
||||
|
||||
export class ParametricExtruder extends Extruder {
|
||||
|
||||
constructor(params) {
|
||||
super();
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
prepareLidCalculation(baseNormal, lidNormal) {
|
||||
let target;
|
||||
this.basis = BasisForPlane(baseNormal);
|
||||
if (this.params.rotation != 0) {
|
||||
target = Matrix3.rotateMatrix(this.params.rotation * Math.PI / 180, this.basis[0], ORIGIN).apply(lidNormal);
|
||||
if (this.params.angle != 0) {
|
||||
target = Matrix3.rotateMatrix(this.params.angle * Math.PI / 180, this.basis[2], ORIGIN)._apply(target);
|
||||
}
|
||||
target._multiply(Math.abs(this.params.value));
|
||||
} else {
|
||||
target = lidNormal.multiply(Math.abs(this.params.value));
|
||||
}
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
calculateLid(basePoints, baseNormal, lidNormal) {
|
||||
if (this.params.prism != 1) {
|
||||
const scale = this.params.prism;
|
||||
|
||||
const _3Dtr = new Matrix3().setBasis(this.basis);
|
||||
const _2Dtr = _3Dtr.invert();
|
||||
const poly2d = basePoints.map(p => _2Dtr.apply(p));
|
||||
basePoints = math.polygonOffset(poly2d, scale).map(p => _3Dtr.apply(p));
|
||||
}
|
||||
return basePoints.map(p => p.plus(this.target));
|
||||
}
|
||||
|
||||
onWallCallback(wallFace, baseHalfEdge) {
|
||||
const conn = baseHalfEdge.vertexA.point.sketchConnectionObject;
|
||||
if (conn && isCurveClass(conn._class)) {
|
||||
if (!conn.stitchedSurface) {
|
||||
conn.stitchedSurface = new stitching.StitchedSurface();
|
||||
}
|
||||
conn.stitchedSurface.addFace(wallFace);
|
||||
export function wallJoiner(wallFace, group) {
|
||||
if (group && group.constructor.name != 'Segment') {
|
||||
if (!group.stitchedSurface) {
|
||||
group.stitchedSurface = new stitching.StitchedSurface();
|
||||
}
|
||||
group.stitchedSurface.addFace(wallFace);
|
||||
}
|
||||
}
|
||||
|
||||
export function getEncloseDetails(params, contours, sketchSurface, invert) {
|
||||
let value = params.value;
|
||||
if (value < 0) {
|
||||
value = Math.abs(value);
|
||||
invert = !invert;
|
||||
}
|
||||
|
||||
const baseSurface = invert ? sketchSurface.invert() : sketchSurface;
|
||||
|
||||
let target;
|
||||
const targetDir = baseSurface.normal.negate();
|
||||
|
||||
if (params.rotation != 0) {
|
||||
target = Matrix3.rotateMatrix(params.rotation * Math.PI / 180, this.basis[0], ORIGIN).apply(targetDir);
|
||||
if (params.angle != 0) {
|
||||
target = Matrix3.rotateMatrix(params.angle * Math.PI / 180, this.basis[2], ORIGIN)._apply(target);
|
||||
}
|
||||
target._multiply(value);
|
||||
} else {
|
||||
target = targetDir.multiply(value);
|
||||
}
|
||||
|
||||
let details = [];
|
||||
for (let contour of contours) {
|
||||
if (invert) {
|
||||
contour.reverse();
|
||||
}
|
||||
const basePath = contour.transferOnSurface(sketchSurface);
|
||||
const lidPath = new CompositeCurve();
|
||||
|
||||
let lidPoints = basePath.points;
|
||||
if (!math.equal(params.prism, 1)) {
|
||||
const _3D = sketchSurface.get3DTransformation();
|
||||
const _2D = _3D.invert();
|
||||
lidPoints = math.polygonOffset(lidPoints.map(p => _2D.apply(p)) , params.prism).map(p => _3D._apply(p));
|
||||
}
|
||||
|
||||
for (let i = 0; i < basePath.points.length; ++i) {
|
||||
const curve = basePath.curves[i];
|
||||
const point = lidPoints[i];
|
||||
const group = basePath.groups[i];
|
||||
lidPath.add(curve.translate(target), point.plus(target), group);
|
||||
}
|
||||
|
||||
const lidSurface = baseSurface.translate(target).invert();
|
||||
details.push({basePath, lidPath, baseSurface, lidSurface});
|
||||
}
|
||||
return details;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
import {sortPolygons, getSketchedPolygons3D} from '../mesh/workbench'
|
||||
|
||||
|
||||
|
||||
// here will be function of conversion sketch objects to brep DS
|
||||
|
||||
export function ReadSketchFromFace(app, face, reverseGeom) {
|
||||
return getSketchedPolygons3D(app, face, reverseGeom);
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
import {CURRENT_SELECTION as S} from './wizard'
|
||||
import {PreviewWizard, SketchBasedPreviewer} from './preview-wizard'
|
||||
import {ParametricExtruder, fixNegativeValue} from '../cut-extrude'
|
||||
import {getEncloseDetails} from '../cut-extrude'
|
||||
import {TriangulatePolygons} from '../../../triangulation'
|
||||
import Vector from '../../../../math/vector'
|
||||
import {reversedIndex} from '../../../../utils/utils'
|
||||
|
||||
|
||||
const METADATA = [
|
||||
['value' , 'number', 50],
|
||||
|
|
@ -50,31 +52,23 @@ export class ExtrudePreviewer extends SketchBasedPreviewer {
|
|||
}
|
||||
|
||||
createImpl(app, params, sketch, face) {
|
||||
const normal = face.normal();
|
||||
let reverseNormal = this.inversed;
|
||||
if (params.value < 0) {
|
||||
params = fixNegativeValue(params);
|
||||
reverseNormal = !reverseNormal;
|
||||
}
|
||||
const parametricExtruder = new ParametricExtruder(params);
|
||||
const baseNormal = reverseNormal ? normal : normal.negate();
|
||||
const lidNormal = reverseNormal ? baseNormal.negate() : normal;
|
||||
|
||||
parametricExtruder.prepareLidCalculation(baseNormal, lidNormal);
|
||||
|
||||
const triangles = [];
|
||||
for (let base of sketch) {
|
||||
var lid = parametricExtruder.calculateLid(base);
|
||||
const encloseDetails = getEncloseDetails(params, sketch, face.brepFace.surface, !this.inversed);
|
||||
const triangles = [];
|
||||
for (let d of encloseDetails) {
|
||||
const base = d.basePath.points;
|
||||
const lid = d.lidPath.points;
|
||||
const n = base.length;
|
||||
for (let p = n - 1, q = 0; q < n; p = q ++) {
|
||||
triangles.push([ base[p], base[q], lid[q] ]);
|
||||
triangles.push([ lid[q], lid[p], base[p] ]);
|
||||
}
|
||||
TriangulatePolygons([base], baseNormal, (v) => v.toArray(), (arr) => new Vector().set3(arr))
|
||||
.forEach(tr => triangles.push(tr));
|
||||
|
||||
TriangulatePolygons([lid], lidNormal, (v) => v.toArray(), (arr) => new Vector().set3(arr))
|
||||
.forEach(tr => triangles.push(tr));
|
||||
function collectOnSurface(points, normal) {
|
||||
TriangulatePolygons([points], normal, (v) => v.toArray(), (arr) => new Vector().set3(arr))
|
||||
.forEach(tr => triangles.push(tr));
|
||||
}
|
||||
collectOnSurface(base, d.baseSurface.normal);
|
||||
collectOnSurface(lid, d.lidSurface.normal);
|
||||
}
|
||||
return triangles;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import {Wizard} from './wizard'
|
||||
import {ReadSketchFromFace} from '../sketch-reader'
|
||||
import {ReadSketchContoursFromFace} from '../../sketch/sketch-reader'
|
||||
import {Loop} from '../../../../brep/topo/loop'
|
||||
|
||||
export class PreviewWizard extends Wizard {
|
||||
|
|
@ -77,7 +77,7 @@ export class SketchBasedPreviewer {
|
|||
if (!face) return null;
|
||||
const needSketchRead = !this.sketch || params.face != this.face;
|
||||
if (needSketchRead) {
|
||||
this.sketch = ReadSketchFromFace(app, face);
|
||||
this.sketch = ReadSketchContoursFromFace(app, face, false);
|
||||
//for (let polygon of this.sketch) {
|
||||
//if (!Loop.isPolygonCCWOnSurface(polygon, face.brepFace.surface) && this.fixToCCW) {
|
||||
// polygon.reverse();
|
||||
|
|
|
|||
|
|
@ -9,197 +9,6 @@ import {LoadSTLFromURL} from '../../io'
|
|||
import revolve from './revolve'
|
||||
import {Triangulate} from '../../triangulation'
|
||||
|
||||
function SketchConnection(a, b, sketchObject) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.sketchObject = sketchObject;
|
||||
}
|
||||
|
||||
export function readSketchGeom(sketch, faceId, readConstructionSegments) {
|
||||
let idCounter = 0;
|
||||
function createData(obj) {
|
||||
return {_class : obj._class, id : faceId + ":" + (idCounter ++)}
|
||||
}
|
||||
|
||||
const RESOLUTION = 20;
|
||||
const out = {connections : [], loops : [], constructionSegments: []};
|
||||
if (sketch.layers !== undefined) {
|
||||
for (let layer of sketch.layers) {
|
||||
const isConstructionLayer = layer.name == "_construction_";
|
||||
if (isConstructionLayer && !readConstructionSegments) continue;
|
||||
for (let obj of layer.data) {
|
||||
if (isConstructionLayer && obj._class !== 'TCAD.TWO.Segment') continue;
|
||||
if (obj.edge !== undefined) continue;
|
||||
if (!!obj.aux) continue;
|
||||
if (obj._class === 'TCAD.TWO.Segment') {
|
||||
const segA = new Vector(obj.points[0][1][1], obj.points[0][2][1], 0);
|
||||
const segB = new Vector(obj.points[1][1][1], obj.points[1][2][1], 0);
|
||||
const sketchConnection = new SketchConnection(segA, segB, createData(obj));
|
||||
if (isConstructionLayer) {
|
||||
out.constructionSegments.push(sketchConnection);
|
||||
} else {
|
||||
out.connections.push(sketchConnection);
|
||||
}
|
||||
} else if (obj._class === 'TCAD.TWO.Arc') {
|
||||
const arcA = new Vector(obj.points[0][1][1], obj.points[0][2][1], 0);
|
||||
const arcB = new Vector(obj.points[1][1][1], obj.points[1][2][1], 0);
|
||||
const arcCenter = new Vector(obj.points[2][1][1], obj.points[2][2][1], 0);
|
||||
const approxedArc = approxArc(arcA, arcB, arcCenter, RESOLUTION);
|
||||
const arcData = createData(obj);
|
||||
for (let j = 0; j < approxedArc.length - 1; j++) {
|
||||
out.connections.push(new SketchConnection(approxedArc[j], approxedArc[j+1], arcData));
|
||||
}
|
||||
} else if (obj._class === 'TCAD.TWO.EllipticalArc') {
|
||||
const ep1 = ReadSketchPoint(obj.ep1);
|
||||
const ep2 = ReadSketchPoint(obj.ep2);
|
||||
const a = ReadSketchPoint(obj.a);
|
||||
const b = ReadSketchPoint(obj.b);
|
||||
const r = obj.r;
|
||||
const approxedEllArc = approxEllipticalArc(ep1, ep2, a, b, r, RESOLUTION);
|
||||
const arcData = createData(obj);
|
||||
for (let j = 0; j < approxedEllArc.length - 1; j++) {
|
||||
out.connections.push(new SketchConnection(approxedEllArc[j], approxedEllArc[j+1], arcData));
|
||||
}
|
||||
} else if (obj._class === 'TCAD.TWO.BezierCurve') {
|
||||
const a = ReadSketchPoint(obj.a);
|
||||
const b = ReadSketchPoint(obj.b);
|
||||
const cp1 = ReadSketchPoint(obj.cp1);
|
||||
const cp2 = ReadSketchPoint(obj.cp2);
|
||||
const approxedCurve = approxBezierCurve(a, b, cp1, cp2, RESOLUTION);
|
||||
const curvedData = createData(obj);
|
||||
for (let j = 0; j < approxedCurve.length - 1; j++) {
|
||||
out.connections.push(new SketchConnection(approxedCurve[j], approxedCurve[j+1], curvedData));
|
||||
}
|
||||
} else if (obj._class === 'TCAD.TWO.Circle') {
|
||||
const circleCenter = new Vector(obj.c[1][1], obj.c[2][1], 0);
|
||||
const approxedCircle = approxCircle(circleCenter, obj.r, RESOLUTION);
|
||||
const circleData = createData(obj);
|
||||
const loop = [];
|
||||
let p, q, n = approxedCircle.length;
|
||||
for (p = n - 1, q = 0; q < n; p = q++) {
|
||||
loop.push(new SketchConnection(approxedCircle[p], approxedCircle[q], circleData));
|
||||
}
|
||||
out.loops.push(loop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function ReadSketchPoint(arr) {
|
||||
return new Vector(arr[1][1], arr[2][1], 0)
|
||||
}
|
||||
|
||||
export function approxArc(ao, bo, c, resolution) {
|
||||
var a = ao.minus(c);
|
||||
var b = bo.minus(c);
|
||||
var points = [ao];
|
||||
var abAngle = Math.atan2(b.y, b.x) - Math.atan2(a.y, a.x);
|
||||
if (abAngle > Math.PI * 2) abAngle = Math.PI / 2 - abAngle;
|
||||
if (abAngle < 0) abAngle = Math.PI * 2 + abAngle;
|
||||
|
||||
var r = a.length();
|
||||
resolution = 1;
|
||||
//var step = Math.acos(1 - ((resolution * resolution) / (2 * r * r)));
|
||||
var step = resolution / (2 * Math.PI);
|
||||
var k = Math.round(abAngle / step);
|
||||
var angle = Math.atan2(a.y, a.x) + step;
|
||||
|
||||
for (var i = 0; i < k - 1; ++i) {
|
||||
points.push(new Vector(c.x + r*Math.cos(angle), c.y + r*Math.sin(angle)));
|
||||
angle += step;
|
||||
}
|
||||
points.push(bo);
|
||||
return points;
|
||||
}
|
||||
|
||||
export function approxEllipticalArc(ep1, ep2, ao, bo, radiusY, resolution) {
|
||||
const axisX = ep2.minus(ep1);
|
||||
const radiusX = axisX.length() * 0.5;
|
||||
axisX._normalize();
|
||||
const c = ep1.plus(axisX.multiply(radiusX));
|
||||
const a = ao.minus(c);
|
||||
const b = bo.minus(c);
|
||||
const points = [ao];
|
||||
const rotation = Math.atan2(axisX.y, axisX.x);
|
||||
let abAngle = Math.atan2(b.y, b.x) - Math.atan2(a.y, a.x);
|
||||
if (abAngle > Math.PI * 2) abAngle = Math.PI / 2 - abAngle;
|
||||
if (abAngle < 0) abAngle = Math.PI * 2 + abAngle;
|
||||
|
||||
const sq = (a) => a * a;
|
||||
|
||||
resolution = 1;
|
||||
|
||||
const step = resolution / (2 * Math.PI);
|
||||
const k = Math.round(abAngle / step);
|
||||
let angle = Math.atan2(a.y, a.x) + step - rotation;
|
||||
|
||||
for (let i = 0; i < k - 1; ++i) {
|
||||
const r = Math.sqrt(1/( sq(Math.cos(angle)/radiusX) + sq(Math.sin(angle)/radiusY)));
|
||||
points.push(new Vector(c.x + r*Math.cos(angle + rotation), c.y + r*Math.sin(angle + rotation)));
|
||||
angle += step;
|
||||
}
|
||||
points.push(bo);
|
||||
return points;
|
||||
}
|
||||
|
||||
export function approxCircle(c, r, resolution) {
|
||||
var points = [];
|
||||
|
||||
resolution = 1;
|
||||
//var step = Math.acos(1 - ((resolution * resolution) / (2 * r * r)));
|
||||
var step = resolution / (2 * Math.PI);
|
||||
var k = Math.round((2 * Math.PI) / step);
|
||||
|
||||
for (var i = 0, angle = 0; i < k; ++i, angle += step) {
|
||||
points.push(new Vector(c.x + r*Math.cos(angle), c.y + r*Math.sin(angle)));
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
export function approxBezierCurve(a, b, cp1, cp2, resolution) {
|
||||
return LUT(a, b, cp1, cp2, 10);
|
||||
}
|
||||
|
||||
export function getSketchedPolygons3D(app, face, reverseGeom) {
|
||||
|
||||
var savedFace = localStorage.getItem(app.faceStorageKey(face.id));
|
||||
if (savedFace == null) return null;
|
||||
|
||||
var geom = readSketchGeom(JSON.parse(savedFace), face.id, false);
|
||||
var polygons2D = cad_utils.sketchToPolygons(geom);
|
||||
if (reverseGeom) {
|
||||
polygons2D.forEach(p => p.reverse());
|
||||
}
|
||||
var depth = null;
|
||||
var sketchedPolygons = [];
|
||||
for (var i = 0; i < polygons2D.length; i++) {
|
||||
var poly2D = polygons2D[i];
|
||||
if (poly2D.length < 3) continue;
|
||||
|
||||
if (depth == null) {
|
||||
var _3dTransformation = new Matrix3().setBasis(face.basis());
|
||||
//we lost depth or z off in 2d sketch, calculate it again
|
||||
depth = face.depth();
|
||||
}
|
||||
|
||||
var polygon = [];
|
||||
polygon._2D = poly2D;
|
||||
for (var m = 0; m < poly2D.length; ++m) {
|
||||
var vec = poly2D[m];
|
||||
vec.z = depth;
|
||||
// var a = _3dTransformation.apply(new Vector(poly2D[m][0], poly2D[m][1], depth));
|
||||
var a = _3dTransformation.apply(vec);
|
||||
a.sketchConnectionObject = vec.sketchConnectionObject;
|
||||
polygon.push(a);
|
||||
}
|
||||
|
||||
sketchedPolygons.push(polygon);
|
||||
}
|
||||
return sketchedPolygons;
|
||||
}
|
||||
|
||||
export function sortPolygons(polygons) {
|
||||
function Loop(polygon) {
|
||||
this.polygon = polygon;
|
||||
|
|
|
|||
252
web/app/3d/craft/sketch/sketch-model.js
Normal file
252
web/app/3d/craft/sketch/sketch-model.js
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
import {CompositeCurve} from '../../../brep/geom/curve'
|
||||
import {ApproxCurve} from '../../../brep/geom/impl/approx'
|
||||
import {Point} from '../../../brep/geom/point'
|
||||
import {Line} from '../../../brep/geom/impl/Line'
|
||||
import {LUT} from '../../../math/bezier-cubic'
|
||||
import {isCCW} from '../../../math/math'
|
||||
|
||||
const RESOLUTION = 20;
|
||||
|
||||
class SketchPrimitive {
|
||||
constructor(id) {
|
||||
this.id = id;
|
||||
this.inverted = false;
|
||||
}
|
||||
|
||||
invert() {
|
||||
this.inverted = !this.inverted;
|
||||
}
|
||||
|
||||
approximate(resolution) {
|
||||
const approximation = this.approximateImpl(resolution);
|
||||
if (this.inverted) {
|
||||
approximation.reverse();
|
||||
}
|
||||
return approximation;
|
||||
}
|
||||
|
||||
isCurve() {
|
||||
return this.constructor.name != 'Segment';
|
||||
}
|
||||
}
|
||||
|
||||
export class Segment extends SketchPrimitive {
|
||||
constructor(id, a, b) {
|
||||
super(id);
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
approximateImpl(resolution) {
|
||||
return [this.a, this.b];
|
||||
}
|
||||
}
|
||||
|
||||
export class Arc extends SketchPrimitive {
|
||||
constructor(id, a, b, c) {
|
||||
super(id);
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.c = c;
|
||||
}
|
||||
|
||||
approximateImpl(resolution) {
|
||||
return Arc.approximateArc(this.a, this.b, this.c, resolution);
|
||||
}
|
||||
|
||||
static approximateArc(ao, bo, c, resolution) {
|
||||
var a = ao.minus(c);
|
||||
var b = bo.minus(c);
|
||||
var points = [ao];
|
||||
var abAngle = Math.atan2(b.y, b.x) - Math.atan2(a.y, a.x);
|
||||
if (abAngle > Math.PI * 2) abAngle = Math.PI / 2 - abAngle;
|
||||
if (abAngle < 0) abAngle = Math.PI * 2 + abAngle;
|
||||
|
||||
var r = a.length();
|
||||
resolution = 1;
|
||||
//var step = Math.acos(1 - ((resolution * resolution) / (2 * r * r)));
|
||||
var step = resolution / (2 * Math.PI);
|
||||
var k = Math.round(abAngle / step);
|
||||
var angle = Math.atan2(a.y, a.x) + step;
|
||||
|
||||
for (var i = 0; i < k - 1; ++i) {
|
||||
points.push(new Point(c.x + r*Math.cos(angle), c.y + r*Math.sin(angle)));
|
||||
angle += step;
|
||||
}
|
||||
points.push(bo);
|
||||
return points;
|
||||
}
|
||||
}
|
||||
|
||||
export class BezierCurve extends SketchPrimitive {
|
||||
constructor(id, a, b, cp1, cp2) {
|
||||
super(id);
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.cp1 = cp1;
|
||||
this.cp2 = cp2;
|
||||
}
|
||||
|
||||
approximateImpl(resolution) {
|
||||
return LUT(this.a, this.b, this.cp1, this.cp2, 10);
|
||||
}
|
||||
}
|
||||
|
||||
export class EllipticalArc extends SketchPrimitive {
|
||||
constructor(id, ep1, ep2, a, b, r) {
|
||||
super(id);
|
||||
this.ep1 = ep1;
|
||||
this.ep2 = ep2;
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.r = r;
|
||||
}
|
||||
|
||||
approximateImpl(resolution) {
|
||||
return EllipticalArc.approxEllipticalArc(this.ep1, this.ep2, this.a, this.b, this.r, resolution);
|
||||
}
|
||||
|
||||
static approxEllipticalArc(ep1, ep2, ao, bo, radiusY, resolution) {
|
||||
const axisX = ep2.minus(ep1);
|
||||
const radiusX = axisX.length() * 0.5;
|
||||
axisX._normalize();
|
||||
const c = ep1.plus(axisX.multiply(radiusX));
|
||||
const a = ao.minus(c);
|
||||
const b = bo.minus(c);
|
||||
const points = [ao];
|
||||
const rotation = Math.atan2(axisX.y, axisX.x);
|
||||
let abAngle = Math.atan2(b.y, b.x) - Math.atan2(a.y, a.x);
|
||||
if (abAngle > Math.PI * 2) abAngle = Math.PI / 2 - abAngle;
|
||||
if (abAngle < 0) abAngle = Math.PI * 2 + abAngle;
|
||||
|
||||
const sq = (a) => a * a;
|
||||
|
||||
resolution = 1;
|
||||
|
||||
const step = resolution / (2 * Math.PI);
|
||||
const k = Math.round(abAngle / step);
|
||||
let angle = Math.atan2(a.y, a.x) + step - rotation;
|
||||
|
||||
for (let i = 0; i < k - 1; ++i) {
|
||||
const r = Math.sqrt(1/( sq(Math.cos(angle)/radiusX) + sq(Math.sin(angle)/radiusY)));
|
||||
points.push(new Point(c.x + r*Math.cos(angle + rotation), c.y + r*Math.sin(angle + rotation)));
|
||||
angle += step;
|
||||
}
|
||||
points.push(bo);
|
||||
return points;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Circle extends SketchPrimitive {
|
||||
constructor(id, c, r) {
|
||||
super(id);
|
||||
this.c = c;
|
||||
this.r = r;
|
||||
}
|
||||
|
||||
approximateImpl(resolution) {
|
||||
return Circle.approxCircle(this.c, this.r, resolution);
|
||||
}
|
||||
|
||||
static approxCircle(c, r, resolution) {
|
||||
var points = [];
|
||||
resolution = 1;
|
||||
//var step = Math.acos(1 - ((resolution * resolution) / (2 * r * r)));
|
||||
var step = resolution / (2 * Math.PI);
|
||||
var k = Math.round((2 * Math.PI) / step);
|
||||
|
||||
for (var i = 0, angle = 0; i < k; ++i, angle += step) {
|
||||
points.push(new Point(c.x + r*Math.cos(angle), c.y + r*Math.sin(angle)));
|
||||
}
|
||||
points.push(points[0]); // close it
|
||||
return points;
|
||||
}
|
||||
}
|
||||
|
||||
export class Ellipse extends SketchPrimitive {
|
||||
constructor(id, ep1, ep2, r) {
|
||||
super(id);
|
||||
this.ep1 = ep1;
|
||||
this.ep2 = ep2;
|
||||
this.r = r;
|
||||
}
|
||||
|
||||
approximateImpl(resolution) {
|
||||
return EllipticalArc.approxEllipticalArc(this.ep1, this.ep2, this.ep1, this.ep1, this.r, resolution);
|
||||
}
|
||||
}
|
||||
|
||||
export class Contour {
|
||||
|
||||
constructor() {
|
||||
this.segments = [];
|
||||
}
|
||||
|
||||
add(obj) {
|
||||
this.segments.push(obj);
|
||||
}
|
||||
|
||||
transferOnSurface(surface, forceApproximation) {
|
||||
const cc = new CompositeCurve();
|
||||
|
||||
const _3dTransformation = surface.get3DTransformation();
|
||||
const depth = surface.w;
|
||||
function tr(v) {
|
||||
v = v.copy();
|
||||
v.z = depth;
|
||||
return _3dTransformation._apply(v);
|
||||
}
|
||||
|
||||
let prev = null;
|
||||
let firstPoint = null;
|
||||
for (let segIdx = 0; segIdx < this.segments.length; ++segIdx) {
|
||||
let segment = this.segments[segIdx];
|
||||
let approximation = segment.approximate(RESOLUTION);
|
||||
|
||||
approximation = approximation.map(p => tr(p));
|
||||
|
||||
const n = approximation.length;
|
||||
prev = prev == null ? approximation[0] : prev;
|
||||
approximation[0] = prev; // this magic is to keep identity of same vectors
|
||||
if (firstPoint == null) firstPoint = approximation[0];
|
||||
|
||||
if (segIdx == this.segments.length - 1) {
|
||||
approximation[n - 1] = firstPoint;
|
||||
}
|
||||
|
||||
if (!forceApproximation && segment.constructor.name == 'Arc') {
|
||||
cc.add(new ApproxCurve(approximation, segment), prev, segment);
|
||||
prev = approximation[n - 1];
|
||||
} else {
|
||||
for (let i = 1; i < n; ++i) {
|
||||
const curr = approximation[i];
|
||||
cc.add(new Line.fromSegment(prev, curr), prev, segment);
|
||||
prev = curr;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cc;
|
||||
}
|
||||
|
||||
approximate(resolution) {
|
||||
const approximation = [];
|
||||
for (let segment of this.segments) {
|
||||
const segmentApproximation = segment.approximate(resolution);
|
||||
//skip last one cuz it's guaranteed to be closed
|
||||
for (let i = 0; i < segmentApproximation.length - 1; ++i) {
|
||||
approximation.push(segmentApproximation[i]);
|
||||
}
|
||||
}
|
||||
return approximation;
|
||||
}
|
||||
|
||||
isCCW() {
|
||||
return isCCW(this.approximate(10));
|
||||
}
|
||||
|
||||
reverse() {
|
||||
this.segments.reverse();
|
||||
this.segments.forEach(s => s.invert());
|
||||
}
|
||||
}
|
||||
170
web/app/3d/craft/sketch/sketch-reader.js
Normal file
170
web/app/3d/craft/sketch/sketch-reader.js
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
import * as sm from './sketch-model'
|
||||
import {Matrix3, AXIS, ORIGIN} from '../../../math/l3space'
|
||||
import Vector from '../../../math/vector'
|
||||
import {Graph} from '../../../math/graph'
|
||||
import * as math from '../../../math/math'
|
||||
import {HashTable} from '../../../utils/hashmap'
|
||||
|
||||
export function ReadSketch(sketch, faceId, readConstructionSegments) {
|
||||
let idCounter = 0;
|
||||
function genID() {
|
||||
return faceId + ":" + (idCounter++);
|
||||
}
|
||||
const out = {connections : [], loops : [], constructionSegments: []};
|
||||
if (sketch.layers !== undefined) {
|
||||
for (let layer of sketch.layers) {
|
||||
const isConstructionLayer = layer.name == "_construction_";
|
||||
if (isConstructionLayer && !readConstructionSegments) continue;
|
||||
for (let obj of layer.data) {
|
||||
if (isConstructionLayer && obj._class !== 'TCAD.TWO.Segment') continue;
|
||||
if (obj.edge !== undefined) continue;
|
||||
if (!!obj.aux) continue;
|
||||
if (obj._class === 'TCAD.TWO.Segment') {
|
||||
const segA = ReadSketchPoint(obj.points[0]);
|
||||
const segB = ReadSketchPoint(obj.points[1]);
|
||||
const pushOn = isConstructionLayer ? out.constructionSegments : out.connections;
|
||||
pushOn.push(new sm.Segment(genID(), segA, segB));
|
||||
} else if (obj._class === 'TCAD.TWO.Arc') {
|
||||
const arcA = ReadSketchPoint(obj.points[0]);
|
||||
const arcB = ReadSketchPoint(obj.points[1]);
|
||||
const arcCenter = ReadSketchPoint(obj.points[2]);
|
||||
out.connections.push(new sm.Arc(genID(), arcA, arcB, arcCenter));
|
||||
} else if (obj._class === 'TCAD.TWO.EllipticalArc') {
|
||||
const ep1 = ReadSketchPoint(obj.ep1);
|
||||
const ep2 = ReadSketchPoint(obj.ep2);
|
||||
const a = ReadSketchPoint(obj.a);
|
||||
const b = ReadSketchPoint(obj.b);
|
||||
out.connections.push(new sm.EllipticalArc(genID(), ep1, ep2, a, b, obj.r));
|
||||
} else if (obj._class === 'TCAD.TWO.BezierCurve') {
|
||||
const a = ReadSketchPoint(obj.a);
|
||||
const b = ReadSketchPoint(obj.b);
|
||||
const cp1 = ReadSketchPoint(obj.cp1);
|
||||
const cp2 = ReadSketchPoint(obj.cp2);
|
||||
out.connections.push(new sm.BezierCurve(genID(), a, b, cp1, cp2));
|
||||
} else if (obj._class === 'TCAD.TWO.Circle') {
|
||||
const circleCenter = ReadSketchPoint(obj.c);
|
||||
out.loops.push(new sm.Circle(genID(), circleCenter, obj.r));
|
||||
} else if (obj._class === 'TCAD.TWO.Ellipse') {
|
||||
const ep1 = ReadSketchPoint(obj.ep1);
|
||||
const ep2 = ReadSketchPoint(obj.ep2);
|
||||
out.loops.push(new sm.Ellipse(genID(), ep1, ep2, obj.r));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export function ReadSketchPoint(arr) {
|
||||
return new Vector(arr[1][1], arr[2][1], 0)
|
||||
}
|
||||
|
||||
export function ReadSketchContoursFromFace(app, face) {
|
||||
const savedFace = localStorage.getItem(app.faceStorageKey(face.id));
|
||||
if (savedFace == null) return null;
|
||||
const geom = ReadSketch(JSON.parse(savedFace), face.id, false);
|
||||
const contours = findClosedContours(geom.connections);
|
||||
for (let loop of geom.loops) {
|
||||
const contour = new sm.Contour();
|
||||
contour.add(loop);
|
||||
contours.push(contour);
|
||||
}
|
||||
for (let contour of contours) {
|
||||
if (!contour.isCCW()) contour.reverse();
|
||||
}
|
||||
return contours;
|
||||
}
|
||||
|
||||
function findClosedContours(segments) {
|
||||
const result = [];
|
||||
findClosedContoursFromPairedCurves(segments, result);
|
||||
findClosedContoursFromGraph(segments, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
function findClosedContoursFromPairedCurves(segments, result) {
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
const s1 = segments[i];
|
||||
for (let j = i; j < segments.length; j++) {
|
||||
if (i == j) continue;
|
||||
const s2 = segments[j];
|
||||
if (s1.isCurve() && s2.isCurve()) {
|
||||
let paired = false;
|
||||
if (math.strictEqual2D(s1.a, s2.a) && math.strictEqual2D(s1.b, s2.b)) {
|
||||
paired = true;
|
||||
s2.invert();
|
||||
} else if (math.strictEqual2D(s1.a, s2.b) && math.strictEqual2D(s1.b, s2.a)) {
|
||||
paired = true;
|
||||
}
|
||||
if (paired) {
|
||||
const contour = new sm.Contour();
|
||||
contour.add(s1);
|
||||
contour.add(s2);
|
||||
result.push(contour);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findClosedContoursFromGraph(segments, result) {
|
||||
|
||||
const dict = HashTable.forVector2d();
|
||||
const edges = HashTable.forDoubleArray();
|
||||
|
||||
function edgeKey(a, b) {
|
||||
return [a.x, a.y, b.x, b.y];
|
||||
}
|
||||
|
||||
const points = [];
|
||||
function memDir(a, b) {
|
||||
let dirs = dict.get(a);
|
||||
if (dirs === null) {
|
||||
dirs = [];
|
||||
dict.put(a, dirs);
|
||||
points.push(a);
|
||||
}
|
||||
dirs.push(b);
|
||||
}
|
||||
|
||||
for (let seg of segments) {
|
||||
const a = seg.a;
|
||||
const b = seg.b;
|
||||
|
||||
memDir(a, b);
|
||||
memDir(b, a);
|
||||
edges.put(edgeKey(a, b), seg);
|
||||
}
|
||||
|
||||
const graph = {
|
||||
|
||||
connections : function(e) {
|
||||
const dirs = dict.get(e);
|
||||
return dirs === null ? [] : dirs;
|
||||
},
|
||||
|
||||
at : function(index) {
|
||||
return points[index];
|
||||
},
|
||||
|
||||
size : function() {
|
||||
return points.length;
|
||||
}
|
||||
};
|
||||
|
||||
const loops = Graph.findAllLoops(graph, dict.hashCodeF, dict.equalsF);
|
||||
for (let loop of loops) {
|
||||
const contour = new sm.Contour();
|
||||
for (let pi = 0; pi < loop.length; ++pi) {
|
||||
const point = loop[pi];
|
||||
const next = loop[(pi + 1) % loop.length];
|
||||
let edge = edges.get(edgeKey(point, next));
|
||||
if (edge === null) {
|
||||
edge = edges.get(edgeKey(next, point));
|
||||
edge.invert();
|
||||
}
|
||||
contour.add(edge);
|
||||
}
|
||||
result.push(contour);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import * as AllActions from './actions/all-actions'
|
|||
import Vector from '../math/vector'
|
||||
import {Matrix3, AXIS, ORIGIN, IDENTITY_BASIS} from '../math/l3space'
|
||||
import {Craft} from './craft/craft'
|
||||
import {ReadSketch} from './craft/sketch/sketch-reader'
|
||||
import * as workbench from './craft/mesh/workbench'
|
||||
import * as cad_utils from './cad-utils'
|
||||
import * as math from '../math/math'
|
||||
|
|
@ -598,7 +599,7 @@ App.prototype.refreshSketchOnFace = function(sketchFace) {
|
|||
var faceStorageKey = this.faceStorageKey(sketchFace.id);
|
||||
var savedFace = localStorage.getItem(faceStorageKey);
|
||||
if (savedFace != null) {
|
||||
var geom = workbench.readSketchGeom(JSON.parse(savedFace), sketchFace.id, true);
|
||||
var geom = ReadSketch(JSON.parse(savedFace), sketchFace.id, true);
|
||||
sketchFace.syncSketches(geom);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import {HashTable} from '../../utils/hashmap'
|
|||
import Vector from '../../math/vector'
|
||||
import Counters from '../counters'
|
||||
import {Matrix3, BasisForPlane} from '../../math/l3space'
|
||||
import {arrFlatten1L, isCurveClass} from '../cad-utils'
|
||||
import {isCurveClass} from '../cad-utils'
|
||||
import DPR from '../../utils/dpr'
|
||||
|
||||
export class SceneSolid {
|
||||
|
|
@ -61,6 +61,8 @@ export function createSolidMaterial(skin) {
|
|||
}, skin));
|
||||
}
|
||||
|
||||
const OFF_LINES_VECTOR = new Vector();//normal.multiply(0); // disable it. use polygon offset feature of material
|
||||
|
||||
export class SceneFace {
|
||||
constructor(solid, propagatedId) {
|
||||
if (propagatedId === undefined) {
|
||||
|
|
@ -109,9 +111,6 @@ export class SceneFace {
|
|||
}
|
||||
|
||||
syncSketches(geom) {
|
||||
const normal = this.normal();
|
||||
const offVector = new Vector();//normal.multiply(0); // disable it. use polygon offset feature of material
|
||||
|
||||
if (this.sketch3DGroup != null) {
|
||||
for (let i = this.sketch3DGroup.children.length - 1; i >= 0; --i) {
|
||||
this.sketch3DGroup.remove(this.sketch3DGroup.children[i]);
|
||||
|
|
@ -125,34 +124,29 @@ export class SceneFace {
|
|||
const _3dTransformation = new Matrix3().setBasis(basis);
|
||||
//we lost depth or z off in 2d sketch, calculate it again
|
||||
const depth = this.depth();
|
||||
const polyLines = new Map();
|
||||
function addSketchConnections(connections, material) {
|
||||
for (let i = 0; i < connections.length; ++i) {
|
||||
const l = connections[i];
|
||||
const addSketchObjects = (sketchObjects, material, close) => {
|
||||
for (let sketchObject of sketchObjects) {
|
||||
let line = new THREE.Line(undefined, material);
|
||||
line.__TCAD_SketchObject = sketchObject;
|
||||
const chunks = sketchObject.approximate(10);
|
||||
function addLine(p, q) {
|
||||
const lg = line.geometry;
|
||||
chunks[p].z = chunks[q].z = depth;
|
||||
const a = _3dTransformation.apply(chunks[p]);
|
||||
const b = _3dTransformation.apply(chunks[q]);
|
||||
|
||||
let line = polyLines.get(l.sketchObject.id);
|
||||
if (!line) {
|
||||
line = new THREE.Line(undefined, material);
|
||||
line.__TCAD_SketchObject = l.sketchObject;
|
||||
polyLines.set(l.sketchObject.id, line);
|
||||
lg.vertices.push(a._plus(OFF_LINES_VECTOR).three());
|
||||
lg.vertices.push(b._plus(OFF_LINES_VECTOR).three());
|
||||
}
|
||||
const lg = line.geometry;
|
||||
l.a.z = l.b.z = depth;
|
||||
const a = _3dTransformation.apply(l.a);
|
||||
const b = _3dTransformation.apply(l.b);
|
||||
|
||||
lg.vertices.push(a.plus(offVector).three());
|
||||
lg.vertices.push(b.plus(offVector).three());
|
||||
for (let q = 1; q < chunks.length; q ++) {
|
||||
addLine(q - 1, q);
|
||||
}
|
||||
this.sketch3DGroup.add(line);
|
||||
}
|
||||
|
||||
}
|
||||
addSketchConnections(geom.constructionSegments, SKETCH_CONSTRUCTION_MATERIAL);
|
||||
addSketchConnections(geom.connections, SKETCH_MATERIAL);
|
||||
addSketchConnections(arrFlatten1L(geom.loops), SKETCH_MATERIAL);
|
||||
|
||||
for (let line of polyLines.values()) {
|
||||
this.sketch3DGroup.add(line);
|
||||
}
|
||||
};
|
||||
addSketchObjects(geom.constructionSegments, SKETCH_CONSTRUCTION_MATERIAL);
|
||||
addSketchObjects(geom.connections, SKETCH_MATERIAL);
|
||||
addSketchObjects(geom.loops, SKETCH_MATERIAL);
|
||||
}
|
||||
|
||||
findById(sketchObjectId) {
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@ import {Loop} from './topo/loop'
|
|||
import {Face} from './topo/face'
|
||||
import {HalfEdge, Edge} from './topo/edge'
|
||||
import {Line} from './geom/impl/line'
|
||||
import {ApproxCurve, ApproxSurface} from './geom/impl/approx'
|
||||
import {Plane} from './geom/impl/plane'
|
||||
import {Point} from './geom/point'
|
||||
import {BasisForPlane, Matrix3} from '../math/l3space'
|
||||
import {CompositeCurve} from './geom/curve'
|
||||
import * as cad_utils from '../3d/cad-utils'
|
||||
import * as math from '../math/math'
|
||||
|
||||
|
|
@ -25,95 +27,111 @@ function checkCCW(points, normal) {
|
|||
}
|
||||
|
||||
export function createPrism(basePoints, height) {
|
||||
return new SimpleExtruder(height).extrude(basePoints, cad_utils.normalOfCCWSeq(basePoints));
|
||||
const normal = cad_utils.normalOfCCWSeq(basePoints);
|
||||
const baseSurface = new Plane(normal, normal.dot(basePoints[0]));
|
||||
const extrudeVector = baseSurface.normal.multiply( - height);
|
||||
const lidSurface = baseSurface.translate(extrudeVector).invert();
|
||||
const lidPoints = basePoints.map(p => p.plus(extrudeVector));
|
||||
const basePath = new CompositeCurve();
|
||||
const lidPath = new CompositeCurve();
|
||||
|
||||
for (let i = 0; i < basePoints.length; i++) {
|
||||
let j = (i + 1) % basePoints.length;
|
||||
basePath.add(Line.fromSegment(basePoints[i], basePoints[j]), basePoints[i], null);
|
||||
lidPath.add(Line.fromSegment(lidPoints[i], lidPoints[j]), lidPoints[i], null);
|
||||
}
|
||||
return enclose(basePath, lidPath, baseSurface, lidSurface, () => {});
|
||||
}
|
||||
|
||||
export class Extruder {
|
||||
|
||||
prepareLidCalculation(baseNormal, lidNormal) {
|
||||
}
|
||||
|
||||
calculateLid(basePoints) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
extrude(basePoints, normal) {
|
||||
const baseLoop = createPlaneLoop(basePoints.map(p => new Vertex(p)));
|
||||
const baseFace = createPlaneFace(normal, baseLoop);
|
||||
const lidNormal = normal.multiply(-1);
|
||||
|
||||
this.prepareLidCalculation(normal, lidNormal);
|
||||
export function enclose(basePath, lidPath, baseSurface, lidSurface, onWallF) {
|
||||
|
||||
//iterateSegments(basePoints.map(p => new Vertex(p.plus(offVector))), (a, b) => lidSegments.push({a, b}));
|
||||
const lidPoints = this.calculateLid(basePoints, normal, lidNormal).reverse();
|
||||
const lidLoop = createPlaneLoop(lidPoints.map(p => new Vertex(p)));
|
||||
if (basePath.points.length != lidPath.points.length) {
|
||||
throw 'illegal arguments';
|
||||
}
|
||||
|
||||
const baseLoop = new Loop();
|
||||
const lidLoop = new Loop();
|
||||
|
||||
const shell = new Shell();
|
||||
const shell = new Shell();
|
||||
const baseVertices = basePath.points.map(p => new Vertex(p));
|
||||
const lidVertices = lidPath.points.map(p => new Vertex(p));
|
||||
|
||||
const n = baseLoop.halfEdges.length;
|
||||
for (let i = 0; i < n; i++) {
|
||||
let lidIdx = n - 2 - i;
|
||||
if (lidIdx == -1) {
|
||||
lidIdx = n - 1;
|
||||
}
|
||||
const baseHalfEdge = baseLoop.halfEdges[i];
|
||||
const lidHalfEdge = lidLoop.halfEdges[lidIdx];
|
||||
const wallPolygon = [baseHalfEdge.vertexB, baseHalfEdge.vertexA, lidHalfEdge.vertexB, lidHalfEdge.vertexA];
|
||||
const wallLoop = createPlaneLoop(wallPolygon);
|
||||
|
||||
const baseEdge = new Edge(Line.fromSegment(baseHalfEdge.vertexA.point, baseHalfEdge.vertexB.point));
|
||||
linkHalfEdges(baseEdge, baseHalfEdge, wallLoop.halfEdges[0]);
|
||||
|
||||
const lidEdge = new Edge(Line.fromSegment(lidHalfEdge.vertexA.point, lidHalfEdge.vertexB.point));
|
||||
linkHalfEdges(lidEdge, lidHalfEdge, wallLoop.halfEdges[2]);
|
||||
|
||||
const wallNormal = cad_utils.normalOfCCWSeq(wallPolygon.map(v => v.point));
|
||||
|
||||
const wallFace = createPlaneFace(wallNormal, wallLoop);
|
||||
wallFace.role = 'wall:' + i;
|
||||
this.onWallCallback(wallFace, baseHalfEdge);
|
||||
|
||||
shell.faces.push(wallFace);
|
||||
}
|
||||
const lidFace = createPlaneFace(lidNormal, lidLoop);
|
||||
iterateSegments(shell.faces, (a, b) => {
|
||||
const halfEdgeA = a.outerLoop.halfEdges[3];
|
||||
const halfEdgeB = b.outerLoop.halfEdges[1];
|
||||
const curve = Line.fromSegment(halfEdgeA.vertexA.point, halfEdgeA.vertexB.point);
|
||||
linkHalfEdges(new Edge(curve), halfEdgeA, halfEdgeB);
|
||||
});
|
||||
|
||||
baseFace.role = 'base';
|
||||
lidFace.role = 'lid';
|
||||
const n = basePath.points.length;
|
||||
for (let i = 0; i < n; i++) {
|
||||
let j = (i + 1) % n;
|
||||
const baseHalfEdge = new HalfEdge().setAB(baseVertices[i], baseVertices[j]);
|
||||
const lidHalfEdge = new HalfEdge().setAB(lidVertices[j], lidVertices[i]);
|
||||
|
||||
shell.faces.push(baseFace, lidFace);
|
||||
shell.faces.forEach(f => f.shell = shell);
|
||||
return shell;
|
||||
}
|
||||
baseHalfEdge.edge = new Edge(basePath.curves[i]);
|
||||
lidHalfEdge.edge = new Edge(lidPath.curves[i]);
|
||||
|
||||
onWallCallback(wallFace, baseHalfEdge) {
|
||||
baseHalfEdge.edge.halfEdge1 = baseHalfEdge;
|
||||
lidHalfEdge.edge.halfEdge1 = lidHalfEdge;
|
||||
|
||||
|
||||
baseHalfEdge.loop = baseLoop;
|
||||
baseLoop.halfEdges.push(baseHalfEdge);
|
||||
|
||||
lidHalfEdge.loop = lidLoop;
|
||||
lidLoop.halfEdges[(n + n - 2 - i) % n] = lidHalfEdge; // keep old style order for the unit tests
|
||||
|
||||
const wallFace = createFaceFromTwoEdges(createTwin(baseHalfEdge), createTwin(lidHalfEdge));
|
||||
|
||||
wallFace.role = 'wall:' + i;
|
||||
onWallF(wallFace, basePath.groups[i]);
|
||||
shell.faces.push(wallFace);
|
||||
}
|
||||
|
||||
iterateSegments(shell.faces, (a, b) => {
|
||||
const halfEdgeA = a.outerLoop.halfEdges[3];
|
||||
const halfEdgeB = b.outerLoop.halfEdges[1];
|
||||
const curve = Line.fromSegment(halfEdgeA.vertexA.point, halfEdgeA.vertexB.point);
|
||||
linkHalfEdges(new Edge(curve), halfEdgeA, halfEdgeB);
|
||||
});
|
||||
|
||||
linkSegments(baseLoop.halfEdges);
|
||||
linkSegments(lidLoop.halfEdges);
|
||||
|
||||
const baseFace = createFace(baseSurface, baseLoop);
|
||||
const lidFace = createFace(lidSurface, lidLoop);
|
||||
baseFace.role = 'base';
|
||||
lidFace.role = 'lid';
|
||||
|
||||
shell.faces.push(baseFace, lidFace);
|
||||
shell.faces.forEach(f => f.shell = shell);
|
||||
return shell;
|
||||
}
|
||||
|
||||
export class SimpleExtruder extends Extruder {
|
||||
|
||||
constructor(height) {
|
||||
super();
|
||||
this.height = height;
|
||||
function createTwin(halfEdge) {
|
||||
const twin = new HalfEdge();
|
||||
twin.vertexA = halfEdge.vertexB;
|
||||
twin.vertexB = halfEdge.vertexA;
|
||||
twin.edge = halfEdge.edge;
|
||||
if (halfEdge.edge.halfEdge1 == halfEdge) {
|
||||
halfEdge.edge.halfEdge2 = twin;
|
||||
} else {
|
||||
halfEdge.edge.halfEdge1 = twin;
|
||||
}
|
||||
return twin;
|
||||
}
|
||||
|
||||
prepareLidCalculation(baseNormal, lidNormal) {
|
||||
this.extrudeVector = lidNormal.multiply(this.height);
|
||||
}
|
||||
function createFace(surface, loop) {
|
||||
const face = new Face(surface);
|
||||
face.outerLoop = loop;
|
||||
loop.face = face;
|
||||
return face;
|
||||
}
|
||||
|
||||
calculateLid(basePoints) {
|
||||
return basePoints.map(p => p.plus(this.extrudeVector))
|
||||
}
|
||||
|
||||
function createPlaneForLoop(normal, loop) {
|
||||
const w = loop.halfEdges[0].vertexA.point.dot(normal);
|
||||
const plane = new Plane(normal, w);
|
||||
return plane;
|
||||
}
|
||||
|
||||
function createPlaneFace(normal, loop) {
|
||||
const w = loop.halfEdges[0].vertexA.point.dot(normal);
|
||||
const plane = new Plane(normal, w);
|
||||
const plane = createPlaneForLoop();
|
||||
const face = new Face(plane);
|
||||
face.outerLoop = loop;
|
||||
loop.face = face;
|
||||
|
|
@ -128,23 +146,26 @@ export function linkHalfEdges(edge, halfEdge1, halfEdge2) {
|
|||
edge.halfEdge2 = halfEdge2;
|
||||
}
|
||||
|
||||
export function createPlaneLoop(vertices) {
|
||||
|
||||
export function createLoopFromCompositeCurve(path) { // TODO: REMOVE!
|
||||
const loop = new Loop();
|
||||
|
||||
iterateSegments(vertices, (a, b) => {
|
||||
createHalfEdge(loop, a, b)
|
||||
});
|
||||
|
||||
const vertices = [];
|
||||
for (let seg of path) {
|
||||
vertices[seg.pos] = new Vertex(s.pointA);
|
||||
}
|
||||
for (let seg of path) {
|
||||
const halfEdge = createHalfEdge(loop, vertices[seg.pos], vertices[seg.posNext]);
|
||||
halfEdge.edge = new Edge(seg.curve);
|
||||
halfEdge.edge.halfEdge1 = halfEdge;
|
||||
}
|
||||
linkSegments(loop.halfEdges);
|
||||
return loop;
|
||||
}
|
||||
|
||||
export function createHalfEdge(loop, a, b) {
|
||||
export function createHalfEdge(loop, vertexA, vertexB) {
|
||||
const halfEdge = new HalfEdge();
|
||||
halfEdge.loop = loop;
|
||||
halfEdge.vertexA = a;
|
||||
halfEdge.vertexB = b;
|
||||
halfEdge.vertexA = vertexA;
|
||||
halfEdge.vertexB = vertexB;
|
||||
loop.halfEdges.push(halfEdge);
|
||||
return halfEdge;
|
||||
}
|
||||
|
|
@ -177,3 +198,54 @@ export function invertLoop(loop) {
|
|||
loop.halfEdges.reverse();
|
||||
linkSegments(loop.halfEdges);
|
||||
}
|
||||
|
||||
export function createPlaneLoop(vertices) {
|
||||
|
||||
const loop = new Loop();
|
||||
|
||||
iterateSegments(vertices, (a, b) => {
|
||||
createHalfEdge(loop, a, b)
|
||||
});
|
||||
|
||||
linkSegments(loop.halfEdges);
|
||||
return loop;
|
||||
}
|
||||
|
||||
export function createFaceFromTwoEdges(e1, e2) {
|
||||
const loop = new Loop();
|
||||
e1.loop = loop;
|
||||
e2.loop = loop;
|
||||
loop.halfEdges.push(
|
||||
e1,
|
||||
HalfEdge.create(e1.vertexB, e2.vertexA, loop),
|
||||
e2,
|
||||
HalfEdge.create(e2.vertexB, e1.vertexA, loop));
|
||||
|
||||
let surface = null;
|
||||
if (e1.edge.curve.constructor.name == 'Line' &&
|
||||
e2.edge.curve.constructor.name == 'Line') {
|
||||
const normal = cad_utils.normalOfCCWSeq(loop.halfEdges.map(e => e.vertexA.point));
|
||||
surface = createPlaneForLoop(normal, loop);
|
||||
} else if ((e1.edge.curve instanceof ApproxCurve) && (e2.edge.curve instanceof ApproxCurve)) {
|
||||
const chunk1 = e1.edge.curve.getChunk(e1.edge.vertexA.point, e1.edge.vertexB.point);
|
||||
const chunk2 = e2.edge.curve.getChunk(e2.edge.vertexA.point, e2.edge.vertexB.point);
|
||||
const n = chunk1.length;
|
||||
if (n != chunk2.length) {
|
||||
throw 'unsupported';
|
||||
}
|
||||
surface = new ApproxSurface();
|
||||
for (let p = n - 1, q = 0; q < n; p = q ++) {
|
||||
const polygon = [ chunk1[p], chunk1[q], chunk2[q], chunk2[p] ];
|
||||
surface.mesh.push(polygon);
|
||||
}
|
||||
} else {
|
||||
throw 'unsupported';
|
||||
}
|
||||
|
||||
linkSegments(loop.halfEdges);
|
||||
|
||||
const face = new Face(surface);
|
||||
face.outerLoop = loop;
|
||||
loop.face = face;
|
||||
return face;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
export class Curve {
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
intersectCurve(curve) {
|
||||
|
|
@ -12,4 +11,24 @@ export class Curve {
|
|||
parametricEquation(t) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
}
|
||||
|
||||
translate(vector) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class CompositeCurve {
|
||||
|
||||
constructor() {
|
||||
this.curves = [];
|
||||
this.points = [];
|
||||
this.groups = [];
|
||||
}
|
||||
|
||||
add(curve, point, group) {
|
||||
this.curves.push(curve);
|
||||
this.points.push(point);
|
||||
this.groups.push(group);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
20
web/app/brep/geom/impl/approx.js
Normal file
20
web/app/brep/geom/impl/approx.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import {Surface} from '../surface'
|
||||
import {Curve} from '../curve'
|
||||
import {Matrix3, AXIS, BasisForPlane} from '../../../math/l3space'
|
||||
import * as math from '../../../math/math'
|
||||
|
||||
export class ApproxSurface extends Surface {
|
||||
constructor(mesh) {
|
||||
super();
|
||||
this.mesh = mesh;
|
||||
}
|
||||
}
|
||||
|
||||
export class ApproxCurve extends Curve {
|
||||
constructor(segments, proto) {
|
||||
super();
|
||||
this.segments = segments;
|
||||
this.proto = proto;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -44,6 +44,10 @@ export class Line extends Curve {
|
|||
}
|
||||
return point;
|
||||
}
|
||||
|
||||
translate(vector) {
|
||||
return new Line(this.p0.plus(vector), this.v);
|
||||
}
|
||||
}
|
||||
|
||||
Line.fromTwoPlanesIntersection = function(plane1, plane2) {
|
||||
|
|
|
|||
|
|
@ -12,16 +12,14 @@ export class Plane extends Surface {
|
|||
}
|
||||
|
||||
calculateBasis() {
|
||||
const normal = this.normal;
|
||||
let alignPlane, x, y;
|
||||
if (Math.abs(normal.dot(AXIS.Y)) < 0.5) {
|
||||
alignPlane = normal.cross(AXIS.Y);
|
||||
} else {
|
||||
alignPlane = normal.cross(AXIS.Z);
|
||||
return BasisForPlane(this.normal);
|
||||
}
|
||||
|
||||
basis() {
|
||||
if (!this._basis) {
|
||||
this._basis = this.calculateBasis();
|
||||
}
|
||||
y = alignPlane.cross(normal);
|
||||
x = y.cross(normal);
|
||||
return [x, y, normal];
|
||||
return this._basis;
|
||||
}
|
||||
|
||||
intersect(other) {
|
||||
|
|
@ -30,7 +28,11 @@ export class Plane extends Surface {
|
|||
}
|
||||
return super.intersect();
|
||||
}
|
||||
|
||||
|
||||
translate(vector) {
|
||||
return new Plane(this.normal, this.normal.dot(this.normal.multiply(this.w)._plus(vector)));
|
||||
}
|
||||
|
||||
invert() {
|
||||
return new Plane(this.normal.multiply(-1), - this.w);
|
||||
}
|
||||
|
|
@ -40,7 +42,7 @@ export class Plane extends Surface {
|
|||
}
|
||||
|
||||
get3DTransformation() {
|
||||
return new Matrix3().setBasis(this.calculateBasis());
|
||||
return new Matrix3().setBasis(this.basis());
|
||||
}
|
||||
|
||||
coplanarUnsigned(other, tol) {
|
||||
|
|
|
|||
62
web/app/brep/geom/path.js
Normal file
62
web/app/brep/geom/path.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import {Point} from './point'
|
||||
import {defineIterable} from '../../utils/utils'
|
||||
|
||||
class Path {
|
||||
|
||||
constructor(head) {
|
||||
this.head = head;
|
||||
defineIterable(this, 'segments', () => segmentsGenerator(this));
|
||||
}
|
||||
|
||||
isClosed() {
|
||||
return this.head.prev != null;
|
||||
}
|
||||
}
|
||||
|
||||
export class PathBuilder {
|
||||
constructor(head) {
|
||||
this.head = null;
|
||||
this.tail = null;
|
||||
defineIterable(this, 'segments', () => segmentsGenerator(this));
|
||||
}
|
||||
|
||||
addSegment(segment) {
|
||||
if (this.tail == null) {
|
||||
this.head = segment;
|
||||
} else {
|
||||
this.tail.next = segment;
|
||||
}
|
||||
segment.prev = this.tail;
|
||||
this.tail = segment;
|
||||
}
|
||||
|
||||
close() {
|
||||
this.tail.next = this.head;
|
||||
this.head.prev = this.tail;
|
||||
return this.head;
|
||||
}
|
||||
|
||||
unclosed() {
|
||||
return this.head;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Segment {
|
||||
constructor(curve, point) {
|
||||
this.curve = curve;
|
||||
this.point = point;
|
||||
this.next = null;
|
||||
this.prev = null;
|
||||
this.data = {};
|
||||
}
|
||||
}
|
||||
|
||||
export function* segmentsGenerator(path) {
|
||||
let node = path.head;
|
||||
while (node != null) {
|
||||
yield node;
|
||||
node = node.next;
|
||||
if (node == path.node) break;
|
||||
}
|
||||
}
|
||||
|
|
@ -29,9 +29,17 @@ export class HalfEdge extends TopoObject {
|
|||
this.prev = null;
|
||||
}
|
||||
|
||||
static create(a, b, loop, edge) {
|
||||
const e = new HalfEdge().setAB(a, b);
|
||||
e.loop = loop;
|
||||
e.edge = edge;
|
||||
return e;
|
||||
}
|
||||
|
||||
setAB(a, b) {
|
||||
this.vertexA = a;
|
||||
this.vertexB = b;
|
||||
return this;
|
||||
}
|
||||
|
||||
twin() {
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@ import * as math from './math'
|
|||
|
||||
export function LUT(a, b, cp1, cp2, scale) {
|
||||
scale = 1 / scale;
|
||||
const lut = [a];
|
||||
const lut = [];
|
||||
for (let t = 0; t < 1; t += 0.1 * scale) {
|
||||
const p = compute(t, a, b, cp1, cp2);
|
||||
lut.push(p);
|
||||
}
|
||||
lut.push(b);
|
||||
lut[0] = a;
|
||||
lut[lut.length - 1] = b;
|
||||
return lut;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,6 +70,10 @@ export function strictEqual(a, b) {
|
|||
return a.x == b.x && a.y == b.y && a.z == b.z;
|
||||
}
|
||||
|
||||
export function strictEqual2D(a, b) {
|
||||
return a.x == b.x && a.y == b.y;
|
||||
}
|
||||
|
||||
export function _vec(size) {
|
||||
var out = [];
|
||||
out.length = size;
|
||||
|
|
|
|||
|
|
@ -67,6 +67,11 @@ export function camelCaseSplit(str) {
|
|||
return words;
|
||||
}
|
||||
|
||||
export function defineIterable(obj, name, iteratorFactory) {
|
||||
obj[name] = {};
|
||||
obj[name][Symbol.iterator] = iteratorFactory;
|
||||
}
|
||||
|
||||
export class DoubleKeyMap {
|
||||
|
||||
constructor() {
|
||||
|
|
@ -98,4 +103,12 @@ export class DoubleKeyMap {
|
|||
}
|
||||
subMap.set(b, value);
|
||||
}
|
||||
}
|
||||
|
||||
export function reversedIndex(i, n) {
|
||||
let lidIdx = n - i;
|
||||
if (lidIdx == n) {
|
||||
lidIdx = 0;
|
||||
}
|
||||
return lidIdx;
|
||||
}
|
||||
Loading…
Reference in a new issue