Merge branch 'master' of https://github.com/GridSpace/grid-apps into drill-optimization
This commit is contained in:
commit
efb41e14fa
128 changed files with 8990 additions and 4345 deletions
|
|
@ -168,7 +168,7 @@ SP.init = function(options = {}) {
|
|||
request = IDB.open(name, this.version);
|
||||
|
||||
request.onupgradeneeded = function(event) {
|
||||
let db = storage.db = request.result;
|
||||
let db = request.result;
|
||||
let current = [...db.objectStoreNames];
|
||||
let stores = storage.stores;
|
||||
// add missing stores
|
||||
|
|
@ -184,13 +184,12 @@ SP.init = function(options = {}) {
|
|||
// remove obsolete stores
|
||||
for (let curr of current) {
|
||||
if (stores.indexOf(curr) < 0) {
|
||||
storage.db.deleteObjectStore(curr);
|
||||
db.deleteObjectStore(curr);
|
||||
console.log({index_dropped: curr});
|
||||
}
|
||||
}
|
||||
event.target.transaction.oncomplete = function(event) {
|
||||
storage.runQueue();
|
||||
};
|
||||
// allow transactions
|
||||
storage.db = db;
|
||||
};
|
||||
|
||||
request.onsuccess = function(event) {
|
||||
|
|
@ -217,6 +216,7 @@ SP.runQueue = function() {
|
|||
let i = 0, q = this.queue, e;
|
||||
while (i < q.length) {
|
||||
e = q[i++];
|
||||
// console.log('runQ', i, ...e);
|
||||
switch (e.shift()) {
|
||||
case 'iterate': this.iterate(...e); break;
|
||||
case 'keys': this.keys(...e); break;
|
||||
|
|
@ -252,7 +252,11 @@ SP.put = function(key, value, callback, store = this.current) {
|
|||
req.onerror = function(event) { callback(false) };
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
if (e.name === 'InvalidStateError') {
|
||||
console.log({ putRetry: key });
|
||||
return this.queue.push(['put', key, value, callback, store]);
|
||||
}
|
||||
console.log(e.name, e);
|
||||
if (callback) callback(false);
|
||||
}
|
||||
};
|
||||
|
|
@ -275,6 +279,10 @@ SP.get = function(key, callback, store = this.current) {
|
|||
req.onerror = function(event) { callback(null) };
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.name === 'InvalidStateError') {
|
||||
console.log({ getRetry: key });
|
||||
return this.queue.push(['get', key, callback, store]);
|
||||
}
|
||||
console.log(e);
|
||||
if (callback) callback(null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@
|
|||
import './pngjs.js';
|
||||
|
||||
// Re-export the PNG object
|
||||
export const PNG = self.png ? self.png.PNG : null;
|
||||
export const PNG = globalThis.png ? globalThis.png.PNG : null;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.png = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
(function(f){globalThis.png = f();if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.png = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
(function (Buffer){
|
||||
"use strict";
|
||||
|
||||
|
|
|
|||
|
|
@ -127,6 +127,12 @@ class Point {
|
|||
return p;
|
||||
}
|
||||
|
||||
// annotate instance with other field data (see clone())
|
||||
annotate(obj = {}) {
|
||||
Object.assign(this, obj);
|
||||
return this;
|
||||
}
|
||||
|
||||
slopeTo(p) {
|
||||
return newSlope(this, p);
|
||||
}
|
||||
|
|
@ -441,10 +447,6 @@ class Point {
|
|||
return this === p || (this.x === p.x && this.y === p.y && this.z === p.z);
|
||||
}
|
||||
|
||||
isEqual2D(p) {
|
||||
return this === p || (this.x === p.x && this.y === p.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if points are close enough to be considered equivalent
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -908,6 +908,7 @@ export class Polygon {
|
|||
* todo make more efficient
|
||||
*
|
||||
* @param {Boolean} deep
|
||||
* @param {String[]} fields to copy beyond class fields
|
||||
* @returns {Polygon}
|
||||
*/
|
||||
clone(deep, fields) {
|
||||
|
|
@ -2145,6 +2146,7 @@ export class Polygon {
|
|||
return null;
|
||||
}
|
||||
|
||||
// annotate instance with other field data (see clone())
|
||||
annotate(obj = {}) {
|
||||
Object.assign(this, obj);
|
||||
return this;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import { polygons } from '../geo/polygons.js';
|
|||
import { newPoint } from '../geo/point.js';
|
||||
import { config } from '../geo/base.js';
|
||||
|
||||
const epsilon = 10e-5;
|
||||
|
||||
function dval(v, dv) {
|
||||
return v !== undefined ? v : dv;
|
||||
}
|
||||
|
|
@ -75,7 +77,7 @@ export async function slice(points, options = {}) {
|
|||
// used to calculate buckets (rough sum of z span)
|
||||
zSum += (Math.abs(p1.z - p2.z) + Math.abs(p2.z - p3.z) + Math.abs(p3.z - p1.z));
|
||||
// use co-flat and co-line detection to adjust slice Z
|
||||
if (p1.z === p2.z && p2.z === p3.z && p1.z >= zMin) {
|
||||
if (Math.abs(p1.z - p2.z) < epsilon && Math.abs(p2.z - p3.z) < epsilon && p1.z >= zMin) {
|
||||
// detect faces co-planar with Z and sum the enclosed area
|
||||
let zkey = p1.z,
|
||||
area = Math.abs(base.util.area2(p1,p2,p3)) / 2;
|
||||
|
|
|
|||
|
|
@ -60,13 +60,6 @@ export class Slope {
|
|||
return this;
|
||||
}
|
||||
|
||||
invert() {
|
||||
this.dx = -this.dx;
|
||||
this.dy = -this.dy;
|
||||
this.angle = 180 - this.angle;
|
||||
return this;
|
||||
}
|
||||
|
||||
angleDiff(s2,sign) {
|
||||
const n1 = this.angle;
|
||||
const n2 = s2.angle;
|
||||
|
|
|
|||
1
src/gpu/raster-worker.js
Symbolic link
1
src/gpu/raster-worker.js
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../node_modules/@gridspace/raster-path/build/raster-worker.js
|
||||
1
src/gpu/raster.js
Symbolic link
1
src/gpu/raster.js
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../node_modules/@gridspace/raster-path/build/raster-path.js
|
||||
|
|
@ -9,7 +9,7 @@ import { client as work } from './client.js';
|
|||
import { consts, COLOR as color, LISTS as lists } from './consts.js';
|
||||
import { device, devices } from './devices.js';
|
||||
import { catalog, dialog, event, group, help, hide, image } from './main.js';
|
||||
import { modal, mode, probe, process, show, space, util, view } from './main.js';
|
||||
import { modal, mode, process, show, space, util, view } from './main.js';
|
||||
import { local as dataLocal } from '../../data/local.js';
|
||||
import { noop, ajax, o2js, js2o } from './utils.js';
|
||||
import { functions } from './function.js';
|
||||
|
|
@ -96,6 +96,7 @@ let UC = UI.prefix('kiri').inputAction(settings.conf.update),
|
|||
put: (key, val) => localSet(key, val),
|
||||
set: (key, val) => localSet(key, val),
|
||||
setItem: (key, val) => localSet(key, val),
|
||||
removeItem: (key) => localRemove(key)
|
||||
},
|
||||
tweak = {
|
||||
line_precision(v) { api.work.config({ base: { clipperClean: v } }) },
|
||||
|
|
@ -119,6 +120,11 @@ function localSet(key, val) {
|
|||
return val;
|
||||
}
|
||||
|
||||
function localRemove(key) {
|
||||
let sloc = api.conf.get().local;
|
||||
return delete sloc[key];
|
||||
}
|
||||
|
||||
export const api = {
|
||||
ajax,
|
||||
beta,
|
||||
|
|
@ -137,6 +143,7 @@ export const api = {
|
|||
dialog,
|
||||
doit,
|
||||
event,
|
||||
electron: navigator.userAgent.includes('Electron'),
|
||||
feature,
|
||||
function: functions,
|
||||
group,
|
||||
|
|
@ -156,7 +163,6 @@ export const api = {
|
|||
o2js,
|
||||
onkey,
|
||||
platform,
|
||||
probe,
|
||||
process,
|
||||
sdb: dataLocal,
|
||||
selection,
|
||||
|
|
|
|||
|
|
@ -225,7 +225,9 @@ Widget.prototype.encode = function(state) {
|
|||
json: json, // safe for JSON (float32array mess)
|
||||
group: this.group.id,
|
||||
track: this.track,
|
||||
geo: json ? Array.from(geo) : geo
|
||||
geo: json ? Array.from(geo) : geo,
|
||||
meta: this.meta,
|
||||
anno: this.anno,
|
||||
};
|
||||
return coded;
|
||||
};
|
||||
|
|
@ -234,8 +236,12 @@ registerDecoder(TYPE.WIDGET, function(v, state) {
|
|||
const id = v.id,
|
||||
group = v.group || id,
|
||||
track = v.track || undefined,
|
||||
widget = newWidget(id, Widget.Groups.forid(group));
|
||||
widget = newWidget(id, Widget.Groups.forid(group)),
|
||||
meta= v.meta || widget.meta,
|
||||
anno = v.anno || widget.anno;
|
||||
|
||||
widget.meta = meta;
|
||||
widget.anno = anno;
|
||||
widget.loadVertices(v.json ? v.geo.toFloat32() : v.geo);
|
||||
widget.saved = Date.now();
|
||||
if (track && track.pos) {
|
||||
|
|
|
|||
|
|
@ -182,33 +182,33 @@ function normalize(settings) {
|
|||
|
||||
// auto field renaming on import
|
||||
const renamed = {
|
||||
roughingTool: "camRoughTool",
|
||||
roughingSpindle: "camRoughSpindle",
|
||||
roughingDown: "camRoughDown",
|
||||
roughingOver: "camRoughOver",
|
||||
roughingSpeed: "camRoughSpeed",
|
||||
roughingPlunge: "camRoughPlunge",
|
||||
roughingStock: "camRoughStock",
|
||||
roughingPocket: "camRoughVoid",
|
||||
roughingOn: "camRoughOn",
|
||||
finishingTool: "camOutlineTool",
|
||||
finishingSpindle: "camOutlineSpindle",
|
||||
camWideCutout: "camOutlineWide",
|
||||
drillDown: "camDrillDown",
|
||||
drillDownSpeed: "camDrillDownSpeed",
|
||||
drillDwell: "camDrillDwell",
|
||||
drillingOn: "camDrillingOn",
|
||||
drillLift: "camDrillLift",
|
||||
drillSpindle: "camDrillSpindle",
|
||||
drillTool: "camDrillTool",
|
||||
finishingDown: "camOutlineDown",
|
||||
finishingOver: "camContourOver",
|
||||
finishingSpeed: "camOutlineSpeed",
|
||||
finishingPlunge: "camOutlinePlunge",
|
||||
finishingOn: "camOutlineOn",
|
||||
finishingOver: "camContourOver",
|
||||
finishingPlunge: "camOutlinePlunge",
|
||||
finishingSpeed: "camOutlineSpeed",
|
||||
finishingSpindle: "camOutlineSpindle",
|
||||
finishingTool: "camOutlineTool",
|
||||
finishingXOn: "camContourXOn",
|
||||
finishingYOn: "camContourYOn",
|
||||
drillTool: "camDrillTool",
|
||||
drillSpindle: "camDrillSpindle",
|
||||
drillDownSpeed: "camDrillDownSpeed",
|
||||
drillDown: "camDrillDown",
|
||||
drillDwell: "camDrillDwell",
|
||||
drillLift: "camDrillLift",
|
||||
drillingOn: "camDrillingOn",
|
||||
camWideCutout: "camOutlineWide",
|
||||
outputClockwise: "camConventional"
|
||||
outputClockwise: "camConventional",
|
||||
roughingDown: "camRoughDown",
|
||||
roughingOn: "camRoughOn",
|
||||
roughingOver: "camRoughOver",
|
||||
roughingPlunge: "camRoughPlunge",
|
||||
roughingPocket: "camRoughVoid",
|
||||
roughingSpeed: "camRoughSpeed",
|
||||
roughingSpindle: "camRoughSpindle",
|
||||
roughingStock: "camRoughStock",
|
||||
roughingTool: "camRoughTool",
|
||||
};
|
||||
|
||||
export const conf = {
|
||||
|
|
@ -258,95 +258,95 @@ export const conf = {
|
|||
},
|
||||
// process defaults FDM:Process
|
||||
p:{
|
||||
antiBacklash: 0,
|
||||
arcTolerance: 0,
|
||||
firstLayerBedTemp: 0,
|
||||
firstLayerBeltBump: 0,
|
||||
firstLayerBeltLead: 3,
|
||||
firstLayerBeltFact: 1,
|
||||
firstLayerBrim: 0,
|
||||
firstLayerBrimGap: 0,
|
||||
firstLayerBrimIn: 0,
|
||||
firstLayerBrimTrig: 0,
|
||||
firstLayerFanSpeed: 0,
|
||||
firstLayerFillRate: 35,
|
||||
firstLayerFlatten: 0,
|
||||
firstLayerNozzleTemp: 0,
|
||||
firstLayerRate: 30,
|
||||
firstLayerYOffset: 0,
|
||||
firstSliceHeight: 0.25,
|
||||
outputAlternating: false,
|
||||
outputAvoidGaps: true,
|
||||
outputBedTemp: 60,
|
||||
outputBeltFirst: false,
|
||||
outputBrimCount: 2,
|
||||
outputBrimOffset: 2,
|
||||
outputCoastDist: 0,
|
||||
outputDraftShield: false,
|
||||
outputFanLayer: 1,
|
||||
outputFanSpeed: 255,
|
||||
outputFeedrate: 50,
|
||||
outputFillMult: 1.25,
|
||||
outputFinishrate: 50,
|
||||
outputInvertX: false,
|
||||
outputInvertY: false,
|
||||
outputLayerRetract: false,
|
||||
outputLoops: 0,
|
||||
outputMaxFlowrate: 15,
|
||||
outputMinLayerTime: 10,
|
||||
outputMinSpeed: 5.0,
|
||||
outputPurgeTower: 0,
|
||||
outputRaft: false,
|
||||
outputRaftSpacing: 0.2,
|
||||
outputRetractDist: 1.5,
|
||||
outputRetractDwell: 20,
|
||||
outputRetractSpeed: 40,
|
||||
outputRetractWipe: 0,
|
||||
outputScarfLength: 0,
|
||||
outputSeekrate: 80,
|
||||
outputShellMult: 1.25,
|
||||
outputShortPoly: 0.0,
|
||||
outputSparseMult: 1.25,
|
||||
outputTemp: 200,
|
||||
processName: "default",
|
||||
ranges: [],
|
||||
sliceAdaptive: false,
|
||||
sliceAngle: 45,
|
||||
sliceBottomLayers: 3,
|
||||
sliceCompInner: 0,
|
||||
sliceCompOuter: 0,
|
||||
sliceDetectThin: "basic",
|
||||
sliceFillAngle: 45,
|
||||
sliceFillGrow: 0,
|
||||
sliceFillOverlap: 0.35,
|
||||
sliceFillRepeat: 1,
|
||||
sliceFillSparse: 0.25,
|
||||
sliceFillType: "hex",
|
||||
sliceFillWidth: 1,
|
||||
sliceHeight: 0.25,
|
||||
sliceShells: 3,
|
||||
sliceShellOrder: "in-out",
|
||||
sliceLayerStart: "last",
|
||||
sliceLayerStartX: 0,
|
||||
sliceLayerStartY: 0,
|
||||
sliceLineWidth: 0,
|
||||
sliceFillGrow: 0,
|
||||
sliceFillAngle: 45,
|
||||
sliceFillWidth: 1,
|
||||
sliceFillOverlap: 0.35,
|
||||
sliceFillSparse: 0.25,
|
||||
sliceFillRepeat: 1,
|
||||
sliceFillRate: 0,
|
||||
sliceSolidRate: 0,
|
||||
sliceFillType: "hex",
|
||||
sliceSupportDensity: 0.1,
|
||||
sliceSupportOffset: 1.0,
|
||||
sliceSupportGap: 1,
|
||||
sliceSupportSize: 5,
|
||||
sliceSupportArea: 0.1,
|
||||
sliceSupportSpan: 5,
|
||||
sliceSupportGrow: 0,
|
||||
sliceSupportExtra: 0,
|
||||
sliceSupportAngle: 50,
|
||||
sliceSupportNozzle: 0,
|
||||
sliceSupportEnable: false,
|
||||
sliceSupportOutline: true,
|
||||
sliceZInterleave: false,
|
||||
sliceSolidMinArea: 1,
|
||||
sliceBottomLayers: 3,
|
||||
sliceTopLayers: 3,
|
||||
firstSliceHeight: 0.25,
|
||||
firstLayerRate: 30,
|
||||
firstLayerFillRate: 35,
|
||||
firstLayerPrintMult: 1.0,
|
||||
firstLayerLineMult: 1.0,
|
||||
firstLayerYOffset: 0,
|
||||
firstLayerNozzleTemp: 0,
|
||||
firstLayerBedTemp: 0,
|
||||
firstLayerBrim: 0,
|
||||
firstLayerBrimIn: 0,
|
||||
firstLayerBrimTrig: 0,
|
||||
firstLayerBrimGap: 0,
|
||||
firstLayerBeltLead: 3,
|
||||
firstLayerBeltBump: 0,
|
||||
firstLayerFanSpeed: 0,
|
||||
firstLayerFlatten: 0,
|
||||
outputRaft: false,
|
||||
outputRaftSpacing: 0.2,
|
||||
outputDraftShield: false,
|
||||
outputTemp: 200,
|
||||
outputBedTemp: 60,
|
||||
outputFeedrate: 50,
|
||||
outputFinishrate: 50,
|
||||
outputSeekrate: 80,
|
||||
outputShellMult: 1.25,
|
||||
outputFillMult: 1.25,
|
||||
outputSparseMult: 1.25,
|
||||
outputFanLayer: 1,
|
||||
outputFanSpeed: 255,
|
||||
outputRetractDist: 1.5,
|
||||
outputRetractSpeed: 40,
|
||||
outputRetractWipe: 0,
|
||||
outputRetractDwell: 20,
|
||||
outputBrimCount: 2,
|
||||
outputBrimOffset: 2,
|
||||
outputScarfLength: 0,
|
||||
outputShortPoly: 100.0,
|
||||
outputMinSpeed: 10.0,
|
||||
outputMinLayerTime: 0,
|
||||
outputCoastDist: 0,
|
||||
outputPurgeTower: 0,
|
||||
outputBeltFirst: false,
|
||||
outputAvoidGaps: true,
|
||||
outputAlternating: false,
|
||||
outputLayerRetract: false,
|
||||
outputLoops: 0,
|
||||
outputInvertX: false,
|
||||
outputInvertY: false,
|
||||
sliceDetectThin: "off",
|
||||
sliceMinHeight: 0,
|
||||
sliceAdaptive: false,
|
||||
sliceShellOrder: "in-out",
|
||||
sliceShells: 3,
|
||||
sliceSolidMinArea: 1,
|
||||
sliceSupportAngle: 50,
|
||||
sliceSupportArea: 0.1,
|
||||
sliceSupportDensity: 0.1,
|
||||
sliceSupportEnable: false,
|
||||
sliceSupportExtra: 0,
|
||||
sliceSupportGap: 1,
|
||||
sliceSupportGrow: 0,
|
||||
sliceSupportNozzle: 0,
|
||||
sliceSupportOffset: 1.0,
|
||||
sliceSupportOutline: true,
|
||||
sliceSupportSize: 5,
|
||||
sliceSupportSpan: 5,
|
||||
sliceTopLayers: 3,
|
||||
sliceZInterleave: false,
|
||||
zHopDistance: 0.2,
|
||||
arcTolerance: 0,
|
||||
antiBacklash: 0,
|
||||
ranges: []
|
||||
}
|
||||
},
|
||||
sla:{
|
||||
|
|
@ -420,186 +420,187 @@ export const conf = {
|
|||
},
|
||||
// process defaults CAM:Process
|
||||
p:{
|
||||
processName: "default",
|
||||
camLevelTool: 1000,
|
||||
camLevelSpindle: 1000,
|
||||
camLevelOver: 0.75,
|
||||
camLevelStepZ: 0,
|
||||
camLevelSpeed: 1000,
|
||||
camLevelDown: 0,
|
||||
camLevelInset: 0.5,
|
||||
camLevelStock: true,
|
||||
camRoughTool: 1000,
|
||||
camRoughSpindle: 1000,
|
||||
camRoughDown: 2,
|
||||
camRoughOver: 0.4,
|
||||
camRoughSpeed: 1000,
|
||||
camRoughPlunge: 250,
|
||||
camRoughStock: 0,
|
||||
camRoughStockZ: 0,
|
||||
camRoughAll: true,
|
||||
camRoughVoid: false,
|
||||
camRoughFlat: true,
|
||||
camRoughTop: true,
|
||||
camRoughIn: true,
|
||||
camRoughOn: true,
|
||||
camRoughOmitThru: false,
|
||||
camRoughOmitVoid: false,
|
||||
camOutlineTool: 1000,
|
||||
camOutlineSpindle: 1000,
|
||||
camOutlineTop: true,
|
||||
camOutlineDown: 3,
|
||||
camOutlineOver: 0.4,
|
||||
camOutlineOverCount: 1,
|
||||
camOutlineSpeed: 800,
|
||||
camOutlinePlunge: 250,
|
||||
camOutlineWide: false,
|
||||
camOutlineDogbone: false,
|
||||
camOutlineOmitThru: false,
|
||||
camOutlineOmitVoid: false,
|
||||
camOutlineOut: true,
|
||||
camOutlineIn: false,
|
||||
camOutlineOn: true,
|
||||
camContourTool: 1000,
|
||||
camContourSpindle: 1000,
|
||||
camContourOver: 0.5,
|
||||
camContourSpeed: 1000,
|
||||
camArcEnabled: false,
|
||||
camArcResolution: 5,
|
||||
camArcTolerance: 0.15,
|
||||
camContourAngle: 85,
|
||||
camContourLeave: 0,
|
||||
camContourReduce: 2,
|
||||
camContourBottom: false,
|
||||
camContourBridge: 0,
|
||||
camContourCurves: false,
|
||||
camContourIn: false,
|
||||
camContourLeave: 0,
|
||||
camContourOver: 0.5,
|
||||
camContourReduce: 2,
|
||||
camContourSpeed: 1000,
|
||||
camContourSpindle: 1000,
|
||||
camContourTool: 1000,
|
||||
camContourXOn: true,
|
||||
camContourYOn: true,
|
||||
camLatheTool: 1000,
|
||||
camLatheSpindle: 1000,
|
||||
camLatheOver: 0.1,
|
||||
camLatheAngle: 1,
|
||||
camLatheSpeed: 500,
|
||||
camLatheLinear: true,
|
||||
camTolerance: 0,
|
||||
camTraceTool: 1000,
|
||||
camTraceSpindle: 1000,
|
||||
camTraceType: "follow",
|
||||
camTraceOver: 0.5,
|
||||
camTraceDown: 0,
|
||||
camTraceThru: 0,
|
||||
camTraceSpeed: 250,
|
||||
camTracePlunge: 200,
|
||||
camTraceOffOver: 0,
|
||||
camTraceDogbone: false,
|
||||
camTraceMerge: false,
|
||||
camTraceLines: false,
|
||||
camTraceZTop: 0,
|
||||
camTraceZBottom: 0,
|
||||
camPocketSpindle: 1000,
|
||||
camPocketTool: 1000,
|
||||
camPocketOver: 0.25,
|
||||
camPocketDown: 1,
|
||||
camPocketSpeed: 250,
|
||||
camPocketPlunge: 200,
|
||||
camPocketExpand: 0,
|
||||
camPocketSmooth: 0,
|
||||
camPocketRefine: 20,
|
||||
camPocketFollow: 5,
|
||||
camPocketContour: false,
|
||||
camPocketEngrave: false,
|
||||
camPocketOutline: false,
|
||||
camPocketZTop: 0,
|
||||
camPocketZBottom: 0,
|
||||
camDrillTool: 1006,
|
||||
camDrillSpindle: 1000,
|
||||
camDrillDownSpeed: 250,
|
||||
camConventional: false, // outputClockwise
|
||||
camDepthFirst: false,
|
||||
camDrillDown: 5,
|
||||
camDrillDownSpeed: 250,
|
||||
camDrillDwell: 250,
|
||||
camDrillFromStockTop: false,
|
||||
camDrillingOn: false,
|
||||
camDrillLift: 2,
|
||||
camDrillMark: false,
|
||||
camDrillFromStockTop: false,
|
||||
camDrillThru: 5,
|
||||
camDrillPrecision: 1,
|
||||
camDrillingOn: false,
|
||||
camRegisterSpeed: 1000,
|
||||
camRegisterThru: 5,
|
||||
camRegisterOffset: 10,
|
||||
camHelicalTool: 1000,
|
||||
camHelicalSpindle: 1000,
|
||||
camHelicalDownSpeed: 250,
|
||||
camHelicalSpeed: 1000,
|
||||
camHelicalDown: 5,
|
||||
camHelicalBottomFinish: true,
|
||||
camHelicalThru: 0,
|
||||
camHelicalOffset: "auto",
|
||||
camHelicalForceStartAngle: false,
|
||||
camHelicalStartAngle: 0,
|
||||
camHelicalOffsetOverride: 0,
|
||||
camHelicalEntry: false,
|
||||
camHelicalEntryOffset:0,
|
||||
camHelicalReverse: false,
|
||||
camHelicalClockwise: true,
|
||||
camFlipAxis: "X",
|
||||
camFlipOther: "",
|
||||
camLaserEnable: ["M321"],
|
||||
camLaserDisable: ["M322"],
|
||||
camLaserOn: ["M3"],
|
||||
camLaserOff: ["M5"],
|
||||
camLaserSpeed: 100,
|
||||
camLaserPower: 1,
|
||||
camLaserAdaptive: false,
|
||||
camLaserAdaptMod: false,
|
||||
camLaserFlatten: false,
|
||||
camLaserFlatZ: 0,
|
||||
camLaserPowerMin: 0,
|
||||
camLaserPowerMax: 1,
|
||||
camLaserZMin: 0,
|
||||
camLaserZMax: 0,
|
||||
camTabsWidth: 5,
|
||||
camTabsHeight: 5,
|
||||
camTabsDepth: 5,
|
||||
camTabsMidline: false,
|
||||
camDepthFirst: false,
|
||||
camInnerFirst: false,
|
||||
camEaseDown: false,
|
||||
camDrillSpindle: 1000,
|
||||
camDrillThru: 5,
|
||||
camDrillTool: 1006,
|
||||
camEaseAngle: 10,
|
||||
camOriginTop: true,
|
||||
camZAnchor: "middle",
|
||||
camZOffset: 0,
|
||||
camZTop: 0,
|
||||
camZBottom: 0,
|
||||
camZClearance: 1,
|
||||
camZThru: 0,
|
||||
camEaseDown: false,
|
||||
camExpertFast: false,
|
||||
camFastFeed: 6000,
|
||||
camFastFeedZ: 300,
|
||||
camTolerance: 0,
|
||||
camFirstZMax: false,
|
||||
camFlatness: 0.001,
|
||||
camContourBridge: 0,
|
||||
camStockX: 5,
|
||||
camStockY: 5,
|
||||
camStockZ: 5,
|
||||
camStockOffset: true,
|
||||
camStockClipTo: false,
|
||||
camStockIndexed: false,
|
||||
camStockIndexGrid: true,
|
||||
camIndexAxis: 0,
|
||||
camFlipAxis: "X",
|
||||
camFlipOther: "",
|
||||
camForceZMax: false,
|
||||
camFullEngage: 0.8,
|
||||
camHelicalBottomFinish: true,
|
||||
camHelicalClockwise: true,
|
||||
camHelicalDown: 5,
|
||||
camHelicalDownSpeed: 250,
|
||||
camHelicalEntry: false,
|
||||
camHelicalEntryOffset:0,
|
||||
camHelicalForceStartAngle: false,
|
||||
camHelicalOffset: "auto",
|
||||
camHelicalOffsetOverride: 0,
|
||||
camHelicalReverse: false,
|
||||
camHelicalSpeed: 1000,
|
||||
camHelicalSpindle: 1000,
|
||||
camHelicalStartAngle: 0,
|
||||
camHelicalThru: 0,
|
||||
camHelicalTool: 1000,
|
||||
camIndexAbs: true,
|
||||
camConventional: false, // outputClockwise
|
||||
camIndexAxis: 0,
|
||||
camInnerFirst: false,
|
||||
camLaserAdaptive: false,
|
||||
camLaserAdaptMod: false,
|
||||
camLaserDisable: ["M322"],
|
||||
camLaserEnable: ["M321"],
|
||||
camLaserFlatten: false,
|
||||
camLaserFlatZ: 0,
|
||||
camLaserOff: ["M5"],
|
||||
camLaserOn: ["M3"],
|
||||
camLaserPower: 1,
|
||||
camLaserPowerMax: 1,
|
||||
camLaserPowerMin: 0,
|
||||
camLaserSpeed: 100,
|
||||
camLaserZMax: 0,
|
||||
camLaserZMin: 0,
|
||||
camLatheAngle: 1,
|
||||
camLatheLinear: true,
|
||||
camLatheOver: 0.1,
|
||||
camLatheSpeed: 500,
|
||||
camLatheSpindle: 1000,
|
||||
camLatheTool: 1000,
|
||||
camLatheOffStart: 0,
|
||||
camLatheOffEnd: 0,
|
||||
camLevelDown: 0,
|
||||
camLevelInset: 0.5,
|
||||
camLevelOver: 0.75,
|
||||
camLevelSpeed: 1000,
|
||||
camLevelSpindle: 1000,
|
||||
camLevelStepZ: 0,
|
||||
camLevelStock: true,
|
||||
camLevelTool: 1000,
|
||||
camOriginCenter: false,
|
||||
camOriginOffX: 0,
|
||||
camOriginOffY: 0,
|
||||
camOriginOffZ: 0,
|
||||
camOriginTop: true,
|
||||
camOutlineDogbone: false,
|
||||
camOutlineDown: 3,
|
||||
camOutlineIn: false,
|
||||
camOutlineOmitThru: false,
|
||||
camOutlineOmitVoid: false,
|
||||
camOutlineOn: true,
|
||||
camOutlineOut: true,
|
||||
camOutlineOver: 0.4,
|
||||
camOutlineOverCount: 1,
|
||||
camOutlinePlunge: 250,
|
||||
camOutlineSpeed: 800,
|
||||
camOutlineSpindle: 1000,
|
||||
camOutlineTool: 1000,
|
||||
camOutlineTop: true,
|
||||
camOutlineWide: false,
|
||||
camPocketContour: false,
|
||||
camPocketDown: 1,
|
||||
camPocketEngrave: false,
|
||||
camPocketExpand: 0,
|
||||
camPocketFollow: 5,
|
||||
camPocketOutline: false,
|
||||
camPocketOver: 0.25,
|
||||
camPocketPlunge: 200,
|
||||
camPocketRefine: 20,
|
||||
camPocketSmooth: 1,
|
||||
camPocketSpeed: 250,
|
||||
camPocketSpindle: 1000,
|
||||
camPocketTool: 1000,
|
||||
camPocketZBottom: 0,
|
||||
camPocketZTop: 0,
|
||||
camRegisterOffset: 10,
|
||||
camRegisterSpeed: 1000,
|
||||
camRegisterThru: 5,
|
||||
camRoughAll: true,
|
||||
camRoughDown: 2,
|
||||
camRoughFlat: true,
|
||||
camRoughIn: true,
|
||||
camRoughOmitThru: false,
|
||||
camRoughOmitVoid: false,
|
||||
camRoughOn: true,
|
||||
camRoughOver: 0.4,
|
||||
camRoughPlunge: 250,
|
||||
camRoughSpeed: 1000,
|
||||
camRoughSpindle: 1000,
|
||||
camRoughStock: 0,
|
||||
camRoughStockZ: 0,
|
||||
camRoughTool: 1000,
|
||||
camRoughTop: true,
|
||||
camRoughVoid: false,
|
||||
camStockClipTo: false,
|
||||
camStockIndexed: false,
|
||||
camStockIndexGrid: true,
|
||||
camStockOffset: true,
|
||||
camStockX: 5,
|
||||
camStockY: 5,
|
||||
camStockZ: 5,
|
||||
camTabsDepth: 5,
|
||||
camTabsHeight: 5,
|
||||
camTabsMidline: false,
|
||||
camTabsWidth: 5,
|
||||
camTolerance: 0,
|
||||
camToolInit: true,
|
||||
camTraceDogbone: false,
|
||||
camTraceDown: 0,
|
||||
camTraceLines: false,
|
||||
camTraceMerge: false,
|
||||
camTraceOffOver: 0,
|
||||
camTraceOver: 0.5,
|
||||
camTracePlunge: 200,
|
||||
camTraceSpeed: 250,
|
||||
camTraceSpindle: 1000,
|
||||
camTraceThru: 0,
|
||||
camTraceTool: 1000,
|
||||
camTraceType: "follow",
|
||||
camTraceZBottom: 0,
|
||||
camTraceZTop: 0,
|
||||
camTrueShadow: false,
|
||||
camZAnchor: "middle",
|
||||
camZBottom: 0,
|
||||
camZClearance: 1,
|
||||
camZOffset: 0,
|
||||
camZThru: 0,
|
||||
camZTop: 0,
|
||||
op2: [], // flip ops
|
||||
ops: [], // current ops
|
||||
outputInvertX: false,
|
||||
outputInvertY: false,
|
||||
camExpertFast: false,
|
||||
camTrueShadow: false,
|
||||
camArcEnabled: false,
|
||||
camArcTolerance: 0.15,
|
||||
camArcResolution: 5,
|
||||
camForceZMax: false,
|
||||
camFirstZMax: false,
|
||||
camToolInit: true,
|
||||
camFullEngage: 0.8,
|
||||
ops: [], // current ops
|
||||
op2: [] // flip ops
|
||||
processName: "default",
|
||||
}
|
||||
},
|
||||
laser: {
|
||||
|
|
@ -915,37 +916,37 @@ export const conf = {
|
|||
devproc: {},
|
||||
// application ui and control preferences (Q menu)
|
||||
controller:{
|
||||
view: null,
|
||||
dark: false,
|
||||
shiny: false,
|
||||
drawer: false,
|
||||
scrolls: true,
|
||||
zoomSpeed: 1.0,
|
||||
lineType: "path",
|
||||
autoSave: true,
|
||||
animesh: "800",
|
||||
antiAlias: true,
|
||||
assembly: false,
|
||||
autoLayout: true,
|
||||
autoSave: true,
|
||||
dark: false,
|
||||
detail: "50",
|
||||
devel: false,
|
||||
drawer: false,
|
||||
edgeangle: 20,
|
||||
exportOcto: false,
|
||||
exportPreview: false,
|
||||
exportThumb: false,
|
||||
freeLayout: true,
|
||||
healMesh: false,
|
||||
lineType: "path",
|
||||
manifold: false,
|
||||
ortho: false,
|
||||
reverseZoom: true,
|
||||
scrolls: true,
|
||||
shiny: true,
|
||||
showOrigin: false,
|
||||
showRulers: true,
|
||||
showSpeeds: true,
|
||||
freeLayout: true,
|
||||
autoLayout: true,
|
||||
spaceRandoX: false,
|
||||
spaceLayout: 1,
|
||||
edgeangle: 20,
|
||||
units: "mm",
|
||||
exportOcto: false,
|
||||
exportGhost: false,
|
||||
exportLocal: false,
|
||||
exportThumb: false,
|
||||
exportPreview: false,
|
||||
detail: "50",
|
||||
animesh: "800",
|
||||
healMesh: false,
|
||||
spaceRandoX: false,
|
||||
threaded: true,
|
||||
assembly: false,
|
||||
ortho: false,
|
||||
devel: false
|
||||
units: "mm",
|
||||
view: null,
|
||||
webGPU: false,
|
||||
zoomSpeed: 1.0,
|
||||
},
|
||||
// default hidden ui groups
|
||||
hidden: {
|
||||
|
|
|
|||
|
|
@ -108,7 +108,8 @@ const LISTS = {
|
|||
regaxis: [
|
||||
{ name: "X" },
|
||||
{ name: "Y" },
|
||||
{ name: "-" }
|
||||
{ name: "-" },
|
||||
{ name: "=" }
|
||||
],
|
||||
regpoints: [
|
||||
{ name: "2" },
|
||||
|
|
|
|||
|
|
@ -218,207 +218,6 @@ function exportGCodeDialog(gcode, sections, info, names) {
|
|||
ajax.send(form);
|
||||
}
|
||||
|
||||
function gridhost_tracker(host,key) {
|
||||
ajax(host+"/api/check?key="+key, function(data) {
|
||||
data = js2o(data);
|
||||
console.log(data);
|
||||
if (!(data.done || data.error)) {
|
||||
setTimeout(function() { gridhost_tracker(host,key) }, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function gridlocal_probe(ev, devs) {
|
||||
if (ev && ev.code !== 'Enter') return;
|
||||
|
||||
if (!devs && api.probe.local(gridlocal_probe)) return;
|
||||
|
||||
grid_local = devs;
|
||||
|
||||
let gdev = localGet('grid-local');
|
||||
let gloc = $('grid-local');
|
||||
let html = [];
|
||||
for (let uuid in devs) {
|
||||
gdev = gdev || uuid;
|
||||
let dev = devs[uuid];
|
||||
let sel = uuid === gdev ? ' selected' : '';
|
||||
html.push(`<option id="gl-${uuid}" value="${uuid}" ${sel}>${dev.stat.device.name}</option>`);
|
||||
}
|
||||
|
||||
if (html.length) {
|
||||
gloc.innerHTML = html.join('\n');
|
||||
gloc.onchange = (ev) => {
|
||||
localSet('grid-local', gloc.options[gloc.selectedIndex].value);
|
||||
};
|
||||
$('send-to-gridhead').style.display = 'flex';
|
||||
$('send-to-gridspool').style.display = 'flex';
|
||||
} else {
|
||||
$('send-to-gridhead').style.display = '';
|
||||
$('send-to-gridspool').style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
function sendto_gridlocal() {
|
||||
let uuid = $('grid-local').value;
|
||||
let dev = grid_local[uuid];
|
||||
if (dev) {
|
||||
let file = $('print-filename').value;
|
||||
fetch(
|
||||
`${api.probe.live}/api/grid_send?uuid=${uuid}&file=${encodeURIComponent(file + "." + fileext)}`,
|
||||
{method: "POST", body: gcode}
|
||||
)
|
||||
.then(t => t.text())
|
||||
.then(t => {
|
||||
api.stats.add(`ua_${api.mode.get_lower()}_print_local_ok`);
|
||||
console.log({grid_spool_said: t});
|
||||
})
|
||||
.catch(e => {
|
||||
api.stats.add(`ua_${api.mode.get_lower()}_print_local_err`);
|
||||
console.log({grid_local_spool_error: e});
|
||||
})
|
||||
.finally(() => {
|
||||
api.modal.hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function admin_gridlocal() {
|
||||
let dev = grid_local[$('grid-local').value];
|
||||
if (dev && dev.stat && dev.stat.device) {
|
||||
let dsd = dev.stat.device;
|
||||
window.open(`http://${dsd.addr[0]}:${dsd.port || 4080}`, "_grid_admin");
|
||||
}
|
||||
}
|
||||
|
||||
function gridhost_probe(ev, set_host) {
|
||||
if (ev && ev.code !== 'Enter') return;
|
||||
if (!(grid_host && grid_apik)) return;
|
||||
|
||||
if (set_host) grid_host.value = set_host;
|
||||
|
||||
let xhtr = new XMLHttpRequest(),
|
||||
host = grid_host.value,
|
||||
apik = grid_apik.value,
|
||||
target = grid_target.value;
|
||||
|
||||
if (!apik) $('gpapik').style.display = 'none';
|
||||
|
||||
if (!host && api.probe.grid(gridhost_probe)) return;
|
||||
|
||||
if (!host) return;
|
||||
|
||||
xhtr.onreadystatechange = function() {
|
||||
if (xhtr.readyState === 4) {
|
||||
if (xhtr.status >= 200 && xhtr.status < 300) {
|
||||
localSet('grid-host', host);
|
||||
localSet('grid-apik', apik);
|
||||
let res = JSON.parse(xhtr.responseText);
|
||||
let sel = false;
|
||||
let match = false;
|
||||
let first = null;
|
||||
let html = [];
|
||||
grid_targets = {};
|
||||
for (let key in res) {
|
||||
first = first || key;
|
||||
if (!localGet('grid-target')) {
|
||||
localSet('grid-target', key);
|
||||
sel = true;
|
||||
} else {
|
||||
sel = localGet('grid-target') === key;
|
||||
}
|
||||
match = match || sel;
|
||||
grid_targets[html.length] = key;
|
||||
html.push(
|
||||
"<option id='gpo-'" + key + " value='" +key + "'" +
|
||||
(sel ? " selected" : "") +
|
||||
">" +
|
||||
(res[key].comment || key) +
|
||||
"</option>"
|
||||
);
|
||||
}
|
||||
if (!match) {
|
||||
localSet('grid-target', first);
|
||||
}
|
||||
grid_target.innerHTML = html.join('\n');
|
||||
} else if (xhtr.status === 401) {
|
||||
$('gpapik').style.display = '';
|
||||
} else {
|
||||
local.removeItem('grid-host');
|
||||
local.removeItem('grid-apik');
|
||||
console.log("invalid grid:host url");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
xhtr.open("GET", host + "/api/active?key=" + apik);
|
||||
xhtr.send();
|
||||
}
|
||||
|
||||
function sendto_gridhost() {
|
||||
if (!(grid_host && grid_apik)) return;
|
||||
|
||||
let xhtr = new XMLHttpRequest(),
|
||||
host = grid_host.value,
|
||||
apik = grid_apik.value,
|
||||
target = localGet('grid-target') || '';
|
||||
|
||||
if (target === '') {
|
||||
api.show.alert('invalid or missing target');
|
||||
return;
|
||||
}
|
||||
if (host.indexOf("http") !== 0) {
|
||||
api.show.alert("host missing protocol (http:// or https://)");
|
||||
return;
|
||||
}
|
||||
if (host.indexOf("://") < 0) {
|
||||
api.show.alert("host:port malformed");
|
||||
return;
|
||||
}
|
||||
if (api.const.SECURE && !api.util.isSecure(host)) {
|
||||
api.show.alert("host must begin with 'https' on a secure site");
|
||||
return;
|
||||
}
|
||||
|
||||
localSet('grid-host', host.trim());
|
||||
localSet('grid-apik', apik.trim());
|
||||
|
||||
xhtr.onreadystatechange = function() {
|
||||
if (xhtr.readyState === 4) {
|
||||
let status = xhtr.status;
|
||||
api.stats.add(`ua_${api.mode.get_lower()}_print_grid_${status}`);
|
||||
if (status >= 200 && status < 300) {
|
||||
let json = js2o(xhtr.responseText);
|
||||
gridhost_tracker(host,json.key);
|
||||
api.ajax(host+"/api/wait?key="+json.key, function(data) {
|
||||
data = js2o(data);
|
||||
console.log(data);
|
||||
api.show.alert("print to "+target+": "+data.status, 600);
|
||||
});
|
||||
} else {
|
||||
api.show.alert("grid:host error\nstatus: "+status+"\nmessage: "+xhtr.responseText, 10000);
|
||||
}
|
||||
api.show.progress(0);
|
||||
}
|
||||
};
|
||||
xhtr.upload.addEventListener('progress', function(evt) {
|
||||
api.show.progress(evt.loaded/evt.total, "sending");
|
||||
});
|
||||
filename = $('print-filename').value;
|
||||
xhtr.open("POST",
|
||||
host + "/api/print?" +
|
||||
"filename=" + filename +
|
||||
"&target=" + target +
|
||||
"&key=" + apik +
|
||||
"&time=" + Math.round(info.time) +
|
||||
"&length=" + Math.round(info.distance) +
|
||||
"&image=" + filename
|
||||
);
|
||||
xhtr.setRequestHeader("Content-Type", "text/plain");
|
||||
let snapshot = api.view.snapshot;
|
||||
xhtr.send(snapshot ? [gcode,snapshot].join("\0") : gcode);
|
||||
api.modal.hide();
|
||||
}
|
||||
|
||||
function download() {
|
||||
filename = $('print-filename').value;
|
||||
api.util.download(gcode, filename + "." + fileext);
|
||||
|
|
@ -463,8 +262,6 @@ function exportGCodeDialog(gcode, sections, info, names) {
|
|||
let set = api.conf.get();
|
||||
let fdm = MODE === MODES.FDM;
|
||||
let octo = set.controller.exportOcto && MODE !== MODES.CAM;
|
||||
let ghost = set.controller.exportGhost;
|
||||
let local = set.controller.exportLocal;
|
||||
let preview = set.controller.exportPreview;
|
||||
$('code-preview-head').style.display = preview ? '' : 'none';
|
||||
$('code-preview').style.display = preview ? '' : 'none';
|
||||
|
|
@ -484,8 +281,6 @@ function exportGCodeDialog(gcode, sections, info, names) {
|
|||
// persist fields when changed
|
||||
bindField('octo-host', 'octo-host');
|
||||
bindField('octo-apik', 'octo-apik');
|
||||
bindField('grid-host', 'grid-host');
|
||||
bindField('grid-apik', 'grid-apik');
|
||||
|
||||
// in cam mode, show zip file option
|
||||
let downloadZip = $('print-zip');
|
||||
|
|
@ -699,37 +494,11 @@ function exportGCodeDialog(gcode, sections, info, names) {
|
|||
$('ophost').style.display = 'none';
|
||||
$('opapik').style.display = 'none';
|
||||
$('oph1nt').style.display = 'none';
|
||||
$('send-to-gridhost').style.display = 'none';
|
||||
}
|
||||
octo_host.value = localGet('octo-host') || '';
|
||||
octo_apik.value = localGet('octo-apik') || '';
|
||||
} catch (e) { console.log(e) }
|
||||
|
||||
// grid:host setup
|
||||
$('send-to-hosthead').style.display = ghost ? '' : 'none';
|
||||
$('send-to-gridhost').style.display = ghost ? '' : 'none';
|
||||
if (ghost) try {
|
||||
$('print-gridhost').onclick = sendto_gridhost;
|
||||
$('grid-host').onkeyup = gridhost_probe;
|
||||
$('grid-apik').onkeyup = gridhost_probe;
|
||||
grid_host = $('grid-host');
|
||||
grid_apik = $('grid-apik');
|
||||
grid_target = $('grid-target');
|
||||
grid_target.onchange = function(ev) {
|
||||
localSet('grid-target', grid_targets[grid_target.selectedIndex]);
|
||||
};
|
||||
grid_host.value = localGet('grid-host') || '';
|
||||
grid_apik.value = localGet('grid-apik') || '';
|
||||
gridhost_probe();
|
||||
} catch (e) { console.log(e) }
|
||||
|
||||
// grid:local setup
|
||||
if (local) try {
|
||||
gridlocal_probe();
|
||||
$('print-gridlocal').onclick = sendto_gridlocal;
|
||||
$('admin-gridlocal').onclick = admin_gridlocal;
|
||||
} catch (e) { console.log(e) }
|
||||
|
||||
// preview of the generated GCODE (first 64k max)
|
||||
if (preview && gcode) $('code-preview-textarea').value = gcode.substring(0,65535);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,28 +142,28 @@ function booleanSave() {
|
|||
if (control.antiAlias != ui.antiAlias.checked) {
|
||||
api.show.alert('Page Reload Required to Change Aliasing');
|
||||
}
|
||||
control.shiny = ui.shiny.checked;
|
||||
control.antiAlias = ui.antiAlias.checked;
|
||||
control.assembly = ui.assembly.checked;
|
||||
control.autoLayout = ui.autoLayout.checked;
|
||||
control.autoSave = ui.autoSave.checked;
|
||||
control.dark = ui.dark.checked;
|
||||
control.devel = ui.devel.checked;
|
||||
control.drawer = ui.drawer.checked;
|
||||
control.exportOcto = ui.exportOcto.checked;
|
||||
control.exportPreview = ui.exportPreview.checked;
|
||||
control.exportThumb = ui.exportThumb.checked;
|
||||
control.freeLayout = ui.freeLayout.checked;
|
||||
control.healMesh = ui.healMesh.checked;
|
||||
control.ortho = ui.ortho.checked;
|
||||
control.manifold = ui.manifold.checked;
|
||||
control.reverseZoom = ui.reverseZoom.checked;
|
||||
control.scrolls = ui.scrolls.checked;
|
||||
control.shiny = ui.shiny.checked;
|
||||
control.showOrigin = ui.showOrigin.checked;
|
||||
control.showRulers = ui.showRulers.checked;
|
||||
control.autoLayout = ui.autoLayout.checked;
|
||||
control.freeLayout = ui.freeLayout.checked;
|
||||
control.spaceRandoX = ui.spaceRandoX.checked;
|
||||
control.autoSave = ui.autoSave.checked;
|
||||
control.antiAlias = ui.antiAlias.checked;
|
||||
control.reverseZoom = ui.reverseZoom.checked;
|
||||
control.dark = ui.dark.checked;
|
||||
control.exportOcto = ui.exportOcto.checked;
|
||||
control.exportGhost = ui.exportGhost.checked;
|
||||
control.exportLocal = ui.exportLocal.checked;
|
||||
control.exportThumb = ui.exportThumb.checked;
|
||||
control.exportPreview = ui.exportPreview.checked;
|
||||
control.healMesh = ui.healMesh.checked;
|
||||
control.threaded = setThreaded(ui.threaded.checked);
|
||||
control.assembly = ui.assembly.checked;
|
||||
control.ortho = ui.ortho.checked;
|
||||
control.devel = ui.devel.checked;
|
||||
control.webGPU = ui.webGPU.checked;
|
||||
space.view.setZoom(control.reverseZoom, control.zoomSpeed);
|
||||
// platform.layout();
|
||||
api.conf.save();
|
||||
|
|
@ -904,7 +904,6 @@ function init_one() {
|
|||
xany: $('mod-x-any'),
|
||||
xsla: $('mod-x-sla'),
|
||||
xlaser: $('mod-x-laser'),
|
||||
local: $('mod-local'),
|
||||
don8: $('mod-don8'),
|
||||
any: $('mod-any'),
|
||||
},
|
||||
|
|
@ -1048,6 +1047,7 @@ function init_one() {
|
|||
showSpeeds: newBoolean(LANG.op_sped_s, speedSave, {title:LANG.op_sped_l}),
|
||||
shiny: newBoolean(LANG.op_shny_s, booleanSave, {title:LANG.op_shny_l, modes:FDM}),
|
||||
lineType: newSelect(LANG.op_line_s, {title: LANG.op_line_l, action: lineTypeSave, modes:FDM}, "linetype"),
|
||||
manifold: newBoolean(LANG.op_mani_s, booleanSave, {title: LANG.op_mani_l, modes:CAM}, "manifold"),
|
||||
animesh: newSelect(LANG.op_anim_s, {title: LANG.op_anim_l, action: aniMeshSave, modes:CAM}, "animesh"),
|
||||
units: newSelect(LANG.op_unit_s, {title: LANG.op_unit_l, action: unitsSave, modes:CAM}, "units"),
|
||||
edgeangle: newInput(LANG.op_spoa_s, {title:LANG.op_spoa_l, convert:toFloat, size:3}),
|
||||
|
|
@ -1058,8 +1058,6 @@ function init_one() {
|
|||
spaceRandoX: newBoolean(LANG.op_spcx_s, booleanSave, {title:LANG.op_spcx_l, show:isBelt}),
|
||||
spaceLayout: newInput(LANG.op_spcr_s, {title:LANG.op_spcr_l, convert:toFloat, size:3, units}),
|
||||
_____: newGroup(LANG.xp_menu, $('prefs-xpo'), {inline: true}),
|
||||
exportLocal: newBoolean(`Grid:Local`, booleanSave, {title:LANG.op_exgl_l}),
|
||||
exportGhost: newBoolean(`Grid:Host`, booleanSave, {title:LANG.op_exgh_l}),
|
||||
exportOcto: newBoolean(`OctoPrint`, booleanSave, {title:LANG.op_exop_l}),
|
||||
exportThumb: newBoolean(`Thumbnail`, booleanSave, {modes:FDM}),
|
||||
exportPreview: newBoolean(`Code Preview`, booleanSave),
|
||||
|
|
@ -1068,6 +1066,7 @@ function init_one() {
|
|||
healMesh: newBoolean(LANG.pt_heal_s, booleanSave, {title: LANG.pt_heal_l}),
|
||||
threaded: newBoolean(LANG.pt_thrd_s, booleanSave, {title: LANG.pt_thrd_l, modes:THREED}),
|
||||
assembly: newBoolean(LANG.pt_assy_s, booleanSave, {title: LANG.pt_assy_l, modes:THREED}),
|
||||
webGPU: newBoolean(LANG.pt_wgpu_s, booleanSave, {title: LANG.pt_wgpu_l, modes:THREED}),
|
||||
|
||||
prefadd: uc.checkpoint($('prefs-add')),
|
||||
|
||||
|
|
@ -1635,20 +1634,23 @@ function init_two() {
|
|||
space.platform.setZOff(0.05);
|
||||
|
||||
// restore UI state from settings
|
||||
ui.antiAlias.checked = control.antiAlias;
|
||||
ui.assembly.checked = control.assembly;
|
||||
ui.autoLayout.checked = control.autoLayout;
|
||||
ui.autoSave.checked = control.autoSave;
|
||||
ui.devel.checked = control.devel;
|
||||
ui.freeLayout.checked = control.freeLayout;
|
||||
ui.healMesh.checked = control.healMesh;
|
||||
ui.manifold.checked = control.manifold;
|
||||
ui.ortho.checked = control.ortho;
|
||||
ui.reverseZoom.checked = control.reverseZoom;
|
||||
ui.showOrigin.checked = control.showOrigin;
|
||||
ui.showRulers.checked = control.showRulers;
|
||||
ui.showSpeeds.checked = control.showSpeeds;
|
||||
ui.freeLayout.checked = control.freeLayout;
|
||||
ui.autoLayout.checked = control.autoLayout;
|
||||
ui.spaceRandoX.checked = control.spaceRandoX;
|
||||
ui.antiAlias.checked = control.antiAlias;
|
||||
ui.reverseZoom.checked = control.reverseZoom;
|
||||
ui.autoSave.checked = control.autoSave;
|
||||
ui.healMesh.checked = control.healMesh;
|
||||
ui.threaded.checked = setThreaded(control.threaded);
|
||||
ui.assembly.checked = control.assembly;
|
||||
ui.ortho.checked = control.ortho;
|
||||
ui.devel.checked = control.devel;
|
||||
ui.webGPU.checked = control.webGPU;
|
||||
|
||||
lineTypeSave();
|
||||
detailSave();
|
||||
updateStats();
|
||||
|
|
@ -1789,6 +1791,7 @@ function init_two() {
|
|||
$('context-duplicate').onclick = duplicateSelection;
|
||||
$('context-mirror').onclick = mirrorSelection;
|
||||
$('context-layflat').onclick = () => { api.event.emit("tool.mesh.lay-flat") };
|
||||
$('context-lefty').onclick = () => { api.event.emit("tool.mesh.lefty") };
|
||||
$('context-setfocus').onclick = () => {
|
||||
api.event.emit(
|
||||
"tool.camera.focus",
|
||||
|
|
|
|||
|
|
@ -114,12 +114,6 @@ export const mode = {
|
|||
}
|
||||
};
|
||||
|
||||
export const probe = {
|
||||
live: "https://live.grid.space",
|
||||
grid: noop,
|
||||
local: noop
|
||||
};
|
||||
|
||||
export const process = {
|
||||
code: currentProcessCode,
|
||||
get: currentProcessName
|
||||
|
|
@ -784,43 +778,7 @@ function showHelpFile(local,then) {
|
|||
}
|
||||
|
||||
function showLocal() {
|
||||
showModal('local');
|
||||
api.probe.local((err,data) => {
|
||||
let devc = 0;
|
||||
let bind = [];
|
||||
let html = ['<table>'];
|
||||
html.push(`<thead><tr><th>device</th><th>type</th><th>status</th><th></th></tr></thead>`);
|
||||
html.push(`<tbody>`);
|
||||
let recs = [];
|
||||
for (let k in data) {
|
||||
recs.push(data[k].stat);
|
||||
}
|
||||
recs.sort((a,b) => {
|
||||
return a.device.name < b.device.name ? -1 : 1;
|
||||
});
|
||||
for (let r of recs) {
|
||||
bind.push({uuid: r.device.uuid, host: r.device.addr[0], port: r.device.port});
|
||||
html.push(`<tr>`);
|
||||
html.push(`<td>${r.device.name}</td>`);
|
||||
html.push(`<td>${r.device.mode}</td>`);
|
||||
html.push(`<td>${r.state}</td>`);
|
||||
html.push(`<td><button id="${r.device.uuid}">admin</button></td>`);
|
||||
html.push(`</tr>`);
|
||||
devc++;
|
||||
}
|
||||
html.push(`</tbody>`);
|
||||
html.push(`</table>`);
|
||||
if (devc) {
|
||||
$('mod-local').innerHTML = html.join('');
|
||||
} else {
|
||||
$('mod-local').innerHTML = `<br><b>no local devices</b>`;
|
||||
}
|
||||
bind.forEach(rec => {
|
||||
$(rec.uuid).onclick = () => {
|
||||
window.open(`http://${rec.host}:${rec.port||4080}/`);
|
||||
};
|
||||
});
|
||||
});
|
||||
// deprecated
|
||||
}
|
||||
|
||||
function setViewMode(mode) {
|
||||
|
|
|
|||
|
|
@ -163,19 +163,19 @@ function platformUpdateTopZ() {
|
|||
const wzmax = bounds.max.z;
|
||||
const zdelta = process.camZOffset || 0;
|
||||
switch (process.camZAnchor) {
|
||||
case 'top':
|
||||
widget.setTopZ(stock.z - zdelta);
|
||||
break;
|
||||
case 'middle':
|
||||
if (widget.isIndexed) {
|
||||
widget.setTopZ(zdelta);
|
||||
} else {
|
||||
widget.setTopZ(stock.z - (stock.z - wzmax) / 2 + zdelta);
|
||||
}
|
||||
break;
|
||||
case 'bottom':
|
||||
widget.setTopZ(wzmax + zdelta);
|
||||
break;
|
||||
case 'top':
|
||||
widget.setTopZ(stock.z - zdelta, true);
|
||||
break;
|
||||
case 'middle':
|
||||
if (widget.isIndexed) {
|
||||
widget.setTopZ(zdelta, true);
|
||||
} else {
|
||||
widget.setTopZ(stock.z - (stock.z - wzmax) / 2 + zdelta, true);
|
||||
}
|
||||
break;
|
||||
case 'bottom':
|
||||
widget.setTopZ(wzmax + zdelta, true);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
widget.setTopZ(0);
|
||||
|
|
|
|||
|
|
@ -56,8 +56,8 @@ class Print {
|
|||
// drop duplicates (usually intruced by FDM bisections)
|
||||
if (lastPoint && point && !arc && type !== 'lerp') {
|
||||
// nested due to uglify confusing browser
|
||||
const { x, y, z } = lastPoint;
|
||||
if (point.x == x && point.y == y && point.z == z && lastEmit == emit) {
|
||||
const { x, y, z, a } = lastPoint;
|
||||
if (point.x == x && point.y == y && point.z == z && point.a == z && lastEmit == emit) {
|
||||
return lastOut;
|
||||
}
|
||||
}
|
||||
|
|
@ -129,6 +129,7 @@ class Print {
|
|||
close = !options.open,
|
||||
scarf = !poly.open ? (options.scarf ?? 0) : false,
|
||||
tool = options.tool,
|
||||
zmax = options.zmax,
|
||||
last = startPoint,
|
||||
first = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ function rotate(x, y, z) {
|
|||
space.update();
|
||||
}
|
||||
|
||||
function merge() {
|
||||
function merge({ deleteMerged = true }) {
|
||||
let sel = widgets();
|
||||
if (sel.length === 0) {
|
||||
sel = api.widgets.all();
|
||||
|
|
@ -207,7 +207,7 @@ function merge() {
|
|||
}
|
||||
});
|
||||
let w = api.platform.load_verts([], obj, "merged");
|
||||
api.platform.delete(sel);
|
||||
if (deleteMerged) api.platform.delete(sel);
|
||||
api.platform.select(w);
|
||||
}
|
||||
|
||||
|
|
@ -234,7 +234,7 @@ function isolateBodies(){
|
|||
if(bodies.length ==1){
|
||||
api.platform.select(widget);
|
||||
}else{
|
||||
console.log(bodies,Array.from(bodies.entries()))
|
||||
// console.log(bodies,Array.from(bodies.entries()))
|
||||
for(let [i,verts] of bodies.entries()){
|
||||
let w = api.platform.load_verts([], verts, `isolate ${i} of ${ widget.anno.file}`);
|
||||
api.platform.select(w)
|
||||
|
|
|
|||
|
|
@ -49,6 +49,24 @@ function onLayFlatSelect() {
|
|||
// endIt();
|
||||
}
|
||||
|
||||
function onLeftySelect() {
|
||||
// Project the face normal onto the XY plane (ignore Z component)
|
||||
let projectedNormal = new Vector3(lastface.normal.x, lastface.normal.y, 0).normalize();
|
||||
|
||||
// Target direction on XY plane (negative Y axis)
|
||||
let targetDir = new Vector3(0, -1, 0);
|
||||
|
||||
// Calculate angle between projected normal and target
|
||||
let angle = Math.atan2(
|
||||
projectedNormal.x * targetDir.y - projectedNormal.y * targetDir.x, // cross product Z component
|
||||
projectedNormal.x * targetDir.x + projectedNormal.y * targetDir.y // dot product
|
||||
);
|
||||
|
||||
// Create quaternion for Z-axis rotation only
|
||||
let q = new Quaternion().setFromAxisAngle(ZAXIS, angle);
|
||||
api.selection.rotate(q);
|
||||
}
|
||||
|
||||
function onFaceUpSelect() {
|
||||
api.event.emit('tool.mesh.face-normal', lastface.normal);
|
||||
// endIt();
|
||||
|
|
@ -105,6 +123,12 @@ api.event.on('tool.mesh.lay-flat', () => {
|
|||
startIt();
|
||||
});
|
||||
|
||||
api.event.on('tool.mesh.lefty', () => {
|
||||
opName = 'align y axis';
|
||||
onDone = onLeftySelect;
|
||||
startIt();
|
||||
});
|
||||
|
||||
function scale() {
|
||||
let cam = space.internals().camera;
|
||||
let dist = cam.position.distanceTo(pmesh.position);
|
||||
|
|
|
|||
|
|
@ -473,10 +473,12 @@ class Widget {
|
|||
|
||||
/**
|
||||
* moves top of widget to given Z
|
||||
* only non-zero in CAM mode
|
||||
*
|
||||
* @param {number} z position
|
||||
* @param {boolean} cam mode
|
||||
*/
|
||||
setTopZ(z) {
|
||||
setTopZ(z, cam) {
|
||||
let mesh = this.mesh,
|
||||
track = this.track,
|
||||
mbb = mesh.getBoundingBox(),
|
||||
|
|
@ -488,7 +490,7 @@ class Widget {
|
|||
track.top = mbz;
|
||||
}
|
||||
// difference between top of stock/bounds and top of widget (cam mode)
|
||||
track.tzoff = mbz - z;
|
||||
track.tzoff = cam ? mbz - z : 0;
|
||||
this._updateMeshPosition();
|
||||
}
|
||||
|
||||
|
|
@ -503,8 +505,7 @@ class Widget {
|
|||
}
|
||||
|
||||
_move(x, y, z, abs) {
|
||||
let mesh = this.mesh,
|
||||
mat = this.getMaterial(),
|
||||
let mat = this.getMaterial(),
|
||||
pos = this.track.pos;
|
||||
// do not allow moves in pure slice view
|
||||
if (!mat.visible) return;
|
||||
|
|
@ -528,13 +529,10 @@ class Widget {
|
|||
track = this.track,
|
||||
tzoff = track.tzoff,
|
||||
top = track.top,
|
||||
tz = tzoff !== top ? -tzoff : 0,
|
||||
tz = -tzoff,
|
||||
{ x, y, z } = track.pos;
|
||||
if (track.indexed) {
|
||||
let rad = track.indexRad;
|
||||
let ln = track.box.d / 2;
|
||||
let dx = Math.sin(rad) * ln;
|
||||
let dy = ln - Math.cos(rad) * ln;
|
||||
this.mesh.rotation.x = -rad;
|
||||
z = top;
|
||||
} else {
|
||||
|
|
@ -782,9 +780,11 @@ class Widget {
|
|||
}
|
||||
if (set) {
|
||||
let dark = this.api.space.is_dark();
|
||||
let cam = this.api.mode.is_cam();
|
||||
let color = dark && !cam ? 0xffffff : 0;
|
||||
let angle = this.api.conf.get().controller.edgeangle || 20;
|
||||
let edges = new THREE.EdgesGeometry(mesh.geometry, angle);
|
||||
let material = new THREE.LineBasicMaterial({ color: 0 });
|
||||
let material = new THREE.LineBasicMaterial({ color });
|
||||
this.outline = new THREE.LineSegments(edges, material);
|
||||
this.outline.renderOrder = -20;
|
||||
mesh.add(this.outline);
|
||||
|
|
|
|||
|
|
@ -5,38 +5,37 @@
|
|||
import { CSG } from '../../../geo/csg.js';
|
||||
import { Tool } from './tool.js';
|
||||
|
||||
let nextMeshID = 1;
|
||||
let stock, center, rez;
|
||||
let path, pathIndex, tool, tools, last;
|
||||
|
||||
let stockZ;
|
||||
let stockIndexMsg = false;
|
||||
let stockSlices;
|
||||
let stockIndex;
|
||||
let startTime;
|
||||
|
||||
let toolID = -1;
|
||||
let toolMesh;
|
||||
let toolRadius;
|
||||
let toolUpdateMsg;
|
||||
|
||||
let animateClear = false;
|
||||
let animating = false;
|
||||
let moveDist = 0;
|
||||
let renderDist = 0;
|
||||
let renderDone = false;
|
||||
let renderPause = 10;
|
||||
let renderSteps = 0;
|
||||
let renderSpeed = 0;
|
||||
let indexCount = 0;
|
||||
let updates = 0;
|
||||
let nextMeshID = 1,
|
||||
stock,
|
||||
path,
|
||||
pathIndex,
|
||||
tool,
|
||||
tools,
|
||||
last,
|
||||
stockZ,
|
||||
stockIndexMsg = false,
|
||||
stockSlices,
|
||||
stockIndex,
|
||||
startTime,
|
||||
toolID = -1,
|
||||
toolMesh,
|
||||
toolRadius,
|
||||
toolUpdateMsg,
|
||||
animateClear = false,
|
||||
animating = false,
|
||||
renderDone = false,
|
||||
renderSteps = 0,
|
||||
renderSpeed = 0,
|
||||
indexCount = 0,
|
||||
updates = 0,
|
||||
maxR;
|
||||
|
||||
export function init(worker) {
|
||||
const { dispatch } = worker;
|
||||
|
||||
dispatch.animate_setup2 = function (data, send) {
|
||||
const { settings } = data;
|
||||
const { process } = settings;
|
||||
const { controller, process } = settings;
|
||||
const print = worker.current.print;
|
||||
const density = parseInt(settings.controller.animesh) * 1000;
|
||||
const isIndexed = process.camStockIndexed;
|
||||
|
|
@ -45,8 +44,6 @@ export function init(worker) {
|
|||
path = print.output.flat();
|
||||
tools = settings.tools;
|
||||
stock = settings.stock;
|
||||
rez = 1 / Math.sqrt(density / (stock.x * stock.y));
|
||||
|
||||
tool = null;
|
||||
last = null;
|
||||
animating = false;
|
||||
|
|
@ -56,25 +53,27 @@ export function init(worker) {
|
|||
startTime = 0;
|
||||
updates = 0;
|
||||
stockZ = isIndexed ? 0 : stock.z;
|
||||
maxR = Math.hypot(stock.y, stock.z);
|
||||
|
||||
stockSlices = [];
|
||||
const { x, y, z } = stock;
|
||||
const sliceCount = parseInt(settings.controller.animesh || 2000) / 100;
|
||||
const sliceWidth = stock.x / sliceCount;
|
||||
|
||||
if (controller.manifold)
|
||||
for (let i = 0; i < sliceCount; i++) {
|
||||
let xmin = -(x / 2) + (i * sliceWidth) + sliceWidth / 2;
|
||||
let slice = new Stock(sliceWidth, y, z).translate(xmin, 0, 0);
|
||||
stockSlices.push(slice);
|
||||
slice.updateMesh([]);
|
||||
slice.send(send);
|
||||
// send({ mesh_move: { id: slice.id, pos: { x:0, y:0, z: stock.z/2} } });
|
||||
// send.data({ mesh_move: { id: slice.id, pos: { x:0, y:0, z: stock.z/2} } });
|
||||
}
|
||||
|
||||
send.done();
|
||||
};
|
||||
|
||||
dispatch.animate2 = function (data, send) {
|
||||
renderPause = data.pause || renderPause;
|
||||
renderSpeed = data.speed || 0;
|
||||
if (animating) {
|
||||
return send.done();
|
||||
|
|
@ -126,7 +125,6 @@ function renderPath(send) {
|
|||
return;
|
||||
}
|
||||
pathIndex++;
|
||||
// console.log(next.point.z);
|
||||
|
||||
if (next.tool && (!tool || tool.getID() !== next.tool.getID())) {
|
||||
// on real tool change, go to safe Z first
|
||||
|
|
@ -143,44 +141,50 @@ function renderPath(send) {
|
|||
}
|
||||
|
||||
const id = toolID;
|
||||
const rezstep = rez * 2;
|
||||
const rezstep = Math.min(tool.maxDiameter(), 1) / 4;
|
||||
// console.log({ rezstep });
|
||||
|
||||
if (last) {
|
||||
// console.log(last.point, next.point);
|
||||
const lp = last.point, np = next.point;
|
||||
last = next;
|
||||
|
||||
// render move line on client
|
||||
send.data({ line: [ lp, np ]});
|
||||
|
||||
// dwell ops have no point
|
||||
if (!np || !lp) {
|
||||
return renderPath(send);
|
||||
}
|
||||
|
||||
let dx = np.x - lp.x,
|
||||
dy = np.y - lp.y,
|
||||
dz = np.z - lp.z,
|
||||
da = Math.abs((np.a || 0) - (lp.a || 0)),
|
||||
dr = (da / 360) * (2 * Math.PI * Math.max(np.z, lp.z)),
|
||||
dist = Math.sqrt(dx * dx + dy * dy + dz * dz + dr * dr);
|
||||
|
||||
moveDist += dist;
|
||||
|
||||
// skip moves that are less than grid resolution
|
||||
if (moveDist < rezstep) {
|
||||
// console.log('skip', moveDist, rezstep, next);
|
||||
renderPath(send);
|
||||
return;
|
||||
}
|
||||
da = (np.a ?? 0) - (lp.a ?? 0), // delta rotation degrees
|
||||
AD = maxR * 2, // arc diameter @ max z
|
||||
dr = (Math.abs(da) / 360) * AD, // delta rotation arc
|
||||
dist = Math.max(Math.hypot(dx, dy, dz), dr); // motion distance
|
||||
|
||||
// console.log({ dz, da, AD, dr, dist });
|
||||
// sub-divide long moves for animation and better boolean ops
|
||||
const md = Math.max(Math.abs(dx), Math.abs(dy), Math.abs(dz), dr);
|
||||
const st = Math.ceil(md / rezstep);
|
||||
const mx = dx / st, my = dy / st, mz = dz / st;
|
||||
// const sd = Math.sqrt(mx*mx + my*my + mz*mz + dr*dr);
|
||||
const sd = Math.sqrt(mx * mx + my * my + Math.min(1, mz * mz) + dr * dr);
|
||||
const st = Math.ceil(md / rezstep); // steps
|
||||
const mx = dx / st, my = dy / st, mz = dz / st, ma = da / st;
|
||||
const sd = Math.sqrt(mx * mx + my * my + mz * mz + ma * ma);
|
||||
const sp = next.speed;
|
||||
const moves = [];
|
||||
for (let i = 0, x = lp.x, y = lp.y, z = lp.z; i < st; i++) {
|
||||
moves.push({ x, y, z, a: lp.a, md: sd, dx, dy, dz });
|
||||
for (let i = 0, x = lp.x, y = lp.y, z = lp.z, a = lp.a; i < st; i++) {
|
||||
moves.push({ x, y, z, a, md: sd, dx, dy, dz, da, sp });
|
||||
x += mx;
|
||||
y += my;
|
||||
z += mz;
|
||||
a += ma;
|
||||
}
|
||||
moveDist = 0;
|
||||
moves.push({ ...next.point, md: sd });
|
||||
// console.log({ dist: md, steps: st, speed: next.speed, moves });
|
||||
|
||||
// push last point
|
||||
moves.push({ ...next.point, md: sd, sp });
|
||||
// console.log(...moves);
|
||||
renderMoves(id, moves, send);
|
||||
} else {
|
||||
last = next;
|
||||
|
|
@ -197,26 +201,25 @@ function renderMoves(id, moves, send, seed = 0) {
|
|||
if (!pos) {
|
||||
throw `no pos @ ${index} of ${moves.length}`;
|
||||
}
|
||||
const { dx, dy, dz } = pos;
|
||||
const { dx, dy, dz, da, sp, md } = pos;
|
||||
toolMove(pos);
|
||||
// console.log('renderMoves', {id, moves, seed});
|
||||
let subs = 0;
|
||||
if (dx || dy || dz < 0)
|
||||
if (dx || dy || dz < 0 || da)
|
||||
for (let slice of stockSlices) {
|
||||
if (slice.bounds.intersectsBox(toolMesh.bounds)) {
|
||||
slice.subtractTool(toolMesh);
|
||||
subs++;
|
||||
}
|
||||
}
|
||||
// console.log({ index, subs });
|
||||
renderDist += pos.md;
|
||||
// pause renderer at specified offsets
|
||||
if (renderSpeed && renderDist >= renderSpeed) {
|
||||
renderDist = 0;
|
||||
if (renderSpeed) {
|
||||
let time = sp ? (md / sp) * 60000 : 10;
|
||||
// console.log({ time, ...pos });
|
||||
renderUpdate(send);
|
||||
setTimeout(() => {
|
||||
renderMoves(id, moves, send, index + 1);
|
||||
}, renderPause);
|
||||
}, time / renderSpeed);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,29 +10,35 @@ const { client } = api;
|
|||
let meshes = {},
|
||||
button = {},
|
||||
label = {},
|
||||
lineTracker,
|
||||
material,
|
||||
speedPauses = [0, 0, 0, 0, 0],
|
||||
speedValues = [1, 2, 4, 8, 32],
|
||||
speedNames = ["1x", "2x", "4x", "8x", "!!"],
|
||||
speedMax = speedValues.length - 1,
|
||||
speedIndex = 0,
|
||||
speed,
|
||||
origin,
|
||||
color = 0;
|
||||
color = 0,
|
||||
dark = false,
|
||||
manifold = false,
|
||||
A2R = Math.PI / 180;
|
||||
|
||||
export function animate_clear2(api) {
|
||||
let { anim } = api.ui;
|
||||
client.animate_cleanup2();
|
||||
lineTracker?.clear();
|
||||
Object.keys(meshes).forEach(id => deleteMesh(id));
|
||||
api.widgets.setAxisIndex(0);
|
||||
api.uc.setVisible(anim.laba, true);
|
||||
api.uc.setVisible(anim.vala, true);
|
||||
anim.vala.value = "0.0";
|
||||
client.animate_cleanup2();
|
||||
}
|
||||
|
||||
export function animate2(api, delay) {
|
||||
let alert = api.alerts.show("building animation");
|
||||
let settings = api.conf.get();
|
||||
dark = settings.controller.dark;
|
||||
manifold = settings.controller.manifold;
|
||||
client.animate_setup2(settings, data => {
|
||||
handleUpdate(data);
|
||||
if (data) {
|
||||
|
|
@ -48,7 +54,8 @@ export function animate2(api, delay) {
|
|||
speed: anim.speed,
|
||||
trans: anim.trans,
|
||||
model: anim.model,
|
||||
shade: anim.shade
|
||||
shade: anim.shade,
|
||||
path: anim.path
|
||||
});
|
||||
Object.assign(label, {
|
||||
progress: anim.progress,
|
||||
|
|
@ -59,6 +66,14 @@ export function animate2(api, delay) {
|
|||
a: anim.vala
|
||||
});
|
||||
|
||||
if (manifold) {
|
||||
anim.trans.classList.remove('hide');
|
||||
// anim.shade.classList.remove('hide');
|
||||
} else {
|
||||
anim.trans.classList.add('hide');
|
||||
// anim.shade.classList.add('hide');
|
||||
}
|
||||
|
||||
updateSpeed(0);
|
||||
setTimeout(step, delay || 0);
|
||||
toggleTrans(undefined, false);
|
||||
|
|
@ -72,6 +87,7 @@ export function animate2(api, delay) {
|
|||
button.trans.onclick = toggleTrans;
|
||||
button.model.onclick = toggleModel;
|
||||
button.shade.onclick = toggleStock;
|
||||
button.path.onclick = togglePath;
|
||||
button.play.style.display = '';
|
||||
button.pause.style.display = 'none';
|
||||
|
||||
|
|
@ -86,8 +102,9 @@ Object.assign(client, {
|
|||
},
|
||||
|
||||
animate_setup2(settings, ondone) {
|
||||
color = settings.controller.dark ? 0x888888 : 0;
|
||||
material = new THREE.MeshPhongMaterial({
|
||||
initPathMesh();
|
||||
color = dark ? 0x888888 : 0;
|
||||
material = material ?? new THREE.MeshPhongMaterial({
|
||||
flatShading: true,
|
||||
transparent: false,
|
||||
opacity: 0.5,
|
||||
|
|
@ -102,6 +119,73 @@ Object.assign(client, {
|
|||
}
|
||||
});
|
||||
|
||||
function initPathMesh() {
|
||||
if (lineTracker) {
|
||||
return lineTracker.clear();
|
||||
}
|
||||
let count = 0,
|
||||
max = 10000,
|
||||
rot = new THREE.Euler(0,0,0),
|
||||
geo = new THREE.BufferGeometry(),
|
||||
mat = new THREE.LineBasicMaterial({ color: dark ? 0xffff00 : 0x771100 }),
|
||||
pos = new Float32Array(max * 6),
|
||||
lines = new THREE.LineSegments(geo, mat),
|
||||
vec = new THREE.Vector3(0,0,0),
|
||||
ang = new THREE.Vector3(1,0,0),
|
||||
show = api.local.get('cam.anim.path') ?? true;
|
||||
geo.setAttribute('position', new THREE.BufferAttribute(pos, 3));
|
||||
let track = lineTracker = {
|
||||
xro(p) {
|
||||
vec.set(p.x, p.y, p.z);
|
||||
vec.applyAxisAngle(ang, p.a * A2R);
|
||||
},
|
||||
add(p1, p2) {
|
||||
let ind = count * 6;
|
||||
track.xro(p1);
|
||||
pos[ind+0] = vec.x;
|
||||
pos[ind+1] = vec.y;
|
||||
pos[ind+2] = vec.z;
|
||||
track.xro(p2);
|
||||
pos[ind+3] = vec.x;
|
||||
pos[ind+4] = vec.y;
|
||||
pos[ind+5] = vec.z;
|
||||
count++;
|
||||
if (count >= max) {
|
||||
let opos = pos;
|
||||
max += 10000;
|
||||
pos = new Float32Array(max * 6),
|
||||
pos.set(opos);
|
||||
geo.setAttribute('position', new THREE.BufferAttribute(pos, 3));
|
||||
}
|
||||
geo.setDrawRange(0, count * 2);
|
||||
geo.attributes.position.needsUpdate = true;
|
||||
track.visible(true);
|
||||
},
|
||||
clear() {
|
||||
count = 0;
|
||||
geo.setDrawRange(0,0);
|
||||
track.rotate(0);
|
||||
track.visible(false);
|
||||
},
|
||||
rotate(angle) {
|
||||
rot.x = angle * A2R;
|
||||
lines.setRotationFromEuler(rot);
|
||||
},
|
||||
visible(bool) {
|
||||
if (show && bool !== lines.visible) {
|
||||
lines.visible = bool;
|
||||
} else if (!show) {
|
||||
lines.visible = false;
|
||||
}
|
||||
},
|
||||
show(bool) {
|
||||
show = bool;
|
||||
lines.visible = bool;
|
||||
}
|
||||
}
|
||||
space.world.add(lines);
|
||||
}
|
||||
|
||||
function meshAdd(id, ind, pos, ilen, plen) {
|
||||
const geo = new THREE.BufferGeometry();
|
||||
const pa = plen ? pos.subarray(0, plen * 3) : pos;
|
||||
|
|
@ -132,6 +216,7 @@ function meshUpdate(id, ind, pos, ilen, plen) {
|
|||
|
||||
function deleteMesh(id) {
|
||||
space.world.remove(meshes[id]);
|
||||
meshes[id].geometry.dispose();
|
||||
delete meshes[id];
|
||||
}
|
||||
|
||||
|
|
@ -141,8 +226,15 @@ function toggleModel(ev, bool) {
|
|||
}
|
||||
|
||||
function toggleStock(ev, bool, set) {
|
||||
set !== false && api.local.toggle('cam.anim.stock', bool);
|
||||
return api.event.emit('cam.stock.toggle', bool ?? undefined);
|
||||
bool = api.local.toggle('cam.anim.stock', bool);
|
||||
for (let [ id, mesh ] of Object.entries(meshes)) {
|
||||
if (id >= 0) mesh.visible = bool;
|
||||
}
|
||||
}
|
||||
|
||||
function togglePath(ev, bool, set) {
|
||||
bool = api.local.toggle('cam.anim.path', bool);
|
||||
lineTracker?.show(bool);
|
||||
}
|
||||
|
||||
function toggleTrans(ev, bool) {
|
||||
|
|
@ -165,8 +257,7 @@ function play(opts) {
|
|||
}
|
||||
client.animate2({
|
||||
speed,
|
||||
steps: steps || Infinity,
|
||||
pause: speedPauses[speedIndex]
|
||||
steps: steps || Infinity
|
||||
}, handleUpdate);
|
||||
}
|
||||
|
||||
|
|
@ -177,8 +268,7 @@ function fast(opts) {
|
|||
button.pause.style.display = '';
|
||||
client.animate2({
|
||||
speed,
|
||||
steps: steps || Infinity,
|
||||
pause: speedPauses[speedIndex]
|
||||
steps: steps || Infinity
|
||||
}, handleUpdate);
|
||||
}
|
||||
|
||||
|
|
@ -216,6 +306,7 @@ function handleUpdate(data) {
|
|||
if (data.stock_index !== undefined) {
|
||||
api.widgets.setAxisIndex(data.stock_index);
|
||||
label.a.value = -data.stock_index.toFixed(1);
|
||||
lineTracker.rotate(-data.stock_index);
|
||||
}
|
||||
if (data.mesh_index) {
|
||||
const { id, index } = data.mesh_index;
|
||||
|
|
@ -229,9 +320,12 @@ function handleUpdate(data) {
|
|||
const { id, ind, pos, ilen, plen } = data.mesh_update;
|
||||
meshUpdate(id, ind, pos, ilen, plen);
|
||||
}
|
||||
if (data && data.progress) {
|
||||
if (data.progress) {
|
||||
label.progress.value = (data.progress * 100).toFixed(1);
|
||||
}
|
||||
if (data.line) {
|
||||
lineTracker.add(...data.line);
|
||||
}
|
||||
}
|
||||
|
||||
function updateSpeed(inc = 0) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { selectHoles } from './cl-hole.js';
|
|||
import { selectHelical } from './cl-helical.js';
|
||||
import { surfaceAdd } from './cl-surface.js';
|
||||
import { traceAdd } from './cl-trace.js';
|
||||
import { opRender } from './client.js';
|
||||
|
||||
const { MODES } = api.const;
|
||||
const { uc: UC } = api;
|
||||
|
|
@ -206,7 +207,7 @@ export function createPopOps() {
|
|||
sep: UC.newBlank({ class: "pop-sep" }),
|
||||
spindle: UC.newInput(LANG.cc_spnd_s, { title: LANG.cc_spnd_l, convert: UC.toInt, show: hasSpindle }),
|
||||
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: UC.toFloat, bound: UC.bound(0.01, 1.0) }),
|
||||
stepz: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: UC.toFloat, bound: UC.bound(0, 100.0) }),
|
||||
stepz: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: UC.toFloat, units: true, bound: UC.bound(0, 100.0) }),
|
||||
rate: UC.newInput(LANG.cc_feed_s, { title: LANG.cc_feed_l, convert: UC.toInt, units: true }),
|
||||
down: UC.newInput(LANG.cc_loff_s, { title: LANG.cc_loff_l, convert: UC.toFloat, units: true }),
|
||||
inset: UC.newInput(LANG.cc_lxyo_s, { title: LANG.cc_lxyo_l, convert: UC.toFloat, units: true, show: () => !env.popOp.level.rec.stock }),
|
||||
|
|
@ -282,7 +283,7 @@ export function createPopOps() {
|
|||
plunge: UC.newInput(LANG.cc_plng_s, { title: LANG.cc_plng_l, convert: UC.toInt, units: true }),
|
||||
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: UC.toFloat, units: true }),
|
||||
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: UC.toFloat, bound: UC.bound(0.01, 1.0), show: () => env.popOp.outline.rec.wide }),
|
||||
steps: UC.newInput(LANG.cc_sovc_s, { title: LANG.cc_sovc_l, convert: UC.toInt, bound: UC.bound(1, 5), show: () => env.popOp.outline.rec.wide }),
|
||||
steps: UC.newInput(LANG.cc_sovc_s, { title: LANG.cc_sovc_l, convert: UC.toInt, bound: UC.bound(1, 500), show: () => env.popOp.outline.rec.wide }),
|
||||
sep: UC.newBlank({ class: "pop-sep" }),
|
||||
top: UC.newBoolean(LANG.co_clrt_s, undefined, { title: LANG.co_clrt_l }),
|
||||
inside: UC.newBoolean(LANG.co_olin_s, undefined, { title: LANG.co_olin_l, show: (op) => { return !op.inputs.outside.checked } }),
|
||||
|
|
@ -349,7 +350,9 @@ export function createPopOps() {
|
|||
tolerance: 'camTolerance',
|
||||
filter: 'camContourFilter',
|
||||
leave: 'camContourLeave',
|
||||
linear: 'camLatheLinear'
|
||||
linear: 'camLatheLinear',
|
||||
offStart: 'camLatheOffStart',
|
||||
offEnd: 'camLatheOffEnd'
|
||||
}).inputs = {
|
||||
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
|
||||
// axis: UC.newSelect(LANG.cd_axis, {}, "xyaxis"),
|
||||
|
|
@ -360,6 +363,9 @@ export function createPopOps() {
|
|||
step: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: UC.toFloat, bound: UC.bound(0.01, 100.0) }),
|
||||
angle: UC.newInput(LANG.cc_sang_s, { title: LANG.cc_sang_l, convert: UC.toFloat, bound: UC.bound(0.01, 180.0) }),
|
||||
sep: UC.newBlank({ class: "pop-sep" }),
|
||||
offStart: UC.newInput(LANG.ci_laso_s, { title: LANG.ci_laso_l, convert: UC.toFloat}),
|
||||
offEnd: UC.newInput(LANG.ci_laeo_s, { title: LANG.ci_laeo_l, convert: UC.toFloat}),
|
||||
sep: UC.newBlank({ class: "pop-sep" }),
|
||||
tolerance: UC.newInput(LANG.ou_toll_s, { title: LANG.ou_toll_l, convert: UC.toFloat, bound: UC.bound(0, 10.0), units: true, round: 4 }),
|
||||
leave: UC.newInput(LANG.cf_leav_s, { title: LANG.cf_leav_l, convert: UC.toFloat, bound: UC.bound(0, 100) }),
|
||||
sep: UC.newBlank({ class: "pop-sep" }),
|
||||
|
|
|
|||
|
|
@ -140,8 +140,8 @@ function pointHoverUp(int, ev) {
|
|||
x += stock.x / 2;
|
||||
y += stock.y / 2;
|
||||
}
|
||||
process.camOriginOffX = x;
|
||||
process.camOriginOffY = y;
|
||||
process.camOriginOffX = x - stock.center.x;
|
||||
process.camOriginOffY = y - stock.center.y;
|
||||
process.camOriginOffZ = z;
|
||||
api.settings.update_fields(process);
|
||||
api.platform.update_origin();
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ export function traceHoverUp(int, ev) {
|
|||
|
||||
export function traceToggle(obj, skip) {
|
||||
let material = obj.material[0] || obj.material;
|
||||
if (!material) return;
|
||||
if (!(material && obj.trace)) return;
|
||||
let { color, colorSave } = material;
|
||||
let { widget, poly } = obj.trace;
|
||||
let areas = env.poppedRec.areas;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { Layers } from '../../core/layers.js';
|
|||
import { Stack } from '../../core/stack.js';
|
||||
import { updateStock } from './cl-stock.js';
|
||||
import { createPopOps } from './cl-ops.js';
|
||||
import { tabAdd, tabDone, tabClear, restoreTabs, rotateTabs, updateTabs, clearTabs } from './cl-tab.js';
|
||||
import { tabAdd, tabDone, tabClear, restoreTabs, rotateTabs, updateTabs, clearTabs, mirrorTabs } from './cl-tab.js';
|
||||
import { traceOn, traceDone, unselectTraces } from './cl-trace.js';
|
||||
import { holeSelOn, selectHolesDone, clearHolesRec } from './cl-hole.js';
|
||||
import { surfaceOn, surfaceDone } from './cl-surface.js';
|
||||
|
|
@ -29,6 +29,7 @@ const { widgets: WIDGETS } = api;
|
|||
class Client {
|
||||
animVer = 0;
|
||||
camStock;
|
||||
showStock = true;
|
||||
current;
|
||||
currentIndex;
|
||||
flipping;
|
||||
|
|
@ -320,6 +321,7 @@ export function opRender() {
|
|||
el.classList.add("disabled");
|
||||
}
|
||||
bounds.push(el);
|
||||
let parent = $('camops');
|
||||
let timer = null;
|
||||
let inside = true;
|
||||
let popped = false;
|
||||
|
|
@ -329,14 +331,14 @@ export function opRender() {
|
|||
}
|
||||
el.rec = rec;
|
||||
el.unpop = () => {
|
||||
let pos = [...el.childNodes].indexOf(poprec.div);
|
||||
let pos = [...parent.childNodes].indexOf(poprec.div);
|
||||
if (pos >= 0) {
|
||||
el.removeChild(poprec.div);
|
||||
parent.removeChild(poprec.div);
|
||||
}
|
||||
popped = false;
|
||||
};
|
||||
function onEnter(ev) {
|
||||
if ((surfaceOn || traceOn) && env.poppedRec != rec) {
|
||||
if ((clock || surfaceOn || traceOn) && env.poppedRec != rec) {
|
||||
return;
|
||||
}
|
||||
if (popped && env.poppedRec != rec) {
|
||||
|
|
@ -351,22 +353,24 @@ export function opRender() {
|
|||
popped = true;
|
||||
poprec.use(rec);
|
||||
env.hoveredOp = el;
|
||||
if (!clock) {
|
||||
// offset Y position of pop div by % of Y screen location of button
|
||||
el.appendChild(poprec.div);
|
||||
poprec.addNote();
|
||||
const { innerHeight } = window;
|
||||
const brect = ev.target.getBoundingClientRect();
|
||||
const prect = poprec.div.getBoundingClientRect();
|
||||
const pcty = (brect.top / innerHeight) * 0.9;
|
||||
const offpx = -pcty * prect.height;
|
||||
poprec.div.style.transform = `translateY(${offpx}px)`;
|
||||
}
|
||||
// offset Y position of pop div by % of Y screen location of button
|
||||
parent.appendChild(poprec.div);
|
||||
poprec.addNote();
|
||||
const { innerHeight } = window;
|
||||
const brect = ev.target.getBoundingClientRect();
|
||||
const prect = parent.getBoundingClientRect();
|
||||
const Prect = poprec.div.getBoundingClientRect();
|
||||
const tdiff = prect.top - brect.top;
|
||||
const botoff = innerHeight - (brect.top + Prect.height);
|
||||
const offpx = -tdiff + (botoff < 0 ? botoff : -Prect.height/3);
|
||||
poprec.div.style.transform = `translateY(${offpx}px)`;
|
||||
poprec.div.onmouseenter = () => { inside = true };
|
||||
poprec.div.onmouseleave = onLeave;
|
||||
// option click event appears latent
|
||||
// and overides the sticky settings
|
||||
setTimeout(() => {
|
||||
UC.setSticky(false);
|
||||
}, 0);
|
||||
}, 10);
|
||||
}
|
||||
function onLeave(ev) {
|
||||
inside = false;
|
||||
|
|
@ -637,7 +641,8 @@ export function init() {
|
|||
});
|
||||
|
||||
api.event.on("cam.stock.toggle", (bool) => {
|
||||
env.camStock && (env.camStock.visible = bool ?? !env.camStock.visible);
|
||||
env.showStock = bool ?? !env.showStock;
|
||||
updateStock();
|
||||
});
|
||||
|
||||
api.event.on("boolean.click", api.platform.update_bounds);
|
||||
|
|
|
|||
|
|
@ -12,10 +12,8 @@ export function cam_export(print, online) {
|
|||
const widget = print.widgets[0];
|
||||
if (!widget) return;
|
||||
|
||||
console.log({ cam_export: print });
|
||||
|
||||
const { settings } = print;
|
||||
const { device, tools } = settings;
|
||||
const { device } = settings;
|
||||
|
||||
let i,
|
||||
time = 0,
|
||||
|
|
@ -257,17 +255,18 @@ export function cam_export(print, online) {
|
|||
points--;
|
||||
return;
|
||||
}
|
||||
let gn;
|
||||
if (out.emit >= 0 && out.emit <= 3) gn = `G${out.emit}`;
|
||||
let speed = out.speed,
|
||||
arc = out.emit == 2 || out.emit == 3,
|
||||
center = out.center,
|
||||
nl = (compact_output && lastGn === gn) ? [] : [gn],
|
||||
dx = opt.dx || newpos.x - pos.x,
|
||||
|
||||
let dx = opt.dx || newpos.x - pos.x,
|
||||
dy = opt.dy || newpos.y - pos.y,
|
||||
dz = opt.dz || newpos.z - pos.z,
|
||||
da = newpos.a != pos.a,
|
||||
maxf = dz ? maxZd : maxXYd,
|
||||
gv = (dz >= 0 && out.emit >= 0 && out.emit <= 3) ? out.emit : 1,
|
||||
gn = `G${gv}`,
|
||||
speed = out.speed,
|
||||
arc = out.emit == 2 || out.emit == 3,
|
||||
center = out.center,
|
||||
nl = (compact_output && lastGn === gn) ? [] : [gn],
|
||||
maxf = (dz < 0 ? maxZd : maxXYd) || speed,
|
||||
feed = Math.min(speed || maxf, maxf),
|
||||
dist = Math.sqrt(dx * dx + dy * dy + dz * dz),
|
||||
newFeed = feed && feed !== pos.f;
|
||||
|
|
@ -424,7 +423,9 @@ export function cam_export(print, online) {
|
|||
toolz[out.tool] = ctool;
|
||||
}
|
||||
point = out.point;
|
||||
if (!point || point.mod) return;
|
||||
if (!point || point.mod) {
|
||||
return;
|
||||
}
|
||||
// ensure not point is modified twice
|
||||
point.mod = 1;
|
||||
if (isOffset) {
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ export function menu() {
|
|||
anim.trans = newButton(null,null,{icon:'<i class="fa-solid fa-border-none"></i>',title:"transparency",class:"padleft"}),
|
||||
anim.model = newButton(null,null,{icon:'<i class="fa-solid fa-eye"></i>',title:"show model"}),
|
||||
anim.shade = newButton(null,null,{icon:'<i class="fa-solid fa-cube"></i>',title:"stock box"}),
|
||||
anim.path = newButton(null,null,{icon:'<i class="fa-solid fa-draw-polygon"></i>',title:"path"}),
|
||||
])
|
||||
},
|
||||
|
||||
|
|
@ -105,7 +106,7 @@ export function menu() {
|
|||
separator: newBlank({ class:"set-sep", driven }),
|
||||
camEaseAngle: newInput(LANG.ou_eang_s, {title:LANG.ou_eang_l, convert:toFloat, bound:bound(0.1,85), show:() => ui.camEaseDown.checked}),
|
||||
camFullEngage: newInput(LANG.ou_feng_s, {title:LANG.ou_feng_l, convert:toFloat, bound:bound(0.1,1.0)}),
|
||||
_____: newGroup(LANG.co_menu, $('cam-origin'), { modes:CAM, driven, separator }),
|
||||
_____: newGroup(LANG.or_menu, $('cam-origin'), { modes:CAM, driven, separator }),
|
||||
camOriginTop: newBoolean(LANG.or_topp_s, onBooleanClick, {title:LANG.or_topp_l}),
|
||||
camOriginCenter: newBoolean(LANG.or_cntr_s, onBooleanClick, {title:LANG.or_cntr_l}),
|
||||
separator: newBlank({ class:"set-sep", driven }),
|
||||
|
|
|
|||
|
|
@ -4,26 +4,70 @@ import { CamOp } from './op.js';
|
|||
import { Tool } from './tool.js';
|
||||
import { generate as Topo } from './topo3.js';
|
||||
import { newPoint } from '../../../geo/point.js';
|
||||
import { newPolygon } from '../../../geo/polygon.js';
|
||||
import { tip2tipEmit } from '../../../geo/paths.js';
|
||||
|
||||
function createFilter(op) {
|
||||
function createFilter(op, origin, axis) {
|
||||
// console.log({ origin, axis });
|
||||
let ok = () => true;
|
||||
let filter = slices => slices;
|
||||
let filterString = op.filter?.map(l => l.trim()).join('\n');
|
||||
if (filterString) {
|
||||
try {
|
||||
const obj = eval(`( ${filterString} )`);
|
||||
let idx = 0;
|
||||
if (obj && obj.slices) {
|
||||
const nadd = [];
|
||||
filter = function (slices) {
|
||||
for (let slice of slices) {
|
||||
if (obj.slices(slice, idx++)) {
|
||||
nadd.push(slice);
|
||||
let box = obj?.box;
|
||||
let slice_fn = obj?.slices;
|
||||
let index = 0;
|
||||
const accept = [];
|
||||
filter = function (slices) {
|
||||
for (let slice of slices) {
|
||||
if (slice_fn && slice_fn(slice, index++)) {
|
||||
accept.push(slice);
|
||||
} else if (box) {
|
||||
// slice.z = x when axis = y
|
||||
// slice.z = y when axis = x
|
||||
let { x, y, z } = box;
|
||||
x = x ?? [ -Infinity, Infinity ];
|
||||
y = y ?? [ -Infinity, Infinity ];
|
||||
z = z ?? [ -Infinity, Infinity ];
|
||||
let ok = false;
|
||||
if (axis === 'x') {
|
||||
let sy = slice.z + origin.y;
|
||||
if (sy >= y[0] && sy <= y[1]) {
|
||||
ok = true;
|
||||
for (let p of slice.camLines) {
|
||||
p.points = p.points.filter(p =>
|
||||
p.x - origin.x >= x[0] && p.x - origin.x <= x[1] &&
|
||||
p.z - origin.z >= z[0] && p.z - origin.z <= z[1]
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let sx = slice.z - origin.x;
|
||||
if (sx >= x[0] && sx <= x[1]) {
|
||||
ok = true;
|
||||
for (let p of slice.camLines) {
|
||||
p.points = p.points.filter(p =>
|
||||
p.y + origin.y >= y[0] && p.y + origin.y <= y[1] &&
|
||||
p.z - origin.z >= z[0] && p.z - origin.z <= z[1]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
slice.camLines = slice.camLines.map(p => {
|
||||
if (p.points.length > 1) {
|
||||
return newPolygon(p.points).setOpen(true);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}).filter(p => p);
|
||||
accept.push(slice);
|
||||
}
|
||||
}
|
||||
return nadd;
|
||||
};
|
||||
}
|
||||
}
|
||||
return accept;
|
||||
};
|
||||
} catch (e) {
|
||||
console.log('filter parse error', e, op.filter);
|
||||
}
|
||||
|
|
@ -38,8 +82,8 @@ class OpContour extends CamOp {
|
|||
|
||||
async slice(progress) {
|
||||
let { op, state } = this;
|
||||
let { addSlices, settings, updateToolDiams } = state;
|
||||
let filter = createFilter(op);
|
||||
let { color, addSlices, settings, updateToolDiams } = state;
|
||||
let filter = createFilter(op, settings.origin, op.axis.toLowerCase());
|
||||
let toolDiam = this.toolDiam = new Tool(settings, op.tool).fluteDiameter();
|
||||
updateToolDiams(toolDiam);
|
||||
// we need topo for safe travel moves when roughing and outlining
|
||||
|
|
@ -54,6 +98,11 @@ class OpContour extends CamOp {
|
|||
slices = filter(slices);
|
||||
this.sliceOut = slices;
|
||||
addSlices(slices);
|
||||
for (let slice of slices) {
|
||||
slice.output()
|
||||
.setLayer(`contour ${op.axis}`, { face: color, line: color })
|
||||
.addPolys(slice.camLines);
|
||||
}
|
||||
},
|
||||
contour: op,
|
||||
state: state
|
||||
|
|
@ -64,11 +113,11 @@ class OpContour extends CamOp {
|
|||
|
||||
prepare(ops, progress) {
|
||||
let { op, state, sliceOut } = this;
|
||||
let { settings, widget } = state;
|
||||
let { settings } = state;
|
||||
let { process } = settings;
|
||||
|
||||
let { setTolerance, setTool, setSpindle, setPrintPoint } = ops;
|
||||
let { camOut, polyEmit, newLayer, printPoint, lastPoint } = ops;
|
||||
let { camOut, newLayer, printPoint, lastPoint } = ops;
|
||||
let { bounds, zmax } = ops;
|
||||
|
||||
let toolDiam = this.toolDiam;
|
||||
|
|
@ -87,9 +136,9 @@ class OpContour extends CamOp {
|
|||
if (!slice.camLines) {
|
||||
continue;
|
||||
}
|
||||
let polys = [], poly, emit;
|
||||
let polys = [], poly;
|
||||
slice.camLines.forEach(function (poly) {
|
||||
if (depthFirst) poly = poly.clone(true);
|
||||
if (depthFirst) poly = poly.clone(true).annotate({ slice: slice.index + 1 });
|
||||
polys.push({ first: poly.first(), last: poly.last(), poly: poly });
|
||||
});
|
||||
if (depthFirst) {
|
||||
|
|
@ -101,7 +150,7 @@ class OpContour extends CamOp {
|
|||
poly.reverse();
|
||||
}
|
||||
poly.forEachPoint(function (point, pidx) {
|
||||
camOut(point.clone(), pidx > 0, stepover);
|
||||
camOut(point.clone(), pidx > 0, { moveLen: stepover });
|
||||
}, false);
|
||||
});
|
||||
newLayer();
|
||||
|
|
@ -115,7 +164,7 @@ class OpContour extends CamOp {
|
|||
poly.reverse();
|
||||
}
|
||||
poly.forEachPoint(function (point, pidx) {
|
||||
camOut(point.clone(), pidx > 0, stepover);
|
||||
camOut(point.clone().annotate({ slice: poly.slice }), pidx > 0, { moveLen: stepover });
|
||||
}, false);
|
||||
newLayer();
|
||||
return lastPoint();
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ class OpDrill extends CamOp {
|
|||
async slice(progress) {
|
||||
let { op, state } = this;
|
||||
let { settings, addSlices, widget, updateToolDiams } = state;
|
||||
let { zBottom, zThru, thruHoles, color } = state;
|
||||
let { drills, drillThrough } = op
|
||||
let { color } = state;
|
||||
let { drills } = op
|
||||
|
||||
let drillTool = new Tool(settings, op.tool),
|
||||
drillToolDiam = drillTool.fluteDiameter(),
|
||||
|
|
@ -47,7 +47,7 @@ class OpDrill extends CamOp {
|
|||
slice.camLines = [poly];
|
||||
|
||||
slice.output()
|
||||
.setLayer("drill", { face: color, line: color })
|
||||
.setLayer(state.layername, { face: color, line: color })
|
||||
.addPolys(slice.camLines);
|
||||
addSlices(slice);
|
||||
sliceOut.push(slice);
|
||||
|
|
@ -55,11 +55,10 @@ class OpDrill extends CamOp {
|
|||
}
|
||||
|
||||
prepare(ops, progress) {
|
||||
let { op, state } = this;
|
||||
let { settings, widget, addSlices, updateToolDiams } = state;
|
||||
let { op } = this;
|
||||
let { setTool, setSpindle, setDrill, emitDrills } = ops;
|
||||
setTool(op.tool, undefined, op.rate);
|
||||
setDrill(op.down, op.lift, op.dwell, op.thru);
|
||||
setDrill(op.down, op.lift, op.dwell);
|
||||
setSpindle(op.spindle);
|
||||
emitDrills(this.sliceOut.map(slice => slice.camLines).flat());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export class OpIndex extends CamOp {
|
|||
}
|
||||
|
||||
prepare(ops, progress) {
|
||||
const { lastPoint, zmax, zclear, camOut, stock } = ops;
|
||||
const { lastPoint, zmax, zclear, camOut, stock, newLayer } = ops;
|
||||
let last = lastPoint();
|
||||
if (last) {
|
||||
// max point of stock corner radius when rotating (safe z when indexing)
|
||||
|
|
@ -34,7 +34,7 @@ export class OpIndex extends CamOp {
|
|||
// move above rotating stock
|
||||
camOut(last = last.clone().setZ(zmove), 0);
|
||||
// issue rotation command
|
||||
camOut(last = last.clone().setY(0).setA(this.degrees), 0);
|
||||
camOut(last = last.clone().setZ(zmove).setA(this.degrees), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,22 +6,23 @@ import { generate as topo4_generate } from './topo4.js';
|
|||
import { newPoint } from '../../../geo/point.js';
|
||||
|
||||
function createFilter(op) {
|
||||
let ok = () => true;
|
||||
let filter = slices => slices;
|
||||
if (op.filter) {
|
||||
let filterString = op.filter?.map(l => l.trim()).join('\n');
|
||||
if (filterString) {
|
||||
try {
|
||||
const obj = eval(`( ${op.filter.join('\n')} )`);
|
||||
let idx = 0;
|
||||
if (obj && obj.slices) {
|
||||
const nadd = [];
|
||||
filter = function (slices) {
|
||||
for (let slice of slices) {
|
||||
if (obj.slices(slice, idx++)) {
|
||||
nadd.push(slice);
|
||||
}
|
||||
const obj = eval(`( ${filterString} )`);
|
||||
const accept = [];
|
||||
let index = 0;
|
||||
let sl_fn = obj?.slices ?? ok;
|
||||
filter = function (slices) {
|
||||
for (let slice of slices) {
|
||||
if (sl_fn(slice, index++)) {
|
||||
accept.push(slice);
|
||||
}
|
||||
return nadd;
|
||||
};
|
||||
}
|
||||
}
|
||||
return accept;
|
||||
};
|
||||
} catch (e) {
|
||||
console.log('filter parse error', e, op.filter);
|
||||
}
|
||||
|
|
@ -36,9 +37,8 @@ class OpLathe extends CamOp {
|
|||
|
||||
async slice(progress) {
|
||||
let { op, state } = this;
|
||||
let { addSlices } = state;
|
||||
let { addSlices, color } = state;
|
||||
let filter = createFilter(op);
|
||||
|
||||
this.topo = await topo4_generate({
|
||||
op,
|
||||
state,
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@ class OpLevel extends CamOp {
|
|||
|
||||
let points = [];
|
||||
let clear = op.stock ?
|
||||
[ newPolygon().centerRectangle({x:0,y:0,z:0}, stock.x, stock.y) ] :
|
||||
[ newPolygon().centerRectangle({x:0,y:0,z:0}, stock.x + toolDiam/2, stock.y) ] :
|
||||
POLY.outer(POLY.offset(tshadow, toolDiam * (op.inset || 0)));
|
||||
|
||||
POLY.fillArea(clear, 90, stepOver, points);
|
||||
POLY.fillArea(clear, 1090, stepOver, points);
|
||||
|
||||
let layers = this.layers = [];
|
||||
for (let z of zList) {
|
||||
|
|
|
|||
|
|
@ -17,9 +17,11 @@ class OpOutline extends CamOp {
|
|||
|
||||
async slice(progress) {
|
||||
let { op, state } = this;
|
||||
let { settings, widget, slicer, addSlices, tshadow, thruHoles, unsafe, color } = state;
|
||||
let { settings, slicer, addSlices, tshadow, thruHoles, unsafe, color, widget } = state;
|
||||
let { updateToolDiams, tabs, cutTabs, cutPolys, workarea, zMax, shadowAt } = state;
|
||||
let { process, stock } = settings;
|
||||
let { controller } = settings;
|
||||
let center_off = widget.track.pos ?? { x: 0, y: 0, z: 0};
|
||||
|
||||
if (op.down <= 0) {
|
||||
throw `invalid step down "${op.down}"`;
|
||||
|
|
@ -39,6 +41,8 @@ class OpOutline extends CamOp {
|
|||
let indices = slicer.interval(op.down, intopt);
|
||||
let trueShadow = process.camTrueShadow === true;
|
||||
let lastShadowZ;
|
||||
let stockClip;
|
||||
|
||||
// shift out first (top-most) slice
|
||||
indices.shift();
|
||||
// add flats to shadow
|
||||
|
|
@ -168,16 +172,12 @@ class OpOutline extends CamOp {
|
|||
let stepover = toolDiam * op.step;
|
||||
let wideCuts = [] //accumulator for wide cuts
|
||||
for (let c = (op.steps || 1); c > 0; c--){
|
||||
offset.slice().forEach(op => {
|
||||
// clone removes inners but the real solution is
|
||||
// to limit expanded shells to through holes
|
||||
let wideCut = POLY.expand([op.clone(true)], stepover*c, slice.z, [], 1);
|
||||
wideCut.forEach(cut =>{ //set order of cuts when wide
|
||||
cut.order = c
|
||||
if(cut.inner) cut.inner.forEach(inn =>{ inn.order = c })
|
||||
});
|
||||
wideCuts.push(...wideCut)
|
||||
let wideCut = POLY.expand(offset.clone(true), stepover * c, slice.z, [], 1);
|
||||
wideCut.forEach(cut =>{ //set order of cuts when wide
|
||||
cut.order = c
|
||||
if(cut.inner) cut.inner.forEach(inn =>{ inn.order = c })
|
||||
});
|
||||
wideCuts.push(...wideCut)
|
||||
}
|
||||
offset.appendAll(wideCuts);
|
||||
}
|
||||
|
|
@ -187,16 +187,19 @@ class OpOutline extends CamOp {
|
|||
addDogbones(offset, toolDiam / 5);
|
||||
}
|
||||
|
||||
if (process.camStockClipTo && stock.x && stock.y && stock.center) {
|
||||
let { center } = stock;
|
||||
let x = center.x - center_off.x;
|
||||
let y = center.y - center_off.y;
|
||||
stockClip = newPolygon().centerRectangle({ x, y }, stock.x + 0.001, stock.y + 0.001);
|
||||
offset = cutPolys([stockClip], offset, slice.z, true);
|
||||
}
|
||||
|
||||
if (tabs) {
|
||||
tabs.forEach(tab => {
|
||||
tab.off = POLY.expand([tab.poly], toolDiam / 2).flat();
|
||||
});
|
||||
offset = cutTabs(tabs, offset, slice.z);
|
||||
}
|
||||
|
||||
if (process.camStockClipTo && stock.x && stock.y && stock.center) {
|
||||
let rect = newPolygon().centerRectangle({x:0,y:0}, stock.x, stock.y);
|
||||
offset = cutPolys([rect], offset, slice.z, true);
|
||||
offset = cutTabs(tabs, offset);
|
||||
}
|
||||
|
||||
// offset.xout(`slice ${slice.z}`);
|
||||
|
|
@ -208,11 +211,17 @@ class OpOutline extends CamOp {
|
|||
|
||||
// project empty up and render
|
||||
for (let slice of slices) {
|
||||
if (false) slice.output()
|
||||
if (controller.devel) slice.output()
|
||||
.setLayer("slice", {line: 0xaaaa00}, false)
|
||||
.addPolys(slice.topPolys())
|
||||
if (controller.devel) slice.output()
|
||||
.setLayer("shadow", {line: 0x557799}, false)
|
||||
.addPolys(slice.shadow)
|
||||
if (controller.devel && stockClip) slice.output()
|
||||
.setLayer("stock clip", {line: 0x557799}, false)
|
||||
.addPolys([ stockClip ])
|
||||
slice.output()
|
||||
.setLayer("outline", {face: color, line: color})
|
||||
.setLayer(state.layername, {face: color, line: color})
|
||||
.addPolys(slice.camLines);
|
||||
}
|
||||
|
||||
|
|
@ -224,9 +233,9 @@ class OpOutline extends CamOp {
|
|||
let { op, state, sliceOut } = this;
|
||||
let { setTool, setSpindle, setPrintPoint } = ops;
|
||||
let { polyEmit, depthOutlinePath } = ops;
|
||||
let { camOut, newLayer, printPoint } = ops;
|
||||
let { settings, widget } = state;
|
||||
let { process, controller } = settings;
|
||||
let { newLayer, printPoint } = ops;
|
||||
let { settings } = state;
|
||||
let { process } = settings;
|
||||
|
||||
let easeDown = process.camEaseDown;
|
||||
let toolDiam = this.toolDiam;
|
||||
|
|
|
|||
|
|
@ -19,13 +19,11 @@ class OpPocket extends CamOp {
|
|||
|
||||
async slice(progress) {
|
||||
const pocket = this;
|
||||
const debug = false;
|
||||
let { op, state } = this;
|
||||
let { tool, rate, down, plunge, expand, contour, smooth, tolerance } = op;
|
||||
let { ov_botz, ov_conv } = op;
|
||||
let { settings, widget, addSlices, zBottom, zThru, tabs, color } = state;
|
||||
let { settings, widget, addSlices, zBottom, tabs, color } = state;
|
||||
let { updateToolDiams, cutTabs, healPolys, shadowAt, workarea } = state;
|
||||
let { process } = settings;
|
||||
zBottom = ov_botz ? workarea.bottom_stock + ov_botz : zBottom;
|
||||
// generate tracing offsets from chosen features
|
||||
let sliceOut;
|
||||
|
|
@ -36,24 +34,18 @@ class OpPocket extends CamOp {
|
|||
let cutdir = ov_conv;
|
||||
let engrave = contour && op.engrave;
|
||||
let zTop = workarea.top_z;
|
||||
let devel = settings.controller.devel;
|
||||
let smoothVal = (smooth ?? 0) / 10;
|
||||
if (contour) {
|
||||
down = 0;
|
||||
this.topo = await Topo({
|
||||
// onupdate: (update, msg) => {
|
||||
onupdate: (index, total, msg) => {
|
||||
progress((index / total) * 0.9, msg);
|
||||
},
|
||||
ondone: (slices) => {
|
||||
// console.log({ contour: slices });
|
||||
},
|
||||
contour: {
|
||||
tool,
|
||||
tolerance,
|
||||
inside: true,
|
||||
axis: "-"
|
||||
},
|
||||
state: state
|
||||
});
|
||||
this.contour = {
|
||||
axis: "-",
|
||||
inside: true,
|
||||
nogpu: true,
|
||||
step: toolOver,
|
||||
tolerance,
|
||||
tool,
|
||||
};
|
||||
}
|
||||
updateToolDiams(toolDiam);
|
||||
if (tabs) {
|
||||
|
|
@ -69,7 +61,7 @@ class OpPocket extends CamOp {
|
|||
sliceOut.push(slice);
|
||||
return slice;
|
||||
}
|
||||
function clearZ(polys, z, down) {
|
||||
async function clearZ(polys, z, down) {
|
||||
if (down) {
|
||||
// adjust step down to a value <= down that
|
||||
// ends on the lowest z specified
|
||||
|
|
@ -92,14 +84,14 @@ class OpPocket extends CamOp {
|
|||
let clip = [], shadow;
|
||||
if (contour) {
|
||||
if (smooth) {
|
||||
clip = POLY.offset(POLY.offset([ poly ], smooth), -smooth);
|
||||
clip = POLY.offset(POLY.offset([ poly ], smoothVal), -smoothVal);
|
||||
} else {
|
||||
clip = [ poly ];
|
||||
}
|
||||
} else {
|
||||
shadow = shadowAt(z);
|
||||
if (smooth) {
|
||||
shadow = POLY.setZ(POLY.offset(POLY.offset(shadow, smooth), -smooth), z);
|
||||
shadow = POLY.setZ(POLY.offset(POLY.offset(shadow, smoothVal), -smoothVal), z);
|
||||
}
|
||||
POLY.subtract([ poly ], shadow, clip, undefined, undefined, 0);
|
||||
if (op.outline) {
|
||||
|
|
@ -130,16 +122,16 @@ class OpPocket extends CamOp {
|
|||
}
|
||||
POLY.setWinding(slice.camLines, cutdir, false);
|
||||
if (contour) {
|
||||
slice.camLines = pocket.conform(slice.camLines, op.refine, engrave, pct => {
|
||||
slice.camLines = await pocket.conform(slice.camLines, op.refine, engrave, pct => {
|
||||
progress(0.9 + (zpro + zinc * pct) * 0.1, "conform");
|
||||
});
|
||||
}
|
||||
slice.output()
|
||||
.setLayer("pocket", {line: color}, false)
|
||||
.setLayer(state.layername, {line: color}, false)
|
||||
.addPolys(slice.camLines)
|
||||
if (debug && shadow) slice.output()
|
||||
if (devel && shadow) slice.output()
|
||||
.setLayer("pocket shadow", {line: 0xff8811}, false)
|
||||
.addPolys(shadow)
|
||||
.addPolys(shadow);
|
||||
if (!contour) {
|
||||
progress(zpro, "pocket");
|
||||
}
|
||||
|
|
@ -168,23 +160,37 @@ class OpPocket extends CamOp {
|
|||
outline = POLY.setWinding(outline, cutdir, false);
|
||||
outline = healPolys(outline);
|
||||
if (smooth) {
|
||||
outline = POLY.offset(POLY.offset(outline, smooth), -smooth);
|
||||
outline = POLY.offset(POLY.offset(outline, smoothVal), -smoothVal);
|
||||
}
|
||||
if (outline.length) {
|
||||
// option to skip interior features (holes, pillars)
|
||||
if (op.outline) {
|
||||
POLY.clearInner(outline);
|
||||
}
|
||||
if (debug) newSliceOut(zmin).output()
|
||||
await clearZ(outline, zmin + 0.0001, down);
|
||||
if (devel && sliceOut?.length) sliceOut[0].output()
|
||||
.setLayer("pocket area", {line: 0x1188ff}, false)
|
||||
.addPolys(outline)
|
||||
clearZ(outline, zmin + 0.0001, down);
|
||||
progress(1, "pocket");
|
||||
}
|
||||
}
|
||||
|
||||
// mold cam output lines to the surface of the topo offset by tool geometry
|
||||
conform(camLines, refine, engrave, progress) {
|
||||
async conform(camLines, refine, engrave, progress) {
|
||||
if (!this.topo) {
|
||||
console.log('deferred topo');
|
||||
this.topo = await Topo({
|
||||
// onupdate: (update, msg) => {
|
||||
onupdate: (index, total, msg) => {
|
||||
progress((index / total) * 0.9, msg);
|
||||
},
|
||||
ondone: (slices) => {
|
||||
// console.log({ contour: slices });
|
||||
},
|
||||
contour: this.contour,
|
||||
state: this.state
|
||||
});
|
||||
}
|
||||
const topo = this.topo;
|
||||
// re-segment polygon to a higher resolution
|
||||
const hirez = camLines.map(p => p.segment(topo.tolerance * 2));
|
||||
|
|
@ -236,7 +242,7 @@ class OpPocket extends CamOp {
|
|||
|
||||
prepare(ops, progress) {
|
||||
let { op, state, pockets } = this;
|
||||
let { setTool, setSpindle, setTolerance, sliceOutput, getPrintPoint } = ops;
|
||||
let { getPrintPoint , pocket, setTool, setSpindle, setTolerance } = ops;
|
||||
let { process } = state.settings;
|
||||
|
||||
setTool(op.tool, op.rate);
|
||||
|
|
@ -269,11 +275,12 @@ class OpPocket extends CamOp {
|
|||
}
|
||||
if (min.pocket) {
|
||||
min.pocket.used = true;
|
||||
sliceOutput(min.pocket, {
|
||||
pocket({
|
||||
cutdir: op.ov_conv,
|
||||
depthFirst: process.camDepthFirst && !state.isIndexed,
|
||||
easeDown: op.down && process.easeDown ? op.down : 0,
|
||||
progress: (n,m) => progress(n/m, "pocket")
|
||||
progress: (n,m) => progress(n/m, "pocket"),
|
||||
slices: min.pocket
|
||||
});
|
||||
} else {
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class OpRegister extends CamOp {
|
|||
|
||||
let tool = new Tool(settings, op.tool);
|
||||
let sliceOut = this.sliceOut = [];
|
||||
let axis = op.axis.toLowerCase();
|
||||
|
||||
updateToolDiams(tool.fluteDiameter());
|
||||
|
||||
|
|
@ -42,7 +43,7 @@ class OpRegister extends CamOp {
|
|||
return;
|
||||
}
|
||||
|
||||
switch (op.axis.toLowerCase()) {
|
||||
switch (axis) {
|
||||
case "x":
|
||||
if (op.points == 3) {
|
||||
pathPoints.push(newPoint(boundMinX - cutOffset, centerY, 0));
|
||||
|
|
@ -64,6 +65,7 @@ class OpRegister extends CamOp {
|
|||
}
|
||||
break;
|
||||
case "-":
|
||||
case "=":
|
||||
let halfOffset = toolOffset / 2,
|
||||
loopMinX = boundMinX - cutOffset,
|
||||
loopMaxX = boundMaxX + cutOffset,
|
||||
|
|
@ -84,7 +86,7 @@ class OpRegister extends CamOp {
|
|||
}
|
||||
function rept(count, step, fn) {
|
||||
while (count-- > 0) {
|
||||
fn(step, count === 0);
|
||||
fn(step, count);
|
||||
step = -step;
|
||||
}
|
||||
}
|
||||
|
|
@ -93,9 +95,16 @@ class OpRegister extends CamOp {
|
|||
addSlices(slice);
|
||||
sliceOut.push(slice);
|
||||
start(z);
|
||||
rept(4, halfOffset, oy => {
|
||||
rept(4, halfOffset, (oy, count) => {
|
||||
move(0, -oy);
|
||||
move(deltaX, 0);
|
||||
if (axis === '=' && count % 2 === 1) {
|
||||
move(deltaX/2, 0);
|
||||
move(0, oy/2);
|
||||
move(0, -oy/2);
|
||||
move(deltaX/2, 0);
|
||||
} else {
|
||||
move(deltaX, 0);
|
||||
}
|
||||
});
|
||||
rept(4, halfOffset, ox => {
|
||||
move(ox, 0);
|
||||
|
|
@ -136,11 +145,10 @@ class OpRegister extends CamOp {
|
|||
}
|
||||
|
||||
prepare(ops, progress) {
|
||||
let { op, state } = this;
|
||||
let { settings, widget, addSlices, updateToolDiams } = state;
|
||||
let { op } = this;
|
||||
let { setTool, setSpindle, setDrill, emitDrills } = ops;
|
||||
|
||||
if (op.axis === '-') {
|
||||
if (op.axis === '-' || op.axis === '=') {
|
||||
setTool(op.tool, op.feed, op.rate);
|
||||
setSpindle(op.spindle);
|
||||
for (let slice of this.sliceOut) {
|
||||
|
|
|
|||
|
|
@ -15,11 +15,12 @@ class OpRough extends CamOp {
|
|||
|
||||
async slice(progress) {
|
||||
let { op, state } = this;
|
||||
let { settings, slicer, addSlices, unsafe, color } = state;
|
||||
let { settings, slicer, addSlices, unsafe, color, widget } = state;
|
||||
let { updateToolDiams, thruHoles, tabs, cutTabs, cutPolys } = state;
|
||||
let { ztOff, zMax, shadowAt, isIndexed} = state;
|
||||
let { workarea } = state;
|
||||
let { process, stock } = settings;
|
||||
let { controller, process, stock } = settings;
|
||||
let center_off = widget.track.pos ?? { x: 0, y: 0, z: 0};
|
||||
|
||||
if (op.down <= 0) {
|
||||
throw `invalid step down "${op.down}"`;
|
||||
|
|
@ -36,6 +37,12 @@ class OpRough extends CamOp {
|
|||
|
||||
updateToolDiams(toolDiam);
|
||||
|
||||
if (tabs) {
|
||||
tabs.forEach(tab => {
|
||||
tab.off = POLY.expand([tab.poly], toolDiam / 2).flat();
|
||||
});
|
||||
}
|
||||
|
||||
// clear the stock above the area to be roughed out
|
||||
if (workarea.top_z > workarea.top_part && !cutThruBypass) {
|
||||
let shadow = state.shadow.base.clone();
|
||||
|
|
@ -58,14 +65,11 @@ class OpRough extends CamOp {
|
|||
slice.z = z;
|
||||
let polys = POLY.setZ(facing.clone(true), slice.z + roughLeaveZ);
|
||||
if (tabs) {
|
||||
tabs.forEach(tab => {
|
||||
tab.off = POLY.expand([tab.poly], toolDiam / 2).flat();
|
||||
});
|
||||
polys = cutTabs(tabs, polys, slice.z);
|
||||
}
|
||||
slice.camLines = polys;
|
||||
slice.output()
|
||||
.setLayer("roughing", {face: color, line: color})
|
||||
.setLayer(state.layername, {face: color, line: color})
|
||||
.addPolys(slice.camLines);
|
||||
addSlices(slice);
|
||||
camFaces.push(slice);
|
||||
|
|
@ -206,20 +210,6 @@ class OpRough extends CamOp {
|
|||
}
|
||||
}
|
||||
|
||||
if (tabs) {
|
||||
tabs.forEach(tab => {
|
||||
tab.off = POLY.expand([tab.poly], toolDiam / 2).flat();
|
||||
});
|
||||
offset = cutTabs(tabs, offset, slice.z);
|
||||
}
|
||||
|
||||
if (!offset) return;
|
||||
|
||||
if (process.camStockClipTo && stock.x && stock.y && stock.center) {
|
||||
let rect = newPolygon().centerRectangle(stock.center, stock.x, stock.y);
|
||||
offset = cutPolys([rect], offset, slice.z, true);
|
||||
}
|
||||
|
||||
// elimate double inset on inners
|
||||
offset.forEach(op => {
|
||||
if (op.inner) {
|
||||
|
|
@ -236,24 +226,38 @@ class OpRough extends CamOp {
|
|||
}
|
||||
});
|
||||
|
||||
if (process.camStockClipTo && stock.x && stock.y && stock.center) {
|
||||
let { center } = stock;
|
||||
let x = center.x - center_off.x;
|
||||
let y = center.y - center_off.y;
|
||||
let rect = newPolygon().centerRectangle({ x, y }, stock.x + 0.001, stock.y + 0.001);
|
||||
offset = cutPolys([rect], offset, slice.z, true);
|
||||
}
|
||||
|
||||
if (!offset) return;
|
||||
|
||||
slice.camLines = offset;
|
||||
if (roughLeaveZ) {
|
||||
// offset roughing in Z as well to minimize
|
||||
// tool marks on curved surfaces
|
||||
// offset roughing in Z as well to minimize tool marks on curved surfaces
|
||||
// const roughLeaveZ = 1 * Math.min(roughDown, roughLeave / 2);
|
||||
slice.camLines.forEach(p => {
|
||||
p.setZ(p.getZ() + roughLeaveZ);
|
||||
});
|
||||
for (let poly of slice.camLines) {
|
||||
for (let point of poly.points) point.z += roughLeaveZ;
|
||||
}
|
||||
}
|
||||
if (controller.devel) {
|
||||
if (tabs) slice.output()
|
||||
.setLayer("tabs clip", {line: 0xaa00aa}, true)
|
||||
.addPolys(tabs.map(tab => tab.off).flat());
|
||||
slice.output()
|
||||
.setLayer("slice", {line: 0xaaaa00}, true)
|
||||
.addPolys(slice.topPolys())
|
||||
// .setLayer("top shadow", {line: 0x0000aa})
|
||||
// .addPolys(tshadow)
|
||||
// .setLayer("rough shadow", {line: 0x00aa00})
|
||||
// .addPolys(shadow)
|
||||
.setLayer("shadow clip", {line: 0xaa0000})
|
||||
.addPolys(shell);
|
||||
}
|
||||
if (false) slice.output()
|
||||
.setLayer("slice", {line: 0xaaaa00}, true)
|
||||
.addPolys(slice.topPolys())
|
||||
// .setLayer("top shadow", {line: 0x0000aa})
|
||||
// .addPolys(tshadow)
|
||||
// .setLayer("rough shadow", {line: 0x00aa00})
|
||||
// .addPolys(shadow)
|
||||
.setLayer("rough shell", {line: 0xaa0000})
|
||||
.addPolys(shell);
|
||||
progress(0.5 + 0.5 * (index / slices.length));
|
||||
});
|
||||
|
||||
|
|
@ -278,8 +282,11 @@ class OpRough extends CamOp {
|
|||
}
|
||||
|
||||
slices.forEach(slice => {
|
||||
if (slice.camLines && tabs) {
|
||||
slice.camLines = cutTabs(tabs, slice.camLines);
|
||||
}
|
||||
slice.output()
|
||||
.setLayer("roughing", {face: color, line: color})
|
||||
.setLayer(state.layername, {face: color, line: color})
|
||||
.addPolys(slice.camLines);
|
||||
});
|
||||
this.sliceOut = slices.filter(slice => slice.camLines);
|
||||
|
|
@ -289,15 +296,13 @@ class OpRough extends CamOp {
|
|||
|
||||
prepare(ops, progress) {
|
||||
let { op, state, sliceOut, camFaces } = this;
|
||||
let { setTool, setSpindle, setPrintPoint, sliceOutput, polyEmit } = ops;
|
||||
let { camOut, newLayer, printPoint } = ops;
|
||||
let { setTool, setSpindle, setPrintPoint, pocket, polyEmit, newLayer, printPoint } = ops;
|
||||
let { settings } = state;
|
||||
let { process } = settings;
|
||||
|
||||
let easeDown = process.camEaseDown;
|
||||
let cutdir = op.ov_conv;
|
||||
let depthFirst = process.camDepthFirst && !state.isIndexed;
|
||||
let depthData = [];
|
||||
|
||||
setTool(op.tool, op.rate, op.plunge);
|
||||
setSpindle(op.spindle);
|
||||
|
|
@ -325,11 +330,12 @@ class OpRough extends CamOp {
|
|||
|
||||
// output the roughing passes
|
||||
setPrintPoint(printPoint);
|
||||
sliceOutput(sliceOut, {
|
||||
pocket({
|
||||
cutdir,
|
||||
depthFirst,
|
||||
easeDown: op.down && easeDown ? 0.001 : 0,
|
||||
progress: (n,m) => progress(n/m, "routing")
|
||||
progress: (n,m) => progress(n/m, "routing"),
|
||||
slices: sliceOut
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ class OpTrace extends CamOp {
|
|||
}
|
||||
POLY.setWinding(slice.camLines, offset === "outside" ? !cutdir : cutdir, false);
|
||||
slice.output()
|
||||
.setLayer("trace follow", {line: color}, false)
|
||||
.setLayer(state.layername, {line: color}, false)
|
||||
.addPolys(slice.camLines)
|
||||
}
|
||||
function clearZnew(polys, z, down) {
|
||||
|
|
@ -154,7 +154,7 @@ class OpTrace extends CamOp {
|
|||
.setLayer("trace poly", {line: 0x1188ff}, false)
|
||||
.addPolys([ poly ])
|
||||
slice.output()
|
||||
.setLayer("trace", {line: color}, false)
|
||||
.setLayer(state.layername, {line: color}, false)
|
||||
.addPolys(slice.camLines)
|
||||
progress(zpro, "trace");
|
||||
zpro += zinc;
|
||||
|
|
@ -295,7 +295,6 @@ class OpTrace extends CamOp {
|
|||
|
||||
prepare(ops, progress) {
|
||||
let { op, state } = this;
|
||||
let { settings } = state;
|
||||
let { setTool, setSpindle } = ops;
|
||||
|
||||
setTool(op.tool, op.rate);
|
||||
|
|
|
|||
|
|
@ -56,8 +56,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
|
||||
if (widget.camops.length === 0 || widget.meta.disabled) return;
|
||||
|
||||
let device = settings.device,
|
||||
process = settings.process,
|
||||
let { device, process } = settings,
|
||||
isIndexed = process.camStockIndexed,
|
||||
startCenter = process.camOriginCenter,
|
||||
alignTop = settings.controller.alignTop,
|
||||
|
|
@ -65,7 +64,6 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
stockz = stock.z * (isIndexed ? 0.5 : 1),
|
||||
outer = settings.bounds || widget.getPositionBox(),
|
||||
outerz = outer.max.z,
|
||||
slices = widget.slices,
|
||||
zclear = (process.camZClearance || 1),
|
||||
zmax_force = process.camForceZMax || false,
|
||||
zmax_outer = stockz + zclear,
|
||||
|
|
@ -75,13 +73,13 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
boundsz = isIndexed ? stock.z / 2 : bounds.max.z + ztOff,
|
||||
zadd = !isIndexed ? stock.z - boundsz : alignTop ? outerz - boundsz : 0,
|
||||
zmax = outerz + zclear + (process.camOriginOffZ || 0),
|
||||
zsafe = isIndexed ? Math.hypot(bounds.dim.y, bounds.dim.z) / 2 + zclear : zmax,
|
||||
wmpos = widget.track.pos,
|
||||
wmx = wmpos.x,
|
||||
wmy = wmpos.y,
|
||||
originx = (startCenter ? 0 : -stock.x / 2) + (process.camOriginOffX || 0),
|
||||
originy = (startCenter ? 0 : -stock.y / 2) + (process.camOriginOffY || 0),
|
||||
origin = newPoint(originx + wmx, originy + wmy, zmax),
|
||||
output = print.output,
|
||||
easeDown = process.camEaseDown,
|
||||
easeAngle = process.camEaseAngle,
|
||||
depthFirst = process.camDepthFirst,
|
||||
|
|
@ -94,13 +92,11 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
drillDown = 0,
|
||||
drillLift = 0,
|
||||
drillDwell = 0,
|
||||
drillThru = 0,
|
||||
lasering = false,
|
||||
laserPower = 0,
|
||||
newOutput = print.output || [],
|
||||
layerOut = [],
|
||||
printPoint,
|
||||
isNewMode,
|
||||
isPocket,
|
||||
isContour,
|
||||
isRough,
|
||||
|
|
@ -112,6 +108,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
toolDiamMove,
|
||||
plungeRate = process.camFastFeedZ,
|
||||
feedRate,
|
||||
lastOp,
|
||||
lastTool,
|
||||
lastPush,
|
||||
lastPoint,
|
||||
|
|
@ -187,11 +184,10 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
laserPower = power;
|
||||
}
|
||||
|
||||
function setDrill(down, lift, dwell, thru) {
|
||||
function setDrill(down, lift, dwell) {
|
||||
drillDown = down;
|
||||
drillLift = lift;
|
||||
drillDwell = dwell;
|
||||
drillThru = thru;
|
||||
}
|
||||
|
||||
function emitDrills(polys) {
|
||||
|
|
@ -264,10 +260,11 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
*/
|
||||
function layerPush(point, emit, speed, tool, options) {
|
||||
const { type, center, arcPoints } = options ?? {};
|
||||
const dz = (point && lastPush && lastPush.point) ? point.z - lastPush.point.z : 0;
|
||||
if (dz < 0 && speed > plungeRate) {
|
||||
const dz = (point && lastPush?.point) ? point.z - lastPush.point.z : 0;
|
||||
if (dz < -0.05 && speed > plungeRate) {
|
||||
speed = plungeRate;
|
||||
}
|
||||
// if (options?.type !== 'lerp') console.log( point, currentOp.type );
|
||||
layerOut.mode = currentOp;
|
||||
if (lasering) {
|
||||
let power = emit ? laserPower : 0;
|
||||
|
|
@ -320,7 +317,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
p.x + wmx,
|
||||
p.y + wmy,
|
||||
p.z + zadd
|
||||
).setA(p.a ?? lastPoint?.a);
|
||||
).setA(p.a ?? lastPoint?.a).annotate({ slice: p.slice });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -333,7 +330,26 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
* @param {number} opts.factor speed scale factor
|
||||
*/
|
||||
function camOut(point, emit = 1, opts) {
|
||||
// console.trace({point, emit, opts})
|
||||
let lop = lastOp;
|
||||
lastOp = currentOp;
|
||||
|
||||
// on operation changes:
|
||||
// 1. move to safe z of current point preserving angle
|
||||
// 2. move to safe z of new point preserving old angle
|
||||
// 3. move to safe z of new point with new angle
|
||||
if (lop && lop !== currentOp && lastPoint) {
|
||||
// compensate for applyWidgetMovement() applied to lastPoint
|
||||
let lpo = lastPoint.clone().move({ x: -wmx, y: -wmy, z: -zadd });
|
||||
camOut(lpo.clone().setZ(zsafe).setA(lastPoint.a), 0);
|
||||
camOut(point.clone().setZ(zsafe).setA(lastPoint.a), 0);
|
||||
camOut(point.clone().setZ(zsafe), 0);
|
||||
}
|
||||
|
||||
if (lop?.type === 'index' && lop !== currentOp ) {
|
||||
// console.log('post index first point', point);
|
||||
camOut(point.clone().setZ(lastPoint.z).setA(lastPoint.a), 1);
|
||||
}
|
||||
|
||||
let {
|
||||
center = {},
|
||||
clockwise = true,
|
||||
|
|
@ -342,14 +358,12 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
factor = 1,
|
||||
} = opts ?? {}
|
||||
|
||||
// console.log({radius})
|
||||
const isArc = emit == 2 || emit == 3;
|
||||
const pointA = point.a;
|
||||
|
||||
// apply widget movement pos
|
||||
const pointA = point.a;
|
||||
point = applyWidgetMovement(point);
|
||||
|
||||
// console.log(point.z);
|
||||
if (nextIsMove) {
|
||||
emit = 0;
|
||||
nextIsMove = false;
|
||||
|
|
@ -370,7 +384,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
for (let a of lerp) {
|
||||
let lp = point.clone().setA(a);
|
||||
// console.log(lp.a, lp.x, lp.y, lp.z);
|
||||
lastPoint = layerPush(
|
||||
layerPush(
|
||||
lp,
|
||||
emit,
|
||||
rate,
|
||||
|
|
@ -403,16 +417,20 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
layerPush(point.clone().setZ(stockz + 1), 0, 0, tool);
|
||||
// change to cutting move for remainder of plunge
|
||||
emit = 1;
|
||||
isMove = false;
|
||||
}
|
||||
|
||||
// drop points too close together
|
||||
if (!isLathe && !isArc && deltaXY < 0.001 && point.z === lastPoint.z) {
|
||||
if (!isLathe && !isArc && deltaXY < 0.001 && point.z === lastPoint.z && point.a === lastPoint.a) {
|
||||
// console.trace(["drop dup",lastPoint,point]);
|
||||
return;
|
||||
}
|
||||
|
||||
// no jump moves in contour mode to adjacent slice points
|
||||
let steady = lastPoint && currentOp.type === 'contour' && Math.abs(point.slice - lastPoint.slice) < 4;
|
||||
|
||||
// convert short planar moves to cuts in some cases
|
||||
if (hasDelta && !isRough && !isArc && isMove && deltaXY <= moveLen && deltaZ <= 0 && !lasering) {
|
||||
if (hasDelta && !isArc && isMove && deltaXY <= moveLen && deltaZ <= 0 && !lasering) {
|
||||
let iscontour = tolerance > 0;
|
||||
let isflat = absDeltaZ < 0.001;
|
||||
// restrict this to contouring
|
||||
|
|
@ -431,7 +449,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
} else if (point.z < lastPoint.z) {
|
||||
layerPush(point.clone().setZ(lastPoint.z), 0, 0, tool);
|
||||
}
|
||||
} else if (isMove) {
|
||||
} else if (isMove && !steady) {
|
||||
// for longer moves, check the terrain to see if we need to go up and over
|
||||
const bigXY = (deltaXY > moveLen && !lasering);
|
||||
const bigZ = (deltaZ > toolDiam / 2 && deltaXY > tolerance);
|
||||
|
|
@ -478,13 +496,15 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
}
|
||||
|
||||
// set new plunge rate
|
||||
let tmprate;
|
||||
if (false)
|
||||
if (!lasering && !isLathe && deltaZ < -tolerance) {
|
||||
let threshold = Math.min(deltaXY / 2, absDeltaZ),
|
||||
modifier = threshold / absDeltaZ;
|
||||
if (synthPlunge && threshold && modifier && deltaXY > tolerance) {
|
||||
// use modifier to speed up long XY move plunge rates
|
||||
// console.log('modifier', modifier);
|
||||
rate = Math.max(
|
||||
tmprate = Math.max(
|
||||
plungeRate,
|
||||
Math.round(plungeRate + ((feedRate - plungeRate) * modifier))
|
||||
);
|
||||
|
|
@ -492,7 +512,8 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
let L = Math.hypot(deltaXY, absDeltaZ);
|
||||
let limXY = feedRate * L / deltaXY;
|
||||
let limZ = plungeRate * L / Math.abs(deltaZ);
|
||||
rate = Math.min(feedRate, limXY, limZ);
|
||||
tmprate = Math.min(feedRate, limXY, limZ);
|
||||
console.log({ rate_override: rate, was: rate });
|
||||
// let zps = len / absDeltaZ;
|
||||
// rate = Math.max(
|
||||
// plungeRate,
|
||||
|
|
@ -508,7 +529,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
lastPoint = layerPush(
|
||||
point,
|
||||
clockwise ? 2 : 3,
|
||||
rate,
|
||||
tmprate ?? rate,
|
||||
tool,
|
||||
{
|
||||
center,
|
||||
|
|
@ -523,7 +544,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
lastPoint = layerPush(
|
||||
point,
|
||||
emit,
|
||||
rate,
|
||||
tmprate ?? rate,
|
||||
tool
|
||||
);
|
||||
}
|
||||
|
|
@ -533,16 +554,15 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
* output an array of slices that form a pocket
|
||||
* used by rough and pocket ops, does not support arcs
|
||||
*
|
||||
* @param {Slice[]} top-down Z stack of slices
|
||||
* @param {*} opts
|
||||
* @param {Slice[]} slices top-down Z stack of slices
|
||||
* @param {boolean} cutdir true=CW false=CCW
|
||||
* @param {boolean} depthFirst prioritize cut depth in pockets by nesting
|
||||
*/
|
||||
function sliceOutput(sliceOut, opts = {}) {
|
||||
const { cutdir, depthFirst, easeDown, progress } = opts;
|
||||
|
||||
function pocket({ slices, cutdir, depthFirst, easeDown, progress }) {
|
||||
let total = 0;
|
||||
let depthData = [];
|
||||
|
||||
for (let slice of sliceOut) {
|
||||
for (let slice of slices) {
|
||||
let polys = [], t = [], c = [];
|
||||
POLY.flatten(slice.camLines).forEach(function (poly) {
|
||||
let child = poly.parent;
|
||||
|
|
@ -569,7 +589,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
}, { swapdir: false });
|
||||
newLayer();
|
||||
}
|
||||
progress(++total, sliceOut.length);
|
||||
progress(++total, slices.length);
|
||||
}
|
||||
|
||||
// crucially returns true for -0 as well as other negative #s
|
||||
|
|
@ -663,44 +683,32 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
// make top start offset configurable
|
||||
printPoint = firstPoint || origin;
|
||||
|
||||
// accumulated data for depth-first optimizations
|
||||
// let depthData = {
|
||||
// rough: [],
|
||||
// outline: [],
|
||||
// roughDiam: 0,
|
||||
// outlineDiam: 0,
|
||||
// contourx: [],
|
||||
// contoury: [],
|
||||
// trace: [],
|
||||
// drill: [],
|
||||
// layer: 0,
|
||||
// };
|
||||
|
||||
let ops = {
|
||||
stock,
|
||||
setTool,
|
||||
setDrill,
|
||||
setSpindle,
|
||||
setTolerance,
|
||||
setPrintPoint,
|
||||
setLasering,
|
||||
getPrintPoint() { return printPoint },
|
||||
printPoint,
|
||||
newLayer,
|
||||
addGCode,
|
||||
bounds,
|
||||
camOut,
|
||||
polyEmit,
|
||||
poly2polyEmit,
|
||||
tip2tipEmit,
|
||||
depthRoughPath,
|
||||
depthOutlinePath,
|
||||
sliceOutput,
|
||||
depthRoughPath,
|
||||
emitDrills,
|
||||
emitTrace,
|
||||
bounds,
|
||||
getPrintPoint() { return printPoint },
|
||||
lastPoint: () => { return lastPoint },
|
||||
newLayer,
|
||||
pocket,
|
||||
poly2polyEmit,
|
||||
polyEmit,
|
||||
printPoint,
|
||||
setDrill,
|
||||
setLasering,
|
||||
setPrintPoint,
|
||||
setSpindle,
|
||||
setTolerance,
|
||||
setTool,
|
||||
stock,
|
||||
tip2tipEmit,
|
||||
zclear,
|
||||
zmax,
|
||||
lastPoint: () => { return lastPoint }
|
||||
zsafe,
|
||||
};
|
||||
|
||||
let opSum = 0;
|
||||
|
|
@ -973,8 +981,8 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
|
||||
|
||||
let arcPoints = arcToPath(from, to, arcPreviewRes, { clockwise, center })
|
||||
.map(applyWidgetMovement);
|
||||
|
||||
.map(applyWidgetMovement);
|
||||
|
||||
// console.log({arcPoints})
|
||||
|
||||
camOut(from, 1);
|
||||
|
|
@ -1096,7 +1104,10 @@ export function prepEach(widget, settings, print, firstPoint, update) {
|
|||
if (lastPoint && newOutput.length) {
|
||||
let lastLayer = newOutput.filter(layer => Array.isArray(layer)).peek();
|
||||
if (Array.isArray(lastLayer)) {
|
||||
print.addOutput(lastLayer, printPoint = lastPoint.clone().setZ(zmax_outer), 0, 0, tool);
|
||||
printPoint = lastPoint.clone();
|
||||
if (printPoint.z < zmax_outer) printPoint.setZ(zmax_outer);
|
||||
print.addOutput(lastLayer, printPoint, 0, 0, tool);
|
||||
// print.addOutput(lastLayer, printPoint = lastPoint.clone().setZ(zmax_outer), 0, 0, tool);
|
||||
}
|
||||
}
|
||||
// console.log("prepare output", newOutput);
|
||||
|
|
|
|||
|
|
@ -119,6 +119,10 @@ export async function cam_slice(settings, widget, onupdate, ondone) {
|
|||
ondone(msg);
|
||||
}
|
||||
|
||||
function alert(msg) {
|
||||
onupdate(null,null,msg);
|
||||
}
|
||||
|
||||
if (unsafe) {
|
||||
console.log("disabling overhang safeties");
|
||||
}
|
||||
|
|
@ -135,20 +139,20 @@ export async function cam_slice(settings, widget, onupdate, ondone) {
|
|||
let maxDelta = 1e-3;
|
||||
|
||||
if (stock.x + maxDelta < (bounds.max.x - bounds.min.x)) {
|
||||
return error('stock X too small for part. resize stock or use offset stock');
|
||||
alert('stock X too small for part');
|
||||
}
|
||||
|
||||
if (stock.y + maxDelta < (bounds.max.y - bounds.min.y)) {
|
||||
return error('stock Y too small for part. resize stock or use offset stock');
|
||||
alert('stock Y too small for part');
|
||||
}
|
||||
|
||||
if (stock.z + maxDelta < (bounds.max.z - bounds.min.z)) {
|
||||
return error('stock Z too small for part. resize stock or use offset stock');
|
||||
alert('stock Z too small for part');
|
||||
}
|
||||
}
|
||||
|
||||
if (zMin >= bounds.max.z) {
|
||||
return error(`invalid z bottom ${(zMin / units).round(3)} >= bounds z max ${(zMax / units).round(3)}`);
|
||||
alert(`invalid z bottom ${(zMin / units).round(3)} >= bounds z max ${(zMax / units).round(3)}`);
|
||||
}
|
||||
|
||||
let mark = Date.now();
|
||||
|
|
@ -158,31 +162,31 @@ export async function cam_slice(settings, widget, onupdate, ondone) {
|
|||
let shadows = {};
|
||||
let slicer;
|
||||
let state = {
|
||||
settings,
|
||||
widget,
|
||||
addSlices,
|
||||
bounds,
|
||||
tabs,
|
||||
cutTabs,
|
||||
cutPolys,
|
||||
color,
|
||||
computeShadows,
|
||||
contourPolys,
|
||||
cutPolys,
|
||||
cutTabs,
|
||||
dark,
|
||||
healPolys,
|
||||
isIndexed,
|
||||
ops: opList,
|
||||
setAxisIndex,
|
||||
settings,
|
||||
shadowAt,
|
||||
slicer,
|
||||
addSlices,
|
||||
isIndexed,
|
||||
setAxisIndex,
|
||||
updateToolDiams,
|
||||
tabs,
|
||||
unsafe,
|
||||
updateSlicer,
|
||||
computeShadows,
|
||||
updateToolDiams,
|
||||
widget,
|
||||
zBottom,
|
||||
zMax,
|
||||
zThru,
|
||||
ztOff,
|
||||
zMax,
|
||||
zTop,
|
||||
unsafe,
|
||||
color,
|
||||
dark,
|
||||
ops: opList
|
||||
zTop
|
||||
};
|
||||
let tracker = setSliceTracker({ rotation: 0 });
|
||||
|
||||
|
|
@ -310,8 +314,11 @@ export async function cam_slice(settings, widget, onupdate, ondone) {
|
|||
workover.bottom_z = isIndexed ? valz.ov_botz : bottom_stock + valz.ov_botz;
|
||||
workover.bottom_cut = Math.max(workover.bottom_z, -zThru);
|
||||
}
|
||||
// state.workarea = workover;
|
||||
let { note, type } = op.op;
|
||||
let named = note ? note.split(' ').filter(v => v.charAt(0) === '#') : [];
|
||||
let layername = named.length ? named : (note ? `${type} (${note})` : type);
|
||||
Object.assign(state, {
|
||||
layername,
|
||||
zBottom,
|
||||
zThru,
|
||||
ztOff,
|
||||
|
|
@ -688,7 +695,7 @@ export async function holes(settings, widget, individual, rec, onProgress) {
|
|||
let opts = { each: onEach, progress: (num, total) => onProgress(num / total * 0.5, "slice") };
|
||||
await slicer.slice(indices, opts);
|
||||
let shadowedDrills = false;
|
||||
// console.log("slices",slices)
|
||||
|
||||
for (let [i, slice] of slices.entries()) {
|
||||
for (let top of slice.tops) {
|
||||
// console.log("slicing",slice.z,top)
|
||||
|
|
@ -698,13 +705,13 @@ export async function holes(settings, widget, individual, rec, onProgress) {
|
|||
continue;
|
||||
}
|
||||
for (let poly of inner) {
|
||||
if (poly.points.length < 7) continue;
|
||||
|
||||
if (poly.points.length < 7) {
|
||||
continue;
|
||||
}
|
||||
let center = poly.calcCircleCenter();
|
||||
center.area = poly.area();
|
||||
center.overlapping = [center];
|
||||
center.depth = 0;
|
||||
// console.log("center",center)
|
||||
if (poly.circularity() < 0.98) {
|
||||
// if not circular, don't add to holes
|
||||
continue;
|
||||
|
|
@ -717,8 +724,7 @@ export async function holes(settings, widget, individual, rec, onProgress) {
|
|||
let overlap = false;
|
||||
for (let [i, circle] of circles.entries()) {
|
||||
let dist = circle.distTo2D(center);
|
||||
|
||||
// //if on the same xy point,
|
||||
// if on the same xy point,
|
||||
if (dist <= centerDiff) {
|
||||
// console.log("overlap",center,circle);
|
||||
circle.overlapping.push(center);
|
||||
|
|
@ -732,12 +738,10 @@ export async function holes(settings, widget, individual, rec, onProgress) {
|
|||
}
|
||||
onProgress(0.5 + (i / slices.length * 0.25), "recognize circles");
|
||||
}
|
||||
|
||||
let drills = [];
|
||||
|
||||
for (let [i, c] of circles.entries()) {
|
||||
let overlapping = c.overlapping;
|
||||
|
||||
let last = overlapping.shift();
|
||||
while (overlapping.length) {
|
||||
let circ = overlapping.shift();
|
||||
|
|
@ -775,14 +779,33 @@ export async function holes(settings, widget, individual, rec, onProgress) {
|
|||
return drills;
|
||||
}
|
||||
|
||||
function cutTabs(tabs, offset, z, inter) {
|
||||
tabs = tabs.filter(tab => z < tab.pos.z + tab.dim.z / 2).map(tab => tab.off).flat();
|
||||
return cutPolys(tabs, offset, z, false);
|
||||
function cutTabs(tabs, offset) {
|
||||
let out = [];
|
||||
for (let tab of tabs) {
|
||||
tab.top = tab.pos.z + tab.dim.z / 2;
|
||||
}
|
||||
for (let poly of offset) {
|
||||
let polyZ = poly.getZ();
|
||||
let cut = tabs.filter(tab => tab.top >= polyZ);
|
||||
let lo = poly.cut(cut.map(tab => tab.off).flat(), false);
|
||||
let hi = [];
|
||||
for (let tab of cut) {
|
||||
hi.appendAll(POLY.setZ(poly.cut(tab.off, true), tab.top));
|
||||
}
|
||||
if (hi.length) {
|
||||
let heal = healPolys([ ...lo, ...hi ], false);
|
||||
POLY.setWinding(heal, poly.isClockwise());
|
||||
out.push(...heal);
|
||||
} else {
|
||||
out.push(poly);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function cutPolys(polys, offset, z, inter) {
|
||||
function cutPolys(polys, offset) {
|
||||
let noff = [];
|
||||
offset.forEach(op => noff.appendAll(op.cut(POLY.union(polys, 0, true), inter)));
|
||||
offset.forEach(op => noff.appendAll( op.cut(POLY.union(polys, 0, true), true) ));
|
||||
return healPolys(noff);
|
||||
}
|
||||
|
||||
|
|
@ -799,7 +822,17 @@ function contourPolys(widget, polys) {
|
|||
}
|
||||
}
|
||||
|
||||
function healPolys(noff) {
|
||||
function dedup(arr, same) {
|
||||
const out = [];
|
||||
let prev;
|
||||
for (const x of arr) {
|
||||
if (!prev || !same(prev, x)) out.push(x);
|
||||
prev = x;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function healPolys(noff, sameZ = true) {
|
||||
for (let p of noff) {
|
||||
if (p.appearsClosed()) {
|
||||
p.points.pop();
|
||||
|
|
@ -818,29 +851,29 @@ function healPolys(noff) {
|
|||
let s2 = ntmp[j];
|
||||
if (!s2) continue;
|
||||
// require polys at same Z to heal
|
||||
if (Math.abs(s1.getZ() - s2.getZ()) > 0.01) {
|
||||
if (sameZ && Math.abs(s1.getZ() - s2.getZ()) > 0.01) {
|
||||
continue;
|
||||
}
|
||||
if (!(s1.open && s2.open)) continue;
|
||||
if (s1.last().isMergable2D(s2.first())) {
|
||||
s1.addPoints(s2.points.slice(1));
|
||||
s1.addPoints(s2.points);
|
||||
ntmp[j] = null;
|
||||
continue outer;
|
||||
}
|
||||
if (s2.last().isMergable2D(s1.first())) {
|
||||
s2.addPoints(s1.points.slice(1));
|
||||
s2.addPoints(s1.points);
|
||||
ntmp[i] = null;
|
||||
continue outer;
|
||||
}
|
||||
if (s1.first().isMergable2D(s2.first())) {
|
||||
s1.reverse();
|
||||
s1.addPoints(s2.points.slice(1));
|
||||
s1.addPoints(s2.points);
|
||||
ntmp[j] = null;
|
||||
continue outer;
|
||||
}
|
||||
if (s1.last().isMergable2D(s2.last())) {
|
||||
s2.reverse();
|
||||
s1.addPoints(s2.points.slice(1));
|
||||
s1.addPoints(s2.points);
|
||||
ntmp[j] = null;
|
||||
continue outer;
|
||||
}
|
||||
|
|
@ -854,9 +887,14 @@ function healPolys(noff) {
|
|||
}
|
||||
// close poly if head meets tail
|
||||
for (let poly of noff) {
|
||||
if (poly.open && poly.first().isMergable2D(poly.last())) {
|
||||
poly.points.pop();
|
||||
poly.open = false;
|
||||
poly.points = dedup(poly.points, (a,b) => a.isMergable3D(b));
|
||||
if (poly.open) {
|
||||
if (poly.first().isMergable3D(poly.last())) {
|
||||
poly.points.pop();
|
||||
poly.open = false;
|
||||
} else if (poly.first().isMergable2D(poly.last())) {
|
||||
poly.open = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ const { sliceConnect, sliceDedup } = slicer;
|
|||
const { config } = base;
|
||||
const timing = false;
|
||||
const zDecimal = 3;
|
||||
const epsilon = 10e-5;
|
||||
|
||||
const begin = function () {
|
||||
if (timing) console.time(...arguments);
|
||||
|
|
@ -94,7 +95,7 @@ export class Slicer {
|
|||
countZ(p3.z);
|
||||
}
|
||||
// use co-flat and co-line detection to adjust slice Z
|
||||
if (p1.z === p2.z && p2.z === p3.z) {
|
||||
if (Math.abs(p1.z - p2.z) < epsilon && Math.abs(p2.z - p3.z) < epsilon) {
|
||||
// detect zFlat faces to avoid slicing directly on them
|
||||
let zkey = p1.z.toFixed(zDecimal),
|
||||
area = Math.abs(util.area2(p1, p2, p3)) / 2;
|
||||
|
|
|
|||
|
|
@ -16,9 +16,10 @@ export class Topo {
|
|||
|
||||
async generate(opt = {}) {
|
||||
let { state, contour, onupdate, ondone } = opt;
|
||||
let { widget, settings, tshadow, center, tabs, color } = opt.state;
|
||||
let { widget, settings, tshadow, tabs } = opt.state;
|
||||
|
||||
let { controller, process } = settings,
|
||||
{ webGPU } = controller,
|
||||
animesh = parseInt(controller.animesh || 100) * 2500,
|
||||
axis = contour.axis.toLowerCase(),
|
||||
contourX = axis === "x",
|
||||
|
|
@ -37,16 +38,15 @@ export class Topo {
|
|||
boundsY = maxY - minY,
|
||||
inside = contour.inside,
|
||||
density = 1 + (contour.reduction || 0),
|
||||
resolution = tolerance ? tolerance : 1 / Math.sqrt(animesh / (boundsX * boundsY)),
|
||||
resolution = (tolerance ? tolerance : 1 / Math.sqrt(animesh / (boundsX * boundsY))).round(5),
|
||||
tool = new Tool(settings, contour.tool),
|
||||
toolOffset = tool.generateProfile(resolution).profile,
|
||||
toolDiameter = tool.fluteDiameter(),
|
||||
toolStep = toolDiameter * contour.step, //tool.contourOffset(contour.step),
|
||||
toolStep = toolDiameter * contour.step,
|
||||
leave = contour.leave || 0,
|
||||
maxangle = contour.angle,
|
||||
curvesOnly = contour.curves,
|
||||
bridge = contour.bridging || 0,
|
||||
// R2A = 180 / Math.PI,
|
||||
stepsX = Math.ceil(boundsX / resolution),
|
||||
stepsY = Math.ceil(boundsY / resolution),
|
||||
widtopo = widget.topo,
|
||||
|
|
@ -75,12 +75,7 @@ export class Topo {
|
|||
clipTo = inside ? shadow : POLY.expand(shadow, toolDiameter / 2 + resolution * 3),
|
||||
partOff = inside ? 0 : toolDiameter / 2 + resolution,
|
||||
gridDelta = Math.floor(partOff / resolution),
|
||||
debug = false,
|
||||
debug_clips = debug && true,
|
||||
debug_topo = debug && true,
|
||||
debug_topo_lines = debug && true,
|
||||
debug_topo_shells = debug && true,
|
||||
time = Date.now;
|
||||
debug_clips = false;
|
||||
|
||||
if (tolerance === 0 && !topoCache) {
|
||||
console.log(widget.id, 'topo auto tolerance', resolution.round(4));
|
||||
|
|
@ -90,7 +85,7 @@ export class Topo {
|
|||
// used by Pocket -> Contour
|
||||
this.tolerance = resolution;
|
||||
|
||||
if (tabs) {
|
||||
if (tabsOn) {
|
||||
clipTab.appendAll(tabs.map(tab => {
|
||||
let ctab = POLY.expand([tab.poly], toolDiameter / 2);
|
||||
ctab.forEach(ct => ct.z = tab.dim.z / 2 + tab.pos.z);
|
||||
|
|
@ -107,6 +102,190 @@ export class Topo {
|
|||
newslices.push(debug);
|
||||
}
|
||||
|
||||
if (webGPU && !contour.nogpu) {
|
||||
// invert tool Z offset for gpu code
|
||||
let toolBounds = new THREE.Box3()
|
||||
.expandByPoint({ x: -toolDiameter/2, y: -toolDiameter/2, z: 0 })
|
||||
.expandByPoint({ x: toolDiameter/2, y: toolDiameter/2, z: 0 });
|
||||
let toolPos = tool.profile.slice();
|
||||
for (let i=0; i<toolPos.length; i+= 3) {
|
||||
// toolPos[i+2] = -toolPos[i+2];
|
||||
toolBounds.expandByPoint({ x: toolPos[i], y: toolPos[i+1], z: toolPos[i+2] });
|
||||
}
|
||||
let toolData = { positions: toolPos, bounds: toolBounds };
|
||||
|
||||
const vertices = widget.getGeoVertices({ unroll: true, translate: true });
|
||||
const wbounds = widget.getBoundingBox();
|
||||
if (!inside) {
|
||||
wbounds.expandByVector({ x: toolDiameter/2, y: toolDiameter/2, z: 0 });
|
||||
}
|
||||
|
||||
if (contourY) {
|
||||
// console.time('swap XY vertices');
|
||||
// swap XY vertices
|
||||
for (let i=0; i<vertices.length; i+= 3) {
|
||||
let tmp = vertices[i+1];
|
||||
vertices[i+1] = vertices[i+0];
|
||||
vertices[i+0] = tmp;
|
||||
}
|
||||
['min','max'].forEach(ext => {
|
||||
ext = wbounds[ext];
|
||||
let tmp = ext.x;
|
||||
ext.x = ext.y;
|
||||
ext.y = tmp;
|
||||
});
|
||||
// console.timeEnd('swap XY vertices');
|
||||
}
|
||||
|
||||
let trace = contour.trace;
|
||||
let gpu = await self.get_raster_gpu({
|
||||
mode: trace ? "tracing" : "planar",
|
||||
resolution
|
||||
});
|
||||
let xStep = density;
|
||||
let yStep = Math.ceil(toolStep / resolution);
|
||||
let epsilon = 10e-4;
|
||||
await gpu.loadTool({
|
||||
sparseData: toolData
|
||||
});
|
||||
let terrain = await gpu.loadTerrain({
|
||||
triangles: vertices,
|
||||
boundsOverride: wbounds
|
||||
});
|
||||
let { gridWidth, positions } = terrain;
|
||||
let output = await gpu.generateToolpaths({
|
||||
xStep,
|
||||
yStep,
|
||||
zFloor: zBottom - 1,
|
||||
onProgress(pct) { console.log({ pct }); onupdate(pct/100, 100) }
|
||||
});
|
||||
gpu.terminate();
|
||||
|
||||
let { numScanlines, pointsPerLine, pathData } = output;
|
||||
let xmult = xStep * resolution;
|
||||
let ymult = yStep * resolution;
|
||||
let xoff = wbounds.min.x;
|
||||
let yoff = wbounds.min.y;
|
||||
let slices = [];
|
||||
function calcPoint(x, y, z, tx, ty) {
|
||||
let p = newPoint(x, y, z);
|
||||
p.tx = tx;
|
||||
p.ty = ty;
|
||||
return p;
|
||||
}
|
||||
for (let i=0; i<numScanlines; i++) {
|
||||
let lineStart = i * pointsPerLine;
|
||||
let lineData = pathData.slice(lineStart, lineStart + pointsPerLine);
|
||||
let points = contourY
|
||||
? Array.from(lineData).map((v,j) => calcPoint(i * ymult + yoff, j * xmult + xoff, v, i * xStep, j * yStep))
|
||||
: Array.from(lineData).map((v,j) => calcPoint(j * xmult + xoff, i * ymult + yoff, v, j * xStep, i * yStep))
|
||||
;
|
||||
let slice = newSlice(i);
|
||||
let poly = newPolygon().setOpen();
|
||||
let lines = [ ];
|
||||
let skip = 0;
|
||||
let lp;
|
||||
for (let p of points) {
|
||||
if (inside && positions[p.ty * gridWidth + p.tx] < zBottom - epsilon) {
|
||||
if (poly.length > 1) {
|
||||
lines.push(poly);
|
||||
poly = newPolygon().setOpen();
|
||||
} else if (poly.length === 1) {
|
||||
poly = newPolygon().setOpen();
|
||||
}
|
||||
lp = undefined;
|
||||
skip = 0;
|
||||
continue;
|
||||
}
|
||||
if (p.z < zBottom - epsilon) {
|
||||
lp = p;
|
||||
if (poly.length === 0) {
|
||||
skip++;
|
||||
continue;
|
||||
}
|
||||
if (poly.length > 1) {
|
||||
skip = 0;
|
||||
p.z = zBottom;
|
||||
poly.push(p);
|
||||
lines.push(poly);
|
||||
poly = newPolygon().setOpen();
|
||||
continue;
|
||||
}
|
||||
} else if (lp && skip && poly.length === 0) {
|
||||
lp.z = zBottom;
|
||||
poly.push(lp);
|
||||
}
|
||||
poly.push(lp = p);
|
||||
skip = 0;
|
||||
}
|
||||
// require two points or more
|
||||
if (poly.length > 1) {
|
||||
lines.push(poly);
|
||||
}
|
||||
// raize output points when inside tab boundaries
|
||||
if (!inside && tabsOn && clipTab.length)
|
||||
for (let poly of lines) {
|
||||
for (let p of poly.points) {
|
||||
for (let clip of clipTab) {
|
||||
if (p.z < clip.z && p.isInPolygon(clip)) {
|
||||
p.z = clip.z;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// drop interior points along a continuous slope
|
||||
for (let poly of lines) {
|
||||
let { points } = poly;
|
||||
let merged = [ points[0] ];
|
||||
let lp = points[1];
|
||||
let dz = points[0].z - lp.z;
|
||||
for (let i=2; i<points.length; i++) {
|
||||
let np = points[i];
|
||||
let ndz = np.z - lp.z;
|
||||
if (Math.abs(dz - ndz) > epsilon) {
|
||||
// slope changed
|
||||
merged.push(lp);
|
||||
dz = ndz;
|
||||
} else if (curvesOnly && Math.abs(ndz) < epsilon) {
|
||||
// add empty points as path separators
|
||||
merged.push(undefined);
|
||||
}
|
||||
lp = np;
|
||||
}
|
||||
if (merged.peek() !== lp) {
|
||||
merged.push(lp);
|
||||
}
|
||||
poly.points = merged;
|
||||
}
|
||||
// drop flat regions when enabled
|
||||
if (curvesOnly) {
|
||||
let nulines = [];
|
||||
for (let poly of lines) {
|
||||
let { points } = poly;
|
||||
let newp = [];
|
||||
for (let p of points) {
|
||||
if (p) {
|
||||
newp.push(p);
|
||||
} else if (newp.length) {
|
||||
nulines.push(newPolygon(newp).setOpen());
|
||||
newp = [];
|
||||
}
|
||||
}
|
||||
nulines.push(newPolygon(newp).setOpen());
|
||||
}
|
||||
lines = nulines.filter(p => p.perimeter() > toolStep);
|
||||
}
|
||||
slice.camLines = lines;
|
||||
slices.push(slice);
|
||||
if (inside) {
|
||||
onupdate(i, numScanlines);
|
||||
}
|
||||
}
|
||||
ondone(slices);
|
||||
return this;
|
||||
}
|
||||
|
||||
const probe = this.probe = new Probe({
|
||||
profile: toolOffset,
|
||||
data,
|
||||
|
|
@ -177,8 +356,7 @@ export class Topo {
|
|||
clipTabZ: clipTab ? clipTab.map(t => t.z) : undefined,
|
||||
tabHeight,
|
||||
newslices,
|
||||
leave,
|
||||
color
|
||||
leave
|
||||
}, (i, l, p) => {
|
||||
onupdate(l / 2 + i / 2, l, p);
|
||||
});
|
||||
|
|
@ -304,7 +482,7 @@ export class Topo {
|
|||
|
||||
const { minX, maxX, minY, maxY, boundsX, boundsY, stepsX, stepsY } = params;
|
||||
const { gridDelta, resolution, density, partOff, toolStep, contourX, contourY } = params;
|
||||
const { clipTo, clipTab, clipTabZ, tabHeight, newslices, color, leave } = params;
|
||||
const { clipTo, clipTab, clipTabZ, tabHeight, newslices, leave } = params;
|
||||
|
||||
let stepsTaken = 0,
|
||||
stepsTotal = 0;
|
||||
|
|
@ -370,11 +548,8 @@ export class Topo {
|
|||
gridy
|
||||
}, segments => {
|
||||
if (segments.length > 0) {
|
||||
let slice = newSlice(gridx);
|
||||
let slice = newSlice(x);
|
||||
slice.camLines = segments;
|
||||
slice.output()
|
||||
.setLayer("contour y", { face: color, line: color })
|
||||
.addPolys(segments);
|
||||
slicesY.push(slice);
|
||||
}
|
||||
onupdate(++stepsTaken, stepsTotal, "contour y");
|
||||
|
|
@ -399,11 +574,8 @@ export class Topo {
|
|||
gridy
|
||||
}, segments => {
|
||||
if (segments.length > 0) {
|
||||
let slice = newSlice(gridy);
|
||||
let slice = newSlice(y);
|
||||
slice.camLines = segments;
|
||||
slice.output()
|
||||
.setLayer("contour x", { face: color, line: color })
|
||||
.addPolys(segments);
|
||||
slicesX.push(slice);
|
||||
}
|
||||
onupdate(++stepsTaken, stepsTotal, "contour x");
|
||||
|
|
@ -534,9 +706,9 @@ export class Trace {
|
|||
console.log(...arguments);
|
||||
}
|
||||
|
||||
const push_point = this.push_point = function (x, y, z) {
|
||||
this.push_point = function (x, y, z) {
|
||||
const newP = newPoint(x, y, z);
|
||||
const lastP = lastPP;//trace.last();
|
||||
const lastP = lastPP;
|
||||
|
||||
if (lastP) {
|
||||
const dl = (x - lastP.x) || (y - lastP.y);
|
||||
|
|
@ -553,7 +725,6 @@ export class Trace {
|
|||
}
|
||||
if (curvesOnly) {
|
||||
const dv = contourX ? Math.abs(lastP.x - x) : Math.abs(lastP.y - y);
|
||||
// const dz = lastPP.z - z;
|
||||
const angle = Math.atan2(Math.abs(dz), dv) * RAD2DEG;
|
||||
if (angle > maxangle) {
|
||||
end_poly();
|
||||
|
|
|
|||
|
|
@ -22,8 +22,9 @@ export class Topo {
|
|||
|
||||
async generate(opt = {}) {
|
||||
let { state, op, onupdate, ondone } = opt;
|
||||
let { widget, settings, tabs } = opt.state;
|
||||
let { widget, settings, tabs, color } = state;
|
||||
let { controller, process } = settings;
|
||||
let { webGPU } = controller;
|
||||
|
||||
let axis = op.axis.toLowerCase(),
|
||||
tool = new Tool(settings, op.tool),
|
||||
|
|
@ -38,9 +39,8 @@ export class Topo {
|
|||
x: axis === "x",
|
||||
y: axis === "y"
|
||||
},
|
||||
zMin = min.z + 0.0001,
|
||||
tolerance = op.tolerance,
|
||||
resolution = tolerance ? tolerance : 1 / Math.sqrt(density / (span.x * span.y)),
|
||||
resolution = (tolerance ? tolerance : 1 / Math.sqrt(density / (span.x * span.y))).round(5),
|
||||
step = this.step = (tool.traceOffset() * 2) * op.step,
|
||||
angle = this.angle = op.angle || 1;
|
||||
|
||||
|
|
@ -52,31 +52,36 @@ export class Topo {
|
|||
console.log(widget.id, 'topo4 auto tolerance', resolution.round(4));
|
||||
}
|
||||
|
||||
this.zBottom = state.zBottom;
|
||||
this.zBottom = state.zBottom ?? 0;
|
||||
this.resolution = resolution;
|
||||
this.vertices = widget.getGeoVertices({ unroll: true, translate: true });
|
||||
this.tabverts = widget.getTabVertices();
|
||||
this.tool = tool.generateProfile(resolution).profile;
|
||||
this.maxo = tool.profileDim.maxo * resolution;
|
||||
this.diam = tool.fluteDiameter();
|
||||
this.unit = tool.unitScale();
|
||||
this.units = controller.units === 'in' ? 25.4 : 1
|
||||
this.zoff = widget.track.top || 0;
|
||||
this.leave = op.leave || 0;
|
||||
this.linear = op.linear || false;
|
||||
this.lineColor = state.settings.controller.dark ? 0xffff00 : 0x555500;
|
||||
this.lineColor = color;//controller.dark ? 0xffff00 : 0x555500;
|
||||
this.offStart = op.offStart ?? 0;
|
||||
this.offEnd = op.offEnd ?? 0;
|
||||
this.bounds = bounds;
|
||||
this.gpu = webGPU ?? false;
|
||||
|
||||
onupdate(0, "lathe");
|
||||
|
||||
const parts = webGPU ? [ 0.8, 0.2 ] : [ 0.25, 0.75 ];
|
||||
const range = this.range = { min: Infinity, max: -Infinity };
|
||||
const slices = this.sliced = await this.slice(scale(onupdate, 0.25, 0));
|
||||
const slices = this.sliced = await this.slice(scale(onupdate, parts[0], 0));
|
||||
|
||||
for (let slice of slices) {
|
||||
range.min = Math.min(range.min, slice.z);
|
||||
range.max = Math.max(range.max, slice.z);
|
||||
slice.output()
|
||||
.setLayer("lathe", { line: 0x888888 })
|
||||
.addPolys(slice.topPolys());
|
||||
}
|
||||
|
||||
const lathe = await this.lathe(scale(onupdate, 0.75, 0.25));
|
||||
const lathe = await this.lathe(scale(onupdate, parts[1], parts[0]));
|
||||
|
||||
onupdate(1, "lathe");
|
||||
ondone(lathe);
|
||||
|
|
@ -86,10 +91,9 @@ export class Topo {
|
|||
}
|
||||
|
||||
async slice(onupdate) {
|
||||
const { vertices, resolution, tabverts, zoff } = this;
|
||||
|
||||
const { vertices, resolution, tabverts, zoff, offStart, offEnd, units } = this;
|
||||
const { minions } = self.kiri_worker;
|
||||
const range = this.range = { min: Infinity, max: -Infinity };
|
||||
const box = this.box = new THREE.Box2();
|
||||
|
||||
// swap XZ in shared array
|
||||
for (let i = 0, l = vertices.length; i < l; i += 3) {
|
||||
|
|
@ -101,9 +105,10 @@ export class Topo {
|
|||
range.max = Math.max(range.max, x);
|
||||
}
|
||||
|
||||
// add tool radius to slice min/max range ot fully carve part
|
||||
range.min -= this.diam / 2;
|
||||
range.max += this.diam / 2;
|
||||
// add tool diameter to slice min/max range to fully carve part
|
||||
range.min += offStart * units;
|
||||
range.max -= offEnd * units;
|
||||
range.max += resolution * 2;
|
||||
|
||||
// merge in tab vertices here so they don't affect slice range / dimensions
|
||||
for (let i = 0, l = tabverts.length; i < l; i += 3) {
|
||||
|
|
@ -116,24 +121,36 @@ export class Topo {
|
|||
// re-create shared vertex array for workers
|
||||
this.vertices = [].appendAll(vertices).appendAll(tabverts).toFloat32().toShared();
|
||||
|
||||
const shards = Math.ceil(Math.min(25, vertices.length / 27000));
|
||||
const step = (range.max - range.min) / shards;
|
||||
|
||||
let index = 0;
|
||||
let slices = this.slices = [];
|
||||
let slice = { min: range.min, max: range.min + step, index };
|
||||
|
||||
for (let z = range.min; z < range.max; z += resolution) {
|
||||
if (z > slice.max) {
|
||||
slices.push(slice);
|
||||
slice = { min: z, max: z + step, index };
|
||||
}
|
||||
index++;
|
||||
// rp.rasterizeMesh(vertices, resolution).then(rpo => console.log({ rpo }));
|
||||
if (this.gpu) {
|
||||
return await this.sliceGPU(onupdate);
|
||||
}
|
||||
slices.push(slice);
|
||||
// console.log({ shards, range, step, slices });
|
||||
|
||||
const { minions } = self.kiri_worker;
|
||||
const zSpan = range.max - range.min;
|
||||
const shards = Math.ceil(Math.min(25, vertices.length / 27000));
|
||||
const totalSteps = Math.ceil(zSpan / resolution);
|
||||
const stardSteps = Math.ceil(totalSteps / shards);
|
||||
const stepWidth = stardSteps * resolution;
|
||||
|
||||
let slices = this.slices = [];
|
||||
for (let i=0; i<shards; i++) {
|
||||
let s;
|
||||
slices.push(s = {
|
||||
min: range.min + i * stepWidth,
|
||||
max: Math.min(range.max, range.min + (i+1) * stepWidth),
|
||||
index: i * stardSteps
|
||||
});
|
||||
}
|
||||
|
||||
// console.log({
|
||||
// shards,
|
||||
// step: stepWidth,
|
||||
// range,
|
||||
// resolution,
|
||||
// slices: slices.slice(),
|
||||
// minions
|
||||
// });
|
||||
|
||||
if (minions?.running > 1) {
|
||||
return await this.sliceMinions(onupdate);
|
||||
} else {
|
||||
|
|
@ -141,6 +158,96 @@ export class Topo {
|
|||
}
|
||||
}
|
||||
|
||||
async sliceGPU(onupdate) {
|
||||
const { angle, diam, linear, offStart, offEnd, resolution, tool, units, vertices, zBottom } = this;
|
||||
|
||||
// invert tool Z offset for gpu code
|
||||
let toolBounds = new THREE.Box3()
|
||||
.expandByPoint({ x: -this.diam/2, y: -diam/2, z: 0 })
|
||||
.expandByPoint({ x: this.diam/2, y: diam/2, z: 0 });
|
||||
let toolPos = tool.slice();
|
||||
let minz = Infinity;
|
||||
for (let i=0; i<toolPos.length; i += 3) {
|
||||
// toolPos[i+2] = -toolPos[i+2];
|
||||
toolBounds.expandByPoint({ x: 0, y: 0, z: toolPos[i+2] });
|
||||
minz = Math.min(minz,toolPos[i+2]);
|
||||
}
|
||||
let toolData = { positions: toolPos, bounds: toolBounds };
|
||||
|
||||
// console.time('swap XZ vertices');
|
||||
// swap XZ vertices for gpu code
|
||||
for (let i=0; i<vertices.length; i+= 3) {
|
||||
let tmp = vertices[i+2];
|
||||
vertices[i+2] = vertices[i+0];
|
||||
vertices[i+1] = -vertices[i+1];
|
||||
vertices[i+0] = tmp;
|
||||
}
|
||||
// console.timeEnd('swap XZ vertices');
|
||||
|
||||
let gpu = await self.get_raster_gpu({
|
||||
mode: "radial",
|
||||
resolution,
|
||||
rotationStep: angle,
|
||||
radialRotationOffset: 90
|
||||
});
|
||||
let xStep = Math.max(1, Math.round(this.step / resolution));
|
||||
let boundsOverride = this.bounds.clone();
|
||||
boundsOverride.min.x += offStart * units;
|
||||
boundsOverride.max.x -= offEnd * units;
|
||||
await gpu.loadTool({
|
||||
sparseData: toolData
|
||||
});
|
||||
await gpu.loadTerrain({
|
||||
triangles: vertices,
|
||||
boundsOverride,
|
||||
onProgress(pct) { onupdate(pct/100) }
|
||||
});
|
||||
let output = await gpu.generateToolpaths({
|
||||
xStep,
|
||||
yStep: 1,
|
||||
zFloor: zBottom,
|
||||
onProgress(i,j) { onupdate(i/j) }
|
||||
});
|
||||
gpu.terminate();
|
||||
|
||||
let { numStrips, strips } = output;
|
||||
let degPerRow = 360 / numStrips;
|
||||
let slices = this.gpu_slices = [];
|
||||
let xmult = resolution * xStep;
|
||||
let xoff = boundsOverride.min.x;
|
||||
let rows = [];
|
||||
for (let i=0; i<numStrips; i++) {
|
||||
let points = Array.from(strips[i].pathData).map((v,j) => newPoint(j * xmult + xoff, 0, v).setA(-i * degPerRow));
|
||||
rows.push(points);
|
||||
}
|
||||
if (linear) {
|
||||
for (let i=0; i<rows.length; i++) {
|
||||
let slice = newSlice(i);
|
||||
slice.index = i;
|
||||
slice.camLines = [ newPolygon(rows[i]).setOpen() ];
|
||||
if (i % 2 === 1) slice.camLines[0].reverse();
|
||||
slices.push(slice);
|
||||
}
|
||||
} else {
|
||||
let { pointsPerLine } = strips[0];
|
||||
for (let i=0; i<pointsPerLine; i++) {
|
||||
let slice = newSlice(i);
|
||||
let points = rows.map(row => row[i]);
|
||||
if (i % 2 === 1) points.reverse();
|
||||
slice.index = i;
|
||||
slice.camLines = [ newPolygon(points).setOpen() ];
|
||||
slices.push(slice);
|
||||
}
|
||||
}
|
||||
for (let slice of slices) {
|
||||
slice.output()
|
||||
.setLayer("lathe", { line: this.lineColor })
|
||||
.addPoly(slice.camLines[0].clone().applyRotations());
|
||||
}
|
||||
// console.log({ webGPU: output, slices });
|
||||
return slices;
|
||||
}
|
||||
|
||||
async sliceWorker(onupdate) {
|
||||
const { vertices, slices, resolution } = this;
|
||||
|
||||
|
|
@ -205,22 +312,28 @@ export class Topo {
|
|||
|
||||
async lathe(onupdate) {
|
||||
const { minions } = self.kiri_worker;
|
||||
if (minions?.running > 1) {
|
||||
if (this.gpu) {
|
||||
return await this.latheGPU(onupdate);
|
||||
} else if (minions?.running > 1) {
|
||||
return await this.latheMinions(onupdate);
|
||||
} else {
|
||||
return await this.latheWorker(onupdate);
|
||||
}
|
||||
}
|
||||
|
||||
async latheGPU(onupdate) {
|
||||
return this.gpu_slices;
|
||||
}
|
||||
|
||||
lathePath(slices, tool) {
|
||||
const { resolution, step, zBottom, maxo } = this;
|
||||
|
||||
const tlen = tool.length;
|
||||
const slen = slices.length;
|
||||
const sinc = Math.max(1, Math.ceil(step / resolution));
|
||||
const heights = [];
|
||||
|
||||
// cull slice lines to only the ones in range (5x faster)
|
||||
// console.log({ tlen, slen, sinc, slices: slices.map(s => s.z) });
|
||||
// cull slice lines to only the ones in range (~5x faster)
|
||||
const oslices = [];
|
||||
for (let slice of slices) {
|
||||
const lines = slice.lines;
|
||||
|
|
@ -244,7 +357,9 @@ export class Topo {
|
|||
// iterate over all slices (real x = z)
|
||||
// find max real z using z ray intersect from tool point to slice lines + offset
|
||||
let lz = 0;
|
||||
for (let si = 0; si < slen; si += sinc) {
|
||||
for (let sz = 0; ; sz += step) {
|
||||
let si = Math.ceil(sz / resolution);
|
||||
if (si >= oslices.length) break;
|
||||
const rx = oslices[si].z;
|
||||
let mz = -Infinity;
|
||||
// iterate over tool offsets
|
||||
|
|
@ -281,12 +396,12 @@ export class Topo {
|
|||
}
|
||||
if (mz === -Infinity && xo === 0 && yo === 0) {
|
||||
// tool tip is off the model
|
||||
continue;
|
||||
// continue;
|
||||
}
|
||||
}
|
||||
if (mz === -Infinity) {
|
||||
mz = zBottom; // Math.min(0, lz)
|
||||
} else if (zBottom && mz < zBottom) {
|
||||
mz = zBottom;
|
||||
} else if (mz < zBottom) {
|
||||
mz = zBottom;
|
||||
}
|
||||
heights.push(rx, 0, lz = mz);
|
||||
|
|
|
|||
|
|
@ -34,14 +34,11 @@ export function init() {
|
|||
"sliceFillType": LANG.fi_type,
|
||||
"sliceFillWidth": LANG.fi_wdth_s,
|
||||
"sliceFillSparse": LANG.fi_pcnt_s,
|
||||
"sliceFillRate": LANG.ou_feed_s,
|
||||
"sliceFillGrow": LANG.fi_grow_s,
|
||||
"sliceSolidRate": LANG.ou_fini_s,
|
||||
"sliceSolidMinArea": LANG.ad_msol_s,
|
||||
"sliceTopLayers": LANG.sl_ltop_s,
|
||||
"sliceBottomLayers": LANG.sl_lbot_s,
|
||||
"firstLayerRate": LANG.fl_rate_s,
|
||||
"firstLayerPrintMult": LANG.fl_mult_s,
|
||||
"firstLayerFanSpeed": LANG.ou_fans_s,
|
||||
// prepare
|
||||
"sliceShellOrder": LANG.sl_ordr_s,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { fdm_export } from './export.js';
|
|||
// fil = filament diameter
|
||||
// slice = slice height
|
||||
function extrudePerMM(noz, fil, slice) {
|
||||
// (ratio of nozzle to filament) * (ratio of slice to nozzle)
|
||||
return ((Math.PI * util.sqr(noz / 2)) / (Math.PI * util.sqr(fil / 2))) * (slice / noz);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ export function fdm_export(print, online, ondone, ondebug) {
|
|||
{ extrudeAbs } = device,
|
||||
{ exportThumb } = controller,
|
||||
{ extrudeMM, extrudePerMM } = FDM,
|
||||
extused = Object.keys(print.extruders).map(v => parseInt(v)),
|
||||
timeFactor = (device.gcodeTime || 1) * 1.5,
|
||||
decimals = config.gcode_decimals || 4,
|
||||
zMoveMax = device.deviceZMax || 0,
|
||||
|
|
@ -49,7 +48,6 @@ export function fdm_export(print, online, ondone, ondebug) {
|
|||
last = null,
|
||||
zpos = 0,
|
||||
bmax = 0,
|
||||
blastz = 0,
|
||||
belt_add_y = (process.firstLayerYOffset || 0) - (belty || 0),
|
||||
oloops = process.outputLoops || 0,
|
||||
zhop = process.zHopDistance || 0, // range
|
||||
|
|
@ -58,10 +56,8 @@ export function fdm_export(print, online, ondone, ondebug) {
|
|||
retDist = process.outputRetractDist || 0, // range
|
||||
retSpeed = process.outputRetractSpeed * 60 || 1, // range
|
||||
retDwell = process.outputRetractDwell || 0, // range
|
||||
timeDwell = retDwell / 1000,
|
||||
scarfZ = process.outputScarfLength ? true : false,
|
||||
arcDist = isBelt || scarfZ ? 0 : (process.arcTolerance || 0),
|
||||
arcMin = 1,
|
||||
arcRes = 20,
|
||||
arcDev = 0.5,
|
||||
arcMax = 40,
|
||||
|
|
@ -85,7 +81,6 @@ export function fdm_export(print, online, ondone, ondebug) {
|
|||
fanSpeedSave = undefined,
|
||||
fanSpeedBase = 0,
|
||||
fanSpeed = 0,
|
||||
lastType = undefined,
|
||||
lastNozzleTemp = nozzleTemp,
|
||||
lastBedTemp = bedTemp,
|
||||
lastFanSpeed = fanSpeed,
|
||||
|
|
@ -169,7 +164,6 @@ export function fdm_export(print, online, ondone, ondebug) {
|
|||
retDist = params.outputRetractDist || 0; // range
|
||||
retSpeed = params.outputRetractSpeed * 60 || 1; // range
|
||||
retDwell = params.outputRetractDwell || 0; // range
|
||||
timeDwell = retDwell / 1000;
|
||||
nozzleTemp = layer === 0 ?
|
||||
params.firstLayerNozzleTemp || params.outputTemp :
|
||||
params.outputTemp || params.firstLayerNozzleTemp;
|
||||
|
|
@ -297,8 +291,9 @@ export function fdm_export(print, online, ondone, ondebug) {
|
|||
append(`; Bed type: ${bedType}`);
|
||||
append(`; Target: ${filter[mode]}`);
|
||||
// inject thumbnail preview
|
||||
if (exportThumb && worker.snap) {
|
||||
let { width, height, url } = worker.snap;
|
||||
let { current } = self.kiri_worker ?? {};
|
||||
if (exportThumb && current?.snap) {
|
||||
let { width, height, url } = current.snap;
|
||||
let data = url.substring(url.indexOf(',') + 1);
|
||||
append(`; thumbnail begin ${width} ${height} ${data.length}`);
|
||||
for (let i=0; i<data.length; i += 78) {
|
||||
|
|
@ -470,7 +465,7 @@ export function fdm_export(print, online, ondone, ondebug) {
|
|||
if (isBelt) {
|
||||
let zheight = path ? path.height || 0 : 0;
|
||||
epos.x = originCenter ? -pos.x : device.bedWidth - pos.x;
|
||||
epos.z = blastz = pos.z * icos;
|
||||
epos.z = pos.z * icos;
|
||||
epos.y = -pos.y + epos.z * bcos + belt_add_y;
|
||||
lout = epos;
|
||||
}
|
||||
|
|
@ -613,6 +608,9 @@ export function fdm_export(print, online, ondone, ondebug) {
|
|||
for (pidx=0; pidx<path.length; pidx++) {
|
||||
out = path[pidx];
|
||||
speedMMM = (out.speed || process.outputFeedrate) * 60; // range
|
||||
if (!isBelt) {
|
||||
fanSpeed = out.fan ?? fanSpeed;
|
||||
}
|
||||
|
||||
// hint to controller that we're working on a specific object
|
||||
// so that gcode between start/stop comments can be cancelled
|
||||
|
|
@ -634,7 +632,7 @@ export function fdm_export(print, online, ondone, ondebug) {
|
|||
|
||||
// emit comment on output type chage
|
||||
if (last && out.type !== last.type) {
|
||||
lastType = subst.feature = out.type;
|
||||
subst.feature = out.type;
|
||||
if (gcodeFeature && gcodeFeature.length) {
|
||||
appendAllSub(gcodeFeature);
|
||||
} else {
|
||||
|
|
@ -687,10 +685,10 @@ export function fdm_export(print, online, ondone, ondebug) {
|
|||
// in belt mode, fan can change per segment (for base)
|
||||
if (isBelt) {
|
||||
setTempFanSpeed(out.fan);
|
||||
if (fanSpeed !== lastFanSpeed) {
|
||||
appendAllSub(gcodeFan);
|
||||
lastFanSpeed = fanSpeed;
|
||||
}
|
||||
}
|
||||
if (fanSpeed !== lastFanSpeed) {
|
||||
appendAllSub(gcodeFan);
|
||||
lastFanSpeed = fanSpeed;
|
||||
}
|
||||
|
||||
if (lastp && out.emit) {
|
||||
|
|
@ -751,7 +749,6 @@ export function fdm_export(print, online, ondone, ondebug) {
|
|||
}
|
||||
|
||||
// if new point is off the arc
|
||||
// if (deem || depm || desp || dc > arcDist || cc.r < arcMin || cc.r > arcMax || dist > cc.r) {
|
||||
if (deem || depm || desp || dc * arcQ.center.length / arcQ.rSum > arcDist || dist > cc.r || cc.r > arcMax || radFault || !arcValid()) {
|
||||
// let debug = [deem, depm, desp, dc * arcQ.center.length / arcQ.rSum > arcDist, dist > cc.r, cc.r > arcMax, radFault];
|
||||
if (arcQ.length === 4) {
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ export function menu() {
|
|||
_____: newGroup(LANG.sw_menu, $('fdm-walls'), { modes:FDM, driven, hideable, separator, group:"fdm-walls" }),
|
||||
sliceShells: newInput(LANG.sl_shel_s, { title:LANG.sl_shel_l, convert:toFloat }),
|
||||
sliceLineWidth: newInput(LANG.sl_line_s, { title:LANG.sl_line_l, convert:toFloat, bound:bound(0,5) }),
|
||||
sliceFillOverlap: newInput(LANG.fi_over_s, {title:LANG.fi_over_l, convert:toFloat, bound:bound(0.0,1.0)}),
|
||||
separator: newBlank({ class:"set-sep", driven }),
|
||||
sliceShellOrder: newSelect(LANG.sl_ordr_s, { title:LANG.sl_ordr_l}, "shell"),
|
||||
sliceDetectThin: newSelect(LANG.ad_thin_s, { title: LANG.ad_thin_l, action: thinWallSave }, "thin"),
|
||||
|
|
@ -86,19 +87,17 @@ export function menu() {
|
|||
sliceZInterleave: newBoolean(LANG.ad_zint_s, onBooleanClick, {title:LANG.ad_zint_l, show:zIntShow}),
|
||||
_____: newGroup(LANG.fs_menu, $('fdm-solid'), { modes:FDM, driven, hideable, separator, group:"fdm-solid" }),
|
||||
sliceFillAngle: newInput(LANG.fi_angl_s, {title:LANG.fi_angl_l, convert:toFloat}),
|
||||
sliceFillOverlap: newInput(LANG.fi_over_s, {title:LANG.fi_over_l, convert:toFloat, bound:bound(0.0,2.0)}),
|
||||
sliceFillGrow: newInput(LANG.fi_grow_s, {title:LANG.fi_grow_l, convert:toFloat}),
|
||||
sliceSolidMinArea: newInput(LANG.ad_msol_s, {title:LANG.ad_msol_l, convert:toFloat}),
|
||||
_____: newGroup(LANG.fi_menu, $('fdm-fill'), { modes:FDM, driven, hideable, separator, group:"fdm-fill" }),
|
||||
sliceFillType: newSelect(LANG.fi_type, {trigger}, "infill"),
|
||||
sliceFillSparse: newInput(LANG.fi_pcnt_s, {title:LANG.fi_pcnt_l, convert:toFloat, bound:bound(0.0,1.0), show:hasInfill}),
|
||||
sliceFillRepeat: newInput(LANG.fi_rept_s, {title:LANG.fi_rept_l, convert:toInt, bound:bound(1,10), show:fillIsLinear}),
|
||||
separator: newBlank({ class:"set-sep", driven }),
|
||||
sliceFillRate: newInput(LANG.ou_feed_s, {title:LANG.ou_feed_l, convert:toInt, bound:bound(0,500)}),
|
||||
sliceSolidRate: newInput(LANG.ou_fini_s, {title:LANG.ou_fini_l, convert:toInt, bound:bound(0,500)}),
|
||||
_____: newGroup(LANG.fh_menu, $('fdm-heat'), { modes:FDM, driven, hideable, separator, group:"fdm-heat" }),
|
||||
outputTemp: newInput(LANG.ou_nozl_s, {title:LANG.ou_nozl_l, convert:toInt}),
|
||||
outputBedTemp: newInput(LANG.ou_bedd_s, {title:LANG.ou_bedd_l, convert:toInt}),
|
||||
separator: newBlank({ class:"set-sep", driven }),
|
||||
outputDraftShield: newBoolean(LANG.fr_draf_s, onBooleanClick, {title:LANG.fr_draf_l, trigger, show:() => isNotBelt()}),
|
||||
_____: newGroup(LANG.fc_menu, $('fdm-cool'), { modes:FDM, driven, hideable, separator, group:"fdm-cool" }),
|
||||
outputFanLayer: newInput(LANG.ou_fanl_s, { title:LANG.ou_fanl_l, convert:toInt, bound:bound(0,255) }),
|
||||
outputFanSpeed: newInput(LANG.ou_fans_s, {title:LANG.ou_fans_l, convert:toInt, bound:bound(0,255)}),
|
||||
|
|
@ -135,11 +134,9 @@ export function menu() {
|
|||
firstLayerFanSpeed: newInput(LANG.ou_fans_s, {title:LANG.ou_fans_l, convert:toInt, bound:bound(0,255), show:isBelt}),
|
||||
firstLayerYOffset: newInput(LANG.fl_zoff_s, {title:LANG.fl_zoff_l, convert:toFloat, show:isBelt}),
|
||||
firstLayerFlatten: newInput(LANG.fl_flat_s, {title:LANG.fl_flat_l, convert:toFloat, show:isBelt}),
|
||||
firstLayerBeltFact: newInput(LANG.fl_mult_s, {title:LANG.fl_mult_l, convert:toFloat, bound:bound(0, 2), show:isBelt}),
|
||||
firstLayerRate: newInput(LANG.fl_rate_s, {title:LANG.fl_rate_l, convert:toFloat}),
|
||||
firstLayerFillRate: newInput(LANG.fl_frat_s, {title:LANG.fl_frat_l, convert:toFloat, show:isNotBelt}),
|
||||
separator: newBlank({ class:"set-sep", driven, show:isNotBelt }),
|
||||
firstLayerLineMult: newInput(LANG.fl_sfac_s, {title:LANG.fl_sfac_l, convert:toFloat, bound:bound(0.5,2), show:isNotBelt}),
|
||||
firstLayerPrintMult: newInput(LANG.fl_mult_s, {title:LANG.fl_mult_l, convert:toFloat}),
|
||||
separator: newBlank({ class:"set-sep", driven, show:isBelt }),
|
||||
firstLayerBrim: newInput(LANG.fl_brim_s, {title:LANG.fl_brim_l, convert:toInt, show:isBelt}),
|
||||
firstLayerBrimIn: newInput(LANG.fl_brin_s, {title:LANG.fl_brin_l, convert:toInt, show:isBelt}),
|
||||
|
|
@ -154,7 +151,6 @@ export function menu() {
|
|||
outputRaftSpacing: newInput(LANG.fr_spac_s, {title:LANG.fr_spac_l, convert:toFloat, bound:bound(0.0,3.0), show: () => ui.outputRaft.checked && isNotBelt() }),
|
||||
separator: newBlank({ class:"set-sep", driven, show:isNotBelt }),
|
||||
outputRaft: newBoolean(LANG.fr_nabl_s, onBooleanClick, {title:LANG.fr_nabl_l, trigger, show:() => isNotBelt()}),
|
||||
outputDraftShield: newBoolean(LANG.fr_draf_s, onBooleanClick, {title:LANG.fr_draf_l, trigger, show:() => isNotBelt()}),
|
||||
_____: newGroup(LANG.ou_menu, $('fdm-output'), { modes:FDM, driven, hideable, separator, group:"fdm-out" }),
|
||||
outputFeedrate: newInput(LANG.ou_feed_s, {title:LANG.ou_feed_l, convert:toInt}),
|
||||
outputFinishrate: newInput(LANG.ou_fini_s, {title:LANG.ou_fini_l, convert:toInt}),
|
||||
|
|
@ -177,10 +173,13 @@ export function menu() {
|
|||
arcTolerance: newInput(LANG.ad_arct_s, {title:LANG.ad_arct_l, convert:toFloat, bound:bound(0,1.0), show:() => { return isNotBelt() }}),
|
||||
outputRetractDwell: newInput(LANG.ad_rdwl_s, {title:LANG.ad_rdwl_l, convert:toInt}),
|
||||
outputMinSpeed: newInput(LANG.ad_mins_s, {title:LANG.ad_mins_l, convert:toFloat, bound:bound(1,200)}),
|
||||
outputMaxFlowrate: newInput(LANG.ad_maxf_s, {title:LANG.ad_maxf_l, convert:toFloat, bound:bound(1,200)}),
|
||||
outputPurgeTower: newInput(LANG.ad_purg_s, {title:LANG.ad_purg_l, convert:toInt, bound:bound(0,1000)}),
|
||||
outputScarfLength: newInput(LANG.ad_scar_s, {title:LANG.ad_scar_l, convert:toFloat, bound:bound(0,1000)}),
|
||||
outputShortPoly: newInput(LANG.ad_spol_s, {title:LANG.ad_spol_l, convert:toFloat, bound:bound(0,10000)}),
|
||||
outputCoastDist: newInput(LANG.ad_scst_s, {title:LANG.ad_scst_l, convert:toFloat, bound:bound(0.0,10)}),
|
||||
sliceCompInner: newInput(LANG.sl_ofin_s, {title:LANG.sl_ofin_l, convert:toFloat, bound:bound(-10.0,10.0)}),
|
||||
sliceCompOuter: newInput(LANG.sl_ofot_s, {title:LANG.sl_ofot_l, convert:toFloat, bound:bound(-10.0,10.0)}),
|
||||
zHopDistance: newInput(LANG.ad_zhop_s, {title:LANG.ad_zhop_l, convert:toFloat, bound:bound(0,3.0)}),
|
||||
outputLoops: newInput(LANG.ag_loop_s, {title:LANG.ag_loop_l, convert:toInt, bound:bound(-1,1000), show:isBelt}),
|
||||
|
||||
|
|
|
|||
|
|
@ -17,12 +17,13 @@ slicer.slicePost.FDM = slicePost;
|
|||
* data object. return is ignored.
|
||||
*/
|
||||
export function slicePost(data, options) {
|
||||
const { z, lines, groups } = data;
|
||||
const { useAssembly, post_args, zIndexes } = options;
|
||||
const { process, isSynth, vaseMode } = post_args;
|
||||
const { shellOffset, fillOffset, clipOffset } = post_args;
|
||||
const { groups, z } = data;
|
||||
const { post_args, useAssembly, zIndexes } = options;
|
||||
const { isSynth, process, vaseMode } = post_args;
|
||||
const { compInner, compOuter, pump } = post_args;
|
||||
const { clipOffset, fillOffset, shellOffset } = post_args;
|
||||
|
||||
if (options.pump) {
|
||||
if (pump) {
|
||||
let nugroups = [];
|
||||
let pump = shellOffset / 10;
|
||||
for (let p of groups) {
|
||||
|
|
@ -41,6 +42,19 @@ export function slicePost(data, options) {
|
|||
data.tops = POLY.nest(groups);
|
||||
}
|
||||
|
||||
// perimeter and inner hole compensation offsets
|
||||
if (compInner || compOuter) {
|
||||
let inner = POLY.inner(data.tops).flat();
|
||||
let outer = POLY.outer(data.tops);
|
||||
if (compInner) {
|
||||
inner = POLY.flatten(POLY.offset(inner, compInner/2));
|
||||
}
|
||||
if (compOuter) {
|
||||
outer = POLY.flatten(POLY.offset(outer, -compOuter/2));
|
||||
}
|
||||
data.tops = POLY.nest([...inner, ...outer]);
|
||||
}
|
||||
|
||||
if (isSynth) {
|
||||
const process = post_args.process;
|
||||
if (process.sliceSupportGrow > 0) {
|
||||
|
|
@ -54,6 +68,7 @@ export function slicePost(data, options) {
|
|||
delete data.groups;
|
||||
return;
|
||||
}
|
||||
|
||||
const index = zIndexes.indexOf(z);
|
||||
const range = getRangeParameters(process, index);
|
||||
// calculate fractional shells
|
||||
|
|
@ -67,12 +82,9 @@ export function slicePost(data, options) {
|
|||
let trg = shellFrac > 0.5 ? 1 : parts - 1;
|
||||
sliceShells += rem >= trg ? 1 : 0;
|
||||
}
|
||||
const isFirst = index === 0;
|
||||
const height = process.sliceHeight;
|
||||
const spaceMult = isFirst ? process.firstLayerLineMult || 1 : 1;
|
||||
const count = isSynth ? 1 : sliceShells;
|
||||
const offset = shellOffset * spaceMult;
|
||||
const fillOff = fillOffset * spaceMult;
|
||||
const offset = shellOffset;
|
||||
const fillOff = fillOffset;
|
||||
const thinType = isSynth ? undefined : process.sliceDetectThin;
|
||||
const nutops = [];
|
||||
// co-locate shell processing with top generation in slicer
|
||||
|
|
@ -129,7 +141,7 @@ function offset_default(params) {
|
|||
}
|
||||
|
||||
const deltaBig = 160;
|
||||
const deltSmall = 20;
|
||||
const deltaSmall = 20;
|
||||
|
||||
// look for close points on adjacent segmented polys and merge
|
||||
let test_polys = first !== last ? [ ...first, ...last ] : last;
|
||||
|
|
@ -139,6 +151,8 @@ function offset_default(params) {
|
|||
for (let poly of test_flats)
|
||||
poly.forEachSegment((p0, p1) => {
|
||||
p0.angle = new Slope(p0, p1);
|
||||
// todo calc vertex angle
|
||||
// exclude segment vertex from merge when angle < deltaSmall
|
||||
});
|
||||
|
||||
// segment each poly to be tested
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
import { FDM } from './driver-be.js';
|
||||
import { base, util } from '../../../geo/base.js';
|
||||
import { poly2polyEmit, tip2tipEmit } from '../../../geo/paths.js';
|
||||
import { newBounds } from '../../../geo/bounds.js';
|
||||
|
|
@ -46,8 +47,6 @@ export async function fdm_prepare(widgets, settings, update) {
|
|||
output = [],
|
||||
layerout = [];
|
||||
|
||||
let lastLayerStart = null;
|
||||
|
||||
// compute bounds if missing
|
||||
if (!bounds) {
|
||||
bounds = new THREE.Box3();
|
||||
|
|
@ -553,12 +552,9 @@ export async function fdm_prepare(widgets, settings, update) {
|
|||
offset,
|
||||
layerout,
|
||||
{
|
||||
routeAround: process.outputAvoidGaps,
|
||||
seedPoint: printPoint.sub(offset),
|
||||
params, // range parameters
|
||||
first: slice.index === 0,
|
||||
support: slice.widget.support,
|
||||
onBelt: beltStart,
|
||||
params, // range parameters
|
||||
pretract: (wipeDist) => {
|
||||
if (!(lastLayer && lastLayer.length)) {
|
||||
return;
|
||||
|
|
@ -573,7 +569,11 @@ export async function fdm_prepare(widgets, settings, update) {
|
|||
print.addOutput(lastLayer, endpoint, null, null, lastOut.tool);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
routeAround: process.outputAvoidGaps,
|
||||
seedPoint: printPoint.sub(offset),
|
||||
support: slice.widget.support,
|
||||
zmax,
|
||||
}
|
||||
);
|
||||
layerout.print_time = printPoint.layer_time;
|
||||
|
|
@ -682,8 +682,8 @@ export async function fdm_prepare(widgets, settings, update) {
|
|||
|
||||
for (let rec of layer) {
|
||||
overate = rec.overate >= 0 ? rec.overate : overate;
|
||||
let bmult = params.firstLayerBeltFact ?? 1;
|
||||
let brate = params.firstLayerRate || firstLayerRate;
|
||||
let bmult = params.firstLayerPrintMult || params.firstLayerPrintMult;
|
||||
let point = rec.point;
|
||||
let belty = rec.belty = -point.y + (point.z * belt.slope);
|
||||
let lowrate = belty <= thresh ? brate : overate || brate;
|
||||
|
|
@ -695,8 +695,9 @@ export async function fdm_prepare(widgets, settings, update) {
|
|||
if (rec.emit && belty <= thresh && lastout && Math.abs(lastout.belty - belty) < 0.01) {
|
||||
// apply base speed to segments touching belt
|
||||
rec.speed = params.firstLayerRate || Math.min(rec.speed, lowrate);
|
||||
rec.emit *= bmult;
|
||||
rec.fan = params.firstLayerFanSpeed;
|
||||
// apply base flowrate multiplier to segments touching belt
|
||||
rec.emit *= bmult;
|
||||
minx = Math.min(minx, point.x, lastout.point.x);
|
||||
maxx = Math.max(maxx, point.x, lastout.point.x);
|
||||
maxy = Math.max(maxy, point.y);
|
||||
|
|
@ -795,10 +796,22 @@ export async function fdm_prepare(widgets, settings, update) {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Output and route Slice (shell,fill) paths for a single Slice
|
||||
*
|
||||
* @param {*} print Print output Object
|
||||
* @param {*} slice current slice being Output
|
||||
* @param {*} startPoint starting point for this layer
|
||||
* @param {*} offset start point offset
|
||||
* @param {*} output output prebuffer
|
||||
* @param {*} opt options
|
||||
*/
|
||||
function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
||||
const { settings } = print;
|
||||
const { device } = settings;
|
||||
const { bedWidth, bedDepth, bedRound } = device;
|
||||
const { extrudePerMM } = FDM;
|
||||
|
||||
let preout = [],
|
||||
process = opt.params || settings.process,
|
||||
|
|
@ -807,6 +820,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
scarfLength = process.outputScarfLength || 0,
|
||||
minLayerTime = process.outputMinLayerTime || 0,
|
||||
nozzleSize = process.sliceLineWidth || device.extruders[extruder].extNozzle,
|
||||
nozzles = device.extruders.map(e => e.extNozzle),
|
||||
firstLayer = (opt.first || false) && !opt.support,
|
||||
thinWall = nozzleSize * (opt.thinWall || 1.75),
|
||||
retractDist = opt.retractOver || (nozzleSize * 5),
|
||||
|
|
@ -815,17 +829,20 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
shellOrder = {"out-in":-1,"in-out":1,"alternate":-2}[process.sliceShellOrder] || -1,
|
||||
sparseMult = process.outputSparseMult,
|
||||
coastDist = process.outputCoastDist || 0,
|
||||
fanSpeed = process.outputFanSpeed,
|
||||
firstShellSpeed = process.firstLayerRate,
|
||||
firstFillSpeed = process.firstLayerFillRate,
|
||||
firstPrintMult = process.firstLayerPrintMult,
|
||||
finishSpeed = firstLayer ? firstShellSpeed : (opt.speed || process.outputFinishrate),
|
||||
printSpeed = opt.speed || (firstLayer ? firstShellSpeed : process.outputFeedrate),
|
||||
fillSpeed = opt.speed || opt.fillSpeed || (firstLayer ? firstFillSpeed || firstShellSpeed : process.outputFeedrate),
|
||||
infillSpeed = process.sliceFillRate || opt.infillSpeed || fillSpeed || printSpeed,
|
||||
infillSpeed = opt.infillSpeed || fillSpeed || printSpeed,
|
||||
moveSpeed = process.outputSeekrate,
|
||||
minSpeed = process.outputMinSpeed,
|
||||
origin = startPoint.add(offset),
|
||||
zhop = process.zHopDistance || 0,
|
||||
zmax = opt.zmax,
|
||||
antiBacklash = process.antiBacklash,
|
||||
maxFlowRate = process.outputMaxFlowrate,
|
||||
wipeDist = process.outputRetractWipe || 0,
|
||||
isBelt = device.bedBelt,
|
||||
bedOffset = originCenter ? {
|
||||
|
|
@ -849,11 +866,6 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
shellOrder = -shellOrder;
|
||||
}
|
||||
|
||||
// update fill speed when solids detected
|
||||
if (slice.finishSolids) {
|
||||
fillSpeed = process.sliceSolidRate || finishSpeed;
|
||||
}
|
||||
|
||||
// on flats layers also slow down shell print speed
|
||||
if (slice.isFlatsLayer) {
|
||||
printSpeed = finishSpeed || printSpeed;
|
||||
|
|
@ -862,13 +874,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
// override: adapt bridge layer speed
|
||||
if (slice.isBridgeLayer) {
|
||||
fillSpeed /= 2;
|
||||
}
|
||||
|
||||
// apply first layer extrusion multipliers
|
||||
if (firstLayer) {
|
||||
fillMult *= firstPrintMult;
|
||||
shellMult *= firstPrintMult;
|
||||
sparseMult *= firstPrintMult;
|
||||
printSpeed /= 2;
|
||||
}
|
||||
|
||||
function retract() {
|
||||
|
|
@ -1100,9 +1106,6 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
coast: firstLayer ? 0 : coastDist,
|
||||
extrude: numOrDefault(opt.extrude, shellMult),
|
||||
nozzleSize,
|
||||
rate: finishShell ? finishSpeed : printSpeed,
|
||||
scarf,
|
||||
tool: extruder,
|
||||
onfirst: (firstPoint, preout) => {
|
||||
let from = seedPoint || startPoint;
|
||||
if (from.distTo2D(firstPoint) > retractDist) {
|
||||
|
|
@ -1111,7 +1114,11 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
}
|
||||
}
|
||||
seedPoint = null;
|
||||
}
|
||||
},
|
||||
rate: finishShell ? finishSpeed : printSpeed,
|
||||
scarf,
|
||||
tool: extruder,
|
||||
zmax
|
||||
});
|
||||
lastPoly = slice.lastPoly = poly;
|
||||
}
|
||||
|
|
@ -1591,16 +1598,34 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
if (!p.emit) continue;
|
||||
let mms = p.speed ?? 1;
|
||||
let dst = lp?.distTo2D(p.point) ?? 0;
|
||||
lp = p.point;
|
||||
if (dst) {
|
||||
tt += (dst/mms);
|
||||
td += dst;
|
||||
p.dst = dst;
|
||||
}
|
||||
lp = p.point;
|
||||
}
|
||||
if (minLayerTime && tt < minLayerTime) {
|
||||
let fact = tt / minLayerTime;
|
||||
for (let p of preout) {
|
||||
if (p.emit) p.speed *= fact;
|
||||
if (p.emit) {
|
||||
p.speed = Math.max(p.speed * fact, minSpeed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// apply speed damping when output exceeds volumetric flowrate cap
|
||||
if (maxFlowRate) {
|
||||
for (let p of preout) {
|
||||
if (p.emit && p.dst) {
|
||||
let vfr = nozzles[p.tool] * slice.height * p.speed;
|
||||
if (vfr > maxFlowRate) {
|
||||
let old = p.speed;
|
||||
let damp = maxFlowRate / vfr;
|
||||
p.speed = p.speed * damp;
|
||||
// console.log({ vfr, damp, old, new: p.speed });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1612,6 +1637,12 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
// add offset points to total print
|
||||
print.addPrintPoints(preout, output);
|
||||
|
||||
// bridge layer fan change
|
||||
if (preout.length && slice.isBridgeLayer) {
|
||||
preout[0].fan = Math.round(fanSpeed / 3);
|
||||
preout.peek().fan = fanSpeed;
|
||||
}
|
||||
|
||||
// create next layer start point and annotate
|
||||
// with print time for previous layer (hack to
|
||||
// avoid changing return signature rn)
|
||||
|
|
|
|||
|
|
@ -140,7 +140,8 @@ export function fdm_slice(settings, widget, onupdate, ondone) {
|
|||
fillOffset = lineWidth * fillOffsetMult,
|
||||
clipOffset = process.sliceSupportOffset,
|
||||
sliceFillAngle = process.sliceFillAngle,
|
||||
supportDensity = process.sliceSupportDensity;
|
||||
supportDensity = process.sliceSupportDensity,
|
||||
{ sliceCompInner, sliceCompOuter } = process;
|
||||
|
||||
// override globals used by vopt()
|
||||
isFlat = controller.lineType === "flat";
|
||||
|
|
@ -230,6 +231,8 @@ export function fdm_slice(settings, widget, onupdate, ondone) {
|
|||
useAssembly,
|
||||
post: 'FDM',
|
||||
post_args: {
|
||||
compInner: sliceCompInner,
|
||||
compOuter: sliceCompOuter,
|
||||
shellOffset,
|
||||
fillOffset,
|
||||
clipOffset,
|
||||
|
|
@ -495,7 +498,6 @@ export function fdm_slice(settings, widget, onupdate, ondone) {
|
|||
// just the top/bottom special solid layers or range defined solid layers
|
||||
forSlices(0.15, 0.2, slice => {
|
||||
let range = slice.params;
|
||||
let spaceMult = slice.index === 0 ? process.firstLayerLineMult || 1 : 1;
|
||||
let isBottom = slice.index < bottomLayers;
|
||||
let isTop = topLayers && slice.index > slices.length - topLayers - 1;
|
||||
let isTopBase = isTop && slice.index === slices.length - topLayers;
|
||||
|
|
@ -503,7 +505,7 @@ export function fdm_slice(settings, widget, onupdate, ondone) {
|
|||
let isSolid = (isBottom || ((isTop || isDense) && !vaseMode)) && !isSynth;
|
||||
let solidWidth = isSolid ? range.sliceFillWidth || 1 : 0;
|
||||
if (solidWidth) {
|
||||
let fillSpace = fillSpacing * spaceMult * solidWidth;
|
||||
let fillSpace = fillSpacing * solidWidth;
|
||||
doSolidLayerFill(slice, fillSpace, sliceFillAngle);
|
||||
}
|
||||
if (slice.index === slices.length - 1) {
|
||||
|
|
@ -752,8 +754,7 @@ export function fdm_slice(settings, widget, onupdate, ondone) {
|
|||
let params = slice.params || process;
|
||||
let first = slice.index === 0;
|
||||
let solidWidth = params.sliceFillWidth || 1;
|
||||
let spaceMult = first ? params.firstLayerLineMult || 1 : 1;
|
||||
let fillSpace = fillSpacing * spaceMult * solidWidth;
|
||||
let fillSpace = fillSpacing * solidWidth;
|
||||
let solidMinArea = params.sliceSolidMinArea;
|
||||
doSolidsFill(slice, fillSpace, sliceFillAngle, solidMinArea, promises);
|
||||
sliceFillAngle = (sliceFillAngle + 90.0) % 360;
|
||||
|
|
|
|||
|
|
@ -315,7 +315,6 @@ const funcs = self.minion = {
|
|||
|
||||
const topo4 = Object.assign(new Topo4(), cache.lathe);
|
||||
const heights = topo4.lathePath(stmp, tool);
|
||||
|
||||
reply({ seq, heights });
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { version } from '../../moto/license.js';
|
|||
import { Widget, newWidget } from '../core/widget.js';
|
||||
import { load } from "../../load/png.js";
|
||||
import { JSZip } from '../../ext/jszip-esm.js';
|
||||
import { RasterPath } from '../../gpu/raster.js';
|
||||
|
||||
import { CAM } from '../mode/cam/driver-be.js';
|
||||
import { DRAG } from '../mode/drag/driver.js';
|
||||
|
|
@ -64,6 +65,22 @@ self.alert = function(o) {
|
|||
|
||||
self.uuid = ((Math.random() * Date.now()) | 0).toString(36);
|
||||
|
||||
/**
|
||||
* @returns {RasterPath}
|
||||
*/
|
||||
self.get_raster_gpu = async function({ mode, resolution, rotationStep }) {
|
||||
let gpu = new RasterPath({
|
||||
mode,
|
||||
resolution,
|
||||
rotationStep,
|
||||
workerName: "/lib/gpu/raster-worker.js",
|
||||
quiet: true,
|
||||
debug: false,
|
||||
});
|
||||
await gpu.init();
|
||||
return gpu;
|
||||
};
|
||||
|
||||
function minhandler(msg) {
|
||||
let data = msg.data;
|
||||
let seq = data.seq;
|
||||
|
|
|
|||
676
src/load/step.js
Normal file
676
src/load/step.js
Normal file
|
|
@ -0,0 +1,676 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
import { tool as MeshTool } from '../mesh/tool.js';
|
||||
|
||||
const { Vector3 } = THREE;
|
||||
function debug() {
|
||||
if (false) console.log(...arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert STL triangles to STEP file format
|
||||
*
|
||||
* @param {Array} triangles - Array of triangle objects with vertices: [{v1, v2, v3}, ...]
|
||||
* where v1, v2, v3 are {x, y, z} objects or Vector3 instances
|
||||
* @param {Object} options - Optional parameters
|
||||
* @param {string} options.productName - Name for the STEP product (default: 'mesh')
|
||||
* @param {string} options.units - Units: 'mm', 'cm', 'm', 'inch' (default: 'mm')
|
||||
* @returns {string} STEP file content as a string
|
||||
*/
|
||||
export function meshToSTEP(triangles, options = {}) {
|
||||
const productName = options.productName || 'mesh';
|
||||
const units = options.units || 'mm';
|
||||
|
||||
// Build vertex and triangle topology
|
||||
const vertices = [];
|
||||
const vertexMap = new Map(); // Map vertex coords to index
|
||||
const faces = [];
|
||||
|
||||
// Helper to find or add vertex
|
||||
function getOrAddVertex(v) {
|
||||
const vec = v instanceof Vector3 ? v : new Vector3(v.x, v.y, v.z);
|
||||
|
||||
// Check if vertex already exists
|
||||
for (let i = 0; i < vertices.length; i++) {
|
||||
if (vertices[i].equals(vec)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// Add new vertex
|
||||
vertices.push(vec);
|
||||
return vertices.length - 1;
|
||||
}
|
||||
|
||||
// Process triangles
|
||||
for (const tri of triangles) {
|
||||
const idx0 = getOrAddVertex(tri.v1 || tri.vertices[0]);
|
||||
const idx1 = getOrAddVertex(tri.v2 || tri.vertices[1]);
|
||||
const idx2 = getOrAddVertex(tri.v3 || tri.vertices[2]);
|
||||
|
||||
faces.push([idx0, idx1, idx2]);
|
||||
}
|
||||
|
||||
// Generate STEP file
|
||||
let step = `ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('STL to STEP Conversion'),'2;1');
|
||||
FILE_NAME('${productName}.step','${new Date().toISOString()}',(''),(''),'stl-to-step','','');
|
||||
FILE_SCHEMA(('AUTOMOTIVE_DESIGN'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
`;
|
||||
|
||||
let id = 1;
|
||||
|
||||
// Units mapping
|
||||
const unitMultiplier = {
|
||||
'mm': '.MILLI.',
|
||||
'cm': '.CENTI.',
|
||||
'm': '',
|
||||
'inch': '.INCH.'
|
||||
};
|
||||
const unitPrefix = unitMultiplier[units] || '.MILLI.';
|
||||
|
||||
// Units and context
|
||||
const lengthUnit = id++;
|
||||
const planeAngleUnit = id++;
|
||||
const solidAngleUnit = id++;
|
||||
|
||||
step += `#${lengthUnit}=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT(${unitPrefix},.METRE.));\n`;
|
||||
step += `#${planeAngleUnit}=(NAMED_UNIT(*)PLANE_ANGLE_UNIT()SI_UNIT($,.RADIAN.));\n`;
|
||||
step += `#${solidAngleUnit}=(NAMED_UNIT(*)SI_UNIT($,.STERADIAN.)SOLID_ANGLE_UNIT());\n`;
|
||||
|
||||
const uncertainty = id++;
|
||||
step += `#${uncertainty}=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-07),#${lengthUnit},'distance_accuracy_value','confusion accuracy');\n`;
|
||||
|
||||
// Geometric representation context
|
||||
const geomContextId = id++;
|
||||
step += `#${geomContextId}=(GEOMETRIC_REPRESENTATION_CONTEXT(3)GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#${uncertainty}))GLOBAL_UNIT_ASSIGNED_CONTEXT((#${lengthUnit},#${planeAngleUnit},#${solidAngleUnit}))REPRESENTATION_CONTEXT('ID1','3D'));\n`;
|
||||
|
||||
// Application context
|
||||
const appContext = id++;
|
||||
step += `#${appContext}=APPLICATION_CONTEXT('configuration controlled 3d designs of mechanical parts and assemblies');\n`;
|
||||
|
||||
const appProtocol = id++;
|
||||
step += `#${appProtocol}=APPLICATION_PROTOCOL_DEFINITION('international standard','automotive_design',1998,#${appContext});\n`;
|
||||
|
||||
// Product definition
|
||||
const productId = id++;
|
||||
step += `#${productId}=PRODUCT('${productName}','${productName}','',(#${appContext}));\n`;
|
||||
|
||||
const productDefFormId = id++;
|
||||
step += `#${productDefFormId}=PRODUCT_DEFINITION_FORMATION('','',#${productId});\n`;
|
||||
|
||||
const productDefCtx = id++;
|
||||
step += `#${productDefCtx}=PRODUCT_DEFINITION_CONTEXT('part definition',#${appContext},'design');\n`;
|
||||
|
||||
const productDefId = id++;
|
||||
step += `#${productDefId}=PRODUCT_DEFINITION('design','',#${productDefFormId},#${productDefCtx});\n`;
|
||||
|
||||
// Shape representation - reserve ID
|
||||
const shapeRepId = id++;
|
||||
|
||||
// Create cartesian points for all vertices
|
||||
const vertexIds = [];
|
||||
for (let i = 0; i < vertices.length; i++) {
|
||||
const v = vertices[i];
|
||||
const pointId = id++;
|
||||
step += `#${pointId}=CARTESIAN_POINT('',(${v.x.toFixed(6)},${v.y.toFixed(6)},${v.z.toFixed(6)}));\n`;
|
||||
vertexIds.push(pointId);
|
||||
}
|
||||
|
||||
// Create vertex points
|
||||
const vertexPointIds = [];
|
||||
for (let i = 0; i < vertices.length; i++) {
|
||||
const vpId = id++;
|
||||
step += `#${vpId}=VERTEX_POINT('',#${vertexIds[i]});\n`;
|
||||
vertexPointIds.push(vpId);
|
||||
}
|
||||
|
||||
// Advanced B-rep shape representation
|
||||
const manifoldId = id++;
|
||||
const closedShellId = id++;
|
||||
|
||||
step += `#${shapeRepId}=ADVANCED_BREP_SHAPE_REPRESENTATION('',(#${manifoldId}),#${geomContextId});\n`;
|
||||
step += `#${manifoldId}=MANIFOLD_SOLID_BREP('',#${closedShellId});\n`;
|
||||
|
||||
const faceIds = [];
|
||||
for (let i = 0; i < faces.length; i++) {
|
||||
faceIds.push(id++);
|
||||
}
|
||||
|
||||
step += `#${closedShellId}=CLOSED_SHELL('',(${faceIds.map(fid => `#${fid}`).join(',')}));\n`;
|
||||
|
||||
// Create each triangular face
|
||||
for (let faceIdx = 0; faceIdx < faces.length; faceIdx++) {
|
||||
const faceVertices = faces[faceIdx];
|
||||
const faceId = faceIds[faceIdx];
|
||||
|
||||
const faceOuterBoundId = id++;
|
||||
const edgeLoopId = id++;
|
||||
|
||||
// Create edges for this triangular face
|
||||
const orientedEdgeIds = [];
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const v1Idx = faceVertices[i];
|
||||
const v2Idx = faceVertices[(i + 1) % 3];
|
||||
|
||||
const orientedEdgeId = id++;
|
||||
const edgeCurveId = id++;
|
||||
const lineId = id++;
|
||||
const vectorId = id++;
|
||||
const directionId = id++;
|
||||
|
||||
orientedEdgeIds.push(orientedEdgeId);
|
||||
|
||||
// Direction vector
|
||||
const v1 = vertices[v1Idx];
|
||||
const v2 = vertices[v2Idx];
|
||||
const dx = v2.x - v1.x;
|
||||
const dy = v2.y - v1.y;
|
||||
const dz = v2.z - v1.z;
|
||||
const len = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
if (len < 1e-10) {
|
||||
console.warn(`Warning: Degenerate edge in face ${faceIdx}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
step += `#${directionId}=DIRECTION('',(${(dx / len).toFixed(6)},${(dy / len).toFixed(6)},${(dz / len).toFixed(6)}));\n`;
|
||||
step += `#${vectorId}=VECTOR('',#${directionId},${len.toFixed(6)});\n`;
|
||||
step += `#${lineId}=LINE('',#${vertexIds[v1Idx]},#${vectorId});\n`;
|
||||
step += `#${edgeCurveId}=EDGE_CURVE('',#${vertexPointIds[v1Idx]},#${vertexPointIds[v2Idx]},#${lineId},.T.);\n`;
|
||||
step += `#${orientedEdgeId}=ORIENTED_EDGE('',*,*,#${edgeCurveId},.T.);\n`;
|
||||
}
|
||||
|
||||
step += `#${edgeLoopId}=EDGE_LOOP('',(${orientedEdgeIds.map(eid => `#${eid}`).join(',')}));\n`;
|
||||
step += `#${faceOuterBoundId}=FACE_OUTER_BOUND('',#${edgeLoopId},.T.);\n`;
|
||||
|
||||
// Create plane for face
|
||||
const planeId = id++;
|
||||
const axisPlacementId = id++;
|
||||
const normalDirId = id++;
|
||||
const refDirId = id++;
|
||||
|
||||
// Calculate face normal and reference direction
|
||||
const v0 = vertices[faceVertices[0]];
|
||||
const v1 = vertices[faceVertices[1]];
|
||||
const v2 = vertices[faceVertices[2]];
|
||||
|
||||
const edge1 = v1.clone().sub(v0);
|
||||
const edge2 = v2.clone().sub(v0);
|
||||
const normal = edge1.clone().cross(edge2).normalize();
|
||||
|
||||
// Calculate face center for plane origin
|
||||
const centerX = (v0.x + v1.x + v2.x) / 3;
|
||||
const centerY = (v0.y + v1.y + v2.y) / 3;
|
||||
const centerZ = (v0.z + v1.z + v2.z) / 3;
|
||||
|
||||
const centerPointId = id++;
|
||||
step += `#${centerPointId}=CARTESIAN_POINT('',(${centerX.toFixed(6)},${centerY.toFixed(6)},${centerZ.toFixed(6)}));\n`;
|
||||
|
||||
step += `#${normalDirId}=DIRECTION('',(${normal.x.toFixed(6)},${normal.y.toFixed(6)},${normal.z.toFixed(6)}));\n`;
|
||||
const refDir = edge1.normalize();
|
||||
step += `#${refDirId}=DIRECTION('',(${refDir.x.toFixed(6)},${refDir.y.toFixed(6)},${refDir.z.toFixed(6)}));\n`;
|
||||
step += `#${axisPlacementId}=AXIS2_PLACEMENT_3D('',#${centerPointId},#${normalDirId},#${refDirId});\n`;
|
||||
step += `#${planeId}=PLANE('',#${axisPlacementId});\n`;
|
||||
|
||||
step += `#${faceId}=ADVANCED_FACE('',(#${faceOuterBoundId}),#${planeId},.T.);\n`;
|
||||
}
|
||||
|
||||
// Product definition shape
|
||||
const prodDefShapeId = id++;
|
||||
step += `#${prodDefShapeId}=PRODUCT_DEFINITION_SHAPE('','',#${productDefId});\n`;
|
||||
step += `#${id++}=SHAPE_DEFINITION_REPRESENTATION(#${prodDefShapeId},#${shapeRepId});\n`;
|
||||
|
||||
step += `ENDSEC;\nEND-ISO-10303-21;\n`;
|
||||
|
||||
return step;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert STL triangles to STEP file format with face merging
|
||||
* Groups co-planar connected triangles into larger faces
|
||||
*
|
||||
* @param {Array} triangles - Array of triangle objects with vertices: [{v1, v2, v3}, ...]
|
||||
* where v1, v2, v3 are {x, y, z} objects or Vector3 instances
|
||||
* @param {Object} options - Optional parameters
|
||||
* @param {string} options.productName - Name for the STEP product (default: 'mesh')
|
||||
* @param {string} options.units - Units: 'mm', 'cm', 'm', 'inch' (default: 'mm')
|
||||
* @param {number} options.angleTolerance - Angle tolerance in degrees for co-planarity (default: 1)
|
||||
* @returns {string} STEP file content as a string
|
||||
*/
|
||||
export function meshToSTEPWithFaces(triangles, options = {}) {
|
||||
const productName = options.productName || 'mesh';
|
||||
const units = options.units || 'mm';
|
||||
const angleTolerance = options.angleTolerance || 1;
|
||||
const radianTolerance = angleTolerance * (Math.PI / 180);
|
||||
|
||||
// Convert triangles to flat vertex array for MeshTool
|
||||
const vertices = [];
|
||||
for (const tri of triangles) {
|
||||
const v1 = tri.v1 || tri.vertices[0];
|
||||
const v2 = tri.v2 || tri.vertices[1];
|
||||
const v3 = tri.v3 || tri.vertices[2];
|
||||
|
||||
vertices.push(
|
||||
v1.x, v1.y, v1.z,
|
||||
v2.x, v2.y, v2.z,
|
||||
v3.x, v3.y, v3.z
|
||||
);
|
||||
}
|
||||
|
||||
// Use MeshTool to index faces and find adjacency
|
||||
const tool = new MeshTool();
|
||||
tool.index(vertices);
|
||||
|
||||
const totalFaces = triangles.length;
|
||||
const processed = {};
|
||||
const surfaces = [];
|
||||
|
||||
// Find all connected surfaces
|
||||
for (let i = 0; i < totalFaces; i++) {
|
||||
if (processed[i]) continue;
|
||||
|
||||
// Find all connected co-planar faces starting from this face
|
||||
const connectedFaces = tool.findConnectedSurface([i], radianTolerance, undefined, processed);
|
||||
|
||||
if (connectedFaces.length > 0) {
|
||||
// Generate outline(s) for this surface
|
||||
const outlines = tool.generateOutlines(connectedFaces);
|
||||
|
||||
if (outlines.length > 1) {
|
||||
debug(`Surface with ${connectedFaces.length} faces has ${outlines.length} outlines:`);
|
||||
outlines.forEach((outline, idx) => {
|
||||
debug(` Outline ${idx}: ${outline.length} vertices`);
|
||||
});
|
||||
}
|
||||
|
||||
// Sort outlines by area (largest first = outer boundary)
|
||||
// Use shoelace formula to calculate signed area
|
||||
const outlinesWithArea = outlines.map(outline => {
|
||||
let area = 0;
|
||||
for (let i = 0; i < outline.length; i++) {
|
||||
const p1 = outline[i];
|
||||
const p2 = outline[(i + 1) % outline.length];
|
||||
area += (p1.x * p2.y - p2.x * p1.y);
|
||||
}
|
||||
return { outline, area: Math.abs(area) };
|
||||
});
|
||||
|
||||
// Sort by area descending (largest = outer boundary)
|
||||
outlinesWithArea.sort((a, b) => b.area - a.area);
|
||||
const sortedOutlines = outlinesWithArea.map(o => o.outline);
|
||||
|
||||
surfaces.push({ faces: connectedFaces, outlines: sortedOutlines });
|
||||
}
|
||||
}
|
||||
|
||||
debug(`[v4] Merged ${totalFaces} triangles into ${surfaces.length} faces`);
|
||||
|
||||
// Generate STEP file
|
||||
let step = `ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('STL to STEP Conversion with Face Merging'),'2;1');
|
||||
FILE_NAME('${productName}.step','${new Date().toISOString()}',(''),(''),'stl-to-step','','');
|
||||
FILE_SCHEMA(('AUTOMOTIVE_DESIGN'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
`;
|
||||
|
||||
let id = 1;
|
||||
|
||||
// Units mapping
|
||||
const unitMultiplier = {
|
||||
'mm': '.MILLI.',
|
||||
'cm': '.CENTI.',
|
||||
'm': '',
|
||||
'inch': '.INCH.'
|
||||
};
|
||||
const unitPrefix = unitMultiplier[units] || '.MILLI.';
|
||||
|
||||
// Units and context
|
||||
const lengthUnit = id++;
|
||||
const planeAngleUnit = id++;
|
||||
const solidAngleUnit = id++;
|
||||
|
||||
step += `#${lengthUnit}=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT(${unitPrefix},.METRE.));\n`;
|
||||
step += `#${planeAngleUnit}=(NAMED_UNIT(*)PLANE_ANGLE_UNIT()SI_UNIT($,.RADIAN.));\n`;
|
||||
step += `#${solidAngleUnit}=(NAMED_UNIT(*)SI_UNIT($,.STERADIAN.)SOLID_ANGLE_UNIT());\n`;
|
||||
|
||||
const uncertainty = id++;
|
||||
step += `#${uncertainty}=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-07),#${lengthUnit},'distance_accuracy_value','confusion accuracy');\n`;
|
||||
|
||||
// Geometric representation context
|
||||
const geomContextId = id++;
|
||||
step += `#${geomContextId}=(GEOMETRIC_REPRESENTATION_CONTEXT(3)GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#${uncertainty}))GLOBAL_UNIT_ASSIGNED_CONTEXT((#${lengthUnit},#${planeAngleUnit},#${solidAngleUnit}))REPRESENTATION_CONTEXT('ID1','3D'));\n`;
|
||||
|
||||
// Application context
|
||||
const appContext = id++;
|
||||
step += `#${appContext}=APPLICATION_CONTEXT('configuration controlled 3d designs of mechanical parts and assemblies');\n`;
|
||||
|
||||
const appProtocol = id++;
|
||||
step += `#${appProtocol}=APPLICATION_PROTOCOL_DEFINITION('international standard','automotive_design',1998,#${appContext});\n`;
|
||||
|
||||
// Product definition
|
||||
const productId = id++;
|
||||
step += `#${productId}=PRODUCT('${productName}','${productName}','',(#${appContext}));\n`;
|
||||
|
||||
const productDefFormId = id++;
|
||||
step += `#${productDefFormId}=PRODUCT_DEFINITION_FORMATION('','',#${productId});\n`;
|
||||
|
||||
const productDefCtx = id++;
|
||||
step += `#${productDefCtx}=PRODUCT_DEFINITION_CONTEXT('part definition',#${appContext},'design');\n`;
|
||||
|
||||
const productDefId = id++;
|
||||
step += `#${productDefId}=PRODUCT_DEFINITION('design','',#${productDefFormId},#${productDefCtx});\n`;
|
||||
|
||||
// Shape representation - reserve ID
|
||||
const shapeRepId = id++;
|
||||
|
||||
// Build vertex map and IDs
|
||||
const vertexMap = new Map();
|
||||
const cartesianPointIds = new Map();
|
||||
const vertexPointIds = new Map();
|
||||
|
||||
function getOrCreateVertex(x, y, z) {
|
||||
const key = `${x.toFixed(6)},${y.toFixed(6)},${z.toFixed(6)}`;
|
||||
|
||||
if (!vertexMap.has(key)) {
|
||||
// Create cartesian point
|
||||
const pointId = id++;
|
||||
step += `#${pointId}=CARTESIAN_POINT('',(${x.toFixed(6)},${y.toFixed(6)},${z.toFixed(6)}));\n`;
|
||||
cartesianPointIds.set(key, pointId);
|
||||
|
||||
// Create vertex point
|
||||
const vpId = id++;
|
||||
step += `#${vpId}=VERTEX_POINT('',#${pointId});\n`;
|
||||
vertexPointIds.set(key, vpId);
|
||||
|
||||
vertexMap.set(key, { pointId, vpId, x, y, z });
|
||||
}
|
||||
|
||||
return vertexMap.get(key);
|
||||
}
|
||||
|
||||
// Build global edge map - each unique edge gets one EDGE_CURVE
|
||||
const edgeMap = new Map();
|
||||
|
||||
function getOrCreateEdge(v1, v2, stepBuffer) {
|
||||
// Create consistent key regardless of vertex order
|
||||
const key1 = `${v1.pointId},${v2.pointId}`;
|
||||
const key2 = `${v2.pointId},${v1.pointId}`;
|
||||
|
||||
let edgeData = edgeMap.get(key1) || edgeMap.get(key2);
|
||||
|
||||
if (!edgeData) {
|
||||
const dx = v2.x - v1.x;
|
||||
const dy = v2.y - v1.y;
|
||||
const dz = v2.z - v1.z;
|
||||
const len = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
const edgeCurveId = id++;
|
||||
const lineId = id++;
|
||||
const vectorId = id++;
|
||||
const directionId = id++;
|
||||
|
||||
stepBuffer.append = stepBuffer.append || '';
|
||||
stepBuffer.append += `#${directionId}=DIRECTION('',(${(dx / len).toFixed(6)},${(dy / len).toFixed(6)},${(dz / len).toFixed(6)}));\n`;
|
||||
stepBuffer.append += `#${vectorId}=VECTOR('',#${directionId},${len.toFixed(6)});\n`;
|
||||
stepBuffer.append += `#${lineId}=LINE('',#${v1.pointId},#${vectorId});\n`;
|
||||
stepBuffer.append += `#${edgeCurveId}=EDGE_CURVE('',#${v1.vpId},#${v2.vpId},#${lineId},.T.);\n`;
|
||||
|
||||
edgeData = { edgeCurveId, v1, v2, len };
|
||||
edgeMap.set(key1, edgeData);
|
||||
}
|
||||
|
||||
return edgeData;
|
||||
}
|
||||
|
||||
// Advanced B-rep shape representation
|
||||
const manifoldId = id++;
|
||||
const closedShellId = id++;
|
||||
|
||||
step += `#${shapeRepId}=ADVANCED_BREP_SHAPE_REPRESENTATION('',(#${manifoldId}),#${geomContextId});\n`;
|
||||
step += `#${manifoldId}=MANIFOLD_SOLID_BREP('',#${closedShellId});\n`;
|
||||
|
||||
// We'll write the CLOSED_SHELL after creating faces
|
||||
// Track which face IDs are actually created
|
||||
const createdFaceIds = [];
|
||||
|
||||
// Helper function to merge co-linear edges in an outline
|
||||
function mergeColinearEdges(outline) {
|
||||
if (outline.length < 3) return outline;
|
||||
|
||||
const tolerance = 1e-6;
|
||||
|
||||
// Helper to check if three points are co-linear
|
||||
function areColinear(p1, p2, p3) {
|
||||
const v1 = new Vector3(p2.x - p1.x, p2.y - p1.y, p2.z - p1.z);
|
||||
const v2 = new Vector3(p3.x - p2.x, p3.y - p2.y, p3.z - p2.z);
|
||||
|
||||
const len1 = v1.length();
|
||||
const len2 = v2.length();
|
||||
|
||||
if (len1 < tolerance || len2 < tolerance) return true;
|
||||
|
||||
v1.normalize();
|
||||
v2.normalize();
|
||||
|
||||
// Check if vectors are parallel (dot product close to 1 or -1)
|
||||
const dot = Math.abs(v1.dot(v2));
|
||||
return dot > 1 - tolerance;
|
||||
}
|
||||
|
||||
// Mark vertices to keep
|
||||
const keep = new Array(outline.length).fill(true);
|
||||
|
||||
// Check each vertex to see if it's on a straight line
|
||||
for (let i = 0; i < outline.length; i++) {
|
||||
const prev = outline[(i - 1 + outline.length) % outline.length];
|
||||
const curr = outline[i];
|
||||
const next = outline[(i + 1) % outline.length];
|
||||
|
||||
if (areColinear(prev, curr, next)) {
|
||||
keep[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Build merged array with only kept vertices
|
||||
const merged = [];
|
||||
for (let i = 0; i < outline.length; i++) {
|
||||
if (keep[i]) {
|
||||
merged.push(outline[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return merged.length >= 3 ? merged : outline;
|
||||
}
|
||||
|
||||
// Buffer to collect face geometry before writing CLOSED_SHELL
|
||||
let faceStepData = '';
|
||||
|
||||
// Create each merged face
|
||||
for (let surfaceIdx = 0; surfaceIdx < surfaces.length; surfaceIdx++) {
|
||||
const surface = surfaces[surfaceIdx];
|
||||
|
||||
if (!surface.outlines || surface.outlines.length === 0) {
|
||||
console.warn(`Skipping invalid surface ${surfaceIdx} with no outlines`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allocate face ID now (only for valid surfaces)
|
||||
const faceId = id++;
|
||||
createdFaceIds.push(faceId);
|
||||
|
||||
// Process all outlines: first is outer boundary, rest are holes
|
||||
const boundIds = [];
|
||||
let allVertices = [];
|
||||
|
||||
for (let outlineIdx = 0; outlineIdx < surface.outlines.length; outlineIdx++) {
|
||||
let outline = surface.outlines[outlineIdx];
|
||||
|
||||
if (!outline || outline.length < 3) {
|
||||
console.warn(`Skipping invalid outline ${outlineIdx} in surface ${surfaceIdx}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Merge co-linear edges
|
||||
const originalLength = outline.length;
|
||||
outline = mergeColinearEdges(outline);
|
||||
|
||||
if (originalLength !== outline.length) {
|
||||
debug(` Merged outline ${outlineIdx}: ${originalLength} -> ${outline.length} vertices`);
|
||||
}
|
||||
|
||||
if (outline.length < 3) {
|
||||
console.warn(`Outline ${outlineIdx} in surface ${surfaceIdx} has < 3 vertices after merging`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const boundId = id++;
|
||||
const edgeLoopId = id++;
|
||||
|
||||
// Create edges for this outline
|
||||
const orientedEdgeIds = [];
|
||||
const vertices = [];
|
||||
|
||||
// Get vertices and create vertex records
|
||||
for (let i = 0; i < outline.length; i++) {
|
||||
const pt = outline[i];
|
||||
const vertex = getOrCreateVertex(pt.x, pt.y, pt.z);
|
||||
vertices.push(vertex);
|
||||
}
|
||||
|
||||
// Store vertices from first outline for normal calculation
|
||||
if (outlineIdx === 0) {
|
||||
allVertices = vertices;
|
||||
}
|
||||
|
||||
// Create edges - for holes (outlineIdx > 0), use opposite orientation
|
||||
const isHole = outlineIdx > 0;
|
||||
|
||||
// Buffer for edge geometry (needs to be written before ORIENTED_EDGEs)
|
||||
const edgeBuffer = {};
|
||||
const orientedEdgeData = []; // Store oriented edge info to write after EDGE_CURVEs
|
||||
|
||||
for (let i = 0; i < vertices.length; i++) {
|
||||
const v1 = vertices[i];
|
||||
const v2 = vertices[(i + 1) % vertices.length];
|
||||
|
||||
// Get or create shared edge
|
||||
const edgeData = getOrCreateEdge(v1, v2, edgeBuffer);
|
||||
|
||||
if (edgeData.len < 1e-10) {
|
||||
console.warn(`Warning: Degenerate edge in surface ${surfaceIdx}, outline ${outlineIdx}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine orientation - check if edge is stored in same direction we're traversing
|
||||
const sameDirection = (edgeData.v1.pointId === v1.pointId && edgeData.v2.pointId === v2.pointId);
|
||||
let edgeOrientation;
|
||||
|
||||
if (isHole) {
|
||||
// For holes, flip the orientation
|
||||
edgeOrientation = sameDirection ? '.F.' : '.T.';
|
||||
} else {
|
||||
// For outer boundary, use natural orientation
|
||||
edgeOrientation = sameDirection ? '.T.' : '.F.';
|
||||
}
|
||||
|
||||
const orientedEdgeId = id++;
|
||||
orientedEdgeIds.push(orientedEdgeId);
|
||||
orientedEdgeData.push({ orientedEdgeId, edgeCurveId: edgeData.edgeCurveId, edgeOrientation });
|
||||
}
|
||||
|
||||
// Write edge geometry first (EDGE_CURVEs must exist before ORIENTED_EDGEs reference them)
|
||||
if (edgeBuffer.append) {
|
||||
faceStepData += edgeBuffer.append;
|
||||
}
|
||||
|
||||
// Now write ORIENTED_EDGEs
|
||||
for (const oed of orientedEdgeData) {
|
||||
faceStepData += `#${oed.orientedEdgeId}=ORIENTED_EDGE('',*,*,#${oed.edgeCurveId},${oed.edgeOrientation});\n`;
|
||||
}
|
||||
|
||||
if (orientedEdgeIds.length === 0) {
|
||||
console.warn(`No valid edges in outline ${outlineIdx} of surface ${surfaceIdx}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
faceStepData += `#${edgeLoopId}=EDGE_LOOP('',(${orientedEdgeIds.map(eid => `#${eid}`).join(',')}));\n`;
|
||||
|
||||
// First outline is FACE_OUTER_BOUND, subsequent ones are holes (FACE_BOUND)
|
||||
if (outlineIdx === 0) {
|
||||
faceStepData += `#${boundId}=FACE_OUTER_BOUND('',#${edgeLoopId},.T.);\n`;
|
||||
} else {
|
||||
faceStepData += `#${boundId}=FACE_BOUND('',#${edgeLoopId},.T.);\n`;
|
||||
}
|
||||
|
||||
boundIds.push(boundId);
|
||||
}
|
||||
|
||||
if (boundIds.length === 0 || allVertices.length < 3) {
|
||||
console.warn(`Skipping surface ${surfaceIdx} - no valid bounds`);
|
||||
// Remove the face ID we allocated since we're not creating this face
|
||||
createdFaceIds.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate face normal from first three vertices of outer boundary
|
||||
const v0 = new Vector3(allVertices[0].x, allVertices[0].y, allVertices[0].z);
|
||||
const v1 = new Vector3(allVertices[1].x, allVertices[1].y, allVertices[1].z);
|
||||
const v2 = new Vector3(allVertices[2].x, allVertices[2].y, allVertices[2].z);
|
||||
|
||||
const edge1 = v1.clone().sub(v0);
|
||||
const edge2 = v2.clone().sub(v0);
|
||||
const normal = edge1.clone().cross(edge2).normalize();
|
||||
|
||||
// Calculate face center for plane origin
|
||||
let centerX = 0, centerY = 0, centerZ = 0;
|
||||
for (let vertex of allVertices) {
|
||||
centerX += vertex.x;
|
||||
centerY += vertex.y;
|
||||
centerZ += vertex.z;
|
||||
}
|
||||
centerX /= allVertices.length;
|
||||
centerY /= allVertices.length;
|
||||
centerZ /= allVertices.length;
|
||||
|
||||
const centerPointId = id++;
|
||||
faceStepData += `#${centerPointId}=CARTESIAN_POINT('',(${centerX.toFixed(6)},${centerY.toFixed(6)},${centerZ.toFixed(6)}));\n`;
|
||||
|
||||
const planeId = id++;
|
||||
const axisPlacementId = id++;
|
||||
const normalDirId = id++;
|
||||
const refDirId = id++;
|
||||
|
||||
faceStepData += `#${normalDirId}=DIRECTION('',(${normal.x.toFixed(6)},${normal.y.toFixed(6)},${normal.z.toFixed(6)}));\n`;
|
||||
const refDir = edge1.normalize();
|
||||
faceStepData += `#${refDirId}=DIRECTION('',(${refDir.x.toFixed(6)},${refDir.y.toFixed(6)},${refDir.z.toFixed(6)}));\n`;
|
||||
faceStepData += `#${axisPlacementId}=AXIS2_PLACEMENT_3D('',#${centerPointId},#${normalDirId},#${refDirId});\n`;
|
||||
faceStepData += `#${planeId}=PLANE('',#${axisPlacementId});\n`;
|
||||
|
||||
// Create ADVANCED_FACE with all bounds (outer + holes)
|
||||
faceStepData += `#${faceId}=ADVANCED_FACE('',(${boundIds.map(bid => `#${bid}`).join(',')}),#${planeId},.T.);\n`;
|
||||
}
|
||||
|
||||
// Now write CLOSED_SHELL with only the faces that were actually created
|
||||
debug(`[v4] Created ${createdFaceIds.length} faces for CLOSED_SHELL (expected ${surfaces.length})`);
|
||||
debug(`[v4] Shared ${edgeMap.size} unique edges across all faces`);
|
||||
step += `#${closedShellId}=CLOSED_SHELL('',(${createdFaceIds.map(fid => `#${fid}`).join(',')}));\n`;
|
||||
|
||||
// Append all the face geometry
|
||||
step += faceStepData;
|
||||
|
||||
// Product definition shape
|
||||
const prodDefShapeId = id++;
|
||||
step += `#${prodDefShapeId}=PRODUCT_DEFINITION_SHAPE('','',#${productDefId});\n`;
|
||||
step += `#${id++}=SHAPE_DEFINITION_REPRESENTATION(#${prodDefShapeId},#${shapeRepId});\n`;
|
||||
|
||||
step += `ENDSEC;\nEND-ISO-10303-21;\n`;
|
||||
|
||||
return step;
|
||||
}
|
||||
|
|
@ -6,12 +6,29 @@ import '../add/three.js';
|
|||
import '../kiri/core/lang.js';
|
||||
import '../kiri/core/lang-en.js';
|
||||
|
||||
import { broker } from '../moto/broker.js';
|
||||
import { run } from '../kiri/core/init.js';
|
||||
|
||||
let traceload = location.search.indexOf('traceload') > 0;
|
||||
let load = [];
|
||||
|
||||
if (traceload) {
|
||||
broker.subscribe([
|
||||
"init.one",
|
||||
"init.two",
|
||||
"init.lang",
|
||||
"init-done",
|
||||
"load-done",
|
||||
], (msg, topic) => {
|
||||
console.log(topic, '->', msg);
|
||||
})
|
||||
}
|
||||
|
||||
function safeExec(fn) {
|
||||
try {
|
||||
if (traceload) {
|
||||
console.log('kiri | exec |', fn);
|
||||
}
|
||||
fn(kiri.api);
|
||||
} catch (error) {
|
||||
console.log('load error', fn, error);
|
||||
|
|
@ -20,13 +37,32 @@ function safeExec(fn) {
|
|||
|
||||
function checkReady() {
|
||||
if (document.readyState === 'complete') {
|
||||
kiri.api = run();
|
||||
self.$ = kiri.api.web.$;
|
||||
let bootctrl = navigator.serviceWorker.controller;
|
||||
console.log(`kiri | boot ctrl | ` + (bootctrl ? true : false));
|
||||
let api = kiri.api = run();
|
||||
self.$ = api.web.$;
|
||||
for (let fn of load) {
|
||||
safeExec(fn);
|
||||
}
|
||||
load = undefined;
|
||||
kiri.api.event.emit('load-done', stats);
|
||||
api.event.emit('load-done', stats);
|
||||
if (api.electron) {
|
||||
$('install').classList.add('hide');
|
||||
$('app-quit').classList.remove('hide');
|
||||
$('app-name-text').innerText = "More Info";
|
||||
$('top-sep').style.display = 'flex';
|
||||
} else if (bootctrl) {
|
||||
$('install').classList.add('hide');
|
||||
$('uninstall').classList.remove('hide');
|
||||
$('uninstall').onclick = () => {
|
||||
bootctrl.postMessage({ clear: true, disable: true });
|
||||
location.reload();
|
||||
}
|
||||
} else {
|
||||
$('install').onclick = () => {
|
||||
location.replace('/boot');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ function init() {
|
|||
|
||||
// for electron
|
||||
self.mesh = { api };
|
||||
self.electron = navigator.userAgent.includes('Electron');
|
||||
}
|
||||
|
||||
// restore space layout and view from previous session
|
||||
|
|
@ -187,7 +188,11 @@ async function restore_space() {
|
|||
// hide loading curtain
|
||||
$d('curtain','none');
|
||||
// restore handles visibility
|
||||
handles.setEnabled(api.prefs.map.space.bounds);
|
||||
handles.setEnabled(api.prefs.map.space.bounds ?? false);
|
||||
// restore script if was showing
|
||||
if (api.prefs.map.space.script) {
|
||||
api.script.show();
|
||||
}
|
||||
if (api.prefs.map.info.welcome !== false) {
|
||||
api.welcome(version);
|
||||
}
|
||||
|
|
@ -224,7 +229,11 @@ function space_init(data) {
|
|||
if (evt.key === '?') {
|
||||
return api.welcome(version);
|
||||
}
|
||||
let { shiftKey, metaKey, ctrlKey, code } = evt;
|
||||
let { shiftKey, metaKey, ctrlKey, code, target } = evt;
|
||||
if (target.nodeName === 'TEXTAREA') {
|
||||
api.script.changed();
|
||||
return;
|
||||
}
|
||||
switch (code) {
|
||||
case 'Digit1':
|
||||
return api.mode.sketch();
|
||||
|
|
@ -286,7 +295,26 @@ function space_init(data) {
|
|||
}
|
||||
},
|
||||
'keydown', evt => {
|
||||
let { shiftKey, metaKey, ctrlKey, code } = evt;
|
||||
let { shiftKey, metaKey, ctrlKey, code, target } = evt;
|
||||
if (target.nodeName === 'TEXTAREA') {
|
||||
if (code === 'Tab') {
|
||||
estop(evt);
|
||||
const start = target.selectionStart;
|
||||
const end = target.selectionEnd;
|
||||
const val = target.value;
|
||||
const upto = val.slice(0, start);
|
||||
const col = start - upto.lastIndexOf('\n') - 1;
|
||||
const tab = ' '.substring(col % 4);
|
||||
target.value = val.slice(0, start) + tab + val.slice(end);
|
||||
target.selectionStart = target.selectionEnd = start + tab.length;
|
||||
}
|
||||
if (code === 'Enter' && metaKey) {
|
||||
api.script.execute();
|
||||
} else {
|
||||
api.script.changed();
|
||||
}
|
||||
return;
|
||||
}
|
||||
let once = keyOnce[code];
|
||||
if (once) {
|
||||
delete keyOnce[code];
|
||||
|
|
@ -471,7 +499,7 @@ function space_init(data) {
|
|||
} else if (sketch_item) {
|
||||
sketch_item.toggle();
|
||||
} else {
|
||||
console.log('unknown selection');
|
||||
// console.log('unknown selection');
|
||||
}
|
||||
} else {
|
||||
// return objects upSelect can choose from
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
const service_worker = navigator.serviceWorker;
|
||||
|
||||
import { version } from '../moto/license.js';
|
||||
|
||||
async function start_service_worker() {
|
||||
function debug() {
|
||||
console.log(`[${version}]`, ...arguments);
|
||||
}
|
||||
|
||||
// install service worker
|
||||
debug('service worker registration');
|
||||
|
||||
try {
|
||||
// const reg = await navigator.serviceWorker.register("/src/moto/service.js?013", { scope: "/" });
|
||||
const reg = await navigator.serviceWorker.register("/code/service.js?"+version, { scope: "/" });
|
||||
if (reg.installing) {
|
||||
debug('service worker installing');
|
||||
} else if (reg.waiting) {
|
||||
debug('service worker waiting');
|
||||
} else if (reg.active) {
|
||||
debug('service worker active');
|
||||
} else {
|
||||
debug({ service_worker: reg });
|
||||
}
|
||||
|
||||
if (service_worker.controller) {
|
||||
service_worker.controller.postMessage('ctrl message');
|
||||
}
|
||||
} catch (err) {
|
||||
debug('service worker registration failed', err);
|
||||
}
|
||||
}
|
||||
|
||||
if (service_worker && self.enable_service) start_service_worker();
|
||||
|
|
@ -11,6 +11,7 @@ import { edges as meshEdges } from './edges.js';
|
|||
import { group as meshGroup } from './group.js';
|
||||
import { model as meshModel } from './model.js';
|
||||
import { sketch as meshSketch } from './sketch.js';
|
||||
import { script } from './script.js';
|
||||
import { newPolygon } from '../geo/polygon.js';
|
||||
import { THREE } from '../ext/three.js';
|
||||
import { $, $C, h } from '../moto/webui.js';
|
||||
|
|
@ -79,6 +80,7 @@ const selection = {
|
|||
selected = objects.map(o => o.models ? o.models : o).flat();
|
||||
tools = toolist || [];
|
||||
meshUtil.defer(selection.update);
|
||||
return api.selection;
|
||||
},
|
||||
|
||||
// @param group {MeshObject}
|
||||
|
|
@ -770,12 +772,55 @@ let add = {
|
|||
api.modal.bound.genvrt.focus();
|
||||
},
|
||||
|
||||
plane() {
|
||||
const [ x,y ] = [ ...arguments ];
|
||||
const box = new THREE.PlaneGeometry(1,1).toNonIndexed();
|
||||
const vert = box.attributes.position.array;
|
||||
const nmdl = new meshModel({ file: "plane", mesh: vert });
|
||||
nmdl._loft = function(plane) {
|
||||
function uv(v) {
|
||||
let m = new Set();
|
||||
let g = [...v].group(3).filter(a => {
|
||||
let k = a.map(v => v.round(5)).join('-');
|
||||
if (m.has(k)) return false;
|
||||
m.add(k); return true;
|
||||
});
|
||||
return [ g[0], g[1], g[3], g[2] ];
|
||||
};
|
||||
let s0 = uv(vert);
|
||||
let s1 = uv(plane.attributes.position.array);
|
||||
let v = [];
|
||||
for (let i=0; i<4; i++) {
|
||||
let p0 = [ s0[i], s0[(i+1)%4] ];
|
||||
let p1 = [ s1[i], s1[(i+1)%4] ];
|
||||
v.push(...p1[1]);
|
||||
v.push(...p0[1]);
|
||||
v.push(...p0[0]);
|
||||
v.push(...p1[0]);
|
||||
v.push(...p1[1]);
|
||||
v.push(...p0[0]);
|
||||
}
|
||||
let pa = [...plane.attributes.position.array];
|
||||
v.push(...pa.group(3).reverse().flat());
|
||||
v.push(...vert);
|
||||
const nmdl = new meshModel({ file: "loft", mesh: v.toFloat32() });
|
||||
const ngrp = api.group.new([ nmdl ]);
|
||||
selection.set([ nmdl ]);
|
||||
return ngrp;
|
||||
};
|
||||
const ngrp = api.group.new([ nmdl ]);
|
||||
ngrp.scale(x ?? 10, y ?? 10, 1).floor();
|
||||
selection.set([ nmdl ]);
|
||||
return ngrp;
|
||||
},
|
||||
|
||||
cube() {
|
||||
const [ x,y,z ] = [ ...arguments ].map(v => parseFloat(v) || undefined);
|
||||
const box = new THREE.BoxGeometry(1,1,1).toNonIndexed();
|
||||
const vert = box.attributes.position.array;
|
||||
const nmdl = new meshModel({ file: "box", mesh: vert });
|
||||
const ngrp = api.group.new([ nmdl ]);
|
||||
ngrp.scale(10, 10, 10).floor();
|
||||
ngrp.scale(x ?? 10, y ?? 10, z ?? 10).floor();
|
||||
selection.set([ nmdl ]);
|
||||
return ngrp;
|
||||
},
|
||||
|
|
@ -964,7 +1009,8 @@ let file = {
|
|||
]),
|
||||
h.div([
|
||||
h.button({ _: "download OBJ", onclick() { doit('obj') } }),
|
||||
h.button({ _: "download STL", onclick() { doit('stl') } })
|
||||
h.button({ _: "download STL", onclick() { doit('stl') } }),
|
||||
h.button({ _: "download STEP", onclick() { doit('step') } }),
|
||||
])
|
||||
]) ]
|
||||
});
|
||||
|
|
@ -984,7 +1030,7 @@ const tool = {
|
|||
return log('nothing to merge');
|
||||
}
|
||||
log(`merging ${models.length} models`).pin();
|
||||
worker.model_merge(models.map(m => {
|
||||
return worker.model_merge(models.map(m => {
|
||||
return { id: m.id, matrix: m.matrix }
|
||||
}))
|
||||
.then(data => {
|
||||
|
|
@ -995,6 +1041,7 @@ const tool = {
|
|||
api.selection.visible(false);
|
||||
api.selection.set([group]);
|
||||
log('merge complete').unpin();
|
||||
return group;
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -1074,7 +1121,7 @@ const tool = {
|
|||
return log('nothing to subtract');
|
||||
}
|
||||
log(`subtract ${models.length} models`).pin();
|
||||
worker.model_subtract(models.map(m => {
|
||||
return worker.model_subtract(models.map(m => {
|
||||
return { id: m.id, tool: m.tool() }
|
||||
}))
|
||||
.then(data => {
|
||||
|
|
@ -1084,6 +1131,7 @@ const tool = {
|
|||
})]).promote();
|
||||
api.selection.delete();
|
||||
api.selection.set([group]);
|
||||
return group;
|
||||
})
|
||||
.catch(error => {
|
||||
log(`subtract error: ${error}`);
|
||||
|
|
@ -1106,7 +1154,7 @@ const tool = {
|
|||
for (let m of models) {
|
||||
nm.push(m.duplicate({ select: dup_sel, shift: dup_shift }));
|
||||
}
|
||||
tool.regroup(nm);
|
||||
return tool.regroup(nm);
|
||||
},
|
||||
|
||||
mirror() {
|
||||
|
|
@ -1397,6 +1445,7 @@ const prefs = {
|
|||
dark: false,
|
||||
floor: false,
|
||||
grid: true,
|
||||
script: false,
|
||||
select: [],
|
||||
snap: 1,
|
||||
snapon: false,
|
||||
|
|
@ -1515,8 +1564,8 @@ const api = {
|
|||
},
|
||||
|
||||
clear() {
|
||||
for (let group of group.list()) {
|
||||
group.remove(group);
|
||||
for (let grp of group.list()) {
|
||||
group.remove(grp);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -1645,6 +1694,8 @@ const api = {
|
|||
|
||||
prefs,
|
||||
|
||||
script,
|
||||
|
||||
selection,
|
||||
|
||||
sketch,
|
||||
|
|
|
|||
|
|
@ -383,7 +383,7 @@ function menu_item(text, fn, short, id) {
|
|||
|
||||
// create html elements
|
||||
function ui_build() {
|
||||
let { bind, div, input, hr, label } = h;
|
||||
let { bind, button, div, input, hr, label, span, textarea } = h;
|
||||
let { file, selection, mode, tool, sketch, prefs, add } = api;
|
||||
let trash = FontAwesome.icon({ prefix: "fas", iconName: "trash" }).html[0];
|
||||
let eye_open = FontAwesome.icon({ prefix: "fas", iconName: "eye" }).html[0];
|
||||
|
|
@ -395,13 +395,14 @@ function ui_build() {
|
|||
div('File'),
|
||||
div({ class: "menu-items" }, [
|
||||
input({
|
||||
id: "import", type: "file", class: ["hide"], multiple: true,
|
||||
id: "import", type: "file", class: ["hide"], multiple: true, accept:".stl,.obj",
|
||||
onchange(evt) { broker.send.load_files(evt.target.files) }
|
||||
}),
|
||||
menu_item('Import', file.import, 'I'),
|
||||
menu_item('Export', file.export, 'X'),
|
||||
hr(),
|
||||
menu_item('Slicer', api.kirimoto),
|
||||
menu_item('Script', api.script.toggle),
|
||||
hr(),
|
||||
menu_item('Close', window.close),
|
||||
])
|
||||
|
|
@ -579,6 +580,15 @@ function ui_build() {
|
|||
div({ id: 'objecttools', class: "tools sketch-off" }),
|
||||
div({ id: 'logger', onmouseover() { log.show() } }),
|
||||
]),
|
||||
div({ id: 'script', class: "hide" }, [
|
||||
div({ id: 'script-header' }, [
|
||||
span("script"),
|
||||
div({ class: "grow" }),
|
||||
button({ _: "run", onclick: api.script.execute }),
|
||||
button({ _: "×", class: "close", onclick: api.script.hide })
|
||||
]),
|
||||
textarea({ id: 'script-editor', rows: 30, cols: 65 }),
|
||||
]),
|
||||
div({ id: 'grouplist' }),
|
||||
div({ id: 'selectlist' }),
|
||||
div({ id: 'download', class: "hide" })
|
||||
|
|
|
|||
|
|
@ -41,6 +41,11 @@ class MeshGroup extends meshObject {
|
|||
return box3;
|
||||
}
|
||||
|
||||
rename(name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
// @param model {MeshModel}
|
||||
add(model) {
|
||||
model.group = this;
|
||||
|
|
|
|||
|
|
@ -334,6 +334,7 @@ class MeshModel extends meshObject {
|
|||
rename(file) {
|
||||
this.file = file;
|
||||
this.sync();
|
||||
return this;
|
||||
}
|
||||
|
||||
// sync to worker and indexeddb for page restoration or worker ops
|
||||
|
|
|
|||
58
src/mesh/script.js
Normal file
58
src/mesh/script.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
import { api } from '../mesh/api.js';
|
||||
import { $, h, estop } from '../moto/webui.js';
|
||||
|
||||
export const script = {
|
||||
running: false,
|
||||
visible: false,
|
||||
changed() {
|
||||
localStorage.script = $('script-editor').value;
|
||||
},
|
||||
error(e) {
|
||||
api.modal.dialog({
|
||||
title: "script error",
|
||||
body: [ h.div({ id: "script-error", _: e.message || e.toString() }) ]
|
||||
});
|
||||
},
|
||||
hide() {
|
||||
$('script').classList.add('hide');
|
||||
api.prefs.save(api.prefs.map.space.script = script.visible = false);
|
||||
},
|
||||
show() {
|
||||
$('script').classList.remove('hide');
|
||||
$('script-editor').value = localStorage.script ?? '';
|
||||
api.prefs.save(api.prefs.map.space.script = script.visible = true);
|
||||
},
|
||||
toggle() {
|
||||
if (script.visible) {
|
||||
script.hide();
|
||||
} else {
|
||||
script.show();
|
||||
}
|
||||
},
|
||||
execute() {
|
||||
let cmd = localStorage.script || `console.log('no script')`;
|
||||
if (cmd) {
|
||||
if (script.running) {
|
||||
return script.error('script running');
|
||||
}
|
||||
cmd = [
|
||||
'(async () => {',
|
||||
cmd,
|
||||
'})()',
|
||||
'.catch(error => {',
|
||||
'api.script.error(error)',
|
||||
'})',
|
||||
'.finally(() => {',
|
||||
'api.script.running = false;',
|
||||
'});'
|
||||
].join('\n');
|
||||
try {
|
||||
eval(`{ \n ${cmd} \n }`);
|
||||
} catch (e) {
|
||||
script.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -16,6 +16,7 @@ import { polygons } from '../geo/polygons.js';
|
|||
import { CSG } from '../geo/csg.js';
|
||||
import { util } from './util.js';
|
||||
import { tool as meshTool } from './tool.js';
|
||||
import { meshToSTEPWithFaces } from '../load/step.js';
|
||||
|
||||
const { Triangle, Vector3, BufferGeometry, BufferAttribute, computeFaceNormal } = THREE;
|
||||
|
||||
|
|
@ -604,6 +605,17 @@ const file = {
|
|||
vtot += (vs.length / 3);
|
||||
}
|
||||
switch (format) {
|
||||
case "step":
|
||||
let varr = [];
|
||||
for (let rec of recs) {
|
||||
let tris = rec.varr
|
||||
.group(3)
|
||||
.map(r => { return { x:r[0], y:r[1], z:r[2] } })
|
||||
.group(3)
|
||||
.map(f => { return { v1: f[0], v2: f[1], v3: f[2] } });
|
||||
varr.push(...tris);
|
||||
}
|
||||
return meshToSTEPWithFaces(varr);
|
||||
case "obj":
|
||||
let p = 1;
|
||||
let obj = [header];
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
const terms = {
|
||||
COPYRIGHT: "Copyright (C) Stewart Allen <sa@grid.space> - All Rights Reserved",
|
||||
LICENSE: "See the license.md file included with the source distribution",
|
||||
VERSION: "4.3.1"
|
||||
VERSION: "4.5.0"
|
||||
};
|
||||
|
||||
export const beta = 0;
|
||||
|
|
|
|||
|
|
@ -98,7 +98,6 @@ let WIN = self.window || {},
|
|||
fontColor = '#333333',
|
||||
fontScale = 1.4, // computed relative to grid size
|
||||
rulerColor,
|
||||
axisColor,
|
||||
axesOn = true,
|
||||
volumeOn = true,
|
||||
viewControl,
|
||||
|
|
@ -388,7 +387,6 @@ function setPlatformColor(color) {
|
|||
function setFont(options) {
|
||||
if (options.color) fontColor = options.color;
|
||||
if (options.scale) fontScale = options.scale;
|
||||
if (options.axisColor) axisColor = options.axisColor;
|
||||
if (options.rulerColor) rulerColor = options.rulerColor;
|
||||
updateRulers();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue