diff --git a/app.js b/app.js index e746a517..8b96bcf4 100644 --- a/app.js +++ b/app.js @@ -21,10 +21,10 @@ const load = []; const synth = {}; const api = {}; -let level; let setupFn; let cacheDir; let startTime; +let oversion; let lastmod; let logger; let debug; @@ -43,25 +43,8 @@ function init(mod) { dir = mod.dir; log = mod.log; - if (mod.util.globdir) { - // when globdir available, look for shared kvstore - // and if not present, create and share in through env - if (mod.env._level) { - level = mod.env._level; - logger.log("using shared kvstore"); - } else { - level = require('level')(mod.util.globdir("kvstore"), {valueEncoding:"json"}); - mod.env._level = level; - logger.log("creating shared kvstore"); - } - } else { - logger.log("fallback to local kvstore"); - level = require('level')(mod.util.datadir("kvstore"), {valueEncoding:"json"}); - } - cacheDir = mod.util.datadir("cache"); - mod.on.reload(() => level.close()); mod.on.test((req) => { let cookie = cookieValue(req.headers.cookie, "version") || undefined; let vmatch = mod.meta.version || "*"; @@ -85,7 +68,6 @@ function init(mod) { mod.add(handleVersion); mod.add(prepath([ [ "/code/", handleCode ], - [ "/data/", handleData ], [ "/wasm/", handleWasm ] ])); mod.add(fixedmap("/api/", api)); @@ -144,7 +126,8 @@ function initModule(mod, file, dir) { require_fresh(file)({ api: api, adm: { - reload: prepareScripts + reload: prepareScripts, + setver: (ver) => { oversion = ver } }, const: { args: {}, @@ -166,6 +149,8 @@ function initModule(mod, file, dir) { time: time, guid: guid, mkdirs: util.mkdir, + isfile: util.isfile, + confdir: util.confdir, datadir: util.datadir, lastmod: lastmod, obj2string: obj2string, @@ -173,10 +158,6 @@ function initModule(mod, file, dir) { getCookieValue: cookieValue, logger: log.new }, - db: { - api: db, - level: level - }, inject: (code, file, options) => { if (!script[code]) { return logger.log(`inject missing target "${code}"`); @@ -239,11 +220,13 @@ const script = { kiri : [ "kiri", "ext/three", + "ext/three-bgu", "license", "ext/clip2", // work.test "ext/tween", "ext/fsave", "ext/earcut", // work.test + "ext/base64", "add/array", "add/three", "geo/base", @@ -267,11 +250,11 @@ const script = { "kiri/ui", "kiri/lang", "kiri/lang-en", - "kiri/fill", "kiri/db", "kiri/slice", "kiri/layers", "kiri/client", + "mode/fdm/fill", "mode/fdm/driver", "mode/fdm/client", "mode/sla/driver", @@ -297,6 +280,7 @@ const script = { "kiri", "ext/three", "ext/pngjs", + "ext/jszip", "license", "ext/clip2", "ext/earcut", @@ -315,11 +299,11 @@ const script = { "geo/polygons", "geo/gyroid", "moto/pack", - "kiri/fill", "kiri/slice", "kiri/slicer", "kiri/slicer2", "kiri/layers", + "mode/fdm/fill", "mode/fdm/driver", "mode/fdm/slice", "mode/fdm/prepare", @@ -404,40 +388,6 @@ const script = { // prevent caching of specified modules const cachever = {}; -const db = { - // -------- - key: arr => arr.join("/"), - // -------- - get: key => { - if (Array.isArray(key)) key = db.key(key); - return promise((resolve,reject) => { - level.get(key,(err,record) => { - resolve(record,err); - }); - }); - }, - - // -------- - put: (key, value) => { - if (Array.isArray(key)) key = db.key(key); - return promise((resolve,reject) => { - level.put(key,value,(err) => { - if (err) reject(err); - else resolve(); - }); - }); - }, - // -------- - del: key => { - return promise((resolve,reject) => { - level.del(key, (err) => { - if (err) reject(err); - else resolve(); - }); - }); - } -}; - function promise(resolve, reject) { return new Promise(resolve, reject); } @@ -471,11 +421,12 @@ function handleSetup(req, res, next) { } function handleVersion(req, res, next) { - if (req.app.path === "/kiri/" && req.url.indexOf(version) < 0) { + let vstr = oversion || version; + if (req.app.path === "/kiri/" && req.url.indexOf(vstr) < 0) { if (req.url.indexOf("?") > 0) { - return http.redirect(res, `${req.url},ver:${version}`); + return http.redirect(res, `${req.url},ver:${vstr}`); } else { - return http.redirect(res, `${req.url}?ver:${version}`); + return http.redirect(res, `${req.url}?ver:${vstr}`); } } else { next(); @@ -496,118 +447,6 @@ function handleOptions(req, res, next) { } } -function handleData(req, res, next) { - addCorsHeaders(req, res); - res.setHeader('Cache-Control', 'private, no-cache, max-age=0'); - - let tok = req.app.path.split('/'), - muid = req.headers['x-moto-ajax'], - space = tok[2] || null, - version = tok[3], - valid = space && space.length >= 4 && space.length <= 8; - - function genKey() { - while (true) { - let k = Math.round(Math.random() * 9999999999).toString(36); - if (k.length >= 4 && k.length <= 8) return k; - } - } - - function countKey(space) { - return db.key(['meta/counter',space]); - } - - function ownerKey(space) { - return db.key(['meta/owner',muid,'space',space]); - } - - function recordKey(space, version) { - return db.key(["meta/space",space,version]); - } - - // retrieve latest space data - if (valid && req.method === 'GET' && valid) { - function send(rec, version) { - if (rec) { - res.write(obj2string({space:space,ver:version,rec:rec})); - res.end(); - } else { - res.end(); - } - } - - function retrieve(version) { - return db.get(recordKey(space,version)) - .then(record => { - send(record || null, version); - }) - } - - if (version) { - retrieve(version) - } else { - db.get(countKey(space)).then(version => retrieve(version)); - } - } else if (valid && req.method === 'POST') { - let dbOwner = null, - dbVersion = null, - postBody = null, - spacein = space, - version = 0, - body = ''; - - function checkDone() { - if (!(dbVersion && postBody)) return; - // if not owner, assign new space id - if (dbVersion > 1) { - if (!dbOwner) { - space = genKey(); - version = 1; - logger.log({forked:space,from:spacein,by:muid}); - } - } - // log what we have - logger.log({ - space: space, - ver: dbVersion, - uid: muid, - size: postBody.length - }); - if (muid && muid.length > 0) { - level.put(recordKey(space, dbVersion), body); - level.put(ownerKey(space), {time: time(), ver: dbVersion}); - level.put(countKey(space), dbVersion); - } - res.end(obj2string({space: space, ver: dbVersion})); - } - - // accumulate post body - req.on('data', data => { body += data }); - req.on('end', () => { - postBody = body; - checkDone(); - }); - - // fetch owner and version information - db.get(ownerKey(space)) - .then(owner => { - dbOwner = owner; - return db.get(countKey(space)); - }) - .then(version => { - dbVersion = parseInt(version || "0") + 1; - checkDone(); - }); - - return; - - } else { - - next(); - - } -} - function handleWasm(req, res, next) { let [root, file] = req.app.path.split('/').slice(1); let ext = (file || '').split('.')[1]; @@ -775,10 +614,14 @@ function getCachedFile(file, fn) { let smod = lastmod(filePath), cmod = cached.mtime; - if (!smod) throw "missing source file"; - if (smod > cmod) cached = null; - - cached.lastcheck = now; + if (!smod) { + throw "missing source file"; + } + if (smod > cmod) { + cached = null; + } else { + cached.lastcheck = now; + } } } diff --git a/notes.md b/notes.md index cd55f420..05785fca 100644 --- a/notes.md +++ b/notes.md @@ -2,46 +2,49 @@ ## `C` cosmetic, `F` functional, `P` performance, `B` bug fix -* `B` saved profile should be restored / defaulted when switching devices? -* `B` normals possibly inverted on faces in 2D to 3D conversion -* `B` normalize left, bottom, right pop menu time/click and value persistence -* `B` origin bug (Onshape?) when switching modes in 2.5 and 2.6 +* `B` origin (and bed size) bug (Onshape?) when switching modes in 2.5 and 2.6 * `P` duplicate objects should share same slice data unless rotated or scaled * `P` allow selection to me decimated on demand (context menu?) * `P` improve decimation speed: omit point conversion or use wasm -* `P` client/worker interface normalization -* `P` explore widget vertex reloading / replacing +* `P` client/worker message interface normalization +* `P` explore widget vertex reloading / replacing (Onshape) (issue #48) * `F` SVG import to 3D part like images +* `F` show slider range values in workspace units (on hover?) +* `F` allow select of a range by typing in values in slices or workspace units +* `F` add % field type with conversion (like units) +* `F` complete G2/G3 arc output / input (85% now) +* `F` expose grouping feature +* `F` add svgnest-like arrange algorithm # FDM -* `B` large # linear points with linear infill (belt only?) -* `B` fix adaptive slicing with multi-extruder -* `B` fix supports with grouped parts +* `B` re-calibrate slicing progress weighting +* `B` infill densities are inaccurate +* `B` fix support projection/end with grouped parts * `B` multi-extruder rendering of raft fails to offset the rest of the print * `B` multi-extruder purge blocks fail to generate properly for rafts -* `F` slicing controls per user-defined z-region +* `F` support pillar should conform to part +* `F` support pillar should have solid top/bottom +* `F` more explicit line width control with ranges and min/max adaptive +* `F` test outlining solid projected areas (internally) * `F` control for size of purge block (with 0=disabled) -* `F` add separate fill speed control * `F` polishing and other non-planar work * `F` gradient infill https://www.youtube.com/watch?v=hq53gsYREHU&feature=emb_logo -* `F` first layer segment large flat areas for better fill reliability * `F` feather sharp tips by reducing extrusion in area of overlap +* `F` first layer segment large flat areas for better fill reliability * `F` enable purge blocks when quick layers are detected * `F` option to support interior bridges when 0% infill -* `F` trim support offset from layer below * `F` calculate filament use per extruder per print -* `F` first layer support speed should be same as shell speed * `F` apply finish speed to exposed top and underside flat areas * `F` expand internal supporting flats / solids before projection +* `P` extruder + filament max flow rate cap in planner * `P` revisit path routing / optimization * `P` implement infill clipping in wasm * `P` solid fill the tops of supports for down facing flats -* `P` sparse infill should follow polys between intersect points # FDM - SLA @@ -51,27 +54,33 @@ # FDM - BELT -* `B` fix supports for belt printing +* `B` auto bed resizing leaves the origin in the wrong place at first +* `B` ranges don't work with anchors +* `B` multiple parts with one angled against origin "floats" in preview +* `B` re-add progress calls for all work units + +* `F` slightly angle supports to lean into the Z of the part +* `F` arrange should just align down Z, not side to side. # CAM +* `B` tabs are not cut to exact height * `B` first rough step too far down in certain circumstances? * `B` trace open polys are not wound consistently * `B` path routing with tabs sometimes makes no sense * `B` clicking on a pop-op should pin it until clicked or [esc] * `B` need to force cut line at synthetic z bottom (midlines, etc) -* `B` non-clear-voids duplicates thru-holes -* `B` failure to go *up* when moving between parts? * `B` starting export during animation unleashes chaos -* `B` climb vs conventional not fully consistent after refactor -* `B` outside cutting direction in roughing mode inverted +* `F` limit cut depth to flute length of selected tool +* `F` add ease-down support to trace op +* `F` add linear clearing strategy +* `F` add adaptive clearing strategy +* `F` user-selectable origin (issue #28) * `F` intelligently turn circle hole pocket clear into spiral down * `F` trace follow hole that matches endmill should turn into a drill op * `F` add climb/conventional into each operation * `F` update analyzer to detect overhangs -* `F` add linear clearing strategy option for roughing / pocket -* `F` adaptive clearing in roughing mode * `F` extend acute roughing on inside polys to clear small voids * `F` option to use part / STL coordinate space to determine X,Y origin * `F` validate muti-part layout and spacing exceeds largest outside tool diameter @@ -88,14 +97,13 @@ * `F` can animation clear the mesh in areas where the cuts go through the stock? * `F` support lathe mode / A-axis / rotary * `F` gcode output option as zip for multiple or flip ops or tool change -* `F` ease-down should be engaged in all descending cutting operations -* `F` entirely user-selectable origin - +* `F` maintain several part orientations + op chains in a single profile * `P` common part pre-analyze to speed up 'slice' and improve shadow (overhangs) * `P` redo all path route / planning in prepare to account for terrain before camOut -* `P` detect render message backlog and pause or warn +* `P` detect render message backlog and pause or warn? * `P` allow faster z movements when contouring (not plunging) +* `P` common / faster shadow generator using vertices shared with ledges # Laser @@ -108,6 +116,7 @@ * http://wiki.imal.org/howto/cnc-milling-introduction-cutting-tools * http://www.twak.co.uk/2011/01/degeneracy-in-weighted-straight.html * http://photon.chrisgraf.de/ +* https://github.com/Jack000/SVGnest # More CNC @@ -118,3 +127,7 @@ * https://users.cs.jmu.edu/buchhofp/forensics/formats/pkzip.html * https://en.wikipedia.org/wiki/ZIP_(file_format) * https://stuk.github.io/jszip/ + +# Meta + +* free-space tetrahedron modeler with vertex mating diff --git a/package.json b/package.json index ff05ee4c..fb1230ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "grid-apps", - "version": "2.6.8D", + "version": "2.8.1D", "description": "grid.space 3d slice & modeling tools", "author": "Stewart Allen ", "license": "MIT", @@ -21,18 +21,17 @@ "slicer" ], "dependencies": { + "base64-js": "^1.5.1", "buffer-crc32": "^0.2.13", "compression": "^1.7.4", "connect": "^3.7.0", "earcut": "^2.2.2", "express-useragent": "^1.0.13", - "level": "^6.0.1", - "leveldown": "^5.6.0", - "levelup": "^4.4.0", + "jszip": "^3.5.0", "moment": "^2.24.0", "pixi.js": "^4.8.9", "serve-static": "^1.14.1", - "three": "^0.122.0", + "three": "^0.126.0", "tween.js": "^0.14.0", "uglify-es": "3.3.9", "validator": "^8.2.0", diff --git a/readme.md b/readme.md index fbf1b802..c147fe0f 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://paypal.me/gridspace3d?locale.x=en_US) -Contributions in all forms (code, bug reports, community engagenment, cash money, etc) are warmly welcomed. They all go to the bottom line of making better apps. +Contributions in all forms (code, bug reports, community engagement, localization, etc) are warmly welcomed. [![Discord](https://img.shields.io/discord/688863523207774209)](https://discord.com/channels/688863523207774209/688863523211968535) ![GitHub](https://img.shields.io/github/license/GridSpace/grid-apps) @@ -19,6 +19,8 @@ Contributions in all forms (code, bug reports, community engagenment, cash money ![GitHub package.json version (branch)](https://img.shields.io/github/package-json/v/GridSpace/grid-apps/rel-2.2) ![GitHub package.json version (branch)](https://img.shields.io/github/package-json/v/GridSpace/grid-apps/rel-2.3) ![GitHub package.json version (branch)](https://img.shields.io/github/package-json/v/GridSpace/grid-apps/rel-2.4) +![GitHub package.json version (branch)](https://img.shields.io/github/package-json/v/GridSpace/grid-apps/rel-2.5) +![GitHub package.json version (branch)](https://img.shields.io/github/package-json/v/GridSpace/grid-apps/rel-2.6) ### Development Activity @@ -54,6 +56,21 @@ to start a local instance of the apps. then open [Kiri:Moto](http://localhost:8080/kiri) or [Meta:Moto](http://localhost:8080/meta) on your local host +Alternatively, if you are using a packaged version of npm that ships with +a Linux distribution, but still want to install in your home directory, you +can use + +``` +npm config set prefix ~/.local +``` + +If gs-app-server is not found, then perhaps ~/.local/bin is not in +your path. You can either add it to your path, or you can run: + +``` +~/local/bin/gs-app-server --debug +``` + ### Windows Developers this git repo requires symbolic link support. on Windows, this means you have to clone the repo in a command shell with Administrator privileges. @@ -68,8 +85,9 @@ web site. requires node.js 12+ -### Javascript Slicing API +### Javascript Slicing APIs -A script include that injects a web worker into the page that will asynchronously perform any of Kiri’s slicing and gcode generation functions. +A script include that injects a web worker into the page that will asynchronously perform any of Kiri’s slicing and gcode generation functions. And a frame messaging API for controlling Kiri:Moto inside an IFrame. -https://grid.space/kiri/engine.html +* https://grid.space/kiri/engine.html +* https://grid.space/kiri/frame.html diff --git a/src/add/array.js b/src/add/array.js index 2c48e292..43e887b1 100644 --- a/src/add/array.js +++ b/src/add/array.js @@ -162,6 +162,10 @@ }) }; + AP.uniq = function() { + return this.slice().sort().filter((x, i, a) => !i || x != a[i-1]); + }; + /** ****************************************************************** * String prototype helpers ******************************************************************* */ diff --git a/src/add/three.js b/src/add/three.js index 6049dfcb..97567201 100644 --- a/src/add/three.js +++ b/src/add/three.js @@ -5,9 +5,18 @@ (function() { var MP = THREE.Mesh.prototype, - GP = THREE.Geometry.prototype, BP = THREE.BufferGeometry.prototype; + THREE.computeFaceNormal = function(vA,vB,vC) { + const ab = new THREE.Vector3(); + const cb = new THREE.Vector3(); + cb.subVectors( vC, vB ); + ab.subVectors( vA, vB ); + cb.cross( ab ); + cb.normalize(); + return cb; + }; + MP.getBoundingBox = function(update) { return this.geometry.getBoundingBox(update); }; @@ -46,7 +55,7 @@ }; // center geometry on x,y,z coordinates (defaults to 0,0,0) - GP.center = function(x,y,z) { + BP.center = function(x,y,z) { var box = this.getBoundingBox(), mid = box.dim.clone().multiplyScalar(0.5), dif = mid.clone().add(box.min), @@ -69,14 +78,17 @@ }; // return cached or refreshed (when update = true) bounding box - GP.getBoundingBox = function(update) { - if (update || !this.boundingBox) this.computeBoundingBox(); + BP.getBoundingBox = function(update) { + if (update || !this.boundingBox) { + this.boundingBox = null; + this.computeBoundingBox(); + } this.boundingBox.dim = this.boundingBox.max.clone().sub(this.boundingBox.min); return this.boundingBox; }; // uniformly scale any mesh to a max x/y/z dim of 'unit' (defaults to 1) - GP.unitScale = function(unit) { + BP.unitScale = function(unit) { var bbox = this.getBoundingBox().clone(), scale = (unit || 1) / Math.max( bbox.max.x - bbox.min.x, @@ -89,20 +101,21 @@ return this; }; - BP.center = GP.center; - BP.getBoundingBox = GP.getBoundingBox; - BP.unitScale = GP.unitScale; - BP.fixNormals = function() { this.computeVertexNormals(); return this; }; - THREE.Geometry.fromVertices = function(vertices) { - var geometry = new THREE.BufferGeometry(); - geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3)); - return geometry; - }; + // may be faster than the equivalent matrix transform. to be tested +// BP.translate = function(x,y,z) { +// let off = [x,y,z], +// arr = this.attributes.position.array, +// len = arr.length; +// for (let i=0; i 0) { from += per * dir; out.push(from); @@ -316,6 +316,94 @@ return (d2y * d1x) - (d2x * d1y); } + /** + * return circle center given three points + * from https://math.stackexchange.com/questions/1076177/3d-coordinates-of-circle-center-given-three-point-on-the-circle + */ + function center3d(a,b,c) { + let ax = a.x, + ay = a.y, + az = a.z || 0, + bx = b.x, + by = b.y, + bz = b.z || 0, + cx = c.x, + cy = c.y, + cz = c.z || 0, + Cx = bx-ax, + Cy = by-ay, + Cz = bz-az, + Bx = cx-ax, + By = cy-ay, + Bz = cz-az, + B2 = (ax*ax)-(cx*cx)+(ay*ay)-(cy*cy)+(az*az)-(cz*cz), + C2 = (ax*ax)-(bx*bx)+(ay*ay)-(by*by)+(az*az)-(bz*bz), + CByz = Cy*Bz-Cz*By, + CBxz = Cx*Bz-Cz*Bx, + CBxy = Cx*By-Cy*Bx, + ZZ1 = -(Bz-Cz*Bx/Cx)/(By-Cy*Bx/Cx), + Z01 = -(B2-Bx/Cx*C2)/(2*(By-Cy*Bx/Cx)), + ZZ2 = -(ZZ1*Cy+Cz)/Cx, + Z02 = -(2*Z01*Cy+C2)/(2*Cx), + dz = -((Z02-ax)*CByz-(Z01-ay)*CBxz-az*CBxy)/(ZZ2*CByz-ZZ1*CBxz+CBxy), + dx = ZZ2*dz + Z02, + dy = ZZ1*dz + Z01; + + return {x:dx, y:dy, z:dz}; + } + + /** + * return circle center given three points + * from https://stackoverflow.com/questions/4103405/what-is-the-algorithm-for-finding-the-center-of-a-circle-from-three-points + */ + function center2d(A,B,C,rad) { + // let yDelta_a = B.y - A.y, + // xDelta_a = B.x - A.x, + // yDelta_b = C.y - B.y, + // xDelta_b = C.x - B.x, + // aSlope = yDelta_a/xDelta_a, + // bSlope = yDelta_b/xDelta_b, + // center = {x:0, y:0, z:0}; + // center.x = (aSlope*bSlope*(A.y - C.y) + bSlope*(A.x + B.x) - aSlope*(B.x+C.x) )/(2* (bSlope-aSlope) ); + // center.y = -1*(center.x - (A.x+B.x)/2)/aSlope + (A.y+B.y)/2; + let center = center3d(A,B,C); + if (rad) { + let dx = center.x - A.x; + let dy = center.y - A.y; + center.r = Math.sqrt(dx*dx + dy*dy) + // if (isNaN(center.r)) console.log('NaN',{dx,dy,A,B,C,center,xDelta_a,xDelta_b,yDelta_a,yDelta_b,aSlope,bSlope}); + } + return center; + } + + /** + * return two possible circle centers given two points and a radius + * https://stackoverflow.com/questions/36211171/finding-center-of-a-circle-given-two-points-and-radius + */ + function center2pr(p1, p2, r) { + let x1 = p1.x, + x2 = p2.x, + y1 = p1.y, + y2 = p2.y, + q = Math.sqrt(Math.pow((x2-x1),2) + Math.pow((y2-y1),2)), + y3 = (y1+y2)/2, + x3 = (x1+x2)/2, + basex = Math.sqrt(Math.pow(r,2)-Math.pow((q/2),2))*(y1-y2)/q, //calculate once + basey = Math.sqrt(Math.pow(r,2)-Math.pow((q/2),2))*(x2-x1)/q, //calculate once + centerx1 = x3 + basex, //center x of circle 1 + centery1 = y3 + basey, //center y of circle 1 + centerx2 = x3 - basex, //center x of circle 2 + centery2 = y3 - basey; //center y of circle 2 + return [{x:centerx1, y:centery1}, {x:centerx2, y:centery2}]; + } + + function thetaDiff(n1, n2, sign) { + let diff = n2 - n1; + while (diff < -Math.PI) diff += Math.PI * 2; + while (diff > Math.PI) diff -= Math.PI * 2; + return sign ? diff : Math.abs(diff); + } + /** ****************************************************************** * Connect to base ******************************************************************* */ @@ -399,6 +487,10 @@ time, round, area2, + center2d, + center3d, + center2pr, + thetaDiff, distSq : dist2, distSqv2 : dist2v2, inRange, diff --git a/src/geo/polygon.js b/src/geo/polygon.js index 402ad5bd..006896b4 100644 --- a/src/geo/polygon.js +++ b/src/geo/polygon.js @@ -260,6 +260,198 @@ return Math.abs(diff); } + // generate center crossing point cloud + PRO.centers = function(step, z, min, max, opt = {}) { + let cloud = [], + bounds = this.bounds, + lines = opt.lines || false, + stepoff = step / 2, + set = [this.points]; + + if (this.inner) { + for (let inner of this.inner) { + set.push(inner.points); + } + } + + for (let y of UTIL.lerp(bounds.miny + stepoff, bounds.maxy - stepoff, step, true)) { + let ints = []; + for (let points of set) { + let length = points.length; + for (let i=0; i y) || + (p1.y > y && p2.y <= y) + ) ints.push([p1,p2]); + } + } + let cntr = []; + if (ints.length && ints.length % 2 === 0) { + for (let int of ints) { + let [p1, p2] = int; + if (p2.y < p1.y) { + let tp = p1; + p1 = p2; + p2 = tp; + } + let minx = Math.min(p1.x, p2.x); + let maxx = Math.max(p1.x, p2.x); + let miny = Math.min(p1.y, p2.y); + let maxy = Math.max(p1.y, p2.y); + let dx = p2.x - p1.x; + let dy = maxy - miny; + let pct = (y - miny) / dy; + let xpo = p1.x + pct * dx; + cntr.push(xpo); + } + } + cntr.sort((a, b) => { + return b - a; + }); + let lp, eo = 0; + for (let x of cntr) { + let p = BASE.newPoint(x, y, z); + if (eo++ % 2) { + let d = lp.distTo2D(p); + if (d >= min && d <= max) { + if (lines) { + cloud.push(lp); + cloud.push(p); + } else { + cloud.push(BASE.newPoint( + (lp.x + p.x) / 2, y, z + )); + } + } + } else { + lp = p; + } + } + } + + for (let x of UTIL.lerp(bounds.minx + stepoff, bounds.maxx - stepoff, step, true)) { + let ints = []; + for (let points of set) { + let length = points.length; + for (let i=0; i x) || + (p1.x > x && p2.x <= x) + ) ints.push([p1,p2]); + } + } + let cntr = []; + if (ints.length && ints.length % 2 === 0) { + for (let int of ints) { + let [p1, p2] = int; + if (p2.x < p1.x) { + let tp = p1; + p1 = p2; + p2 = tp; + } + let minx = Math.min(p1.x, p2.x); + let maxx = Math.max(p1.x, p2.x); + let miny = Math.min(p1.y, p2.y); + let maxy = Math.max(p1.y, p2.y); + let dx = maxx - minx; + let dy = p2.y - p1.y; + let pct = (x - minx) / dx; + let ypo = p1.y + pct * dy; + cntr.push(ypo); + } + } + cntr.sort((a, b) => { + return b - a; + }); + let lp, eo = 0; + for (let y of cntr) { + let p = BASE.newPoint(x, y, z); + if (eo++ % 2) { + let d = lp.distTo2D(p); + if (d >= min && d <= max) { + if (lines) { + cloud.push(lp); + cloud.push(p); + } else { + cloud.push(BASE.newPoint( + x, (lp.y + p.y) / 2, z + )); + } + } + } else { + lp = p; + } + } + } + + if (lines) { + return cloud; + } + + let mindist = opt.mindist || step * 1.5; + + function build(poly) { + let lastp = poly.last(); + let minp; + let mind = Infinity; + for (let point of cloud) { + let dist = point.distTo2D(lastp); + if (dist < mindist && dist < mind) { + mind = dist; + minp = point; + } + } + if (minp) { + cloud = cloud.filter(p => p !== minp); + poly.push(minp); + return true; + } + return false; + } + + // join points into polys + let polys = []; + let poly = []; + while (cloud.length) { + if (poly.length === 0) { + poly = [ cloud.shift() ]; + polys.push(poly); + continue; + } + if (build(poly)) { + continue; + } + if (!poly.flip) { + poly.reverse(); + poly.flip = true; + continue; + } + if (poly.length) { + poly = []; + } else { + throw "whoop there it is"; + } + } + + return polys + .filter(poly => poly.length > 1) + .map(poly => { + let np = BASE.newPolygon().setOpen(); + for (let p of poly) { + np.push(p); + } + if (np.last().distTo2D(np.first()) <= max) { + np.setClosed(); + } + np = np.clean(); + return np; + }); + }; + PRO.debur = function(dist) { if (this.len < 2) { return null; @@ -666,6 +858,16 @@ return this; }; + PRO.addObj = function(obj) { + if (Array.isArray(obj)) { + for (let o of obj) { + this.addObj(o); + } + return this; + } + return this.add(obj.x, obj.y, obj.z); + } + /** * append array of points to polygon and return polygon * @@ -1201,9 +1403,11 @@ return this; }; - function fromClipperPath(path,z) { + function fromClipperPath(path, z) { let poly = newPolygon(), i = 0, l = path.length; - while (i < l) poly.push(newPoint(null,null,z,null,path[i++])); + while (i < l) { + poly.push(newPoint(null,null,z,null,path[i++])); + } return poly; }; @@ -1223,6 +1427,31 @@ poly.parent = parent || this.parent; poly.area2 = this.area2; poly.open = this.open; + if (this.open) { + // when open, ensure first point on new poly matches old + let start = this.points[0]; + let points = poly.points; + let length = points.length; + let mi, min = Infinity; + for (let i=0; i 0 means first point didn't match + if (mi) { + let nupoints = []; + for (let i=mi; i 0 && ref && ref.length) { + let off = offset(ref, -dist, {z}); + let mid = offset(off, dist / 2, {z}); + let cmp = offset(off, dist, {z}); + let gap = []; + let aref = ref.map(p => p.areaDeep()).reduce((a,p) => a +p); + let cref = cmp.length ? cmp.map(p => p.areaDeep()).reduce((a,p) => a + p) : 0; + // threshold subtraction to area deltas > 0.1 % to filter out false + // positives where inset/outset are identical floating point error + if (Math.abs(aref - cref) > 1 - (Math.abs(aref / cref) / 1000)) { + subtract(ref, cmp, gap, null, z); + } + layers.push({idx: total-count, off, mid, gap}); + // fixup depth cues + for (let m of mid) { + m.depth = depth++; + if (m.inner) { + for (let mi of m.inner) { + mi.depth = m.depth; + } + } + } + ref = off; + } + return layers; + } + /** * todo use clipper opne poly clipping? * diff --git a/src/geo/render.js b/src/geo/render.js index 866fcc1b..becf435a 100644 --- a/src/geo/render.js +++ b/src/geo/render.js @@ -30,9 +30,10 @@ */ function wireframe(group, points, color) { if (points.length % 3 != 0) throw "invalid line : "+points.length; - let lines = new THREE.Geometry(), + let lines = new THREE.BufferGeometry(), hash = {}, - added = 0; + added = 0, + vertices = []; for (let i = 0; i < points.length; i += 3) { let p1 = points[i], p2 = points[i + 1], @@ -41,22 +42,28 @@ l2 = newOrderedLine(p2, p3), l3 = newOrderedLine(p3, p1); if (!hash[l1.key]) { - lines.vertices.push(new THREE.Vector3(p1.x, p1.y, p1.z)); - lines.vertices.push(new THREE.Vector3(p2.x, p2.y, p2.z)); + vertices.appendAll([ + p1.x, p1.y, p1.z, + p2.x, p2.y, p2.z + ]); hash[l1.key] = ++added; } if (!hash[l2.key]) { - lines.vertices.push(new THREE.Vector3(p2.x, p2.y, p2.z)); - lines.vertices.push(new THREE.Vector3(p3.x, p3.y, p3.z)); + vertices.appendAll([ + p2.x, p2.y, p2.z, + p3.x, p3.y, p3.z + ]); hash[l2.key] = ++added; } if (!hash[l3.key]) { - lines.vertices.push(new THREE.Vector3(p3.x, p3.y, p3.z)); - lines.vertices.push(new THREE.Vector3(p1.x, p1.y, p1.z)); + vertices.appendAll([ + p3.x, p3.y, p3.z, + p1.x, p1.y, p1.z + ]); hash[l3.key] = ++added; } } - lines.verticesNeedUpdate = true; + lines.setAttribute('position', new THREE.BufferAttribute( vertices.toFloat32(), 3 ) ); let mesh = new THREE.LineSegments(lines, getMaterial(color)); group.add(mesh); return mesh; diff --git a/src/kiri.js b/src/kiri.js index 03574794..3d39ec4a 100644 --- a/src/kiri.js +++ b/src/kiri.js @@ -6,7 +6,7 @@ if (!self.kiri) { self.kiri = { - beta: 2608, + beta: 2801, driver: {}, // driver modules loader: [] // module loading: array of functions }; diff --git a/src/kiri/client.js b/src/kiri/client.js index 635798c8..6b475fbd 100644 --- a/src/kiri/client.js +++ b/src/kiri/client.js @@ -14,8 +14,10 @@ let KIRI = self.kiri = self.kiri || {}, seqid = 1, syncd = {}, running = {}, - slicing = {}, - worker = null; + worker = null, + restarting = false + // occ = new Worker("/kiri/ext/occ-worker.js", {type:"module"}), + ; /** * @param {Function} fn name of function in KIRI.worker @@ -26,8 +28,11 @@ let KIRI = self.kiri = self.kiri || {}, function send(fn, data, onreply, zerocopy) { let seq = seqid++; - running[seq] = { fn:onreply }; - // console.log('send', data); + if (onreply) { + // track it only when we expect and can handle a reply + running[seq] = { fn:onreply }; + } + // console.log('send', {fn, data, onreply}); try { worker.postMessage({ @@ -55,25 +60,35 @@ KIRI.work = { } }, - isSlicing: function() { + isBusy: function() { let current = 0; - for (let key in slicing) { - current++; + + for (let rec of Object.values(running)) { + if (rec.fn) current++; } return current > 0; }, restart: function() { + // prevent re-entry from cancel callback + if (restarting) { + return; + } + if (worker) { worker.terminate(); } - for (let key in slicing) { - slicing[key]({error: "cancelled slicing"}); + restarting = true; + + for (let key in running) { + let rec = running[key]; + if (rec.fn) { + rec.fn({error: "cancelled operation"}); + } } syncd = {}; - slicing = {}; running = {}; worker = KIRI.work.newWorker(); @@ -81,7 +96,7 @@ KIRI.work = { let now = time(), reply = e.data, record = running[reply.seq], - onreply = record.fn; + onreply = record ? record.fn : undefined; // console.log('recv', reply.data) if (reply.done) { @@ -91,8 +106,14 @@ KIRI.work = { // calculate and replace recv time reply.time_recv = now - reply.time_recv; - onreply(reply.data, reply); + if (onreply) { + onreply(reply.data, reply); + } else { + console.log({unexpected_reply: reply}); + } }; + + restarting = false; }, decimate: function(vertices, options, callback) { @@ -105,35 +126,36 @@ KIRI.work = { }, config: function(obj) { - send("config", obj, function(reply) { - // console.log({config:reply}); - }); + send("config", obj, function(reply) { }); }, - clear: function(widget) { - send("clear", widget ? {id:widget.id} : {}, function(reply) { - syncd = {}; - }); + clear: function() { + syncd = {}; + send("clear", {}, function(reply) { }); }, snap: function(data) { - send("snap", data, function(reply) { - // console.log({snap:reply}) - }); + send("snap", data, function(reply) { }); }, // widget sync - sync: function(widget) { - let widgets = widget ? [ widget ] : KIRI.api.widgets.all(); + sync: function(widgets) { + if (!widgets) { + widgets = KIRI.api.widgets.all(); + } + // send list of currently valid widgets + send("sync", { valid: widgets.map(w => w.id) }, () => {}); + // sync any widget that has changed widgets.forEach(widget => { if (widget.modified || !syncd[widget.id]) { syncd[widget.id] = true; let vertices = widget.getGeoVertices().buffer.slice(0); send("sync", { id: widget.id, + group: widget.group.id, + track: widget.track, vertices: vertices, position: widget.mesh.position, - tracking: widget.track }, done => { widget.modified = false; }, [vertices]); @@ -141,18 +163,42 @@ KIRI.work = { }); }, - slice: function(settings, widget, callback) { - CLIENT.sync(widget); - slicing[widget.id] = callback; + rotate: function(settings, callback) { + send("rotate", { settings }, reply => { + if (reply.group) { + for (let widget of KIRI.Widget.Groups.forid(reply.group)) { + widget.belt = reply.belt; + } + } else if (callback) { + callback(); + } + }); + }, + unrotate: function(settings, callback) { + send("unrotate", { settings }, reply => { + if (reply.group) { + for (let widget of KIRI.Widget.Groups.forid(reply.group)) { + widget.rotinfo = reply.rotinfo; + } + } else if (callback) { + callback(); + } + }); + }, + + slice: function(settings, widget, callback) { send("slice", { id: widget.id, settings: settings }, function(reply) { - if (reply.done || reply.error) { - delete slicing[widget.id]; - } callback(reply); + if (reply.done || reply.error) { + // in belt mode, slicing modifies a widget and requires re-sync + if (settings.device.bedBelt) { + widget.modified = true; + } + } }); }, @@ -164,6 +210,9 @@ KIRI.work = { if (reply.done) { done(reply.output, reply.maxSpeed); } + if (reply.error) { + done(reply); + } }); }, @@ -175,6 +224,9 @@ KIRI.work = { if (reply.done) { ondone(reply.output); } + if (reply.error) { + ondone(null, reply.error); + } }); }, @@ -204,7 +256,7 @@ KIRI.work = { } send("parse", args, function(reply) { if (reply.progress) { - progress(reply); + progress(reply.progress); } if (reply.parsed) { done(KIRI.codec.decode(reply.parsed), reply.maxSpeed); @@ -221,6 +273,16 @@ KIRI.work = { output(reply.done); } }, [ data.png ]); + }, + + zip: function(files, progress, output) { + send("zip", {files}, reply => { + if (reply.percent !== undefined) { + progress(reply); + } else { + output(reply); + } + }); } }; diff --git a/src/kiri/codec.js b/src/kiri/codec.js index af2bd088..1fbfff78 100644 --- a/src/kiri/codec.js +++ b/src/kiri/codec.js @@ -11,6 +11,7 @@ const zeroOut = true; const codec = KIRI.codec = { + undef: undefined, encode: encode, decode: decode, registerDecoder: registerDecoder, @@ -154,7 +155,7 @@ polys: encode(layer.polys, state), lines: encodePointArray(layer.lines, state), faces: codec.allocFloat32Array(layer.faces), - cface: layer.cface, + cface: layer.cface || codec.undef, color: layer.color, paths: layer.paths.map(lp => { const pe = { @@ -167,13 +168,13 @@ } return pe; }), - cpath: layer.cpath, + cpath: layer.cpath || codec.undef, off: layer.off }; if (zeroOut && state.zeros && e.faces.length) { state.zeros.push(e.faces.buffer); } - // console.log('-->',e); + // console.log('-->',layer,e); return e; }) }; diff --git a/src/kiri/conf.js b/src/kiri/conf.js index 0bef439a..1ea366af 100644 --- a/src/kiri/conf.js +++ b/src/kiri/conf.js @@ -77,6 +77,7 @@ extFilament: device.filamentSize, extNozzle: device.nozzleSize, extSelect: ["T0"], + extDeselect: [], extOffsetX: 0, extOffsetY: 0 }]; @@ -143,6 +144,7 @@ if (rec.offset_x) e.extOffsetX = rec.offset_x; if (rec.offset_y) e.extOffsetY = rec.offset_y; if (rec.select) e.extSelect = rec.select; + if (rec.deselect) e.extDeselect = rec.deselect; device.extruders.push(e); }); } else { @@ -259,6 +261,7 @@ bedHeight: 2.5, bedRound: false, bedBelt: false, + fwRetract: false, originCenter: false, maxHeight: 150, gcodePre: [], @@ -273,6 +276,7 @@ extFilament: 1.75, extNozzle: 0.4, extSelect: ["T0"], + extDeselect: [], extOffsetX: 0, extOffsetY: 0 }] @@ -285,12 +289,14 @@ sliceShellOrder: "in-out", sliceLayerStart: "last", sliceFillAngle: 45, + sliceFillWidth: 1, sliceFillOverlap: 0.3, sliceFillSparse: 0.5, + sliceFillRate: 0, sliceFillType: "hex", sliceSupportDensity: 0.25, sliceSupportOffset: 1.0, - sliceSupportGap: 0.75, + sliceSupportGap: 1, sliceSupportSize: 5, sliceSupportArea: 0.1, sliceSupportExtra: 0, @@ -311,10 +317,11 @@ firstLayerBedTemp: 0, firstLayerBrim: 0, firstLayerBrimTrig: 0, + firstLayerBeltLead: 3, + firstLayerFanSpeed: 0, outputRaft: false, outputRaftSpacing: 0.2, outputTemp: 200, - outputFanMax: 255, outputBedTemp: 60, outputFeedrate: 50, outputFinishrate: 50, @@ -322,27 +329,30 @@ 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, outputShortPoly: 100.0, outputMinSpeed: 10.0, outputCoastDist: 0, - outputWipeDistance: 0, outputLayerRetract: false, outputOriginCenter: true, outputLoopLayers: "", + outputPeelGuard: 0, outputInvertX: false, outputInvertY: false, detectThinWalls: false, sliceMinHeight: 0, sliceAdaptive: false, zHopDistance: 0.2, + arcTolerance: 0, antiBacklash: 1, - gcodePause: "" + gcodePause: "", + ranges: [] } }, sla:{ @@ -458,6 +468,7 @@ camTraceDown: 0, camTraceSpeed: 250, camTracePlunge: 200, + camTraceLines: false, camDrillTool: 1000, camDrillSpindle: 1000, camDrillDownSpeed: 250, @@ -630,6 +641,7 @@ view: null, dark: false, decals: true, + danger: false, zoomSpeed: 1.0, lineType: "path", autoSave: true, @@ -639,6 +651,7 @@ showSpeeds: true, freeLayout: true, autoLayout: true, + spaceLayout: 1, units: "mm", exportOcto: false, exportGhost: false, @@ -646,7 +659,16 @@ exportPreview: false, decimate: true, detail: "good", - animesh: "200" + animesh: "200", + healMesh: false, + ortho: false + }, + // label state preferences + labels: { + 'FDM-3-engage': false, + 'FDM-3-retract': false, + 'FDM-3-arrows': false, + 'FDM-3-move': false }, // for passing temporary slice hints (topo currently) synth: {}, diff --git a/src/kiri/engine.js b/src/kiri/engine.js index 39efd654..3b4c0a36 100644 --- a/src/kiri/engine.js +++ b/src/kiri/engine.js @@ -7,6 +7,14 @@ let KIRI = self.kiri = self.kiri || {}; KIRI.newEngine = ()=> { return new Engine ()}; +if (!KIRI.api) { + KIRI.api = { + event: { + emit: () => {} + } + }; +} + class Engine { constructor() { this.widget = KIRI.newWidget(); @@ -27,7 +35,7 @@ class Engine { try { new moto.STL().load(url, vertices => { this.listener({loaded: url, vertices}); - this.widget.loadVertices(vertices) + this.widget.loadVertices(vertices).center(); accept(this); }); } catch (error) { @@ -39,11 +47,10 @@ class Engine { parse(data) { return new Promise((accept, reject) => { try { - new moto.STL().parse(data, vertices => { - this.listener({parsed: data, vertices}); - this.widget.loadVertices(vertices) - accept(this); - }); + let vertices = new moto.STL().parse(data); + this.listener({parsed: data, vertices}); + this.widget.loadVertices(vertices).center(); + accept(this); } catch (error) { reject(error); } @@ -97,6 +104,8 @@ class Engine { slice() { return new Promise((accept, reject) => { + KIRI.client.clear(); + KIRI.client.sync([ this.widget ]); KIRI.client.slice(this.settings, this.widget, msg => { this.listener({slice:msg}); if (msg.error) { diff --git a/src/kiri/export.js b/src/kiri/export.js index 86a57025..67783e97 100644 --- a/src/kiri/export.js +++ b/src/kiri/export.js @@ -31,11 +31,15 @@ } function callExport(callback, mode) { + let alert = API.feature.work_alerts ? API.show.alert("Exporting") : null; const gcode = []; KIRI.client.export(API.conf.get(), (line) => { gcode.push(line); - }, (output) => { - if (callback) { + }, (output, error) => { + API.hide.alert(alert); + if (error) { + API.show.alert(error, 5); + } else if (callback) { callback(gcode.join('\r\n'), output); } else { exportGCodeDialog(gcode.join('\r\n'), output); @@ -46,8 +50,12 @@ function callExportLaser(options) { KIRI.client.export(API.conf.get(), (line) => { console.log({unexpected_line: line}); - }, (output) => { - exportLaserDialog(output); + }, (output, error) => { + if (error) { + API.show.alert(error, 5); + } else { + exportLaserDialog(output); + } }); } @@ -58,9 +66,13 @@ if (line.data) { preview.push(line.data); } - }, (output) => { + }, (output, error) => { API.show.progress(0); - KIRI.driver.SLA.printDownload(preview, output, API); + if (error) { + API.show.alert(error, 5); + } else { + KIRI.driver.SLA.printDownload(preview, output, API); + } }); } @@ -112,7 +124,14 @@ let settings = API.conf.get(), MODE = API.mode.get_id(), - pre = (MODE === MODES.CAM ? "cnc-" : "print-") + (printSeq.toString().padStart(3,"0")), + names = API.widgets.all() + .map(w => w.meta ? w.meta.file : undefined) + .filter(v => v) + .map(v => v.replace(/ /g,'-')) + .map(v => v.substring(0,20)) + .map(v => v.split('.')[0]), + name = names[0] || (MODE === MODES.CAM ? "cnc" : "print"), + pre = `${name}-${(printSeq.toString().padStart(3,"0"))}`, filename = pre,// + (new Date().getTime().toString(36)), fileext = settings.device.gcodeFExt || "gcode", codeproc = settings.device.gcodeProc, @@ -489,8 +508,8 @@ $('admin-gridlocal').onclick = admin_gridlocal; } catch (e) { console.log(e) } - // preview of the generated GCODE - if (preview && gcode) $('code-preview-textarea').value = gcode; + // preview of the generated GCODE (first 64k max) + if (preview && gcode) $('code-preview-textarea').value = gcode.substring(0,65535); // show dialog API.modal.show('print'); diff --git a/src/kiri/init.js b/src/kiri/init.js index b954f2cf..243baed3 100644 --- a/src/kiri/init.js +++ b/src/kiri/init.js @@ -46,7 +46,6 @@ platform = API.platform, selection = API.selection, proto = location.protocol, - root = "grid.space", ver = Date.now().toString(36); let currentDevice = null, @@ -58,7 +57,8 @@ editTools = null, maxTool = 0, fpsTimer = null, - platformColor; + platformColor, + contextInt; // extend KIRI API with local functions API.show.devices = showDevices; @@ -138,7 +138,9 @@ function booleanSave() { let control = settings().controller; let isDark = control.dark; + let doAlert = UI.ortho.checked !== control.ortho; control.decals = UI.decals.checked; + control.danger = UI.danger.checked; control.showOrigin = UI.showOrigin.checked; control.showRulers = UI.showRulers.checked; control.autoLayout = UI.autoLayout.checked; @@ -151,8 +153,10 @@ control.exportLocal = UI.exportLocal.checked; control.exportPreview = UI.exportPreview.checked; control.decimate = UI.decimate.checked; + control.healMesh = UI.healMesh.checked; + control.ortho = UI.ortho.checked; SPACE.view.setZoom(control.reverseZoom, control.zoomSpeed); - platform.layout(); + // platform.layout(); API.conf.save(); API.platform.update_size(); API.catalog.setOptions({ @@ -166,6 +170,9 @@ clearDeviceTexture(); } API.event.emit('boolean.update'); + if (doAlert) { + API.show.alert("change requires page refresh"); + } } function updateFPS() { @@ -191,7 +198,7 @@ // } API.conf.update(); DOC.activeElement.blur(); - // API.event.emit("boolean.click"); + API.event.emit("boolean.click"); } function onButtonClick(ev) { @@ -240,7 +247,7 @@ // hide all dialogs API.dialog.hide(); // cancel slicing - if (KIRI.work.isSlicing()) KIRI.work.restart(); + API.function.cancel(); // kill any poppers in compact mode UC.hidePoppers(); // and send an event (used by FDM client) @@ -321,16 +328,19 @@ } function keyHandler(evt) { - let handled = true, - current = settings(), - style, sel, i, m, bb, - ncc = evt.charCode - 48; + let handled = true; if (API.modal.visible() || inputHasFocus()) { return false; } if (API.feature.on_key) { if (API.feature.on_key({key:evt})) return; } + if (evt.ctrlKey) { + switch (evt.key) { + case 'g': return API.group.merge(); + case 'u': return API.group.split(); + } + } switch (evt.charCode) { case cca('`'): API.show.slices(0); break; case cca('0'): API.show.slices(API.var.layer_max); break; @@ -357,14 +367,6 @@ case cca('i'): // file import API.event.import(); break; - case cca('U'): // full settings url - storeSettingsToServer(true); - break; - case cca('u'): // full settings url - UC.prompt("settings id to load", "").then(id => { - if (id) loadSettingsFromServer(id); - }); - break; case cca('S'): // slice case cca('s'): // slice if (evt.shiftKey) { @@ -414,6 +416,9 @@ API.show.local(); break; case cca('v'): // toggle single slice view mode + if (API.view.get() === VIEWS.ARRANGE) { + API.space.set_focus(API.selection.widgets()); + } if (API.var.layer_hi == API.var.layer_lo) { API.var.layer_lo = 0; } else { @@ -422,52 +427,75 @@ API.show.slices(); break; case cca('d'): // duplicate object - sel = API.selection.meshes(); - platform.deselect(); - for (i=0; i { - tab.id = Date.now() + i - }); - } - KIRI.driver.CAM.restoreTabs([nw]); - } + duplicateSelection(); break; case cca('m'): // mirror object - API.selection.for_widgets(function(widget) { - widget.mirror(); - }); - SPACE.update(); + mirrorSelection(); break; case cca('R'): // toggle slice render mode renderMode++; API.function.slice(); break; - case cca('a'): // auto arrange items on platform - platform.layout(); + case cca('a'): + if (API.view.get() === VIEWS.ARRANGE) { + // auto arrange items on platform + platform.layout(); + API.space.set_focus(API.selection.widgets()); + } else { + // go to arrange view + API.view.set(VIEWS.ARRANGE); + } break; default: API.event.emit('keypress', evt); handled = false; break; } - if (handled) evt.preventDefault(); + if (handled) { + evt.preventDefault(); + evt.stopPropagation(); + } return false; } + function layFlat() { + let int = contextInt[0]; + if (int && int.object && int.object.widget) { + let q = new THREE.Quaternion(); + q.setFromUnitVectors(contextInt[0].face.normal, new THREE.Vector3(0,0,-1)); + API.selection.rotate(q); + } + } + + function duplicateSelection() { + API.selection.for_widgets(function(widget) { + let mesh = widget.mesh; + let bb = mesh.getBoundingBox(); + let ow = widget; + let nw = API.widgets.new().loadGeometry(mesh.geometry.clone()); + nw.meta.file = ow.meta.file; + nw.meta.vertices = ow.meta.vertices; + nw.move(bb.max.x - bb.min.x + 1, 0, 0); + platform.add(nw,true); + let owa = API.widgets.annotate(ow.id); + let nwa = API.widgets.annotate(nw.id); + if (owa.tab) { + nwa.tab = Object.clone(owa.tab); + nwa.tab.forEach((tab,i) => { + tab.id = Date.now() + i + }); + } + KIRI.driver.CAM.restoreTabs([nw]); + }); + } + + function mirrorSelection() { + API.selection.for_widgets(function(widget) { + widget.mirror(); + }); + SPACE.update(); + } + function keys(o) { let key, list = []; for (key in o) { if (o.hasOwnProperty(key)) list.push(key) } @@ -515,43 +543,6 @@ moveSelection(x, y, z, true); } - function loadSettingsFromServer(tok) { - let hash = (tok || LOC.hash.substring(1)).split("/"); - if (hash.length === 2) { - new moto.Ajax(function(reply) { - if (reply) { - let res = JSON.parse(reply); - if (res && res.ver && res.rec) { - let set = JSON.parse(atob(res.rec)); - set.id = res.space; - set.ver = res.ver; - API.conf.put(set); - API.event.settings(); - API.ui.sync(); - LOC.hash = ''; - } - } - }).request("/data/"+ hash[0] + "/" + hash[1]); - } - } - - function storeSettingsToServer(display) { - let set = btoa(JSON.stringify(settings())); - new moto.Ajax(function(reply) { - if (reply) { - let res = JSON.parse(reply); - if (res && res.ver) { - LOC.hash = res.space + "/" + res.ver; - if (display) { - UC.alert(`unique settings id is: ${res.space}/${res.ver}`); - } - } - } else { - updateSpaceState(); - } - }).request("/data/"+ settings().id + "/" + settings().ver, set); - } - function deviceExport(exp, name) { name = (name || "device") .toLowerCase() @@ -559,10 +550,7 @@ .replace(/\./g,'_'); UC.prompt("Export Device Filename", name).then(name => { if (name) { - let blob = new Blob([exp], {type: "octet/stream"}), - url = WIN.URL.createObjectURL(blob); - $('mod-any').innerHTML = `x`; - $('sexport').click(); + API.util.download(exp, `${name}.km`); } }); } @@ -574,13 +562,7 @@ if (name.toLowerCase().indexOf(".stl") < 0) { name = `${name}.stl`; } - let xprt = API.selection.export(), - blob = new Blob([xprt], {type: "octet/stream"}), - url = WIN.URL.createObjectURL(blob); - $('mod-any').innerHTML = [ - `x` - ].join(''); - $('sexport').click(); + API.util.download(API.selection.export(), name); }); } @@ -601,13 +583,8 @@ UC.confirm("Export Filename", {ok:true, cancel: false}, "workspace", opt).then(name => { if (name) { let work = $('incwork').checked, - json = API.conf.export({work}), - blob = new Blob([json], {type: "octet/stream"}), - url = WIN.URL.createObjectURL(blob); - $('mod-any').innerHTML = [ - `x` - ].join(''); - $('sexport').click(); + json = API.conf.export({work}); + API.util.download(json, `${name}.km`); } }); } @@ -630,10 +607,12 @@ if (name) { let np = pl[name] = {}; cp.processName = name; + pl[name] = Object.clone(cp); for (let k in cp) { if (!cp.hasOwnProperty(k)) continue; np[k] = cp[k]; } + s.cproc[mode] = name; s.devproc[s.device.deviceName] = name; API.conf.save(); API.conf.update(); @@ -694,28 +673,28 @@ let mode = API.mode.get(), current = settings(), local = isLocalDevice(devicename), - dproc = current.devproc[devicename], dev = current.device = CONF.device_from_code(code,mode), - proc = current.process, - newdev = dproc === undefined; + dproc = current.devproc[devicename], // last process name for this device + newdev = dproc === undefined, // first time device is selected + predev = current.filter[mode], // previous device selection + chgdev = predev !== devicename; // device is changing // first time device use, add any print profiles and set to default if present - // if (newdev) { - // console.log('new device', code); - if (code.profiles) { - for (let profile of code.profiles) { - let profname = profile.processName; - if (!current.sproc[mode][profname]) { - console.log('adding profile', profname, 'to', mode); - current.sproc[mode][profname] = profile; - } - if (newdev && !current.devproc[devicename]) { - console.log('setting default profile for new device', devicename, 'to', profname); - current.devproc[devicename] = dproc = profname; - } + if (code.profiles) { + for (let profile of code.profiles) { + let profname = profile.processName; + // if no saved profile by that name for this mode... + if (!current.sproc[mode][profname]) { + console.log('adding profile', profname, 'to', mode); + current.sproc[mode][profname] = profile; + } + // if it's a new device, seed the new profile name as last profile + if (newdev && !current.devproc[devicename]) { + console.log('setting default profile for new device', devicename, 'to', profname); + current.devproc[devicename] = dproc = profname; } } - // } + } dev.new = false; dev.deviceName = devicename; @@ -724,9 +703,16 @@ UI.deviceBelt.checked = dev.bedBelt; UI.deviceRound.checked = dev.bedRound; UI.deviceOrigin.checked = dev.outputOriginCenter || dev.originCenter; + UI.fwRetract.checked = dev.fwRetract; // add extruder selection buttons if (dev.extruders) { + for (let ext of dev.extruders) { + // add missing deselect field from legacy configs + if (!ext.extDeselect) { + ext.extDeselect = []; + } + } let ext = API.lists.extruders = []; dev.internal = 0; let selext = $('pop-nozzle'); @@ -756,10 +742,10 @@ UI.bedDepth, UI.bedWidth, UI.maxHeight, - UI.extrudeAbs, UI.deviceOrigin, UI.deviceRound, UI.deviceBelt, + UI.fwRetract, UI.gcodeFan, UI.gcodeTrack, UI.gcodeLayer, @@ -780,12 +766,12 @@ UI.extDel, UI.extOffsetX, UI.extOffsetY, - UI.extSelect + UI.extSelect, + UI.extDeselect ].forEach(function(e) { e.disabled = !local; }); - UI.extrudeAbs.style.display = mode === 'CAM' ? 'none' : 'flex'; UI.deviceSave.disabled = !local; UI.deviceDelete.disabled = !local; UI.deviceExport.disabled = !local; @@ -802,8 +788,12 @@ API.view.update_fields(); platform.update_size(); + platform.update_origin(); + platform.update(); + // store current device name for this mode current.filter[mode] = devicename; + // cache device record for this mode (restored in setMode) current.cdev[mode] = currentDevice = dev; if (dproc) { @@ -814,6 +804,13 @@ } API.conf.save(); + + API.const.SPACE.view.setHome(dev.bedBelt ? Math.PI/2 : 0); + // when changing devices, update focus on widgets + if (chgdev) { + setTimeout(API.space.set_focus, 0); + } + UC.refresh(); if (dev.imageURL) { @@ -844,12 +841,14 @@ } function loadDeviceImage(dev, url) { - new THREE.TextureLoader().load(url || dev.imageURL, texture => { + let turl = url || dev.imageURL; + if (!turl) return; + new THREE.TextureLoader().load(turl, texture => { loadDeviceTexture(dev, texture); }, inc => { console.log({load_inc: inc}); }, error => { - console.log({load_error: error}); + console.log({load_error: error, turl}); clearDeviceTexture(); }); } @@ -949,12 +948,13 @@ showDevices(); }; UI.deviceExport.onclick = function() { - let exp = btoa(JSON.stringify({ + let exp = API.util.b64enc({ version: KIRI.version, device: selected, + process: API.process.code(), code: devs[selected], time: Date.now() - })); + }); deviceExport(exp, selected); }; @@ -1236,6 +1236,12 @@ function dragOverHandler(evt) { evt.stopPropagation(); evt.preventDefault(); + + // prevent drop actions when a dialog is open + if (API.modal.visible()) { + return; + } + evt.dataTransfer.dropEffect = 'copy'; let oldcolor = SPACE.platform.setColor(0x00ff00); if (oldcolor !== 0x00ff00) platformColor = oldcolor; @@ -1249,6 +1255,11 @@ evt.stopPropagation(); evt.preventDefault(); + // prevent drop actions when a dialog is open + if (API.modal.visible()) { + return; + } + SPACE.platform.setColor(platformColor); let files = evt.dataTransfer.files; @@ -1352,13 +1363,15 @@ return !isBelt(); } + function isDanger() { + return UI.danger.checked; + } + // MAIN INITIALIZATION FUNCTION function init_one() { API.event.emit('init.one'); - $('gsbox').src = `${proto}//${root}/kiri/gsbox.png?ver=${ver}`; - // ensure we have settings from last session API.conf.restore(); @@ -1387,7 +1400,7 @@ SPACE.setSkyColor(controller.dark ? 0 : 0xffffff); SPACE.init(container, function (delta) { if (API.var.layer_max === 0 || !delta) return; - if (settings().controller.reverseZoom) delta = -delta; + if (controller.reverseZoom) delta = -delta; let same = API.var.layer_hi === API.var.layer_lo; let track = API.var.layer_lo > 0; if (delta > 0) { @@ -1406,7 +1419,7 @@ } API.view.update_slider(); API.show.slices(); - }); + }, controller.ortho); SPACE.platform.onMove(API.conf.save); SPACE.platform.setRound(true); SPACE.useDefaultKeys(API.feature.on_key === undefined || API.feature.on_key_defaults); @@ -1448,6 +1461,10 @@ back: $('lt-back'), trash: $('lt-trash'), + ltsetup: $('lt-setup'), + ltfile: $('lt-file'), + ltview: $('lt-view'), + ltact: $('act-slice'), rotate: $('lt-rotate'), scale: $('lt-scale'), nozzle: $('lt-nozzle'), @@ -1534,6 +1551,7 @@ deviceOrigin: UC.newBoolean(LANG.dv_orgc_s, onBooleanClick, {title:LANG.dv_orgc_l, modes:FDM_LASER_SLA}), deviceRound: UC.newBoolean(LANG.dv_bedc_s, onBooleanClick, {title:LANG.dv_bedc_l, modes:FDM, trigger:true, show:isNotBelt}), deviceBelt: UC.newBoolean(LANG.dv_belt_s, onBooleanClick, {title:LANG.dv_belt_l, modes:FDM, trigger:true, show:() => !UI.deviceRound.checked}), + fwRetract: UC.newBoolean(LANG.dv_retr_s, onBooleanClick, {title:LANG.dv_retr_l, modes:FDM}), extruder: UC.newGroup(LANG.dv_gr_ext, $('device2'), {group:"dext", inline:true, modes:FDM}), extFilament: UC.newInput(LANG.dv_fila_s, {title:LANG.dv_fila_l, convert:UC.toFloat, modes:FDM}), @@ -1541,7 +1559,7 @@ extOffsetX: UC.newInput(LANG.dv_exox_s, {title:LANG.dv_exox_l, convert:UC.toFloat, modes:FDM}), extOffsetY: UC.newInput(LANG.dv_exoy_s, {title:LANG.dv_exoy_l, convert:UC.toFloat, modes:FDM}), extSelect: UC.newText(LANG.dv_exts_s, {title:LANG.dv_exts_l, modes:FDM, size:14, height:3, modes:FDM, area:gcode}), - extrudeAbs: UC.newBoolean(LANG.dv_xtab_s, onBooleanClick, {title:LANG.dv_xtab_l, modes:FDM}), + extDeselect: UC.newText(LANG.dv_dext_s, {title:LANG.dv_dext_l, modes:FDM, size:14, height:3, modes:FDM, area:gcode}), extActions: UC.newRow([ UI.extPrev = UC.newButton(undefined, undefined, {icon:''}), UI.extAdd = UC.newButton(undefined, undefined, {icon:''}), @@ -1571,8 +1589,10 @@ lprefs: UC.newGroup(LANG.op_menu, $('prefs-gen1'), {inline: true}), reverseZoom: UC.newBoolean(LANG.op_invr_s, booleanSave, {title:LANG.op_invr_l}), + ortho: UC.newBoolean(LANG.op_orth_s, booleanSave, {title:LANG.op_orth_l}), dark: UC.newBoolean(LANG.op_dark_s, booleanSave, {title:LANG.op_dark_l}), decals: UC.newBoolean(LANG.op_decl_s, booleanSave, {title:LANG.op_decl_s}), + danger: UC.newBoolean(LANG.op_dang_s, booleanSave, {title:LANG.op_dang_l}), lprefs: UC.newGroup(LANG.op_disp, $('prefs-gen2'), {inline: true}), showOrigin: UC.newBoolean(LANG.op_shor_s, booleanSave, {title:LANG.op_shor_l}), @@ -1580,12 +1600,13 @@ showSpeeds: UC.newBoolean(LANG.op_sped_s, speedSave, {title:LANG.op_sped_l}), lineType: UC.newSelect(LANG.op_line_s, {title: LANG.op_line_l, action: lineTypeSave, modes:FDM}, "linetype"), animesh: UC.newSelect(LANG.op_anim_s, {title: LANG.op_anim_l, action: aniMeshSave, modes:CAM}, "animesh"), - units: UC.newSelect(LANG.op_unit_s, {title: LANG.op_unit_l, action:unitsSave, modes:CAM, trace:true}, "units"), + units: UC.newSelect(LANG.op_unit_s, {title: LANG.op_unit_l, action: unitsSave, modes:CAM, trace:true}, "units"), layout: UC.newGroup(LANG.lo_menu, $('prefs-lay'), {inline: true}), + autoSave: UC.newBoolean(LANG.op_save_s, booleanSave, {title:LANG.op_save_l}), autoLayout: UC.newBoolean(LANG.op_auto_s, booleanSave, {title:LANG.op_auto_l}), freeLayout: UC.newBoolean(LANG.op_free_s, booleanSave, {title:LANG.op_free_l}), - autoSave: UC.newBoolean(LANG.op_save_s, booleanSave, {title:LANG.op_save_l}), + spaceLayout: UC.newInput(LANG.op_spcr_s, {title:LANG.op_spcr_l, convert:UC.toFloat, size:3, units:true}), export: UC.newGroup(LANG.xp_menu, $('prefs-xpo'), {inline: true}), exportOcto: UC.newBoolean(`OctoPrint`, booleanSave), @@ -1596,6 +1617,7 @@ parts: UC.newGroup(LANG.pt_menu, $('prefs-prt'), {inline: true}), detail: UC.newSelect(LANG.pt_qual_s, {title: LANG.pt_qual_l, action: detailSave}, "detail"), decimate: UC.newBoolean(LANG.pt_deci_s, booleanSave, {title: LANG.pt_deci_l}), + healMesh: UC.newBoolean(LANG.pt_heal_s, booleanSave, {title: LANG.pt_heal_l}), prefadd: UC.checkpoint($('prefs-add')), @@ -1603,7 +1625,7 @@ sliceHeight: UC.newInput(LANG.sl_lahi_s, {title:LANG.sl_lahi_l, convert:UC.toFloat, modes:FDM}), sliceMinHeight: UC.newInput(LANG.ad_minl_s, {title:LANG.ad_minl_l, bound:UC.bound(0,3.0), convert:UC.toFloat, modes:FDM, show: () => UI.sliceAdaptive.checked}), fdmSep: UC.newBlank({class:"pop-sep", modes:FDM}), - sliceShells: UC.newInput(LANG.sl_shel_s, {title:LANG.sl_shel_l, convert:UC.toInt, modes:FDM}), + sliceShells: UC.newInput(LANG.sl_shel_s, {title:LANG.sl_shel_l, convert:UC.toFloat, modes:FDM}), sliceTopLayers: UC.newInput(LANG.sl_ltop_s, {title:LANG.sl_ltop_l, convert:UC.toInt, modes:FDM}), sliceSolidLayers: UC.newInput(LANG.sl_lsld_s, {title:LANG.sl_lsld_l, convert:UC.toInt, modes:FDM}), sliceBottomLayers: UC.newInput(LANG.sl_lbot_s, {title:LANG.sl_lbot_l, convert:UC.toInt, modes:FDM}), @@ -1621,15 +1643,20 @@ fdmSep: UC.newBlank({class:"pop-sep", modes:FDM, show:isNotBelt}), firstLayerNozzleTemp:UC.newInput(LANG.fl_nozl_s, {title:LANG.fl_nozl_l, convert:UC.toInt, modes:FDM, show:isNotBelt}), firstLayerBedTemp: UC.newInput(LANG.fl_bedd_s, {title:LANG.fl_bedd_l, convert:UC.toInt, modes:FDM, show:isNotBelt}), + firstLayerFanSpeed: UC.newInput(LANG.ou_fans_s, {title:LANG.ou_fans_l, convert:UC.toInt, bound:UC.bound(0,255), modes:FDM}), fdmSep: UC.newBlank({class:"pop-sep", modes:FDM, show:isNotBelt}), firstLayerYOffset: UC.newInput(LANG.fl_zoff_s, {title:LANG.fl_zoff_l, convert:UC.toFloat, modes:FDM, show:isBelt}), + fdmSep: UC.newBlank({class:"pop-sep", modes:FDM, show:isBelt}), firstLayerRate: UC.newInput(LANG.fl_rate_s, {title:LANG.fl_rate_l, convert:UC.toFloat, modes:FDM}), firstLayerFillRate: UC.newInput(LANG.fl_frat_s, {title:LANG.fl_frat_l, convert:UC.toFloat, modes:FDM, show:isNotBelt}), fdmSep: UC.newBlank({class:"pop-sep", modes:FDM, show:isNotBelt}), firstLayerLineMult: UC.newInput(LANG.fl_sfac_s, {title:LANG.fl_sfac_l, convert:UC.toFloat, bound:UC.bound(0.5,2), modes:FDM, show:isNotBelt}), firstLayerPrintMult: UC.newInput(LANG.fl_mult_s, {title:LANG.fl_mult_l, convert:UC.toFloat, modes:FDM}), + fdmSep: UC.newBlank({class:"pop-sep", modes:FDM, show:isBelt}), firstLayerBrim: UC.newInput(LANG.fl_brim_s, {title:LANG.fl_brim_l, convert:UC.toFloat, modes:FDM, show:isBelt}), firstLayerBrimTrig: UC.newInput(LANG.fl_brmn_s, {title:LANG.fl_brmn_l, convert:UC.toFloat, modes:FDM, show:isBelt}), + fdmSep: UC.newBlank({class:"pop-sep", modes:FDM, show:isBelt}), + firstLayerBeltLead: UC.newInput(LANG.fl_bled_s, {title:LANG.fl_bled_l, convert:UC.toFloat, modes:FDM, show:isBelt}), fdmSep: UC.newBlank({class:"pop-sep", modes:FDM, show:isNotBelt}), outputBrimCount: UC.newInput(LANG.fl_skrt_s, {title:LANG.fl_skrt_l, convert:UC.toInt, modes:FDM, show:isNotBelt}), outputBrimOffset: UC.newInput(LANG.fl_skro_s, {title:LANG.fl_skro_l, convert:UC.toFloat, modes:FDM, show:isNotBelt}), @@ -1639,29 +1666,32 @@ fdmInfill: UC.newGroup(LANG.fi_menu, $('settings'), {modes:FDM}), sliceFillType: UC.newSelect(LANG.fi_type, {modes:FDM}, "infill"), - fdmSep: UC.newBlank({class:"pop-sep", modes:FDM}), sliceFillSparse: UC.newInput(LANG.fi_pcnt_s, {title:LANG.fi_pcnt_l, convert:UC.toFloat, bound:UC.bound(0.0,1.0), modes:FDM}), - sliceFillAngle: UC.newInput(LANG.fi_angl_s, {title:LANG.fi_angl_l, convert:UC.toFloat, modes:FDM}), + fdmSep: UC.newBlank({class:"pop-sep", modes:FDM}), sliceFillOverlap: UC.newInput(LANG.fi_over_s, {title:LANG.fi_over_l, convert:UC.toFloat, bound:UC.bound(0.0,2.0), modes:FDM}), + sliceFillRate: UC.newInput(LANG.ou_feed_s, {title:LANG.fi_rate_l, convert:UC.toInt, bound:UC.bound(0,300), modes:FDM}), + fdmSep: UC.newBlank({class:"pop-sep", modes:FDM}), + sliceFillAngle: UC.newInput(LANG.fi_angl_s, {title:LANG.fi_angl_l, convert:UC.toFloat, modes:FDM}), + // sliceFillWidth: UC.newInput(LANG.fi_wdth_s, {title:LANG.fi_wdth_l, convert:UC.toFloat, modes:FDM}), fdmSupport: UC.newGroup(LANG.sp_menu, null, {modes:FDM, marker:false}), sliceSupportNozzle: UC.newSelect(LANG.sp_nozl_s, {title:LANG.sp_nozl_l, modes:FDM}, "extruders"), - sliceSupportDensity: UC.newInput(LANG.sp_dens_s, {title:LANG.sp_dens_l, convert:UC.toFloat, bound:UC.bound(0.05,1.0), modes:FDM}), + sliceSupportDensity: UC.newInput(LANG.sp_dens_s, {title:LANG.sp_dens_l, convert:UC.toFloat, bound:UC.bound(0.0,1.0), modes:FDM}), fdmSep: UC.newBlank({class:"pop-sep", modes:FDM}), sliceSupportSize: UC.newInput(LANG.sp_size_s, {title:LANG.sp_size_l, bound:UC.bound(1.0,200.0), convert:UC.toFloat, modes:FDM}), sliceSupportOffset: UC.newInput(LANG.sp_offs_s, {title:LANG.sp_offs_l, bound:UC.bound(0.0,200.0), convert:UC.toFloat, modes:FDM}), - sliceSupportGap: UC.newInput(LANG.sp_gaps_s, {title:LANG.sp_gaps_l, bound:UC.bound(0,5), convert:UC.toInt, modes:FDM}), + sliceSupportGap: UC.newInput(LANG.sp_gaps_s, {title:LANG.sp_gaps_l, bound:UC.bound(0,5), convert:UC.toInt, modes:FDM, show:isNotBelt}), fdmSep: UC.newBlank({class:"pop-sep", modes:FDM}), sliceSupportArea: UC.newInput(LANG.sp_area_s, {title:LANG.sp_area_l, bound:UC.bound(0.0,200.0), convert:UC.toFloat, modes:FDM}), sliceSupportExtra: UC.newInput(LANG.sp_xpnd_s, {title:LANG.sp_xpnd_l, bound:UC.bound(0.0,200.0), convert:UC.toFloat, modes:FDM}), fdmSep: UC.newBlank({class:"pop-sep", modes:FDM}), - sliceSupportAngle: UC.newInput(LANG.sp_angl_s, {title:LANG.sp_angl_l, bound:UC.bound(0.0,90.0), convert:UC.toFloat, modes:FDM}), + sliceSupportAngle: UC.newInput(LANG.sp_angl_s, {title:LANG.sp_angl_l, bound:UC.bound(0.0,90.0), convert:UC.toFloat, modes:FDM, xshow:isNotBelt}), sliceSupportEnable: UC.newBoolean(LANG.sp_auto_s, onBooleanClick, {title:LANG.sp_auto_l, modes:FDM, show:isNotBelt}), sliceSupportGen: UC.newRow([ UI.ssaGen = UC.newButton(LANG.sp_detect, onButtonClick, {class: "f-col grow a-center"}) ], { modes: FDM, class: "ext-buttons f-row grow" }), - fdmSep: UC.newBlank({class:"pop-sep", modes:FDM}), + fdmSep: UC.newBlank({class:"pop-sep", modes:FDM, show:isNotBelt}), sliceSupportManual: UC.newRow([ (UI.ssmAdd = UC.newButton(undefined, onButtonClick, {icon:''})), (UI.ssmDun = UC.newButton(undefined, onButtonClick, {icon:''})), @@ -1720,6 +1750,7 @@ outputTemp: UC.newInput(LANG.ou_nozl_s, {title:LANG.ou_nozl_l, convert:UC.toInt, modes:FDM}), outputBedTemp: UC.newInput(LANG.ou_bedd_s, {title:LANG.ou_bedd_l, convert:UC.toInt, modes:FDM}), + outputFanSpeed: UC.newInput(LANG.ou_fans_s, {title:LANG.ou_fans_l, convert:UC.toInt, bound:UC.bound(0,255), modes:FDM}), fdmSep: UC.newBlank({class:"pop-sep", modes:FDM}), outputFeedrate: UC.newInput(LANG.ou_feed_s, {title:LANG.ou_feed_l, convert:UC.toInt, modes:FDM}), outputFinishrate: UC.newInput(LANG.ou_fini_s, {title:LANG.ou_fini_l, convert:UC.toInt, modes:FDM}), @@ -1729,7 +1760,6 @@ outputFillMult: UC.newInput(LANG.ou_flml_s, {title:LANG.ou_exml_l, convert:UC.toFloat, bound:UC.bound(0.0,2.0), modes:FDM}), outputSparseMult: UC.newInput(LANG.ou_spml_s, {title:LANG.ou_exml_l, convert:UC.toFloat, bound:UC.bound(0.0,2.0), modes:FDM}), fdmSep: UC.newBlank({class:"pop-sep", modes:FDM}), - outputFanLayer: UC.newInput(LANG.ou_fanl_s, {title:LANG.ou_fanl_l, convert:UC.toInt, bound:UC.bound(0,100), modes:FDM}), sliceShellOrder: UC.newSelect(LANG.sl_ordr_s, {title:LANG.sl_ordr_l, modes:FDM}, "shell"), sliceLayerStart: UC.newSelect(LANG.sl_strt_s, {title:LANG.sl_strt_l, modes:FDM}, "start"), camConventional: UC.newBoolean(LANG.ou_conv_s, onBooleanClick, {title:LANG.ou_conv_l, modes:CAM}), @@ -1745,18 +1775,20 @@ advanced: UC.newGroup(LANG.ad_menu, null, {modes:FDM}), outputRetractDist: UC.newInput(LANG.ad_rdst_s, {title:LANG.ad_rdst_l, convert:UC.toFloat, modes:FDM}), outputRetractSpeed: UC.newInput(LANG.ad_rrat_s, {title:LANG.ad_rrat_l, convert:UC.toInt, modes:FDM}), + outputRetractWipe: UC.newInput(LANG.ad_wpln_s, {title:LANG.ad_wpln_l, bound:UC.bound(0.0,10), convert:UC.toFloat, modes:FDM}), outputRetractDwell: UC.newInput(LANG.ad_rdwl_s, {title:LANG.ad_rdwl_l, convert:UC.toInt, modes:FDM}), fdmSep: UC.newBlank({class:"pop-sep", modes:FDM}), - // outputWipeDistance: UC.newInput("wipe", {title:"non-printing move at\close of polygon\nin millimeters", bound:UC.bound(0.0,10), convert:UC.toFloat, modes:FDM}), sliceSolidMinArea: UC.newInput(LANG.ad_msol_s, {title:LANG.ad_msol_l, convert:UC.toFloat, modes:FDM}), outputMinSpeed: UC.newInput(LANG.ad_mins_s, {title:LANG.ad_mins_l, bound:UC.bound(5,200), convert:UC.toFloat, modes:FDM}), - outputCoastDist: UC.newInput(LANG.ad_scst_s, {title:LANG.ad_scst_l, bound:UC.bound(0.0,10), convert:UC.toFloat, modes:FDM}), outputShortPoly: UC.newInput(LANG.ad_spol_s, {title:LANG.ad_spol_l, bound:UC.bound(0,10000), convert:UC.toFloat, modes:FDM}), + outputCoastDist: UC.newInput(LANG.ad_scst_s, {title:LANG.ad_scst_l, bound:UC.bound(0.0,10), convert:UC.toFloat, modes:FDM}), fdmSep: UC.newBlank({class:"pop-sep", modes:FDM}), zHopDistance: UC.newInput(LANG.ad_zhop_s, {title:LANG.ad_zhop_l, bound:UC.bound(0,3.0), convert:UC.toFloat, modes:FDM}), + arcTolerance: UC.newInput(LANG.ad_arct_s, {title:LANG.ad_arct_l, bound:UC.bound(0,1.0), convert:UC.toFloat, modes:FDM, show:() => { return isDanger() && isNotBelt() }}), antiBacklash: UC.newInput(LANG.ad_abkl_s, {title:LANG.ad_abkl_l, bound:UC.bound(0,3), convert:UC.toInt, modes:FDM}), fdmSep: UC.newBlank({class:"pop-sep", modes:FDM}), - gcodePauseLayers: UC.newInput(LANG.ag_paws_s, {title:LANG.ag_paws_l, modes:FDM, comma:true}), + outputPeelGuard: UC.newInput(LANG.ag_peel_s, {title:LANG.ag_peel_l, convert:UC.toInt, modes:FDM, comma:true, show:isBelt}), + gcodePauseLayers: UC.newInput(LANG.ag_paws_s, {title:LANG.ag_paws_l, modes:FDM, comma:true, show:isNotBelt}), outputLoopLayers: UC.newInput(LANG.ag_loop_s, {title:LANG.ag_loop_l, modes:FDM, comma:true, show:isBelt}), outputLayerRetract: UC.newBoolean(LANG.ad_lret_s, onBooleanClick, {title:LANG.ad_lret_l, modes:FDM}), @@ -1798,6 +1830,9 @@ slaFirstOffset: UC.newInput(LANG.sa_opzo_s, {title:LANG.sa_opzo_l, convert:UC.toFloat, bound:UC.bound(0,1), modes:SLA}), slaAntiAlias: UC.newSelect(LANG.sa_opaa_s, {title:LANG.sa_opaa_l, modes:SLA}, "antialias"), + rangeGroup: UC.newGroup("ranges", null, {modes:FDM, group:"ranges"}), + rangeList: UC.newRow([], {}), + settingsGroup: UC.newGroup(LANG.se_menu, $('settings')), settingsTable: UC.newRow([ UI.settingsLoad = UC.newButton(LANG.se_load, settingsLoad) ]), settingsTable: UC.newRow([ UI.settingsSave = UC.newButton(LANG.se_save, settingsSave) ]), @@ -1955,6 +1990,19 @@ UI.sliderHold.style.marginRight = `${hival}px`; }); + // store layer preferences + API.event.on('stack.show', label => { + let mode = API.mode.get(); + let view = API.view.get(); + API.conf.get().labels[`${mode}-${view}-${label}`] = true; + }); + + API.event.on('stack.hide', label => { + let mode = API.mode.get(); + let view = API.view.get(); + API.conf.get().labels[`${mode}-${view}-${label}`] = false; + }); + // bind language choices $('lset-en').onclick = function() { SDB.setItem('kiri-lang', 'en-us'); @@ -1964,6 +2012,18 @@ SDB.setItem('kiri-lang', 'da-dk'); API.space.reload(); }; + $('lset-de').onclick = function() { + SDB.setItem('kiri-lang', 'de-de'); + API.space.reload(); + }; + $('lset-fr').onclick = function() { + SDB.setItem('kiri-lang', 'fr-fr'); + API.space.reload(); + }; + $('lset-pl').onclick = function() { + SDB.setItem('kiri-lang', 'pl-pl'); + API.space.reload(); + }; SPACE.addEventHandlers(self, [ 'keyup', keyUpHandler, @@ -1981,9 +2041,17 @@ xv = parseFloat(UI.sizeX.was), yv = parseFloat(UI.sizeY.was), zv = parseFloat(UI.sizeZ.was), - xr = (UI.lockX.checked ? ra : 1), - yr = (UI.lockY.checked ? ra : 1), - zr = (UI.lockZ.checked ? ra : 1); + ta = e.target, + xc = UI.lockX.checked, + yc = UI.lockY.checked, + zc = UI.lockZ.checked, + xt = ta === UI.sizeX, + yt = ta === UI.sizeY, + zt = ta === UI.sizeZ, + tl = (xt && xc) || (yt && yc) || (zt && zc), + xr = ((tl && xc) || (!tl && xt) ? ra : 1), + yr = ((tl && yc) || (!tl && yt) ? ra : 1), + zr = ((tl && zc) || (!tl && zt) ? ra : 1); API.selection.scale(xr,yr,zr); UI.sizeX.was = UI.sizeX.value = xv * xr; UI.sizeY.was = UI.sizeY.value = yv * yr; @@ -1997,9 +2065,17 @@ xv = parseFloat(UI.scaleX.was), yv = parseFloat(UI.scaleY.was), zv = parseFloat(UI.scaleZ.was), - xr = (UI.lockX.checked ? ra : 1), - yr = (UI.lockY.checked ? ra : 1), - zr = (UI.lockZ.checked ? ra : 1); + ta = e.target, + xc = UI.lockX.checked, + yc = UI.lockY.checked, + zc = UI.lockZ.checked, + xt = ta === UI.scaleX, + yt = ta === UI.scaleY, + zt = ta === UI.scaleZ, + tl = (xt && xc) || (yt && yc) || (zt && zc), + xr = ((tl && xc) || (!tl && xt) ? ra : 1), + yr = ((tl && yc) || (!tl && yt) ? ra : 1), + zr = ((tl && zc) || (!tl && zt) ? ra : 1); API.selection.scale(xr,yr,zr); UI.scaleX.was = UI.scaleX.value = xv * xr; UI.scaleY.was = UI.scaleY.value = yv * yr; @@ -2035,6 +2111,35 @@ $('rot_z'), selectionRotate ]); + $('lab-axis').onclick = () => { + UI.lockX.checked = + UI.lockY.checked = + UI.lockZ.checked = !( + UI.lockX.checked || + UI.lockY.checked || + UI.lockZ.checked + ); + }; + + $('lab-scale').onclick = () => { + API.selection.scale(1 / UI.scaleX.was, 1 / UI.scaleY.was, 1 / UI.scaleZ.was); + UI.scaleX.value = UI.scaleY.value = UI.scaleZ.value = + UI.scaleX.was = UI.scaleY.was = UI.scaleZ.was = 1; + }; + + let hpops = []; + UC.hoverPop(UI.ltsetup, { group: hpops, target: $('set-pop') }); + UC.hoverPop(UI.ltfile, { group: hpops, target: $('file-pop') }); + UC.hoverPop(UI.ltview, { group: hpops, target: $('pop-view') }); + UC.hoverPop(UI.ltact, { group: hpops, target: $('pop-slice') }); + UC.hoverPop(UI.render, { group: hpops, target: $('pop-render'), sticky: true }); + UC.hoverPop(UI.rotate, { group: hpops, target: $('pop-rotate'), sticky: true }); + UC.hoverPop(UI.scale, { group: hpops, target: $('pop-scale'), sticky: true }); + UC.hoverPop(UI.nozzle, { group: hpops, target: $('pop-nozzle'), sticky: true }); + UC.hoverPop($('app-acct'), { group: hpops, target: $('acct-pop') } ); + UC.hoverPop($('app-mode'), { group: hpops, target: $('mode-info') } ); + UC.hoverPop($('app-name'), { group: hpops, target: $('app-info') } ); + UC.onBlur([ UI.toolName, UI.toolNum, @@ -2080,8 +2185,21 @@ if (int) API.event.emit('mouse.hover', {point: int, event, type: 'platform'}); }); + // block standard browser context menu + DOC.oncontextmenu = (event) => { + if (event.target.tagName === 'CANVAS') { + event.preventDefault(); + event.stopPropagation(); + return false; + } + }; + SPACE.mouse.up((event, int) => { - if (event.button === 2 && API.view.isArrange()) { + if (event.button === 2) { + let full = API.view.isArrange(); + for (let key of ["layflat","mirror","duplicate"]) { + $(`context-${key}`).disabled = !full; + } let style = UI.context.style; style.display = 'flex'; style.left = `${event.clientX-3}px`; @@ -2089,6 +2207,9 @@ UI.context.onmouseleave = () => { style.display = ''; }; + event.preventDefault(); + event.stopPropagation(); + contextInt = int; } }); @@ -2114,6 +2235,10 @@ // return selected meshes for further mouse processing return API.feature.hovers || API.selection.meshes(); } + if (event.button === 2 && API.view.isArrange()) { + event.preventDefault(); + event.stopPropagation(); + } }); SPACE.mouse.upSelect(function(object, event) { @@ -2172,6 +2297,8 @@ function init_two() { API.event.emit('init.two'); + // API.space.set_focus(); + // call driver initializations, if present Object.values(KIRI.driver).forEach(driver => { if (driver.init) try { @@ -2238,6 +2365,8 @@ UI.reverseZoom.checked = control.reverseZoom; UI.autoSave.checked = control.autoSave; UI.decimate.checked = control.decimate; + UI.healMesh.checked = control.healMesh; + UI.ortho.checked = control.ortho; lineTypeSave(); detailSave(); updateFPS(); @@ -2272,9 +2401,6 @@ UI.sync(); - // load settings provided in url hash - loadSettingsFromServer(); - // clear alerts as they build up setInterval(API.event.alerts, 1000); @@ -2292,7 +2418,7 @@ API.event.emit('init-done', STATS); // show gdpr if it's never been seen and we're not iframed - if (!SDB.gdpr && WIN.self === WIN.top) { + if (!SDB.gdpr && WIN.self === WIN.top && !SETUP.debug) { $('gdpr').style.display = 'flex'; } @@ -2359,7 +2485,14 @@ // context menu $('context-export-stl').onclick = () => { objectsExport() }; $('context-export-workspace').onclick = () => { profileExport(true) }; - $('context-clear-workspace').onclick = () => { API.platform.clear(); UI.context.onmouseleave() }; + $('context-clear-workspace').onclick = () => { + API.view.set(VIEWS.ARRANGE); + API.platform.clear(); + UI.context.onmouseleave(); + }; + $('context-duplicate').onclick = duplicateSelection; + $('context-mirror').onclick = mirrorSelection; + $('context-layflat').onclick = layFlat; UI.modal.onclick = API.modal.hide; UI.modalBox.onclick = (ev) => { ev.stopPropagation() }; @@ -2376,21 +2509,38 @@ } } + // update static html elements with language overrides + UI.lang = function() { + for (let el of [...DOC.querySelectorAll("[lk]")]) { + let key = el.getAttribute('lk'); + let val = LANG[key]; + if (val) { + el.innerText = val; + } else { + console.log({missing_ln: key}); + } + } + }; + // if a language needs to load, the script is injected and loaded // first. once this loads, or doesn't, the initialization begins + let lang_load = false; let lang_set = undefined; let lang = SETUP.ln ? SETUP.ln[0] : SDB.getItem('kiri-lang') || KIRI.lang.get(); // inject language script if not english if (lang && lang !== 'en' && lang !== 'en-us') { lang_set = lang; + let map = KIRI.lang.map(lang); let scr = DOC.createElement('script'); // scr.setAttribute('defer',true); - scr.setAttribute('src',`/kiri/lang/${lang}.js`); + scr.setAttribute('src',`/kiri/lang/${map}.js?${KIRI.version}`); (DOC.body || DOC.head).appendChild(scr); STATS.set('ll',lang); scr.onload = function() { - KIRI.lang.set(lang); + lang_load = true; + KIRI.lang.set(map); + UI.lang(); init_one(); }; scr.onerror = function(err) { @@ -2401,7 +2551,10 @@ // set to browser default which will be overridden // by any future script loads (above) - KIRI.lang.set(); + if (!lang_load) { + KIRI.lang.set(); + UI.lang(); + } // schedule init_one to run after all page content is loaded // unless a languge script is loading first, in which case it diff --git a/src/kiri/lang.js b/src/kiri/lang.js index d5fe584c..1745774e 100644 --- a/src/kiri/lang.js +++ b/src/kiri/lang.js @@ -7,9 +7,25 @@ if (self.kiri.lang) return; const KIRI = self.kiri, LANG = self.kiri.lang = { current: {} }; + const KDFL = 'en-us'; let lset = navigator.language.toLocaleLowerCase(); + LANG.map = function(key) { + if (!key) { + return KDFL; + } + let tok = key.split('-'); + switch (tok[0]) { + case 'da': return 'da-dk'; + case 'de': return 'de-de'; + case 'en': return 'en-us'; + case 'fr': return 'fr-fr'; + case 'pl': return 'pl-pl'; + } + return KDFL; + }; + LANG.get = function() { return lset; }; @@ -39,7 +55,12 @@ // update current map from chosen map Object.keys(map).forEach(key => { if (LANG.en[key]) { - LANG.current[key] = map[key]; + let val = map[key]; + // turn arrays into newline separated strings + if (Array.isArray(val)) { + val = val.join('\n'); + } + LANG.current[key] = val; } else { invalid.push(key); } diff --git a/src/kiri/main.js b/src/kiri/main.js index da537b6d..f62cfb8f 100644 --- a/src/kiri/main.js +++ b/src/kiri/main.js @@ -4,9 +4,7 @@ (function () { - let iOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent), - // --------------- - MOTO = moto, + let MOTO = moto, KIRI = self.kiri, BASE = self.base, UTIL = BASE.util, @@ -69,7 +67,7 @@ controls: true, // show or not side menus drop_group: undefined, // optional array to group multi drop drop_layout: true, // layout on new drop - preview: true, // allow bypassing preview generation + work_alerts: true, // allow disabling work progress alerts hover: false, // when true fires mouse hover events hoverAdds: false, // when true only searches widget additions }; @@ -80,10 +78,14 @@ scale: scaleSelection, rotate: rotateSelection, meshes: function() { return selectedMeshes.slice() }, - widgets: function() { return selectedMeshes.slice().map(m => m.widget) }, + widgets: function(orall) { + let sel = selectedMeshes.slice().map(m => m.widget); + return sel.length ? sel : orall ? WIDGETS.slice() : [] + }, for_groups: forSelectedGroups, for_meshes: forSelectedMeshes, for_widgets: forSelectedWidgets, + update_bounds: updateSelectedBounds, delete: function() { platform.delete(selection.widgets()) }, export: exportSelection }; @@ -108,6 +110,7 @@ load_files: platformLoadFiles, group: platformGroup, group_done: platformGroupDone, + update: SPACE.platform.update, set_font: SPACE.platform.setFont, set_axes: SPACE.platform.setAxes, set_volume: SPACE.platform.setVolume, @@ -228,6 +231,7 @@ dec: () => { kiri.api.event.emit("busy", --busy) } }, conf: { + dbo: () => { return ls2o('ws-settings') }, get: getSettings, put: putSettings, load: loadSettings, @@ -256,6 +260,7 @@ layer_max: 0 }, device: { + code: currentDeviceCode, get: currentDeviceName, set: noop, // set during init clone: noop // set during init @@ -280,10 +285,15 @@ slice: prepareSlices, print: preparePreview, prepare: preparePreview, - animate: noop, + animate: prepareAnimation, export: prepareExport, cancel: cancelWorker, - clear: KIRI.client.clear + clear: KIRI.client.clear, + parse: loadCode + }, + group: { + merge: groupMerge, + split: groupSplit, }, hide: { alert: function(rec) { alert2cancel(rec) }, @@ -310,6 +320,10 @@ grid : function() { return false }, local : function() { return false } }, + process: { + code: currentProcessCode, + get: currentProcessName + }, platform, selection, show: { @@ -327,13 +341,17 @@ reload: reload, restore: restoreWorkspace, clear: clearWorkspace, - save: saveWorkspace + save: saveWorkspace, + set_focus: setFocus }, tweak, util: { isSecure, ui2rec: updateSettingsFromFields, - rec2ui: updateFieldsFromSettings + rec2ui: updateFieldsFromSettings, + download: downloadBlob, + b64enc: obj => { return base64js.fromByteArray(new TextEncoder().encode(JSON.stringify(obj))) }, + b64dec: obj => { return JSON.parse(new TextDecoder().decode(base64js.toByteArray(obj))) } }, view: { get: function() { return viewMode }, @@ -354,7 +372,9 @@ return map; }, new: newWidget, - all: function() { return WIDGETS.slice() }, + all: () => { return WIDGETS.slice() }, + add: (widget) => { WIDGETS.push(widget) }, + filter: (fn) => { WIDGETS = WIDGETS.filter(fn) }, for: forAllWidgets, load: Widget.loadFromCatalog, meshes: meshArray, @@ -368,6 +388,29 @@ work: KIRI.work }; + function setFocus(widgets) { + if (widgets === undefined) { + widgets = WIDGETS; + } else if (!Array.isArray) { + widgets = [ widgets ]; + } else if (widgets.length === 0) { + widgets = WIDGETS; + } + let pos = { x:0, y:0, z:0 }; + for (let widget of widgets) { + pos.x += widget.track.pos.x; + pos.y += widget.track.pos.y; + pos.z += widget.track.pos.z; + } + if (widgets.length) { + pos.x /= widgets.length; + pos.y /= widgets.length; + pos.z /= widgets.length; + } + SPACE.platform.setCenter(pos.x, -pos.y, topZ / 2); + SPACE.view.setFocus(new THREE.Vector3(pos.x, topZ / 2, -pos.y)); + } + function reload() { API.event.emit('reload'); do_reload(100); @@ -506,7 +549,7 @@ } function alert2(message, time) { - if (message === undefined) { + if (message === undefined || message === null) { return updateAlerts(true); } let rec = [message, Date.now(), time, true]; @@ -516,8 +559,10 @@ } function alert2cancel(rec) { - rec[3] = false; - updateAlerts(); + if (Array.isArray(rec)) { + rec[3] = false; + updateAlerts(); + } } function updateAlerts(clear) { @@ -640,11 +685,127 @@ return Math.max(min,Math.min(max,v)); } + function getOverlappingRanges(lo, hi) { + let ranges = []; + for (let range of settings.process.ranges) { + let in_lo = range.lo >= lo && range.lo <= hi; + let in_hi = range.hi >= lo && range.hi <= hi; + if (in_lo || in_hi) { + ranges.push(range); + } + } + return ranges; + } + + // set process override values for a range + function updateRange(lo, hi, values, add) { + let ranges = settings.process.ranges; + let slices = {}; + let min = lo; + let max = hi; + + // just remove values from matching ranges + if (!add) { + for (let range of getOverlappingRanges(lo, hi)) { + for (let key of Object.keys(values)) { + delete range.fields[key]; + } + if (Object.keys(range.fields).length === 0) { + let pos = ranges.indexOf(range); + if (pos >= 0) { + ranges.splice(pos,1); + } + } + } + API.event.emit("range.updates", ranges); + return; + } + + // flatten ranges + ranges.push({lo, hi, fields: values}); + for (let range of ranges) { + min = Math.min(range.lo, min); + max = Math.max(range.hi, max); + for (let i=range.lo; i<=range.hi; i++) { + let slice = slices[i]; + if (!slice) { + slice = slices[i] = {}; + } + for (let [key,val] of Object.entries(range.fields)) { + slice[key] = val; + } + } + } + + // merge contiguous matching ranges + ranges.length = 0; + let range; + for (let i=min; i<=max; i++) { + let slice = slices[i]; + if (slice && !range) { + range = {lo: i, hi: i, fields: slice}; + } else if (slice && range && isEquals(range.fields, slice)) { + range.hi = i; + } else if (range) { + ranges.push(range); + if (slice) { + range = {lo: i, hi: i, fields: slice}; + } else { + range = undefined; + } + } + } + + ranges.push(range); + updateFieldsFromSettings(settings.process); + API.show.alert("update ranges", 2); + API.event.emit("range.updates", ranges); + } + + let overrides = {}; + + // updates editable fields that are range dependent + function updateFieldsFromRange() { + return; + if (settings.mode !== 'FDM' || viewMode !== VIEWS.SLICE || !settings.process.ranges) { + let okeys = Object.keys(overrides); + if (okeys.length) { + updateFieldsFromSettings(overrides); + overrides = {}; + } + return; + } + let match = 0; + let values = {}; + let restores = Object.clone(overrides); + let { layer_lo, layer_hi } = API.var; + for (let range of getOverlappingRanges(API.var.layer_lo, API.var.layer_hi)) { + for (let key of Object.keys(range.fields)) { + values[key] = range.fields[key]; + overrides[key] = settings.process[key]; + delete restores[key]; + match++; + } + } + if (match) { + updateFieldsFromSettings(values); + } + let rkeys = Object.keys(restores); + if (rkeys.length) { + updateFieldsFromSettings(restores); + for (let key of rkeys) { + delete overrides[key]; + } + } + UC.refresh(); + } + function updateSlider() { API.event.emit("slider.set", { start: (API.var.layer_lo / API.var.layer_max), end: (API.var.layer_hi / API.var.layer_max) }); + updateFieldsFromRange(); } function setVisibleLayer(h, l) { @@ -675,13 +836,10 @@ } function forSelectedGroups(f) { - let m = selectedMeshes; - if (m.length === 0 && WIDGETS.length === 1) m = [ WIDGETS[0].mesh ]; - let v = []; - m.slice().forEach(function (mesh) { - if (v.indexOf(mesh.widget.group) < 0) f(mesh.widget); - v.push(mesh.widget.group); - }); + let groups = API.selection.widgets(true).map(w => w.group).uniq(); + for (let group of groups) { + f(group[0]); + } } function forSelectedWidgets(f,noauto) { @@ -764,7 +922,9 @@ } function cancelWorker() { - if (KIRI.work.isSlicing()) KIRI.work.restart(); + if (KIRI.work.isBusy()) { + KIRI.work.restart(); + } } function showSlider() { @@ -825,60 +985,103 @@ API.conf.save(); API.event.emit('slice.begin', getMode()); - let countdown = WIDGETS.length, + let slicing = WIDGETS.slice().filter(w => !w.track.ignore), + countdown = slicing.length, totalProgress, track = {}, mode = settings.mode, - now = Date.now(); + now = Date.now(), + totvert = 0, + defvert; + + // determing this widgets % of processing time estimated by vertex count + for (let widget of slicing) { + totvert += widget.getVertices().count; + } + defvert = totvert / slicing.length; setOpacity(color.slicing_opacity); STACKS.clear(); + if (settings.device.bedBelt) { + KIRI.client.clear(); + } + KIRI.client.sync(); + KIRI.client.rotate(settings); + + let segtimes = {}, + segNumber = 0, + errored = false, + startTime = Date.now(), + lastMsg; // for each widget, slice - forAllWidgets(function(widget) { - let segtimes = {}, - segNumber = 0, - errored = false, - startTime = Date.now(), - lastMsg, - camOrLaser = mode === 'CAM' || mode === 'LASER', - stack = STACKS.create(widget.id, widget.mesh); + for (let widget of slicing) { + let camOrLaser = mode === 'CAM' || mode === 'LASER', + stack = widget.stack = STACKS.create(widget.id, widget.mesh), + factor = (widget.getVertices().count / defvert); widget.stats.progress = 0; widget.setColor(color.slicing); widget.slice(settings, function(sliced, error) { + widget.rotinfo = null; let mark = Date.now(); // update UI info if (sliced) { // update segment time if (lastMsg) { - segtimes[`${segNumber++}_${lastMsg}`] = mark - startTime; + segtimes[`${widget.id}_${segNumber++}_${lastMsg}`] = mark - startTime; } API.event.emit('slice', getMode()); } - // on done - segtimes[`${segNumber}_draw`] = widget.render(stack); - // rotate stack for belt beds - if (settings.device.bedBelt && widget.rotinfo) { - stack.obj.rotate(widget.rotinfo); + // handle slicing errors + if (error && !errored) { + errored = true; + setViewMode(VIEWS.ARRANGE); + alert2(error, 5); + API.show.progress(0); + KIRI.client.restart(); + API.event.emit('slice.error', error); } - updateSliderMax(true); - setVisibleLayer(-1, 0); - if (scale === 1) { - // clear wireframe - widget.setWireframe(false, color.wireframe, color.wireframe_opacity); - widget.setOpacity(camOrLaser ? color.cam_sliced_opacity : color.sliced_opacity); - widget.setColor(color.deselected); + // discard remaining errors + if (errored) { + return; } + let alert = null; // on the last exit, update ui and call the callback - if (--countdown === 0 || error || errored) { - // mark slicing complete for prep/preview - complete.slice = true; + if (--countdown === 0) { + if (scale === 1 && feature.work_alerts) { + alert = API.show.alert("Rendering"); + }; + KIRI.client.unrotate(settings, () => { + for (let widget of slicing) { + // on done + segtimes[`${widget.id}_${segNumber++}_draw`] = widget.render(widget.stack); + // rotate stack for belt beds + if (widget.rotinfo) { + widget.stack.obj.rotate(widget.rotinfo); + } + if (scale === 1) { + // clear wireframe + widget.setWireframe(false, color.wireframe, color.wireframe_opacity); + widget.setOpacity(camOrLaser ? color.cam_sliced_opacity : color.sliced_opacity); + widget.setColor(color.deselected); + API.hide.alert(alert); + } + } + updateSliderMax(true); + setVisibleLayer(-1, 0); + if (scale === 1) { + updateStackLabelState(); + } + }); if (scale === 1) { API.show.progress(0); } + // cause visuals to update SPACE.scene.active(); + // mark slicing complete for prep/preview + complete.slice = true; API.event.emit('slice.end', getMode()); // print stats segtimes.total = Date.now() - now; @@ -887,32 +1090,24 @@ callback(); } } - // handle slicing errors - if (error && !errored) { - errored = true; - setViewMode(VIEWS.ARRANGE); - setOpacity(color.model_opacity); - platform.deselect(); - alert2(error); - } }, function(update, msg) { if (msg && msg !== lastMsg) { let mark = Date.now(); if (lastMsg) { - segtimes[`${segNumber++}_${lastMsg}`] = mark - startTime; + segtimes[`${widget.id}_${segNumber++}_${lastMsg}`] = mark - startTime; } lastMsg = msg; startTime = mark; } // on update - track[widget.id] = update; + track[widget.id] = (update || 0) * factor; totalProgress = 0; - forAllWidgets(function(w) { + for (let w of slicing) { totalProgress += (track[w.id] || 0); - }); + } API.show.progress(offset + (totalProgress / WIDGETS.length) * scale, msg); }); - }); + } } function preparePreview(callback, scale = 1, offset = 0) { @@ -933,9 +1128,7 @@ let isCam = MODE === MODES.CAM, pMode = getMode(); - if (feature.preview) { - setViewMode(VIEWS.PREVIEW); - } + setViewMode(VIEWS.PREVIEW); API.conf.save(); API.event.emit('preview.begin', pMode); @@ -944,7 +1137,7 @@ forAllWidgets(function(widget) { widget.setColor(color.cam_preview); }); - } else if (feature.preview && offset === 0) { + } else if (offset === 0) { setOpacity(color.preview_opacity); } @@ -968,7 +1161,17 @@ startTime = mark; } API.show.progress(offset + progress * scale, message); - }, function (oldout, maxSpeed) { + }, function (reply, maxSpeed) { + // handle worker errors + if (reply && reply.error) { + alert2(reply.error, 5); + setViewMode(VIEWS.ARRANGE); + API.event.emit('preview.error', reply.error); + API.show.progress(0); + SPACE.update(); + return; + } + if (lastMsg) { segtimes[`${segNumber++}_${lastMsg}`] = Date.now() - startTime; } @@ -976,7 +1179,8 @@ API.show.progress(0); if (!isCam) setOpacity(0); - if (feature.preview && output.length) { + if (output.length) { + let alert = feature.work_alerts ? API.show.alert("Rendering") : null; startTime = Date.now(); STACKS.clear(); const stack = STACKS.create('print', SPACE.platform.world) @@ -990,6 +1194,7 @@ ri.dy = settings.device.bedDepth / 2; stack.obj.rotate(WIDGETS[0].rotinfo); } + API.hide.alert(alert); segtimes[`${segNumber}_draw`] = Date.now() - startTime; } @@ -1000,12 +1205,11 @@ API.event.emit('print', pMode); API.event.emit('preview.end', pMode); - if (feature.preview) { - SPACE.update(); - updateSliderMax(true); - setVisibleLayer(-1, 0); - updateSpeeds(maxSpeed); - } + SPACE.update(); + updateSliderMax(true); + setVisibleLayer(-1, 0); + updateSpeeds(maxSpeed); + updateStackLabelState(); // mark preview complete for export complete.preview = true; @@ -1016,6 +1220,10 @@ }); } + function prepareAnimation() { + API.event.emit("function.animate", {mode: settings.mode}); + } + function prepareExport() { const argsave = arguments; if (!complete.preview) { @@ -1028,11 +1236,18 @@ KIRI.export(...argsave); } + function updateStackLabelState() { + // match label checkboxes to preference + for (let label of STACKS.getLabels()) { + let check = `${settings.mode}-${viewMode}-${label}`; + STACKS.setVisible(label, settings.labels[check] !== false); + } + } + function loadCode(code, type) { + API.event.emit("code.load", {code, type}); setViewMode(VIEWS.PREVIEW); setOpacity(0); - - API.event.emit("code.load", {code, type}); KIRI.client.parse({code, type, settings}, progress => { API.show.progress(progress, "parsing"); }, (layers, maxSpeed) => { @@ -1043,6 +1258,7 @@ updateSliderMax(true); updateSpeeds(maxSpeed); showSlices(); + updateStackLabelState(); SPACE.update(); API.event.emit("code.loaded", {code, type}); }); @@ -1125,6 +1341,14 @@ * Selection Functions ******************************************************************* */ + function groupMerge() { + Widget.Groups.merge(API.selection.widgets(true)); + } + + function groupSplit() { + Widget.Groups.split(API.selection.widgets(false)); + } + function updateSelectedInfo() { let bounds = new THREE.Box3(), track; forSelectedMeshes(mesh => { @@ -1155,18 +1379,73 @@ updateSelectedBounds(); } - function updateSelectedBounds() { + function fitDeviceToWidgets() { + let maxy = 0; + forAllWidgets(function(widget) { + let wb = widget.mesh.getBoundingBox().clone(); + maxy = Math.max(maxy, wb.max.y - wb.min.y); + }); + let dev = settings.device; + if (maxy > dev.bedDepth) { + dev.bedDepthSave = dev.bedDepth; + dev.bedDepth = maxy + 10; + SPACE.platform.setSize( + parseInt(dev.bedWidth), + parseInt(dev.bedDepth), + parseFloat(dev.bedHeight), + parseFloat(dev.maxHeight) + ); + SPACE.platform.update(); + return true; + } + } + + function updateSelectedBounds(widgets) { // update bounds on selection for drag limiting + let isBelt = settings.device.bedBelt; + if (isBelt) { + if (fitDeviceToWidgets()) { + platform.update_origin(); + SPACE.update(); + } + } + let dvy = settings.device.bedDepth; + let dvx = settings.device.bedWidth; let bounds_sel = new THREE.Box3(); - forSelectedWidgets(function(widget) { + if (!widgets) { + widgets = selectedMeshes.map(m => m.widget); + } + for (let widget of widgets) { let wp = widget.track.pos; + let bx = widget.track.box; + let miny = wp.y - bx.h/2 + dvy/2; + let maxy = wp.y + bx.h/2 + dvy/2; + let minx = wp.x - bx.w/2 + dvx/2; + let maxx = wp.x + bx.w/2 + dvx/2; + + // keep widget in bounds when rotated or scaled + let ylo = miny < 0; + let yhi = !isBelt && maxy > dvy + if (ylo && !yhi) { + widget.move(0, -miny, 0); + } else if (yhi && !ylo) { + widget.move(0, dvy - maxy, 0); + } + let xlo = minx < 0; + let xhi = maxx > dvx; + if (xlo && !xhi) { + widget.move(-minx, 0, 0); + } else if (xhi && !xlo) { + widget.move(dvx - maxx, 0, 0); + } + let wb = widget.mesh.getBoundingBox().clone(); wb.min.x += wp.x; wb.max.x += wp.x; wb.min.y += wp.y; wb.max.y += wp.y; bounds_sel.union(wb); - }); + } settings.bounds_sel = bounds_sel; } @@ -1227,18 +1506,23 @@ let outs = []; widgets.forEach(widget => { let mesh = widget.mesh; - let geo = new THREE.Geometry().fromBufferGeometry(mesh.geometry); + let geo = mesh.geometry; outs.push({geo, widget}); - facets += geo.faces.length; + facets += geo.attributes.position.count; }); let stl = new Uint8Array(80 + 4 + facets * 50); let dat = new DataView(stl.buffer); let pos = 84; dat.setInt32(80, facets, true); - outs.forEach(out => { - let { faces, vertices } = out.geo; - for (let i=0, il=faces.length; i 1) { let {x, y, z} = out.widget.track.pos; @@ -1246,21 +1530,21 @@ yo = y; zo = z; } - dat.setFloat32(pos + 0, normal.x, true); - dat.setFloat32(pos + 4, normal.y, true); - dat.setFloat32(pos + 8, normal.z, true); - dat.setFloat32(pos + 12, vertices[a].x + xo, true); - dat.setFloat32(pos + 16, vertices[a].y + yo, true); - dat.setFloat32(pos + 20, vertices[a].z + zo, true); - dat.setFloat32(pos + 24, vertices[b].x + xo, true); - dat.setFloat32(pos + 28, vertices[b].y + yo, true); - dat.setFloat32(pos + 32, vertices[b].z + zo, true); - dat.setFloat32(pos + 36, vertices[c].x + xo, true); - dat.setFloat32(pos + 40, vertices[c].y + yo, true); - dat.setFloat32(pos + 44, vertices[c].z + zo, true); + dat.setFloat32(pos + 0, norm.x, true); + dat.setFloat32(pos + 4, norm.y, true); + dat.setFloat32(pos + 8, norm.z, true); + dat.setFloat32(pos + 12, p0.x + xo, true); + dat.setFloat32(pos + 16, p0.y + yo, true); + dat.setFloat32(pos + 20, p0.z + zo, true); + dat.setFloat32(pos + 24, p1.x + xo, true); + dat.setFloat32(pos + 28, p1.y + yo, true); + dat.setFloat32(pos + 32, p1.z + zo, true); + dat.setFloat32(pos + 36, p2.x + xo, true); + dat.setFloat32(pos + 40, p2.y + yo, true); + dat.setFloat32(pos + 44, p2.z + zo, true); pos += 50; } - }); + } return stl; } @@ -1390,27 +1674,20 @@ wb.max.y += wp.y; bounds.union(wb); }); - if (settings.device.bedBelt) { - let dev = settings.device, - isBelt = dev.bedBelt, - maxy = bounds.max.y - bounds.min.y; - // if (maxy > dev.bedDepth) { - // console.log({maxy}, settings.device.bedDepth) - // SPACE.platform.setSize( - // parseInt(dev.bedWidth), - // parseInt(maxy), - // parseFloat(dev.bedHeight), - // parseFloat(dev.maxHeight) - // ); - // } - } return settings.bounds = bounds; } - function platformSelect(widget, shift) { + function platformSelect(widget, shift, recurse = true) { if (viewMode !== VIEWS.ARRANGE) { return; } + // apply deselect to entire group + if (recurse && widget && widget.group.length > 1) { + for (let w of widget.group) { + platformSelect(w, true, false); + } + return; + } let mesh = widget.mesh, sel = (selectedMeshes.indexOf(mesh) >= 0); if (sel) { @@ -1443,9 +1720,9 @@ let menu_show = selcount ? 'flex': ''; $('winfo').style.display = 'none'; if (selcount) { - UI.scale.classList.add('lt-enabled'); - UI.rotate.classList.add('lt-enabled'); - UI.nozzle.classList.add('lt-enabled'); + UI.scale.classList.add('lt-active'); + UI.rotate.classList.add('lt-active'); + UI.nozzle.classList.add('lt-active'); UI.trash.style.display = 'flex'; if (feature.meta && selcount === 1) { let sel = selection.widgets()[0]; @@ -1465,9 +1742,9 @@ } } } else { - UI.scale.classList.remove('lt-enabled'); - UI.rotate.classList.remove('lt-enabled'); - UI.nozzle.classList.remove('lt-enabled'); + UI.scale.classList.remove('lt-active'); + UI.rotate.classList.remove('lt-active'); + UI.nozzle.classList.remove('lt-active'); UI.trash.style.display = ''; } UI.nozzle.style.display = extruders && extruders.length > 1 ? 'flex' : ''; @@ -1485,12 +1762,19 @@ } } - function platformDeselect(widget) { + function platformDeselect(widget, recurse = true) { if (viewMode !== VIEWS.ARRANGE) { // don't de-select and re-color widgets in, // for example, sliced or preview modes return; } + // apply deselect to entire group + if (recurse && widget && widget.group.length > 1) { + for (let w of widget.group) { + platformDeselect(w, false); + } + return; + } if (!widget) { forAllWidgets(function(widget) { platform.deselect(widget); @@ -1647,7 +1931,7 @@ forAllWidgets(function(w) { platform.select(w, true) }) } - function platformLayout(event, space) { + function platformLayout(event, space = settings.controller.spaceLayout) { let auto = UI.autoLayout.checked, proc = settings.process, dev = settings.device, @@ -1665,6 +1949,10 @@ break; case MODES.FDM: space = space || ((proc.sliceSupportExtra || 0) * 2) + 1; + // auto resize device to support a larger object + if (isBelt) { + fitDeviceToWidgets(); + } break; } @@ -1717,7 +2005,12 @@ if (isBelt) { let bounds = platformUpdateBounds(), movey = -(dev.bedDepth / 2 + bounds.min.y); - forAllWidgets(widget => widget.move(0, movey + 5, 0)); + forAllWidgets(widget => { + // only move the root widget in the group + if (widget.id === widget.group.id) { + widget.move(0, movey + 5, 0) + } + }); } platform.update_origin(); @@ -1781,18 +2074,41 @@ } } + function isEquals(o1, o2) { + if (o1 == o2) return true; + if (Array.isArray(o1) && Array.isArray(o2)) { + if (o1.length === o2.length) { + for (let i=0; i 0 || layer_hi < layer_max) { + values = Object.clone(process); + add = true; + } + updateSettingsFromFields(values, undefined, changes); + if (range) { + updateRange(range.lo, range.hi, changes, add); + } + } else { + updateSettingsFromFields(process); + } if (device.extruders && device.extruders[device.internal]) { updateSettingsFromFields(device.extruders[device.internal]); } API.conf.save(); - $('mode-device').innerText = settings.device.deviceName; - $('mode-profile').innerText = settings.process.processName; + let compare = sproc[mode][cproc[mode]]; + let same = true; + if (compare) + for (let [key, val] of Object.entries(compare).filter(v => v[0] !== 'processName')) { + let tval = process[key]; + // outputLoopLayers misbehaving and setting null on empty + if (val === '' && tval == null) { + continue; + } + if (Array.isArray(tval) && Array.isArray(val)) { + if (JSON.stringify(tval) == JSON.stringify(val)) { + continue; + } + } + if (tval != val) { + // console.log(key, 'expected', val, 'got', tval); + same = false; + } + } + $('mode-device').innerText = device.deviceName; + $('mode-profile').innerText = `${cproc[mode]}${same ? '' : ' *'}`; } function saveSettings() { @@ -1895,9 +2254,11 @@ } const mode = settings.mode; settings.sproc[mode].default = settings.process; + settings.sproc[mode][settings.process.processName] = settings.process; settings.device.bedBelt = UI.deviceBelt.checked; settings.device.bedRound = UI.deviceRound.checked; settings.device.originCenter = UI.deviceOrigin.checked; + settings.device.fwRetract = UI.fwRetract.checked; SDB.setItem('ws-settings', JSON.stringify(settings)); API.event.emit('settings.saved', settings); } @@ -1905,18 +2266,19 @@ function settingsImport(data, ask) { if (typeof(data) === 'string') { try { - data = JSON.parse(atob(data)); + data = API.util.b64dec(data); } catch (e) { - UC.alert('invalid settings format'); + UC.alert('invalid import format'); console.log('data',data); return; } } if (LOCAL) console.log('import',data); let isSettings = (data.settings && data.version && data.time); + let isProcess = (data.process && data.version && data.time && data.mode && data.name); let isDevice = (data.device && data.version && data.time); let isWork = (data.work); - if (!isSettings && !isDevice) { + if (!isSettings && !isDevice && !isProcess) { UC.alert('invalid settings or device format'); console.log('data',data); return; @@ -1935,11 +2297,25 @@ API.show.devices(); } } + if (isProcess) { + if (settings.sproc[data.mode][data.name]) { + UC.confirm(`Replace process ${data.name}?`).then(yes => { + if (yes) { + settings.sproc[data.mode][data.name] = data.process; + API.conf.show(); + } + }); + } else { + settings.sproc[data.mode][data.name] = data.process; + API.conf.show(); + } + } if (isSettings) { + clearWorkspace(); settings = CONF.normalize(data.settings); SDB.setItem('ws-settings', JSON.stringify(settings)); - if (LOCAL) console.log('settings',Object.clone(settings)); restoreSettings(); + if (LOCAL) console.log('settings',Object.clone(settings)); if (isWork) { API.platform.clear(); KIRI.codec.decode(data.work).forEach(widget => { @@ -1957,7 +2333,8 @@ if (ask) { let opt = {}; let prompt = isDevice ? - `Import device "${data.device}"?` : + `Import device "${data.device}"?` : isProcess ? + `Import process "${data.name}"?` : `Import settings made in Kiri:Moto version ${data.version} on
${new Date(data.time)}?`; if (data.screen) { opt.pre = [ @@ -1980,7 +2357,7 @@ const shot = opts.work || opts.screen ? SPACE.screenshot() : undefined; const work = opts.work ? KIRI.codec.encode(WIDGETS,{_json_:true}) : undefined; const view = opts.work ? SPACE.view.save() : undefined; - return btoa(JSON.stringify({ + return API.util.b64enc({ settings: settings, version: KIRI.version, screen: shot, @@ -1991,7 +2368,7 @@ moto: MOTO.id, init: SDB.getItem('kiri-init'), time: Date.now() - })); + }); } function platformLoadFiles(files,group) { @@ -2005,6 +2382,7 @@ isstl = lower.indexOf(".stl") > 0, issvg = lower.indexOf(".svg") > 0, ispng = lower.indexOf(".png") > 0, + isjpg = lower.indexOf(".jpg") > 0, isgcode = lower.indexOf(".gcode") > 0 || lower.indexOf(".nc") > 0, isset = lower.indexOf(".b64") > 0 || lower.indexOf(".km") > 0; reader.file = files[i]; @@ -2029,9 +2407,10 @@ if (issvg) loadCode(e.target.result, 'svg'); if (isset) settingsImport(e.target.result, true); if (ispng) loadImageDialog(e.target.result, e.target.file.name); + if (isjpg) loadImageConvert(e.target.result, e.target.file.name); if (--loaded === 0) platform.group_done(isgcode); }; - if (isstl || ispng) { + if (isstl || ispng || isjpg) { reader.readAsArrayBuffer(reader.file); } else { reader.readAsBinaryString(reader.file); @@ -2039,6 +2418,25 @@ } } + function loadImageConvert(res, name) { + let url = URL.createObjectURL(new Blob([res])); + + $('mod-any').innerHTML = ``; + + let img = $('xsrc'); + let can = $('xdst'); + + img.onload = () => { + can.width = img.width; + can.height = img.height; + let ctx = can.getContext('2d'); + ctx.drawImage(img, 0, 0); + fetch(can.toDataURL()).then(r => r.arrayBuffer()).then(data => { + loadImageDialog(data, name); + }); + }; + } + function loadFile() { $('load-file').onchange = function(event) { DBUG.log(event); @@ -2053,6 +2451,7 @@ let newWidgets = [], oldWidgets = js2o(SDB.getItem('ws-widgets'), []); forAllWidgets(function(widget) { + if (widget.synth) return; newWidgets.push(widget.id); oldWidgets.remove(widget.id); widget.saveState(); @@ -2079,7 +2478,6 @@ function restoreSettings(save) { let newset = ls2o('ws-settings') || settings; - settings = CONF.normalize(newset); // override camera from settings if (settings.controller.view) { @@ -2242,15 +2640,34 @@ } } + function exportSettings(e) { + let mode = getMode(), + name = e.target.getAttribute("name"), + data = API.util.b64enc({ + process: settings.sproc[mode][name], + version: KIRI.version, + moto: MOTO.id, + time: Date.now(), + mode, + name + }); + UC.prompt("Export Process Filename", name).then(name => { + if (name) { + API.util.download(data, `${name}.km`); + } + }); + } + function loadSettings(e, named) { let mode = getMode(), - name = e ? e.target.getAttribute("load") : named || "default", + name = e ? e.target.getAttribute("load") : named || currentProcessName() || "default", load = settings.sproc[mode][name]; if (!load) return; - // clone loaded process into settings - settings.process = clone(load); + // cloning loaded process into settings requires user to save + // process before switching devices or risk losing any changes + settings.process = load;//clone(load); // update process name settings.process.processName = name; // save named process with the current device @@ -2287,6 +2704,7 @@ let row = DOC.createElement('div'), load = DOC.createElement('button'), edit = DOC.createElement('button'), + xprt = DOC.createElement('button'), del = DOC.createElement('button'), name = sk; @@ -2311,9 +2729,15 @@ edit.innerHTML = ''; edit.onclick = editSettings; + xprt.setAttribute('name', sk); + xprt.setAttribute('title', 'export'); + xprt.innerHTML = ''; + xprt.onclick = exportSettings; + row.setAttribute("class", "flow-row"); row.appendChild(edit); row.appendChild(load); + row.appendChild(xprt); row.appendChild(del); table.appendChild(row); }); @@ -2411,7 +2835,6 @@ complete = {}; UI.back.style.display = ''; UI.render.style.display = ''; - UI.render.classList.add('lt-enabled'); // KIRI.work.clear(); STACKS.clear(); hideSlider(); @@ -2492,6 +2915,7 @@ // other housekeeping triggerSettingsEvent(); platformUpdateSelected(); + updateSelectedBounds(WIDGETS); updateFields(); // because device dialog, if showing, needs to be updated if (modalShowing()) { @@ -2508,11 +2932,29 @@ return settings.filter[getMode()]; } + function currentDeviceCode() { + return settings.devices[currentDeviceName()]; + } + + function currentProcessName() { + return settings.cproc[getMode()]; + } + + function currentProcessCode() { + return settings.sproc[getMode()][currentProcessName()]; + } + function setControlsVisible(show) { $('mid-lcol').style.display = show ? 'flex' : 'none'; $('mid-rcol').style.display = show ? 'flex' : 'none'; } + function downloadBlob(data, filename) { + let url = WIN.URL.createObjectURL(new Blob([data], {type: "octet/stream"})); + $('mod-any').innerHTML = `x`; + $('_dexport_').click(); + } + // prevent safari from exiting full screen mode DOC.onkeydown = function (evt) { if (evt.keyCode == 27) evt.preventDefault() } @@ -2523,4 +2965,6 @@ // new module loading kiri.loader.forEach(mod => { mod(kiri.api)} ); + // upon restore, seed presets + API.event.emit('preset', API.conf.dbo()); })(); diff --git a/src/kiri/print.js b/src/kiri/print.js index 86a12c12..44583264 100644 --- a/src/kiri/print.js +++ b/src/kiri/print.js @@ -133,33 +133,102 @@ maxf = 0, seq = [], autolayer = true, - newlayer = false; + newlayer = false, + arcdivs = Math.PI / 12, + hasmoved = false; const output = scope.output = [ seq ]; const beltaxis = { X: "X", Y: "Z", Z: "Y", E: "E", F: "F" }; const beltfact = Math.cos(Math.PI/4); + function LOG() { + console.log(...[...arguments].map(o => Object.clone(o))); + } + + function G2G3(g2, line) { + const rec = {}; + + line.forEach(function(tok) { + rec[tok.charAt(0)] = parseFloat(tok.substring(1)); + }); + + let center = { x:0, y:0, r:0 }; + + if (rec.I !== undefined && rec.J !== undefined) { + center.x = pos.X + rec.I; + center.y = pos.Y + rec.J; + //center.r = TODO + console.log("G[2,3] IJ not supported"); + } else if (rec.R !== undefined) { + let pd = { x: rec.X - pos.X, y: rec.Y - pos.Y }; + let dst = Math.sqrt(pd.x * pd.x + pd.y * pd.y) / 2; + let pr2; + if (Math.abs(dst - rec.R) < 0.001) { + // center point radius + pr2 = { x: (rec.X + pos.X) / 2, y: (rec.Y + pos.Y) / 2}; + } else { + // triangulate + pr2 = BASE.util.center2pr({ + x: pos.X, + y: pos.Y + }, { + x: rec.X, + y: rec.Y + }, rec.R)[g2 ? 1 : 0]; + } + center.x = pr2.x; + center.y = pr2.y; + center.r = rec.R; + } else { + console.log({malfomed_arc: line}); + } + + // line angles + let a1 = Math.atan2(center.y - pos.Y, center.x - pos.X) + Math.PI; + let a2 = Math.atan2(center.y - rec.Y, center.x - rec.X) + Math.PI; + let ad = BASE.util.thetaDiff(a1, a2, true); + let steps = Math.floor(Math.abs(ad) / arcdivs); + let step = (Math.abs(ad) > 0.001 ? ad : Math.PI * 2) / steps; + let rot = a1 + step; + + // LOG({first: pos, last: rec, center, a1, a2, ad, step, rot, line}); + // G0G1(false, [`X${center.x}`, `Y${center.y}`, `E1`]); + + let pc = { X: pos.X, Y: pos.Y }; + for (let i=0; i<=steps-2; i++) { + let np = { + X: center.x + Math.cos(rot) * center.r, + Y: center.y + Math.sin(rot) * center.r + }; + rot += step; + G0G1(false, [`X${np.X}`, `Y${np.Y}`, `E1`]); + } + + G0G1(false, [`X${rec.X}`, `Y${rec.Y}`, `E1`]); + + pos.X = rec.X; + pos.Y = rec.Y; + } + function G0G1(g0, line) { const mov = {}; + const axes = {}; line.forEach(function(tok) { let axis = tok.charAt(0); if (morph && belt) { axis = beltaxis[axis]; } + let val = parseFloat(tok.substring(1)); + axes[axis] = val; if (abs) { - pos[axis] = parseFloat(tok.substring(1)); + pos[axis] = val; } else { - mov[axis] = parseFloat(tok.substring(1)); + mov[axis] = val; + pos[axis] += val; } }); - if (!abs) { - for (let [k,v] of Object.entries(mov)) { - pos[k] += v; - } - } - const point = newPoint( factor * pos.X + off.X + xoff.X, factor * pos.Y + off.Y + xoff.Y, @@ -172,7 +241,7 @@ } const retract = (fdm && pos.E < 0) || undefined; - const moving = g0 || (fdm && pos.E <= 0); + const moving = g0 || (fdm && (pos.E <= 0 || !(axes.X || axes.Y || axes.Z))); if (!moving && point.x) bounds.min.x = Math.min(bounds.min.x, point.x); if (!moving && point.x) bounds.max.x = Math.max(bounds.max.x, point.x); @@ -210,6 +279,11 @@ output.push(seq); } + if (!hasmoved && !moving) { + seq.height = seq.Z = pos.Z; + hasmoved = true; + } + // debug extrusion rate if (false && fdm && lastPos) { let dE = (absE ? pos.E - lastPos.E : pos.E); @@ -278,12 +352,25 @@ pos[tok.charAt(0)] = parseFloat(tok.substring(1)); }); break; + case 'G10': + seq.last().retract = true; + break; + case 'G11': + break; case 'G0': G0G1(true, line); break; case 'G1': G0G1(false, line); break; + case 'G2': + // turn arc into a series of points + G2G3(true, line) + break; + case 'G3': + // turn arc into a series of points + G2G3(false, line); + break; case 'M6': tool = parseInt(line[0].substring(1)); break; @@ -371,7 +458,6 @@ first = true, close = !options.open, last = startPoint, - wipeDist = options.wipe || 0, coastDist = options.coast || 0, tool = options.tool; @@ -385,7 +471,7 @@ if (options.onfirst) { options.onfirst(point); } - // move from startPoint to point + // move to first output point on poly addOutput(output, point, 0, moveSpeed, tool); first = false; } else { @@ -418,19 +504,18 @@ */ PRO.slicePrintPath = function(slice, startPoint, offset, output, opt = {}) { // console.log({slicePrintPath: slice.index, ext:slice.extruder}); - let i, preout = [], scope = this, settings = this.settings, device = settings.device, - process = settings.process, + process = opt.params || settings.process, extruder = slice.extruder || 0, nozzleSize = device.extruders[extruder].extNozzle, firstLayer = opt.first || false, - minSeek = nozzleSize * (opt.minSeek || 1.5), thinWall = nozzleSize * (opt.thinWall || 1.75), retractDist = opt.retractOver || 2, + solidWidth = process.sliceFillWidth || 1, fillMult = opt.mult || process.outputFillMult, shellMult = opt.mult || process.outputShellMult || (process.laserSliceHeight >= 0 ? 1 : 0), shellOrder = {"out-in":-1,"in-out":1}[process.sliceShellOrder] || -1, @@ -442,14 +527,17 @@ firstPrintMult = process.firstLayerPrintMult, printSpeed = opt.speed || (firstLayer ? firstShellSpeed : process.outputFeedrate), fillSpeed = opt.speed || opt.fillSpeed || (firstLayer ? firstFillSpeed || firstShellSpeed : process.outputFeedrate), + infillSpeed = process.sliceFillRate || opt.infillSpeed || fillSpeed || printSpeed, moveSpeed = process.outputSeekrate, origin = startPoint.add(offset), zhop = process.zHopDistance || 0, antiBacklash = process.antiBacklash, - doSupport = opt.support, + wipeDist = process.outputRetractWipe || 0, isBelt = device.bedBelt, startClone = startPoint.clone(), - z = slice.z; + seedPoint = opt.seedPoint || startPoint, + z = slice.z, + lastPoly; // apply first layer extrusion multipliers if (firstLayer) { @@ -459,28 +547,202 @@ } function retract() { - if (preout.length) preout.last().retract = true; + let array = preout.length ? preout : output; + if (array.length) { + let last = array.last(); + last.retract = true; + if (wipeDist && lastPoly && last.point) { + let endpoint = last.point.followTo(lastPoly.center(true), wipeDist); + if (endpoint.inPolygon(lastPoly)) { + addOutput(array, endpoint); + } + } + } else if (opt.pretract) { + opt.pretract(wipeDist); + } else { + console.log('unable to retract. no preout or output'); + } } function intersectsTop(p1, p2) { + if (opt.danger) { + return retractRequired(p1, p2); + } let int = false; POLY.flatten(slice.topPolys().clone(true)).forEach(function(poly) { if (!int) poly.forEachSegment(function(s1, s2) { if (UTIL.intersect(p1,p2,s1,s2,BASE.key.SEGINT)) { - int = true; - return int; + return int = true; } }); }); return int; } - function outputTraces(poly, extrude) { + // returns true if no path around and retract required + // returns false if routed around or no retract + function retractRequired(p1, p2) { + const dbug = false; + + let ints = []; + let tops = POLY.flatten(slice.topPolys().clone(true)); + for (let poly of tops) { + poly.forEachSegment(function(s1, s2) { + let ip = UTIL.intersect(p1,p2,s1,s2,BASE.key.SEGINT); + if (ip) { + ints.push({ip, poly}); + } + }); + } + + // no intersections + if (ints.length === 0) { + return false; + } + + // odd # of intersections ?!? do retraction + if (ints.length && ints.length % 2 !== 0) { + if (dbug === slice.index) console.log(slice.index, {odd_intersects: ints}); + return true; + } + + // sort by distance + ints.sort((a, b) => { + return a.ip.dist - b.ip.dist; + }); + + let valid = ints.length; + + // check pairs. eliminate too close points. + // pairs must intersect same poly or retract. + for (let i=0; i 2) { + if (dbug === slice.index) console.log(slice.index, {complex_route: valid}); + return true; + } + + if (valid) { + // can route around intersected top polys + for (let i=0; ii.ip.dist), + i1, i2, same: i1.poly === i2.poly, + route, + r1, r1d, r1s, r1e, + r2, r2d, r2s, r2e, + isCW}); + + for (let p of route) { + addOutput(preout, p, 0, moveSpeed, extruder); + } + + // output last point + addOutput(preout, i2.ip, 0, moveSpeed, extruder); + } + } + + return false; + } + + function outputTraces(poly, opt = {}) { if (!poly) return; if (Array.isArray(poly)) { - outputOrderClosest(poly, function(next) { - outputTraces(next, extrude); - }, null); + if (opt.sort) { + let polys = poly.slice().sort(function(a,b) { + return (a.perimeter() - b.perimeter()) * opt.sort; + }); + let debug = polys.length > 3; + let last; + while (polys.length) { + let next; + for (let p of polys) { + if (!last) { + next = p; + break; + } + if (opt.sort > 0) { + // in-out + if (last.isInside(p)) { + next = p; + break; + } + } else { + // out-in + if (p.isInside(last)) { + next = p; + break; + } + } + } + if (next) { + last = next; + polys.remove(next); + outputTraces(next, opt); + } else { + last = null; + } + } + } else { + outputOrderClosest(poly, function(next) { + outputTraces(next, opt); + }, null); + } } else { let finishShell = poly.depth === 0 && !firstLayer; startPoint = scope.polyPrintPath(poly, startPoint, preout, { @@ -489,13 +751,18 @@ accel: finishShell, wipe: process.outputWipeDistance || 0, coast: firstLayer ? 0 : coastDist, - extrude: pref(extrude, shellMult), + extrude: pref(opt.extrude, shellMult), onfirst: function(firstPoint) { - if (startPoint.distTo2D(firstPoint) > retractDist) { - retract(); + let from = seedPoint || startPoint; + if (from.distTo2D(firstPoint) > retractDist) { + if (intersectsTop(from, firstPoint)) { + retract(); + } } + seedPoint = null; } }); + lastPoly = slice.lastPoly = poly; } } @@ -530,12 +797,12 @@ // let emit = i === 0 ? 0 : extrude; addOutput(preout, p, emit, speed || printSpeed, extruder); lp = p; - }); + }, !poly.open); return lp; }); } - function outputFills(lines, options) { + function outputFills(lines, opt = {}) { if (!lines || lines.length === 0) { return; } @@ -544,10 +811,10 @@ start = 0, skip = false, lastIndex = -1, - opt = options || {}, + flow = opt.flow || 1, near = opt.near || false, fast = opt.fast || false, - fill = opt.fill >= 0 ? opt.fill : fillMult, + fill = (opt.fill >= 0 ? opt.fill : fillMult) * flow, thinDist = near ? thinWall : thinWall; while (lines && marked < lines.length) { @@ -684,11 +951,11 @@ * the special exception that depth is considered into distance * so that inner polygons are emitted first. * - * @param {Array} array of Polygon or Polygon wrappers - * @param {Function} fn - * @param {Function} fnp convert 'next' object into a Polygon + * @param {Array} array of Polygons or Polygon wrappers (tops) + * @param {Function} fn call to emit next candidate + * @param {Function} fnp convert 'next' object into a Polygon for closeness */ - function outputOrderClosest(array, fn, fnp, newTop) { + function outputOrderClosest(array, fn, fnp) { if (array.length === 1) { return fn(array[0]); } @@ -708,7 +975,6 @@ d: find.distance - (poly.depth * thinWall), }); } - newTop = false; if (order.length === 0) { return; } @@ -753,37 +1019,31 @@ // and enforce optimal shell order (outer first) if (isBelt && opt.onBelt) { startPoint = startClone; - shellOrder = 1; + shellOrder = -1; } - // top object - let bounds = POLY.flatten(next.shellsAtDepth(0).clone(true)); + // innermost shells + let inner = next.innerShells() || []; // output inner polygons - if (shellOrder === 1) - outputTraces([].appendAll(next.innerShells() || [])); + if (shellOrder === 1) outputTraces(inner, { sort: shellOrder }); - // sort perimeter polygon by length to go out-to-in or in-to-out - (next.shells || []).sort(function(a,b) { - return a.perimeter() > b.perimeter() ? shellOrder : -shellOrder; - }).forEach(function(poly, index) { - outputTraces(poly); - }); + outputTraces(next.shells, { sort: shellOrder }); // output outer polygons - if (shellOrder === -1) - outputTraces([].appendAll(next.innerShells() || [])); + if (shellOrder === -1) outputTraces(inner, { sort: shellOrder }); // output thin fill outputFills(next.thin_fill, {near: true}); // then output solid and sparse fill - outputFills(next.fill_lines); - outputSparse(next.fill_sparse, sparseMult); + outputFills(next.fill_lines, {flow: solidWidth}); + outputSparse(next.fill_sparse, sparseMult, infillSpeed); lastTop = next; } }, function(obj) { + // for tops return obj instanceof Polygon ? obj : obj.poly; }); @@ -971,15 +1231,15 @@ function constOp(tok, consts, opch, op) { let pos, v1, v2; if ((pos = tok.indexOf(opch)) > 0) { - v1 = consts[tok.substring(0,pos)] || 0; - v2 = parseInt(tok.substring(pos+1)) || 0; - return op(v1,v2); + v1 = parseFloat(consts[tok.substring(0,pos)] || 0); + v2 = parseFloat(tok.substring(pos+1)) || 0; + return op(v1,v2).round(4); } else { return null; } } - function constReplace(str, consts, start) { + function constReplace(str, consts, start, pad, short) { let cs = str.indexOf("{", start || 0), ce = str.indexOf("}", cs), tok, nutok, nustr; @@ -991,9 +1251,24 @@ constOp(tok, consts, "/", function(v1,v2) { return v1/v2 }) || constOp(tok, consts, "*", function(v1,v2) { return v1*v2 }) || consts[tok] || 0; + if (pad) { + nutok = nutok.toString(); + let oldln = ce-cs+1; + let tokln = nutok.length; + if (tokln < oldln) { + short = (short || 1) + (oldln - tokln); + } + } nustr = str.replace("{"+tok+"}",nutok); - return constReplace(nustr, consts, ce+1+(nustr.length-str.length)); + return constReplace(nustr, consts, ce+1+(nustr.length-str.length), pad, short); } else { + // insert compensating spaces for accumulated replace string shortages + if (short) { + let si = str.indexOf(';'); + if (si > 0) { + str = str.replace(';', ';'.padStart(short,' ')); + } + } return str; } } diff --git a/src/kiri/slicer.js b/src/kiri/slicer.js index f3013cc9..94816a56 100644 --- a/src/kiri/slicer.js +++ b/src/kiri/slicer.js @@ -221,6 +221,7 @@ zM = Math.max(p1.z, p2.z, p3.z), bm = Math.floor(zm * zScale), bM = Math.ceil(zM * zScale); + if (bm < 0) bm = 0; for (j = bm; j < bM; j++) { buckets[j].push(p1); buckets[j].push(p2); @@ -529,6 +530,9 @@ // for topo slices, we just need the raw lines if (!topoMode && !simpleMode) { slice.groups = connectLines(slice.lines, slice.z); + if (options.union) { + slice.groups = POLY.flatten(POLY.union(POLY.nest(slice.groups), null, true), null, true); + } if ((debug && slice.lines.excessive) || xray) { const dash = xray || 3; slice.lines.forEach((line, i) => { diff --git a/src/kiri/slicer2.js b/src/kiri/slicer2.js index 814257ad..d06e308f 100644 --- a/src/kiri/slicer2.js +++ b/src/kiri/slicer2.js @@ -722,7 +722,11 @@ } } else { // no more closest polys (open set) - emit(BASE.newPolygon().addPoints(array)); + if (opt.openok) { + emit(BASE.newPolygon().addPoints(array).setOpen()); + } else { + emit(BASE.newPolygon().addPoints(array)); + } break loop; } } diff --git a/src/kiri/stack.js b/src/kiri/stack.js index 206c3509..48d6f268 100644 --- a/src/kiri/stack.js +++ b/src/kiri/stack.js @@ -44,6 +44,17 @@ this.show(); for (let i=0, s=this.slices, len=s.length; i= newMin && i <= newMax; + if (s[i].visible) this.lastVis = s[i]; + } + } + + setLastFraction(frac = 1) { + if (this.lastVis) + for (let mesh of this.lastVis.children) { + let geo = mesh.geometry; + let pos = geo.attributes.position; + let len = frac === 1 ? Infinity : Math.round(pos.count * frac); + geo.setDrawRange(0, len); } } diff --git a/src/kiri/stacks.js b/src/kiri/stacks.js index d1571633..dce39e29 100644 --- a/src/kiri/stacks.js +++ b/src/kiri/stacks.js @@ -52,6 +52,24 @@ return stacks[name]; } + function getStacks() { + return Object.entries(stacks); + } + + function getLabels() { + return Object.keys(DYN); + } + + function setVisible(label, bool) { + let ctrl = DYN[label]; + if (ctrl) { + ctrl.toggle.checked = bool; + ctrl.group.forEach(mat => { + mat.visible = bool; + }); + } + } + function create(name, view) { if (stacks[name]) { return stacks[name]; @@ -70,6 +88,11 @@ ctrl.group.forEach(mat => { mat.visible = ctrl.toggle.checked; }); + if (ctrl.toggle.checked) { + KIRI.api.event.emit("stack.show", label); + } else { + KIRI.api.event.emit("stack.hide", label) + } }) }; let color = lmap[label].color.check; @@ -120,14 +143,24 @@ max = newMax; } + function setFraction(frac) { + Object.values(stacks).forEach(stack => { + stack.obj.setLastFraction(frac); + }); + } + KIRI.stacks = { clear, create, rotate, remove, getStack, + getStacks, getRange, setRange, + getLabels, + setVisible, + setFraction, setFreeMem }; diff --git a/src/kiri/ui.js b/src/kiri/ui.js index 8aa1b96b..6329ceec 100644 --- a/src/kiri/ui.js +++ b/src/kiri/ui.js @@ -30,6 +30,7 @@ prefix = "tab", state = {}, units = 1, + lastChange = null, lastBtn = null, lastTxt = null, lastPop = null, @@ -40,6 +41,7 @@ KIRI.ui = { prefix: function(pre) { prefix = pre; return kiri.ui }, inputAction: function(fn) { inputAction = fn; return kiri.ui }, + lastChange: function() { return lastChange }, checkpoint, restore, refresh, @@ -47,6 +49,7 @@ setExpert, setHoverPop, hidePoppers, + hoverPop, bound, toInt, toFloat, @@ -683,6 +686,7 @@ } }); ip.addEventListener('blur', function(event) { + lastChange = ip; action(event); if (opt.trigger) { refresh(); @@ -761,6 +765,7 @@ } ip.setVisible = row.setVisible; ip.onchange = function(ev) { + lastChange = ip; action(); }; ip.onclick = (ev) => { @@ -882,4 +887,54 @@ return array; } + function hoverPop(el, opt = {}) { + if (Array.isArray(el)) { + opt.group = []; + return el.forEach(ev => hoverPop(ev, opt)); + } + let group = opt.group || []; + let closes = opt.closes || group; + let target = opt.target || el; + let popped = false; + group.push(target); + if (closes !== group) { + closes.push(target); + } + let openit = () => { + clearTimeout(closes.timer); + for (let close of closes) { + close.classList.remove("hoverpop"); + } + target.classList.add("hoverpop"); + popped = true; + }; + let closeit = () => { + target.classList.remove("hoverpop"); + clearTimeout(closes.timer); + popped = false; + }; + if (opt.auto !== false) { + el.addEventListener("mouseenter", openit); + } + el.addEventListener("mouseleave", (ev) => { + closes.timer = setTimeout(() => { + target.classList.remove("hoverpop"); + }, 750); + }); + if (!opt.sticky) { + el.addEventListener("mouseup", (ev) => { + if (popped) { + closeit(); + } else { + openit(); + } + }); + } + document.addEventListener("keydown", (ev) => { + if (ev.keyCode === 27 || ev.code === 'Escape') { + closeit(); + } + }); + } + })(); diff --git a/src/kiri/widget.js b/src/kiri/widget.js index ace821a8..f1244471 100644 --- a/src/kiri/widget.js +++ b/src/kiri/widget.js @@ -32,6 +32,39 @@ let Group = Widget.Groups = { + list: () => { + return groups.slice() + }, + + merge: (widgets) => { + let grps = widgets.map(w => w.group).uniq(); + if (grps.length > 1) { + let root = grps.shift(); + let rpos = root[0].track.pos; + for (let grp of grps) { + for (let w of grp) { + let wpos = w.track.pos; + w.group = root; + w.moveMesh(rpos.x - wpos.x, rpos.y - wpos.y, rpos.z - wpos.z); + w._move(rpos.x, rpos.y, rpos.z, true); + root.push(w); + } + groups.splice(groups.indexOf(grp),1); + } + } + }, + + split: (widgets) => { + for (let group of widgets.map(w => w.group).uniq()) { + groups.splice(groups.indexOf(group),1); + for (let widget of group) { + let nugroup = Group.forid(widget.id); + nugroup.push(widget); + widget.group = nugroup; + } + } + }, + forid: function(id) { for (let i=0; i { - w._rotate(x, y, z, false); + w._rotate(x, y, z, temp); }); - this.center(); + this.center(false); }; PRO._rotate = function(x, y, z, temp) { @@ -509,20 +546,55 @@ }; PRO._mirror = function() { - this.setWireframe(false); this.clearSlices(); - this.mesh.applyMatrix4(new THREE.Matrix4().makeScale(-1, 1, 1)); - let geo = this.mesh.geometry, - o = this.track; + this.setWireframe(false); + let geo = this.mesh.geometry, ot = this.track; + let pos = geo.attributes.position; + let arr = pos.array; + let count = pos.count; + // invert x + for (let i=0; i v.id !== key); + group.id = id; + } + } + } + send.done(data.id); + return; + } + + let group = wgroup[data.group]; + if (!group) { + group = []; + group.id = data.group; + wgroup[data.group] = group; + } let vertices = new Float32Array(data.vertices), - points = BASE.verticesToPoints(vertices, { maxpass: 0 }), - widget = KIRI.newWidget(data.id).setPoints(points); + widget = KIRI.newWidget(data.id, group).loadVertices(vertices); // do it here so cancel can work - cache[data.id] = widget; + wcache[data.id] = widget; // stored for possible future rotations widget.vertices = vertices; - - // fake mesh object to satisfy printing - widget.track = data.tracking; - widget.mesh = { - widget: widget, - position: data.position - }; - + // restore tracking object + widget.track = data.track; send.done(data.id); }, - slice: function(data, send) { - let { id, settings } = data; + rotate: function(data, send) { + let { settings } = data; + if (!settings.device.bedBelt) { + return send.done({}); + } - send.data({update:0.05, updateStatus:"slicing"}); - - let widget = cache[data.id], - last = time(), - xpos, - ypos, - now; - - try { - - let rotation = (Math.PI/180) * (settings.device.bedBelt ? 45 : 0); - if (rotation) { - // need min y for post-rotation offset in prepare - let vert = widget.vertices; - let miny = Infinity; + function mins(vert, last = {}) { + let miny = last.miny || Infinity, + maxy = last.maxy || -Infinity; for (let i=0, l=vert.length; i { send.data({ progress }); }, { thin: true }); send.done({parsed: KIRI.codec.encode(layers)}); }, - clear: function(data, send) { - current.snap = null; - current.print = null; - if (!data.id) { - cache = {}; - send.done({ clear: true }); - return; - } - let had = cache[data.id] !== undefined; - delete cache[data.id]; - send.done({ - id: data.id, - had: had, - has: cache[data.id] !== undefined - }); - }, - config: function(data, send) { const update = {}; if (data.base) { @@ -383,11 +415,11 @@ KIRI.worker = { let p3 = (x - 0) * height + (y - 0); let p4 = (x - 1) * height + (y - 1); faces[ii++] = p1; - faces[ii++] = p2; faces[ii++] = p3; - faces[ii++] = p1; - faces[ii++] = p4; faces[ii++] = p2; + faces[ii++] = p1; + faces[ii++] = p2; + faces[ii++] = p4; } } send.data({progress: x / width}); @@ -504,6 +536,24 @@ KIRI.worker = { // send.done({done: {verts, faces, bigv, vi, ii}}, [ bigv.buffer ]); send.done({done: {bigv}}, [ bigv.buffer ]); }); + }, + + zip: function(data, send) { + let { files } = data; + let zip = new JSZip(); + for (let file of files) { + zip.file(file.name, file.data); + } + zip.generateAsync({ + type: "uint8array", + compression: "DEFLATE", + compressionOptions: { level: 3 }, + streamFiles: true + }, progress => { + send.data(progress); + }).then(output => { + send.done(output); + }); } }; @@ -543,20 +593,26 @@ self.onmessage = function(e) { }; if (run) { - let time_xfer = (time_recv - msg.time), - output = run(msg.data, send), - time_send = time(), - time_proc = time_send - time_recv; + try { + let time_xfer = (time_recv - msg.time), + output = run(msg.data, send), + time_send = time(), + time_proc = time_send - time_recv; - if (output) self.postMessage({ - seq: msg.seq, - task: msg.task, - time_send: time_xfer, - time_proc: time_proc, - // replaced on reply side - time_recv: time(), - data: output - }); + if (output) self.postMessage({ + seq: msg.seq, + task: msg.task, + time_send: time_xfer, + time_proc: time_proc, + // replaced on reply side + time_recv: time(), + data: output + }); + } catch (wrkerr) { + // console.log(wrkerr); + console.trace(wrkerr.stack); + send.done({error: wrkerr.toString()}); + } } else { console.log({kiri_msg:e}); } diff --git a/src/license.js b/src/license.js index 9d070655..e86c51bb 100644 --- a/src/license.js +++ b/src/license.js @@ -3,7 +3,7 @@ let terms = { COPYRIGHT: "Copyright (C) Stewart Allen - All Rights Reserved", LICENSE: "See the license.md file included with the source distribution", - VERSION: "2.6.D8" + VERSION: "2.8.D1" }; if (typeof(module) === 'object') { diff --git a/src/mode/cam/client.js b/src/mode/cam/client.js index 7fd97d3f..c5402b50 100644 --- a/src/mode/cam/client.js +++ b/src/mode/cam/client.js @@ -41,7 +41,10 @@ API = api; // wire up animate button in ui - api.function.animate = () => { + api.event.on("function.animate", (mode) => { + if (isAnimate) { + return; + } if (isCamMode && !camStock) { return api.show.alert("animation requires stock to be enabled"); } @@ -50,7 +53,7 @@ animate(); } }); - }; + }); api.event.on("mode.set", (mode) => { isCamMode = mode === 'CAM'; @@ -61,11 +64,11 @@ if (!isCamMode) { func.tabClear(); func.traceDone(); - UI.label.slice.innerText = 'slice'; - UI.label.preview.innerText = 'preview'; - UI.label.export.innerText = 'export'; + UI.label.slice.innerText = LANG.slice; + UI.label.preview.innerText = LANG.preview; + UI.label.export.innerText = LANG.export; } else { - UI.label.slice.innerText = 'start'; + UI.label.slice.innerText = LANG.start; } // do not persist traces across page reloads func.traceClear(); @@ -534,7 +537,7 @@ KIRI.api.widgets.opacity(0.8); KIRI.api.widgets.for(widget => { if (ids.indexOf(widget.id) >= 0) { - unselectTraces(widget); + unselectTraces(widget, true); widget.trace_stack = null; } if (widget.trace_stack) { @@ -578,7 +581,7 @@ } }); }); - }); + }, poppedRec.single); api.feature.hover = true; api.feature.hoverAdds = true; func.hover = func.traceHover; @@ -831,7 +834,7 @@ dogbones: UC.newBoolean(LANG.co_dogb_s, undefined, {title:LANG.co_dogb_l, show:(op) => { return !op.inputs.wide.checked }}), inside: UC.newBoolean(LANG.co_olin_s, undefined, {title:LANG.co_olin_l, show:(op) => { return !op.inputs.outside.checked }}), outside: UC.newBoolean(LANG.co_olot_s, undefined, {title:LANG.co_olot_l, show:(op) => { return !op.inputs.inside.checked }}), - omitthru: UC.newBoolean(LANG.co_omit_s, undefined, {title:LANG.co_omit_l, show:(op) => { return op.inputs.outside.checked }}), + omitthru: UC.newBoolean(LANG.co_omit_s, undefined, {title:LANG.co_omit_l, xshow:(op) => { return op.inputs.outside.checked }}), wide: UC.newBoolean(LANG.co_wide_s, undefined, {title:LANG.co_wide_l, show:(op) => { return !op.inputs.inside.checked }}) }; @@ -867,7 +870,8 @@ step: 'camTraceOver', down: 'camTraceDown', rate: 'camTraceSpeed', - plunge: 'camTracePlunge' + plunge: 'camTracePlunge', + single: 'camTraceLines', }).inputs = { tool: UC.newSelect(LANG.cc_tool, {}, "tools"), sep: UC.newBlank({class:"pop-sep"}), @@ -879,6 +883,7 @@ rate: UC.newInput(LANG.cc_feed_s, {title:LANG.cc_feed_l, convert:UC.toInt, units:true}), plunge: UC.newInput(LANG.cc_plng_s, {title:LANG.cc_plng_l, convert:UC.toInt, units:true}), sep: UC.newBlank({class:"pop-sep"}), + single: UC.newBoolean(LANG.cc_sngl_s, undefined, {title:LANG.cc_sngl_l}), select: UC.newRow([ UC.newButton(undefined, func.traceAdd, {icon:''}), UC.newButton(undefined, func.traceDone, {icon:''}), @@ -1044,11 +1049,11 @@ } } - function unselectTraces(widget) { + function unselectTraces(widget, skip) { if (widget.trace_stack) { widget.trace_stack.meshes.forEach(mesh => { if (mesh.selected) { - func.traceToggle(mesh); + func.traceToggle(mesh, skip); } }); } diff --git a/src/mode/cam/driver.js b/src/mode/cam/driver.js index 6c4a7263..6629f311 100644 --- a/src/mode/cam/driver.js +++ b/src/mode/cam/driver.js @@ -25,11 +25,11 @@ KIRI.loader.push(function(API) { if (KIRI.client) - CAM.traces = function(ondone) { + CAM.traces = function(ondone, single) { KIRI.client.sync(); let settings = API.conf.get(); let widgets = API.widgets.map(); - KIRI.client.send("cam_traces", { settings }, output => { + KIRI.client.send("cam_traces", { settings, single }, output => { let ids = []; KIRI.codec.decode(output).forEach(rec => { ids.push(rec.id); @@ -41,9 +41,9 @@ if (KIRI.worker) KIRI.worker.cam_traces = function(data, send) { - const { settings } = data; - const widgets = Object.values(cache); - const fresh = widgets.filter(widget => CAM.traces(settings, widget)); + const { settings, single } = data; + const widgets = Object.values(wcache); + const fresh = widgets.filter(widget => CAM.traces(settings, widget, single)); send.done(KIRI.codec.encode(fresh.map(widget => { return { id: widget.id, traces: widget.traces, diff --git a/src/mode/cam/export.js b/src/mode/cam/export.js index fe098518..fbd13fec 100644 --- a/src/mode/cam/export.js +++ b/src/mode/cam/export.js @@ -31,6 +31,7 @@ gcodes = settings.device || {}, tools = settings.tools, space = gcodes.gcodeSpace ? ' ' : '', + isRML = device.gcodeFExt.toLowerCase() === 'rml', stripComments = gcodes.gcodeStrip || false, cmdToolChange = gcodes.gcodeChange || [ "M6 T{tool}" ], cmdSpindle = gcodes.gcodeSpindle || [ "M3 S{speed}" ], @@ -88,7 +89,7 @@ if (!array) return; for (i=0; i= 0) { + if (!isRML && stripComments && (cidx = line.indexOf(";")) >= 0) { line = line.substring(0, cidx).trim(); if (line.length === 0) continue; } @@ -179,7 +180,8 @@ dz = newpos.z - pos.z, maxf = dz ? maxZd : maxXYd, feed = Math.min(speed || maxf, maxf), - dist = Math.sqrt(dx * dx + dy * dy + dz * dz); + dist = Math.sqrt(dx * dx + dy * dy + dz * dz), + newFeed = feed && feed !== pos.f; // drop dup points (all deltas are 0) if (!(dx || dy || dz)) { @@ -204,11 +206,23 @@ runbox.max.z = Math.max(runbox.max.z, pos.z); nl.append(space).append("Z").append(add0(pos.z * factor)); } - if (feed && feed !== pos.f) { + if (newFeed) { pos.f = feed; nl.append(space).append("F").append(add0(feed * factor, true)); } + // temp hack to support RML1 dialect from a file extensions trigger + if (isRML) { + if (speed) { + if (newFeed) { + append(`VS${feed};`); + } + nl = [ "Z", add0(pos.x * factor), ",", add0(pos.y * factor), ",", add0(pos.z * factor), ";" ]; + } else { + nl = [ "PU", add0(pos.x * factor), ",", add0(pos.y * factor), ";" ]; + } + } + // update time calculation time += (dist / (pos.f || 1000)) * 60; @@ -279,7 +293,10 @@ if (layerout.spindle && layerout.spindle !== spindle) { spindle = layerout.spindle; if (spindle > 0) { - filterEmit(cmdSpindle, {speed: Math.abs(spindle)}); + let speed = Math.abs(spindle); + filterEmit(cmdSpindle, { + speed, spindle: speed, rpm: speed + }); } else { append("M4"); } diff --git a/src/mode/cam/ops.js b/src/mode/cam/ops.js index 53ab28a1..28884e0c 100644 --- a/src/mode/cam/ops.js +++ b/src/mode/cam/ops.js @@ -109,6 +109,10 @@ let { tshadow, shadowTop, ztOff, zBottom, zMax } = state; let { process, stock } = settings; + if (op.down <= 0) { + throw `invalid step down "${op.down}"`; + } + let roughIn = op.inside; let roughTop = op.top; let roughDown = op.down; @@ -219,7 +223,7 @@ }); shadow = POLY.nest(shadow); - // expand shadow by half tool diameter + stock to leave + // shell = shadow expanded by half tool diameter + leave stock const sadd = roughIn ? toolDiam / 2 : toolDiam / 2; const shell = POLY.offset(shadow, sadd + roughLeave); @@ -262,7 +266,7 @@ if (!offset) return; - if (process.camStockClipTo) { + 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); } @@ -270,10 +274,12 @@ // elimate double inset on inners offset.forEach(op => { if (op.inner) { - let operim = op.perimeter(); + let pv1 = op.perimeter(); let newinner = []; op.inner.forEach(oi => { - if (Math.abs(oi.perimeter() - operim) > 0.01) { + let pv2 = oi.perimeter(); + let pct = pv1 > pv2 ? pv2/pv1 : pv1/pv2; + if (pct < 0.98) { newinner.push(oi); } }); @@ -307,8 +313,10 @@ let { polyEmit, poly2polyEmit, depthRoughPath } = ops; let { camOut, newLayer, printPoint } = ops; let { settings, widget } = state; - let { process } = settings; + let { process, controller } = settings; + let danger = controller.danger; + let easeDown = process.camEaseDown; let cutdir = process.camConventional; let depthFirst = process.camDepthFirst; let depthData = []; @@ -368,7 +376,9 @@ // return POLY.nest(level.filter(poly => poly.depth === 0)); return POLY.nest(level.filter(poly => poly.depth === 0).clone()); }); - printPoint = depthRoughPath(printPoint, 0, depthData, tops, polyEmit); + // experimental start of ease down + let ease = danger && op.down && easeDown ? op.down : 0; + printPoint = depthRoughPath(printPoint, 0, depthData, tops, polyEmit, false, ease); // printPoint = depthRoughPath(printPoint, 0, depthData, tops, (poly, index, count, start) => { // console.log({z: poly.getZ(), i: poly.id, index, poly}); // return polyEmit(poly, index, count, start); @@ -390,6 +400,10 @@ let { updateToolDiams, zThru, zBottom, shadowTop, tabs, cutTabs, cutPolys } = state; let { process, stock } = settings; + if (op.down <= 0) { + throw `invalid step down "${op.down}"`; + } + let toolDiam = this.toolDiam = new CAM.Tool(settings, op.tool).fluteDiameter(); updateToolDiams(toolDiam); @@ -405,7 +419,7 @@ indices = indices.appendAll(flats).sort((a,b) => b-a); // console.log('indices', ...indices, {zBottom, slicer}); if (op.outside && !op.inside) { - console.log({outline_bypass: indices, down: op.down}); + // console.log({outline_bypass: indices, down: op.down}); indices.forEach((ind,i) => { if (flats.indexOf(ind) >= 0) { // exclude flats @@ -509,7 +523,7 @@ CAM.addDogbones(offset, toolDiam / 5); } - if (process.camStockClipTo) { + 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); } @@ -534,8 +548,10 @@ let { polyEmit, poly2polyEmit, depthOutlinePath } = ops; let { camOut, newLayer, printPoint } = ops; let { settings, widget } = state; - let { process } = settings; + let { process, controller } = settings; + let danger = controller.danger; + let easeDown = process.camEaseDown; let toolDiam = this.toolDiam; let cutdir = process.camConventional; let depthFirst = process.camDepthFirst; @@ -584,8 +600,10 @@ printPoint = flatLevels[0] .sort((a,b) => { return a.area() - b.area() })[0] .average(); - printPoint = depthOutlinePath(printPoint, 0, flatLevels, toolDiam, polyEmit, false); - printPoint = depthOutlinePath(printPoint, 0, flatLevels, toolDiam, polyEmit, true); + // experimental start of ease down + let ease = danger && op.down && easeDown ? op.down : 0; + printPoint = depthOutlinePath(printPoint, 0, flatLevels, toolDiam, polyEmit, false, ease); + printPoint = depthOutlinePath(printPoint, 0, flatLevels, toolDiam, polyEmit, true, ease); } } @@ -629,7 +647,8 @@ let { settings, widget } = state; let { process } = settings; - let toolDiam = this.toolDiam; + let toolDiam = this.toolDiam = new CAM.Tool(settings, op.tool).fluteDiameter(); + let stepover = toolDiam * op.step * 2; let cutdir = process.camConventional; let depthFirst = process.camDepthFirst; let depthData = []; @@ -656,7 +675,7 @@ poly.reverse(); } poly.forEachPoint(function(point, pidx) { - camOut(point.clone(), pidx > 0); + camOut(point.clone(), pidx > 0, stepover); }, false); }); newLayer(); @@ -670,7 +689,7 @@ poly.reverse(); } poly.forEachPoint(function(point, pidx) { - camOut(point.clone(), pidx > 0); + camOut(point.clone(), pidx > 0, stepover); }, false); newLayer(); return lastPoint(); @@ -700,7 +719,8 @@ let toolOver = toolDiam * op.step; let cutdir = process.camConventional; let polys = []; - let stockRect = newPolygon().centerRectangle(stock.center, stock.x, stock.y); + let stockRect = stock.center && stock.x && stock.y ? + newPolygon().centerRectangle(stock.center, stock.x, stock.y) : undefined; updateToolDiams(toolDiam); if (tabs) { tabs.forEach(tab => { @@ -727,7 +747,7 @@ } else { slice.camLines = [ poly ]; } - if (process.camStockClipTo) { + if (process.camStockClipTo && stockRect) { slice.camLines = cutPolys([stockRect], slice.camLines, z, true); } slice.output() @@ -771,19 +791,22 @@ case "inside": offdist = -toolDiam / 2; break; } if (offdist) { - let pnew = POLY.offset([poly], offdist)[0]; + let pnew = POLY.offset([poly], offdist); if (pnew) { - poly = pnew.setZ(poly.getZ()); + poly = POLY.setZ(pnew, poly.getZ()); } else { continue; } + } else { + poly = [ poly ]; } + for (let pi of poly) if (down) { - for (let z of BASE.util.lerp(zMax, poly.getZ(), down)) { - followZ(poly.clone().setZ(z)); + for (let z of BASE.util.lerp(zMax, pi.getZ(), down)) { + followZ(pi.clone().setZ(z)); } } else { - followZ(poly); + followZ(pi); } } break; @@ -904,6 +927,7 @@ updateToolDiams(tool.fluteDiameter()); let { stock } = settings, + tz = widget.track.pos.z, lx = bounds.min.x, hx = bounds.max.x, ly = bounds.min.y, @@ -915,7 +939,10 @@ dx = (stock.x - (hx - lx)) / 4, dy = (stock.y - (hy - ly)) / 4, dz = stock.z, - points = []; + points = [], + wo = stock.z - bounds.max.z, + z1 = bounds.max.z + wo + tz, + z2 = tz - mz; if (!(stock.x && stock.y && stock.z)) { return; @@ -969,7 +996,7 @@ tv = -tv; } } - for (let z of BASE.util.lerp(zMax, -zThru, op.down)) { + for (let z of BASE.util.lerp(z1, z2, op.down)) { let slice = newSlice(z); sliceAll.push(slice); sliceOut.push(slice); @@ -1005,8 +1032,8 @@ let slice = newSlice(0,null), polys = []; points.forEach(point => { polys.push(newPolygon() - .append(point.clone().setZ(bounds.max.z)) - .append(point.clone().setZ(bounds.max.z - stock.z - mz))); + .append(point.clone().setZ(z1)) + .append(point.clone().setZ(z2))); }); slice.camLines = polys; slice.output() @@ -1096,7 +1123,7 @@ let terrain = slicer.slice(tzindex, { each: (data, index, total) => { tshadow = POLY.union(tshadow.slice().appendAll(data.tops), 0.01, true); tslices.push(data.slice); - if (true) { + if (false) { const slice = data.slice; sliceAll.push(slice); slice.output() diff --git a/src/mode/cam/prepare.js b/src/mode/cam/prepare.js index ece67c60..022d6681 100644 --- a/src/mode/cam/prepare.js +++ b/src/mode/cam/prepare.js @@ -69,7 +69,7 @@ boundsz = bounds.max.z + ztOff, zadd = hasStock ? stock.z - boundsz : alignTop ? outerz - boundsz : 0, zmax = outerz + zclear, - wmpos = widget.mesh.position, + wmpos = widget.track.pos, wmx = wmpos.x, wmy = wmpos.y, originx = startCenter ? 0 : hasStock ? -stock.x / 2 : bounds.min.x, @@ -231,7 +231,7 @@ ); } - function camOut(point, cut) { + function camOut(point, cut, moveLen = toolDiamMove) { point = point.clone(); point.x += wmx; point.y += wmy; @@ -264,7 +264,7 @@ } // convert short planar moves to cuts in some cases - if (isMove && deltaXY <= toolDiamMove) { + if (isMove && deltaXY <= moveLen) { let iscontour = tolerance > 0; let isflat = absDeltaZ < 0.001; // restrict this to contouring @@ -279,7 +279,7 @@ } } else if (isMove) { // for longer moves, check the terrain to see if we need to go up and over - const bigXY = (deltaXY > toolDiamMove); + const bigXY = (deltaXY > moveLen); const bigZ = (deltaZ > toolDiam/2 && deltaXY > tolerance); const midZ = (absDeltaZ >= tolerance); if ((bigXY || bigZ) && (isMove || midZ)) { @@ -296,11 +296,8 @@ ) + ztOff, mustGoUp = Math.max(maxz - point.z, maxz - lastPoint.z) >= tolerance, clearz = maxz; - // up if any point between higher than start/outline, go up + // up if any point between higher than start/outline, go up first if (mustGoUp) { - if (bigXY) { - clearz += zclear; - } layerPush(lastPoint.clone().setZ(clearz), 0, 0, tool.getNumber()); } // move to point above target point @@ -414,7 +411,7 @@ return last; } - function depthRoughPath(start, depth, levels, tops, emitter, fit) { + function depthRoughPath(start, depth, levels, tops, emitter, fit, ease) { let level = levels[depth]; if (!level) { return start; @@ -424,13 +421,19 @@ fitted.filter(top => !top.level_emit).forEach(top => { top.level_emit = true; let inside = level.filter(poly => poly.isInside(top)); + if (ease) { + start.z += ease; + } start = poly2polyEmit(inside, start, emitter, { mark: "emark", perm: true }); - start = depthRoughPath(start, depth + 1, levels, tops, emitter, top); + if (ease) { + start.z += ease; + } + start = depthRoughPath(start, depth + 1, levels, tops, emitter, top, ease); }); return start; } - function depthOutlinePath(start, depth, levels, radius, emitter, clr) { + function depthOutlinePath(start, depth, levels, radius, emitter, clr, ease) { let bottm = depth < levels.length - 1 ? levels[levels.length - 1] : null; let above = levels[depth-1]; let level = levels[depth]; @@ -458,8 +461,14 @@ // omit polys that match bottom level polys unless level above is cleared start = poly2polyEmit(level, start, (poly, index, count, fromPoint) => { poly.level_emit = true; + if (ease) { + fromPoint.z += ease; + } fromPoint = polyEmit(poly, index, count, fromPoint); - fromPoint = depthOutlinePath(fromPoint, depth + 1, levels, radius, emitter, clr); + if (ease) { + fromPoint.z += ease; + } + fromPoint = depthOutlinePath(fromPoint, depth + 1, levels, radius, emitter, clr, ease); return fromPoint; }, {weight: true}); return start; @@ -479,8 +488,9 @@ * return tool Z clearance height for a line segment movement path */ function getZClearPath(terrain, x1, y1, x2, y2, z, zadd, off, over) { + // when terrain skipped, top + pass used if (terrain > 0) { - return terrain + zadd + over; + return terrain; } let maxz = z; let check = []; diff --git a/src/mode/cam/slice.js b/src/mode/cam/slice.js index 25e1ee25..da4689b2 100644 --- a/src/mode/cam/slice.js +++ b/src/mode/cam/slice.js @@ -221,8 +221,8 @@ } }; - CAM.traces = function(settings, widget) { - if (widget.traces) { + CAM.traces = function(settings, widget, single) { + if (widget.traces && widget.trace_single === single) { // do no work if cached return false; } @@ -242,6 +242,13 @@ let traces = []; // find and trim polys (including open) to shadow let oneach = (data, index, total) => { + if (single) { + for (let line of data.lines) { + if (line.p1.distTo2D(line.p2) > 1) { + traces.push(newPolygon().append(line.p1).append(line.p2).setOpen()); + } + } + } else BASE.polygons.flatten(data.tops,null,true).forEach(poly => { poly.inner = null; poly.parent = null; @@ -265,6 +272,7 @@ opts.over = true; slicer.slice(indices, opts); widget.traces = traces; + widget.trace_single = single; return true; }; diff --git a/src/mode/cam/topo.js b/src/mode/cam/topo.js index e94be23c..0b6434bb 100644 --- a/src/mode/cam/topo.js +++ b/src/mode/cam/topo.js @@ -154,8 +154,8 @@ if (curvesOnly && lastP) { // maxangle const dz = Math.abs(lastP.z - z); - const dxy = Math.max(Math.abs(lastP.x - x), Math.abs(lastP.y - y)); - const angle = Math.atan2(dz, dxy) * RAD2DEG; + const dv = contourX ? Math.abs(lastP.x - x) : Math.abs(lastP.y - y); + const angle = Math.atan2(dz, dv) * RAD2DEG; // if (lastP.z < 0.1) console.log('pp', {dz, dxy, angle}); if (angle > maxangle) { lastP = newP; diff --git a/src/mode/fdm/client.js b/src/mode/fdm/client.js index 46c9e9cd..28c339a9 100644 --- a/src/mode/fdm/client.js +++ b/src/mode/fdm/client.js @@ -8,9 +8,12 @@ BASE = self.base, UTIL = BASE.util, FDM = KIRI.driver.FDM, - SPACE, API, + SPACE, API, VIEWS, LANG, PROC, UI, UC, + nextID = Date.now(), p1, p2, iw, - lastMode, lastView, lastPillar, + lastBox, + lastMode, lastView, + lastPillar, fromPillar, isFdmMode = false, addingSupports = false, alert = [], @@ -18,8 +21,133 @@ func = {}; FDM.init = function(kiri, api) { + UI = api.ui; + UC = api.uc; API = api; SPACE = api.const.SPACE; + VIEWS = api.const.VIEWS; + LANG = KIRI.lang.current; + PROC = Object.keys(kiri.conf.defaults.fdm.p); + + let rangeVars = { + // slice + "sliceShells": LANG.sl_shel_s, + "sliceFillType": LANG.fi_type, + "sliceFillWidth": LANG.fi_wdth_s, + "sliceFillSparse": LANG.fi_pcnt_s, + "sliceSolidMinArea": LANG.ad_msol_s, + // prepare + "sliceShellOrder": LANG.sl_ordr_s, + "sliceFillOverlap": LANG.fi_over_s, + "outputFeedrate": LANG.ou_feed_s, + "outputFinishrate": LANG.ou_fini_s, + "outputShellMult": LANG.ou_shml_s, + "outputFillMult": LANG.ou_flml_s, + "outputSparseMult": LANG.ou_spml_s, + "outputRetractWipe": LANG.ad_wpln_s, + "outputShortPoly": LANG.ad_spol_s, + "outputMinSpeed": LANG.ad_mins_s, + "outputCoastDist": LANG.ad_scst_s, + "sliceSupportDensity": LANG.sp_dens_s, + "sliceSupportOffset": LANG.sp_offs_s, + "sliceLayerStart": LANG.sl_strt_s, + // export + "zHopDistance": LANG.ad_zhop_s, + "arcTolerance": LANG.ad_zhop_s, + "antiBacklash": LANG.ad_abkl_s, + "outputTemp": LANG.ou_nozl_s, + "outputBedTemp": LANG.ou_bedd_s, + "outputFanSpeed": LANG.ou_fans_s, + "outputRetractDist": LANG.ad_rdst_s, + "outputRetractSpeed": LANG.outputRetractSpeed, + "outputRetractDwell": LANG.ad_rdwl_s, + }; + + for (let key of Object.keys(rangeVars)) { + if (UI[key]) { + UI[key].range = true; + } + } + + function filterSynth() { + api.widgets.filter((widget) => { + if (widget.track.synth) { + api.const.SPACE.platform.remove(widget.mesh); + kiri.Widget.Groups.remove(widget); + } + return !widget.track.synth + }); + } + + function updateRanges(ranges = []) { + UI.rangeGroup.style.display = isFdmMode && ranges && ranges.length ? 'flex' : 'none'; + let html = []; + let bind = []; + let now = Date.now(); + let sorted = ranges.sort((a,b) => b.lo - a.lo); + for (let range of sorted) { + let id = (now++).toString(36); + let rows = Object.entries(range.fields).map(a => `
`).join(''); + let hover = `
${rows}
`; + let info = ``; + html.appendAll([ + `
${hover}${info}
` + ]); + bind.push({id, range}); + } + UI.rangeGroup.firstElementChild.innerHTML = html.join(''); + for (let rec of bind) { + $(`sel_${rec.id}`).onclick = () => { + api.show.layer(rec.range.hi, rec.range.lo); + }; + $(`del_${rec.id}`).onclick = () => { + let io = ranges.indexOf(rec.range); + ranges.splice(io,1); + updateRanges(ranges); + }; + } + } + + api.event.on("boolean.click", func.updateSupportButtons = () => { + if (!isFdmMode) { + return; + } + for (let btn of [ + UI.ssaGen, + UI.ssmAdd, + UI.ssmDun, + UI.ssmClr + ]) { + btn.disabled = UI.sliceSupportEnable.checked; + } + if (UI.sliceSupportEnable.checked) { + func.sclear(); + } + }); + + api.event.on("function.animate", (mode) => { + if (!isFdmMode) { + return; + } + if (!API.conf.get().controller.danger) { + return; + } + let pct = 0; + let int = setInterval(function() { + if (pct <= 1) { + api.const.STACKS.setFraction(pct); + pct += 0.05; + } else { + api.const.STACKS.setFraction(1); + clearTimeout(int); + } + }, 50); + // let slider = $('top-slider'); + // slider.style.display = 'flex'; + // slider.oninput = slider.onchange = (ev) => { + // api.const.STACKS.setFraction(parseInt(ev.target.value)/100); + // }; + }); api.event.on("mode.set", mode => { isFdmMode = mode === 'FDM'; lastMode = mode; @@ -28,14 +156,36 @@ api.event.on("view.set", view => { lastView = view; updateVisiblity(); + filterSynth(); + func.sdone(); + // let ranges = API.conf.get().process.ranges; + if (isFdmMode) { + if (lastView === VIEWS.SLICE) { + for (let key of PROC) { + if (UI[key] && !UI[key].range) { + UI[key].disabled = true; + } + } + } else { + for (let key of PROC) { + if (UI[key]) UI[key].disabled = false; + } + } + } + // UI.rangeGroup.style.display = ranges && ranges.length ? 'flex' : 'none'; }); + api.event.on("range.updates", updateRanges); api.event.on("settings.load", (settings) => { if (settings.mode !== 'FDM') return; settings.process.outputOriginCenter = (settings.device.originCenter || false); restoreSupports(api.widgets.all()); + updateRanges(settings.process.ranges); }); api.event.on("settings.saved", (settings) => { - api.ui.fdmSupport.style.display = lastMode !== 'FDM' || settings.device.bedBelt ? 'none' : 'flex'; + updateRanges(settings.process.ranges); + func.updateSupportButtons(); + // let ranges = settings.process.ranges; + // UI.rangeGroup.style.display = isFdmMode && ranges && ranges.length ? 'flex' : 'none'; }); api.event.on("button.click", target => { switch (target) { @@ -43,9 +193,10 @@ case api.ui.ssmAdd: return func.sadd(); case api.ui.ssmDun: return func.sdone(); case api.ui.ssmClr: - return api.uc.confirm("clear supports?").then(ok => { - if (ok) func.sclear(); - }); + // return api.uc.confirm("clear supports?").then(ok => { + // if (ok) func.sclear(); + // }); + func.sclear(); } }); api.event.on("fdm.supports.detect", func.sgen = () => { @@ -86,11 +237,13 @@ delbox('supp'); api.hide.alert(alert); api.feature.hover = addingSupports = false; + fromPillar = undefined; }); api.event.on("fdm.supports.clear", func.sclear = () => { func.sdone(); - clearAllWidgetSupports(); - API.conf.save(); + if (clearAllWidgetSupports()) { + API.conf.save(); + } }); api.event.on("slice.begin", () => { if (!isFdmMode) { @@ -98,6 +251,37 @@ } func.sdone(); updateVisiblity(); + + // synth support widget for each widget group + let synth = []; + for (let group of kiri.Widget.Groups.list()) { + let merge = group.filter(w => w.sups).map(w => Object.values(w.sups)).flat(); + if (!merge.length) { + continue; + } + let boxen = merge.map(m => { + let geo = m.box.geometry.clone(); + if (geo.index) geo = geo.toNonIndexed(); + return geo.translate(m.x, m.y, m.z); + }); + for (let box of boxen) { + // eliminate / normalize uv to allow other widget merge + box.setAttribute('uv',new THREE.BufferAttribute(new Float32Array(0), 3)); + } + let bbg = THREE.BufferGeometryUtils.mergeBufferGeometries(boxen); + let sw = kiri.newWidget(null, group); + let fwp = group[0].track.pos; + sw.loadGeometry(bbg); + sw._move(fwp.x, fwp.y, fwp.z); + api.widgets.add(sw); + sw.track.synth = true; + api.const.SPACE.platform.add(sw.mesh); + } + }); + api.event.on("slice.end", () => { + if (!isFdmMode) { + return; + } }); api.event.on("key.esc", () => { if (!isFdmMode) { @@ -132,8 +316,8 @@ }); } }); - api.event.on("mouse.hover.up", on => { - let { object, event } = on; + api.event.on("mouse.hover.up", func.hoverup = (on = {}) => { + let { event } = on; if (!isFdmMode) { return; } @@ -142,34 +326,77 @@ } delbox('supp'); if (lastPillar) { - const {widget, box, id} = lastPillar; - widget.adds.remove(box); - widget.mesh.remove(box); - delete widget.sups[id]; - let sa = API.widgets.annotate(widget.id).support; - let ix = 0; - sa.forEach((rec,i) => { - if (rec.id === id) { - ix = i; - } - }); - sa.splice(ix,1); - API.conf.save(); + removeWidgetSupport(lastPillar.widget, lastPillar); return; } if (!iw) return; - let hy = (p1.y + p2.y) / 2; - let dh = Math.abs(p1.y - p2.y); + let { point, dim } = lastBox; + p1.y = Math.max(0, p1.y); + p2.y = Math.max(0, p2.y); + let rz = dim.rz; + let dh = dim.z; let dw = api.conf.get().process.sliceSupportSize / 2; let ip = iw.track.pos; let wa = api.widgets.annotate(iw.id); let ws = (wa.support = wa.support || []); - let x = p1.x - ip.x, y = -p1.z - ip.y, z = hy, id = Date.now(); - let rec = {x, y, z, dw, dh, id}; + let id = ++nextID; + let rec = {x: point.x - ip.x, y: -point.z - ip.y, z: point.y, rz, dw, dh, id}; + if (fromPillar && event && event.shiftKey) { + let targets = api.widgets.meshes().append(SPACE.internals().platform); + let from = fromPillar, + d = { + x: rec.x - from.x, + y: rec.y - from.y, + z: rec.z - from.z + }; + let dist = Math.sqrt(d.x*d.x + d.y*d.y + d.z*d.z); + let lerp = UTIL.lerp(0,dist,dw).map(v => v/dist); + let angle = rec.rz = Math.atan2(d.y,d.x); + let fromrec = ws.filter(r => r.id === from.id)[0]; + if (fromrec) { + from.rz = fromrec.rz = angle; + removeWidgetSupport(iw, fromPillar); + ws.push(Object.clone(fromrec)); + addWidgetSupport(iw, fromrec); + } + lerp.pop(); + let seed = Math.random(); + for (let pct of lerp) { + let point = new THREE.Vector3(from.x + d.x * pct, from.z + d.z * pct, from.y + d.y * pct); + addSupportAtPoint(targets, point, ip, angle, seed++); + } + } ws.push(Object.clone(rec)); - addWidgetSupport(iw, rec); + fromPillar = addWidgetSupport(iw, rec); API.conf.save(); }); + function addSupportAtPoint(targets, point, ip, angle, id) { + let up = new THREE.Vector3(0,1,0) + let dn = new THREE.Vector3(0,-1,0) + let rp = new THREE.Vector3(point.x + ip.x, point.y, -point.z - ip.y); + let iup = new THREE.Raycaster(rp, up) + .intersectObjects(targets, false) + .filter(t => !t.object.pillar); + if (iup.length && iup.length % 2 === 0) { + let idn = new THREE.Raycaster(rp, dn) + .intersectObjects(targets, false) + .filter(t => !t.object.pillar); + if (idn.length && idn.length % 2 === 0) { + iw = iup[0].object.widget || iw; + let wa = api.widgets.annotate(iw.id); + let ws = (wa.support = wa.support || []); + let phi = iup[0].point; + let plo = idn[0].point; + let ply = Math.max(0, plo.y); + let mp = (phi.y + ply) / 2; + let dy = Math.abs(phi.y - ply); + let dw = api.conf.get().process.sliceSupportSize / 2; + let rec = { rz:angle, x:point.x, y:point.z, z:mp, dw, dh:dy, id }; + ws.push(Object.clone(rec)); + fromPillar = addWidgetSupport(iw, rec); + } + } + } api.event.on("mouse.hover", data => { if (!isFdmMode) { return; @@ -177,11 +404,8 @@ if (!addingSupports) { return; } - // delbox('intZ'); - // delbox('intW'); - // addbox(point, 0xff0000, 'intZ'); delbox('supp'); - const { int, type, point } = data; + const { int, type, point, event } = data; const pillar = int ? int.object.pillar : undefined; if (lastPillar) { lastPillar.box.material.color.r = 0; @@ -190,6 +414,9 @@ if (pillar) { pillar.box.material.color.r = 0.5; lastPillar = pillar; + if (event && (event.metaKey || event.ctrlKey)) { + return func.hoverup(); + } return; } if (int && type === 'widget') { @@ -200,30 +427,29 @@ p1 = point; let dir = new THREE.Vector3(0,1,0) let ray = new THREE.Raycaster(point, dir); + let rz = int && int.face ? Math.atan2(int.face.normal.y, int.face.normal.x) : 0; // when on object, project down on downward faces if (int && int.face && int.face.normal.z < -0.1) { dir.y = -1; } - let targets = api.widgets.meshes() - .append(SPACE.internals().platform) - .appendAll(activeSupports()) - ; - let i2 = ray.intersectObjects(targets, false); - if (i2 && i2.length > 0) { - // prevent false matches close to origin of ray - i2 = i2.filter(i => i.distance > 0.01); - // prevent single point base to top matches - if (i2.length > 1) { - p2 = i2[0].point; - iw = i2[0].object.widget || iw; - let hy = (p1.y + p2.y) / 2; - let dy = Math.abs(p1.y - p2.y); - let dw = api.conf.get().process.sliceSupportSize / 2; - // addbox(p2, 0x00ff00, 'intW'); - addbox({x:p1.x, y:hy, z:p1.z}, 0x0000dd, 'supp', { - x:dw, y:dw, z:dy - }); - } + let targets = api.widgets.meshes().append(SPACE.internals().platform); + let i2 = ray.intersectObjects(targets, false) + .filter(t => !t.object.pillar) // eliminate other pillars + .filter(i => i.distance > 0.1); // false matches close to origin of ray + if (i2.length && i2.length % 2 === 0) { + p2 = i2[0].point; + iw = i2[0].object.widget || iw; + let p1y = Math.max(0, p1.y); + let p2y = Math.max(0, p2.y); + let hy = (p1y + p2y) / 2; + let dy = Math.abs(p1y - p2y); + let dw = api.conf.get().process.sliceSupportSize / 2; + addbox({x:p1.x, y:hy, z:p1.z}, 0x0000dd, 'supp', { + x:dw, y:dw, z:dy, rz + }); + } + if (event && event.altKey) { + return func.hoverup(); } }); } @@ -234,7 +460,6 @@ Object.values(widget.sups || {}).forEach(support => { active.push(support.box); support.box.support = true; - // console.log({support}); }); }); return active; @@ -250,23 +475,41 @@ } function addWidgetSupport(widget, pos) { - const { x, y, z, dw, dh, id } = pos; + const { x, y, z, rz, dw, dh, id } = pos; const sups = widget.sups = (widget.sups || {}); // prevent duplicate restore from repeated settings load calls if (!sups[id]) { pos.box = addbox( { x, y, z }, 0x0000dd, id, - { x:dw, y:dw, z:dh }, { group: widget.mesh } + { x:dw, y:dw, z:dh, rz }, { group: widget.mesh } ); pos.box.pillar = Object.assign({widget}, pos); sups[id] = pos; widget.adds.push(pos.box); } + return sups[id]; + } + + function removeWidgetSupport(widget, rec) { + const {box, id} = rec; + widget.adds.remove(box); + widget.mesh.remove(box); + delete widget.sups[id]; + let sa = API.widgets.annotate(widget.id).support; + let ix = 0; + sa.forEach((rec,i) => { + if (rec.id === id) { + ix = i; + } + }); + sa.splice(ix,1); + API.conf.save(); + fromPillar = undefined; } function updateVisiblity() { API.widgets.all().forEach(w => { - setSupportVisiblity(w, lastMode === 'FDM' && lastView === API.const.VIEWS.ARRANGE); + setSupportVisiblity(w, lastMode === 'FDM' && lastView === VIEWS.ARRANGE); }); } @@ -277,18 +520,23 @@ } function clearAllWidgetSupports() { + let cleared = 0; API.widgets.all().forEach(widget => { - clearWidgetSupports(widget); + cleared += clearWidgetSupports(widget); }); + return cleared; } function clearWidgetSupports(widget) { + let cleared = 0; Object.values(widget.sups || {}).forEach(support => { widget.adds.remove(support.box); widget.mesh.remove(support.box); + cleared++; }); widget.sups = {}; delete API.widgets.annotate(widget.id).support; + return cleared; } function delbox(name) { @@ -298,7 +546,7 @@ } } - function addbox(point, color, name, dim = {x:1,y:1,z:1}, opt = {}) { + function addbox(point, color, name, dim = {x:1,y:1,z:1,rz:0}, opt = {}) { delbox(name); const box = boxes[name] = new THREE.Mesh( new THREE.BoxGeometry(dim.x, dim.y, dim.z), @@ -312,10 +560,15 @@ box.position.y = point.y; box.position.z = point.z; + lastBox = {point, dim}; + const group = opt.group || SPACE.scene group.add(box); box.groupTo = group; + if (dim.rz) { + opt.rotate = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1), dim.rz); + } if (opt.rotate) { opt.matrix = new THREE.Matrix4().makeRotationFromQuaternion(opt.rotate); } diff --git a/src/mode/fdm/driver.js b/src/mode/fdm/driver.js index f06e8e89..b50bb300 100644 --- a/src/mode/fdm/driver.js +++ b/src/mode/fdm/driver.js @@ -14,6 +14,7 @@ // slice, // src/mode/fdm/slice.js // prepare, // src/mode/fdm/prepare.js // export, // src/mode/fdm/export.js + getRangeParameters, fixExtruders }; @@ -28,10 +29,25 @@ return settings; } - // customer gcode post function for XYZ daVinci Mini W - self.kiri_fdm_xyz_mini_w = function(gcode, options) { - return btoa("; filename = kirimoto.gcode\n; machine = dv1MW0A000\n" + gcode); - }; + function getRangeParameters(settings, index) { + if (index === undefined || index === null || index < 0) { + return settings.process; + } + let ranges = settings.process.ranges; + if (!(ranges && ranges.length)) { + return settings.process; + } + let params = Object.clone(settings.process); + for (let range of ranges) { + if (index >= range.lo && index <= range.hi) { + for (let [key,value] of Object.entries(range.fields)) { + params[key] = value; + params._range = true; + } + } + } + return params; + } // defer loading until KIRI.client and KIRI.worker exist KIRI.loader.push(function(API) { @@ -39,6 +55,7 @@ if (KIRI.client) // FDM.support_generate = KIRI.client.fdm_support_generate = function(ondone) { FDM.support_generate = function(ondone) { + KIRI.client.clear(); KIRI.client.sync(); let settings = API.conf.get(); let widgets = API.widgets.map(); @@ -51,7 +68,7 @@ if (KIRI.worker) KIRI.worker.fdm_support_generate = function(data, send) { const { settings } = data; - const widgets = Object.values(cache); + const widgets = Object.values(wcache); const fresh = widgets.filter(widget => FDM.supports(settings, widget)); send.done(KIRI.codec.encode(fresh.map(widget => { return { id: widget.id, diff --git a/src/mode/fdm/export.js b/src/mode/fdm/export.js index 4ef4ea8d..66688c48 100644 --- a/src/mode/fdm/export.js +++ b/src/mode/fdm/export.js @@ -15,25 +15,28 @@ FDM.export = function(print, online) { let layers = print.output, settings = FDM.fixExtruders(print.settings), + getRangeParameters = FDM.getRangeParameters, device = settings.device, extruders = device.extruders, - process = settings.process, gcodeFan = device.gcodeFan, gcodeLayer = device.gcodeLayer, gcodeTrack = device.gcodeTrack, tool = 0, + fwRetract = device.fwRetract, + isDanger = settings.controller.danger, isBelt = device.bedBelt, bedType = isBelt ? "belt" : "fixed", extruder = extruders[tool], offset_x = extruder.extOffsetX, offset_y = extruder.extOffsetY, - extrudeAbs = device.extrudeAbs, + extrudeAbs = device.extrudeAbs || false, + extrudeSet = false, time = 0, layer = 0, pause = [], pauseCmd = device.gcodePause, output = [], - outputLength = 0, + outputLength = 0, // absolute extruder position lastProgress = 0, decimals = BASE.config.gcode_decimals || 4, progress = 0, @@ -44,26 +47,39 @@ lout = null, last = null, zpos = 0, - zhop = process.zHopDistance || 0, + bmax = 0, + blast = 0, + blastz = 0, + process = settings.process, + loops = process.outputLoopLayers, + zhop = process.zHopDistance || 0, // range seekMMM = process.outputSeekrate * 60, - retDist = process.outputRetractDist, - retSpeed = process.outputRetractSpeed * 60, - retDwell = process.outputRetractDwell || 0, + retDist = process.outputRetractDist || 0, // range + retSpeed = process.outputRetractSpeed * 60, // range + retDwell = process.outputRetractDwell || 0, // range timeDwell = retDwell / 1000, + peelGuard = process.outputPeelGuard || 0, + arcDist = isBelt || !isDanger ? 0 : (process.arcTolerance || 0), originCenter = process.outputOriginCenter, offset = originCenter ? null : { x: device.bedWidth/2, y: isBelt ? 0 : device.bedDepth/2 }, + nozzleTemp = process.firstLayerNozzleTemp || process.outputTemp, + bedTemp = process.firstLayerBedTemp || process.outputBedTemp, + fanSpeed = undefined, + lastNozzleTemp = nozzleTemp, + lastBedTemp = bedTemp, + lastFanSpeed = fanSpeed, subst = { travel_speed: seekMMM, retract_speed: retSpeed, retract_distance: retDist, - temp: process.firstLayerNozzleTemp || process.outputTemp, - temp_bed: process.firstLayerBedTemp || process.outputBedTemp, - bed_temp: process.firstLayerBedTemp || process.outputBedTemp, - fan_speed: process.outputFanMax, - speed: process.outputFanMax, // legacy + temp: nozzleTemp, + temp_bed: bedTemp, + bed_temp: bedTemp, + fan_speed: fanSpeed, + speed: fanSpeed, // legacy top: offset ? device.bedDepth : device.bedDepth/2, left: offset ? 0 : -device.bedWidth/2, right: offset ? device.bedWidth : device.bedWidth/2, @@ -79,8 +95,38 @@ bytes = 0, bcos = Math.cos(Math.PI/4), icos = 1 / bcos, - loops = process.outputLoopLayers, - inLoop; + inLoop, + arcQ = []; + + // smallish band-aid. refactor above to remove redundancy + function updateParams(layer) { + let process = getRangeParameters(settings, layer); + zhop = process.zHopDistance || 0; // range + retDist = process.outputRetractDist || 0; // range + retSpeed = process.outputRetractSpeed * 60; // range + retDwell = process.outputRetractDwell || 0; // range + timeDwell = retDwell / 1000; + nozzleTemp = layer === 0 ? + process.firstLayerNozzleTemp || process.outputTemp : + process.outputTemp || process.firstLayerNozzleTemp; + bedTemp = layer === 0 ? + process.firstLayerBedTemp || process.outputBedTemp : + process.outputBedTemp || process.firstLayerBedTemp; + fanSpeed = layer === 0 ? + process.firstLayerFanSpeed || 0 : + process.outputFanSpeed || 0; + Object.assign(subst, { + temp_bed: bedTemp, + bed_temp: bedTemp, + fan_speed: fanSpeed, + speed: fanSpeed, // legacy + retract_speed: retSpeed, + retract_distance: retDist, + temp: process.outputTemp, // range + temp_bed: process.outputBedTemp, // range + bed_temp: process.outputBedTemp, // range + }); + } if (isBelt && loops) { loops = loops.split(',').map(range => { @@ -116,8 +162,12 @@ } }; - function appendSub(line) { - append(print.constReplace(line, subst)); + function appendSubPad(line, pad) { + appendSub(line, true); + } + + function appendSub(line, pad) { + append(print.constReplace(line, subst, 0, pad)); } function appendAll(arr) { @@ -126,10 +176,10 @@ arr.forEach(function(line) { append(line) }); } - function appendAllSub(arr) { - if (!arr) return; + function appendAllSub(arr, pad) { + if (!arr || arr.length === 0) return; if (!Array.isArray(arr)) arr = [ arr ]; - arr.forEach(function(line) { appendSub(line) }); + arr.forEach(function(line) { appendSub(line, pad) }); } append(`; Generated by Kiri:Moto ${KIRI.version}`); @@ -146,12 +196,18 @@ let t1 = false; for (let i=0; i= 0) t0 = true; else - if (line.indexOf('T1') >= 0) t1 = true; else - if (line.indexOf('M82') >= 0) extrudeAbs = true; else - if (line.indexOf('M83') >= 0) extrudeAbs = false; else - if (line.indexOf('G90') >= 0) extrudeAbs = true; else - if (line.indexOf('G91') >= 0) extrudeAbs = false; else + if (line.indexOf('T0') === 0) t0 = true; else + if (line.indexOf('T1') === 0) t1 = true; else + if (line.indexOf('M82') === 0) { + extrudeAbs = true; + extrudeSet = true; + } else + if (line.indexOf('M83') === 0) { + extrudeAbs = false; + extrudeSet = true; + } else + if (line.indexOf('G90') === 0 && !extrudeSet) extrudeAbs = true; else + if (line.indexOf('G91') === 0 && !extrudeSet) extrudeAbs = false; else if (line.indexOf('G92') === 0) { line.split(";")[0].split(' ').forEach(function (tok) { let val = parseFloat(tok.substring(1) || 0) || 0; @@ -174,11 +230,11 @@ if (line.indexOf("{tool}") > 0 && extruders.length > 1) { for (let i=0; i= 0) { + updateParams(path.layer); + } + emitPerMM = print.extrudePerMM( extruder.extNozzle, extruder.extFilament, path.layer === 0 ? (process.firstSliceHeight || process.sliceHeight) : path.height); - subst.z = zpos = path.z; - subst.Z = subst.z; + zpos = path.z || zpos; + bmax = Math.max(bmax, pos.z * icos); + subst.z = subst.Z = zpos.round(3); subst.e = subst.E = outputLength; subst.layer = layer; subst.height = path.height.toFixed(3); if (isBelt) { pos.z = zpos; + if (peelGuard && bmax > peelGuard && blast < peelGuard) { + peelGuard += 50; + append(`G0 Z${(blast + 55).round(decimals)} F200 ; peel guard`); + append(`G0 Z${blastz.round(decimals)} F200 ; unpeel`); + } + blast = bmax; } if (pauseCmd && pause.indexOf(layer) >= 0) { @@ -309,31 +390,23 @@ if (gcodeLayer && gcodeLayer.length) { appendAllSub(gcodeLayer); } else { - append(`;; --- layer ${layer} (${subst.height} @ ${subst.z}) ---`); + append(`;; --- layer ${layer} (${subst.height} @ ${subst.z.round(3)}) ---`); } - if (layer > 0 && process.outputLayerRetract) { - retract(); - } - - // enable fan at fan layer - if (gcodeFan && layer === process.outputFanLayer) { + // layer temp and fan overrides at layer changes + if (fanSpeed !== lastFanSpeed) { appendAllSub(gcodeFan); + lastFanSpeed = fanSpeed; } - - // second layer transitions - if (layer === 1) { - // update temps when first layer overrides are present - if (process.firstLayerNozzleTemp) { - subst.temp = process.outputTemp; - if (t0) appendSub("M104 S{temp} T0"); - if (t1) appendSub("M104 S{temp} T1"); - if (!(t0 || t1)) appendSub("M104 S{temp} T{tool}"); - } - if (process.firstLayerBedTemp) { - subst.bed_temp = subst.temp_bed = process.outputBedTemp; - appendSub("M140 S{temp_bed} T0"); - } + if (bedTemp !== lastBedTemp) { + append(`M140 S${bedTemp} T0`); + lastBedTemp = bedTemp; + } + if (nozzleTemp !== lastNozzleTemp) { + if (t0) append(`M104 S${nozzleTemp} T0`); + if (t1) append(`M104 S${nozzleTemp} T1`); + if (!(t0 || t1)) append(`M104 S${nozzleTemp} T${tool}`); + lastNozzleTemp = nozzleTemp; } // move Z to layer height @@ -344,10 +417,11 @@ // iterate through layer outputs for (pidx=0; pidx 1) { + deem = arcQ[0].e !== rec.e; + depm = arcQ[0].emitPerMM !== rec.emitPerMM; + desp = arcQ[0].speedMMM !== rec.speedMMM; + } + if (arcQ.length > 2) { + let el = arcQ.length; + let e1 = arcQ[el-3]; + let e2 = arcQ[el-2]; + let e3 = arcQ[el-1]; + let cc = BASE.util.center2d(e1, e2, e3, 1); + let dc = 0; + if (arcQ.length === 3) { + arcQ.center = [ cc ]; + } else { + // check center point delta + let dx = cc.x - arcQ.center[0].x; + let dy = cc.y - arcQ.center[0].y; + dc = Math.sqrt(dx * dx + dy * dy); + } + // if new point is off the arc + if (deem || depm || desp || dc > arcDist) { + // console.log({dc, depm, desp}); + if (arcQ.length === 4) { + // not enough points for an arc, drop first point and recalc center + emitQrec(arcQ.shift()); + arcQ.center = [ BASE.util.center2d(arcQ[0], arcQ[1], arcQ[2], 1) ]; + } else { + // enough to consider an arc, emit and start new arc + let defer = arcQ.pop(); + drainQ(); + // re-add point that was off the last arc + arcQ.push(defer); + } + } else { + // new point is on the arc + arcQ.center.push(cc); + } + } + } else { + emitMM = emitPerMM * out.emit * dist; + moveTo({x:x, y:y, e:emitMM}, speedMMM); + emitted += emitMM; + } } else { + drainQ(); moveTo({x:x, y:y}, seekMMM); // TODO disabling out of plane z moves until a better mechanism // can be built that doesn't rely on computed zpos from layer heights... @@ -407,7 +535,8 @@ } // retract filament if point retract flag set - if (!retracted && out.retract) { + if (out.retract) { + drainQ(); retract(zhop); } @@ -427,13 +556,72 @@ } layer++; } + drainQ(); + + function emitQrec(rec) { + let {e, x, y, dist, emitPerMM, speedMMM} = rec; + emitMM = emitPerMM * e * dist; + moveTo({x:x, y:y, e:emitMM}, speedMMM); + emitted += emitMM; + } + + function drainQ() { + if (!arcDist) { + return; + } + if (arcQ.length > 4) { + let area = BASE.newPolygon().addObj(arcQ).area(); + let from = arcQ[0]; + let to = arcQ.peek(); + let cc = {x:0, y:0, z:0, r:0}; + let cl = 0; + for (let center of arcQ.center.filter(rec => !isNaN(rec.r))) { + cc.x += center.x; + cc.y += center.y; + cc.z += center.z; + cc.r += center.r; + cl++; + } + cc.x /= cl; + cc.y /= cl; + cc.z /= cl; + cc.r /= cl; + // first arc point + emitQrec(from); + // console.log(arcQ.slice(), arcQ.center); + // console.log({first: from, last: arcQ.peek(), center: cc}); + // rest of arc to final point + let dist = arcQ.slice(1).map(v => v.dist).reduce((a,v) => a+v); + let emit = from.e;//arcQ.slice(1).map(v => v.e).reduce((a,v) => a+v); + emit = (from.emitPerMM * emit * dist); + outputLength += emit; + emitted += emit; + if (extrudeAbs) { + emit = outputLength; + } + let gc = area > 0 ? 'G2' : 'G3'; + let pre = `${gc} X${to.x.toFixed(decimals)} Y${to.y.toFixed(decimals)} R${cc.r.toFixed(decimals)} E${emit.toFixed(decimals)}`; + let add = pos.f !== from.speedMMM ? ` E${from.speedMMM}` : ''; + append(`${pre}${add} ; merged=${cl-1} len=${dist.toFixed(decimals)} cp=${cc.x.round(2)},${cc.y.round(2)}`); + pos.x = to.x; + pos.y = to.y; + pos.z = to.z; + } else { + for (let rec of arcQ) { + emitQrec(rec); + } + } + arcQ.length = 0; + arcQ.center = undefined; + } if (inLoop) { append(`M808`); } - subst.time = UTIL.round(time,2); subst.material = UTIL.round(emitted,2); + subst.time = UTIL.round(time,2); + subst['print-time'] = subst.time; append("; --- shutdown ---"); appendAllSub(device.gcodePost); diff --git a/src/kiri/fill.js b/src/mode/fdm/fill.js similarity index 94% rename from src/kiri/fill.js rename to src/mode/fdm/fill.js index daf825d4..4b73126c 100644 --- a/src/kiri/fill.js +++ b/src/mode/fdm/fill.js @@ -155,25 +155,28 @@ function fillLinear(target) { let bounds = target.bounds(); let height = target.zHeight(); + + let density = target.density(); + let line = target.lineWidth(); + let span_x = bounds.max.x - bounds.min.x; let span_y = bounds.max.y - bounds.min.y; - let density = target.density(); - let offset = target.offset() / 2; - let tile = 1 + (1 - density); - let tile_xc = Math.ceil(span_x / tile); - let tile_yc = Math.ceil(span_y / tile); + let steps_x = (span_x / line) * density; + let steps_y = (span_y / line) * density; + let step_x = span_x / steps_x; + let step_y = span_x / steps_x; if (target.zIndex() % 2 === 1) { - for (let tx=0; tx<=tile_xc; tx++) { + for (let tx=bounds.min.x; tx<=bounds.max.x; tx += step_x) { target.newline(); - target.emit(tx * tile + bounds.min.x, bounds.min.y); - target.emit(tx * tile + bounds.min.x, bounds.max.y); + target.emit(tx, bounds.min.y); + target.emit(tx, bounds.max.y); } } else { - for (let ty=0; ty<=tile_yc; ty++) { + for (let ty=bounds.min.y; ty<=bounds.max.y; ty += step_y) { target.newline(); - target.emit(bounds.min.x, ty * tile + bounds.min.y); - target.emit(bounds.max.x, ty * tile + bounds.min.y); + target.emit(bounds.min.x, ty); + target.emit(bounds.max.x, ty); } } } diff --git a/src/mode/fdm/prepare.js b/src/mode/fdm/prepare.js index 63d7b99b..04c38bfe 100644 --- a/src/mode/fdm/prepare.js +++ b/src/mode/fdm/prepare.js @@ -10,7 +10,8 @@ POLY = BASE.polygons, FDM = KIRI.driver.FDM, newPoint = BASE.newPoint, - newPolygon = BASE.newPolygon; + newPolygon = BASE.newPolygon, + getRangeParameters = FDM.getRangeParameters; /** * DRIVER PRINT CONTRACT @@ -19,34 +20,31 @@ * @returns {Object[]} returns array of render objects */ FDM.prepare = function(widgets, settings, update) { - settings = FDM.fixExtruders(settings); + // filter ignored widgets + widgets = widgets.filter(w => !w.track.ignore); + settings = FDM.fixExtruders(settings); let render = settings.render !== false, { device, process, controller, bounds, mode } = settings, + { bedWidth, bedDepth } = device, output = [], printPoint = newPoint(0,0,0), nozzle = device.extruders[0].extNozzle, isBelt = device.bedBelt, isThin = controller.lineType === "line", isFlat = controller.lineType === "flat", + isDanger = controller.danger || false, firstLayerHeight = isBelt ? process.sliceHeight : process.firstSliceHeight || process.sliceHeight, firstLayerSeek = process.outputSeekrate, firstLayerRate = process.firstLayerRate, firstLayerMult = process.firstLayerPrintMult, firstLayerBrim = process.firstLayerBrim, firstLayerBrimTrig = process.firstLayerBrimTrig, - maxLayers = 0, - layer = 0, + layerRetract = process.outputLayerRetract, + layerno = 0, zoff = 0, - meshIndex, - lastIndex, - closest, - mindist, - minidx, - find, layerout = [], slices = [], - sliceEntry, print = self.worker.print = KIRI.newPrint(settings, widgets), beltYoff = device.bedDepth / 2, beltfact = Math.cos(Math.PI/4), @@ -75,7 +73,7 @@ offset = process.outputBrimOffset || (process.outputRaft ? 4 : 0); // compute first brim - widgets.forEach(function(widget) { + widgets.filter(w => w.slices.length).forEach(function(widget) { let tops = []; // collect top outer polygons widget.slices[0].tops.forEach(function(top) { @@ -88,15 +86,16 @@ }); // nest and offset tops POLY.nest(tops).forEach(function(poly) { - poly.offset(-offset + nozzle / 2).forEach(function(brim) { - brim.move(widget.mesh.position); + let off = poly.offset(-offset + nozzle / 2); + if (off) off.forEach(function(brim) { + brim.move(widget.track.pos); brims.push(brim); }); }); }); // merge brims - brims = POLY.union(brims); + brims = POLY.union(brims, undefined, true); // if brim is offset, over-expand then shrink to induce brims to merge if (offset && brims.length) { @@ -132,6 +131,7 @@ speed: speed, mult: extrude, }); + layerout.z = zoff + height; layerout.height = height; output.append(layerout); @@ -139,14 +139,15 @@ zoff += height; }; - raft(nozzle/1, process.sliceFillAngle + 0 , nozzle * 6.0, firstLayerRate / 3, 4); - raft(nozzle/1, process.sliceFillAngle + 0 , nozzle * 6.0, firstLayerRate / 2, 4); + raft(nozzle/1, process.sliceFillAngle + 0 , nozzle * 5.0, firstLayerRate / 3, 4); + raft(nozzle/1, process.sliceFillAngle + 0 , nozzle * 5.0, firstLayerRate / 2, 4); raft(nozzle/2, process.sliceFillAngle + 90, nozzle * 3.0, process.outputFeedrate, 2.5); raft(nozzle/2, process.sliceFillAngle + 0 , nozzle * 1.0, process.outputFeedrate, 1.5); - raft(nozzle/2, process.sliceFillAngle + 0 , nozzle * 1.0, process.outputFeedrate, 1.0); + raft(nozzle/2, process.sliceFillAngle + 90 , nozzle * 0.7, process.outputFeedrate, 0.75); // raise first layer off raft slightly to lessen adhesion firstLayerHeight += process.outputRaftSpacing || 0; + zoff += process.outputRaftSpacing || 0; // retract after last raft layer output.last().last().retract = true; @@ -169,7 +170,8 @@ }); // output brim points - printPoint = print.poly2polyEmit(polys, printPoint, function(poly, index, count, startPoint) { + let brimStart = offset < nozzle * 2 ? newPoint(-bedWidth, -bedDepth, 0) : printPoint; + printPoint = print.poly2polyEmit(polys, brimStart, (poly, index, count, startPoint) => { return print.polyPrintPath(poly, startPoint, preout, { rate: firstLayerRate, onfirst: function(point) { @@ -203,6 +205,10 @@ // so that they can use a separate extruder for (let widget of widgets.slice()) { let sslices = []; + if (!widget.slices) { + console.log('invalid widget', widget); + continue; + } for (let slice of widget.slices) { if (!slice.supports) { continue; @@ -210,25 +216,30 @@ let sslice = KIRI.newSlice(slice.z); sslice.extruder = process.sliceSupportNozzle; sslice.supports = slice.supports.slice(); + sslice.height = slice.height; sslices.push(sslice); } if (sslices.length) { - let swidget = KIRI.newWidget(); + let swidget = KIRI.newWidget(null,widget.group); swidget.slices = sslices; swidget.support = true; - swidget.mesh = { widget: swidget, position: widget.mesh.position }; + swidget.rotinfo = widget.rotinfo; + swidget.belt = widget.belt; + swidget.track = Object.clone(widget.track); + swidget.mesh = { widget: swidget, position: swidget.track.pos }; settings.widget[swidget.id] = { extruder: process.sliceSupportNozzle }; widgets.push(swidget); } } + let lastPoly; + let lastLayer; let extruders = []; let extcount = 0; // find max layers (for updates) // generate list of used extruders for purge blocks for (let widget of widgets) { - maxLayers = Math.max(maxLayers, widget.slices.length); let extruder = (settings.widget[widget.id] || {}).extruder || 0; if (!extruders[extruder]) { extruders[extruder] = {}; @@ -265,13 +276,15 @@ // generate purge block for given nozzle function purge(nozzle, track, layer, start, z, using) { - if (extcount < 2) { + if (extcount < 2 || isBelt) { return start; } let rec = track[nozzle]; if (rec) { track[nozzle] = null; - if (layer.last()) layer.last().retract = true; + if (layer.last()) { + layer.last().retract = true; + } start = print.polyPrintPath(rec.poly.clone().setZ(z), start, layer, { tool: using || nozzle, open: true, @@ -288,13 +301,15 @@ // establish offsets for (let widget of widgets) { let { rotinfo, belt } = widget; - // console.log({rotinfo, belt}); - let offset = widget.mesh ? Object.clone(widget.mesh.position) : {x:0, y:0, z:0}; + let offset = Object.clone(widget.track.pos); if (isBelt) { - offset.x = rotinfo.xpos; - offset.y = -belt.midy; - offset.y += rotinfo.ypos * beltfact; - offset.z = rotinfo.ypos * beltfact; + let o = rotinfo.ypos * beltfact; + offset = { + x: rotinfo.xpos, + y: o - belt.midy - belt.yadd/2, + z: o, + }; + // offset = { x:0, y:0, z:0 }; // locate the lowest point in slices and widget overall let minby = Infinity; for (let slice of widget.slices) { @@ -308,10 +323,11 @@ } } } + // todo remove b/c this is now calculated at slice time // flag slices as being on or off the bed / belt - for (let slice of widget.slices) { - slice.onbelt = Math.abs(minby - slice.minby) < 0.01; - } + // for (let slice of widget.slices) { + // slice.onbelt = Math.abs(minby - slice.minby) < 0.01; + // } } else { // when rafts used this is non-zero offset.z = zoff; @@ -341,6 +357,8 @@ return a.z - b.z; }); + let lastWidget; + // walk cake layers bottom up for (let layer of cake) { // track purge blocks generated for each layer @@ -376,41 +394,63 @@ return a.dst - b.dst; }); let { z, slice, offset } = order[0]; + let params = getRangeParameters(settings, slice.index); slice.prep = true; - // retract between widgets - if (layerout.length && minidx !== lastIndex) { + // retract between widgets or layers (when set) + if (layerout.length && slice.widget !== lastWidget) { layerout.last().retract = true; } - layerout.z = z; + lastWidget = slice.widget; + layerout.z = z + slice.height / 2; layerout.height = layerout.height || slice.height; + layerout.slice = slice; + // mark layer as anchor if slice is belt and flag set + layerout.anchor = slice.belt && slice.belt.anchor; // detect extruder change and print purge block if (!lastOut || lastOut.extruder !== slice.extruder) { printPoint = purge(slice.extruder, track, layerout, printPoint, slice.z); } - let tmpout = []; let wtb = slice.widget.track.box; // output seek to start point between mesh slices if previous data printPoint = print.slicePrintPath( slice, - isBelt ? newPoint(-wtb.w, wtb.d * 2, 0) : printPoint.sub(offset), + slice.belt && slice.belt.touch ? newPoint(-wtb.w, wtb.d * 2, 0) : printPoint.sub(offset), offset, - tmpout, //layerout, + layerout, { + seedPoint: printPoint.sub(offset), + danger: isDanger, + params, // range parameters first: slice.index === 0, support: slice.widget.support, - onBelt: slice.onbelt + onBelt: slice.belt && slice.belt.touch, + pretract: (wipeDist) => { + if (lastLayer && lastLayer.length) { + let lastOut = lastLayer.last(); + lastOut.retract = true; + if (wipeDist && lastPoly && lastOut.point) { + let endpoint = lastOut.point.followTo(lastPoly.center(true).add(offset), wipeDist); + if (endpoint.inPolygon(lastPoly)) { + print.addOutput(lastLayer, endpoint); + } + } + } + } } ); - layerout.appendAll(tmpout); lastOut = slice; lastExt = lastOut.ext - lastIndex = minidx; + lastPoly = slice.lastPoly; + lastLayer = layerout; + if (layerRetract && layerout.length) { + layerout.last().retract = true; + } } // if a declared extruder isn't used in a layer, use selected // extruder to fill the relevant purge blocks for later support track.forEach(ext => { - if (ext) { + if (ext && lastOut) { printPoint = purge(ext.extruder, track, layerout, printPoint, lastOut.z, lastExt); } }); @@ -421,13 +461,13 @@ } // retract after last layer - if (layer === maxLayers && layerout.length) { + if (layerno === cake.length - 1 && layerout.length) { layerout.last().retract = true; } // notify progress - layerout.layer = layer++; - update((layer / maxLayers) * 0.5, "prepare"); + layerout.layer = layerno++; + update((layerno / cake.length) * 0.5, "prepare"); slices = []; layerout = []; @@ -454,6 +494,13 @@ rec.point.y += poff; } } + + // add lead in, when specified + // if (process.firstLayerBeltLead) { + // // add belt lead in + // console.log({process, output}); + // } + let thresh = firstLayerHeight * 1.05; // iterate over layers, find extrusion on belt and // apply corrections and add brim when specified @@ -479,13 +526,17 @@ } lastout = rec; } - // skip if brig trigger not met - if (firstLayerBrimTrig && mins > firstLayerBrimTrig) { + // skip if brim trigger not met + if (firstLayerBrimTrig > 0 && mins > firstLayerBrimTrig) { + continue; + } + // do not add brims to anchor layers + if (layer.anchor) { continue; } // add brim, if specified if (first && firstLayerBrim) { - let {emit, tool } = first; + let { emit, tool } = first; let y = maxy; let z = minz; let b = Math.max(firstLayerBrim, 1); @@ -541,8 +592,11 @@ const flat = opts.flat; const thin = opts.thin && !flat; const ckspeed = opts.speed !== false; + const headColor = 0x888888; const moveColor = opts.move >= 0 ? opts.move : 0xaaaaaa; const printColor = opts.print >= 0 ? opts.print : 0x777700; + const arrowAll = false; + const arrowSize = arrowAll ? 0.2 : 0.4; const layers = []; const moveOpt = { @@ -569,6 +623,7 @@ let lastOut = null; let current = null; let retracted = false; + let retractz = 0; function color(point) { return FDM.rateToColor(point.speed, maxspd); @@ -604,6 +659,7 @@ if (out.retract) { retracts.push(out.point); retracted = true; + retractz++; } if (!out.point) { // in cam mode, these are drilling or dwell ops @@ -611,7 +667,7 @@ } if (lastOut) { - if (lastOut.emit !== out.emit) { + if (arrowAll || lastOut.emit !== out.emit) { heads.push({p1: lastOut.point, p2: out.point}); } const op = out.point, lp = lastOut.point; @@ -670,14 +726,14 @@ } if (heads.length) { output - .setLayer('arrows', { face: moveColor, line: 0x555555, opacity: 0.5 }, true) + .setLayer('arrows', { face: headColor, line: 0x112233, opacity: 0.5 }, true) .addAreas(heads.map(points => { const {p1, p2} = points; const slope = p2.slopeTo(p1); const s1 = BASE.newSlopeFromAngle(slope.angle + 20); const s2 = BASE.newSlopeFromAngle(slope.angle - 20); - const p3 = points.p2.projectOnSlope(s1, 0.4); - const p4 = points.p2.projectOnSlope(s2, 0.4); + const p3 = points.p2.projectOnSlope(s1, arrowSize); + const p4 = points.p2.projectOnSlope(s2, arrowSize); return newPolygon().addPoints([p2,p3,p4]).setZ(p2.z + 0.01); }), { thin: true, outline: true }); } @@ -685,9 +741,7 @@ .setLayer(opts.other || 'move', moveOpt, opts.moves !== true) .addPolys(moves, { thin: true, z: opts.z }); // force level when present - if (level.z) { - opts.z = level.z; - } + let pz = level.z ? level.z - height : opts.z; Object.values(prints).forEach(array => { array.forEach(poly => { if (flat && poly.appearsClosed()) { @@ -700,10 +754,10 @@ .addPolys([ poly ], thin ? { thin, z: opts.z, color: poly.color } : flat ? { - flat, z: opts.z, color: poly.color, + flat, z: pz, color: poly.color, outline: true, offset: array.width, open: poly.open } : { - offset: array.width, height, z: opts.z, + offset: array.width, height, z: pz, color: { face: poly.color, line: poly.color } }) }); @@ -711,7 +765,7 @@ update(index / levels.length, output); }); - + // console.log({retractz}); return layers; } diff --git a/src/mode/fdm/slice.js b/src/mode/fdm/slice.js index 17406b53..688d3c40 100644 --- a/src/mode/fdm/slice.js +++ b/src/mode/fdm/slice.js @@ -21,9 +21,13 @@ shell: { check: 0x0077bb, face: 0x0077bb, line: 0x0077bb, opacity: 1 }, fill: { check: 0x00bb77, face: 0x00bb77, line: 0x00bb77, opacity: 1 }, infill: { check: 0x3322bb, face: 0x3322bb, line: 0x3322bb, opacity: 1 }, - support: { check: 0xaa5533, face: 0xaa5533, line: 0xaa5533, opacity: 1 } + support: { check: 0xaa5533, face: 0xaa5533, line: 0xaa5533, opacity: 1 }, + gaps: { check: 0xaa3366, face: 0xaa3366, line: 0xaa3366, opacity: 1 } }, - PROTO = Object.clone(COLOR); + PROTO = Object.clone(COLOR), + bwcomp = (1 / Math.cos(Math.PI/4)), + getRangeParameters = FDM.getRangeParameters, + debug = false; let isThin = false; // force line rendering let isFlat = false; // force flat rendering @@ -57,14 +61,16 @@ FDM.slice = function(settings, widget, onupdate, ondone) { FDM.fixExtruders(settings); let render = settings.render !== false, + ctrl = settings.controller, spro = settings.process, sdev = settings.device, isBelt = sdev.bedBelt, + isSynth = widget.track.synth, update_start = Date.now(), minSolid = spro.sliceSolidMinArea, solidLayers = spro.sliceSolidLayers, - vaseMode = spro.sliceFillType === 'vase', - doSolidLayers = solidLayers && !vaseMode, + vaseMode = spro.sliceFillType === 'vase' && !isSynth, + doSolidLayers = solidLayers && !vaseMode && !isSynth, metadata = settings.widget[widget.id] || {}, extruder = metadata.extruder || 0, sliceHeight = spro.sliceHeight, @@ -77,10 +83,13 @@ fillSpacing = lineWidth, fillOffset = lineWidth * fillOffsetMult, sliceFillAngle = spro.sliceFillAngle, - view = widget.mesh && widget.mesh.newGroup ? widget.mesh.newGroup() : null; + supportDensity = spro.sliceSupportDensity, + view = widget.mesh && widget.mesh.newGroup ? widget.mesh.newGroup() : null, + beltfact = Math.cos(Math.PI/4), + invbfact = 1 / beltfact; - isFlat = settings.controller.lineType === "flat"; - isThin = !isFlat && settings.controller.lineType === "line"; + isFlat = ctrl.lineType === "flat"; + isThin = !isFlat && ctrl.lineType === "line"; offset = lineWidth / 2; if (isFlat) { @@ -145,6 +154,7 @@ height: sliceHeight, minHeight: sliceMinHeight, firstHeight: firstSliceHeight, + union: ctrl.healMesh, // debug: true, // xray: 3, // view: view @@ -155,9 +165,9 @@ } function onSliceDone(slices) { - // slices = slices.filter(slice => slice.tops.length); // remove all empty slices above part but leave below // for multi-part (multi-extruder) setups where the void is ok + // also reverse because slicing occurs bottom-up let found = false; slices = slices.reverse().filter(slice => { if (slice.tops.length) { @@ -173,6 +183,61 @@ return; } + // for synth support widgets, merge tops + if (isSynth) { + for (let slice of slices) { + // union top support polys + let tops = slice.topPolys(); + let union = POLY.union(tops, null, true); + if (union.length < tops.length) { + slice.tops = []; + for (let u of union) { + slice.addTop(u); + } + } + let gap = sliceHeight * (isBelt ? 0 : spro.sliceSupportGap); + // clip tops to other widgets in group + tops = slice.topPolys(); + for (let peer of widget.group) { + // skip self + if (peer === widget) { + continue; + } + for (let pslice of peer.slices) { + if (Math.abs(Math.abs(pslice.z - slice.z) - gap) > 0.1) { + continue; + } + // offset pslice tops by spro.sliceSupportOffset + if (!pslice.synth_off) { + pslice.synth_off = POLY.offset(pslice.topPolys(), spro.sliceSupportOffset); + } + let ptops = pslice.synth_off; + let ntops = []; + POLY.subtract(tops, ptops, ntops, null, slice.z, 0); + tops = ntops; + } + // trim to group's shadow if not in belt mode + if (!isBelt) { + let group = widget.group[0]; + if (!group.shadow) { + let gs = []; + for (let w of group) { + if (w.shadow) { + gs = POLY.union([w.shadow,...gs],null,0.1); + } + } + group.shadow = gs; + } + tops = POLY.setZ(POLY.trimTo(tops, group.shadow), slice.z); + } + } + slice.tops = []; + for (let t of tops) { + slice.addTop(t); + } + } + } + // calculate % complete and call onupdate() function doupdate(index, from, to, msg) { onupdate(0.5 + (from + ((index/slices.length) * (to-from))) * 0.5, msg); @@ -197,16 +262,15 @@ slice.solids = []; }); - // fixed supports (generated ahead of time) - const fixed = Object.values(settings.widget[widget.id].support || {}); + // create shadow for clipping supports let shadow = null; - if (fixed.length || spro.sliceSupportEnable) { - // create shadow. TODO test if any supports set + if (true || spro.sliceSupportEnable) { let alltops = slices.map(slice => slice.topPolys()).flat(); shadow = POLY.union(alltops,null,0.1); if (spro.sliceSupportExtra) { shadow = POLY.offset(shadow, spro.sliceSupportExtra); } + widget.shadow = shadow; // slices[0].output() // .setLayer('shadow', { line: 0xff0000, check: 0xff0000 }) // .addPolys(shadow); @@ -214,28 +278,100 @@ // create shells and diff inner fillable areas forSlices(0.0, 0.2, slice => { + let params = getRangeParameters(settings, slice.index); + let shellFrac = (params.sliceShells - (params.sliceShells | 0)); + let sliceShells = params.sliceShells | 0; + if (ctrl.danger && shellFrac) { + let v1 = shellFrac > 0.5 ? 1 - shellFrac : shellFrac; + let v2 = 1 - v1; + let parts = Math.round(v2/v1) + 1; + let rem = slice.index % parts; + let trg = shellFrac > 0.5 ? 1 : parts - 1; + sliceShells += rem >= trg ? 1 : 0; + } let first = slice.index === 0; - let solid = ( - slice.index < spro.sliceBottomLayers || - slice.index > slices.length - spro.sliceTopLayers-1 || - spro.sliceFillSparse > 0.95 - ) && !vaseMode; + let isBottom = slice.index < spro.sliceBottomLayers; + let isTop = slice.index > slices.length - spro.sliceTopLayers-1; + let isDense = params.sliceFillSparse > 0.98; + let solid = (isBottom || ((isTop || isDense) && !vaseMode)) && !isSynth; + let solidWidth = params.sliceFillWidth || 1; let spaceMult = first ? spro.firstLayerLineMult || 1 : 1; let offset = shellOffset * spaceMult; let fillOff = fillOffset * spaceMult; - doShells(slice, spro.sliceShells, offset, fillOff, { + let count = isSynth ? 1 : sliceShells; + doShells(slice, count, offset, fillOff, { vase: vaseMode, - thin: spro.detectThinWalls, - belt0: isBelt && first, - widget: widget + thin: spro.detectThinWalls && !isSynth, + widget: widget, + danger: ctrl.danger }); if (solid) { - let fillSpace = fillSpacing * spaceMult; + let fillSpace = fillSpacing * spaceMult * solidWidth; doSolidLayerFill(slice, fillSpace, sliceFillAngle); } sliceFillAngle += 90.0; }, "offsets"); + // add lead in when specified in belt mode + if (!isSynth && isBelt) { + let wb = widget.bounds; + // find adjusted zero point from slices + let smin = Infinity; + for (let slice of slices) { + let miny = Infinity; + for (let poly of slice.topPolys()) { + let y = poly.bounds.maxy; + let z = slice.z; + let by = -y + z; + if (by < miny) miny = by; + if (by < smin) smin = by; + } + slice.belt = { miny, touch: false }; + } + // mark slices with tops touching belt + // also find max width of first 5 layers + let start; + let minx = Infinity, maxx = -Infinity; + for (let slice of slices) { + if (slice.index < 5) { + for (let poly of slice.topPolys()) { + minx = Math.min(minx, poly.bounds.minx); + maxx = Math.max(maxx, poly.bounds.maxx); + } + } + if (Math.abs(slice.belt.miny - smin) < 0.001) { + slice.belt.touch = true; + if (!start) start = slice; + } + } + // console.log({smin: smin.round(4)}); + let offset = spro.firstLayerBeltLead * beltfact; + // ensure we start against a layer with shells + while (start.up && start.topShells().length === 0) { + start = start.up; + } + while (offset && start && offset >= sliceHeight) { + let addto = start.down; + if (!addto) { + addto = newSlice(start.z - sliceHeight); + addto.belt = { }; + addto.height = start.height; + addto.up = start; + start.down = addto; + slices.splice(0,0,addto); + } + addto.index = -1; + addto.belt.anchor = true; + let z = addto.z; + let y = z - smin - (nozzleSize / 2); + // let splat = BASE.newPolygon().add(wb.min.x, y, z).add(wb.max.x, y, z).setOpen(); + let splat = BASE.newPolygon().add(minx, y, z).add(maxx, y, z).setOpen(); + addto.addTop(splat).fill_sparse = [ splat ]; + start = addto; + offset -= sliceHeight; + } + } + // calculations only relevant when solid layers are used if (doSolidLayers) { forSlices(0.2, 0.34, slice => { @@ -246,48 +382,68 @@ projectBridges(slice, solidLayers); }, "solids"); forSlices(0.35, 0.5, slice => { + let params = getRangeParameters(settings, slice.index); let first = slice.index === 0; - let spaceMult = first ? spro.firstLayerLineMult || 1 : 1; - let fillSpace = fillSpacing * spaceMult; + let solidWidth = params.sliceFillWidth || 1; + let spaceMult = first ? params.firstLayerLineMult || 1 : 1; + let fillSpace = fillSpacing * spaceMult * solidWidth; doSolidsFill(slice, fillSpace, sliceFillAngle, minSolid); sliceFillAngle += 90.0; }, "solids"); } // sparse layers only present when non-vase mose and sparse % > 0 - if (!vaseMode && spro.sliceFillSparse > 0.0) { + if (!isSynth) { + let lastType; forSlices(0.5, 0.7, slice => { + let params = getRangeParameters(settings, slice.index); + if (vaseMode || !params.sliceFillSparse) { + return; + } + let newType = params.sliceFillType; doSparseLayerFill(slice, { settings: settings, process: spro, device: sdev, lineWidth: lineWidth, spacing: fillOffset, - density: spro.sliceFillSparse, + density: params.sliceFillSparse, bounds: widget.getBoundingBox(), height: sliceHeight, - type: spro.sliceFillType + type: newType, + cache: params._range !== true && lastType === newType }); + lastType = newType; + }, "infill"); + } else if (isSynth) { + forSlices(0.5, 0.7, slice => { + let params = getRangeParameters(settings, slice.index); + let density = params.sliceSupportDensity; + if (density) + for (let top of slice.tops) { + let offset = []; + POLY.expand(top.shells, -nozzleSize/4, slice.z, offset); + fillSupportPolys(offset, lineWidth, density, slice.z); + top.fill_lines = offset.map(o => o.fill).flat().filter(v => v); + } }, "infill"); } - // calculations only relevant when supports are enabled - if (!isBelt) { - let auto = spro.sliceSupportEnable && spro.sliceSupportDensity > 0.0, - minArea = spro.sliceSupportArea; - + // auto support generation + if (!isBelt && !isSynth && supportDensity && spro.sliceSupportEnable) { forSlices(0.7, 0.8, slice => { - doSupport(slice, spro, auto, fixed, shadow); + doSupport(slice, spro, shadow); }, "support"); forSlices(0.8, 0.9, slice => { - doSupportFill(slice, lineWidth, spro.sliceSupportDensity, minArea); + doSupportFill(slice, lineWidth, supportDensity, spro.sliceSupportArea); }, "support"); } // render if not explicitly disabled if (render) { forSlices(0.9, 1.0, slice => { - doRender(slice); + let params = getRangeParameters(settings, slice.index); + doRender(slice, isSynth, params); }, "render"); } @@ -310,32 +466,35 @@ return Math.max(min,Math.min(max,v)); } - function doRender(slice) { + function doRender(slice, isSynth, params) { const output = slice.output(); const height = slice.height / 2; + const solidWidth = params.sliceFillWidth || 1; slice.tops.forEach(top => { - if (isThin) { - output - .setLayer('slice', { line: 0x000066, check: 0x000066 }) - .addPolys(top.poly); - } + if (isThin) output + .setLayer('slice', { line: 0x000066, check: 0x000066 }) + .addPolys(top.poly); - output - .setLayer("shells", COLOR.shell) + if (top.shells) output + .setLayer("shells", isSynth ? COLOR.support : COLOR.shell) .addPolys(top.shells, vopt({ offset, height })); - // if (isThin && debug) { - // slice.output() - // .setLayer('offset', { face: 0, line: 0x888888 }) - // .addPolys(top.fill_off) - // .setLayer('last', { face: 0, line: 0x008888 }) - // .addPolys(top.last); - // } + if (top.gaps) output + .setLayer("gaps", COLOR.gaps) + .addPolys(top.gaps, vopt({ offset, height, thin: true })); - if (top.fill_lines) output - .setLayer("fill", COLOR.fill) - .addLines(top.fill_lines, vopt({ offset, height })); + if (isThin && debug) { + slice.output() + .setLayer('offset', { face: 0, line: 0x888888 }) + .addPolys(top.fill_off) + .setLayer('last', { face: 0, line: 0x008888 }) + .addPolys(top.last); + } + + if (top.fill_lines && top.fill_lines.length) output + .setLayer("fill", isSynth ? COLOR.support : COLOR.fill) + .addLines(top.fill_lines, vopt({ offset: offset * solidWidth, height })); if (top.fill_sparse) output .setLayer("infill", COLOR.infill) @@ -344,15 +503,22 @@ if (top.thin_fill) output .setLayer("fill", COLOR.fill) .addLines(top.thin_fill, vopt({ offset, height })); - - // emit solid areas - // if (isThin && debug) { - // output - // .setLayer("solids", { face: 0x00dd00 }) - // .addAreas(slice.solids); - // } }); + if (isThin && debug) { + if (slice.solids) output + .setLayer("solids", { face: 0x00dd00 }) + .addAreas(slice.solids); + + if (slice.bridges) output + .setLayer("bridges", { face: 0x00aaaa, line: 0x00aaaa }) + .addAreas(slice.bridges); + + if (slice.flats) output + .setLayer("flats", { face: 0xaa00aa, line: 0xaa00aa }) + .addAreas(slice.flats); + } + if (slice.supports) output .setLayer("support", COLOR.support) .addPolys(slice.supports, vopt({ offset, height })); @@ -363,16 +529,6 @@ .addLines(poly.fill, vopt({ offset, height })); }); - // if (isThin && debug) { - // output - // .setLayer("bridges", { face: 0x00aaaa, line: 0x00aaaa }) - // .addAreas(top.bridges); - // - // output - // .setLayer("flats", { face: 0xaa00aa, line: 0xaa00aa }) - // .addAreas(top.flats); - // } - // console.log(slice.index, slice.render.stats); } @@ -433,7 +589,22 @@ let dist = p.first().distTo2D(p.last()); if (dist < 1) p.open = false; } }); - if (opt.thin) { + if (opt.danger && opt.thin) { + top.thin_fill = []; + top.fill_sparse = []; + let layers = POLY.inset(top_poly, offsetN, count, z); + last = layers.last().mid; + top.shells = layers.map(r => r.mid).flat(); + top.gaps = layers.map(r => r.gap).flat(); + let off = offsetN; + let min = off * 0.75; + let max = off * 4; + for (let poly of layers.map(r => r.gap).flat()) { + let centers = poly.centers(off/2, z, min, max, {lines:false}); + top.fill_sparse.appendAll(centers); + // top.fill_lines.appendAll(centers); + } + } else if (opt.thin) { top.thin_fill = []; let oso = {z, count, gaps: [], outs: [], minArea: 0.05}; POLY.offset(top_poly, [-offset1, -offsetN], oso); @@ -444,6 +615,11 @@ if (p.fill_off) { p.fill_off.forEach(pi => pi.depth = i); } + if (p.inner) { + for (let pi of p.inner) { + pi.depth = p.depth; + } + } top.shells.push(p); }); last = polys; @@ -453,12 +629,9 @@ oso.gaps.forEach((polys, i) => { let off = (i == 0 ? offset1 : offsetN); polys = POLY.offset(polys, -off * 0.8, {z, minArea: 0}); - // polys.forEach(p => { slice.solids.trimmed.push(p); }); top.thin_fill.appendAll(cullIntersections( fillArea(polys, 45, off/2, [], 0.01, off*2), fillArea(polys, 135, off/2, [], 0.01, off*2), - // fillArea(polys, 90, off, [], 0.05, off*4), - // fillArea(polys, 180, off, [], 0.05, off*4), )); gaps = polys; }); @@ -481,6 +654,11 @@ // use negative offset for inners pi.depth = -(count - countNow); }); + if (p.inner) { + for (let pi of p.inner) { + pi.depth = p.depth; + } + } }); }); } @@ -507,23 +685,6 @@ top.last = last; shellout += top.shells.length; - - // add anchor extrusion if missing on the first belt layer - // todo: fix. disabled because throws off layer min y calculations in some cases - if (false && opt.belt0 && top.shells.length === 0 && slice.up && slice.up.tops.length) { - for (let up of slice.up.tops) { - let bounds = up.poly.bounds, - midy = (bounds.miny + bounds.maxy) / 2; - if (top.poly.isInside(up.poly, 1)) { - slice.widget.belt.ymid = midy; - slice.widget.belt.zmid = slice.z; - top.shells.push(BASE.newPolygon() - .setOpen() - .add(bounds.minx,midy,slice.z) - .add(bounds.maxx,midy,slice.z)); - } - } - } }); }; @@ -559,6 +720,7 @@ density = options.density, // density of infill 0.0 - 1.0 bounds = options.bounds, // bounding box of widget height = options.height, // z layer height + cache = !(options.cache === false), type = options.type || 'hex'; if (slice.tops.length === 0 || density === 0.0 || slice.isSolidLayer) { @@ -622,7 +784,7 @@ // prepare top infill structure tops.forEach(function(top) { - top.fill_sparse = []; + top.fill_sparse = top.fill_sparse || []; polys.appendAll(top.fill_off); polys.appendAll(top.solids); }); @@ -630,7 +792,7 @@ // update fill fingerprint for this slice slice._fill_finger = POLY.fingerprint(polys); - let skippable = FILLFIXED[type] ? true : false; + let skippable = cache && FILLFIXED[type] ? true : false; let miss = false; // if the layer below has the same fingerprint, // we may be able to clone the infill instead of regenerating it @@ -779,25 +941,27 @@ let minarea = minArea || 1, tops = slice.tops, - solids = slice.solids, - unioned = POLY.union(solids, undefined, true).flat(), // TODO verify + solids = slice.solids; + + if (!(tops && solids)) { + return; + } + + let unioned = POLY.union(solids, undefined, true).flat(), // TODO verify isSLA = (spacing === undefined && angle === undefined); if (solids.length === 0) return false; if (unioned.length === 0) return false; - let masks, - trims = [], + let trims = [], inner = isSLA ? slice.topPolys() : slice.topFillOff(); // trim each solid to the inner bounds unioned.forEach(function(p) { p.setZ(slice.z); inner.forEach(function(i) { - if (p.del) return; - masks = p.mask(i); + let masks = p.mask(i); if (masks && masks.length > 0) { - p.del = true; trims.appendAll(masks); } }); @@ -832,6 +996,10 @@ // create empty filled line array for each top tops.forEach(function(top) { + // synth belt anchor tops don't want fill + if (!top.fill_lines) { + return; + } const tofill = []; const angfill = []; const newfill = []; @@ -857,7 +1025,6 @@ top.fill_lines_ang.poly.push(af.clone()); }); } - top.fill_lines.appendAll(newfill); }); @@ -865,12 +1032,10 @@ }; /** - * calculate external overhangs requiring support. - * this is done bottom-up except for fixed supports (manual) + * calculate external overhangs requiring support */ - function doSupport(slice, proc, auto, fixed, shadow) { - let minOffset = proc.sliceSupportOffset, - maxBridge = proc.sliceSupportSpan || 5, + function doSupport(slice, proc, shadow) { + let maxBridge = proc.sliceSupportSpan || 5, minArea = proc.supportMinArea, pillarSize = proc.sliceSupportSize, offset = proc.sliceSupportOffset, @@ -900,7 +1065,7 @@ let supported = point.isInPolygonOnly(down_tops); if (!supported) down_traces.forEach(function(trace) { trace.forEachSegment(function(p1, p2) { - if (point.distToLine(p1, p2) <= minOffset) { + if (point.distToLine(p1, p2) <= offset) { return supported = true; } }); @@ -927,7 +1092,7 @@ let supports = []; // generate support polys from unsupported points - if (auto && slice.down) (function() { + if (slice.down) (function() { // check trace line support needs traces.forEach(function(trace) { trace.forEachSegment(function(p1, p2) { checkLineSupport(p1, p2, true) }); @@ -952,21 +1117,10 @@ }); })(); - if (supports.length === 0 && !(fixed && fixed.length)) { + if (supports.length === 0) { return; } - if (fixed && fixed.length) { - for (let sup of fixed) { - let zmin = sup.z - sup.dh / 2; - let zmax = sup.z + sup.dh / 2; - if (slice.z >= zmin && slice.z <= zmax) { - let center = BASE.newPoint(sup.x, sup.y, slice.z); - supports.push(BASE.newPolygon().centerRectangle(center, sup.dw, sup.dw)); - } - } - } - // then union supports supports = POLY.union(supports, null, true); @@ -984,7 +1138,7 @@ // set depth hint on support polys for infill density trimmed.forEach(function(trim) { - if (trim.area() < 0.1) return; + // if (trim.area() < 0.1) return; culled.push(trim.setZ(down.z)); }); @@ -1031,23 +1185,29 @@ supports = nsC; } - if (supports) supports.forEach(function (poly) { + if (supports) { + fillSupportPolys(supports, linewidth, density, slice.z); + } + + // re-assign new supports back to slice + slice.supports = supports; + }; + + function fillSupportPolys(polys, linewidth, density, z) { + // calculate fill density + let spacing = linewidth * (1 / density); + polys.forEach(function (poly) { // angle based on width/height ratio let angle = (poly.bounds.width() / poly.bounds.height() > 1) ? 90 : 0; - // calculate fill density - let spacing = linewidth * (1 / density); // inset support poly for fill lines 33% of nozzle width - let inset = POLY.offset([poly], -linewidth/3, {flat: true, z: slice.z}); + let inset = POLY.offset([poly], -linewidth/3, {flat: true, z}); // do the fill if (inset && inset.length > 0) { fillArea(inset, angle, spacing, poly.fill = []); } return true; }); - - // re-assign new supports back to slice - slice.supports = supports; - }; + } /** * @@ -1126,14 +1286,16 @@ } FDM.supports = function(settings, widget) { + let isBelt = settings.device.bedBelt; let process = settings.process; let size = process.sliceSupportSize; - let min = process.sliceSupportArea || 0.1; - let buf = new THREE.BufferGeometry(); - buf.setAttribute('position', new THREE.BufferAttribute(widget.vertices, 3)); + let min = process.sliceSupportArea || 1; + let geo = new THREE.BufferGeometry(); + geo.setAttribute('position', new THREE.BufferAttribute(widget.vertices, 3)); let mat = new THREE.MeshBasicMaterial(); - let geo = new THREE.Geometry().fromBufferGeometry(buf); - let angle = (Math.PI / 180) * settings.process.sliceSupportAngle; + let rad = (Math.PI / 180); + let deg = (180 / Math.PI); + let angle = rad * settings.process.sliceSupportAngle; let thresh = -Math.sin(angle); let dir = new THREE.Vector3(0,0,-1) let add = []; @@ -1167,6 +1329,13 @@ if (point.added) { return; } + for (let added of add) { + let p2 = new THREE.Vector2(point.x, point.y); + let pm = new THREE.Vector2(added.mid.x, added.mid.y); + if (p2.distanceTo(pm) < 1) { + return; + } + } let ray = new THREE.Raycaster(point, dir); let int = ray.intersectObjects([ mesh, platform ], false); if (int && int.length && int[0].distance > 0.01) { @@ -1175,20 +1344,33 @@ point.added = true; } } - geo.faces.filter(f => f.normal.z < thresh).forEach(face => { - let a = geo.vertices[face.a]; - let b = geo.vertices[face.b]; - let c = geo.vertices[face.c]; + let filter = isBelt ? (norm) => { + return norm.z < thresh && norm.y < -0.001; + } : (norm) => { + return norm.z < thresh; + }; + let { position } = geo.attributes; + let { itemSize, count, array } = position; + for (let i = 0; i 0; }; diff --git a/src/mode/laser/driver.js b/src/mode/laser/driver.js index 36847b82..10fb3196 100644 --- a/src/mode/laser/driver.js +++ b/src/mode/laser/driver.js @@ -301,7 +301,7 @@ e = o.point; e.x += p.max.w / 2 - m.fit.x; e.y += p.max.h / 2 - m.fit.y; - // e.z = 0; + e.z = 0; }); } diff --git a/src/mode/sla/export.js b/src/mode/sla/export.js index fd41292d..c8974928 100644 --- a/src/mode/sla/export.js +++ b/src/mode/sla/export.js @@ -476,6 +476,8 @@ // }; // } else // load renderer code in worker context only + let wasm; + fetch('/wasm/kiri-sla.wasm') .then(response => response.arrayBuffer()) .then(bytes => WebAssembly.instantiate(bytes, { @@ -488,7 +490,7 @@ let {module, instance} = results; let {exports} = instance; let heap = new Uint8Array(exports.memory.buffer); - self.wasm = { + wasm = { heap, memory: exports.memory, render: exports.render, @@ -502,7 +504,6 @@ let width2 = width / 2, height2 = height / 2; let array = []; let count = 0; - let wasm = self.wasm; function scaleMovePoly(poly) { let points = poly.points; diff --git a/src/moto/ctrl.js b/src/moto/ctrl.js index 0155dad6..8115aa6c 100644 --- a/src/moto/ctrl.js +++ b/src/moto/ctrl.js @@ -181,6 +181,15 @@ MOTO.CTRL = function (object, domElement, notify, slider) { } }; + this.getTarget = function() { + return this.target; + }; + + this.setTarget = function(t) { + this.target = t; + this.target0 = t.clone(); + } + function isValue(v) { return v !== null && v !== undefined; } diff --git a/src/moto/space.js b/src/moto/space.js index b1b1111c..5aaed5d5 100644 --- a/src/moto/space.js +++ b/src/moto/space.js @@ -16,8 +16,13 @@ PI2 = PI / 2, PI4 = PI / 4, ROUND = Math.round, + panX = 0, panY = 0, + panZ = 0, + home = 0, gridZOff = 0, + tweenTime = 500, + tweenDelay = 20, platformZOff = 0, perspective = 35, refreshTimeout = null, @@ -92,6 +97,7 @@ camera, renderer, container, + raycaster, freezeTo, freeze, isRound = false, @@ -139,7 +145,7 @@ function tweenit() { TWEEN.update(); - setTimeout(tweenit, 20); + setTimeout(tweenit, tweenDelay); } tweenit(); @@ -170,12 +176,13 @@ } } new TWEEN.Tween(from). - to(to, 500). + to(to, tweenTime). onUpdate(tf). onComplete(() => { viewControl.setPosition(pos); updateLastAction(); refresh(); + if (pos.then) pos.then(); }). start(); } @@ -195,7 +202,7 @@ setGrid(); }; new TWEEN.Tween(from). - to(to, 500). + to(to, tweenTime). onStart(start). onUpdate(update). onComplete(complete). @@ -676,14 +683,15 @@ if (points.length % 2 != 0) { throw "invalid line : "+points.length; } - const geo = new THREE.Geometry(); - for (let i=0; i < points.length; ) { - const p1 = points[i++]; - const p2 = points[i++]; - geo.vertices.push(new THREE.Vector3(p1.x, p1.y, p1.z)); - geo.vertices.push(new THREE.Vector3(p2.x, p2.y, p2.z)); + const geo = new THREE.BufferGeometry(); + const vrt = new Float32Array(points.length * 3); + let vi = 0; + for (let p of points) { + vrt[vi++] = p.x; + vrt[vi++] = p.y; + vrt[vi++] = p.z; } - geo.verticesNeedUpdate = true; + geo.setAttribute('position', new THREE.BufferAttribute(vrt, 3)); return new THREE.LineSegments(geo, new THREE.LineBasicMaterial({ color: color, opacity: opacity || 1, @@ -692,9 +700,7 @@ } function intersect(objects, recurse) { - let lookAt = new THREE.Vector3(mouse.x, mouse.y, 0.0).unproject(camera); - let ray = new THREE.Raycaster(camera.position, lookAt.sub(camera.position).normalize()); - return ray.intersectObjects(objects, recurse); + return raycaster.intersectObjects(objects, recurse); } /** ****************************************************************** @@ -802,6 +808,11 @@ updateLastAction(); let int, vis; + const mv = new THREE.Vector2(); + mv.x = ( event.clientX / window.innerWidth ) * 2 - 1; + mv.y = - ( event.clientY / window.innerHeight ) * 2 + 1; + raycaster.setFromCamera( mv, camera ); + if (viewControl.enabled) { event.preventDefault(); let selection = mouseHover ? mouseHover() : null; @@ -847,6 +858,7 @@ onEnterKey: onEnterKey, onResize: onResize, update: requestRefresh, + raycast: intersect, refresh: refresh, showSkyGrid: function(b) { @@ -884,6 +896,7 @@ }, platform: { + update: updateDraws, tweenTo: tweenPlatform, setSize: setPlatformSizeUpdateGrid, setColor: setPlatformColor, @@ -896,6 +909,7 @@ add: function(o) { WORLD.add(o) }, remove: function(o) { WORLD.remove(o) }, setMaxZ: function(z) { panY = z / 2 }, + setCenter: function(x,y,z) { panX = x; panY = z, panZ = y }, isHidden: function() { return !showPlatform }, setHidden: function(b) { showPlatform = !b; platform.visible = !b }, setHiding: function(b) { hidePlatformBelow = b }, @@ -935,12 +949,12 @@ }, view: { - top: function() { tweenCam({left: 0, up: 0, panX: 0, panY: panY, panZ: 0}) }, - back: function() { tweenCam({left: PI, up: PI2, panX: 0, panY: panY, panZ: 0}) }, - home: function() { tweenCam({left: 0, up: PI4, panX: 0, panY: panY, panZ: 0}) }, - front: function() { tweenCam({left: 0, up: PI2, panX: 0, panY: panY, panZ: 0}) }, - right: function() { tweenCam({left: PI2, up: PI2, panX: 0, panY: panY, panZ: 0}) }, - left: function() { tweenCam({left: -PI2, up: PI2, panX: 0, panY: panY, panZ: 0}) }, + top: function(then) { tweenCam({left: home, up: 0, panX, panY, panZ, then}) }, + back: function(then) { tweenCam({left: PI, up: PI2, panX, panY, panZ, then}) }, + home: function(then) { tweenCam({left: home, up: PI4, panX, panY, panZ, then}) }, + front: function(then) { tweenCam({left: 0, up: PI2, panX, panY, panZ, then}) }, + right: function(then) { tweenCam({left: PI2, up: PI2, panX, panY, panZ, then}) }, + left: function(then) { tweenCam({left: -PI2, up: PI2, panX, panY, panZ, then}) }, reset: function() { viewControl.reset(); requestRefresh() }, load: function(cam) { viewControl.setPosition(cam) }, save: function() { return viewControl.getPosition(true) }, @@ -953,7 +967,30 @@ viewControl.setMouse(viewControl.mouseDefault); } }, - getFPS: function() { return fps } + getFPS: function() { return fps }, + getFocus: function() { return viewControl.getTarget() }, + setFocus: function(v) { + viewControl.setTarget(v); + refresh(); + }, + setHome: function(r) { + home = r || 0; + }, + spin: function(then, count) { + Space.view.front(() => { + Space.view.right(() => { + Space.view.back(() => { + Space.view.left(() => { + if (--count > 0) { + Space.view.spin(then, count); + } else { + Space.view.front(then); + } + }); + }); + }); + }); + } }, mouse: { @@ -973,6 +1010,14 @@ selectRecurse = b; }, + setTweenTime: function(t) { + tweenTime = t || 500; + }, + + setTweenDelay: function(d) { + tweenDelay = d || 20; + }, + objects: function() { return WC; }, @@ -985,7 +1030,7 @@ return { renderer, camera, platform }; }, - init: function(domelement, slider) { + init: function(domelement, slider, ortho) { container = domelement; WORLD.rotation.x = -PI2; @@ -998,14 +1043,16 @@ antialias: true, preserveDrawingBuffer: true }); - camera = perspective ? - new THREE.PerspectiveCamera(perspective, aspect(), 5, 100000) : - new THREE.OrthographicCamera(-100 * aspect(), 100 * aspect(), 100, -100, 0.1, 100000); + camera = ortho ? + new THREE.OrthographicCamera(-100 * aspect(), 100 * aspect(), 100, -100, 0.1, 100000) : + new THREE.PerspectiveCamera(perspective, aspect(), 5, 100000); camera.position.set(0, 200, 340); renderer.setSize(width(), height()); domelement.appendChild(renderer.domElement); + raycaster = new THREE.Raycaster(); + viewControl = new MOTO.CTRL(camera, domelement, function (position, moved) { if (platform) { platform.visible = hidePlatformBelow ? diff --git a/web/kiri/engine.html b/web/kiri/engine.html index f9d59708..711ee395 100644 --- a/web/kiri/engine.html +++ b/web/kiri/engine.html @@ -43,12 +43,12 @@ #jbody, #gbody { flex-grow: 1; } - #jfoot { + #jfoot, #dfoot { display: flex; flex-direction: row; justify-content: center; } - #jfoot button { + #jfoot button, #dfoot button { width: 100%; } #gbody { @@ -58,6 +58,7 @@ overflow: auto; } #gfoot { + display: none; background-color: rgba(61,133,198,0.25); border-top: 1px solid #aaa; padding: 3px; @@ -66,6 +67,9 @@ #api { width: 812px; } + #download { + display: none; + } li { list-style: circle; } @@ -77,7 +81,7 @@ @@ -127,6 +152,7 @@
gcode output
+
@@ -145,5 +171,6 @@
  • - async path routing of slice data
  • - async gcode generation from path routing
  • + diff --git a/web/kiri/index.css b/web/kiri/index.css index 2d07ad6c..28d25b73 100644 --- a/web/kiri/index.css +++ b/web/kiri/index.css @@ -269,7 +269,7 @@ th, tr, td, label { #app-mode, #app-acct { width: 125px; } -#app-mode:hover #mode-info { +#mode-info.hoverpop { display: flex; } #mode-info { @@ -295,7 +295,7 @@ th, tr, td, label { padding-right: 5px; } -#app-name:hover #app-info { +#app-info.hoverpop { display: flex; } #app-info { @@ -317,8 +317,8 @@ th, tr, td, label { align-items: center; justify-content: flex-end; } -#app-acct:hover #acct-pop { - display: flex; +#acct-pop.hoverpop { + display: flex !important; } #acct-pop { display: none; @@ -441,7 +441,7 @@ th, tr, td, label { background-color: rgba(255,255,255,0.85); border: 1px solid rgba(0,0,0,0.1); border-radius: 3px; - width: 60px; + min-width: 60px; color: var(--fg-gray); } .pop-lcol > div:hover { @@ -471,10 +471,10 @@ th, tr, td, label { .lt-menu label { font-size: smaller; } -.lt-enabled { +.lt-enabled, .lt-active { color: var(--fg-gray); } -.lt-enabled:hover { +.lt-enabled:hover, .lt-active:hover { background-color: #eee; border: 1px solid #aaa; color: #000; @@ -482,6 +482,9 @@ th, tr, td, label { .lt-enabled:hover .pop-lcol { display: flex; } +.lt-active .hoverpop { + display: flex; +} @media only screen and (max-height : 800px) { .lt-menu, .pop-lcol { font-size: 16px !important; @@ -524,7 +527,7 @@ th, tr, td, label { .txt-sel button, .btn-sel { background-color: #ccc; } -#pop-rotate .value, #pop-scale .value { +#pop-rotate input.value, #pop-scale input.value { width: 90%; } #pop-rotate label { @@ -870,8 +873,8 @@ th, tr, td, label { min-height: 4em !important; } #code-preview-textarea { - width: 100%; - height: 250px; + width: 40em; + height: 12em; font-size: 10px; } .mod-end { @@ -962,6 +965,8 @@ th, tr, td, label { } #help .content li { line-height: 1.75em; + padding-left: 22px ; + text-indent: -22px ; } #help .footer { font-family: monospace; @@ -1239,6 +1244,17 @@ th, tr, td, label { width: 20px; } +#top-slider { + position: absolute; + display: none; + top: 60px; + left: 50px; + right: 50px; +} +#anim-slider { + width: 100%; +} + #alert-area { display: none; justify-content: center; @@ -1500,3 +1516,43 @@ th, tr, td, label { border-top: 1px solid #ddd; margin: 5px 0 5px 0; } + +#lab-axis, #lab-scale { + padding: 0 6px 0 6px; + border-radius: 3px; +} +#lab-axis:hover, #lab-scale:hover { + background-color: rgba(0,0,0,0.2); +} + +.range-info { + position: relative; +} +.range-info:hover .range-detail { + display: flex; +} +.range-detail { + position: absolute; + display: none; + flex-direction: column; + bottom: 50%; + right: 100%; + padding: 5px; + margin: 0 10px 0 10px; + font-size: smaller; + font-family: monospace; + transform: translateY(50%); + background-color: rgba(255,255,255,0.8); + border: 1px solid #aaa; + border-radius: 5px; +} +.range-detail label.pad { + padding-right: 8px; + border-bottom: 1px solid rgba(0,0,0,0.1); +} +.range-detail label.val { + font-weight: bold; +} +.range-detail span { + flex-grow: 1; +} diff --git a/web/kiri/index.html b/web/kiri/index.html index b6cf9d1d..f4809bfa 100644 --- a/web/kiri/index.html +++ b/web/kiri/index.html @@ -3,7 +3,7 @@ - + @@ -45,17 +45,20 @@
    - +
    -
    english
    danish
    +
    english
    +
    french
    +
    german
    +
    polish
    - +
    - +
    @@ -72,104 +75,104 @@
    -
    +
    - +
    - +
    -
    -
    -
    +
    - +
    - +
    -
    -
    +
    - +
    - +
    - +
    - +
    - +
    -
    +
    - +
    - +
    - +
    - +
    -
    +
    - +
    - +
    - +
    - +
    - +
    - +
    @@ -196,12 +199,12 @@
    -
    reset
    +
    reset
    - +
    @@ -222,9 +225,9 @@
    -
    -
    -
    +
    +
    +
    @@ -249,7 +252,7 @@
    - +
    @@ -260,7 +263,7 @@
    - +
    - +
    @@ -296,27 +299,27 @@
    - +
    - +
    - +
    - +
    - +
    @@ -324,23 +327,23 @@
    -
    -
    +
    +
    -
    -
    -
    shaft
    +
    +
    +
    shaft
    -
    flute
    +
    flute
    -
    taper
    +
    taper
    @@ -351,13 +354,13 @@
    - - + +
    - Application Preferences + Application Preferences
    @@ -378,7 +381,7 @@
    - +
    @@ -440,13 +443,14 @@
    - - + +
    - - + + +
    - +
    @@ -457,6 +461,9 @@
    alerts
    alerts
    alerts
    +
    + +
    diff --git a/web/kiri/lang/da-help.html b/web/kiri/lang/da-help.html index b2a2b1a1..0bd54eb4 100644 --- a/web/kiri/lang/da-help.html +++ b/web/kiri/lang/da-help.html @@ -67,8 +67,7 @@ mouse-wheelZoom middle-dragZoom 2-touch-pinchZoom - - + diff --git a/web/kiri/lang/da.js b/web/kiri/lang/da.js index c50e2166..73af3fd1 100644 --- a/web/kiri/lang/da.js +++ b/web/kiri/lang/da.js @@ -1,552 +1,647 @@ kiri.lang['da'] = - kiri.lang['da-dk'] = { - version: "version", - enable: "enable", - settings: "indstillinger", +kiri.lang['da-dk'] = { + version: "version", + enable: "enable", + settings: "indstillinger", + // common keys and menus + animate: "animer", // CAM animate button + arrange: "arranger", // layout workspace objects + axis: "ake", // left object scale pop menu + clear: "ryd", // clear workspace (remove all objects) + copy: "kopier", + delete: "slet", + detail: "detaljer", + done: "færdig", + enable: "aktiver", + export: "eksporter", + files: "filer", + help: "hjælp", + ghost: "spøgelse", // left render pop menu (wireframe) + hide: "skjul", // left render pop menu (invisible) + home: "hjem", + import: "importer", + language: "sprog", + machine: "maskine", // device or machine + metric: "måleenhed", + name: "navn", + prefs: "præferencer", // left menu "preferences" + preview: "forhåndsvisning", + recent: "nylig", + render: "render", // left render pop menu + reset: "Nulstil", + rotate: "roter", // left rotate pop menu + save: "gem", + scale: "skala", // left object scale pop menu + setup: "setup", + settings: "indstillinger", + size: "størrelse", + solid: "solid", // view type pop menu + tool: "værktøj", + tools: "værktøjer", // CAM tool menu button + top: "top", + type: "type", // CAM tool type + version: "version", + view: "vis", // left view pop menu + wire: "tråd", // left render pop menu - acct_xpo: "lav en sikkerhedskopi af din enhed\nog enhedsprofiler med mulighed for at\nmedtage arbejdsområdeobjekter og positioner", + acct_xpo: ["lav en sikkerhedskopi af din enhed","og enhedsprofiler med mulighed for at","medtage arbejdsområdeobjekter og positioner"], - // DEVICE dialog groups - dv_gr_dev: "enhed", - dv_gr_ext: "ekstruder", - dv_gr_out: "output", - dv_gr_gco: "gcode makroer", + // RIGHT-CLICK CONTEXT MENU + rc_clws: "ryd arbejdsområde", + rc_xpws: "eksporter arbejdsområde", + rc_lafl: "læg flat", + rc_mirr: "spejlvend", + rc_dupl: "dupliker", + rc_xstl: "eksporter som STL", - // DEVICE dialog (_s = label, _l = hover help) - dv_name_s: "navn", - dv_name_l: "enhedens navn", - dv_fila_s: "filament", - dv_fila_l: "diameter i millimeter", - dv_nozl_s: "dyse", - dv_nozl_l: "diameter i millimeter", - dv_bedw_s: "bredde", - dv_bedw_l: "arbejdsområde enheder", - dv_bedd_s: "dybde", - dv_bedd_l: "arbejdsområde enheder", - dv_bedh_s: "højde", - dv_bedh_l: "max bygge højde\narbejdsområde enheder", - dv_spmx_s: "maks. spindel", - dv_spmx_l: "maks. spindel rpm\n0 for at deaktivere", - dv_xtab_s: "absolut positionering", - dv_xtab_l: "ekstruder bevægelser absolutte", - dv_orgc_s: "udgangspunkt i center", - dv_orgc_l: "plade har udgangspunkt i center", - dv_orgt_s: "udgangspunkt i toppen", - dv_orgt_l: "part z origin top", - dv_bedc_s: "rund plade", - dv_bedc_l: "enhedens plade er rund", - dv_belt_s: "bælte plade", - dv_belt_l: "kontinuerlig printplade", - dv_fanp_s: "blæser styrke", - dv_fanp_l: "bestem køle blæser styrke", - dv_prog_s: "fremgang", - dv_prog_l: "output for hver % fremgang", - dv_layr_s: "lag", - dv_layr_l: "output fra hver\nlag skifte", - dv_tksp_s: "token mellemrum", - dv_tksp_l: "tilføj et mellemrum mellem\ngkode akseparametre\nG0X0Y0X0\nvs\nG0 X0 Y0 Z0", - dv_strc_s: "strip comments", - dv_strc_l: "fjern gkode kommentarer\nsom begynder med ;", - dv_fext_s: "filtypenavn", - dv_fext_l: "file name extension", - dv_dwll_s: "dwell", - dv_dwll_l: "gkode dvale kode", - dv_tool_s: "værktøj skift", - dv_tool_l: "værktøj skift kode", - dv_sspd_s: "spindel hastighed", - dv_sspd_l: "sæt spindel hastighed", - dv_paus_s: "pause", - dv_paus_l: "gkode pause kode", - dv_head_s: "sidehovede", - dv_head_l: "sidehovede gkode", - dv_foot_s: "sidefod", - dv_foot_l: "sidefod gkode", - dv_lzon_s: "tænd laser", - dv_lzon_l: "tænd laser gkode", - dv_lzof_s: "sluk laser", - dv_lzof_l: "sluk laser gkode", - dv_exts_s: "vælg", - dv_exts_l: "gkode for at vælge denne ekstruder", - dv_extd_s: "fravalgt", - dv_extd_l: "gkode for at fravælge denne ekstruder", - dv_exox_s: "forskydning x", - dv_exox_l: "dyse forskydning x", - dv_exoy_s: "forskydning y", - dv_exoy_l: "dyse forskydning y", + // DEVICE MENU and related dialogs + dm_sldt: "vælg en enhedstype", + dm_stdd: "standard enhed", + dm_mydd: "mine enheder", + dm_seld: "valgte enheder", + dm_rcnt: "nylige filer", + dm_savs: "gemte indstillinger", + dm_appp: "Programindstillinger", - // MODE - mo_menu: "tilstand", - mo_fdmp: "FDM Print", - mo_slap: "SLA Print", - mo_lazr: "Laser skær", - mo_cncm: "CNC fræs", + // CAM Tool Dialog + td_tyem: "ende", // end mill + td_tybm: "kugle", // ball mill + td_tytm: "konisk", // taper mill + td_tonm: "værktøj #", + td_shft: "skaft", // endmill shaft specs + td_flut: "spiral", // endmill flute specs + td_tapr: "konisk", // endmill taper specs - // SETUP - su_menu: "setup", - su_devi: "Enheder", - su_tool: "Værktøjer", - su_locl: "Lokal", - su_xprt: "Eksport", - su_help: "Hjælp", + // DEVICE dialog groups + dv_gr_dev: "enhed", + dv_gr_ext: "ekstruder", + dv_gr_out: "output", + dv_gr_gco: "gcode makroer", - // LOAD - fe_menu: "fil", - fn_recn: "Nylig", - fn_impo: "Importér", + // DEVICE dialog (_s = label, _l = hover help) + dv_name_s: "navn", + dv_name_l: "enhedens navn", + dv_fila_s: "filament", + dv_fila_l: "diameter i millimeter", + dv_nozl_s: "dyse", + dv_nozl_l: "diameter i millimeter", + dv_bedw_s: "bredde", + dv_bedw_l: "arbejdsområde enheder", + dv_bedd_s: "dybde", + dv_bedd_l: "arbejdsområde enheder", + dv_bedh_s: "højde", + dv_bedh_l: ["max bygge højde","arbejdsområde enheder"], + dv_spmx_s: "maks. spindel", + dv_spmx_l: ["maks. spindel rpm","0 for at deaktivere"], + dv_xtab_s: "absolut positionering", + dv_xtab_l: "ekstruder bevægelser absolutte", + dv_orgc_s: "udgangspunkt i center", + dv_orgc_l: "plade har udgangspunkt i center", + // dv_orgt_s: "udgangspunkt i toppen", + // dv_orgt_l: "part z origin top", + dv_bedc_s: "rund plade", + dv_bedc_l: "enhedens plade er rund", + dv_belt_s: "bælte plade", + dv_belt_l: "kontinuerlig printplade", + dv_fanp_s: "blæser styrke", + dv_fanp_l: "bestem køle blæser styrke", + dv_prog_s: "fremgang", + dv_prog_l: "output for hver % fremgang", + dv_layr_s: "lag", + dv_layr_l: ["output fra hver","lag skifte"], + dv_tksp_s: "token mellemrum", + dv_tksp_l: ["tilføj et mellemrum mellem","gkode akseparametre","G0X0Y0X0","vs","G0 X0 Y0 Z0"], + dv_strc_s: "strip comments", + dv_strc_l: ["fjern gkode kommentarer","som begynder med ;"], + dv_fext_s: "filtypenavn", + dv_fext_l: "file name extension", + dv_dwll_s: "dwell", + dv_dwll_l: "gkode dvale kode", + dv_tool_s: "værktøj skift", + dv_tool_l: "værktøj skift kode", + dv_sspd_s: "spindel hastighed", + dv_sspd_l: "sæt spindel hastighed", + dv_paus_s: "pause", + dv_paus_l: "gkode pause kode", + dv_head_s: "sidehovede", + dv_head_l: "sidehovede gkode", + dv_foot_s: "sidefod", + dv_foot_l: "sidefod gkode", + dv_lzon_s: "tænd laser", + dv_lzon_l: "tænd laser gkode", + dv_lzof_s: "sluk laser", + dv_lzof_l: "sluk laser gkode", + dv_exts_s: "vælg", + dv_exts_l: "gkode for at aktivere denne ekstruder", + dv_dext_s: "fravalgt", + dv_dext_l: "gcode køres, før en anden ekstruder aktiveres", + dv_extd_s: "fravalgt", + dv_extd_l: "gkode for at fravælge denne ekstruder", + dv_exox_s: "forskydning x", + dv_exox_l: "dyse forskydning x", + dv_exoy_s: "forskydning y", + dv_exoy_l: "dyse forskydning y", - // FUNCTION - fn_menu: "handling", - fn_arra: "Arranger", - fn_slic: "Slice", - fn_prev: "Eksempel", - fn_expo: "Eksport", + // MODE + mo_menu: "tilstand", + mo_fdmp: "FDM Print", + mo_slap: "SLA Print", + mo_lazr: "Laser skær", + mo_cncm: "CNC fræs", - // VIEW - vu_menu: "vis", - vu_home: "hjem", - vu_rset: "Nulstil", - vu_sptp: "Top", - vu_spfr: "Front", - vu_splt: "Venstre", - vu_sprt: "Højre", + // SETUP + su_menu: "setup", + su_devi: "Enheder", + su_tool: "Værktøjer", + su_locl: "Lokal", + su_xprt: "Eksport", + su_help: "Hjælp", - // WORKSPACE - ws_menu: "vis", - ws_save: "Gem", - ws_cler: "Ryd", + // LOAD + fe_menu: "fil", + fn_recn: "Nylig", + fn_impo: "Importér", - // OPTIONS - op_menu: "brugerflade", - op_disp: "Skærm", - op_xprt_s: "ekspert", - op_xprt_l: "vis flere indstillinger", - op_decl_s: "mærkater", - op_decl_l: "vis enhedsmærkater og logoer", - op_hopo_s: "hold over pop op", - op_hopo_l: "aktiver menu ved at\nholde over den", - op_dark_s: "mørkt tema", - op_dark_l: "mørkt tema brugerflade", - op_comp_s: "kompakt brugerflade", - op_comp_l: "kompakt brugerflade\nbedre til små skærme\nog tablets", - op_shor_s: "vis udgangspunkt", - op_shor_l: "vis enhedens eller proccens udgangspunkt", - op_shru_s: "vis linealer", - op_shru_l: "vis akse linealer på større\ngitterlinjer", - op_sped_s: "vis hastigheder", - op_sped_l: "vis hastighed til farve bar\ni forhåndsvisning", - op_auto_s: "auto fordel", - op_auto_l: "automatisk layoutplatform,\nnår nye dele tilføjes, - op_free_s: "free layout", - op_free_l: "permit dragable layout\nno effect in laser mode", - op_invr_s: "vend zoom om", - op_invr_l: "vender zoom på musens\nhjul om", - op_save_s: "auto gem", - op_save_l: "beholder objekter i arbejdsområdet\nmellem genindlæsninger", - op_line_s: "linje type", - op_line_l: "linjestil til gengivelse\naf linjer i 3d\sti: 3d bedst\nflad: 2d god\nlinje 1d hurtig", - op_unit_s: "enheder", - op_unit_l: "arbejdsområde enheder har effekt\npå hatigheder og distancer", - op_anim_s: "animer", - op_anim_l: "animation mesh tæthed\nhøjere er mere tæt\nkræver mere hukommelse\nog er langsommere", - lo_menu: "fordeling", + // FUNCTION + fn_menu: "handling", + fn_arra: "Arranger", + fn_slic: "Slice", + fn_prev: "Eksempel", + fn_expo: "Eksport", - pt_menu: "dele", - pt_deci_s: "decimer", - pt_deci_l: "aktivere eller deaktivere punktdecimering\nunder portimport. for hurtigere hebandling\nog lavere hukommelsesforbrug", - pt_qual_s: "kvalitet", - pt_qual_l: "detaljeringsniveau, der\nskal bevares under behandling\n lavere er hurtigere", + // VIEW + vu_menu: "vis", + vu_home: "hjem", + vu_rset: "Nulstil", + vu_sptp: "Top", + vu_spfr: "Front", + vu_splt: "Venstre", + vu_sprt: "Højre", - xp_menu: "eksporteringer", + // WORKSPACE + ws_menu: "vis", + ws_save: "Gem", + ws_cler: "Ryd", - // SETTINGS - se_menu: "profil", - se_load: "indlæs", - se_save: "gem", + // OPTIONS + op_menu: "brugerflade", + op_disp: "Skærm", + op_xprt_s: "ekspert", + op_xprt_l: "vis flere indstillinger", + op_decl_s: "mærkater", + op_decl_l: "vis enhedsmærkater og logoer", + op_dang_s: "eksperimentel", + op_dang_l: "Vis eksperimentelle parametre", + op_hopo_s: "hold over pop op", + op_hopo_l: ["aktiver menu ved at","holde over den"], + op_dark_s: "mørkt tema", + op_dark_l: "mørkt tema brugerflade", + op_comp_s: "kompakt brugerflade", + op_comp_l: ["kompakt brugerflade","bedre til små skærme","og tablets"], + op_shor_s: "vis udgangspunkt", + op_shor_l: "vis enhedens eller proccens udgangspunkt", + op_shru_s: "vis linealer", + op_shru_l: ["vis akse linealer på større","gitterlinjer"], + op_sped_s: "vis hastigheder", + op_sped_l: ["vis hastighed til farve bar","i forhåndsvisning"], + op_auto_s: "auto fordel", + op_auto_l: ["automatisk layoutplatform,","når nye dele tilføjes"], + op_free_s: "frit layout", + op_free_l: ["tillad trækbart layout","ingen effekt i laser-tilstand"], + op_spcr_s: "afstand", + op_spcr_l: ["afstand mellem objekter","under automatisk layout","i arbejdsrumsenheder"], + op_invr_s: "vend zoom om", + op_invr_l: ["vender zoom på musens","hjul om"], + op_save_s: "auto gem", + op_save_l: ["beholder objekter i arbejdsområdet","mellem genindlæsninger"], + op_line_s: "linje type", + op_line_l: ["linjestil til gengivelse","af linjer i 3d\sti: 3d bedst","flad: 2d god","linje 1d hurtig"], + op_unit_s: "enheder", + op_unit_l: ["arbejdsområde enheder har effekt","på hatigheder og distancer"], + op_anim_s: "animer", + op_anim_l: ["animation mesh tæthed","højere er mere tæt","kræver mere hukommelse","og er langsommere"], - // FDM SLICING - sl_menu: "lag", - sl_lahi_s: "højde", - sl_lahi_l: "højden af hver\nlag i milimeter", - ad_minl_s: "højde min", - ad_minl_l: "adaptiv min laghøjde\ni millimeter\nskal være ikke-nul", - sl_ltop_s: "top lag", - sl_ltop_l: "antallet af faste lag\nder skal lægges på\ntoppen af en print", - sl_lsld_s: "faste lag", - sl_lsld_l: "faste udfyldnigner beregnet\nud fra lag deltaen. se\nlag pop op menuen", - sl_lbot_s: "bund lag", - sl_lbot_l: "antallet af faste lag\nder skal lægges i\nbunden af en print", - ad_adap_s: "fleksibel", - ad_adap_l: "brug adaptive laghøjder\nmed 'laghøjde' som maks\nog 'lag min' som minimum", + lo_menu: "fordeling", - // FDM SHELLS - sl_shel_s: "antal skaller", - sl_shel_l: "antal omkredse\nvægge, der skal genereres", - sl_ordr_s: "skal rækkefølge", - sl_ordr_l: "output shell rækkefølge\nindvendigt til udvendigt\neller udvendigt til indvendigt\npåvirker overfladekvaliteten", - sl_strt_s: "lag start", - sl_strt_l: "lagets startpunkt\nsidste = sidste lag slut\ncenter = del center\nudgangspunkt = delens udgangspunkt", - ad_thin_s: "tynde vægge", - ad_thin_l: "opdage og udfylde huller\nmellem vægge", + pt_menu: "dele", + pt_deci_s: "decimer", + pt_deci_l: ["aktivere eller deaktivere punktdecimering","under portimport. for hurtigere hebandling","og lavere hukommelsesforbrug"], + pt_qual_s: "kvalitet", + pt_qual_l: ["detaljeringsniveau, der","skal bevares under behandling"," lavere er hurtigere"], + pt_heal_s: "reparer medeller", + pt_heal_l: ["forsøg på at reparere","ikke-manifold modeller","forøger slice tid"], - // FDM FILL - fi_menu: "fylde", - fi_type: "type", - fi_pcnt_s: "brøkdel", - fi_pcnt_l: "fylde densitet værdier\n0.0 - 1.0", - fi_angl_s: "startvinkel", - fi_angl_l: "bund vinkel i grader", - fi_over_s: "overlap", - fi_over_l: "overlap as fyldet og skallen\nsom % af dysen bredde\nhøjere binder bedre\n0.0 - 1.0", + xp_menu: "eksporteringer", - // FDM FIRST LAYER - fl_menu: "bund", - fl_lahi_s: "laghøjde", - fl_lahi_l: "højde af hver lag\ni millimeter\nskulle være >= laghøjde", - fl_rate_s: "skal hastighed", - fl_rate_l: "max hastighed på skaller\ni millimeter / minuttet", - fl_frat_s: "fylde hastighed", - fl_frat_l: "fylde max print hastighed\ni millimeter / minuttet", - fl_mult_s: "print factor", - fl_mult_l: "ekstrudering multiplikator\n0.0 - 2.0", - fl_sfac_s: "width factor", - fl_sfac_l: "dyse strørrelse multiplikator\nændre linjeafstanden", - fl_zoff_s: "lag forskydning", - fl_zoff_l: "højde forskydning fra bælte\naf laveste ekstrudering\ni millimeter", - fl_brim_s: "skygge størrelse", - fl_brim_l: "tilføj skygge til delens bund\nstørrelsen er bredden i millimeter\n0 for at deaktivere", - fl_skrt_s: "antal skørter", - fl_skrt_l: "antal første lag udvidelser\nskørter at generere", - fl_skro_s: "skirt offset", - fl_skro_l: "skirt offset from part\nin millimeters", - fl_nozl_s: "dyse temp", - fl_nozl_l: "grader i celsius\noutput indstillingen bruges\nhvis denne er nul", - fl_bedd_s: "plade temp", - fl_bedd_l: "grader i celsius\noutput indstillingen bruges\nhvis denne er nul", - fr_spac_s: "flåde mellemrum", - fr_spac_l: "ekstra afstand\nmellem første lag og flåden\ni millimeter", - fr_nabl_s: "slå flåde til", - fr_nabl_l: "lav en flåde under\nmodellen for bedre vedhæftning\nbruger skørte afstand og\nslår skørte fra", + // SETTINGS + se_menu: "profil", + se_load: "indlæs", + se_save: "gem", - // FDM SUPPORT - sp_menu: "støtte", - sp_detect: "registrer", - sp_dens_s: "densitet", - sp_dens_l: "brøkdel 0.0 - 1.0\anbefaledeet 0.15\n0 for at deaktivere", - sp_size_s: "søjle størrelse", - sp_size_l: "søjle bredde\ni millimeter", - sp_offs_s: "del afstand", - sp_offs_l: "afstand til del\ni millimeter", - sp_gaps_s: "mellemrum", - sp_gaps_l: "antal lag\nmellem støtte og del", - sp_span_s: "max bro", - sp_span_l: "maximum ikke-understøttet afstand\n der generere støtte\ni millimeter", - sp_angl_s: "max vinkel", - sp_angl_l: "max overhæng vinkel før\nstøtte søjler genereres", - sp_area_s: "min område", - sp_area_l: "minimum område for\nen støtte søjle\ni millimeter", - sp_xpnd_s: "udvid", - sp_xpnd_l: "udvid støtte område\nudover delen beyond part grænse\ni millimeter", - sp_nozl_s: "ekstruder", - sp_nozl_l: "til multi-ekstruder systemer\nhvor én ekstruder bruges\ntil støtte materiale", - sp_auto_s: "automatisk", - sp_auto_l: "aktiver genereret støtte\ved brug af 'at-slice-time' geometri\nstøtte vil først laves\nefter slicing er færdig", + // FDM SLICING + sl_menu: "lag", + sl_lahi_s: "højde", + sl_lahi_l: ["højden af hver","lag i milimeter"], + ad_minl_s: "højde min", + ad_minl_l: ["adaptiv min laghøjde","i millimeter","skal være ikke-nul"], + sl_ltop_s: "top lag", + sl_ltop_l: ["antallet af faste lag","der skal lægges på","toppen af en print"], + sl_lsld_s: "faste lag", + sl_lsld_l: ["faste udfyldnigner beregnet","ud fra lag deltaen. se","lag pop op menuen"], + sl_lbot_s: "bund lag", + sl_lbot_l: ["antallet af faste lag","der skal lægges i","bunden af en print"], + ad_adap_s: "fleksibel", + ad_adap_l: ["brug adaptive laghøjder","med 'laghøjde' som maks","og 'lag min' som minimum"], - // LASER SLICING - ls_offs_s: "forskydning", - ls_offs_l: "juster stråle bredden \ni millimeter", - ls_lahi_s: "højde", - ls_lahi_l: "laghøjde\ni millimeter\n0 = auto/opdag", - ls_lahm_s: "min", - ls_lahm_l: "layer height minimum\nwill merge auto slices\nunder this thickness\nin millimeters", - ls_sngl_s: "enkelt", - ls_sngl_l: "lav kun én omgang\nved den specificerede laghøjde", + // FDM SHELLS + sl_shel_s: "antal skaller", + sl_shel_l: ["antal omkredse","vægge, der skal genereres"], + sl_ordr_s: "skal rækkefølge", + sl_ordr_l: ["output shell rækkefølge","indvendigt til udvendigt","eller udvendigt til indvendigt","påvirker overfladekvaliteten"], + sl_strt_s: "lag start", + sl_strt_l: ["lagets startpunkt","sidste = sidste lag slut","center = del center","udgangspunkt = delens udgangspunkt"], + ad_thin_s: "tynde vægge", + ad_thin_l: ["opdage og udfylde huller","mellem vægge"], - // CNC COMMON terms - cc_tool: "Værktøj", - cc_offs_s: "forskydning", - cc_offs_l: "forskyd værktøj center\n fra valgt sti", - cc_spnd_s: "spindel rpm", - cc_spnd_l: "spindel hastighed i\nrevolutioner / minuttet", - cc_sovr_s: "step over", - cc_sovr_l: "as a brøkdel af\nværktøjsdiameteren", - cc_sdwn_s: "step ned", - cc_sdwn_l: "step ned dybde\nfor hver omgang\ni arbejdsområdets enheder\n0 for at deaktivere", - cc_feed_s: "hastighed", - cc_feed_l: "max skærehastighed i\n arbejdsområdets enheder / minuttet", - cc_plng_s: "dyk hastighed", - cc_plng_l: "max z ake hastighed i\narbejdsområdets enheder / minuttet", + // FDM FILL + fi_menu: "fylde", + fi_type: "type", + fi_pcnt_s: "fylde brøkdel", + fi_pcnt_l: ["fylde densitet værdier","0.0 - 1.0"], + fi_angl_s: "startvinkel", + fi_angl_l: "bund vinkel i grader", + fi_over_s: "overlap", + fi_over_l: ["overlap as fyldet og skallen","som % af dysen bredde","højere binder bedre","0.0 - 1.0"], + // fi_rate_s: "print hastighed", + fi_rate_l: ["ekstruderingshastighed til fylde","indstillet til 0 for at bruge standard","output-printhastigheder"], - // CNC COMMON - cc_menu: "begrændsninger", - cc_flip: "flip", - cc_rapd_s: "xy hastighed", - cc_rapd_l: "max xy rejse hastighed\ni arbejdsområdets enheder / minuttet", - cc_rzpd_s: "z hastighed", - cc_rzpd_l: "max z rejse hastighed \ni arbejdsområdets enheder / minuttet", + // FDM FIRST LAYER + fl_menu: "bund", + fl_lahi_s: "laghøjde", + fl_lahi_l: ["højde af hver lag","i millimeter","skulle være >= laghøjde"], + fl_rate_s: "skal hastighed", + fl_rate_l: ["max hastighed på skaller","i millimeter / minuttet"], + fl_frat_s: "fylde hastighed", + fl_frat_l: ["fylde max print hastighed","i millimeter / minuttet"], + fl_mult_s: "print factor", + fl_mult_l: ["ekstrudering multiplikator","0.0 - 2.0"], + fl_sfac_s: "width factor", + fl_sfac_l: ["dyse strørrelse multiplikator","ændre linjeafstanden"], + fl_zoff_s: "lag forskydning", + fl_zoff_l: ["højde forskydning fra bælte","af laveste ekstrudering","i millimeter"], + fl_brim_s: "skygge størrelse", + fl_brim_l: ["tilføj skygge til delens bund","størrelsen er bredden i millimeter","0 for at deaktivere"], + fl_skrt_s: "antal skørter", + fl_skrt_l: ["antal første lag udvidelser","skørter at generere"], + fl_skro_s: "skirt offset", + fl_skro_l: ["skirt offset from part","in millimeters"], + fl_nozl_s: "dyse temp", + fl_nozl_l: ["grader i celsius","output indstillingen bruges","hvis denne er nul"], + fl_bedd_s: "plade temp", + fl_bedd_l: ["grader i celsius","output indstillingen bruges","hvis denne er nul"], + fr_spac_s: "flåde mellemrum", + fr_spac_l: ["ekstra afstand","mellem første lag og flåden","i millimeter"], + fr_nabl_s: "slå flåde til", + fr_nabl_l: ["lav en flåde under","modellen for bedre vedhæftning","bruger skørte afstand og","slår skørte fra"], - cc_loff_s: "forskydning", - cc_loff_l: "distance fra materialets flade\nfor planing\ni arbejdsområdets enheder", + // FDM BELT ONLY + fl_zoff_s: "bælte forskydning", + fl_zoff_l: ["højde forskudt fra bælte","af laveste ekstrudering","i millimeter"], + fl_brim_s: "skygge størrelse", + fl_brim_l: ["tilføj skygge til delens bund","størrelse er bredde i millimeter","0 for at deaktivere"], + fl_brmn_s: "skygge udløser", + fl_brmn_l: ["kun tilføje bremser, når segmentet","vendt mod bælet er kortere end dette","værdi i millimeter","0 = Uendelig"], + fl_bled_s: "model anker", + fl_bled_l: ["bælte model anker","ved start af print","i millimeter"], - // CNC ROUGHING - cr_menu: "groft", - cr_lsto_s: "efterlad materiale", - cr_lsto_l: "horizontal forskydning fra vertikal flade\nmateriale efterlades til efterbehandling\ni arbejdsområdets enheder", - cr_ease_s: "let ned", - cr_ease_l: "dyk skæring vil\nfølge en spiralform ned eller\nkøre langsomt langs en linært sti", - cr_clrt_s: "ryd top", - cr_clrt_l: "kør en rydning over\ngrændseområdet af delen\nved z = 0", - cr_clrp_s: "ryd hulrum", - cr_clrp_l: "fræs ud gennem lommer\ni stedet for kun omridset", - cr_clrf_s: "ryd flader", - cr_clrf_l: "interpolere trin ned for at\nrydde eventuelle opdagede flade områder", - cr_olin_s: "kun indersider", - cr_olin_l: "begræns skæring til\nindvendige delgrænser", + // FDM SUPPORT + sp_menu: "støtte", + sp_detect: "registrer", + sp_dens_s: "densitet", + sp_dens_l: ["brøkdel 0.0 - 1.0\anbefaledeet 0.15","0 for at deaktivere"], + sp_size_s: "søjle størrelse", + sp_size_l: ["søjle bredde","i millimeter"], + sp_offs_s: "del afstand", + sp_offs_l: ["afstand til del","i millimeter"], + sp_gaps_s: "mellemrum", + sp_gaps_l: ["antal lag","mellem støtte og del"], + sp_span_s: "max bro", + sp_span_l: ["maximum ikke-understøttet afstand"," der generere støtte","i millimeter"], + sp_angl_s: "max vinkel", + sp_angl_l: ["max overhæng vinkel før","støtte søjler genereres"], + sp_area_s: "min område", + sp_area_l: ["minimum område for","en støtte søjle","i millimeter"], + sp_xpnd_s: "udvid", + sp_xpnd_l: ["udvid støtte område","udover delen beyond part grænse","i millimeter"], + sp_nozl_s: "ekstruder", + sp_nozl_l: ["til multi-ekstruder systemer","hvor én ekstruder bruges","til støtte materiale"], + sp_auto_s: "automatisk", + sp_auto_l: ["aktiver genereret støtte\ved brug af 'at-slice-time' geometri","støtte vil først laves","efter slicing er færdig"], - // CNC OUTLINE - co_menu: "omrids", - co_dogb_s: "dogbones", - co_dogb_l: "indsæt dogbone skræinger\nind i indersidens hjørner", - co_wide_s: "bred udskæring", - co_wide_l: "udvide udskæringsstier\nfor deep cuts in hard material", - co_olin_s: "kun indersider", - co_olin_l: "begræns skæring til\nindvendige delgrænser", - co_olot_s: "kun ydersider", - co_olot_l: "begrænse skæring til\nudvendige delgrænser\nsom kan betragtes som skyggeomrids", - co_omit_s: "udelade igennem", - co_omit_l: "eliminer gennemhulninger", - co_olen_s: "aktiver", - co_olen_l: "aktiver omrids skæring", + // LASER SLICING + ls_offs_s: "forskydning", + ls_offs_l: ["juster stråle bredden ","i millimeter"], + ls_lahi_s: "højde", + ls_lahi_l: ["laghøjde","i millimeter","0 = auto/opdag"], + ls_lahm_s: "min", + ls_lahm_l: ["layer height minimum","will merge auto slices","under this thickness","in millimeters"], + ls_sngl_s: "enkelt", + ls_sngl_l: ["lav kun én omgang","ved den specificerede laghøjde"], - // CNC CONTOUR - cn_menu: "kontur", - cf_angl_s: "max vinkel", - cf_angl_l: "vinkler større end dette\nbetragtes som lodrette", - cf_curv_s: "kun kurver", - cf_curv_l: "begrænse lineær oprydning\ntil buede overflader", - cf_olin_s: "kun indersider", - cf_olin_l: "begræns skæring til\nindvendige delgrænser", - cf_linx_s: "aktiver y passering", - cf_linx_l: "lineær y-akse efterbehandling", - cf_liny_s: "aktiver x passering", - cf_liny_l: "lineær x-akse efterbehandling", + // CNC COMMON terms + cc_tool: "Værktøj", + cc_offs_s: "forskydning", + cc_offs_l: ["forskyd værktøj center"," fra valgt sti"], + cc_spnd_s: "spindel rpm", + cc_spnd_l: ["spindel hastighed i","revolutioner / minuttet"], + cc_sovr_s: "step over", + cc_sovr_l: ["as a brøkdel af","værktøjsdiameteren"], + cc_sdwn_s: "step ned", + cc_sdwn_l: ["step ned dybde","for hver omgang","i arbejdsområdets enheder","0 for at deaktivere"], + cc_feed_s: "hastighed", + cc_feed_l: ["max skærehastighed i"," arbejdsområdets enheder / minuttet"], + cc_plng_s: "dyk hastighed", + cc_plng_l: ["max z ake hastighed i","arbejdsområdets enheder / minuttet"], + cc_sngl_s: "vælg kun linjer", + cc_sngl_l: ["vælg kun enkelte kanter","i stedet for forbundede polyliner"], - // CNC TRACE - cu_menu: "spor", - cu_type_s: "type", - cu_type_l: "følg = værktøjsspids følger linje\ntil højre eller venstre = værktøjsspids\nfølger linje forskudt efter værktøjsradius", + // CNC COMMON + cc_menu: "begrændsninger", + cc_flip: "flip", + cc_rapd_s: "xy hastighed", + cc_rapd_l: ["max xy rejse hastighed","i arbejdsområdets enheder / minuttet"], + cc_rzpd_s: "z hastighed", + cc_rzpd_l: ["max z rejse hastighed ","i arbejdsområdets enheder / minuttet"], - // CNC DRILLING - cd_menu: "bore", - cd_axis: "akse", - cd_points: "punkter", - cd_plpr_s: "dyk per", - cd_plpr_l: "max dyk mellem\ndvale perioden\ni arbejdsområdets enheder\n0 for at deaktivere", - cd_dwll_s: "dvale tid", - cd_dwll_l: "dvale tid\nmellem dyk\ni millisekunder", - cd_lift_s: "løft bor", - cd_lift_l: "løft mellem dyk\nefter opholdsperiode\ni arbejdsområdets enheder\n0 for at deaktivere", - cd_regi_s: "registrer", - cd_regi_l: "bore registreringshuller\ntil dobbeltsidede dele\uafhængigt af aktivering af\nboring, men bruger samme værktøj og indstillinger", + cc_loff_s: "forskydning", + cc_loff_l: ["distance fra materialets flade","for planing","i arbejdsområdets enheder"], - // CNC CUTOUT TABS - ct_menu: "tapper", - ct_angl_s: "vinkel", - ct_angl_l: "startvinkel for tapmellemrum\ni grader (0-360)", - ct_numb_s: "antal", - ct_numb_l: "antal tapper der skal bruges\nbliver fordelt jævnt\nomkring delen", - ct_wdth_s: "bredde", - ct_wdth_l: "bredde i arbejdsområdets enheder", - ct_hght_s: "højde", - ct_hght_l: "højde i arbejdsområdets enheder", - ct_dpth_s: "dybde", - ct_dpth_l: "distance arbejdsområdets enheder\nsom tappen projekteres fra\ndelens flade", - ct_midl_s: "midtlinje", - ct_midl_l: "brug midterlinjen på tappen\ni stedet for z-bunden\ntil dobbeltsidet arbejde", - ct_nabl_s: "auto", - ct_nabl_l: "generer automatisk radiale tapper\nprojekteret fra delens center\nved hjælp af optælling og vinkelforskydning", + // CNC ROUGHING + cr_menu: "groft", + cr_lsto_s: "efterlad materiale", + cr_lsto_l: ["horizontal forskydning fra vertikal flade","materiale efterlades til efterbehandling","i arbejdsområdets enheder"], + cr_ease_s: "let ned", + cr_ease_l: ["dyk skæring vil","følge en spiralform ned eller","køre langsomt langs en linært sti"], + cr_clrt_s: "ryd top", + cr_clrt_l: ["kør en rydning over","grændseområdet af delen","ved z = 0"], + cr_clrp_s: "ryd hulrum", + cr_clrp_l: ["fræs ud gennem lommer","i stedet for kun omridset"], + cr_clrf_s: "ryd flader", + cr_clrf_l: ["interpolere trin ned for at","rydde eventuelle opdagede flade områder"], + cr_olin_s: "kun indersider", + cr_olin_l: ["begræns skæring til","indvendige delgrænser"], - // OUTPUT - ou_menu: "output", + // CNC OUTLINE + co_menu: "omrids", + co_dogb_s: "dogbones", + co_dogb_l: ["indsæt dogbone skræinger","ind i indersidens hjørner"], + co_wide_s: "bred udskæring", + co_wide_l: ["udvide udskæringsstier","for deep cuts in hard material"], + co_olin_s: "kun indersider", + co_olin_l: ["begræns skæring til","indvendige delgrænser"], + co_olot_s: "kun ydersider", + co_olot_l: ["begrænse skæring til","udvendige delgrænser","som kan betragtes som skyggeomrids"], + co_omit_s: "udelade igennem", + co_omit_l: "eliminer gennemhulninger", + co_olen_s: "aktiver", + co_olen_l: "aktiver omrids skæring", - // LASER KNIFE - dk_menu: "kniv", - dk_dpth_s: "skæredybde", - dk_dpth_l: "endelig skæredybde\ni millimeter", - dk_pass_s: "skærepasseringer", - dk_pass_l: "antal passeringer\nned for at skære dybde", - dk_offs_s: "spids forskydning", - dk_offs_l: "afstand fra knivspids\ntil midten af værktøjet\ni millimeter", + // CNC CONTOUR + cn_menu: "kontur", + cf_angl_s: "max vinkel", + cf_angl_l: ["vinkler større end dette","betragtes som lodrette"], + cf_curv_s: "kun kurver", + cf_curv_l: ["begrænse lineær oprydning","til buede overflader"], + cf_olin_s: "kun indersider", + cf_olin_l: ["begræns skæring til","indvendige delgrænser"], + cf_linx_s: "aktiver y passering", + cf_linx_l: "lineær y-akse efterbehandling", + cf_liny_s: "aktiver x passering", + cf_liny_l: "lineær x-akse efterbehandling", - // OUTPUT LASER - ou_spac_s: "mellemrum", - ou_spac_l: "afstand mellem lagoutput\ni millimeter", - ou_scal_s: "skalering", - ou_scal_l: "multiplikator (0.1 to 100)", - ou_powr_s: "kraft", - ou_powr_l: "0 - 100\nrepræsenterer %", - ou_sped_s: "hastighed", - ou_sped_l: "millimeter / minuttet", - ou_mrgd_s: "sammensæt", - ou_mrgd_l: "flet alle lag ved hjælp af\nfarvekodning for at betegne\nstabeldybde", - ou_grpd_s: "grupperet", - ou_grpd_l: "bevarer hvert lag som\nen samlet gruppering\ni stedet for adskilt\npolygoner", - ou_layr_s: "lagrækkefølge", - ou_layr_l: "outputlagsrækkefølge\nfra øverst til højre til\nnederst til venstre", - ou_layo_s: "lagfarve", - ou_layo_l: "outputlagsfarver\nfor hvert z-indeks\noverskrævet af sammensætning", - ou_drkn_s: "træk kniv", - ou_drkn_l: "aktiver trækkniv\noutput i gcode\nskårne radier tilføjes\ntil hjørner med\nnedskæringspasseringer", + // CNC TRACE + cu_menu: "spor", + cu_type_s: "type", + cu_type_l: ["følg = værktøjsspids følger linje","til højre eller venstre = værktøjsspids","følger linje forskudt efter værktøjsradius"], - // OUTPUT FDM - ou_nozl_s: "dyse temp", - ou_nozl_l: "grader i celsius", - ou_bedd_s: "plade temp", - ou_bedd_l: "grader i celsius", - ou_feed_s: "print hastighed", - ou_feed_l: "max print hastighed\nmillimeter / minuttet", - ou_fini_s: "afslutningshastighed", - ou_fini_l: "yderste skalhastighed\nmillimeter / minuttet", - ou_move_s: "print hastighed", - ou_move_l: "rejsehastighed\nmillimeter / minuttet\n0 = aktiver G0 bevægelser", - ou_shml_s: "skal faktor", - ou_flml_s: "solid faktor", - ou_spml_s: "fylde faktor", - ou_exml_l: "ekstruderingsmultiplikator\n0.0 - 2.0", - ou_fanl_s: "blæser lag", - ou_fanl_l: "laget hvor blæseren aktiveres", + // CNC DRILLING + cd_menu: "bore", + cd_axis: "akse", + cd_points: "punkter", + cd_plpr_s: "dyk per", + cd_plpr_l: ["max dyk mellem","dvale perioden","i arbejdsområdets enheder","0 for at deaktivere"], + cd_dwll_s: "dvale tid", + cd_dwll_l: ["dvale tid","mellem dyk","i millisekunder"], + cd_lift_s: "løft bor", + cd_lift_l: ["løft mellem dyk","efter opholdsperiode","i arbejdsområdets enheder","0 for at deaktivere"], + cd_regi_s: "registrer", + cd_regi_l: ["bore registreringshuller","til dobbeltsidede dele","uafhængigt af aktivering af","boring, men bruger samme værktøj og indstillinger"], - // OUTPUT CAM - ou_toll_s: "tolerance", - ou_toll_l: "overfladepræcision\ni arbejdsområdets enheder\nlavere er langsommere og\nbruger mere hukommelse\n0 = automatisk baseret\npå animation præference", - ou_zanc_s: "z anker", - ou_zanc_l: "styrer delens position\nnår lager Z overstiger del Z", - ou_ztof_s: "z forskydning", - ou_ztof_l: "forskyd z anker\ni arbejdsområdets enheder\nhar ingen effekt når\nanker er i midten", - ou_zbot_s: "z bottom", - ou_zbot_l: "afstand fra delens bund\n til begrænset skære dybde\ni arbejdsområdets enheder", - ou_zclr_s: "z frihøjde", - ou_zclr_l: "sikker rejse forskydning\nfra toppen af delen\ni arbejdsområdets enheder", - ou_ztru_s: "z gennem", - ou_ztru_l: "forlæng udskæringen nedad\ni arbejdsområdets enheder", - ou_conv_s: "konventionel", - ou_conv_l: "fræsningsretning\nfjern markeringen for 'stigning'", - ou_depf_s: "dybde først", - ou_depf_l: "optimer lommee skæringer\n med dybde prioritet", + // CNC CUTOUT TABS + ct_menu: "tapper", + ct_angl_s: "vinkel", + ct_angl_l: ["startvinkel for tapmellemrum","i grader (0-360)"], + ct_numb_s: "antal", + ct_numb_l: ["antal tapper der skal bruges","bliver fordelt jævnt","omkring delen"], + ct_wdth_s: "bredde", + ct_wdth_l: "bredde i arbejdsområdets enheder", + ct_hght_s: "højde", + ct_hght_l: "højde i arbejdsområdets enheder", + ct_dpth_s: "dybde", + ct_dpth_l: ["distance arbejdsområdets enheder","som tappen projekteres fra","delens flade"], + ct_midl_s: "midtlinje", + ct_midl_l: ["brug midterlinjen på tappen","i stedet for z-bunden","til dobbeltsidet arbejde"], + ct_nabl_s: "auto", + ct_nabl_l: ["generer automatisk radiale tapper","projekteret fra delens center","ved hjælp af optælling og vinkelforskydning"], - // CAM STOCK - cs_menu: "materiale", - cs_wdth_s: "bredde", - cs_wdth_l: "bredde (x) i arbejdsområdets enheder\n0 er standard sat til delens størrelse", - cs_dpth_s: "dybde", - cs_dpth_l: "dybde (y) i arbejdsområdets enheder\n0 er standard sat til delens størrelse", - cs_hght_s: "højde", - cs_hght_l: "højde (z) i arbejdsområdets enheder\n0 er standard sat til delens størrelse", - cs_offs_s: "forskydning", - cs_offs_l: "brug bredde, dybde, højde\n som astand fra max\ndelstørrelse på platformen", - cs_clip_s: "fastgør til", - cs_clip_l: "groft og omrids\nfastgør skærestier\ntil defineret materiale", - cs_offe_s: "aktiver", - cs_offe_l: "aktiver fræsemateriale", + // OUTPUT + ou_menu: "output", - // ORIGIN (CAM & LASER) - or_bnds_s: "udgangspunkt grænser", - or_bnds_l: "udgangspunkt er relativt til\ngrænserne af alle objekter", - or_cntr_s: "udgangspunkt center", - or_cntr_l: "udgangspunkt er refereret til fra centrum", - or_topp_s: "udgangspunkt top", - or_topp_l: "udgangspunkt er refereretfra toppen af objektet", + // LASER KNIFE + dk_menu: "kniv", + dk_dpth_s: "skæredybde", + dk_dpth_l: ["endelig skæredybde","i millimeter"], + dk_pass_s: "skærepasseringer", + dk_pass_l: ["antal passeringer","ned for at skære dybde"], + dk_offs_s: "spids forskydning", + dk_offs_l: ["afstand fra knivspids","til midten af værktøjet","i millimeter"], - // FDM ADVANCED - ad_menu: "ekpert", - ad_rdst_s: "tilbagetræk afstand", - ad_rdst_l: "mængden af tilbagetrukket filament\nfor lange rejser. i millimeter", - ad_rrat_s: "tilbagetræk hastighed", - ad_rrat_l: "hastigheden på filamentet\ni et tilbagetræk i mm/s", - ad_rdwl_s: "påbegynd dvale", - ad_rdwl_l: "tid mellem genopstart af\nfilament og bevægelser\ni millisekunder", - ad_scst_s: "skal trykudligning", - ad_scst_l: "ikke printende ende\naf en skal omkreds\ni millimeter", - ad_msol_s: "min solid", - ad_msol_l: "minimumsareal (mm^2)\nkræves for at holde solid\nskal være > 0.1", - ad_mins_s: "min hastighed", - ad_mins_l: "minimum hastighed\nfor korte sektioner", - ad_spol_s: "langsomme polygoner", - ad_spol_l: "polygoner kortere end dette\nhar deres printhastighed\nnedskaleret til min. hastighed\ni millimeter", - ad_zhop_s: "z hop dist", - ad_zhop_l: "mængden z skal hæves\ikke tilbagetrækning bevægelser\ni millimeter\n0 for at deaktivere", - ad_abkl_s: "anti-tilbageslag", - ad_abkl_l: "brug mikrobevægelser til at annullere\ntilbageslag mens der fyldes\ni millimeter", - ad_lret_s: "lag tilbagetræk", - ad_lret_l: "tving filament tilbagetræk\nmellem lag", - ad_play_s: "polish layers", - ad_play_l: "polish up to specified\n# of layers at a time", - ad_pspd_s: "polish speed", - ad_pspd_l: "polishing speed\nin millimeters / minute", + // OUTPUT LASER + ou_spac_s: "mellemrum", + ou_spac_l: ["afstand mellem lagoutput","i millimeter"], + ou_scal_s: "skalering", + ou_scal_l: "multiplikator (0.1 to 100)", + ou_powr_s: "kraft", + ou_powr_l: ["0 - 100","repræsenterer %"], + ou_sped_s: "hastighed", + ou_sped_l: "millimeter / minuttet", + ou_mrgd_s: "sammensæt", + ou_mrgd_l: ["flet alle lag ved hjælp af","farvekodning for at betegne","stabeldybde"], + ou_grpd_s: "grupperet", + ou_grpd_l: ["bevarer hvert lag som","en samlet gruppering","i stedet for adskilt","polygoner"], + ou_layr_s: "lagrækkefølge", + ou_layr_l: ["outputlagsrækkefølge","fra øverst til højre til","nederst til venstre"], + ou_layo_s: "lagfarve", + ou_layo_l: ["outputlagsfarver","for hvert z-indeks","overskrævet af sammensætning"], + ou_drkn_s: "træk kniv", + ou_drkn_l: ["aktiver trækkniv","output i gcode","skårne radier tilføjes","til hjørner med","nedskæringspasseringer"], - // CAM EXPERT - cx_fast_s: "spring skygge over", - cx_fast_l: "deaktiver detektering af overhæng\nkan være hurtigere og bruge mindre\nhukommelse med komplekse modeller\nmen mislykkes med udhæng\nprøv at aktivere, hvis slicer\nhænger under skygge", + // OUTPUT FDM + ou_nozl_s: "dyse temp", + ou_nozl_l: "grader i celsius", + ou_bedd_s: "plade temp", + ou_bedd_l: "grader i celsius", + ou_feed_s: "print hastighed", + ou_feed_l: ["max print hastighed","millimeter / minuttet"], + ou_fini_s: "afslutningshastighed", + ou_fini_l: ["yderste skalhastighed","millimeter / minuttet"], + ou_move_s: "print hastighed", + ou_move_l: ["rejsehastighed","millimeter / minuttet","0 = aktiver G0 bevægelser"], + ou_shml_s: "skal faktor", + ou_flml_s: "solid faktor", + ou_spml_s: "fylde faktor", + ou_exml_l: ["ekstruderingsmultiplikator","0.0 - 2.0"], + // ou_fanl_s: "blæser lag", + // ou_fanl_l: "laget hvor blæseren aktiveres", + ou_fans_s: "blæser hastighed", + ou_fans_l: "0 - 255", - // FDM GCODE - ag_menu: "gkode", - ag_nozl_s: "dyse", - ag_nozl_l: "vælg output dyse eller hoved", - ag_paws_s: "pause lag", - ag_paws_l: "kommasepareret liste over lag\nder skal indsættet pause kommandoer før", - ag_loop_s: "loop lag", - ag_loop_l: "lagintervaller, der skal gentages i formatet\nførste-sidste-optælling,første-sidste-optælling,...\nudeladt antal = 1", + // OUTPUT CAM + ou_toll_s: "tolerance", + ou_toll_l: ["overfladepræcision","i arbejdsområdets enheder","lavere er langsommere og","bruger mere hukommelse","0 = automatisk baseret","på animation præference"], + ou_zanc_s: "z anker", + ou_zanc_l: ["styrer delens position","når lager Z overstiger del Z"], + ou_ztof_s: "z forskydning", + ou_ztof_l: ["forskyd z anker","i arbejdsområdets enheder","har ingen effekt når","anker er i midten"], + ou_zbot_s: "z bottom", + ou_zbot_l: ["afstand fra delens bund"," til begrænset skære dybde","i arbejdsområdets enheder"], + ou_zclr_s: "z frihøjde", + ou_zclr_l: ["sikker rejse forskydning","fra toppen af delen","i arbejdsområdets enheder"], + ou_ztru_s: "z gennem", + ou_ztru_l: ["forlæng udskæringen nedad","i arbejdsområdets enheder"], + ou_conv_s: "konventionel", + ou_conv_l: ["fræsningsretning","fjern markeringen for 'stigning'"], + ou_depf_s: "dybde først", + ou_depf_l: ["optimer lommee skæringer"," med dybde prioritet"], - // SLA MENU - sa_menu: "slicing", - sa_lahe_s: "laghøjde", - sa_lahe_l: "laghøjdet\ni millimeter", - sa_shel_s: "hul skal", - sa_shel_l: "skaltykkelse i mm\nBrug flere af lagets højde\nbrug 0 for solid (deaktiveret)", - sa_otop_s: "åben top", - sa_otop_l: "hvis skal er aktiveret\nresulterer i en åben top", - sa_obas_s: "åben bund", - sa_obas_l: "hvis skal er aktiveret\nresulterer i en åben bund\ndeaktiveret med understøtninger", + // CAM STOCK + cs_menu: "materiale", + cs_wdth_s: "bredde", + cs_wdth_l: ["bredde (x) i arbejdsområdets enheder","0 er standard sat til delens størrelse"], + cs_dpth_s: "dybde", + cs_dpth_l: ["dybde (y) i arbejdsområdets enheder","0 er standard sat til delens størrelse"], + cs_hght_s: "højde", + cs_hght_l: ["højde (z) i arbejdsområdets enheder","0 er standard sat til delens størrelse"], + cs_offs_s: "forskydning", + cs_offs_l: ["brug bredde, dybde, højde"," som astand fra max","delstørrelse på platformen"], + cs_clip_s: "fastgør til", + cs_clip_l: ["groft og omrids","fastgør skærestier","til defineret materiale"], + cs_offe_s: "aktiver", + cs_offe_l: "aktiver fræsemateriale", - sa_layr_m: "lag", - sa_lton_s: "lys tændt tid", - sa_lton_l: "lys tændt tid\ntid i sekunder", - sa_ltof_s: "lys slukket tid", - sa_ltof_l: "lys slukket tid\ntid i sekunder", - sa_pldi_s: "skræl afstand", - sa_pldi_l: "skræl afstand\ni millimeter", - sa_pllr_s: "afskalningshastighed", - sa_pllr_l: "afskalningshastighed\ni mm/sek", - sa_pldr_s: "afskalningshastighed drop", - sa_pldr_l: "afskalningshastighed drop\ni mm/sek", + // ORIGIN (CAM & LASER) + or_bnds_s: "udgangspunkt grænser", + or_bnds_l: ["udgangspunkt er relativt til","grænserne af alle objekter"], + or_cntr_s: "udgangspunkt center", + or_cntr_l: "udgangspunkt er refereret til fra centrum", + or_topp_s: "udgangspunkt top", + or_topp_l: "udgangspunkt er refereretfra toppen af objektet", - sa_base_m: "bund", - sa_balc_s: "antal lag", - sa_balc_l: "antal\nbundlag", - sa_bltn_l: "bundlag lys tændt\ntid i sekunder", - sa_bltf_l: "bundlag lys slukket\ntid i sekunder", + // FDM ADVANCED + ad_menu: "ekpert", + ad_rdst_s: "tilbagetræk afstand", + ad_rdst_l: ["mængden af tilbagetrukket filament","for lange rejser. i millimeter"], + ad_rrat_s: "tilbagetræk hastighed", + ad_rrat_l: ["hastigheden på filamentet","i et tilbagetræk i mm/s"], + ad_rdwl_s: "påbegynd dvale", + ad_wpln_s: "tilbagetræk aftørring", + ad_wpln_l: ["ikke-printbevægelse","efter tilbagetræk","i millimeter"], + ad_rdwl_l: ["tid mellem genopstart af","filament og bevægelser","i millisekunder"], + ad_scst_s: "skal trykudligning", + ad_scst_l: ["ikke-printende ende","af en skal omkreds","i millimeter"], + ad_msol_s: "min solid", + ad_msol_l: ["minimumsareal (mm^2)","kræves for at holde solid","skal være > 0.1"], + ad_mins_s: "min hastighed", + ad_mins_l: ["minimum hastighed","for korte sektioner"], + ad_spol_s: "korte polygoner", + ad_spol_l: ["polygoner kortere end dette","har deres printhastighed","nedskaleret til min. hastighed","i millimeter"], + ad_arct_s: "bue tolerance", + ad_arct_l: ["konverter facetterede linjer til buer","center point drift tolerance","når matchende buepunkter","overvej værdier omkring 0,15","i millimeter","0 for at deaktivere"], + ad_zhop_s: "z hop dist", + ad_zhop_l: ["mængden z skal hæves\ikke tilbagetrækning bevægelser","i millimeter","0 for at deaktivere"], + ad_abkl_s: "anti-tilbageslag", + ad_abkl_l: ["brug mikrobevægelser til at annullere","tilbageslag mens der fyldes","i millimeter"], + ad_lret_s: "lag tilbagetræk", + ad_lret_l: ["tving filament tilbagetræk","mellem lag"], + ad_play_s: "polish layers", + ad_play_l: ["polish up to specified","# of layers at a time"], + ad_pspd_s: "polish speed", + ad_pspd_l: ["polishing speed","in millimeters / minute"], - sa_infl_m: "fylde", - sa_ifdn_s: "densitet", - sa_ifdn_l: "procent udfyldning\nkræver skal\n0 = deaktiveret\ngyldig 0.0 - 1.0", - sa_iflw_s: "linjebredde", - sa_iflw_l: "luge linjebredde\ni millimeter", + // CAM EXPERT + cx_fast_s: "spring skygge over", + cx_fast_l: ["deaktiver detektering af overhæng","kan være hurtigere og bruge mindre","hukommelse med komplekse modeller","men mislykkes med udhæng","prøv at aktivere, hvis slicer","hænger under skygge"], - sa_supp_m: "støtte", - sa_slyr_s: "bundlag", - sa_slyr_l: "bund støtte lag\nværdiområde 0-10", - sa_slgp_s: "mellemrum", - sa_slgp_l: "antal lag mellem\nflåde og bund på objektet", - sa_sldn_s: "densitet", - sa_sldn_l: "bruges til at beregne\nantal støttesøjler\n0.0-1.0 (0 = deaktiver)", - sa_slsz_s: "størrelse", - sa_slsz_l: "maks. størrelse på en\nstøtte søjle\ni millimeter", - sa_slpt_s: "punkter", - sa_slpt_l: "antal punkter i\nhver støttesøjle\ni millimeter", - sl_slen_l: "aktiver understøtte", + // FDM GCODE + ag_menu: "gkode", + ag_nozl_s: "dyse", + ag_nozl_l: "vælg output dyse eller hoved", + ag_paws_s: "pause lag", + ag_paws_l: ["kommasepareret liste over lag","der skal indsættet pause kommandoer før"], + ag_loop_s: "loop lag", + ag_loop_l: ["lagintervaller, der skal gentages i formatet","første-sidste-optælling,første-sidste-optælling,...","udeladt antal = 1"], - sa_outp_m: "output", - sa_opzo_s: "z forskydning", - sa_opzo_l: "z lagforskydning\nalmost always 0.0\n0.0-1.0 in millimeters", - sa_opaa_s: "anti alias", - sa_opaa_l: "aktivere anti-aliasing\nproducerer større filer\nkan sløre detaljer" - }; + // SLA MENU + sa_menu: "slicing", + sa_lahe_s: "laghøjde", + sa_lahe_l: ["laghøjdet","i millimeter"], + sa_shel_s: "hul skal", + sa_shel_l: ["skaltykkelse i mm","Brug flere af lagets højde","brug 0 for solid (deaktiveret)"], + sa_otop_s: "åben top", + sa_otop_l: ["hvis skal er aktiveret","resulterer i en åben top"], + sa_obas_s: "åben bund", + sa_obas_l: ["hvis skal er aktiveret","resulterer i en åben bund","deaktiveret med understøtninger"], + + sa_layr_m: "lag", + sa_lton_s: "lys tændt tid", + sa_lton_l: ["lys tændt tid","tid i sekunder"], + sa_ltof_s: "lys slukket tid", + sa_ltof_l: ["lys slukket tid","tid i sekunder"], + sa_pldi_s: "skræl afstand", + sa_pldi_l: ["skræl afstand","i millimeter"], + sa_pllr_s: "afskalningshastighed", + sa_pllr_l: ["afskalningshastighed","i mm/sek"], + sa_pldr_s: "afskalningshastighed drop", + sa_pldr_l: ["afskalningshastighed drop","i mm/sek"], + + sa_base_m: "bund", + sa_balc_s: "antal lag", + sa_balc_l: ["antal","bundlag"], + sa_bltn_l: ["bundlag lys tændt","tid i sekunder"], + sa_bltf_l: ["bundlag lys slukket","tid i sekunder"], + + sa_infl_m: "fylde", + sa_ifdn_s: "densitet", + sa_ifdn_l: ["procent udfyldning","kræver skal","0 = deaktiveret","gyldig 0.0 - 1.0"], + sa_iflw_s: "linjebredde", + sa_iflw_l: ["luge linjebredde","i millimeter"], + + sa_supp_m: "støtte", + sa_slyr_s: "bundlag", + sa_slyr_l: ["bund støtte lag","værdiområde 0-10"], + sa_slgp_s: "mellemrum", + sa_slgp_l: ["antal lag mellem","flåde og bund på objektet"], + sa_sldn_s: "densitet", + sa_sldn_l: ["bruges til at beregne","antal støttesøjler","0.0-1.0 (0 = deaktiver)"], + sa_slsz_s: "størrelse", + sa_slsz_l: ["maks. størrelse på en","støtte søjle","i millimeter"], + sa_slpt_s: "punkter", + sa_slpt_l: ["antal punkter i","hver støttesøjle","i millimeter"], + sl_slen_l: "aktiver understøtte", + + sa_outp_m: "output", + sa_opzo_s: "z forskydning", + sa_opzo_l: ["z lagforskydning","almost always 0.0","0.0-1.0 in millimeters"], + sa_opaa_s: "anti alias", + sa_opaa_l: ["aktivere anti-aliasing","producerer større filer","kan sløre detaljer"] +}; diff --git a/web/kiri/lang/de-de-help.html b/web/kiri/lang/de-de-help.html new file mode 120000 index 00000000..8692fdc3 --- /dev/null +++ b/web/kiri/lang/de-de-help.html @@ -0,0 +1 @@ +de-help.html \ No newline at end of file diff --git a/web/kiri/lang/de-de.js b/web/kiri/lang/de-de.js new file mode 100644 index 00000000..d727f8bd --- /dev/null +++ b/web/kiri/lang/de-de.js @@ -0,0 +1,640 @@ +// english. other language maps will defer to english +// map for any missing key/value pairs +kiri.lang['de'] = +kiri.lang['de-de'] = { + // common keys and menus + animate: "Animieren", // CAM animate button + arrange: "Anordnen", // layout workspace objects + axis: "Achse", // left object scale pop menu + clear: "alles entfernen", // clear workspace (remove all objects) + copy: "Kopieren", + delete: "Löschen", + detail: "Details", + done: "Fertig", + enable: "Aktivieren", + export: "Export", + files: "Dateien", + help: "Hilfe", + ghost: "Geist", // left render pop menu (wireframe) + hide: "Verbergen", // left render pop menu (invisible) + home: "home", + import: "Import", + language: "Sprache", + machine: "Maschine", // device or machine + metric: "Metrisch", + name: "Name", + prefs: "Einstellungen", // left menu "preferences" + preview: "Vorschau", + recent: "Letzte Objekte", + render: "Rendern", // left render pop menu + reset: "Zurücksetzen", + rotate: "Rotieren", // left rotate pop menu + save: "Speichern", + scale: "Skalieren", // left object scale pop menu + setup: "Einrichtung", + settings: "Einstellungen", + size: "Größe", + solid: "Undurchsichtig", // view type pop menu + tool: "Werkzeug", + tools: "Werkzeuge", // CAM tool menu button + top: "Oben", + type: "Typ", // CAM tool type + version: "Version", + view: "Ansicht", // left view pop menu + wire: "Drahtansicht", // left render pop menu + + acct_xpo: ["Lege ein Backup Deiner Geräte","und Geräte-Profile an, mit der","Option, den Arbeitsbereich und","mit Objekten und Anordnung zu speichern"], + + // RIGHT-CLICK CONTEXT MENU + rc_clws: "Arbeitsbereich freiräumen", + rc_xpws: "Arbeitsbereich exportieren", + rc_lafl: "Auf das Bett", + rc_mirr: "Spiegeln", + rc_dupl: "Duplizieren", + rc_xstl: "Als STL exportieren", + + // DEVICE MENU and related dialogs + dm_sldt: "Wähle einen Geräte-Typ aus", + dm_stdd: "Vordefinierte Geräte", + dm_mydd: "Meine Geräte", + dm_seld: "Ausgweähltes Gerät", + dm_rcnt: "Letzte Dateien", + dm_savs: "Gespeicherte Voreinstellungen", + dm_appp: "Anwendungseinsetllungen", + + // CAM Tool Dialog + td_tyem: "end", // end mill + td_tybm: "ball", // ball mill + td_tytm: "taper", // taper mill + td_tonm: "tool #", + td_shft: "shaft", // endmill shaft specs + td_flut: "flute", // endmill flute specs + td_tapr: "taper", // endmill taper specs + + // DEVICE dialog groups + dv_gr_dev: "Gerät", + dv_gr_ext: "Extruder", + dv_gr_out: "Output", + dv_gr_gco: "GCode Macros", + + // DEVICE dialog (_s = label, _l = hover help) + dv_name_s: "Name", + dv_name_l: "Geräte-Name", + dv_fila_s: "Filament", + dv_fila_l: "Durchmesser in mm", + dv_nozl_s: "Düse", + dv_nozl_l: "Durchmesser in mm", + dv_bedw_s: "Breite", + dv_bedw_l: "Maßeinheiten des Arbeitsbereichs", + dv_bedd_s: "Tiefe", + dv_bedd_l: "Maßeinheiten der Arbeitsfläche", + dv_bedh_s: "Höhe", + dv_bedh_l: ["Maximale Objekthöhe","in Maßeinheiten der Arbeitsfläche"], + dv_spmx_s: "Spindel-Maximum", + dv_spmx_l: ["Maximale Umdrehungen pro Minute der Spindel","0 zum deaktivieren"], + dv_xtab_s: "Absolute Positionierung", + dv_xtab_l: "Filament wird mit absoluter Positionierung extrudiert", + dv_orgc_s: "Nullpunkt in der Mitte", + dv_orgc_l: "Nullpunkt des Druckbetts in der Mitte", + // dv_orgt_s: "origin top", + // dv_orgt_l: "part z origin top", + dv_bedc_s: "Rundes Druckbett", + dv_bedc_l: "Der Druckbereich des Gerätes ist rund", + dv_belt_s: "Riemen-Druckbett", + dv_belt_l: "Für bewegliche Druckbetten", + dv_fanp_s: "Lüfter-Einstellung", + dv_fanp_l: "Wie stark der Lüfter eingestellt sein soll", + dv_prog_s: "Fortschrittsanzeige", + dv_prog_l: "Wenn für jedes % der Fertigstellung eine Nachicht angezeigt werden soll", + dv_layr_s: "Ebene", + dv_layr_l: ["Ausgabe bei jeder","neuen Ebene"], + dv_tksp_s: "Befehls-Trenner", + dv_tksp_l: ["Füge ein Leerzeichen ein bei","GCode-Achsen-Befehlen","G0X0Y0X0","oder","G0 X0 Y0 Z0"], + dv_strc_s: "Kommentare entfernen", + dv_strc_l: ["Kommentare aus GCode entfernen","Kommentare beginnen mit ;"], + dv_fext_s: "Datei-Erweiterung", + dv_fext_l: "Datei-Erweiterung", + dv_dwll_s: "dwell", + dv_dwll_l: "gcode dwell script", + dv_tool_s: "Werkzeg-Wechel", + dv_tool_l: "Skript zum Werkzegwechsel", + dv_sspd_s: "Spindelgeschwindigkeit", + dv_sspd_l: "Einstellung der Spindelgeschwindigkeit", + dv_paus_s: "Pause", + dv_paus_l: "Skript zum Ausführen bei einer Pause", + dv_head_s: "Anfang", + dv_head_l: "Skript für den Anfang einer GCode-Datei", + dv_foot_s: "Ende", + dv_foot_l: "Skript für das Ende einer GCode-Datei", + dv_lzon_s: "Laser an", + dv_lzon_l: "Skript, um den Laser einzuschalten", + dv_lzof_s: "Laser aus", + dv_lzof_l: "Skript, um den Laser auszuschalten", + dv_exts_s: "Auswhälen", + dv_exts_l: "GCode, um diesen Extruder auszuwählen", + dv_dext_s: "Abwählen", + dv_dext_l: "GCode, bevor ein anderer Extruder ausgewählt wird", + dv_extd_s: "Deaktivieren", + dv_extd_l: "GCode, um diesen Extruder zu deaktivieren", + dv_exox_s: "X-Offset", + dv_exox_l: "X-Offset der Düse", + dv_exoy_s: "Y-Offset", + dv_exoy_l: "Y-Offset der Düse", + + // MODE + mo_menu: "Arbeits-Modus", + mo_fdmp: "FDM-Druck", + mo_slap: "SLA-Druck", + mo_lazr: "Laser-Cutting", + mo_cncm: "CNC-Fräsen", + + // SETUP + su_menu: "setup", + su_devi: "Geräte", + su_tool: "Werkzeuge", + su_locl: "Lokal", + su_xprt: "Export", + su_help: "Hilfe", + + // LOAD + fe_menu: "Datei", + fn_recn: "Letzte", + fn_impo: "Import", + + // FUNCTION + fn_menu: "Modus", + fn_arra: "Anordnen", + fn_slic: "Slice", + fn_prev: "Vorschau", + fn_expo: "Export", + + // VIEW + vu_menu: "Ansicht", + vu_home: "Standard", + vu_rset: "Zurücksetzen", + vu_sptp: "Oben", + vu_spfr: "Vorn", + vu_splt: "Links", + vu_sprt: "Ansicht", + + // WORKSPACE + ws_menu: "Ansicht", + ws_save: "Speichern", + ws_cler: "Leeren", + + // OPTIONS + op_menu: "interface", + op_disp: "Anzeige", + op_xprt_s: "Fortgeschritten", + op_xprt_l: "weitere Einstellungen", + op_decl_s: "decals", + op_decl_l: "show device decals and logos", + op_dang_s: "Experimentell", + op_dang_l: "Zeige experimentelle Parameter", + op_hopo_s: "hover pop", + op_hopo_l: ["enable menu hover","to activate"], + op_dark_s: "Dunkler Modus", + op_dark_l: "Dunkles Interface", + op_comp_s: "Kompakt", + op_comp_l: ["Kompakte Ansicht","Besser für kleine Bildschirme","und Tablets"], + op_shor_s: "Nullpunkt anzeigen", + op_shor_l: "Zeige den Nullpunkt des Gerätes oder des Prozesses", + op_shru_s: "Lineal einblenden", + op_shru_l: ["Zeige Lineale mit Maßen","auf den Hauptachsen"], + op_sped_s: "Geschwindigkeiten einblenden", + op_sped_l: ["Zeige Legende für Farben und Geschwindigkeiten","in der Vorschau"], + op_auto_s: "Automatische Anordnung", + op_auto_l: ["Ordne automatisch an","wenn neue Objekte hinzugefügt werden"], + op_free_s: "Freie Anordnung", + op_free_l: ["Erlaube Freie Anordnung von Objekten","Keine Auswirkung im Laser-Cutting Arbeitsmodus"], + op_spcr_s: "Abstand", + op_spcr_l: ["Abstand zwischen Objekten","bei automatischer Anordnung","in Maßeinheiten der Arbeitsfläche"], + op_invr_s: "Zoom Umkehren", + op_invr_l: ["Kehre die Richtung beim Mausrad-","Zoomen um"], + op_save_s: "Automatisch speichern", + op_save_l: ["Behalte Objekte im Arbeitsbereich","wenn die Anwendung neu geladen wird"], + op_line_s: "Darstellungs-Typ", + op_line_l: ["Wie Linien bei der Darstellung der Pfade gezeichnet werden","Beeinflußt 3D-Performance","Pfad: 3D (beste)","Flach: 2d (gut)","Linie = 1d (schnell)"], + op_unit_s: "Maßeinheiten", + op_unit_l: ["die Maßeinheit des Arbeitsbereich wirkt sich auf die Darstellung von ","Geschwindigkeiten und Entfernungen aus"], + op_anim_s: "Animieren", + op_anim_l: ["Dichte des Netzgitters beim Animieren","Je höher, desto dichter","benötigt mehr Speicher","und ist langsamer"], + + lo_menu: "Anordnung", + + pt_menu: "Objekte", + pt_deci_s: "Deizimierung", + pt_deci_l: ["Punktdezimierung","während des Imports. Für schnelleres Slicen","und weniger Speicherverbrauch"], + pt_qual_s: "Qualität", + pt_qual_l: ["Detailstufe beim","Slicen","geringer ist schneller"], + pt_heal_s: "Mesh reparieren", + pt_heal_l: ["Versuche","defekte Meshes zu reparieren","wirkt sich auf die Verarbeitungszeit us"], + + xp_menu: "Export", + + // SETTINGS + se_menu: "Profil", + se_load: "Laden", + se_save: "Speichern", + + // FDM SLICING + sl_menu: "Schichten", + sl_lahi_s: "Höhe", + sl_lahi_l: ["Höhe jeder Schicht","Schichthöhe in mm"], + ad_minl_s: "Mindesthöhe", + ad_minl_l: ["Mindesthöhe für adaptive Schichthöhe","in mm","Darf nicht null sein"], + sl_ltop_s: "top layers", + sl_ltop_l: ["number of solid layers","to enforce at the","top of the print"], + sl_lsld_s: "solid layers", + sl_lsld_l: ["solid fill areas computed","from layer deltas. see","layer pop menu"], + sl_lbot_s: "base layers", + sl_lbot_l: ["Anzahl der soliden Schichten","um die Unterseite des","Drucks zu verstärken"], + ad_adap_s: "Adaptive Höhe", + ad_adap_l: ["Verwende adaptive Schichthöhe","mit 'Maximale Höhe' als Maximum","und 'Mindesthöhe' als Minimum"], + + // FDM SHELLS + sl_shel_s: "Anzahl Außenwände", + sl_shel_l: ["Anzahl der äußeren","Wände, die erzeugt werden sollen"], + sl_ordr_s: "Druckreihenfolge der Wände", + sl_ordr_l: ["In welcher Reihenfolge die Wände gedruckt werden","innen nach aussen","oder aussen nach innen","wirkt sich auf die Oberflächenqualität aus"], + sl_strt_s: "Start der Ebene", + sl_strt_l: ["Startpunkt der Ebene","Letzte = Ende der letzten Ebene ","Mitte = Zur Mitte des Objekts","Nullpunkt = Zum Nullpunkt des Gerätes"], + ad_thin_s: "Dünne Wände", + ad_thin_l: ["Finde und fülle schmale Lücken","zwischen Außenwänden"], + + // FDM FILL + fi_menu: "Füllmaterial", + fi_type: "Typ", + fi_pcnt_s: "Füll-Dichte", + fi_pcnt_l: ["Füll-Dichte ","0.0 - 1.0"], + fi_angl_s: "Startwinkel für volle Füllung", + fi_angl_l: ["Startwinkel in Grad","90 Grad werden ","bei jeder folgender Schicht dazugerechnet","Wirkt sich nur auf vollgefüllte Schichten aus."], + fi_over_s: "Schicht-Überlappung", + fi_over_l: ["Überlappung von Schichten und Füllung","als Anteil des Düsen-Durchmessers","0.0 - 1.0"], + // fi_rate_s: "print speed", + fi_rate_l: ["Druckgeschwindigkeit für Füllmaterial","Auf 0 setzen, um die Standard-","Druckgeschwindigkeit zu verwenden"], + + // FDM FIRST LAYER + fl_menu: "Erste Schicht", + fl_lahi_s: "Schichthöhe", + fl_lahi_l: ["Höhe der ersten Schicht","in in mm","sollte >= der Standard-Schichthöhe sein"], + fl_rate_s: "Wand-Geschwindigkeit", + fl_rate_l: ["Maximalgeschwindigkeit beim Drucken von Wänden","in mm / min"], + fl_frat_s: "Füll-Geschwindikeit", + fl_frat_l: ["Maximalgeschwindikeit beim Drucken von Füllung","in mm / min"], + fl_mult_s: "Extrusions-Faktor", + fl_mult_l: ["Exktrusions-Faktor","0.0 - 2.0"], + fl_sfac_s: "Breiten-Faktor", + fl_sfac_l: ["Multipliziert mit dem Düsenfaktor","beeinflußt die Breite der Drucklinien"], + fl_skrt_s: "Anzahl der Skirt-Linien", + fl_skrt_l: ["Wieviele Linien um die erste Schicht","herum generiert werden sollen"], + fl_skro_s: "Skirt-Abstand", + fl_skro_l: ["Abstand des Skirts vom Objekt","in mm"], + fl_nozl_s: "Drucktemperatur", + fl_nozl_l: ["in Grad Celsius","Standard-Einstellung wird verwendet","wenn auf 0 gestellt"], + fl_bedd_s: "Betttemperatur", + fl_bedd_l: ["in Grad Celsius","Standard-Einstellung wird verwendet","when auf 0 gestellt"], + fr_spac_s: "Raft Abstand", + fr_spac_l: ["Zusätzlicher Abstand","zwischen erster Schicht und Raft","in mm"], + fr_nabl_s: "Raft verwenden", + fr_nabl_l: ["Erzeuge ein Raft unter dem","Objekt für bessere Betthaftung","verwendet Skirt-Abstand und","Deaktiviert die Erzeugung eines Skirt"], + + // FDM BELT ONLY + fl_zoff_s: "belt offset", + fl_zoff_l: ["height offset from belt","of lowest extrusion","in millimeters"], + fl_brim_s: "Größe des Brim", + fl_brim_l: ["Brim am Fuß des Objekts erzeugen","Größe in mm","0 um zu deaktivieren"], + fl_brmn_s: "Brim-Auslöser", + fl_brmn_l: ["Erzeuge nur Brim, wenn die Fläche","auf dem Gurt kürzer ist als dieser Wert","Angegeben in mm","0 = Immer"], + fl_bled_s: "Objekt verankern", + fl_bled_l: ["Objekt mit dem Gurt verankern","zu Beginn des Drucks","in mm"], + + // FDM SUPPORT + sp_menu: "Stützstrukturen", + sp_detect: "Analysieren", + sp_dens_s: "Dichte", + sp_dens_l: ["Anteil 0.0 - 1.0","empfohlen 0.15","0 zum deaktivieren"], + sp_size_s: "Breite der Säule", + sp_size_l: ["Breite der Säule","in mm"], + sp_offs_s: "Abstand", + sp_offs_l: ["Abstand vom Objekt","in mm"], + sp_gaps_s: "Schicht-Abstand", + sp_gaps_l: ["Anzahl der Schichten","zwischen Stützstruktur und Objekt"], + sp_span_s: "Maximale Spannbreite", + sp_span_l: ["Für Spannen über dieser Breite","werden Stützstrukturen erzeugt","in mm"], + sp_angl_s: "Maximaler Winkel", + sp_angl_l: ["Für Überhänge über diesem Winkel","werden Stützstrukturen erzeugt"], + sp_area_s: "Mindestfläche", + sp_area_l: ["Mindestfläche für","Eine Stützsäule","in mm"], + sp_xpnd_s: "Ausdehung", + sp_xpnd_l: ["Dehne die Stützstruktur","über die Objekt-Grenzen aus","in mm"], + sp_nozl_s: "Extruder", + sp_nozl_l: ["In Geräten mit mehreren Extrudern","welcher Extruder wird für","Stützmaterial verwendet"], + sp_auto_s: "Automatisch", + sp_auto_l: ["Erzeuge Stützstrukturen automatisch","beim Slicen","Stützstruktuen werden erst","Nach dem Slicen angezeigt"], + + // LASER SLICING + ls_offs_s: "Abstand", + ls_offs_l: ["Anpassung for Strahlbreite","in millimeters"], + ls_lahi_s: "Höhe", + ls_lahi_l: ["Schichthöhe","in mm","0 = auto/erkennen"], + ls_lahm_s: "min", + ls_lahm_l: ["Minimale Schichthöhe","Autoslices unter dieser Höhe","werden zusammengeführt","in millimeters"], + ls_sngl_s: "Einfach", + ls_sngl_l: ["Nur einen Slice bei","angegebener Schichthöhe ausführen"], + + // CNC COMMON terms + cc_tool: "Werkzeug", + cc_offs_s: "offset", + cc_offs_l: ["offset tool center","from chosen path"], + cc_spnd_s: "spindle rpm", + cc_spnd_l: ["spindle speed in","revolutions / minute"], + cc_sovr_s: "step over", + cc_sovr_l: ["as a fraction of","tool diameter"], + cc_sdwn_s: "step down", + cc_sdwn_l: ["step down depth","for each pass","in workspace units","0 to disable"], + cc_feed_s: "feed rate", + cc_feed_l: ["max cutting speed in","workspace units / minute"], + cc_plng_s: "plunge rate", + cc_plng_l: ["max z axis speed in","workspace units / minute"], + cc_sngl_s: "select lines only", + cc_sngl_l: ["select only single edges","instead of connected polylines"], + + // CNC COMMON + cc_menu: "limits", + cc_flip: "flip", + cc_rapd_s: "xy feed", + cc_rapd_l: ["max xy moves feedrate","in workspace units / minute"], + cc_rzpd_s: "z feed", + cc_rzpd_l: ["max z moves feedrate","in workspace units / minute"], + + cc_loff_s: "offset", + cc_loff_l: ["distance from stock face","for leveling pass","in workspace units"], + + // CNC ROUGHING + cr_menu: "rough", + cr_lsto_s: "leave stock", + cr_lsto_l: ["horizontal offset from vertical faces","stock to leave for finishing pass","in workspace units"], + cr_ease_s: "ease down", + cr_ease_l: ["plunge cuts will","spiral down or ease","along a linear path"], + cr_clrt_s: "clear top", + cr_clrt_l: ["run a clearing pass over","the bounding area of the part","at z = 0"], + cr_clrp_s: "clear voids", + cr_clrp_l: ["mill out through pockets","instead of just the outline"], + cr_clrf_s: "clear faces", + cr_clrf_l: ["interpolate step down to","clear any detected flat areas"], + cr_olin_s: "inside only", + cr_olin_l: ["limit cutting to","inside part boundaries"], + + // CNC OUTLINE + co_menu: "outline", + co_dogb_s: "dogbones", + co_dogb_l: ["insert dogbone cuts","into inside corners"], + co_wide_s: "wide cutout", + co_wide_l: ["widen outside cutout paths","for deep cuts in hard material"], + co_olin_s: "inside only", + co_olin_l: ["limit cutting to","inside part boundaries"], + co_olot_s: "outside only", + co_olot_l: ["limit cutting to","exterior part boundaries","which can be thought of","as the shadow outline"], + co_omit_s: "omit through", + co_omit_l: "eliminate thru holes", + co_olen_s: "enable", + co_olen_l: "enabled outline cutting", + + // CNC CONTOUR + cn_menu: "contour", + cf_angl_s: "max angle", + cf_angl_l: ["angles greater than this","are considered vertical"], + cf_curv_s: "curves only", + cf_curv_l: ["limit linear cleanup","to curved surfaces"], + cf_olin_s: "inside only", + cf_olin_l: ["limit cutting to","inside part boundaries"], + cf_linx_s: "enable y pass", + cf_linx_l: "linear y-axis finishing", + cf_liny_s: "enable x pass", + cf_liny_l: "linear x-axis finishing", + + // CNC TRACE + cu_menu: "trace", + cu_type_s: "type", + cu_type_l: ["follow = tool tip follows line","right or left = tool tip","follows line offset by tool radius"], + + // CNC DRILLING + cd_menu: "drill", + cd_axis: "axis", + cd_points: "points", + cd_plpr_s: "plunge per", + cd_plpr_l: ["max plunge between","dwell periods","in workspace units","0 to disable"], + cd_dwll_s: "dwell time", + cd_dwll_l: ["dwell time","between plunges in","in milliseconds"], + cd_lift_s: "drill lift", + cd_lift_l: ["lift between plunges","after dwell period","in workspace units","0 to disable"], + cd_regi_s: "register", + cd_regi_l: ["drill registration holes","for double-sided parts","independent of enable","drilling but uses same","tool and settings"], + + // CNC CUTOUT TABS + ct_menu: "tabs", + ct_angl_s: "angle", + ct_angl_l: ["starting angle for tab spacing","in degrees (0-360)"], + ct_numb_s: "count", + ct_numb_l: ["number of tabs to use","will be spaced evenly","around the part"], + ct_wdth_s: "width", + ct_wdth_l: "width in workspace units", + ct_hght_s: "height", + ct_hght_l: "height in workspace units", + ct_dpth_s: "depth", + ct_dpth_l: ["distance in workspace units","that the tab projects from","the part surface"], + ct_midl_s: "midline", + ct_midl_l: ["use midline of tab","instead of z bottom","for double-sided work"], + ct_nabl_s: "auto", + ct_nabl_l: ["auto generate radial tabs","projected from part center","using count and angle offset"], + + // OUTPUT + ou_menu: "output", + + // LASER KNIFE + dk_menu: "knife", + dk_dpth_s: "cut depth", + dk_dpth_l: ["final cut depth","in millimeters"], + dk_pass_s: "cut passes", + dk_pass_l: ["number of passes","down to cut depth"], + dk_offs_s: "tip offset", + dk_offs_l: ["distance from blade tip","to center of tool","in millimeters"], + + // OUTPUT LASER + ou_spac_s: "spacing", + ou_spac_l: ["distance between layer output","in millimeters"], + ou_scal_s: "scaling", + ou_scal_l: "multiplier (0.1 to 100)", + ou_powr_s: "power", + ou_powr_l: ["0 - 100","represents %"], + ou_sped_s: "speed", + ou_sped_l: "millimeters / minute", + ou_mrgd_s: "merged", + ou_mrgd_l: ["merge all layers using","color coding to denote","stacking depth"], + ou_grpd_s: "grouped", + ou_grpd_l: ["retain each layer as","a unified grouping","instead of separated","polygons"], + ou_layr_s: "layer order", + ou_layr_l: ["output layer order","from top right to","bottom left"], + ou_layo_s: "layer color", + ou_layo_l: ["output layer colors","for each z index","overridden by merged"], + ou_drkn_s: "drag knife", + ou_drkn_l: ["enable drag knife","output in gcode","cut radii are added","to corners with","cut down passes"], + + // OUTPUT FDM + ou_nozl_s: "Drucktemperatur", + ou_nozl_l: "In Grad Celsius", + ou_bedd_s: "Betttemperatur", + ou_bedd_l: "In Grad Celsius", + ou_feed_s: "Druckgewschwindigkeit", + ou_feed_l: ["Maximale Druckgeschwindigkeit","mm / min"], + ou_fini_s: "Außengeschwindigkeit", + ou_fini_l: ["Geschwindigkeit für Außenwände","mm / min"], + ou_move_s: "Bewegungsgeschwnidgkeit", + ou_move_l: ["Geschwndigkeit, wenn nicht gedruckt wird ","mm / min","0 = aktiviere G0 Befehle"], + ou_shml_s: "Wand-Faktor", + ou_flml_s: "Voll-Füll-Faktor", + ou_spml_s: "Füll-Material-Faktor", + ou_exml_l: ["Extrusions-Multiplikator","0.0 - 2.0"], + ou_fans_s: "Geschwindigkeit der Bauteilkühlung", + ou_fans_l: "0 - 255", + + // OUTPUT CAM + ou_toll_s: "tolerance", + ou_toll_l: ["surface precision","in workspace units","lower is slower and","uses more memory","0 = automatic based","on animate preference"], + ou_zanc_s: "z anchor", + ou_zanc_l: ["controls the position of the part","when stock Z exceeds part Z"], + ou_ztof_s: "z offset", + ou_ztof_l: ["offset z anchor","in workspace units","has no effect when","anchor is middle"], + ou_zbot_s: "z bottom", + ou_zbot_l: ["offset from part bottom","to limit cutting depth","in workspace units"], + ou_zclr_s: "z clearance", + ou_zclr_l: ["safe travel offset","from top of part","in workspace units"], + ou_ztru_s: "z thru", + ou_ztru_l: ["extend cutout pass down","in workspace units"], + ou_conv_s: "conventional", + ou_conv_l: ["milling direction","uncheck for 'climb'"], + ou_depf_s: "depth first", + ou_depf_l: ["optimize pocket cuts","with depth priority"], + + // CAM STOCK + cs_menu: "stock", + cs_wdth_s: "width", + cs_wdth_l: ["width (x) in workspace units","0 defaults to part size"], + cs_dpth_s: "depth", + cs_dpth_l: ["depth (y) in workspace units","0 defaults to part size"], + cs_hght_s: "height", + cs_hght_l: ["height (z) in workspace units","0 defaults to part size"], + cs_offs_s: "offset", + cs_offs_l: ["use width, depth, height","as offsets from max","part size on platform"], + cs_clip_s: "clip to", + cs_clip_l: ["rough and outline","clip cutting paths","to defined stock"], + cs_offe_s: "enable", + cs_offe_l: "enable milling stock", + + // ORIGIN (CAM & LASER) + or_bnds_s: "origin bounds", + or_bnds_l: ["origin is relative to","boundary of all objects"], + or_cntr_s: "origin center", + or_cntr_l: "origin is referenced from the center", + or_topp_s: "origin top", + or_topp_l: "origin is references from the top of objects", + + // FDM ADVANCED + ad_menu: "Fortgeschritten", + ad_rdst_s: "Einzugslänge", + ad_rdst_l: ["Wie weit das Filament eingezogen werden soll","bei langen Bewegungen (in mm)"], + ad_rrat_s: "Einzugsgeschwindigkeit", + ad_rrat_l: ["Geschwindigkeit des Filamenteinzugs","in mm/s"], + ad_rdwl_s: "Vorschub-Pause", + ad_wpln_s: "Einzugs-Bewegung", + ad_wpln_l: ["Bewegung ohne Druck","nach Einzug","in millimeters"], + ad_rdwl_l: ["Zeitabstand zwischen","Bewegung und Vorschub nach Einzug ","in msec"], + ad_scst_s: "Außenwand-Coasting", + ad_scst_l: ["Welche Strecke am Ende einer","Außenwand nicht extrudiert wird","in mm"], + ad_msol_s: "Mindestfläche für vollständige Füllung", + ad_msol_l: ["Mindestfläche in mm^2 ","für vollständige Füllung","muß > 0.1 sein"], + ad_mins_s: "Mindestgeschwindigkeit", + ad_mins_l: ["Mindestgeschwindigkeit","für kurze Segmente"], + ad_spol_s: "Kurze Segmente", + ad_spol_l: ["Segmente die kürzer sind","werden mit","Mindestgeschwindigkeit gedruckt","in mm"], + ad_arct_s: "arc tolerance", + ad_arct_l: ["convert faceted lines to arcs","center point drift tolerance","when matching arc points","consider values around 0.15","in millimeters","0 to disable"], + ad_zhop_s: "Z Hop", + ad_zhop_l: ["Hebe Düse bei Einzug an","in mm","0 zum deaaktivieren"], + ad_abkl_s: "anti-backlash", + ad_abkl_l: ["Für schöneres Aussehen Flacher Oberflächen","verwende Micro-Bewegungen","um Backlash auszugleichen","in mm","0 zum deaktivieren","Wenn Deine Firmware M425 unterstützt","verwende das im Start-GCode und setze hier auf 0"], + ad_lret_s: "Einzug bei Ebenenwechsel", + ad_lret_l: ["Erzwinge Einzug","bei Ebenen wechseln"], + ad_play_s: "Ebenen glätten", + ad_play_l: ["glätte bis zu","dieser Anzahl Ebenen"], + ad_pspd_s: "Glätt-Geschwindigkeit", + ad_pspd_l: ["Glätt-Geschwindigkeit","in mm / min"], + + // CAM EXPERT + cx_fast_s: "skip shadow", + cx_fast_l: ["disable overhang detection","can be faster and use less","memory with complex models","but fails with overhangs","try enabling if slicing","hangs during shadowing"], + + // FDM GCODE + ag_menu: "GCode", + ag_nozl_s: "Düse", + ag_nozl_l: "Wählt Düse oder Druckkopf aus", + ag_paws_s: "Pause bei Ebenen", + ag_paws_l: ["Liste von Ebenen (kommasepariert","vor denen pausiert werden soll"], + ag_loop_s: "Schichten wiederholen", + ag_loop_l: ["Schichten die wiederholt werden sollen, im Format ","erste-letzte-anzahl,erste-letzte-anzahl,...","omitted count = 1"], + + // SLA MENU + sa_menu: "Slicen", + sa_lahe_s: "Schichthöhe", + sa_lahe_l: ["Schichthöhe","in mm"], + sa_shel_s: "Aushöhlen", + sa_shel_l: ["Wanddick mm","verwene Mehrfaches der Schichthöhe","setze 0 zum deaktivieren"], + sa_otop_s: "oben offen", + sa_otop_l: ["Wenn Aushöhlen aktiviert ist","bleibt das Objekt oben offen"], + sa_obas_s: "unten offen", + sa_obas_l: ["Wenn Aushöhlen aktiviert ist","bleibt das Objekt unten offen","deaktiviert, wenn Stützstrukturen verwendet werden"], + + sa_layr_m: "layers", + sa_lton_s: "light on time", + sa_lton_l: ["layer light on","time in seconds"], + sa_ltof_s: "light off time", + sa_ltof_l: ["layer light off","time in seconds"], + sa_pldi_s: "peel distance", + sa_pldi_l: ["peel distance","in millimeters"], + sa_pllr_s: "peel lift rate", + sa_pllr_l: ["peel lift speed","in mm/sec"], + sa_pldr_s: "peel drop rate", + sa_pldr_l: ["peel drop speed","in mm/sec"], + + sa_base_m: "base", + sa_balc_s: "layer count", + sa_balc_l: ["number of","base layers"], + sa_bltn_l: ["base layer light on","time in seconds"], + sa_bltf_l: ["base layer light off","time in seconds"], + + sa_infl_m: "Füllstrukturen", + sa_ifdn_s: "Dichte", + sa_ifdn_l: ["percent infill","requires shell","0 = disabled","valid 0.0 - 1.0"], + sa_iflw_s: "line width", + sa_iflw_l: ["hatch line width","in millimeters"], + + sa_supp_m: "support", + sa_slyr_s: "base layers", + sa_slyr_l: ["base support layers","value range 0-10"], + sa_slgp_s: "gap layers", + sa_slgp_l: ["number of layers between","raft and bottom of object"], + sa_sldn_s: "density", + sa_sldn_l: ["used to compute the","number of support pillars","0.0-1.0 (0 = disable)"], + sa_slsz_s: "size", + sa_slsz_l: ["max size of a","support pillar","in millimeters"], + sa_slpt_s: "points", + sa_slpt_l: ["number of points in","each support pillar","in millimeters"], + sl_slen_l: "enable supports", + + sa_outp_m: "output", + sa_opzo_s: "z offset", + sa_opzo_l: ["z layer offset","almost always 0.0","0.0-1.0 in millimeters"], + sa_opaa_s: "anti alias", + sa_opaa_l: ["enable anti-aliasing","produces larger files","can blur details"] +}; diff --git a/web/kiri/lang/de-help.html b/web/kiri/lang/de-help.html new file mode 100644 index 00000000..4cf21fe2 --- /dev/null +++ b/web/kiri/lang/de-help.html @@ -0,0 +1,177 @@ +
    + +
    + + +
    + +
    + +
    +
    Über
    +
    starting
    +
    Steuerung
    +
    Unterstützung
    +
    Das Projekt
    +
    + +
    +
    +
    +

    + Kiri:Moto ist ein einzigartiger browser-basierter, komplett quelloffener slicer, + mit dem Du 3D-Modelle in Steuerbefehle für 3D-Drucker, CNC-Fräsen, + uns Laser Cutter umwandeln kannst. +


    + Browser-basiert bedeutet, dass alle Verarbeitung in Deinem Browser stattfindet, + und nicht in der Cloud. Deine Modelle und Daten bleiben privat auf Deinem Rechner. +


    + Das heisst, das Kiri wie eine lokale Anwendung auf Deinem Computer läuft, aber zusätzlich + so sicher ist, wie Dein Browser. +


    + Ein weiterer Vorteil von Kiri als Web-Anwendung ist, dass Du immer die aktuelle Version + verwendest. Neue Funktionen, Fehlerbehebungen, und andere Updates kommen regelmäßig und automatisch + bei Dir an. Wenn Du eine ältere, oder eine Entwicklungsversion von Kiri verwenden willst, kannst + Du diese im Nutzer-Menü oben rechts auswählen. +

    +
    +
    + Kiri leitet sich von Kiri-e ab, der Japanischen Scherenschnitt-Kunst. + Moto ist ein Kürzel für "Modeling Tool" (Modellierungs-Werkzeug) +
    + +
    + Schnell-Anleitung +
    +

    +

  • Schau Dir The Definitive Video Guide to Kiri:Moto 2.5 an (auf Englisch)
  • +
  • Im Einrichtungs-Menü wählst Du Geräte und Arbeitsmodus
  • +
  • preferences allow interface and mode-specific settings
  • +
  • Du kannst Geräte-Konfigurationen anpassen, wenn Du die Standard-Einstellungen verändern willst
  • +
  • Öffne Dateien importiere Teile in den Arbeitsbereich
  • +
  • use the right-side profile menus to customize slicing parameters
  • +
  • Mit Slice siehst du, wie die Parameter sich auf Objekte auswirken
  • +
  • Vorschau berechnet Pfade (Maschinen-Anweisungen) aus den gesliceten Objekten
  • +
  • Export speichert diese Pfade in GCode- oder SVG-Dateien
  • + +

    +
    +
    + Arbeits-Modi +

    Jeder Modus (FDM, CAM, Laser, SLA) hat unterschiedliche Funktionen und Einstellungsmöglichkeiten. + Schau Dir die Youtube-Videos an, um mehr zu erfahren.

    +
    +
    + +
    Über 3d-Druck
    +
    Über CNC-Fräsen
    +
    Über Laser-Cutting
    + +
    +
    +


    Konfiguration
    Dette Hjælpe Vindue
    Enhed Udvælger
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Tastatur-Befehle
    TasteBefehl
    Konfiguration
    Geräte Auswahl
    Auswahl des Fräswerkzeugs
    Grid-Geräte im Netzwerk
    Voreinstellungen
    Arbeits-Profile
    Modus
    Platzieren
    Slice
    Vorschau
    Animieren
    Export
    Datei
    Importieren
    Letzte Dateien
    Ansicht
    Standard
    Oben
    Vorn
    Hinten
    Ansicht zurüksetzen
    Durch die Seitenansichten wechseln
    Fokus (Platzieren)
    Ebene (Slice/Vorschau)
    Objekte
    Duplizieren
    Spiegeln
    Rotieren (Dialog)
    Löschen
    Um 90° rotieren
    Um 5° rotieren
    5mm bewegen
    Alle Objekte auswählen
    Hilfe
    Dieses Fenster


    Maus-Kontrollen
    TastenkombinationBefehl
    Ansicht
    wheelSchicht wechseln
    verschiebenAnsicht schwenken
    Ansicht schwenken
    Ansicht rotieren
    Ausschnitt verändern
    Ausschnitt verändern
    dragAnsicht schwenkden
    Objekte
    Seite aufs Bett ausrichten
    Seite aufs Bett ausrichten
    +
    +
    + +
    + Weitere Informationen + + Weitere Optionen +

    + Im button> NutzerMenü open rechts, kannst Du die Funktion + Export verwenden, um eine Kopie Deiner Geräte Profile und (optional) Deines Arbeitsbereichs + herunterzuladen. Mit dieser Datei kannst Du jederzeit Deine komplette Arbeitsumgebung bei Kiri:Moto wieder herstellen. + Du kannst sie auch mit anderen Teilen, zum Beispiel um die Entwickler oder andere Nutzer bei der Fehlersuche zu unterstützen. +

    +
    + +
    + Open Source +

    + Kiri:Moto ist komplett quelloffen. + Mehr kannst Du auf der Github-Seite zum Projekt erfahten. Es gibt verwandte Projekte, sowohl Software als auch + Hardware. Neue Entwickler und Code-Erweiterungen werden gerne entgegen genommen. Der Quellcode ist unter der + MIT-Lizenz veröffentlicht. +


    + Spenden sind natürlich auch willkommen. +

    +
    Integrationen +

    Kiri:Moto ist auch in anderen Applikationen integriert.

    +
  • Onshape im App store
  • +
  • Thingiverse als Thing Apps
  • +
  • SimplyPrint.io verwendet Kiri:Moto als primären Slicer
  • +
  • CAMLab ist eine Variante einer früheren Version von Kiri:Moto
  • +
    + +
    + +
    + diff --git a/web/kiri/lang/de.js b/web/kiri/lang/de.js new file mode 120000 index 00000000..bccb4c51 --- /dev/null +++ b/web/kiri/lang/de.js @@ -0,0 +1 @@ +de-de.js \ No newline at end of file diff --git a/web/kiri/lang/en-help.html b/web/kiri/lang/en-help.html index 8a70359c..acd45712 100644 --- a/web/kiri/lang/en-help.html +++ b/web/kiri/lang/en-help.html @@ -11,13 +11,6 @@
    about
    starting
    controls
    -
    support
    project
    @@ -83,6 +76,7 @@ CAM Tool Selection Grid Local Devices Preference panel + Work Profiles Mode Arrange @@ -96,11 +90,14 @@ Recent View - Top Home - Layer + Top + Front + Back Reset View - dragPan + Cycle Side Views + Focus (arrange) + Layer (slice/preview) Objects Duplicate @@ -114,10 +111,7 @@ Help This Window - - -
    - + @@ -128,6 +122,7 @@ + @@ -140,12 +135,12 @@
    Resources In-App Options

    diff --git a/web/kiri/lang/en.js b/web/kiri/lang/en.js index 82547eb2..4016cfe4 100644 --- a/web/kiri/lang/en.js +++ b/web/kiri/lang/en.js @@ -2,11 +2,76 @@ // map for any missing key/value pairs kiri.lang['en'] = kiri.lang['en-us'] = { - version: "version", + // common keys and menus + animate: "animate", // CAM animate button + arrange: "arrange", // layout workspace objects + axis: "axis", // left object scale pop menu + clear: "clear", // clear workspace (remove all objects) + copy: "copy", + delete: "delete", + detail: "detail", + done: "done", enable: "enable", + export: "export", + files: "files", + help: "help", + ghost: "ghost", // left render pop menu (wireframe) + hide: "hide", // left render pop menu (invisible) + home: "home", + import: "import", + language: "language", + machine: "machine", // device or machine + metric: "metric", + name: "name", + prefs: "prefs", // left menu "preferences" + preview: "preview", + recent: "recent", + render: "render", // left render pop menu + reset: "reset", + rotate: "rotate", // left rotate pop menu + save: "save", + scale: "scale", // left object scale pop menu + setup: "setup", settings: "settings", + size: "size", + slice: "slice", + solid: "solid", // view type pop menu + start: "start", + tool: "tool", + tools: "tools", // CAM tool menu button + top: "top", + type: "type", // CAM tool type + version: "version", + view: "view", // left view pop menu + wire: "wire", // left render pop menu - acct_xpo: "make a backup of your device\nand device profiles with the\noption to include workspace\nobjects and positions", + acct_xpo: ["make a backup of your device","and device profiles with the","option to include workspace","objects and positions"], + + // RIGHT-CLICK CONTEXT MENU + rc_clws: "clear workspace", + rc_xpws: "export workspace", + rc_lafl: "lay flat", + rc_mirr: "mirror", + rc_dupl: "duplicate", + rc_xstl: "export as STL", + + // DEVICE MENU and related dialogs + dm_sldt: "select a device type", + dm_stdd: "standard devices", + dm_mydd: "my devices", + dm_seld: "selected device", + dm_rcnt: "recent files", + dm_savs: "saved settings", + dm_appp: "Application Preferences", + + // CAM Tool Dialog + td_tyem: "end", // end mill + td_tybm: "ball", // ball mill + td_tytm: "taper", // taper mill + td_tonm: "tool #", + td_shft: "shaft", // endmill shaft specs + td_flut: "flute", // endmill flute specs + td_tapr: "taper", // endmill taper specs // DEVICE dialog groups dv_gr_dev: "device", @@ -26,9 +91,9 @@ kiri.lang['en-us'] = { dv_bedd_s: "depth", dv_bedd_l: "workspace units", dv_bedh_s: "height", - dv_bedh_l: "max build height\nin workspace units", + dv_bedh_l: ["max build height","in workspace units"], dv_spmx_s: "max spindle", - dv_spmx_l: "max spindle rpm speed\n0 to disable", + dv_spmx_l: ["max spindle rpm speed","0 to disable"], dv_xtab_s: "absolute positioning", dv_xtab_l: "extrusion moves absolute", dv_orgc_s: "origin center", @@ -39,16 +104,18 @@ kiri.lang['en-us'] = { dv_bedc_l: "device bed is circular", dv_belt_s: "belt bed", dv_belt_l: "continuous printing bed", + dv_retr_s: "firmware retract", + dv_retr_l: ["device fimware supports G10/G11"], dv_fanp_s: "fan power", dv_fanp_l: "set cooling fan power", dv_prog_s: "progress", dv_prog_l: "output on each % progress", dv_layr_s: "layer", - dv_layr_l: "output at each\nlayer change", + dv_layr_l: ["output at each","layer change"], dv_tksp_s: "token spacer", - dv_tksp_l: "add a space between\ngcode axis parameters\nG0X0Y0X0\nvs\nG0 X0 Y0 Z0", + dv_tksp_l: ["add a space between","gcode axis parameters","G0X0Y0X0","vs","G0 X0 Y0 Z0"], dv_strc_s: "strip comments", - dv_strc_l: "strip gcode comments\ncomments begin with ;", + dv_strc_l: ["strip gcode comments","comments begin with ;"], dv_fext_s: "file extension", dv_fext_l: "file name extension", dv_dwll_s: "dwell", @@ -68,7 +135,9 @@ kiri.lang['en-us'] = { dv_lzof_s: "laser off", dv_lzof_l: "gcode laser off script", dv_exts_s: "select", - dv_exts_l: "gcode to select this extruder", + dv_exts_l: "gcode run to enable this extruder", + dv_dext_s: "deselect", + dv_dext_l: "gcode run before enabling another extruder", dv_extd_s: "deselect", dv_extd_l: "gcode to deselect this extruder", dv_exox_s: "offset x", @@ -124,40 +193,48 @@ kiri.lang['en-us'] = { op_xprt_l: "show more settings options", op_decl_s: "decals", op_decl_l: "show device decals and logos", + op_dang_s: "experimental", + op_dang_l: "show experimental parameters", op_hopo_s: "hover pop", - op_hopo_l: "enable menu hover\nto activate", + op_hopo_l: ["enable menu hover","to activate"], op_dark_s: "dark mode", op_dark_l: "dark mode interface", op_comp_s: "compact ui", - op_comp_l: "compact user interface\nbetter for small screens\nand tablets", + op_comp_l: ["compact user interface","better for small screens","and tablets"], op_shor_s: "show origin", op_shor_l: "show device or process origin", op_shru_s: "show rulers", - op_shru_l: "show axes rulers\non major gridlines", + op_shru_l: ["show axes rulers","on major gridlines"], op_sped_s: "show speeds", - op_sped_l: "show speed to color bar\nin preview mode", + op_sped_l: ["show speed to color bar","in preview mode"], op_auto_s: "auto layout", - op_auto_l: "automatically layout platform\nwhen new items added", + op_auto_l: ["automatically layout platform","when new items added"], op_free_s: "free layout", - op_free_l: "permit dragable layout\nno effect in laser mode", + op_free_l: ["permit dragable layout","no effect in laser mode"], + op_spcr_s: "spacing", + op_spcr_l: ["spacing between objects","during auto layout","in workspace units"], + op_orth_s: "orthographic", + op_orth_l: ["orthographic display","requires page refresh"], op_invr_s: "invert zoom", - op_invr_l: "invert mouse wheel\nscroll zoom", + op_invr_l: ["invert mouse wheel","scroll zoom"], op_save_s: "auto save", - op_save_l: "preserve objects in workspace\nbetween application reloads", + op_save_l: ["preserve objects in workspace","between application reloads"], op_line_s: "line type", - op_line_l: "line style for path rendering\nimpacts 3d performance\npath: 3d best\nflat: 2d good\nline = 1d fast", + op_line_l: ["line style for path rendering","impacts 3d performance","path: 3d best","flat: 2d good","line = 1d fast"], op_unit_s: "units", - op_unit_l: "workspace units affects\nspeeds and distances", + op_unit_l: ["workspace units affects","speeds and distances"], op_anim_s: "animate", - op_anim_l: "animation mesh density\nhigher is more dense\ntakes more memory\nand is slower", + op_anim_l: ["animation mesh density","higher is more dense","takes more memory","and is slower"], lo_menu: "layout", pt_menu: "parts", pt_deci_s: "decimate", - pt_deci_l: "enable or disable point decimation\nduring port import. for faster slicing\nand lower memory usage", + pt_deci_l: ["enable or disable point decimation","during port import. for faster slicing","and lower memory usage"], pt_qual_s: "quality", - pt_qual_l: "level of detail to retain\nduring slicing operations\nlower is faster", + pt_qual_l: ["level of detail to retain","during slicing operations","lower is faster"], + pt_heal_s: "heal mesh", + pt_heal_l: ["attempt to heal","non-manifold meshes","adds to slicing time"], xp_menu: "exports", @@ -169,156 +246,164 @@ kiri.lang['en-us'] = { // FDM SLICING sl_menu: "layers", sl_lahi_s: "height", - sl_lahi_l: "height of each slice\nlayer in millimeters", + sl_lahi_l: ["height of each slice","layer in millimeters"], ad_minl_s: "height min", - ad_minl_l: "adaptive min layer height\nin millimeters\nmust be non-zero", + ad_minl_l: ["adaptive min layer height","in millimeters","must be non-zero"], sl_ltop_s: "top layers", - sl_ltop_l: "number of solid layers\nto enforce at the\ntop of the print", + sl_ltop_l: ["number of solid layers","to enforce at the","top of the print"], sl_lsld_s: "solid layers", - sl_lsld_l: "solid fill areas computed\nfrom layer deltas. see\nlayer pop menu", + sl_lsld_l: ["solid fill areas computed","from layer deltas. see","layer pop menu"], sl_lbot_s: "base layers", - sl_lbot_l: "number of solid layers\nto enforce at the\nbottom of the print", + sl_lbot_l: ["number of solid layers","to enforce at the","bottom of the print"], ad_adap_s: "adaptive", - ad_adap_l: "use adaptive layer heights\nwith 'layer height' as max\nand 'layer min' as the min", + ad_adap_l: ["use adaptive layer heights","with 'layer height' as max","and 'layer min' as the min"], // FDM SHELLS sl_shel_s: "shell count", - sl_shel_l: "number of perimeter\nwalls to generate", + sl_shel_l: ["number of perimeter","walls to generate"], sl_ordr_s: "shell order", - sl_ordr_l: "output shell order\ninside to outside\nor outside to inside\naffects surface quality", + sl_ordr_l: ["output shell order","inside to outside","or outside to inside","affects surface quality"], sl_strt_s: "layer start", - sl_strt_l: "layer starting point\nlast = last layer end\ncenter = part center\norigin = device origin", + sl_strt_l: ["layer starting point","last = last layer end","center = part center","origin = device origin"], ad_thin_s: "thin walls", - ad_thin_l: "detect and fill gaps\nbetween shell walls", + ad_thin_l: ["detect and fill gaps","between shell walls"], // FDM FILL fi_menu: "infill", - fi_type: "type", - fi_pcnt_s: "fraction", - fi_pcnt_l: "fill density values\n0.0 - 1.0", - fi_angl_s: "start angle", - fi_angl_l: "base angle in degrees", - fi_over_s: "overlap", - fi_over_l: "overlap with shell and fill\nas % of nozzle width\nhigher bonds better\n0.0 - 1.0", + fi_type: "fill type", + fi_pcnt_s: "fill fraction", + fi_pcnt_l: ["fill density values","0.0 - 1.0"], + fi_angl_s: "solid start", + fi_angl_l: ["starting angle in degrees","90 degrees added to ","each following layer","applies only to solid layers"], + fi_wdth_s: "solid width", + fi_wdth_l: ["line width for solid fill","as a fraction of nozzle width","values < 1 are more dense","good for surface finishes","0.0 - 1.0"], + fi_over_s: "shell overlap", + fi_over_l: ["overlap with shell and other fill","as fraction of nozzle diameter","0.0 - 2.0"], + // fi_rate_s: "print speed", + fi_rate_l: ["extrusion speed for infill","set to 0 to use default","output print speeds"], // FDM FIRST LAYER fl_menu: "base", fl_lahi_s: "layer height", - fl_lahi_l: "height of each slice\nin millimeters\nshould be >= slice height", + fl_lahi_l: ["height of each slice","in millimeters","should be >= slice height"], fl_rate_s: "shell speed", - fl_rate_l: "shell printing max speed\nin millimeters / minute", + fl_rate_l: ["shell printing max speed","in millimeters / minute"], fl_frat_s: "fill speed", - fl_frat_l: "fill printing max speed\nin millimeters / minute", + fl_frat_l: ["fill printing max speed","in millimeters / minute"], fl_mult_s: "flow factor", - fl_mult_l: "extrusion multiplier\n0.0 - 2.0", + fl_mult_l: ["extrusion multiplier","0.0 - 2.0"], fl_sfac_s: "width factor", - fl_sfac_l: "multiplier on nozzle size\nchanges line spacing", + fl_sfac_l: ["multiplier on nozzle size","changes line spacing"], fl_skrt_s: "skirt count", - fl_skrt_l: "number of first-layer offset\nbrims to generate", + fl_skrt_l: ["number of first-layer offset","brims to generate"], fl_skro_s: "skirt offset", - fl_skro_l: "skirt offset from part\nin millimeters", + fl_skro_l: ["skirt offset from part","in millimeters"], fl_nozl_s: "nozzle temp", - fl_nozl_l: "degrees in celsius\noutput setting used\nwhen this is zero", + fl_nozl_l: ["degrees in celsius","output setting used","when this is zero"], fl_bedd_s: "bed temp", - fl_bedd_l: "degrees in celsius\noutput setting used\nwhen this is zero", + fl_bedd_l: ["degrees in celsius","output setting used","when this is zero"], fr_spac_s: "raft gap", - fr_spac_l: "additional layer spacing\nbetween 1st layer and raft\nin millimeters", + fr_spac_l: ["additional layer spacing","between 1st layer and raft","in millimeters"], fr_nabl_s: "raft enable", - fr_nabl_l: "create a raft under the\nmodel for better adhesion\nuses skirt offset and\ndisables skirt output", + fr_nabl_l: ["create a raft under the","model for better adhesion","uses skirt offset and","disables skirt output"], // FDM BELT ONLY - fl_zoff_s: "layer offset", - fl_zoff_l: "height offset from belt\nof lowest extrusion\nin millimeters", + fl_zoff_s: "belt offset", + fl_zoff_l: ["height offset from belt","of lowest extrusion","in millimeters"], fl_brim_s: "brim size", - fl_brim_l: "add brim to part bottom\nsize is width in millimeters\n0 to disable", + fl_brim_l: ["add brim to part bottom","size is width in millimeters","0 to disable"], fl_brmn_s: "brim trigger", - fl_brmn_l: "add brims only when segment\nfacing belt is shorter than this\nvalue in millimeters\n0 = Infinity", + fl_brmn_l: ["add brims only when segment","facing belt is shorter than this","value in millimeters","0 = Infinity"], + fl_bled_s: "part anchor", + fl_bled_l: ["belt part anchor","at start of print","in millimeters"], // FDM SUPPORT sp_menu: "support", sp_detect: "detect", sp_dens_s: "density", - sp_dens_l: "fraction 0.0 - 1.0\nrecommended 0.15\n0 to disable", + sp_dens_l: ["fraction 0.0 - 1.0","recommended 0.15","0 to disable"], sp_size_s: "pillar size", - sp_size_l: "pillar width\nin millimeters", + sp_size_l: ["pillar width","in millimeters"], sp_offs_s: "part offset", - sp_offs_l: "offset from part\nin millimeters", + sp_offs_l: ["offset from part","in millimeters"], sp_gaps_s: "gap layers", - sp_gaps_l: "number of layers\noffset from part", + sp_gaps_l: ["number of layers","offset from part"], sp_span_s: "max span", - sp_span_l: "unsupported span that causes\na new support to be generated\nin millimeters", + sp_span_l: ["unsupported span that causes","a new support to be generated","in millimeters"], sp_angl_s: "max angle", - sp_angl_l: "max overhang angle before\nsupporting pillar is generated", + sp_angl_l: ["max overhang angle before","supporting pillar is generated"], sp_area_s: "min area", - sp_area_l: "minimum area for\na support column\nin millimeters", + sp_area_l: ["minimum area for","a support column","in millimeters"], sp_xpnd_s: "expand", - sp_xpnd_l: "expand support area\nbeyond part boundary\nin millimeters", + sp_xpnd_l: ["expand support area","beyond part boundary","in millimeters"], sp_nozl_s: "extruder", - sp_nozl_l: "in multi-extruder systems\nthe extruder to use for\nsupport material", + sp_nozl_l: ["in multi-extruder systems","the extruder to use for","support material"], sp_auto_s: "automatic", - sp_auto_l: "enable generated supports\nusing at-slice-time geometry\nsupports will only appear\nafter slicing completes", + sp_auto_l: ["enable generated supports","using at-slice-time geometry","supports will only appear","after slicing completes"], // LASER SLICING ls_offs_s: "offset", - ls_offs_l: "adjust for beam width\nin millimeters", + ls_offs_l: ["adjust for beam width","in millimeters"], ls_lahi_s: "height", - ls_lahi_l: "layer height\nin millimeters\n0 = auto/detect", + ls_lahi_l: ["layer height","in millimeters","0 = auto/detect"], ls_lahm_s: "min", - ls_lahm_l: "layer height minimum\nwill merge auto slices\nunder this thickness\nin millimeters", + ls_lahm_l: ["layer height minimum","will merge auto slices","under this thickness","in millimeters"], ls_sngl_s: "single", - ls_sngl_l: "perform only one slice\nat specified layer height", + ls_sngl_l: ["perform only one slice","at specified layer height"], // CNC COMMON terms cc_tool: "tool", cc_offs_s: "offset", - cc_offs_l: "offset tool center\nfrom chosen path", + cc_offs_l: ["offset tool center","from chosen path"], cc_spnd_s: "spindle rpm", - cc_spnd_l: "spindle speed in\nrevolutions / minute", + cc_spnd_l: ["spindle speed in","revolutions / minute"], cc_sovr_s: "step over", - cc_sovr_l: "as a fraction of\ntool diameter", + cc_sovr_l: ["as a fraction of","tool diameter"], cc_sdwn_s: "step down", - cc_sdwn_l: "step down depth\nfor each pass\nin workspace units\n0 to disable", + cc_sdwn_l: ["step down depth","for each pass","in workspace units","0 to disable"], cc_feed_s: "feed rate", - cc_feed_l: "max cutting speed in\nworkspace units / minute", + cc_feed_l: ["max cutting speed in","workspace units / minute"], cc_plng_s: "plunge rate", - cc_plng_l: "max z axis speed in\nworkspace units / minute", + cc_plng_l: ["max z axis speed in","workspace units / minute"], + cc_sngl_s: "select lines only", + cc_sngl_l: ["select only single edges","instead of connected polylines"], // CNC COMMON cc_menu: "limits", cc_flip: "flip", cc_rapd_s: "xy feed", - cc_rapd_l: "max xy moves feedrate\nin workspace units / minute", + cc_rapd_l: ["max xy moves feedrate","in workspace units / minute"], cc_rzpd_s: "z feed", - cc_rzpd_l: "max z moves feedrate\nin workspace units / minute", + cc_rzpd_l: ["max z moves feedrate","in workspace units / minute"], cc_loff_s: "offset", - cc_loff_l: "distance from stock face\nfor leveling pass\nin workspace units", + cc_loff_l: ["distance from stock face","for leveling pass","in workspace units"], // CNC ROUGHING cr_menu: "rough", cr_lsto_s: "leave stock", - cr_lsto_l: "horizontal offset from vertical faces\nstock to leave for finishing pass\nin workspace units", + cr_lsto_l: ["horizontal offset from vertical faces","stock to leave for finishing pass","in workspace units"], cr_ease_s: "ease down", - cr_ease_l: "plunge cuts will\nspiral down or ease\nalong a linear path", + cr_ease_l: ["plunge cuts will","spiral down or ease","along a linear path"], cr_clrt_s: "clear top", - cr_clrt_l: "run a clearing pass over\nthe bounding area of the part\nat z = 0", + cr_clrt_l: ["run a clearing pass over","the bounding area of the part","at z = 0"], cr_clrp_s: "clear voids", - cr_clrp_l: "mill out through pockets\ninstead of just the outline", + cr_clrp_l: ["mill out through pockets","instead of just the outline"], cr_clrf_s: "clear faces", - cr_clrf_l: "interpolate step down to\nclear any detected flat areas", + cr_clrf_l: ["interpolate step down to","clear any detected flat areas"], cr_olin_s: "inside only", - cr_olin_l: "limit cutting to\ninside part boundaries", + cr_olin_l: ["limit cutting to","inside part boundaries"], // CNC OUTLINE co_menu: "outline", co_dogb_s: "dogbones", - co_dogb_l: "insert dogbone cuts\ninto inside corners", + co_dogb_l: ["insert dogbone cuts","into inside corners"], co_wide_s: "wide cutout", - co_wide_l: "widen outside cutout paths\nfor deep cuts in hard material", + co_wide_l: ["widen outside cutout paths","for deep cuts in hard material"], co_olin_s: "inside only", - co_olin_l: "limit cutting to\ninside part boundaries", + co_olin_l: ["limit cutting to","inside part boundaries"], co_olot_s: "outside only", - co_olot_l: "limit cutting to\nexterior part boundaries\nwhich can be thought of\nas the shadow outline", + co_olot_l: ["limit cutting to","exterior part boundaries","which can be thought of","as the shadow outline"], co_omit_s: "omit through", co_omit_l: "eliminate thru holes", co_olen_s: "enable", @@ -327,11 +412,11 @@ kiri.lang['en-us'] = { // CNC CONTOUR cn_menu: "contour", cf_angl_s: "max angle", - cf_angl_l: "angles greater than this\nare considered vertical", + cf_angl_l: ["angles greater than this","are considered vertical"], cf_curv_s: "curves only", - cf_curv_l: "limit linear cleanup\nto curved surfaces", + cf_curv_l: ["limit linear cleanup","to curved surfaces"], cf_olin_s: "inside only", - cf_olin_l: "limit cutting to\ninside part boundaries", + cf_olin_l: ["limit cutting to","inside part boundaries"], cf_linx_s: "enable y pass", cf_linx_l: "linear y-axis finishing", cf_liny_s: "enable x pass", @@ -340,37 +425,37 @@ kiri.lang['en-us'] = { // CNC TRACE cu_menu: "trace", cu_type_s: "type", - cu_type_l: "follow = tool tip follows line\nright or left = tool tip\nfollows line offset by tool radius", + cu_type_l: ["follow = tool tip follows line","right or left = tool tip","follows line offset by tool radius"], // CNC DRILLING cd_menu: "drill", cd_axis: "axis", cd_points: "points", cd_plpr_s: "plunge per", - cd_plpr_l: "max plunge between\ndwell periods\nin workspace units\n0 to disable", + cd_plpr_l: ["max plunge between","dwell periods","in workspace units","0 to disable"], cd_dwll_s: "dwell time", - cd_dwll_l: "dwell time\nbetween plunges in\nin milliseconds", + cd_dwll_l: ["dwell time","between plunges in","in milliseconds"], cd_lift_s: "drill lift", - cd_lift_l: "lift between plunges\nafter dwell period\nin workspace units\n0 to disable", + cd_lift_l: ["lift between plunges","after dwell period","in workspace units","0 to disable"], cd_regi_s: "register", - cd_regi_l: "drill registration holes\nfor double-sided parts\nindependent of enable\ndrilling but uses same\ntool and settings", + cd_regi_l: ["drill registration holes","for double-sided parts","independent of enable","drilling but uses same","tool and settings"], // CNC CUTOUT TABS ct_menu: "tabs", ct_angl_s: "angle", - ct_angl_l: "starting angle for tab spacing\nin degrees (0-360)", + ct_angl_l: ["starting angle for tab spacing","in degrees (0-360)"], ct_numb_s: "count", - ct_numb_l: "number of tabs to use\nwill be spaced evenly\naround the part", + ct_numb_l: ["number of tabs to use","will be spaced evenly","around the part"], ct_wdth_s: "width", ct_wdth_l: "width in workspace units", ct_hght_s: "height", ct_hght_l: "height in workspace units", ct_dpth_s: "depth", - ct_dpth_l: "distance in workspace units\nthat the tab projects from\nthe part surface", + ct_dpth_l: ["distance in workspace units","that the tab projects from","the part surface"], ct_midl_s: "midline", - ct_midl_l: "use midline of tab\ninstead of z bottom\nfor double-sided work", + ct_midl_l: ["use midline of tab","instead of z bottom","for double-sided work"], ct_nabl_s: "auto", - ct_nabl_l: "auto generate radial tabs\nprojected from part center\nusing count and angle offset", + ct_nabl_l: ["auto generate radial tabs","projected from part center","using count and angle offset"], // OUTPUT ou_menu: "output", @@ -378,31 +463,31 @@ kiri.lang['en-us'] = { // LASER KNIFE dk_menu: "knife", dk_dpth_s: "cut depth", - dk_dpth_l: "final cut depth\nin millimeters", + dk_dpth_l: ["final cut depth","in millimeters"], dk_pass_s: "cut passes", - dk_pass_l: "number of passes\ndown to cut depth", + dk_pass_l: ["number of passes","down to cut depth"], dk_offs_s: "tip offset", - dk_offs_l: "distance from blade tip\nto center of tool\nin millimeters", + dk_offs_l: ["distance from blade tip","to center of tool","in millimeters"], // OUTPUT LASER ou_spac_s: "spacing", - ou_spac_l: "distance between layer output\nin millimeters", + ou_spac_l: ["distance between layer output","in millimeters"], ou_scal_s: "scaling", ou_scal_l: "multiplier (0.1 to 100)", ou_powr_s: "power", - ou_powr_l: "0 - 100\nrepresents %", + ou_powr_l: ["0 - 100","represents %"], ou_sped_s: "speed", ou_sped_l: "millimeters / minute", ou_mrgd_s: "merged", - ou_mrgd_l: "merge all layers using\ncolor coding to denote\nstacking depth", + ou_mrgd_l: ["merge all layers using","color coding to denote","stacking depth"], ou_grpd_s: "grouped", - ou_grpd_l: "retain each layer as\na unified grouping\ninstead of separated\npolygons", + ou_grpd_l: ["retain each layer as","a unified grouping","instead of separated","polygons"], ou_layr_s: "layer order", - ou_layr_l: "output layer order\nfrom top right to\nbottom left", + ou_layr_l: ["output layer order","from top right to","bottom left"], ou_layo_s: "layer color", - ou_layo_l: "output layer colors\nfor each z index\noverridden by merged", + ou_layo_l: ["output layer colors","for each z index","overridden by merged"], ou_drkn_s: "drag knife", - ou_drkn_l: "enable drag knife\noutput in gcode\ncut radii are added\nto corners with\ncut down passes", + ou_drkn_l: ["enable drag knife","output in gcode","cut radii are added","to corners with","cut down passes"], // OUTPUT FDM ou_nozl_s: "nozzle temp", @@ -410,54 +495,54 @@ kiri.lang['en-us'] = { ou_bedd_s: "bed temp", ou_bedd_l: "degrees in celsius", ou_feed_s: "print speed", - ou_feed_l: "max print speed\nmillimeters / minute", + ou_feed_l: ["max print speed","millimeters / minute"], ou_fini_s: "finish speed", - ou_fini_l: "outermost shell speed\nmillimeters / minute", + ou_fini_l: ["outermost shell speed","millimeters / minute"], ou_move_s: "move speed", - ou_move_l: "non-print move speed\nmillimeters / minute\n0 = enable G0 moves", + ou_move_l: ["non-print move speed","millimeters / minute","0 = enable G0 moves"], ou_shml_s: "shell factor", ou_flml_s: "solid factor", ou_spml_s: "infill factor", - ou_exml_l: "extrusion multiplier\n0.0 - 2.0", - ou_fanl_s: "fan layer", - ou_fanl_l: "layer to enable fan", + ou_exml_l: ["extrusion multiplier","0.0 - 2.0"], + ou_fans_s: "fan speed", + ou_fans_l: "0 - 255", // OUTPUT CAM ou_toll_s: "tolerance", - ou_toll_l: "surface precision\nin workspace units\nlower is slower and\nuses more memory\n0 = automatic based\non animate preference", + ou_toll_l: ["surface precision","in workspace units","lower is slower and","uses more memory","0 = automatic based","on animate preference"], ou_zanc_s: "z anchor", - ou_zanc_l: "controls the position of the part\nwhen stock Z exceeds part Z", + ou_zanc_l: ["controls the position of the part","when stock Z exceeds part Z"], ou_ztof_s: "z offset", - ou_ztof_l: "offset z anchor\nin workspace units\nhas no effect when\nanchor is middle", + ou_ztof_l: ["offset z anchor","in workspace units","has no effect when","anchor is middle"], ou_zbot_s: "z bottom", - ou_zbot_l: "offset from part bottom\nto limit cutting depth\nin workspace units", + ou_zbot_l: ["offset from part bottom","to limit cutting depth","in workspace units"], ou_zclr_s: "z clearance", - ou_zclr_l: "safe travel offset\nfrom top of part\nin workspace units", + ou_zclr_l: ["safe travel offset","from top of part","in workspace units"], ou_ztru_s: "z thru", - ou_ztru_l: "extend cutout pass down\nin workspace units", + ou_ztru_l: ["extend cutout pass down","in workspace units"], ou_conv_s: "conventional", - ou_conv_l: "milling direction\nuncheck for 'climb'", + ou_conv_l: ["milling direction","uncheck for 'climb'"], ou_depf_s: "depth first", - ou_depf_l: "optimize pocket cuts\nwith depth priority", + ou_depf_l: ["optimize pocket cuts","with depth priority"], // CAM STOCK cs_menu: "stock", cs_wdth_s: "width", - cs_wdth_l: "width (x) in workspace units\n0 defaults to part size", + cs_wdth_l: ["width (x) in workspace units","0 defaults to part size"], cs_dpth_s: "depth", - cs_dpth_l: "depth (y) in workspace units\n0 defaults to part size", + cs_dpth_l: ["depth (y) in workspace units","0 defaults to part size"], cs_hght_s: "height", - cs_hght_l: "height (z) in workspace units\n0 defaults to part size", + cs_hght_l: ["height (z) in workspace units","0 defaults to part size"], cs_offs_s: "offset", - cs_offs_l: "use width, depth, height\nas offsets from max\npart size on platform", + cs_offs_l: ["use width, depth, height","as offsets from max","part size on platform"], cs_clip_s: "clip to", - cs_clip_l: "rough and outline\nclip cutting paths\nto defined stock", + cs_clip_l: ["rough and outline","clip cutting paths","to defined stock"], cs_offe_s: "enable", cs_offe_l: "enable milling stock", // ORIGIN (CAM & LASER) or_bnds_s: "origin bounds", - or_bnds_l: "origin is relative to\nboundary of all objects", + or_bnds_l: ["origin is relative to","boundary of all objects"], or_cntr_s: "origin center", or_cntr_l: "origin is referenced from the center", or_topp_s: "origin top", @@ -466,94 +551,100 @@ kiri.lang['en-us'] = { // FDM ADVANCED ad_menu: "expert", ad_rdst_s: "retract dist", - ad_rdst_l: "amount to retract filament\nfor long moves. in millimeters", + ad_rdst_l: ["amount to retract filament","for long moves. in millimeters"], ad_rrat_s: "retract rate", - ad_rrat_l: "speed of filament\nretraction in mm/s", + ad_rrat_l: ["speed of filament","retraction in mm/s"], ad_rdwl_s: "engage dwell", - ad_rdwl_l: "time between re-engaging\nfilament and movement\nin milliseconds", + ad_wpln_s: "retract wipe", + ad_wpln_l: ["non-printing move","after retraction","in millimeters"], + ad_rdwl_l: ["time between re-engaging","filament and movement","in milliseconds"], ad_scst_s: "shell coast", - ad_scst_l: "non-printing end\nof perimeter shells\nin millimeters", + ad_scst_l: ["non-printing end","of perimeter shells","in millimeters"], ad_msol_s: "min solid", - ad_msol_l: "minimum area (mm^2)\nrequired to keep solid\nmust be > 0.1", + ad_msol_l: ["minimum area (mm^2)","required to keep solid","must be > 0.1"], ad_mins_s: "min speed", - ad_mins_l: "minimum speed\nfor short segments", - ad_spol_s: "slow poly", - ad_spol_l: "polygons shorter than this\nwill have their print speed\nscaled down to min speed\nin millimeters", + ad_mins_l: ["minimum speed","for short segments"], + ad_spol_s: "short path", + ad_spol_l: ["polygons shorter than this","will have their print speed","scaled down to min speed","in millimeters"], + ad_arct_s: "arc tolerance", + ad_arct_l: ["convert faceted lines to arcs","center point drift tolerance","when matching arc points","consider values around 0.15","in millimeters","0 to disable"], ad_zhop_s: "z hop dist", - ad_zhop_l: "amount to raise z\non retraction moves\nin millimeters\n0 to disable", + ad_zhop_l: ["amount to raise z","on retraction moves","in millimeters","0 to disable"], ad_abkl_s: "anti-backlash", - ad_abkl_l: "use micro-movements to cancel\nbacklash during fills\nin millimeters", + ad_abkl_l: ["for better flat surface finish","use micro-movements to cancel","backlash in solid layer output","in millimeters","0 to disable","if your firmware has M425","put that in the gcode header","and leave this as 0"], ad_lret_s: "layer retract", - ad_lret_l: "force filament retraction\nbetween layers", + ad_lret_l: ["force filament retraction","between layers"], ad_play_s: "polish layers", - ad_play_l: "polish up to specified\n# of layers at a time", + ad_play_l: ["polish up to specified","# of layers at a time"], ad_pspd_s: "polish speed", - ad_pspd_l: "polishing speed\nin millimeters / minute", + ad_pspd_l: ["polishing speed","in millimeters / minute"], // CAM EXPERT cx_fast_s: "skip shadow", - cx_fast_l: "disable overhang detection\ncan be faster and use less\nmemory with complex models\nbut fails with overhangs\ntry enabling if slicing\nhangs during shadowing", + cx_fast_l: ["disable overhang detection","can be faster and use less","memory with complex models","but fails with overhangs","try enabling if slicing","hangs during shadowing"], // FDM GCODE ag_menu: "gcode", ag_nozl_s: "nozzle", ag_nozl_l: "select output nozzle or head", + ag_peel_s: "peel guard", + ag_peel_l: ["starting at this belt z positoin","periodically roll the print off and","back on to the belt to unstick it","and prevent rolling deflection"], ag_paws_s: "pause layers", - ag_paws_l: "comma-separated list of layers\nto inject pause commands before", + ag_paws_l: ["comma-separated list of layers","to inject pause commands before"], ag_loop_s: "loop layers", - ag_loop_l: "layer ranges to repeat in the format\nfirst-last-count,first-last-count,...\nomitted count = 1", + ag_loop_l: ["layer ranges to repeat in the format","first-last-count,first-last-count,...","omitted count = 1"], // SLA MENU sa_menu: "slicing", sa_lahe_s: "layer height", - sa_lahe_l: "layer height\nin millimeters", + sa_lahe_l: ["layer height","in millimeters"], sa_shel_s: "hollow shell", - sa_shel_l: "shell thickness in mm\nuse multiple of layer height\nuse 0 for solid (disabled)", + sa_shel_l: ["shell thickness in mm","use multiple of layer height","use 0 for solid (disabled)"], sa_otop_s: "open top", - sa_otop_l: "if shell is enabled\nresults in an open top", + sa_otop_l: ["if shell is enabled","results in an open top"], sa_obas_s: "open base", - sa_obas_l: "if shell is enabled\nresults in an open base\ndisabled with supports", + sa_obas_l: ["if shell is enabled","results in an open base","disabled with supports"], sa_layr_m: "layers", sa_lton_s: "light on time", - sa_lton_l: "layer light on\ntime in seconds", + sa_lton_l: ["layer light on","time in seconds"], sa_ltof_s: "light off time", - sa_ltof_l: "layer light off\ntime in seconds", + sa_ltof_l: ["layer light off","time in seconds"], sa_pldi_s: "peel distance", - sa_pldi_l: "peel distance\nin millimeters", + sa_pldi_l: ["peel distance","in millimeters"], sa_pllr_s: "peel lift rate", - sa_pllr_l: "peel lift speed\nin mm/sec", + sa_pllr_l: ["peel lift speed","in mm/sec"], sa_pldr_s: "peel drop rate", - sa_pldr_l: "peel drop speed\nin mm/sec", + sa_pldr_l: ["peel drop speed","in mm/sec"], sa_base_m: "base", sa_balc_s: "layer count", - sa_balc_l: "number of\nbase layers", - sa_bltn_l: "base layer light on\ntime in seconds", - sa_bltf_l: "base layer light off\ntime in seconds", + sa_balc_l: ["number of","base layers"], + sa_bltn_l: ["base layer light on","time in seconds"], + sa_bltf_l: ["base layer light off","time in seconds"], sa_infl_m: "infill", sa_ifdn_s: "density", - sa_ifdn_l: "percent infill\nrequires shell\n0 = disabled\nvalid 0.0 - 1.0", + sa_ifdn_l: ["percent infill","requires shell","0 = disabled","valid 0.0 - 1.0"], sa_iflw_s: "line width", - sa_iflw_l: "hatch line width\nin millimeters", + sa_iflw_l: ["hatch line width","in millimeters"], sa_supp_m: "support", sa_slyr_s: "base layers", - sa_slyr_l: "base support layers\nvalue range 0-10", + sa_slyr_l: ["base support layers","value range 0-10"], sa_slgp_s: "gap layers", - sa_slgp_l: "number of layers between\nraft and bottom of object", + sa_slgp_l: ["number of layers between","raft and bottom of object"], sa_sldn_s: "density", - sa_sldn_l: "used to compute the\nnumber of support pillars\n0.0-1.0 (0 = disable)", + sa_sldn_l: ["used to compute the","number of support pillars","0.0-1.0 (0 = disable)"], sa_slsz_s: "size", - sa_slsz_l: "max size of a\nsupport pillar\nin millimeters", + sa_slsz_l: ["max size of a","support pillar","in millimeters"], sa_slpt_s: "points", - sa_slpt_l: "number of points in\neach support pillar\nin millimeters", + sa_slpt_l: ["number of points in","each support pillar","in millimeters"], sl_slen_l: "enable supports", sa_outp_m: "output", sa_opzo_s: "z offset", - sa_opzo_l: "z layer offset\nalmost always 0.0\n0.0-1.0 in millimeters", + sa_opzo_l: ["z layer offset","almost always 0.0","0.0-1.0 in millimeters"], sa_opaa_s: "anti alias", - sa_opaa_l: "enable anti-aliasing\nproduces larger files\ncan blur details" + sa_opaa_l: ["enable anti-aliasing","produces larger files","can blur details"] }; diff --git a/web/kiri/lang/fr-fr-help.html b/web/kiri/lang/fr-fr-help.html new file mode 120000 index 00000000..1cabd5c2 --- /dev/null +++ b/web/kiri/lang/fr-fr-help.html @@ -0,0 +1 @@ +fr-help.html \ No newline at end of file diff --git a/web/kiri/lang/fr-fr.js b/web/kiri/lang/fr-fr.js new file mode 120000 index 00000000..b0fd9cf9 --- /dev/null +++ b/web/kiri/lang/fr-fr.js @@ -0,0 +1 @@ +fr.js \ No newline at end of file diff --git a/web/kiri/lang/fr-help.html b/web/kiri/lang/fr-help.html new file mode 100644 index 00000000..60351e17 --- /dev/null +++ b/web/kiri/lang/fr-help.html @@ -0,0 +1,176 @@ +

    + +
    + + +
    + +
    + +
    +
    A propos
    +
    débuter
    +
    contrôles
    +
    support
    +
    projet
    +
    + +
    +
    +
    +

    + Kiri:Moto est un logiciel de découpe unique, fonctionnant dans un navigateur, entièrement open source, qui + transforme les modèles 3d en instructions de machine pour des imprimantes 3D, des CNC, + et des graveurs laser. +


    + Le fait d'être basé sur un navigateur signifie que tout le processus de découpe a lieu dans le navigateur local, + et non dans le nuage. Vos données et modèles restent totalement privés. +


    + Cela signifie que Kiri se comporte comme une application native de votre ordinateur avec en plus + la sécurité de l'exécution dans le bac à sable de sécurité du navigateur. +


    + L'autre avantage de fonctionner à partir du web est que Kiri est toujours à jour. Les nouvelles + fonctionnalités, les corrections de bogues et les mises à jour sont fréquemment mises en place. Si vous souhaitez exécuter une ancienne + ou une version de développement de Kiri, sélectionnez cette version dans le menu utilisateur en haut à droite. +

    +
    +
    + Kiri vient de Kiri-e qui est l'art Japonais du découpage de papiers + Moto est un raccourci pour "Modeling Tool" (Outil de modélisation) +
    + +
    + Guide de démarrage rapide +
    +

    +

  • visionner The Definitive Video Guide to Kiri:Moto 2.5
  • +
  • le menu setup sélectionne le type de matériel et un appareil
  • +
  • les préférences permettent de régler l'interface et les caractéristiques de l'appareil
  • +
  • personnaliser un appareil si vous souhaitez modifier les paramètres par défaut
  • +
  • ouvre un fichier ou importe des pièces dans l'espace de travail
  • +
  • utiliser les menus de droite pour personnaliser les paramètres de découpage
  • +
  • découper les objets pour voir l'effet des paramètres
  • +
  • la prévisualisation calcule les données de cheminement des objets tranchés
  • +
  • l'exportation convertit les chemins d'accès aux fichiers GCode et SVG
  • +

    +
    +
    + Modes de fonctionnement +

    Chaque mode (FDM, CAM, Laser, SLA) a des caractéristiques et des paramètres différents. + Explorez les vidéos Youtube pour en savoir plus.

    +
    +
    + +
    about 3d printing
    +
    about cnc milling
    +
    about laser cutting
    + +
    +
    +


    Mouse Controls
    ComboAction
    Rotate
    Zoom
    Zoom
    dragPan
    Objects
    Align face to bed
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Contrôles via le clavier
    ToucheAction
    Configuration
    Sélection de l'appareil
    CAM Sélection des outils
    Grid Local Devices
    Panneau des préférence
    Profils de travail
    Mode
    Organiser
    Découper
    Prévisualiser
    Animer
    Exporter
    Fichier
    Importer
    Récent
    Voir
    Home
    Top
    Avant
    Arrière
    Réinitialiser la vue
    Alterner les vues de côtés
    Focus (organiser)
    Couche (découper/prévisualiser)
    Objets
    Dupliquer
    Miroir
    Pivoter/tourner
    Supprimer
    Tounrer de 90°
    Tourner de 5°
    Déplacer de 5mm
    Tout sélectionner
    Aide
    Cette fenêtre


    Contrôles via la souris
    ComboAction
    View
    moletteChanger de couche
    déplacerPan
    Pan
    Tourner
    Zoom
    Zoom
    déplacerPan
    Objets
    Aligner la face sur le plateau
    Aligner la face sur le plateau
    + + + +
    + Ressources + + Options complémentaires +

    + Dans le menu utilsateur en haut à droite, + utiliser l'option export pour créer unecopie téléchargeable des + profils du matériel et (optionellement) de l'espace de travail. Ce fichier pourra ensuite + être utilisé pour contrôler et rétablir un instantané de votre installation à tout moment. + Il peut également être partagé afin que les développeurs et autres utilisateurs puissent + reproduire votre environnement pour aider au débogage. +

    +
    + +
    + Open Source +

    + Kiri:Moto est totalement open-source. + Consultez la page du projet Github. Il existe des projets complémentaires, tant sur le plan matériel que logiciel. + Les nouveaux développeurs et les contributions au code sont les bienvenus à tout moment. La licence du code est celle du MIT. +


    + Toute donation est également bienvenue. +

    +
    Intégrations +

    Kiri:Moto est également intégré dans plusieurs autres applications web

    +
  • Onshape via son magasin d'Applications
  • +
  • Thingiverse en tant que native Thing Apps
  • +
  • SimplyPrint.io as the primary slicer under the covers
  • +
  • CAMLab est une branche d'une version antérieure de Kiri:Moto
  • +
    + + + + + diff --git a/web/kiri/lang/fr.js b/web/kiri/lang/fr.js new file mode 100644 index 00000000..72f1ade3 --- /dev/null +++ b/web/kiri/lang/fr.js @@ -0,0 +1,642 @@ +// French localization for Kiri:Moto +// Will defer to English map for any missing key/value pairs +kiri.lang['fr'] = +kiri.lang['fr-fr'] = { + // common keys and menus + animate: "animer", // CAM animate button + arrange: "organiser", // layout workspace objects + axis: "axe", // left object scale pop menu + clear: "effacer", // clear workspace (remove all objects) + copy: "copier", + delete: "supprimer", + detail: "détail", + done: "terminé", + enable: "permettre", + export: "exporter", + files: "fichiers", + help: "aide", + ghost: "translucide", // left render pop menu (wireframe) + hide: "cacher", // left render pop menu (invisible) + home: "position d'origine", + import: "importer", + language: "langue", + machine: "machine", // device or machine + metric: "métrique", + name: "nom", + prefs: "préfs", // left menu "preferences" + preview: "prévisualiser", + recent: "récent", + render: "rendu", // left render pop menu + reset: "remettre", + rotate: "tourner", // left rotate pop menu + save: "sauvegarder", + scale: "échelle", // left object scale pop menu + setup: "setup", + settings: "paramètres", + size: "taille", + solid: "solide", // view type pop menu + tool: "outil", + tools: "outils", // CAM tool menu button + top: "haut", + type: "genre", // CAM tool type + version: "version", + view: "vue", // left view pop menu + wire: "filaire", // left render pop menu + + acct_xpo: ["faites une sauvegarde de votre machine","et des profils des machines avec","l'option d'inclure les","objets et positions d'environnement"], + + // RIGHT-CLICK CONTEXT MENU + rc_clws: "effacer l'environnement", + rc_xpws: "exporter l'environnement", + rc_lafl: "poser à plat", + rc_mirr: "refléter", + rc_dupl: "dupliquer", + rc_xstl: "exporter en STL", + + // DEVICE MENU and related dialogs + dm_sldt: "sélectionner un genre de machine", + dm_stdd: "machines standards", + dm_mydd: "mes machines", + dm_seld: "machine sélectionnée", + dm_rcnt: "fichiers récents", + dm_savs: "paramètres sauvegardés", + dm_appp: "Préférences d'Application", + + // CAM Tool Dialog + td_tyem: "plat", // end mill + td_tybm: "boule", // ball mill + td_tytm: "v forme", // taper mill + td_tonm: "outil #", + td_shft: "tige", // endmill shaft specs + td_flut: "flûte", // endmill flute specs + td_tapr: "cône", // endmill taper specs + + // DEVICE dialog groups + dv_gr_dev: "périphérique", + dv_gr_ext: "extrudeur", + dv_gr_out: "sortie", + dv_gr_gco: "macros gcode", + + // DEVICE dialog (_s = label, _l = hover help) + dv_name_s: "nom", + dv_name_l: "nom du matériel", + dv_fila_s: "filament", + dv_fila_l: "diamètre en millimètres", + dv_nozl_s: "buse", + dv_nozl_l: "diamètre en millimètres", + dv_bedw_s: "largeur", + dv_bedw_l: "unités de l'espace de travail", + dv_bedd_s: "profondeur", + dv_bedd_l: "unités de l'espace de travail", + dv_bedh_s: "hauteur", + dv_bedh_l: ["hauteur de construction maximale","en unités de l'espace de travail"], + dv_spmx_s: "max spindle", + dv_spmx_l: ["max spindle rpm speed","0 to disable"], + dv_xtab_s: "positionnement absolu", + dv_xtab_l: "déplacements extrusion absolu", + dv_orgc_s: "origine au centre", + dv_orgc_l: "origin du plateau au centre", + // dv_orgt_s: "origin top", + // dv_orgt_l: "part z origin top", + dv_bedc_s: "plateau circulaire", + dv_bedc_l: "le plateau du matériel est circulaire", + dv_belt_s: "plateau tapis-roulant", + dv_belt_l: "plateau à impression continue", + dv_retr_s: "rétraction firmware", + dv_retr_l: ["le firmware supporte G10/G11"], + dv_fanp_s: "refroidissement", + dv_fanp_l: "régler la puissance du ventilateur de refroidissement", + dv_prog_s: "progression", + dv_prog_l: "résultats de chaque % de progression", + dv_layr_s: "couche", + dv_layr_l: ["résultats de chaque","changement de couche"], + dv_tksp_s: "délimiteur d'instruction", + dv_tksp_l: ["ajouter un espace","entre les paramètres des axes","G0X0Y0X0","vs","G0 X0 Y0 Z0"], + dv_strc_s: "éliminer les commentaires", + dv_strc_l: ["éliminer les commentaires du gcode","les commentaires débutent par ;"], + dv_fext_s: "extension du fichier", + dv_fext_l: "extension du nom de fichier", + dv_dwll_s: "dwell", + dv_dwll_l: "script gcode dwell", + dv_tool_s: "changement d'outil", + dv_tool_l: "script de changement d'outil", + dv_sspd_s: "spindle speed", + dv_sspd_l: "set spindle speed", + dv_paus_s: "pause", + dv_paus_l: "script gcode de pause", + dv_head_s: "header", + dv_head_l: "script gcode de démarrage", + dv_foot_s: "footer", + dv_foot_l: "script gcode de fin", + dv_lzon_s: "laser on", + dv_lzon_l: "gcode laser on script", + dv_lzof_s: "laser off", + dv_lzof_l: "gcode laser off script", + dv_exts_s: "sélectionner", + dv_exts_l: "gcode pour activer cet extrudeur", + dv_dext_s: "désélectionner", + dv_dext_l: "gcode à lancer avant d'activer un autre extrudeur", + dv_extd_s: "désélectionner", + dv_extd_l: "gcode pour désactiver cet extrudeur", + dv_exox_s: "décalage x", + dv_exox_l: "décalage de buse en x", + dv_exoy_s: "décalage y", + dv_exoy_l: "décalage de buse en y", + + // MODE + mo_menu: "mode", + mo_fdmp: "Impression FDM", + mo_slap: "Impression SLA", + mo_lazr: "Découpe / gravure Laser", + mo_cncm: "Routeur CNC", + + // SETUP + su_menu: "installation", + su_devi: "Matériels", + su_tool: "Outils", + su_locl: "Local", + su_xprt: "Exporter", + su_help: "Aide", + + // LOAD + fe_menu: "fichier", + fn_recn: "Récents", + fn_impo: "Importer", + + // FUNCTION + fn_menu: "action", + fn_arra: "Organiser", + fn_slic: "Découper", + fn_prev: "Prévisualiser", + fn_expo: "Exporter", + + // VIEW + vu_menu: "vue", + vu_home: "Origine", + vu_rset: "RAZ", + vu_sptp: "Haut", + vu_spfr: "Avant", + vu_splt: "Gauche", + vu_sprt: "Droite", + + // WORKSPACE + ws_menu: "vue", + ws_save: "Sauvegarder", + ws_cler: "Effacer", + + // OPTIONS + op_menu: "interface", + op_disp: "affichage", + op_xprt_s: "expert", + op_xprt_l: "afficher plus d'options de paramétrage", + op_decl_s: "logos", + op_decl_l: "montrer les logos des appareils", + op_dang_s: "expérimental", + op_dang_l: "montrer les paramètres expérimentaux", + op_hopo_s: "survol du menu", + op_hopo_l: ["activer le survol du menu", "pour activer"], + op_dark_s: "mode sombre", + op_dark_l: "interface en mode sombre", + op_comp_s: "ui compacte", + op_comp_l: ["interface utilisateur compacte", "plus adapté aux petits écrans", "et tablettes"], + op_shor_s: "montrer l'origine", + op_shor_l: "indiquer l'origine du dispositif ou du processus", + op_shru_s: "afficher les règles", + op_shru_l: ["montrer les règles des axes", "sur le quadrillage"], + op_sped_s: "afficher les vitesses", + op_sped_l: ["afficher la vitesse en barre de couleur","en mode prévisualisation"], + op_auto_s: "disposition automatique", + op_auto_l: ["disposition automatique sur le plateau", "lorsque de nouveaux éléments sont ajoutés"], + op_free_s: "disposition libre", + op_free_l: ["autoriser la disposition déplaçable","sans effet en mode laser"], + op_spcr_s: "espacement", + op_spcr_l: ["espacement entre les objets", "avec la disposition automatique", "en unités d'espace de travail"], + op_invr_s: "inverser zoom", + op_invr_l: ["inverser la molette de la souris", "faire défiler le zoom"], + op_save_s: "auto enregt", + op_save_l: ["préserve les objets dans l'espace de travail","entre rechargement de l'application"], + op_line_s: "type de ligne", + op_line_l: ["style de ligne pour le rendu","impacte les performances 3d","chemin: 3d meilleur","plat: 2d bon","ligne = 1d rapide"], + op_unit_s: "unités", + op_unit_l: ["les unités d'espace de travail affectent","vitesses et distances"], + op_anim_s: "animer", + op_anim_l: ["densité du maillage de l'animation","plus haut = plus dense","nécessite plus de mémoire","et est plus lent"], + + lo_menu: "disposition", + + pt_menu: "pièces", + pt_deci_s: "compression", + pt_deci_l: ["activer / désactiver la compression","durant l'importation. Tranchage plus rapide","et usage réduit de la mémoire"], + pt_qual_s: "qualité", + pt_qual_l: ["niveau de détail à garder", "lors des opérations de tranchage", "plus bas = plus rapide"], + pt_heal_s: "correction de maillage", + pt_heal_l: ["tentative de correction", "des mailles non-fermées", "accroit le temps de tranchage"], + + xp_menu: "exports", + + // SETTINGS + se_menu: "profil", + se_load: "charger", + se_save: "sauvegarder", + + // FDM SLICING + sl_menu: "couches", + sl_lahi_s: "hauteur", + sl_lahi_l: ["hauteur de chaque couche", "couche en millimètres"], + ad_minl_s: "hauteur min", + ad_minl_l: ["hauteur de couche minimale adaptative", "en millimètres", "doit être non nulle"], + sl_ltop_s: "couches du haut", + sl_ltop_l: ["nombre de couches solides", "à appliquer en", "haut de l'impression"], + sl_lsld_s: "couches solides", + sl_lsld_l: ["zones de remplissage solide calculées", "à partir des deltas de couches. voir", "menu surgissant des couches"], + sl_lbot_s: "couches de base", + sl_lbot_l: ["nombre de couches solides", "à appliquer en", "bas de l'impression"], + ad_adap_s: "adaptive", + ad_adap_l: ["utiliser des hauteurs de couche adaptatives", "avec 'hauteur de couche' en maximum", "et 'hauteur min' en minimum"], + + // FDM SHELLS + sl_shel_s: "parois", + sl_shel_l: ["nombre de périmètre", "murs à générer"], + sl_ordr_s: "ordre des parois", + sl_ordr_l: ["ordre d'impression des parois","de l'intérieur vers l'extérieur", "ou de l'extérieur vers l'intérieur","affecte la qualité de surface"], + sl_strt_s: "début de couche", + sl_strt_l: ["point de départ de la couche", "dernier = fin de la couche", "centre = centre de la pièce", "origine = origine du dispositif"], + ad_thin_s: "parois fines", + ad_thin_l: ["détecte et comble les vides", "entre les parois de la coque"], + + // FDM FILL + fi_menu: "remplissage", + fi_type: "type", + fi_pcnt_s: "fraction de remplissage", + fi_pcnt_l: ["valeurs de densité de remplissage","0.0 - 1.0"], + fi_angl_s: "départ solide", + fi_angl_l: ["angle de départ en degrés", "90 degrés ajoutés à ", "chaque couche suivante", "s'applique uniquement aux couches solides"], + fi_over_s: "chevauchement", + fi_over_l: ["chevauchement de l'enveloppe et du remplissage", "en tant que fraction du diamètre de la buse", "0,0 - 1,0"], + // fi_rate_s: "vitesse d'impression", + fi_rate_l: ["vitesse d'extrusion du remplissage", "régler à 0 pour utiliser par défaut", "la vitesse d'impression"], + + // FDM FIRST LAYER + fl_menu: "base", + fl_lahi_s: "hauteur de couche", + fl_lahi_l: ["hauteur de chaque couche", "en millimètres", "devrait être >= hauteur de la couche"], + fl_rate_s: "vitesse périmètres", + fl_rate_l: ["vitesse maximale d'impression des coques", "en millimètres / minute"], + fl_frat_s: "vitesse remplissage", + fl_frat_l: ["vitesse maximale d'impression du remplissage", "en millimètres / minute"], + fl_mult_s: "débit", + fl_mult_l: ["multiplicateur d'extrusion","0.0 - 2.0"], + fl_sfac_s: "largeur", + fl_sfac_l: ["multiplicateur de la taille de la buse", "modifie l'espacement des lignes"], + fl_skrt_s: "jupe nb", + fl_skrt_l: ["nombre de lignes de la jupe", "à générer pour la première couche"], + fl_skro_s: "décalage bordure", + fl_skro_l: ["jupe décalée de la partie", "en millimètres"], + fl_nozl_s: "buse", + fl_nozl_l: ["degrés en Celsius", "réglage de sortie utilisé", "lorsque celui-ci est égal à zéro"], + fl_bedd_s: "température plateau", + fl_bedd_l: ["degrés en Celsius", "réglage de sortie utilisé", "lorsque celui-ci est égal à zéro"], + fr_spac_s: "écart radeau", + fr_spac_l: ["espacement des couches supplémentaires", "entre la 1ère couche et le radeau", "en millimètres"], + fr_nabl_s: "activer un radeau", + fr_nabl_l: ["créer un radeau sous le ", "modèle pour une meilleure adhérence", "utilise le décalage des jupes et", "désactive la sortie des jupes"], + + // FDM BELT ONLY + fl_zoff_s: "décalage bande", + fl_zoff_l: ["décalage de hauteur par rapport à la bande", "de l'extrusion la plus basse", "en millimètres"], + fl_brim_s: "taille de bordure", + fl_brim_l: ["ajoute une bordure à la partie inférieure","la taille est la largeur en millimètres","0 désactive"], + fl_brmn_s: "seuil de bordure", + fl_brmn_l: ["ajout de bordures uniquement quand le segment", "face à la bande est plus court que cela", "valeur en millimètres", "0 = infini"], + fl_bled_s: "ancrage de pièce", + fl_bled_l: ["ancrage de la pièce sur la bande", "au début de l'impression", "en millimètres"], + + // FDM SUPPORT + sp_menu: "support", + sp_detect: "détecter", + sp_dens_s: "densité", + sp_dens_l: ["fraction 0.0 - 1.0","recommendé 0.15","0 désactive"], + sp_size_s: "taille pilier", + sp_size_l: ["largeur du pilier","en millimètres"], + sp_offs_s: "décalage du pilier", + sp_offs_l: ["décalage par rapport à la pièce","en millimètres"], + sp_gaps_s: "écart des couches", + sp_gaps_l: ["nombre de couches","de décalage de la pièce"], + sp_span_s: "portée maximale", + sp_span_l: ["portée non soutenue nécessitant", "un nouveau soutien à générer","en millimètres"], + sp_angl_s: "angle max", + sp_angl_l: ["angle de surplomb maximum avant", "de générer un pilier de soutien"], + sp_area_s: "surface min", + sp_area_l: ["surface minimale de","la colonne support","en millimètres"], + sp_xpnd_s: "étendre", + sp_xpnd_l: ["étendre la zone de soutien", "au-delà des limites d'une pièce","en millimètres"], + sp_nozl_s: "extrudeur", + sp_nozl_l: ["dans les systèmes multi-extrudeurs", "l'extrudeur à utiliser pour", "le matériau de support"], + sp_auto_s: "automatique", + sp_auto_l: ["activer les supports générés", "utiliser la géométrie at-slice-time", "les supports n'apparaîtront", "qu'une fois le tranchage terminé"], + + // LASER SLICING + ls_offs_s: "offset", + ls_offs_l: ["adjust for beam width","en millimètres"], + ls_lahi_s: "height", + ls_lahi_l: ["layer height","in millimeters","0 = auto/detect"], + ls_lahm_s: "min", + ls_lahm_l: ["layer height minimum","will merge auto slices","under this thickness","in millimeters"], + ls_sngl_s: "single", + ls_sngl_l: ["perform only one slice","at specified layer height"], + + // CNC COMMON terms + cc_tool: "tool", + cc_offs_s: "offset", + cc_offs_l: ["offset tool center","from chosen path"], + cc_spnd_s: "spindle rpm", + cc_spnd_l: ["spindle speed in","revolutions / minute"], + cc_sovr_s: "step over", + cc_sovr_l: ["as a fraction of","tool diameter"], + cc_sdwn_s: "step down", + cc_sdwn_l: ["step down depth","for each pass","in workspace units","0 to disable"], + cc_feed_s: "feed rate", + cc_feed_l: ["max cutting speed in","workspace units / minute"], + cc_plng_s: "plunge rate", + cc_plng_l: ["max z axis speed in","workspace units / minute"], + cc_sngl_s: "select lines only", + cc_sngl_l: ["select only single edges","instead of connected polylines"], + + // CNC COMMON + cc_menu: "limits", + cc_flip: "flip", + cc_rapd_s: "xy feed", + cc_rapd_l: ["max xy moves feedrate","in workspace units / minute"], + cc_rzpd_s: "z feed", + cc_rzpd_l: ["max z moves feedrate","in workspace units / minute"], + + cc_loff_s: "offset", + cc_loff_l: ["distance from stock face","for leveling pass","in workspace units"], + + // CNC ROUGHING + cr_menu: "rough", + cr_lsto_s: "leave stock", + cr_lsto_l: ["horizontal offset from vertical faces","stock to leave for finishing pass","in workspace units"], + cr_ease_s: "ease down", + cr_ease_l: ["plunge cuts will","spiral down or ease","along a linear path"], + cr_clrt_s: "clear top", + cr_clrt_l: ["run a clearing pass over","the bounding area of the part","at z = 0"], + cr_clrp_s: "clear voids", + cr_clrp_l: ["mill out through pockets","instead of just the outline"], + cr_clrf_s: "clear faces", + cr_clrf_l: ["interpolate step down to","clear any detected flat areas"], + cr_olin_s: "inside only", + cr_olin_l: ["limit cutting to","inside part boundaries"], + + // CNC OUTLINE + co_menu: "outline", + co_dogb_s: "dogbones", + co_dogb_l: ["insert dogbone cuts","into inside corners"], + co_wide_s: "wide cutout", + co_wide_l: ["widen outside cutout paths","for deep cuts in hard material"], + co_olin_s: "inside only", + co_olin_l: ["limit cutting to","inside part boundaries"], + co_olot_s: "outside only", + co_olot_l: ["limit cutting to","exterior part boundaries","which can be thought of","as the shadow outline"], + co_omit_s: "omit through", + co_omit_l: "eliminate thru holes", + co_olen_s: "enable", + co_olen_l: "enabled outline cutting", + + // CNC CONTOUR + cn_menu: "contour", + cf_angl_s: "max angle", + cf_angl_l: ["angles greater than this","are considered vertical"], + cf_curv_s: "curves only", + cf_curv_l: ["limit linear cleanup","to curved surfaces"], + cf_olin_s: "inside only", + cf_olin_l: ["limit cutting to","inside part boundaries"], + cf_linx_s: "enable y pass", + cf_linx_l: "linear y-axis finishing", + cf_liny_s: "enable x pass", + cf_liny_l: "linear x-axis finishing", + + // CNC TRACE + cu_menu: "trace", + cu_type_s: "type", + cu_type_l: ["follow = tool tip follows line","right or left = tool tip","follows line offset by tool radius"], + + // CNC DRILLING + cd_menu: "drill", + cd_axis: "axis", + cd_points: "points", + cd_plpr_s: "plunge per", + cd_plpr_l: ["max plunge between","dwell periods","in workspace units","0 to disable"], + cd_dwll_s: "dwell time", + cd_dwll_l: ["dwell time","between plunges in","in milliseconds"], + cd_lift_s: "drill lift", + cd_lift_l: ["lift between plunges","after dwell period","in workspace units","0 to disable"], + cd_regi_s: "register", + cd_regi_l: ["drill registration holes","for double-sided parts","independent of enable","drilling but uses same","tool and settings"], + + // CNC CUTOUT TABS + ct_menu: "tabs", + ct_angl_s: "angle", + ct_angl_l: ["starting angle for tab spacing","in degrees (0-360)"], + ct_numb_s: "count", + ct_numb_l: ["number of tabs to use","will be spaced evenly","around the part"], + ct_wdth_s: "width", + ct_wdth_l: "width in workspace units", + ct_hght_s: "height", + ct_hght_l: "height in workspace units", + ct_dpth_s: "depth", + ct_dpth_l: ["distance in workspace units","that the tab projects from","the part surface"], + ct_midl_s: "midline", + ct_midl_l: ["use midline of tab","instead of z bottom","for double-sided work"], + ct_nabl_s: "auto", + ct_nabl_l: ["auto generate radial tabs","projected from part center","using count and angle offset"], + + // OUTPUT + ou_menu: "sortie", + + // LASER KNIFE + dk_menu: "knife", + dk_dpth_s: "cut depth", + dk_dpth_l: ["final cut depth","in millimeters"], + dk_pass_s: "cut passes", + dk_pass_l: ["number of passes","down to cut depth"], + dk_offs_s: "tip offset", + dk_offs_l: ["distance from blade tip","to center of tool","in millimeters"], + + // OUTPUT LASER + ou_spac_s: "spacing", + ou_spac_l: ["distance between layer output","in millimeters"], + ou_scal_s: "scaling", + ou_scal_l: "multiplier (0.1 to 100)", + ou_powr_s: "power", + ou_powr_l: ["0 - 100","represents %"], + ou_sped_s: "speed", + ou_sped_l: "millimeters / minute", + ou_mrgd_s: "merged", + ou_mrgd_l: ["merge all layers using","color coding to denote","stacking depth"], + ou_grpd_s: "grouped", + ou_grpd_l: ["retain each layer as","a unified grouping","instead of separated","polygons"], + ou_layr_s: "layer order", + ou_layr_l: ["output layer order","from top right to","bottom left"], + ou_layo_s: "layer color", + ou_layo_l: ["output layer colors","for each z index","overridden by merged"], + ou_drkn_s: "drag knife", + ou_drkn_l: ["enable drag knife","output in gcode","cut radii are added","to corners with","cut down passes"], + + // OUTPUT FDM + ou_nozl_s: "température buse", + ou_nozl_l: "degrés en celsius", + ou_bedd_s: "température plateau", + ou_bedd_l: "degrés en celsius", + ou_feed_s: "vitesse impression", + ou_feed_l: ["vitesse d'impression maximale","millimètres / minute"], + ou_fini_s: "vitesse coque", + ou_fini_l: ["vitesse de la coque extérieure","millimètres / minute"], + ou_move_s: "vitesse déplacement", + ou_move_l: ["vitesse de déplacement sans impression","millimètres / minute","0 = active déplacements G0"], + ou_shml_s: "débit de coque", + ou_flml_s: "débit de solidité", + ou_spml_s: "débit de remplissage", + ou_exml_l: ["multiplicateur d'extrusion","0.0 - 2.0"], + ou_fans_s: "vitesse ventilateur", + ou_fans_l: "0 - 255", + + // OUTPUT CAM + ou_toll_s: "tolerance", + ou_toll_l: ["surface precision","in workspace units","lower is slower and","uses more memory","0 = automatic based","on animate preference"], + ou_zanc_s: "z anchor", + ou_zanc_l: ["controls the position of the part","when stock Z exceeds part Z"], + ou_ztof_s: "z offset", + ou_ztof_l: ["offset z anchor","in workspace units","has no effect when","anchor is middle"], + ou_zbot_s: "z bottom", + ou_zbot_l: ["offset from part bottom","to limit cutting depth","in workspace units"], + ou_zclr_s: "z clearance", + ou_zclr_l: ["safe travel offset","from top of part","in workspace units"], + ou_ztru_s: "z thru", + ou_ztru_l: ["extend cutout pass down","in workspace units"], + ou_conv_s: "conventional", + ou_conv_l: ["milling direction","uncheck for 'climb'"], + ou_depf_s: "depth first", + ou_depf_l: ["optimize pocket cuts","with depth priority"], + + // CAM STOCK + cs_menu: "stock", + cs_wdth_s: "width", + cs_wdth_l: ["width (x) in workspace units","0 defaults to part size"], + cs_dpth_s: "depth", + cs_dpth_l: ["depth (y) in workspace units","0 defaults to part size"], + cs_hght_s: "height", + cs_hght_l: ["height (z) in workspace units","0 defaults to part size"], + cs_offs_s: "offset", + cs_offs_l: ["use width, depth, height","as offsets from max","part size on platform"], + cs_clip_s: "clip to", + cs_clip_l: ["rough and outline","clip cutting paths","to defined stock"], + cs_offe_s: "enable", + cs_offe_l: "enable milling stock", + + // ORIGIN (CAM & LASER) + or_bnds_s: "origin bounds", + or_bnds_l: ["origin is relative to","boundary of all objects"], + or_cntr_s: "origin center", + or_cntr_l: "origin is referenced from the center", + or_topp_s: "origin top", + or_topp_l: "origin is references from the top of objects", + + // FDM ADVANCED + ad_menu: "expert", + ad_rdst_s: "distance de rétraction", + ad_rdst_l: ["longueur de filament à rétracter", "pour les longs mouvements. en millimètres"], + ad_rrat_s: "vitesse de rétraction", + ad_rrat_l: ["vitesse de rétraction","du filament en mm/s"], + ad_rdwl_s: "réenclencher", + ad_wpln_s: "essuyage rétractation", + ad_wpln_l: ["mouvement sans impression", "après rétraction", "en millimètres"], + ad_rdwl_l: ["durée de réenclenchement", "filament et mouvement", "en millisecondes"], + ad_scst_s: "cabotage", + ad_scst_l: ["extrémité non imprimée", "des coques du périmètre", "en millimètres"], + ad_msol_s: "min solide", + ad_msol_l: ["aire minimale (mm^2)","requise pour rester solide","doit être > 0.1"], + ad_mins_s: "vitesse minimale", + ad_mins_l: ["vitesse minimale","des segments courts"], + ad_spol_s: "chemin court", + ad_spol_l: ["les polygones plus courts", "auront la vitesse réduite et", "seront ramenés à la vitesse minimale", "en millimètres"], + ad_arct_s: "tolérance arc", + ad_arct_l: ["convertir les lignes des facettes en arcs", "tolérance de dérive du point central", "lors de la correspondance des points d'arc", "considérer les valeurs autour de 0,15", "en millimètres", "0 désactive"], + ad_zhop_s: "décalage en Z", + ad_zhop_l: ["levée du z","sur les mouvements de rétraction", "en millimètres", "0 désactive"], + ad_abkl_s: "anti-backlash", + ad_abkl_l: ["pour une meilleure finition de la surface plane", "utiliser des micro-mouvements pour annuler", "le jeu dans la sortie de la couche solide", "en millimètres", "0 désactive", "si le firmware reconnait le M425", "l'utiliser dans l'en-tête du gcode", "et mettre à 0"], + ad_lret_s: "rétraction au changement de couche", + ad_lret_l: ["forcer la rétraction du filament", "entre les couches"], + ad_play_s: "couches de polissage", + ad_play_l: ["polir jusqu'au nombre spécifié", "# de couches à la fois"], + ad_pspd_s: "vitesse de polissage", + ad_pspd_l: ["vitesse de polissage", "en millimètres / minute"], + + // CAM EXPERT + cx_fast_s: "skip shadow", + cx_fast_l: ["disable overhang detection","can be faster and use less","memory with complex models","but fails with overhangs","try enabling if slicing","hangs during shadowing"], + + // FDM GCODE + ag_menu: "gcode", + ag_nozl_s: "buse", + ag_nozl_l: "sélectionner la buse ou la tête d'impression", + ag_paws_s: "couches de pause", + ag_paws_l: ["liste de couches séparées par des virgules", "pour injecter des commandes de pause avant"], + ag_loop_s: "couches de répétitions", + ag_loop_l: ["des plages de couches à répéter dans le format ", "premier-compte dernier,premier-compte dernier,...", "compte omis = 1"], + + // SLA MENU + sa_menu: "tranchage", + sa_lahe_s: "hauteur de couche", + sa_lahe_l: ["hauteur de couche","en millimètres"], + sa_shel_s: "hollow shell", + sa_shel_l: ["shell thickness in mm","use multiple of layer height","use 0 for solid (disabled)"], + sa_otop_s: "open top", + sa_otop_l: ["if shell is enabled","results in an open top"], + sa_obas_s: "open base", + sa_obas_l: ["if shell is enabled","results in an open base","disabled with supports"], + + sa_layr_m: "layers", + sa_lton_s: "light on time", + sa_lton_l: ["layer light on","time in seconds"], + sa_ltof_s: "light off time", + sa_ltof_l: ["layer light off","time in seconds"], + sa_pldi_s: "peel distance", + sa_pldi_l: ["peel distance","in millimeters"], + sa_pllr_s: "peel lift rate", + sa_pllr_l: ["peel lift speed","in mm/sec"], + sa_pldr_s: "peel drop rate", + sa_pldr_l: ["peel drop speed","in mm/sec"], + + sa_base_m: "base", + sa_balc_s: "layer count", + sa_balc_l: ["number of","base layers"], + sa_bltn_l: ["base layer light on","time in seconds"], + sa_bltf_l: ["base layer light off","time in seconds"], + + sa_infl_m: "infill", + sa_ifdn_s: "density", + sa_ifdn_l: ["percent infill","requires shell","0 = disabled","valid 0.0 - 1.0"], + sa_iflw_s: "line width", + sa_iflw_l: ["hatch line width","in millimeters"], + + sa_supp_m: "support", + sa_slyr_s: "base layers", + sa_slyr_l: ["base support layers","value range 0-10"], + sa_slgp_s: "gap layers", + sa_slgp_l: ["number of layers between","raft and bottom of object"], + sa_sldn_s: "density", + sa_sldn_l: ["used to compute the","number of support pillars","0.0-1.0 (0 = disable)"], + sa_slsz_s: "size", + sa_slsz_l: ["max size of a","support pillar","in millimeters"], + sa_slpt_s: "points", + sa_slpt_l: ["number of points in","each support pillar","in millimeters"], + sl_slen_l: "enable supports", + + sa_outp_m: "output", + sa_opzo_s: "z offset", + sa_opzo_l: ["z layer offset","almost always 0.0","0.0-1.0 in millimeters"], + sa_opaa_s: "anti alias", + sa_opaa_l: ["enable anti-aliasing","produces larger files","can blur details"] +}; diff --git a/web/kiri/lang/pl-help.html b/web/kiri/lang/pl-help.html new file mode 120000 index 00000000..e1c48e23 --- /dev/null +++ b/web/kiri/lang/pl-help.html @@ -0,0 +1 @@ +pl-pl-help.html \ No newline at end of file diff --git a/web/kiri/lang/pl-pl-help.html b/web/kiri/lang/pl-pl-help.html new file mode 100644 index 00000000..bb5c4f49 --- /dev/null +++ b/web/kiri/lang/pl-pl-help.html @@ -0,0 +1,180 @@ +
    + +
    + + +
    + +
    + +
    +
    o programie
    +
    początki
    +
    sterowanie
    +
    wsparcie
    +
    projekt
    +
    + +
    +
    +
    +

    + Kiri:Moto jest unikalnym, opartym na przeglądarce, w pełni otwartym programem + do cięcia modeli, który zamienia modele 3d w instrukcje maszynowe dla drukarek 3D, + frezarek CNC i wycinarek laserowych. +


    +

    + Oparcie na przeglądarce oznacza, że całe przetwarzanie odbywa się w lokalnej + przeglądarce, a nie w chmurze. Twoje dane i modele pozostają prywatne tylko dla Ciebie. +


    +

    + Oznacza to, że Kiri zachowuje się tak samo, jak natywny program na komputerze użytkownika. + Bezpieczna tak jak bezpieczna jest piaskownica przeglądarki. +


    +

    + Kolejną zaletą działania w sieci jest to, że Kiri jest zawsze aktualny. Nowa strona + nowe funkcje, poprawki błędów i aktualizacje pojawiają się często. Jeśli chcesz uruchomić + starszą wersję Kiri lub wersję rozwojową, należy wybrać ją z menu użytkownika w prawym górnym rogu. +

    +
    +
    + Słowo Kiri pochodzi od kiri-e, która jest japońską sztuką wycinania papieru. + Moto to skrót od "Modeling Tool". +
    + +
    + Skrócona instrukcja obsługi +
    +

    +

  • obejrzyj (po angielsku) The Definitive Video Guide to Kiri:Moto 2.5
  • +
  • menu ustawienia urządzenia i tryby
  • +
  • ustawienia interfejsu użytkownika i trybów
  • +
  • dopasowanie parametrów urządzenia, jeśli chcesz zmienić ustawienia domyślne
  • +
  • otwórz plik lub zaimportuj element do przestrzeni roboczej
  • +
  • uzyj menu po prawej stronie by dopasować parametry krojenia obiektów
  • +
  • aby zobaczyć jak działają parametry, pokrój obiekty
  • +
  • podgląd ścieżki z pokrojonych obiektów
  • +
  • eksportuj ścieżki zmian do g-kodu lub pliku svg
  • +

    +
    +
    + Tryby pracy +

    Każdy tryb (FDM, CAM, Laser, SLA) ma inne funkcje i ustawienia. + Obejrzyj filmy na Youtube, aby dowiedzieć się więcej. +

    +
    +
    + +
    o druku 3d
    +
    o frezowaniu cnc
    +
    o cięciu laserem
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Sterowanie klawiaturą
    KlawiszDziałanie
    Konfiguracja
    Wybór urządzenia
    Wybór narzędzi CAM
    Lokalna siatka odniesienia urządzeń
    Panel sterowania
    Profile prac
    Tryb
    Wyrównanie
    Krojenie
    Podgląd
    Animacja
    Eksport
    Plik
    Import
    Ostatnie
    Widok
    Początek
    Góra
    Z przodu
    Z tyłu
    Przywrócenie ustawień widoku
    Widok cykli
    Podświetl (wyrównanie)
    Warstwa (plastry/podgląd)
    Obiekty
    Powiel
    Odbij
    Ustawienia obrotów
    Kasowanie
    Obrót o 90°
    Obrót o 5°
    Przesuń o 5mm
    Zaznacz wszystko
    Pomoc
    To okno


    Ustawienia myszy
    ComboAkcje
    Widok
    kołaZmiana warstwy
    przemieśćprzemieść obiekty
    przemieść obiekty
    Obróć
    Powiększ
    Powiększ
    przesuńprzemieść obiekty
    Obiekty
    Dopasowanie ściany do podłoża
    Dopasowanie ściany do podłoża
    +
    +
    + +
    + Zasoby + + Opcje w programie +

    + Pod przyciskiem menu użytkownika w prawym górnym rogu, + jest opcja eksport, tworzy on kopię do pobrania profili urządzenia + (opcjonalnie również obszaru roboczego). Plik ten może być użyty do tworzenia kolejnych wersji i + przywracania pełnej migawki konfiguracji w dowolnym momencie. Można go również udostępnić programistom + lub innym użytkownikom, aby mogli odtworzyć środowisko użytkownika. Może to pomóc w usuwaniu błędów. +

    +
    + +
    + Otwarte Oprogramowanie +

    + Kiri:Moto jest w całości wolnym oprogramowaniem. + Sprawdź stronę projektu na Githubie. Istnieją projekty towarzyszące, zarówno sprzętowe jak i programowe. + Nowi programiści i wkłady w kod źródłowy są oczywiście mile widziane. Kod źródłowy na licencji MIT. +


    +

    + Dotacje mile widziane. +

    +
    Integrations +

    Kiri:Moto współdziała z różnymi innymi aplikacjami jako strona internetowa

    +
  • Onshape w sklepie App Store
  • +
  • Thingiverse jako natywne 'Thing Apps'
  • +
  • Jako główny program do cięcia uzywany przez SimplyPrint.io
  • +
  • CAMLab jest programem pochodnym poprzedniej wersji Kiri:Moto
  • +
    + +
    + +
    +
    diff --git a/web/kiri/lang/pl-pl.js b/web/kiri/lang/pl-pl.js new file mode 100644 index 00000000..f6870e66 --- /dev/null +++ b/web/kiri/lang/pl-pl.js @@ -0,0 +1,645 @@ +// Polish. Język polski tłumaczony na zasadach własności publicznej +kiri.lang['pl'] = +kiri.lang['pl-pl'] = { + // hasło i tłumaczenie + animate: "animacja", // CAM animate button + arrange: "rozmieść", // layout workspace objects + axis: "osie", // left object scale pop menu + clear: "wyczyść", //"czyszczenie z obiektów", // clear workspace (remove all objects) + copy: "kopiowanie", + delete: "kasowanie", + detail: "szczegóły", + done: "wykonaj", + enable: "włącz", + export: "eksport", + files: "pliki", + help: "pomoc", + ghost: "druciak", // left render pop menu (wireframe) + hide: "ukryj", // left render pop menu (invisible) + home: "początek", + import: "import", + language: "język", + machine: "maszyna", // device or machine + metric: "metryka", + name: "nazwa", + prefs: "ustawienia", // left menu "preferences" + preview: "podgląd", + recent: "recent", + render: "render", // left render pop menu + reset: "reset", + rotate: "obróć", // left rotate pop menu + save: "zapisz", + scale: "skala", // left object scale pop menu + setup: "ustaw", + settings: "ustawienia", + size: "wielkość", + slice: "plaserki", + solid: "wypełniony", // view type pop menu + start: "start", + tool: "narzędzie", + tools: "narzędzia", // CAM tool menu button + top: "góra", + type: "typ", // CAM tool type + version: "wersja", + view: "widok", // left view pop menu + wire: "druciak", // left render pop menu "wire" + + acct_xpo: ["zrób backup swojego urządzenia","i profil urządzenia z","opcja by zapisać przestrzeń roboczą","obiekty i pozycje"], + + // RIGHT-CLICK CONTEXT MENU + rc_clws: "wyczyść przestrzeń roboczą", + rc_xpws: "wyeksportuj przestrzeń roboczą", + rc_lafl: "ułóż płasko", + rc_mirr: "odbicie", // lustro + rc_dupl: "skopiuj", + rc_xstl: "zapisz w formacie STL", + + // DEVICE MENU and related dialogs + dm_sldt: "wybierz rodzaj swojego urządzenia", + dm_stdd: "zwykłe urządzenia", + dm_mydd: "moje urządzenia", + dm_seld: "wybierz urządzenie", + dm_rcnt: "ostatnio uzywane pliki", + dm_savs: "zachowane ustawienia", + dm_appp: "Ustawienia programu", + + // CAM Tool Dialog + td_tyem: "tępo zakończone", // end mill + td_tybm: "kula na końcu", // ball mill + td_tytm: "zwężający się", // taper mill + td_tonm: "narzędzie #", + td_shft: "wał (pionowy)", // endmill shaft specs + td_flut: "czołowy", // endmill flute specs + td_tapr: "zwężający się", // endmill taper specs + + // DEVICE dialog groups + dv_gr_dev: "urządzenie", + dv_gr_ext: "ekstruder", + dv_gr_out: "wynik", + dv_gr_gco: "makra gkodu", + + // DEVICE dialog (_s = label, _l = hover help) + dv_name_s: "nazwa", + dv_name_l: "nazwa urządzenia", + dv_fila_s: "filament", + dv_fila_l: "średnica w milimetrach", + dv_nozl_s: "dysza", + dv_nozl_l: "średnica w milimetrach", + dv_bedw_s: "szerokość", + dv_bedw_l: "pole robocze w jednostkach", + dv_bedd_s: "głębokość", + dv_bedd_l: "pole robocze w jednostkach", + dv_bedh_s: "wysokość", + dv_bedh_l: ["maksymalna wysokość budowania","w jednostkach pola roboczego"], + dv_spmx_s: "maksymalna prędkość wrzeciona", + dv_spmx_l: ["maksymalna prędkość wrzeciona w obrotach na minutę","0 wyłączone"], + dv_xtab_s: "ustawienie pozycji absolutnie", + dv_xtab_l: "absolutna wartość wypchnięcia", + dv_orgc_s: "środek odniesienia", + dv_orgc_l: "środek odniesienia na platformie", + // dv_orgt_s: "odniesienie u góry", + // dv_orgt_l: "oś z odniesienie u góry", + dv_bedc_s: "okrądła platforma drukowania", + dv_bedc_l: "platforma druku jest okrągła", + dv_belt_s: "pas na platformie", + dv_belt_l: "kontynuacja drukowania platformy", + dv_retr_s: "domyślne cofnięcie (retrakcja)", + dv_retr_l: ["firmware urządzenia wspiera kody G10/G11"], + dv_fanp_s: "zasilanie wiatraczków", + dv_fanp_l: "ustawienia zasilania wiatraczków", + dv_prog_s: "postęp", + dv_prog_l: "wynik w czasie każdego % postępu", + dv_layr_s: "warstwa", + dv_layr_l: ["output at each","layer change"], + dv_tksp_s: "token spacer", + dv_tksp_l: ["add a space between","gcode axis parameters","G0X0Y0X0","vs","G0 X0 Y0 Z0"], + dv_strc_s: "strip comments", + dv_strc_l: ["strip gcode comments","comments begin with ;"], + dv_fext_s: "rozszerzenie pliku", + dv_fext_l: "rozszerzenie pliku", + dv_dwll_s: "dwell", + dv_dwll_l: "gcode dwell script", + dv_tool_s: "zmiana narzędzia", + dv_tool_l: "skrypt zmiany narzędzia", + dv_sspd_s: "prędkość wrzeciona", + dv_sspd_l: "ustaw prędkość wrzeciona", + dv_paus_s: "pauza", + dv_paus_l: "gkod skryptu pauzy", + dv_head_s: "nagłówek (początek)", + dv_head_l: "skrypt gkod nagłówka", + dv_foot_s: "stopka (zakończenie)", + dv_foot_l: "skrypt gkod stopki", + dv_lzon_s: "laser włączony", + dv_lzon_l: "gcode laser on script", + dv_lzof_s: "laser wyłączony", + dv_lzof_l: "gcode laser off script", + dv_exts_s: "zaznacz", + dv_exts_l: "gcode run to enable this extruder", + dv_dext_s: "odznacz", + dv_dext_l: "gcode run before enabling another extruder", + dv_extd_s: "deselect", + dv_extd_l: "gcode to deselect this extruder", + dv_exox_s: "offset x", + dv_exox_l: "nozzle offset x", + dv_exoy_s: "offset y", + dv_exoy_l: "nozzle offset y", + + // MODE + mo_menu: "tryb", + mo_fdmp: "FDM Druk przyrostowy", + mo_slap: "SLA Druk przyrostowy", + mo_lazr: "Ciecie laserem", + mo_cncm: "Frezowanie CNC", + + // SETUP + su_menu: "ustawienia", + su_devi: "Urządzenia", + su_tool: "Narzędzia", + su_locl: "Lokalnie", + su_xprt: "Eksport", + su_help: "Pomoc", + + // LOAD + fe_menu: "plik", + fn_recn: "ostatnie pliki", + fn_impo: "Importowanie", + + // FUNCTION + fn_menu: "action", + fn_arra: "Arrange", + fn_slic: "Slice", + fn_prev: "Preview", + fn_expo: "Export", + + // VIEW + vu_menu: "view", + vu_home: "Home", + vu_rset: "Reset", + vu_sptp: "Top", + vu_spfr: "Front", + vu_splt: "Left", + vu_sprt: "Right", + + // WORKSPACE + ws_menu: "view", + ws_save: "Save", + ws_cler: "Clear", + + // OPTIONS + op_menu: "interface", + op_disp: "display", + op_xprt_s: "expert", + op_xprt_l: "show more settings options", + op_decl_s: "decals", + op_decl_l: "show device decals and logos", + op_dang_s: "experimental", + op_dang_l: "show experimental parameters", + op_hopo_s: "hover pop", + op_hopo_l: ["enable menu hover","to activate"], + op_dark_s: "dark mode", + op_dark_l: "dark mode interface", + op_comp_s: "compact ui", + op_comp_l: ["compact user interface","better for small screens","and tablets"], + op_shor_s: "show origin", + op_shor_l: "show device or process origin", + op_shru_s: "show rulers", + op_shru_l: ["show axes rulers","on major gridlines"], + op_sped_s: "show speeds", + op_sped_l: ["show speed to color bar","in preview mode"], + op_auto_s: "auto layout", + op_auto_l: ["automatically layout platform","when new items added"], + op_free_s: "free layout", + op_free_l: ["permit dragable layout","no effect in laser mode"], + op_spcr_s: "spacing", + op_spcr_l: ["spacing between objects","during auto layout","in workspace units"], + op_orth_s: "orthographic", + op_orth_l: ["orthographic display","requires page refresh"], + op_invr_s: "invert zoom", + op_invr_l: ["invert mouse wheel","scroll zoom"], + op_save_s: "auto save", + op_save_l: ["preserve objects in workspace","between application reloads"], + op_line_s: "line type", + op_line_l: ["line style for path rendering","impacts 3d performance","path: 3d best","flat: 2d good","line = 1d fast"], + op_unit_s: "units", + op_unit_l: ["workspace units affects","speeds and distances"], + op_anim_s: "animate", + op_anim_l: ["animation mesh density","higher is more dense","takes more memory","and is slower"], + + lo_menu: "layout", + + pt_menu: "parts", + pt_deci_s: "decimate", + pt_deci_l: ["enable or disable point decimation","during port import. for faster slicing","and lower memory usage"], + pt_qual_s: "quality", + pt_qual_l: ["level of detail to retain","during slicing operations","lower is faster"], + pt_heal_s: "heal mesh", + pt_heal_l: ["attempt to heal","non-manifold meshes","adds to slicing time"], + + xp_menu: "exports", + + // SETTINGS + se_menu: "profile", + se_load: "load", + se_save: "save", + + // FDM SLICING + sl_menu: "layers", + sl_lahi_s: "height", + sl_lahi_l: ["height of each slice","layer in millimeters"], + ad_minl_s: "height min", + ad_minl_l: ["adaptive min layer height","in millimeters","must be non-zero"], + sl_ltop_s: "top layers", + sl_ltop_l: ["number of solid layers","to enforce at the","top of the print"], + sl_lsld_s: "solid layers", + sl_lsld_l: ["solid fill areas computed","from layer deltas. see","layer pop menu"], + sl_lbot_s: "base layers", + sl_lbot_l: ["number of solid layers","to enforce at the","bottom of the print"], + ad_adap_s: "adaptive", + ad_adap_l: ["use adaptive layer heights","with 'layer height' as max","and 'layer min' as the min"], + + // FDM SHELLS + sl_shel_s: "shell count", + sl_shel_l: ["number of perimeter","walls to generate"], + sl_ordr_s: "shell order", + sl_ordr_l: ["output shell order","inside to outside","or outside to inside","affects surface quality"], + sl_strt_s: "layer start", + sl_strt_l: ["layer starting point","last = last layer end","center = part center","origin = device origin"], + ad_thin_s: "thin walls", + ad_thin_l: ["detect and fill gaps","between shell walls"], + + // FDM FILL + fi_menu: "infill", + fi_type: "type", + fi_pcnt_s: "fill fraction", + fi_pcnt_l: ["fill density values","0.0 - 1.0"], + fi_angl_s: "solid start", + fi_angl_l: ["starting angle in degrees","90 degrees added to ","each following layer","applies only to solid layers"], + fi_over_s: "overlap", + fi_over_l: ["overlap with shell and other fill","as fraction of nozzle diameter","0.0 - 1.0"], + // fi_rate_s: "print speed", + fi_rate_l: ["extrusion speed for infill","set to 0 to use default","output print speeds"], + + // FDM FIRST LAYER + fl_menu: "base", + fl_lahi_s: "layer height", + fl_lahi_l: ["height of each slice","in millimeters","should be >= slice height"], + fl_rate_s: "shell speed", + fl_rate_l: ["shell printing max speed","in millimeters / minute"], + fl_frat_s: "fill speed", + fl_frat_l: ["fill printing max speed","in millimeters / minute"], + fl_mult_s: "flow factor", + fl_mult_l: ["extrusion multiplier","0.0 - 2.0"], + fl_sfac_s: "width factor", + fl_sfac_l: ["multiplier on nozzle size","changes line spacing"], + fl_skrt_s: "skirt count", + fl_skrt_l: ["number of first-layer offset","brims to generate"], + fl_skro_s: "skirt offset", + fl_skro_l: ["skirt offset from part","in millimeters"], + fl_nozl_s: "nozzle temp", + fl_nozl_l: ["degrees in celsius","output setting used","when this is zero"], + fl_bedd_s: "bed temp", + fl_bedd_l: ["degrees in celsius","output setting used","when this is zero"], + fr_spac_s: "raft gap", + fr_spac_l: ["additional layer spacing","between 1st layer and raft","in millimeters"], + fr_nabl_s: "raft enable", + fr_nabl_l: ["create a raft under the","model for better adhesion","uses skirt offset and","disables skirt output"], + + // FDM BELT ONLY + fl_zoff_s: "belt offset", + fl_zoff_l: ["height offset from belt","of lowest extrusion","in millimeters"], + fl_brim_s: "brim size", + fl_brim_l: ["add brim to part bottom","size is width in millimeters","0 to disable"], + fl_brmn_s: "brim trigger", + fl_brmn_l: ["add brims only when segment","facing belt is shorter than this","value in millimeters","0 = Infinity"], + fl_bled_s: "part anchor", + fl_bled_l: ["belt part anchor","at start of print","in millimeters"], + + // FDM SUPPORT + sp_menu: "support", + sp_detect: "detect", + sp_dens_s: "density", + sp_dens_l: ["fraction 0.0 - 1.0","recommended 0.15","0 to disable"], + sp_size_s: "pillar size", + sp_size_l: ["pillar width","in millimeters"], + sp_offs_s: "part offset", + sp_offs_l: ["offset from part","in millimeters"], + sp_gaps_s: "gap layers", + sp_gaps_l: ["number of layers","offset from part"], + sp_span_s: "max span", + sp_span_l: ["unsupported span that causes","a new support to be generated","in millimeters"], + sp_angl_s: "max angle", + sp_angl_l: ["max overhang angle before","supporting pillar is generated"], + sp_area_s: "min area", + sp_area_l: ["minimum area for","a support column","in millimeters"], + sp_xpnd_s: "expand", + sp_xpnd_l: ["expand support area","beyond part boundary","in millimeters"], + sp_nozl_s: "extruder", + sp_nozl_l: ["in multi-extruder systems","the extruder to use for","support material"], + sp_auto_s: "automatic", + sp_auto_l: ["enable generated supports","using at-slice-time geometry","supports will only appear","after slicing completes"], + + // LASER SLICING + ls_offs_s: "offset", + ls_offs_l: ["adjust for beam width","in millimeters"], + ls_lahi_s: "height", + ls_lahi_l: ["layer height","in millimeters","0 = auto/detect"], + ls_lahm_s: "min", + ls_lahm_l: ["layer height minimum","will merge auto slices","under this thickness","in millimeters"], + ls_sngl_s: "single", + ls_sngl_l: ["perform only one slice","at specified layer height"], + + // CNC COMMON terms + cc_tool: "tool", + cc_offs_s: "offset", + cc_offs_l: ["offset tool center","from chosen path"], + cc_spnd_s: "spindle rpm", + cc_spnd_l: ["spindle speed in","revolutions / minute"], + cc_sovr_s: "step over", + cc_sovr_l: ["as a fraction of","tool diameter"], + cc_sdwn_s: "step down", + cc_sdwn_l: ["step down depth","for each pass","in workspace units","0 to disable"], + cc_feed_s: "feed rate", + cc_feed_l: ["max cutting speed in","workspace units / minute"], + cc_plng_s: "plunge rate", + cc_plng_l: ["max z axis speed in","workspace units / minute"], + cc_sngl_s: "select lines only", + cc_sngl_l: ["select only single edges","instead of connected polylines"], + + // CNC COMMON + cc_menu: "limits", + cc_flip: "flip", + cc_rapd_s: "xy feed", + cc_rapd_l: ["max xy moves feedrate","in workspace units / minute"], + cc_rzpd_s: "z feed", + cc_rzpd_l: ["max z moves feedrate","in workspace units / minute"], + + cc_loff_s: "offset", + cc_loff_l: ["distance from stock face","for leveling pass","in workspace units"], + + // CNC ROUGHING + cr_menu: "rough", + cr_lsto_s: "leave stock", + cr_lsto_l: ["horizontal offset from vertical faces","stock to leave for finishing pass","in workspace units"], + cr_ease_s: "ease down", + cr_ease_l: ["plunge cuts will","spiral down or ease","along a linear path"], + cr_clrt_s: "clear top", + cr_clrt_l: ["run a clearing pass over","the bounding area of the part","at z = 0"], + cr_clrp_s: "clear voids", + cr_clrp_l: ["mill out through pockets","instead of just the outline"], + cr_clrf_s: "clear faces", + cr_clrf_l: ["interpolate step down to","clear any detected flat areas"], + cr_olin_s: "inside only", + cr_olin_l: ["limit cutting to","inside part boundaries"], + + // CNC OUTLINE + co_menu: "outline", + co_dogb_s: "dogbones", + co_dogb_l: ["insert dogbone cuts","into inside corners"], + co_wide_s: "wide cutout", + co_wide_l: ["widen outside cutout paths","for deep cuts in hard material"], + co_olin_s: "inside only", + co_olin_l: ["limit cutting to","inside part boundaries"], + co_olot_s: "outside only", + co_olot_l: ["limit cutting to","exterior part boundaries","which can be thought of","as the shadow outline"], + co_omit_s: "omit through", + co_omit_l: "eliminate thru holes", + co_olen_s: "enable", + co_olen_l: "enabled outline cutting", + + // CNC CONTOUR + cn_menu: "contour", + cf_angl_s: "max angle", + cf_angl_l: ["angles greater than this","are considered vertical"], + cf_curv_s: "curves only", + cf_curv_l: ["limit linear cleanup","to curved surfaces"], + cf_olin_s: "inside only", + cf_olin_l: ["limit cutting to","inside part boundaries"], + cf_linx_s: "enable y pass", + cf_linx_l: "linear y-axis finishing", + cf_liny_s: "enable x pass", + cf_liny_l: "linear x-axis finishing", + + // CNC TRACE + cu_menu: "trace", + cu_type_s: "type", + cu_type_l: ["follow = tool tip follows line","right or left = tool tip","follows line offset by tool radius"], + + // CNC DRILLING + cd_menu: "drill", + cd_axis: "axis", + cd_points: "points", + cd_plpr_s: "plunge per", + cd_plpr_l: ["max plunge between","dwell periods","in workspace units","0 to disable"], + cd_dwll_s: "dwell time", + cd_dwll_l: ["dwell time","between plunges in","in milliseconds"], + cd_lift_s: "drill lift", + cd_lift_l: ["lift between plunges","after dwell period","in workspace units","0 to disable"], + cd_regi_s: "register", + cd_regi_l: ["drill registration holes","for double-sided parts","independent of enable","drilling but uses same","tool and settings"], + + // CNC CUTOUT TABS + ct_menu: "tabs", + ct_angl_s: "angle", + ct_angl_l: ["starting angle for tab spacing","in degrees (0-360)"], + ct_numb_s: "count", + ct_numb_l: ["number of tabs to use","will be spaced evenly","around the part"], + ct_wdth_s: "width", + ct_wdth_l: "width in workspace units", + ct_hght_s: "height", + ct_hght_l: "height in workspace units", + ct_dpth_s: "depth", + ct_dpth_l: ["distance in workspace units","that the tab projects from","the part surface"], + ct_midl_s: "midline", + ct_midl_l: ["use midline of tab","instead of z bottom","for double-sided work"], + ct_nabl_s: "auto", + ct_nabl_l: ["auto generate radial tabs","projected from part center","using count and angle offset"], + + // OUTPUT + ou_menu: "output", + + // LASER KNIFE + dk_menu: "knife", + dk_dpth_s: "cut depth", + dk_dpth_l: ["final cut depth","in millimeters"], + dk_pass_s: "cut passes", + dk_pass_l: ["number of passes","down to cut depth"], + dk_offs_s: "tip offset", + dk_offs_l: ["distance from blade tip","to center of tool","in millimeters"], + + // OUTPUT LASER + ou_spac_s: "spacing", + ou_spac_l: ["distance between layer output","in millimeters"], + ou_scal_s: "scaling", + ou_scal_l: "multiplier (0.1 to 100)", + ou_powr_s: "power", + ou_powr_l: ["0 - 100","represents %"], + ou_sped_s: "speed", + ou_sped_l: "millimeters / minute", + ou_mrgd_s: "merged", + ou_mrgd_l: ["merge all layers using","color coding to denote","stacking depth"], + ou_grpd_s: "grouped", + ou_grpd_l: ["retain each layer as","a unified grouping","instead of separated","polygons"], + ou_layr_s: "layer order", + ou_layr_l: ["output layer order","from top right to","bottom left"], + ou_layo_s: "layer color", + ou_layo_l: ["output layer colors","for each z index","overridden by merged"], + ou_drkn_s: "drag knife", + ou_drkn_l: ["enable drag knife","output in gcode","cut radii are added","to corners with","cut down passes"], + + // OUTPUT FDM + ou_nozl_s: "nozzle temp", + ou_nozl_l: "degrees in celsius", + ou_bedd_s: "bed temp", + ou_bedd_l: "degrees in celsius", + ou_feed_s: "print speed", + ou_feed_l: ["max print speed","millimeters / minute"], + ou_fini_s: "finish speed", + ou_fini_l: ["outermost shell speed","millimeters / minute"], + ou_move_s: "move speed", + ou_move_l: ["non-print move speed","millimeters / minute","0 = enable G0 moves"], + ou_shml_s: "shell factor", + ou_flml_s: "solid factor", + ou_spml_s: "infill factor", + ou_exml_l: ["extrusion multiplier","0.0 - 2.0"], + ou_fans_s: "fan speed", + ou_fans_l: "0 - 255", + + // OUTPUT CAM + ou_toll_s: "tolerance", + ou_toll_l: ["surface precision","in workspace units","lower is slower and","uses more memory","0 = automatic based","on animate preference"], + ou_zanc_s: "z anchor", + ou_zanc_l: ["controls the position of the part","when stock Z exceeds part Z"], + ou_ztof_s: "z offset", + ou_ztof_l: ["offset z anchor","in workspace units","has no effect when","anchor is middle"], + ou_zbot_s: "z bottom", + ou_zbot_l: ["offset from part bottom","to limit cutting depth","in workspace units"], + ou_zclr_s: "z clearance", + ou_zclr_l: ["safe travel offset","from top of part","in workspace units"], + ou_ztru_s: "z thru", + ou_ztru_l: ["extend cutout pass down","in workspace units"], + ou_conv_s: "conventional", + ou_conv_l: ["milling direction","uncheck for 'climb'"], + ou_depf_s: "depth first", + ou_depf_l: ["optimize pocket cuts","with depth priority"], + + // CAM STOCK + cs_menu: "stock", + cs_wdth_s: "width", + cs_wdth_l: ["width (x) in workspace units","0 defaults to part size"], + cs_dpth_s: "depth", + cs_dpth_l: ["depth (y) in workspace units","0 defaults to part size"], + cs_hght_s: "height", + cs_hght_l: ["height (z) in workspace units","0 defaults to part size"], + cs_offs_s: "offset", + cs_offs_l: ["use width, depth, height","as offsets from max","part size on platform"], + cs_clip_s: "clip to", + cs_clip_l: ["rough and outline","clip cutting paths","to defined stock"], + cs_offe_s: "enable", + cs_offe_l: "enable milling stock", + + // ORIGIN (CAM & LASER) + or_bnds_s: "origin bounds", + or_bnds_l: ["origin is relative to","boundary of all objects"], + or_cntr_s: "origin center", + or_cntr_l: "origin is referenced from the center", + or_topp_s: "origin top", + or_topp_l: "origin is references from the top of objects", + + // FDM ADVANCED + ad_menu: "expert", + ad_rdst_s: "retract dist", + ad_rdst_l: ["amount to retract filament","for long moves. in millimeters"], + ad_rrat_s: "retract rate", + ad_rrat_l: ["speed of filament","retraction in mm/s"], + ad_rdwl_s: "engage dwell", + ad_wpln_s: "retract wipe", + ad_wpln_l: ["non-printing move","after retraction","in millimeters"], + ad_rdwl_l: ["time between re-engaging","filament and movement","in milliseconds"], + ad_scst_s: "shell coast", + ad_scst_l: ["non-printing end","of perimeter shells","in millimeters"], + ad_msol_s: "min solid", + ad_msol_l: ["minimum area (mm^2)","required to keep solid","must be > 0.1"], + ad_mins_s: "min speed", + ad_mins_l: ["minimum speed","for short segments"], + ad_spol_s: "short path", + ad_spol_l: ["polygons shorter than this","will have their print speed","scaled down to min speed","in millimeters"], + ad_arct_s: "arc tolerance", + ad_arct_l: ["convert faceted lines to arcs","center point drift tolerance","when matching arc points","consider values around 0.15","in millimeters","0 to disable"], + ad_zhop_s: "z hop dist", + ad_zhop_l: ["amount to raise z","on retraction moves","in millimeters","0 to disable"], + ad_abkl_s: "anti-backlash", + ad_abkl_l: ["for better flat surface finish","use micro-movements to cancel","backlash in solid layer output","in millimeters","0 to disable","if your firmware has M425","put that in the gcode header","and leave this as 0"], + ad_lret_s: "layer retract", + ad_lret_l: ["force filament retraction","between layers"], + ad_play_s: "polish layers", + ad_play_l: ["polish up to specified","# of layers at a time"], + ad_pspd_s: "polish speed", + ad_pspd_l: ["polishing speed","in millimeters / minute"], + + // CAM EXPERT + cx_fast_s: "skip shadow", + cx_fast_l: ["disable overhang detection","can be faster and use less","memory with complex models","but fails with overhangs","try enabling if slicing","hangs during shadowing"], + + // FDM GCODE + ag_menu: "gcode", + ag_nozl_s: "nozzle", + ag_nozl_l: "select output nozzle or head", + ag_paws_s: "pause layers", + ag_paws_l: ["comma-separated list of layers","to inject pause commands before"], + ag_loop_s: "loop layers", + ag_loop_l: ["layer ranges to repeat in the format","first-last-count,first-last-count,...","omitted count = 1"], + + // SLA MENU + sa_menu: "slicing", + sa_lahe_s: "layer height", + sa_lahe_l: ["layer height","in millimeters"], + sa_shel_s: "hollow shell", + sa_shel_l: ["shell thickness in mm","use multiple of layer height","use 0 for solid (disabled)"], + sa_otop_s: "open top", + sa_otop_l: ["if shell is enabled","results in an open top"], + sa_obas_s: "open base", + sa_obas_l: ["if shell is enabled","results in an open base","disabled with supports"], + + sa_layr_m: "layers", + sa_lton_s: "light on time", + sa_lton_l: ["layer light on","time in seconds"], + sa_ltof_s: "light off time", + sa_ltof_l: ["layer light off","time in seconds"], + sa_pldi_s: "peel distance", + sa_pldi_l: ["peel distance","in millimeters"], + sa_pllr_s: "peel lift rate", + sa_pllr_l: ["peel lift speed","in mm/sec"], + sa_pldr_s: "peel drop rate", + sa_pldr_l: ["peel drop speed","in mm/sec"], + + sa_base_m: "base", + sa_balc_s: "layer count", + sa_balc_l: ["number of","base layers"], + sa_bltn_l: ["base layer light on","time in seconds"], + sa_bltf_l: ["base layer light off","time in seconds"], + + sa_infl_m: "infill", + sa_ifdn_s: "density", + sa_ifdn_l: ["percent infill","requires shell","0 = disabled","valid 0.0 - 1.0"], + sa_iflw_s: "line width", + sa_iflw_l: ["hatch line width","in millimeters"], + + sa_supp_m: "support", + sa_slyr_s: "base layers", + sa_slyr_l: ["base support layers","value range 0-10"], + sa_slgp_s: "gap layers", + sa_slgp_l: ["number of layers between","raft and bottom of object"], + sa_sldn_s: "density", + sa_sldn_l: ["used to compute the","number of support pillars","0.0-1.0 (0 = disable)"], + sa_slsz_s: "size", + sa_slsz_l: ["max size of a","support pillar","in millimeters"], + sa_slpt_s: "points", + sa_slpt_l: ["number of points in","each support pillar","in millimeters"], + sl_slen_l: "enable supports", + + sa_outp_m: "output", + sa_opzo_s: "przesunięcie osi Z", + sa_opzo_l: ["z layer offset","almost always 0.0","0.0-1.0 in millimeters"], + sa_opaa_s: "antyaliasing", + sa_opaa_l: ["włączenie tej opcji","tworzy większe pliki","ale pokazuje przejścia tonalne"] +}; diff --git a/web/kiri/lang/pl.js b/web/kiri/lang/pl.js new file mode 120000 index 00000000..73247290 --- /dev/null +++ b/web/kiri/lang/pl.js @@ -0,0 +1 @@ +pl-pl.js \ No newline at end of file