Merge branch 'rel-4.0'
This commit is contained in:
commit
c486d98cbf
12 changed files with 223 additions and 8 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "grid-apps",
|
"name": "grid-apps",
|
||||||
"version": "4.0.7",
|
"version": "4.0.8",
|
||||||
"description": "grid.space 3d slice & modeling tools",
|
"description": "grid.space 3d slice & modeling tools",
|
||||||
"author": "Stewart Allen <sa@grid.space>",
|
"author": "Stewart Allen <sa@grid.space>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,25 @@ function sqr(v) {
|
||||||
return v * 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
|
* return distance between two points
|
||||||
*/
|
*/
|
||||||
|
|
@ -576,6 +595,7 @@ base.util = {
|
||||||
pwait,
|
pwait,
|
||||||
ptimer,
|
ptimer,
|
||||||
flatten,
|
flatten,
|
||||||
|
rotate,
|
||||||
dist2D,
|
dist2D,
|
||||||
distSq,
|
distSq,
|
||||||
distSqv2,
|
distSqv2,
|
||||||
|
|
@ -587,6 +607,8 @@ base.util = {
|
||||||
offsetPrecision,
|
offsetPrecision,
|
||||||
circleCenter,
|
circleCenter,
|
||||||
numOrDefault,
|
numOrDefault,
|
||||||
|
toRadians,
|
||||||
|
toDegrees,
|
||||||
thetaDiff,
|
thetaDiff,
|
||||||
intersect,
|
intersect,
|
||||||
inRange,
|
inRange,
|
||||||
|
|
|
||||||
|
|
@ -551,6 +551,13 @@ class Point {
|
||||||
this.z += delta.z;
|
this.z += delta.z;
|
||||||
return this;
|
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) {
|
function dot(u, v) {
|
||||||
|
|
|
||||||
|
|
@ -992,6 +992,20 @@ class Polygon {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {int[]} verts flat array of x,y,z vertices
|
||||||
|
*/
|
||||||
|
addVerts(verts) {
|
||||||
|
for (let i=0; i<verts.length; ) {
|
||||||
|
this.add(
|
||||||
|
verts[i++],
|
||||||
|
verts[i++],
|
||||||
|
verts[i++]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* append point to polygon and return point
|
* append point to polygon and return point
|
||||||
*/
|
*/
|
||||||
|
|
@ -1080,6 +1094,7 @@ class Polygon {
|
||||||
} else if (this.isClockwise() !== poly.isClockwise()) {
|
} else if (this.isClockwise() !== poly.isClockwise()) {
|
||||||
this.reverse();
|
this.reverse();
|
||||||
}
|
}
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1091,6 +1106,7 @@ class Polygon {
|
||||||
} else if (this.isClockwise() === poly.isClockwise()) {
|
} else if (this.isClockwise() === poly.isClockwise()) {
|
||||||
this.reverse();
|
this.reverse();
|
||||||
}
|
}
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ const { broker } = gapp;
|
||||||
const { moto } = root;
|
const { moto } = root;
|
||||||
const { space } = moto;
|
const { space } = moto;
|
||||||
|
|
||||||
const version = '1.3.0';
|
const version = '1.4.0';
|
||||||
const call = broker.send;
|
const call = broker.send;
|
||||||
const dbindex = [ "admin", "space" ];
|
const dbindex = [ "admin", "space" ];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -318,6 +318,48 @@ let add = {
|
||||||
});
|
});
|
||||||
api.modal.bound.gencyl.focus();
|
api.modal.bound.gencyl.focus();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
gear(opt = {}) {
|
||||||
|
function gengear(teeth, module, angle, twist, shaft) {
|
||||||
|
api.modal.hide();
|
||||||
|
worker.model_gen_gear({
|
||||||
|
teeth: parseInt(teeth),
|
||||||
|
module: parseFloat(module),
|
||||||
|
angle: parseFloat(angle),
|
||||||
|
twist: parseFloat(twist),
|
||||||
|
shaft: parseFloat(shaft),
|
||||||
|
}).then(gear => {
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ const modal = api.modal = {
|
||||||
let pinner;
|
let pinner;
|
||||||
// transient logging window bottom/left
|
// transient logging window bottom/left
|
||||||
const log = api.log = {
|
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
|
data: [], // last n messages
|
||||||
|
|
||||||
|
|
@ -187,7 +187,9 @@ const log = api.log = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// bind endpoint for worker to log in the ui
|
// 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
|
// welcome dialog
|
||||||
api.welcome = function(version = "unknown") {
|
api.welcome = function(version = "unknown") {
|
||||||
|
|
@ -373,6 +375,7 @@ function ui_build() {
|
||||||
h.div({ class: "pop"}, [
|
h.div({ class: "pop"}, [
|
||||||
h.button({ _: 'cylinder', onclick: add.cylinder }),
|
h.button({ _: 'cylinder', onclick: add.cylinder }),
|
||||||
h.button({ _: 'cube', onclick: add.cube }),
|
h.button({ _: 'cube', onclick: add.cube }),
|
||||||
|
h.button({ _: 'gear', onclick: add.gear }),
|
||||||
devel ? h.button({ _: 'input', onclick: add.input }) : undefined
|
devel ? h.button({ _: 'input', onclick: add.input }) : undefined
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
|
|
|
||||||
|
|
@ -737,6 +737,89 @@ mesh.tool = class MeshTool {
|
||||||
}
|
}
|
||||||
return out;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,10 @@
|
||||||
gapp.main("mesh.work", [], (root) => {
|
gapp.main("mesh.work", [], (root) => {
|
||||||
|
|
||||||
const { Matrix4, Vector3, BufferGeometry, BufferAttribute, computeFaceNormal } = THREE;
|
const { Matrix4, Vector3, BufferGeometry, BufferAttribute, computeFaceNormal } = THREE;
|
||||||
const { mesh, moto } = root;
|
const { base, mesh, moto } = root;
|
||||||
const { client, worker } = moto;
|
const { client, worker } = moto;
|
||||||
const { util } = mesh;
|
const { util } = mesh;
|
||||||
|
const { newPolygon } = base;
|
||||||
const cache = {};
|
const cache = {};
|
||||||
|
|
||||||
// add scoped access to cache
|
// add scoped access to cache
|
||||||
|
|
@ -397,6 +398,37 @@ let model = {
|
||||||
log(`${id} | rebuild complete`);
|
log(`${id} | rebuild complete`);
|
||||||
send.done({ lines: layers });
|
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<verts.length; i += 3) {
|
||||||
|
if (Math.abs(verts[i+2]) < 0.001) {
|
||||||
|
let [x, y] = base.util.rotate(
|
||||||
|
verts[i],
|
||||||
|
verts[i+1],
|
||||||
|
rad
|
||||||
|
);
|
||||||
|
verts[i] = x;
|
||||||
|
verts[i+1] = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
send.done(verts);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
let terms = {
|
let terms = {
|
||||||
COPYRIGHT: "Copyright (C) Stewart Allen <sa@grid.space> - All Rights Reserved",
|
COPYRIGHT: "Copyright (C) Stewart Allen <sa@grid.space> - All Rights Reserved",
|
||||||
LICENSE: "See the license.md file included with the source distribution",
|
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') {
|
if (typeof(module) === 'object') {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "Kiri:Moto 4.0.7",
|
"name": "Kiri:Moto 4.0.8",
|
||||||
"short_name": "Kiri:Moto",
|
"short_name": "Kiri:Moto",
|
||||||
"description": "Slicer for 3D printers, CNC mills, laser cutters and more",
|
"description": "Slicer for 3D printers, CNC mills, laser cutters and more",
|
||||||
"start_url": "/kiri/",
|
"start_url": "/kiri/",
|
||||||
|
|
|
||||||
|
|
@ -225,10 +225,20 @@ button:hover {
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.addact input {
|
.addact input, .addgear input {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.addgear {
|
||||||
|
gap: 5px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addgear button {
|
||||||
|
column-span: all;
|
||||||
|
}
|
||||||
|
|
||||||
.image-import {
|
.image-import {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 3px;
|
gap: 3px;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue