diff --git a/package.json b/package.json index 0e8743f5..f448a148 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "grid-apps", - "version": "4.0.7", + "version": "4.0.8", "description": "grid.space 3d slice & modeling tools", "author": "Stewart Allen ", "license": "MIT", diff --git a/src/geo/base.js b/src/geo/base.js index e84b0f49..415d7c94 100644 --- a/src/geo/base.js +++ b/src/geo/base.js @@ -109,6 +109,25 @@ function sqr(v) { return v * v } +// radians rotatition around origin +function rotate(x,y,radians) { + return [ + x * Math.cos(radians) - y * Math.sin(radians), + y * Math.cos(radians) + x * Math.sin(radians) + ]; +} + +const deg2rad = (Math.PI / 180); +const rad2deg = (180 / Math.PI); + +function toRadians(degrees) { + return degrees * deg2rad; +} + +function toDegrees(radians) { + return radians * rad2deg; +} + /** * return distance between two points */ @@ -576,6 +595,7 @@ base.util = { pwait, ptimer, flatten, + rotate, dist2D, distSq, distSqv2, @@ -587,6 +607,8 @@ base.util = { offsetPrecision, circleCenter, numOrDefault, + toRadians, + toDegrees, thetaDiff, intersect, inRange, diff --git a/src/geo/point.js b/src/geo/point.js index 1bf6133b..0c857a89 100644 --- a/src/geo/point.js +++ b/src/geo/point.js @@ -551,6 +551,13 @@ class Point { this.z += delta.z; return this; } + + // radians rotatition in XY around origin + rotate(angle) { + const { x, y } = this; + this.x = x * Math.cos(angle) - y * Math.sin(angle); + this.y = y * Math.cos(angle) + x * Math.sin(angle); + } } function dot(u, v) { diff --git a/src/geo/polygon.js b/src/geo/polygon.js index ee85691f..c251f66e 100644 --- a/src/geo/polygon.js +++ b/src/geo/polygon.js @@ -992,6 +992,20 @@ class Polygon { return this; } + /** + * @param {int[]} verts flat array of x,y,z vertices + */ + addVerts(verts) { + for (let i=0; i { + const nmdl = new mesh.model({ file: "gear", mesh: gear.toFloat32() }); + const ngrp = group.new([ nmdl ]); + ngrp.floor(); + }); + } + api.modal.dialog({ + title: "gear generator", + body: [ h.div({ class: "addgear" }, [ + h.label('number of teeth'), + h.input({ value: opt.teeth || 20, size: 5, id: "gteeth" }), + h.label('module (diam / teeth)'), + h.input({ value: opt.module || 3, size: 5, id: "gmodule" }), + h.label('pressure angle'), + h.input({ value: opt.angle || 20, size: 5, id: "gangle" }), + h.label('twist angle'), + h.input({ value: opt.twist || 0, size: 5, id: "gtwist" }), + h.label('shaft diameter'), + h.input({ value: opt.shaft || 5, size: 5, id: "gshaft" }), + h.button({ _: "create", onclick() { + gengear( + api.modal.bound.gteeth.value, + api.modal.bound.gmodule.value, + api.modal.bound.gangle.value, + api.modal.bound.gtwist.value, + api.modal.bound.gshaft.value, + ) + } }) + ]) ] + }); + api.modal.bound.gteeth.focus(); } }; diff --git a/src/mesh/build.js b/src/mesh/build.js index 53255861..13ac1d36 100644 --- a/src/mesh/build.js +++ b/src/mesh/build.js @@ -118,7 +118,7 @@ const modal = api.modal = { let pinner; // transient logging window bottom/left const log = api.log = { - age: 10000, // age out lines more than 10 seconds old + age: 600000, // age out lines more than 10 minutes old data: [], // last n messages @@ -187,7 +187,9 @@ const log = api.log = { }; // bind endpoint for worker to log in the ui -gapp.broker.subscribe("mesh.log", log.emit); +gapp.broker.subscribe("mesh.log", msg => { + log.emit(msg); +}); // welcome dialog api.welcome = function(version = "unknown") { @@ -373,6 +375,7 @@ function ui_build() { h.div({ class: "pop"}, [ h.button({ _: 'cylinder', onclick: add.cylinder }), h.button({ _: 'cube', onclick: add.cube }), + h.button({ _: 'gear', onclick: add.gear }), devel ? h.button({ _: 'input', onclick: add.input }) : undefined ]) ]), diff --git a/src/mesh/tool.js b/src/mesh/tool.js index 611a9bdc..f75c3438 100644 --- a/src/mesh/tool.js +++ b/src/mesh/tool.js @@ -737,6 +737,89 @@ mesh.tool = class MeshTool { } return out; } + + // returns points array for a polygon + // extrusion and twist is handled in work.js + generateGear(numTeeth, module, pressureAngle) { + // Adapted from: Public Domain Parametric Involute Spur Gear by Leemon Baird, 2011, Leemon@Leemon.com http://www.thingiverse.com/thing:5505 + // see also http://grabcad.com/questions/tutorial-how-to-model-involute-gears-in-solidworks-and-show-design-intent + + // const pitch_radius = (numTeeth * module) / 2; + const pi = Math.PI; + + // degrees to radians + function degrees_to_radians(theta) { return theta / 180 * pi; } + + // polar to cartesian + function polar(r, theta) { return [r * Math.sin(theta), r * Math.cos(theta)]; } + + // point on involute curve + function q6(b, s, t, d) { return polar(d, s * (iang(b, d) + t)); } + + // unwind this many degrees to go from r1 to r2 + function iang(r1, r2) { return Math.sqrt((r2 / r1) * (r2 / r1) - 1) - Math.acos(r1 / r2); } + + // radius a fraction f up the curved side of the tooth + function q7(f, r, b, r2, t, s) { return q6(b, s, t, (1 - f) * Math.max(b, r) + f * r2); } + + // lerp q7 0..1 with a given # steps + function qf(steps, r, b, r2, t, s) { + let res = []; + let step = 1/steps; + for (let f=0; f <= 1; f += step) { + res.push(q7(f, r, b, r2, t, s)); + } + return res; + } + + // rotate an array of 2d points + function rotate(points_array, angle) { + let answer = []; + for (let i = 0; i < points_array.length; i++) { + let x = points_array[i][0]; + let y = points_array[i][1]; + let xr = x * Math.cos(angle) - y * Math.sin(angle); + let yr = y * Math.cos(angle) + x * Math.sin(angle); + answer.push([xr, yr]); + } + return answer; + } + + // gear parameter setup + let mm_per_tooth = module * pi; // mm size of one gear tooth + let clearance = 0.1; // freedom between two gear centers + let backlash = 0.1; // freedom between two gear contact points + let pressure_angle = degrees_to_radians(pressureAngle); + let gear = []; + + // involute gear maker + { + let p = mm_per_tooth * numTeeth / pi / 2; // radius of pitch circle + let c = p + mm_per_tooth / pi - clearance; // radius of outer circle + let b = p * Math.cos(pressure_angle); // radius of base circle + let r = p - (c - p) - clearance; // radius of root circle + let t = mm_per_tooth / 2 - backlash / 2; // tooth thickness at pitch circle + let k = -iang(b, p) - t / 2 / p; // angle where involute meets base circle on side of tooth + let points = [ // [x,y] points for a single gear tooth + polar(r, -pi / numTeeth), + polar(r, r < b ? k : -pi / numTeeth), + ...qf(10, r, b, c, k, 1), + ...qf(10, r, b, c, k, -1).reverse(), + polar(r, r < b ? -k : pi / numTeeth), + // polar(r, 3.142 / numTeeth) // omit b/c overlaps start when rotated + ]; + + // create all gear teeth by rotating the first tooth + for (var i = 0; i < numTeeth; i++) { + gear.appendAll(rotate(points, -i * 2 * pi / numTeeth)); + } + + mesh.log(`gear pitch radius: ${p.round(3)}`); + } + + return gear; + } + }; }); diff --git a/src/mesh/work.js b/src/mesh/work.js index 313cdeb6..75afc371 100644 --- a/src/mesh/work.js +++ b/src/mesh/work.js @@ -21,9 +21,10 @@ gapp.main("mesh.work", [], (root) => { const { Matrix4, Vector3, BufferGeometry, BufferAttribute, computeFaceNormal } = THREE; -const { mesh, moto } = root; +const { base, mesh, moto } = root; const { client, worker } = moto; const { util } = mesh; +const { newPolygon } = base; const cache = {}; // add scoped access to cache @@ -397,6 +398,37 @@ let model = { log(`${id} | rebuild complete`); send.done({ lines: layers }); });; + }, + + gen_gear(data, send) { + const { teeth, module, angle, twist, shaft, z } = data; + let points = new mesh.tool() + .generateGear(teeth, module, angle) + .map(v => [...v, 0]).flat(); + let poly = newPolygon().addVerts(points); + if (shaft) { + poly.addInner( + newPolygon().centerCircle({ + x:0, y:0, z:0 + }, shaft / 2, Math.min(20 + shaft,100)) + ); + } + let verts = poly.extrude(z || 15); + if (twist) { + let rad = base.util.toRadians(twist); + for (let i=0; i - All Rights Reserved", LICENSE: "See the license.md file included with the source distribution", - VERSION: (is_self ? self : this).debug_version || "4.0.7" + VERSION: (is_self ? self : this).debug_version || "4.0.8" }; if (typeof(module) === 'object') { diff --git a/web/kiri/manifest.json b/web/kiri/manifest.json index a207a50a..fc0a46cb 100644 --- a/web/kiri/manifest.json +++ b/web/kiri/manifest.json @@ -1,5 +1,5 @@ { - "name": "Kiri:Moto 4.0.7", + "name": "Kiri:Moto 4.0.8", "short_name": "Kiri:Moto", "description": "Slicer for 3D printers, CNC mills, laser cutters and more", "start_url": "/kiri/", diff --git a/web/mesh/index.css b/web/mesh/index.css index 00c0cfab..bf3defb0 100644 --- a/web/mesh/index.css +++ b/web/mesh/index.css @@ -225,10 +225,20 @@ button:hover { gap: 4px; } -.addact input { +.addact input, .addgear input { text-align: center; } +.addgear { + gap: 5px; + display: grid; + grid-template-columns: 1fr 50px; +} + +.addgear button { + column-span: all; +} + .image-import { flex-direction: column; gap: 3px;