diff --git a/web/app/3d/main.js b/web/app/3d/main.js index 92b3fa7a..c93f498f 100644 --- a/web/app/3d/main.js +++ b/web/app/3d/main.js @@ -63,7 +63,87 @@ TCAD.App.prototype.sketchFace = function() { } else { data = JSON.parse(savedFace); } - data.boundary = polyFace.polygon.to2D(); + data.boundary = {lines : [], arcs : []}; + function sameSketchObject(a, b) { + if (a.sketchConnectionObject === undefined || b.sketchConnectionObject === undefined) { + return false; + } + return a.sketchConnectionObject.id === b.sketchConnectionObject.id; + } + var paths = []; + polyFace.polygon.collectPaths(paths); + var _2dTr = polyFace.polygon.get2DTransformation(); + for (var i = 0; i < paths.length; i++) { + var path = paths[i]; + var shift = 0; + TCAD.utils.iteratePath(path, 0, function(a, b, ai, bi) { + shift = bi; + return sameSketchObject(a, b); + }); + + function addSegment(a, b) { + data.boundary.lines.push({ + a : {x : a.x, y: a.y}, + b : {x : b.x, y: b.y} + }); + } + function addArc(arc) { + if (arc.length < 2) { + return; + } + var a = arc[0], b = arc[arc.length - 1]; + if (arc.length == 2) { + addSegment(a, b); + return; + } + var mid = (arc.length / 2) >> 0; + var c = TCAD.math.circleFromPoints(arc[0], arc[mid], arc[arc.length-1]); + if (c == null) { + return; + } + if (!TCAD.geom.isCCW([arc[0], arc[mid], arc[arc.length-1]])) { + var t = a; + a = b; + b = t; + } + data.boundary.arcs.push({ + a : {x : a.x, y: a.y}, + b : {x : b.x, y: b.y}, + c : {x : c.x, y : c.y} + }); + } + var currSko = null; + var arc = null; + TCAD.utils.iteratePath(path, shift+1, function(a, b, ai, bi, iterNumber, path) { + var isArc = a.sketchConnectionObject !== undefined && a.sketchConnectionObject._class == 'TCAD.TWO.Arc'; + var a2d = _2dTr.apply(a); + if (isArc) { + if (currSko !== a.sketchConnectionObject.id) { + currSko = a.sketchConnectionObject.id; + if (arc != null) { + arc.push(a2d); + addArc(arc); + } + arc = []; + } + arc.push(a2d); + if (iterNumber === path.length - 1) { + arc.push(_2dTr.apply(b)); + addArc(arc); + } + } else { + if (arc != null) { + arc.push(a2d); + addArc(arc); + arc = null; + } + currSko = null; + addSegment(a2d, _2dTr.apply(b)); + } + return true; + }); + } + localStorage.setItem(faceStorageKey, JSON.stringify(data)); window.open("sketcher.html#" + faceStorageKey.substring(14), "Edit Sketch", "height=900,width=1200"); diff --git a/web/app/engine.js b/web/app/engine.js index 92b9842f..ea7af5e0 100644 --- a/web/app/engine.js +++ b/web/app/engine.js @@ -172,14 +172,18 @@ TCAD.utils.isPointInsidePolygon = function( inPt, inPolygon ) { TCAD.utils.sketchToPolygons = function(geom) { var dict = {}; - var lines = geom.lines; + var lines = geom.connections; function key(a) { - return a[0] + ":" + a[1]; + return a.x + ":" + a.y; + } + function edgeKey(a, b) { + return key(a) + ":" + key(b); } var size = 0; var points = []; + var edges = {}; function memDir(a, b) { var ak = key(a); var dirs = dict[ak]; @@ -192,10 +196,11 @@ TCAD.utils.sketchToPolygons = function(geom) { } for (var i = 0; i < lines.length; i++) { - var a = lines[i].slice(0,2); - var b = lines[i].slice(2,4); + var a = lines[i].a; + var b = lines[i].b; memDir(a, b); memDir(b, a); + edges[edgeKey(a, b)] = lines[i]; } var graph = { @@ -223,11 +228,18 @@ TCAD.utils.sketchToPolygons = function(geom) { var polyPoints = []; for (var pi = 0; pi < loop.length; ++pi) { var point = loop[pi]; - polyPoints.push(new TCAD.Vector(point[0], point[1], 0)); + var next = loop[(pi + 1) % loop.length]; + var edge = edges[edgeKey(point, next)]; + if (edge === undefined) { + edge = edges[edgeKey(next, point)]; + } + polyPoints.push(point); + point.sketchConnectionObject = edge.sketchObject; } if (polyPoints.length >= 3) { - polygons.push(new TCAD.Polygon(polyPoints)); + var polygon = new TCAD.Polygon(polyPoints); + polygons.push(polygon); } else { console.warn("Points count < 3!"); } @@ -303,6 +315,7 @@ TCAD.geom.extrude = function(source, target) { lidShell[p], lidShell[i] ]); + face.csgInfo = {derivedFrom: source.shell[i].sketchConnectionObject}; poly.push(face); } return poly; @@ -418,11 +431,12 @@ TCAD.SketchFace.prototype.syncSketches = function(geom) { var _3dTransformation = new TCAD.Matrix().setBasis(TCAD.geom.someBasis(this.polygon.shell, normal)); //we lost depth or z off in 2d sketch, calculate it again var depth = normal.dot(this.polygon.shell[0]); - for (i = 0; i < geom.lines.length; ++i) { - var l = geom.lines[i]; + for (i = 0; i < geom.connections.length; ++i) { + var l = geom.connections[i]; var lg = new THREE.Geometry(); - var a = _3dTransformation.apply(new TCAD.Vector(l[0], l[1], depth)); - var b = _3dTransformation.apply(new TCAD.Vector(l[2], l[3], depth)); + l.a.z = l.b.z = depth; + var a = _3dTransformation.apply(l.a); + var b = _3dTransformation.apply(l.b); lg.vertices.push(a.plus(offVector).three()); lg.vertices.push(b.plus(offVector).three()); @@ -489,11 +503,15 @@ TCAD.Polygon.prototype.shift = function(target) { return new TCAD.Polygon(shell, holes, this.normal); }; +TCAD.Polygon.prototype.get2DTransformation = function() { + var _3dTransformation = new TCAD.Matrix().setBasis(TCAD.geom.someBasis(this.shell, this.normal)); + var _2dTransformation = _3dTransformation.invert(); + return _2dTransformation; +}; TCAD.Polygon.prototype.to2D = function() { - var _3dTransformation = new TCAD.Matrix().setBasis(TCAD.geom.someBasis(this.shell, this.normal)); - var _2dTransformation = _3dTransformation.invert(); + var _2dTransformation = this.get2DTransformation(); var i, h; var shell = []; @@ -510,6 +528,11 @@ TCAD.Polygon.prototype.to2D = function() { return {shell: shell, holes: holes}; }; +TCAD.Polygon.prototype.collectPaths = function(paths) { + paths.push(this.shell); + paths.push.apply(paths, this.holes); +}; + TCAD.Polygon.prototype.triangulate = function() { function triangulateShape( contour, holes ) { @@ -547,4 +570,15 @@ TCAD.Polygon.prototype.eachVertex = function(handler) { /** @constructor */ TCAD.Sketch = function() { this.group = new THREE.Object3D(); -}; \ No newline at end of file +}; + +TCAD.utils.iteratePath = function(path, shift, callback) { + var p, q, n = path.length; + for (p = n - 1,q = 0;q < n; p = q++) { + var ai = (p + shift) % n; + var bi = (q + shift) % n; + if (!callback(path[ai], path[bi], ai, bi, q, path)) { + break + } + } +}; diff --git a/web/app/math/math.js b/web/app/math/math.js index 682c4cfc..26d7d225 100644 --- a/web/app/math/math.js +++ b/web/app/math/math.js @@ -81,4 +81,20 @@ TCAD.math.rotateMatrix = function(angle, axis, pivot) { m.mzz = cos + axisZ * axisZ * (1 - cos); m.tz = pz * (1 - m.mzz) - px * m.mzx - py * m.mzy; return m; +}; + +TCAD.math.circleFromPoints = function(p1, p2, p3) { + var center = new TCAD.Vector(); + var offset = p2.x*p2.x + p2.y*p2.y; + var bc = ( p1.x*p1.x + p1.y*p1.y - offset )/2.0; + var cd = (offset - p3.x*p3.x - p3.y*p3.y)/2.0; + var det = (p1.x - p2.x) * (p2.y - p3.y) - (p2.x - p3.x)* (p1.y - p2.y); + + if (Math.abs(det) < TCAD.TOLERANCE) { return null; } + + var idet = 1/det; + + center.x = (bc * (p2.y - p3.y) - cd * (p1.y - p2.y)) * idet; + center.y = (cd * (p1.x - p2.x) - bc * (p2.x - p3.x)) * idet; + return center; }; \ No newline at end of file diff --git a/web/app/math/vector.js b/web/app/math/vector.js index 8d8b428e..eb5e5692 100644 --- a/web/app/math/vector.js +++ b/web/app/math/vector.js @@ -27,6 +27,10 @@ TCAD.Vector.prototype.setV = function(data) { return this; }; +TCAD.Vector.prototype.asKey = function() { + return this.x + ":" + this.y + ":" + this.z +}; + TCAD.Vector.prototype.multiply = function(scalar) { return new TCAD.Vector(this.x * scalar, this.y * scalar, this.z * scalar); }; diff --git a/web/app/sketcher/io.js b/web/app/sketcher/io.js index 6a27c86e..7b0af7e1 100644 --- a/web/app/sketcher/io.js +++ b/web/app/sketcher/io.js @@ -213,50 +213,44 @@ TCAD.IO.prototype.updateBoundary = function (boundary) { this.boundaryLayer.readOnly = true; this.viewer.layers.splice(0, 0, this.boundaryLayer); } - var edges = []; - var bbox = [Number.MAX_VALUE, Number.MAX_VALUE, - Number.MAX_VALUE, - Number.MAX_VALUE]; - var flattenPolygon = function(points) { - var n = points.length; - for ( var p = n - 1, q = 0; q < n; p = q ++ ) { - edges.push([points[p].x, points[p].y, points[q].x, points[q].y]); - bbox[0] = Math.min(bbox[0], points[p].x); - bbox[1] = Math.min(bbox[1], points[p].y); - bbox[2] = Math.max(bbox[2], points[q].x); - bbox[3] = Math.max(bbox[3], points[q].y); - } - }; - - flattenPolygon(boundary.shell); - for (var i = 0; i < boundary.holes.length; ++i ) { - flattenPolygon(boundary.holes[i]); - } // if (bbox[0] < Number.MAX_VALUE && bbox[1] < Number.MAX_VALUE && -bbox[2] < Number.MAX_VALUE && -bbox[3] < Number.MAX_VALUE) { // this.viewer.showBounds(bbox[0], bbox[1], bbox[2], bbox[3]) // } - for (var l = 0; l < this.viewer.layers.length; ++l) { - var layer = this.viewer.layers[l]; - for (var i = 0; i < layer.objects.length; ++i) { - var obj = layer.objects[i]; - if (obj.edge !== undefined) { - var edge = edges[obj.edge]; - if (edge !== undefined && edge != null) { - obj.a.x = edge[0]; - obj.a.y = edge[1]; - obj.b.x = edge[2]; - obj.b.y = edge[3]; - edges[obj.edge] = null; - } - } - } + //for (var l = 0; l < this.viewer.layers.length; ++l) { + // var layer = this.viewer.layers[l]; + // for (var i = 0; i < layer.objects.length; ++i) { + // var obj = layer.objects[i]; + // if (obj.edge !== undefined) { + // var edge = edges[obj.edge]; + // if (edge !== undefined && edge != null) { + // obj.a.x = edge[0]; + // obj.a.y = edge[1]; + // obj.b.x = edge[2]; + // obj.b.y = edge[3]; + // edges[obj.edge] = null; + // } + // } + // } + //} + + var id, i = 0; + for (i = 0; i < boundary.lines.length; ++i, ++id) { + var edge = boundary.lines[i]; + var seg = this.viewer.addSegment(edge.a.x, edge.a.y, edge.b.x, edge.b.y, this.boundaryLayer); + seg.accept(function(o){o.aux = true; return true;}); + seg.edge = id ++; } - for (var i = 0; i < edges.length; ++i ) { - var edge = edges[i]; - if (edge != null) { - var seg = this.viewer.addSegment(edge[0], edge[1], edge[2], edge[3], this.boundaryLayer); - seg.accept(function(o){o.aux = true; return true;}); - seg.edge = i; - } + for (i = 0; i < boundary.arcs.length; ++i, ++id) { + var a = boundary.arcs[i]; + var arc = new TCAD.TWO.Arc( + new TCAD.TWO.EndPoint(a.a.x, a.a.y), + new TCAD.TWO.EndPoint(a.b.x, a.b.y), + new TCAD.TWO.EndPoint(a.c.x, a.c.y) + ); + this.boundaryLayer.objects.push(arc); + arc.accept(function(o){o.aux = true; return true;}); + arc.edge = id ++; } }; diff --git a/web/app/workbench.js b/web/app/workbench.js index 217a02ac..39807bf7 100644 --- a/web/app/workbench.js +++ b/web/app/workbench.js @@ -1,25 +1,38 @@ TCAD.workbench = {}; +TCAD.workbench.SketchConnection = function(a, b, sketchObject) { + this.a = a; + this.b = b; + this.sketchObject = sketchObject; +}; + TCAD.workbench.readSketchGeom = function(sketch) { - var out = {lines : [], circles : [], arcs : []}; + var out = {connections : []}; + var id = 0; if (sketch.layers !== undefined) { for (var l = 0; l < sketch.layers.length; ++l) { for (var i = 0; i < sketch.layers[l].data.length; ++i) { var obj = sketch.layers[l].data[i]; if (obj.edge !== undefined) continue; if (!!obj.aux) continue; + var a = new TCAD.Vector(obj.points[0][1][1], obj.points[0][2][1], 0); + var b = new TCAD.Vector(obj.points[1][1][1], obj.points[1][2][1], 0); if (obj._class === 'TCAD.TWO.Segment') { - out.lines.push([ - obj.points[0][1][1], obj.points[0][2][1], //x,y - obj.points[1][1][1], obj.points[1][2][1] //x,y - ]); + out.connections.push(new TCAD.workbench.SketchConnection( + a, b, {_class : obj._class, id : id++} + )); } else if (obj._class === 'TCAD.TWO.Arc') { - out.lines.push.apply(out.lines, TCAD.workbench.integrate( - [obj.points[0][1][1], obj.points[0][2][1]], - [obj.points[1][1][1], obj.points[1][2][1]], - [obj.points[2][1][1], obj.points[2][2][1]], - 20 - )); + var center = new TCAD.Vector(obj.points[2][1][1], obj.points[2][2][1], 0); + var approxArc = TCAD.workbench.approxArc(a, b, center, 20); + var data = {_class : obj._class, id : id++}; + for (var j = 0; j < approxArc.length - 1; j++) { + var pa = approxArc[j]; + var pb = approxArc[j+1]; + out.connections.push(new TCAD.workbench.SketchConnection( + pa, pb, data + )); + } + } else if (obj._class === 'TCAD.TWO.Circle') { } } @@ -28,13 +41,10 @@ TCAD.workbench.readSketchGeom = function(sketch) { return out; }; -TCAD.workbench.integrate = function(_a, _b, _c, k) { - var ao = new TCAD.Vector(_a[0], _a[1], 0); - var bo = new TCAD.Vector(_b[0], _b[1], 0); - var c = new TCAD.Vector(_c[0], _c[1], 0); +TCAD.workbench.approxArc = function(ao, bo, c, k) { var a = ao.minus(c); var b = bo.minus(c); - var points = [[ao.x, ao.y]]; + 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; @@ -44,21 +54,15 @@ TCAD.workbench.integrate = function(_a, _b, _c, k) { var angle = Math.atan2(a.y, a.x) + step; for (var i = 0; i < k - 2; ++i) { - points.push([c.x + r*Math.cos(angle), c.y + r*Math.sin(angle)]); + points.push(new TCAD.Vector(c.x + r*Math.cos(angle), c.y + r*Math.sin(angle))); angle += step; } - points.push([bo.x, bo.y]); - var lines = []; - for (var i = 0; i < points.length - 1; i++) { - var p1 = points[i]; - var p2 = points[i + 1]; - lines.push([p1[0], p1[1], p2[0], p2[1]]); - } - return lines; + points.push(bo); + return points; }; TCAD.workbench.serializeSolid = function(solid) { - data = {}; + var data = {}; data.faceCounter = TCAD.geom.FACE_COUNTER; for (var fi = 0; fi < solid.faces.length; ++fi) { var face = solid.faces[fi]; @@ -129,9 +133,11 @@ TCAD.craft.getSketchedPolygons3D = function(app, face) { vec.z = depth; // var a = _3dTransformation.apply(new TCAD.Vector(poly2D[m][0], poly2D[m][1], depth)); var a = _3dTransformation.apply(vec); - shell.push(a) + a.sketchConnectionObject = vec.sketchConnectionObject; + shell.push(a); } - sketchedPolygons.push(new TCAD.Polygon(shell)); + var polygon = new TCAD.Polygon(shell); + sketchedPolygons.push(polygon); } return sketchedPolygons; }; @@ -195,7 +201,7 @@ TCAD.craft._mergeCSGPolygonsTest = function() { console.log(paths); }; -TCAD.craft._mergeCSGPolygons = function (__cgsPolygons) { +TCAD.craft._mergeCSGPolygons = function (__cgsPolygons, allPoints) { var pointToPoly = {}; var points = []; var pkey = TCAD.craft.pkey; @@ -231,6 +237,7 @@ TCAD.craft._mergeCSGPolygons = function (__cgsPolygons) { vertices: cp.vertices.map(function (cv) { return vec(cv.pos) }), + csgInfo : cp.shared.__tcad, normal: vec(cp.plane.normal), w: cp.plane.w }; @@ -243,12 +250,13 @@ TCAD.craft._mergeCSGPolygons = function (__cgsPolygons) { for (var vi = 0; vi < poly.vertices.length; ++vi) { var vert = poly.vertices[vi]; points.push(vert); + allPoints.push(vert); } } var tol = 1E-6; - for (var i = 0; i < points.length; i++) { - var a = points[i]; + for (var i = 0; i < allPoints.length; i++) { + var a = allPoints[i]; for (var j = i + 1; j < points.length; j++) { var b = points[j]; if ( @@ -508,6 +516,7 @@ TCAD.craft._mergeCSGPolygons = function (__cgsPolygons) { visited[pnkey(pCurr, normal)] = true; var foundNext = true; + var csgInfo; while (foundNext) { foundNext = false; path.push(vec(pCurr)); @@ -515,6 +524,7 @@ TCAD.craft._mergeCSGPolygons = function (__cgsPolygons) { POLY: for (pi = 0; pi < gons.length; pi++) { poly = gons[pi]; + csgInfo = poly.csgInfo; if (normalKey != pkey(poly.normal)) continue; var dirs = getDirs(poly.vertices, keyCurr); if (dirs == null) continue; @@ -543,7 +553,8 @@ TCAD.craft._mergeCSGPolygons = function (__cgsPolygons) { paths.push({ vertices : path, normal : normal, - w : w + w : w, + csgInfo : csgInfo }); } } @@ -619,6 +630,7 @@ TCAD.craft._makeFromPolygons = function(polygons) { } var pid = poly.id; var shared = new CSG.Polygon.Shared([pid, pid, pid, pid]); + shared.__tcad = poly.csgInfo; var refs = poly.triangulate(); for ( var i = 0; i < refs.length; ++ i ) { var a = refs[i][0] + off; @@ -639,6 +651,43 @@ TCAD.craft._makeFromPolygons = function(polygons) { return csgPolygons; }; +TCAD.craft.recoverySketchInfo = function(polygons) { + var nonStructuralGons = []; + var sketchEdges = {}; + function key(a, b) {return a.asKey() + ":" + b.asKey()} + + for (var pi = 0; pi < polygons.length; pi++) { + var poly = polygons[pi]; + var paths = []; + poly.collectPaths(paths); + for (var i = 0; i < paths.length; i++) { + var path = paths[i]; + if (poly.csgInfo !== undefined && poly.csgInfo.derivedFrom !== undefined) { + var n = path.length; + for (var p = n - 1, q = 0; q < n ; p = q++ ) { + sketchEdges[key(path[p], path[q])] = poly.csgInfo; + } + } else { + nonStructuralGons.push(path); + } + } + } + + for (var i = 0; i < nonStructuralGons.length; i++) { + var path = nonStructuralGons[i]; + var n = path.length; + for (var p = n - 1, q = 0; q < n ; p = q++ ) { + var csgInfo = sketchEdges[key(path[p], path[q])]; + if (!csgInfo) { + csgInfo = sketchEdges[key(path[q], path[p])]; + } + if (csgInfo) { + path[p].sketchConnectionObject = csgInfo.derivedFrom; + } + } + } +}; + TCAD.craft.cut = function(app, face, faces, height) { var sketchedPolygons = TCAD.craft.getSketchedPolygons3D(app, face); @@ -703,7 +752,8 @@ TCAD.craft.cut = function(app, face, faces, height) { return { vertices : path.vertices.map(function(v) {return tr.apply(v);}), normal : path.normal, - w : path.w + w : path.w, + csgInfo : path.csgInfo } }); @@ -782,15 +832,21 @@ TCAD.craft.cut = function(app, face, faces, height) { byShared[tag].push(p); } var result = []; + var allPoints = []; for (var tag in byShared) { - var merged = TCAD.craft._mergeCSGPolygons(byShared[tag]); + var merged = TCAD.craft._mergeCSGPolygons(byShared[tag], allPoints); var sorted = sortPaths(merged); result.push.apply(result, sorted.map(function(path) { - return new TCAD.Polygon(path.vertices, path.holes.map(function(path){return path.vertices}), path.normal); + var p = new TCAD.Polygon(path.vertices, path.holes.map(function (path) { + return path.vertices + }), path.normal); + p.csgInfo = path.csgInfo; + return p; }) ); - } + TCAD.craft.recoverySketchInfo(result); + return result; };