Merge branch 'rel-4.2' into dev-4.2-refactor-module
This commit is contained in:
commit
ed722edff7
12 changed files with 256 additions and 130 deletions
23
.github/workflows/prettier-check.yml
vendored
Normal file
23
.github/workflows/prettier-check.yml
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
name: 'Docs Fomatting Check'
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- './docs/**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
format-check:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
- name: check format for docs
|
||||
run:
|
||||
bun docs-check
|
||||
|
|
@ -16,13 +16,13 @@ you can view some example of how to use the engine [here](https://grid.space/kir
|
|||
## Methods
|
||||
|
||||
| Method | Description | Returns |
|
||||
| --------------------------- | -------------------------------------------------------------------------------------------------------------- | ----------------- | ------- | ------- | -------- |
|
||||
| --------------------------- | -------------------------------------------------------------------------------------------------------------- | ----------------- |
|
||||
| `load(url)` | Loads an [STL](<https://en.wikipedia.org/wiki/STL_(file_format)>) file from a given URL and centers the model. | `Promise<Engine>` |
|
||||
| `clear()` | Clears all objects from workspace. | `void` |
|
||||
| `parse(data)` | Parses raw [STL](<https://en.wikipedia.org/wiki/STL_(file_format)>) data and loads it as a centered widget. | `Promise<Engine>` |
|
||||
| `setListener(listener)` | Sets an event listener function to receive engine progress updates | `Engine` |
|
||||
| `setRender(bool)` | Enables or disables rendering. | `Engine` |
|
||||
| `setMode(mode)` | Sets the slicing mode where mode is "CAM" | "FDM" | "LASER" | "SLA" . | `Engine` |
|
||||
| `setMode(mode)` | Sets the slicing mode where mode is "CAM" \| "FDM" \| "LASER" \| "SLA" . | `Engine` |
|
||||
| `setDevice(device)` | Merges a custom device profile into the current settings. | `Engine` |
|
||||
| `setProcess(process)` | Merges custom slicing process parameters. | `Engine` |
|
||||
| `setController(controller)` | Sets the slicing controller settings and starts/stops the worker pool accordingly. | `Engine` |
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ id: mesh-tool
|
|||
|
||||
[Mesh:Tool](https://grid.space/mesh) is an intentionally simplified tool designed to address the most common cases of mesh editing and repair. It is under active development with new features daily or weekly. This means the docs can get out of date quickly.
|
||||
|
||||
Mesh:Tool is part of the [Grid.Apps GitHub Repo](https://github.com/GridSpace/grid-apps) and is Open Source under the [MIT License](https://en.wikipedia.org/wiki/MIT\_License). It runs entirely in browser, is not connected to the cloud, and does not collect or share any model data. [Desktop builds](https://github.com/GridSpace/grid-apps/releases) are available whiel start in the Slicer view. Use the central drop-menu to switch to Mesh:Tool.
|
||||
Mesh:Tool is part of the [Grid.Apps GitHub Repo](https://github.com/GridSpace/grid-apps) and is Open Source under the [MIT License](https://en.wikipedia.org/wiki/MIT_License). It runs entirely in browser, is not connected to the cloud, and does not collect or share any model data. [Desktop builds](https://github.com/GridSpace/grid-apps/releases) are available whiel start in the Slicer view. Use the central drop-menu to switch to Mesh:Tool.
|
||||
|
||||
Browse the Mesh:Tool [video playlist](https://www.youtube.com/playlist?list=PLRoVgyRoWZps84Scj5wQ2LYK-wMu-7r0L) on YouTube
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ const sidebars = {
|
|||
{
|
||||
type: "doc",
|
||||
id: "index",
|
||||
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
|
|
@ -14,8 +13,8 @@ const sidebars = {
|
|||
type: "category",
|
||||
label: "Kiri Moto",
|
||||
link: {
|
||||
type: 'doc',
|
||||
id: 'kiri-moto/index',
|
||||
type: "doc",
|
||||
id: "kiri-moto/index",
|
||||
},
|
||||
items: [
|
||||
"kiri-moto/interface",
|
||||
|
|
@ -31,17 +30,17 @@ const sidebars = {
|
|||
{
|
||||
type: "category",
|
||||
label: "CAM Mode",
|
||||
link:{
|
||||
type: 'doc',
|
||||
id: 'kiri-moto/CAM/index',
|
||||
link: {
|
||||
type: "doc",
|
||||
id: "kiri-moto/CAM/index",
|
||||
},
|
||||
items:[
|
||||
items: [
|
||||
"kiri-moto/CAM/processOpts",
|
||||
"kiri-moto/CAM/machines",
|
||||
"kiri-moto/CAM/tools",
|
||||
"kiri-moto/CAM/ops",
|
||||
],
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -52,39 +51,37 @@ const sidebars = {
|
|||
type: "category",
|
||||
label: "GridBot",
|
||||
link: {
|
||||
type: 'doc',
|
||||
id: 'gridbot/index',
|
||||
type: "doc",
|
||||
id: "gridbot/index",
|
||||
},
|
||||
items: [
|
||||
"gridbot/bom",
|
||||
]
|
||||
}
|
||||
items: ["gridbot/bom"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Community',
|
||||
type: "category",
|
||||
label: "Community",
|
||||
collapsed: false,
|
||||
items: [
|
||||
{
|
||||
type: 'link',
|
||||
label: 'Forums',
|
||||
href: 'https://forum.grid.space',
|
||||
type: "link",
|
||||
label: "Forums",
|
||||
href: "https://forum.grid.space",
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
label: 'Discord',
|
||||
href: 'https://discord.gg/suyCCgr',
|
||||
type: "link",
|
||||
label: "Discord",
|
||||
href: "https://discord.gg/suyCCgr",
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
label: 'GitHub',
|
||||
href: 'https://github.com/GridSpace/grid-apps',
|
||||
type: "link",
|
||||
label: "GitHub",
|
||||
href: "https://github.com/GridSpace/grid-apps",
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
label: 'BlueSky',
|
||||
href: 'https://bsky.app/profile/grid.space',
|
||||
type: "link",
|
||||
label: "BlueSky",
|
||||
href: "https://bsky.app/profile/grid.space",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -104,4 +101,3 @@ const sidebars = {
|
|||
};
|
||||
|
||||
module.exports = sidebars;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "grid-apps",
|
||||
"version": "4.1.9",
|
||||
"version": "4.2.0",
|
||||
"description": "grid.space 3d slicing & modeling tools",
|
||||
"author": "Stewart Allen <sa@grid.space>",
|
||||
"license": "MIT",
|
||||
|
|
@ -50,6 +50,7 @@
|
|||
"jszip": "^3.7.1",
|
||||
"manifold-3d": "^3.1.1",
|
||||
"moment": "^2.29.4",
|
||||
"prettier": "^3.5.3",
|
||||
"react-responsive-carousel": "^3.2.23",
|
||||
"serve-static": "^1.14.1",
|
||||
"three": "^0.174.0",
|
||||
|
|
@ -99,7 +100,8 @@
|
|||
"preinstall2": "npx webpack --config src2/webpack/webpack-three-esm.js",
|
||||
"docs-dev": "docusaurus start",
|
||||
"docs-build": "docusaurus build",
|
||||
"docs-serve": "docusaurus serve"
|
||||
"docs-serve": "docusaurus serve",
|
||||
"docs-check": "prettier ./docs --check"
|
||||
},
|
||||
"main": "app-el.js",
|
||||
"build": {
|
||||
|
|
|
|||
|
|
@ -1002,6 +1002,23 @@ class Polygon {
|
|||
return this.z !== undefined ? this.z : this.points[i || 0]?.z || 0;
|
||||
}
|
||||
|
||||
minZ() {
|
||||
return Math.min(...this.points.map(p => p.z));
|
||||
}
|
||||
|
||||
avgZ() {
|
||||
return [...this.points.map(p => p.z)].reduce((a,v) => a+v) / this.points.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} z value target
|
||||
* @param {*} epsilon allowed Z variance
|
||||
* @returns {boolean} true if all points Z within epsilon of value
|
||||
*/
|
||||
onZ(z, epsilon = 10e-4) {
|
||||
return Math.max(...this.points.map(p => Math.abs(z - p.z))) < epsilon;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
render(layer, color, recursive, open) {
|
||||
|
|
|
|||
|
|
@ -1008,7 +1008,6 @@ CAM.init = function(kiri, api) {
|
|||
CAM.traces((ids) => {
|
||||
api.hide.alert(alert);
|
||||
alert = api.show.alert("[esc] cancels trace editing");
|
||||
kiri.api.widgets.opacity(0.8);
|
||||
kiri.api.widgets.for(widget => {
|
||||
if (ids.indexOf(widget.id) >= 0) {
|
||||
unselectTraces(widget, true);
|
||||
|
|
@ -1042,6 +1041,7 @@ CAM.init = function(kiri, api) {
|
|||
});
|
||||
// ensure appropriate traces are toggled matching current record
|
||||
kiri.api.widgets.for(widget => {
|
||||
widget.setVisualState({ opacity: 0.25 });
|
||||
let areas = (poppedRec.areas[widget.id] || []);
|
||||
let stack = widget.trace_stack;
|
||||
stack.meshes.forEach(mesh => {
|
||||
|
|
@ -1070,6 +1070,7 @@ CAM.init = function(kiri, api) {
|
|||
api.feature.hover = false;
|
||||
api.feature.hoverAdds = false;
|
||||
kiri.api.widgets.for(widget => {
|
||||
widget.restoreVisualState();
|
||||
if (widget.trace_stack) {
|
||||
widget.trace_stack.hide();
|
||||
widget.adds.removeAll(widget.trace_stack.meshes);
|
||||
|
|
@ -1096,17 +1097,15 @@ CAM.init = function(kiri, api) {
|
|||
color.r = colorSave.r;
|
||||
color.g = colorSave.g;
|
||||
color.b = colorSave.b;
|
||||
lastTrace.position.z -= 0.01;
|
||||
}
|
||||
lastTrace = null;
|
||||
if (data.type === 'platform') {
|
||||
lastTrace = null;
|
||||
return;
|
||||
}
|
||||
if (!data.int.object.trace) {
|
||||
return;
|
||||
}
|
||||
lastTrace = data.int.object;
|
||||
lastTrace.position.z += 0.01;
|
||||
if (lastTrace.selected) {
|
||||
let event = data.event;
|
||||
let target = event.target;
|
||||
|
|
@ -1115,11 +1114,8 @@ CAM.init = function(kiri, api) {
|
|||
}
|
||||
let material = lastTrace.material[0] || lastTrace.material;
|
||||
let color = material.color;
|
||||
let {r, g, b} = color;
|
||||
material.colorSave = {r, g, b};
|
||||
color.r = 0;
|
||||
color.g = 0;
|
||||
color.b = 1;
|
||||
material.colorSave = color.clone();
|
||||
color.setHex(isDark() ? 0x0066ff : 0x0000ff);
|
||||
};
|
||||
func.traceHoverUp = function(int, ev) {
|
||||
if (!int) return;
|
||||
|
|
@ -1128,8 +1124,9 @@ CAM.init = function(kiri, api) {
|
|||
if (ev.metaKey || ev.ctrlKey) {
|
||||
let { selected } = object;
|
||||
let { widget, poly } = object.trace;
|
||||
let avgZ = poly.avgZ();
|
||||
for (let add of widget.adds) {
|
||||
if (add.trace && add.selected !== selected && add.trace.poly.getZ() === poly.getZ()) {
|
||||
if (add.trace && add.selected !== selected && add.trace.poly.onZ(avgZ)) {
|
||||
func.traceToggle(add);
|
||||
}
|
||||
}
|
||||
|
|
@ -1147,23 +1144,17 @@ CAM.init = function(kiri, api) {
|
|||
let wlist = areas[widget.id] = areas[widget.id] || [];
|
||||
obj.selected = !obj.selected;
|
||||
if (!colorSave) {
|
||||
colorSave = material.colorSave = {
|
||||
r: color.r,
|
||||
g: color.g,
|
||||
b: color.b
|
||||
};
|
||||
colorSave = material.colorSave = color.clone();
|
||||
}
|
||||
if (obj.selected) {
|
||||
obj.position.z += 0.01;
|
||||
color.r = colorSave.r = 0.9;
|
||||
color.g = colorSave.g = 0;
|
||||
color.b = colorSave.b = 0.1;
|
||||
color.setHex(isDark() ? 0xdd0011 : 0xff0033);
|
||||
colorSave.r = color.r;
|
||||
colorSave.g = color.g;
|
||||
colorSave.b = color.b;
|
||||
if (!skip) wlist.push(poly._trace);
|
||||
} else {
|
||||
obj.position.z -= 0.01;
|
||||
color.r = colorSave.r = 0xaa/255;
|
||||
color.g = colorSave.g = 0xaa/255;
|
||||
color.b = colorSave.b = 0x55/255;
|
||||
color.setHex(0xaaaa55);
|
||||
colorSave.setHex(0xaaaa55);
|
||||
if (!skip) wlist.remove(poly._trace);
|
||||
}
|
||||
API.conf.save();
|
||||
|
|
@ -1192,9 +1183,6 @@ CAM.init = function(kiri, api) {
|
|||
func.hover = func.selectHolesHover;
|
||||
func.hoverUp = func.selectHolesHoverUp;
|
||||
|
||||
|
||||
|
||||
|
||||
const widgets = kiri.api.widgets.all()
|
||||
/**
|
||||
* creates a mesh for a hole and adds it to a widget
|
||||
|
|
@ -1712,13 +1700,11 @@ CAM.init = function(kiri, api) {
|
|||
dogbone: 'camTraceDogbone',
|
||||
revbone: 'camTraceDogbone',
|
||||
merge: 'camTraceMerge',
|
||||
select: 'camTraceMode',
|
||||
ov_topz: 0,
|
||||
ov_botz: 0,
|
||||
ov_conv: '~camConventional',
|
||||
}).inputs = {
|
||||
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
|
||||
select: UC.newSelect(LANG.cc_sele_s, {title:LANG.cc_sele_l}, "select"),
|
||||
mode: UC.newSelect(LANG.cu_type_s, {title:LANG.cu_type_l}, "trace"),
|
||||
offset: UC.newSelect(LANG.cc_offs_s, {title: LANG.cc_offs_l, show:() => (poppedRec.mode === 'follow')}, "traceoff"),
|
||||
sep: UC.newBlank({class:"pop-sep"}),
|
||||
|
|
@ -1969,10 +1955,12 @@ function createPopOp(type, map) {
|
|||
const settings = conf.get();
|
||||
const {tool} = new CAM.Tool(settings,op.rec.tool); //get tool by id
|
||||
const opType = op.rec.type
|
||||
if( opType != "drill" && tool.type == "drill"){
|
||||
const drillOrRegister = opType == "drill" || opType == "register"
|
||||
|
||||
if ( !drillOrRegister && tool.type == "drill"){
|
||||
alerts.show(`Warning: Drills should not be used for ${opType} operations.`)
|
||||
}
|
||||
else if( opType == "drill" && tool.type != "drill"){
|
||||
else if ( drillOrRegister && tool.type != "drill"){
|
||||
alerts.show(`Warning: Only drills should be used for drilling operations.`)
|
||||
}
|
||||
|
||||
|
|
@ -2160,16 +2148,18 @@ function validateTools(tools) {
|
|||
}
|
||||
}
|
||||
|
||||
function isDark() { return API.space.is_dark() };
|
||||
|
||||
function addbox() { return FDM.addbox(...arguments)};
|
||||
|
||||
function delbox() { return FDM.delbox(...arguments)};
|
||||
|
||||
function boxColor() {
|
||||
return API.space.is_dark() ? 0x00ddff : 0x0000dd;
|
||||
return isDark() ? 0x00ddff : 0x0000dd;
|
||||
}
|
||||
|
||||
function boxOpacity() {
|
||||
return API.space.is_dark() ? 0.75 : 0.6;
|
||||
return isDark() ? 0.75 : 0.6;
|
||||
}
|
||||
|
||||
function animate() {
|
||||
|
|
@ -2252,7 +2242,7 @@ function updateStock() {
|
|||
camStock.position.z = center.z;
|
||||
camStock.rotation.x = currentIndex || 0;
|
||||
camStock.lines.material.color =
|
||||
new THREE.Color(API.space.is_dark() ? 0x555555 : 0xaaaaaa);
|
||||
new THREE.Color(isDark() ? 0x555555 : 0xaaaaaa);
|
||||
} else if (camStock) {
|
||||
SPACE.world.remove(camStock);
|
||||
camStock = null;
|
||||
|
|
|
|||
|
|
@ -976,11 +976,12 @@ class OpTrace extends CamOp {
|
|||
}
|
||||
|
||||
async slice(progress) {
|
||||
const debug = false;
|
||||
let { op, state } = this;
|
||||
let { tool, rate, down, plunge, offset, offover, thru } = op;
|
||||
let { ov_conv } = op;
|
||||
let { settings, widget, addSlices, zThru, tabs, workarea } = state;
|
||||
let { updateToolDiams, cutTabs, cutPolys, healPolys, color } = state;
|
||||
let { updateToolDiams, cutTabs, cutPolys, healPolys, color, shadowAt } = state;
|
||||
let { process, stock } = settings;
|
||||
let { camStockClipTo } = process;
|
||||
if (state.isIndexed) {
|
||||
|
|
@ -1042,17 +1043,39 @@ class OpTrace extends CamOp {
|
|||
.setLayer("trace follow", {line: color}, false)
|
||||
.addPolys(slice.camLines)
|
||||
}
|
||||
function clearZ(polys, z, down) {
|
||||
function clearZnew(polys, z, down) {
|
||||
if (down) {
|
||||
// adjust step down to a value <= down that
|
||||
// ends on the lowest z specified
|
||||
let diff = zTop - z;
|
||||
down = diff / Math.ceil(diff / down);
|
||||
}
|
||||
let zs = down ? base.util.lerp(zTop, z, down) : [ z ];
|
||||
let nested = POLY.nest(polys);
|
||||
for (let poly of nested) {
|
||||
let zpro = 0, zinc = 1 / (polys.length * zs.length);
|
||||
for (let poly of polys) {
|
||||
// newPocket();
|
||||
for (let z of zs) {
|
||||
let clip = [], shadow;
|
||||
shadow = shadowAt(z);
|
||||
POLY.subtract([ poly ], shadow, clip, undefined, undefined, 0);
|
||||
if (op.outline) {
|
||||
POLY.clearInner(clip);
|
||||
}
|
||||
if (clip.length === 0) {
|
||||
continue;
|
||||
}
|
||||
let slice = newSliceOut(z);
|
||||
let count = 999;
|
||||
slice.camTrace = { tool, rate, plunge };
|
||||
POLY.offset([ poly ], [ -toolDiam/2, -toolOver ], {
|
||||
count:999, outs: slice.camLines = [], flat:true, z,
|
||||
minArea: 0
|
||||
});
|
||||
if (toolDiam) {
|
||||
const offs = [ -toolDiam / 2, -toolOver ];
|
||||
POLY.offset(clip, offs, {
|
||||
count, outs: slice.camLines = [], flat:true, z, minArea: 0
|
||||
});
|
||||
} else {
|
||||
// when engraving with a 0 width tip
|
||||
slice.camLines = clip;
|
||||
}
|
||||
if (tabs) {
|
||||
slice.camLines = cutTabs(tabs, POLY.flatten(slice.camLines, null, true), z);
|
||||
} else {
|
||||
|
|
@ -1060,8 +1083,14 @@ class OpTrace extends CamOp {
|
|||
}
|
||||
POLY.setWinding(slice.camLines, cutdir, false);
|
||||
slice.output()
|
||||
.setLayer("trace clear", {line: color}, false)
|
||||
.setLayer("trace", {line: color}, false)
|
||||
.addPolys(slice.camLines)
|
||||
if (debug && shadow) slice.output()
|
||||
.setLayer("trace shadow", {line: 0xff8811}, false)
|
||||
.addPolys(shadow)
|
||||
progress(zpro, "trace");
|
||||
zpro += zinc;
|
||||
addSlices(slice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1176,7 +1205,7 @@ class OpTrace extends CamOp {
|
|||
const zbo = widget.track.top - widget.track.box.d;
|
||||
let zmap = {};
|
||||
for (let poly of polys) {
|
||||
let z = minZ(poly.getZ());
|
||||
let z = minZ(poly.minZ());
|
||||
if (offover) {
|
||||
let pnew = POLY.offset([poly], -offover, { minArea: 0, open: true });
|
||||
if (pnew) {
|
||||
|
|
@ -1190,7 +1219,7 @@ class OpTrace extends CamOp {
|
|||
(zmap[z] = zmap[z] || []).appendAll(poly);
|
||||
}
|
||||
for (let [zv, polys] of Object.entries(zmap)) {
|
||||
clearZ(polys, parseFloat(zv), down);
|
||||
clearZnew(polys, parseFloat(zv), down);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
// dep: geo.line
|
||||
// dep: geo.point
|
||||
// dep: geo.polygons
|
||||
// dep: kiri.slice
|
||||
|
|
@ -14,7 +15,7 @@ gapp.register("kiri-mode.cam.slice", (root, exports) => {
|
|||
const { base, kiri } = root;
|
||||
const { util } = base;
|
||||
const { driver, newSlice, setSliceTracker } = kiri;
|
||||
const { polygons, newPoint, newPolygon } = base;
|
||||
const { polygons, newLine, newPoint, newPolygon } = base;
|
||||
const { CAM } = driver;
|
||||
const { OPS } = CAM;
|
||||
|
||||
|
|
@ -379,60 +380,107 @@ CAM.addDogbones = function(poly, dist, reverse) {
|
|||
};
|
||||
|
||||
CAM.traces = async function(settings, widget, single) {
|
||||
if (widget.traces && widget.trace_single === single) {
|
||||
// do no work if cached
|
||||
if (widget.traces) {
|
||||
return false;
|
||||
}
|
||||
let slicer = new kiri.cam_slicer(widget);
|
||||
let zees = Object.assign({}, slicer.zFlat, slicer.zLine);
|
||||
let indices = [...new Set(Object.keys(zees)
|
||||
.map(kv => parseFloat(kv).round(5))
|
||||
.appendAll(Object.entries(single ? slicer.zLine : {}).map(ze => {
|
||||
let [ zk, zv ] = ze;
|
||||
return zv > 1 ? parseFloat(zk).round(5) : null;
|
||||
})
|
||||
.filter(v => v !== null))
|
||||
)]
|
||||
.sort((a,b) => b - a);
|
||||
let traces = [];
|
||||
// find and trim polys (including open) to shadow
|
||||
let oneach = data => {
|
||||
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).forEach(poly => {
|
||||
poly.inner = null;
|
||||
poly.parent = null;
|
||||
let z = poly.getZ();
|
||||
for (let i=0, il=traces.length; i<il; i++) {
|
||||
let trace = traces[i];
|
||||
let dz = Math.abs(z - trace.getZ());
|
||||
// only compare polys farther apart in Z
|
||||
if (dz > 0.01) {
|
||||
continue;
|
||||
}
|
||||
// do not add duplicates
|
||||
if (traces[i].isEquivalent(poly) && dz < 0.1) {
|
||||
// console.log({ dup: poly });
|
||||
return;
|
||||
}
|
||||
}
|
||||
traces.push(poly);
|
||||
});
|
||||
};
|
||||
|
||||
// --- points → line segments ---
|
||||
let edges = new THREE.EdgesGeometry(widget.mesh.geometry, 20);
|
||||
let array = edges.attributes.position.array;
|
||||
let pcache = {};
|
||||
let points = new Array(2);
|
||||
let lines = new Array(points.length / 2);
|
||||
for (let i=0, j=0, k=0, l=array.length; i<l; ) {
|
||||
let ps = [ array[i++], array[i++], array[i++] ];
|
||||
let key = ps.map(v => (v * 10000) | 0).join(',');
|
||||
let point = pcache[key];
|
||||
if (!point) {
|
||||
point = newPoint(ps[0], ps[1], ps[2], key);
|
||||
point.lines = [];
|
||||
pcache[key] = point;
|
||||
}
|
||||
points[j++ % 2] = point;
|
||||
if (j % 2 === 0) {
|
||||
let [ p0, p1 ] = points;
|
||||
let line = lines[k++] = newLine(p0, p1);
|
||||
p0.lines.push(line);
|
||||
p1.lines.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
// --- segments → chains (bidirectional walk) ---
|
||||
let chains = [];
|
||||
{
|
||||
const segs = lines.slice(); // shallow copy
|
||||
segs.forEach(s => (s.visited = false));
|
||||
|
||||
function step(dirPoint, prevSeg, pushFront, chain) {
|
||||
let curr = dirPoint;
|
||||
let prev = prevSeg;
|
||||
|
||||
while (curr.lines.length === 2) { // stay inside the arc
|
||||
const next = curr.lines.find(l => !l.visited && l !== prev);
|
||||
if (!next) break; // should not happen
|
||||
|
||||
next.visited = true;
|
||||
if (pushFront) chain.unshift(next); // prepend or append?
|
||||
else chain.push(next);
|
||||
|
||||
curr = next.p1 === curr ? next.p2 : next.p1;
|
||||
prev = next;
|
||||
}
|
||||
}
|
||||
|
||||
for (const seed of segs) {
|
||||
if (seed.visited) continue;
|
||||
|
||||
seed.visited = true; // always include the seed
|
||||
const chain = [seed];
|
||||
|
||||
// grow backwards from p1 (prepend)
|
||||
step(seed.p1, seed, true, chain);
|
||||
|
||||
// grow forwards from p2 (append)
|
||||
step(seed.p2, seed, false, chain);
|
||||
|
||||
chains.push(chain);
|
||||
}
|
||||
}
|
||||
|
||||
// --- chains → polylines ---
|
||||
const polylines = [];
|
||||
|
||||
for (const chain of chains) {
|
||||
const poly = newPolygon().setOpen();
|
||||
|
||||
// choose any node whose degree !== 2; if none, it’s a closed loop
|
||||
let start = null;
|
||||
for (const s of chain) {
|
||||
if (s.p1.lines.length !== 2) { start = s.p1; break; }
|
||||
if (s.p2.lines.length !== 2) { start = s.p2; break; }
|
||||
}
|
||||
if (!start) start = chain[0].p1; // closed loop
|
||||
|
||||
let curr = start, prevSeg = null;
|
||||
poly.push(curr); // first point
|
||||
|
||||
while (true) {
|
||||
const nextSeg = curr.lines.find(
|
||||
l => l !== prevSeg && chain.includes(l)
|
||||
);
|
||||
if (!nextSeg) break; // open end reached
|
||||
|
||||
curr = nextSeg.p1 === curr ? nextSeg.p2 : nextSeg.p1;
|
||||
poly.push(curr); // << push *before* test
|
||||
if (curr === start) break; // loop closed
|
||||
|
||||
prevSeg = nextSeg;
|
||||
}
|
||||
polylines.push(poly);
|
||||
}
|
||||
|
||||
widget.traces = polylines;
|
||||
|
||||
let opts = { each: oneach, over: false, flatoff: 0, edges: true, openok: true, lines: true };
|
||||
await slicer.slice(indices, opts);
|
||||
// pick up bottom features
|
||||
opts.over = true;
|
||||
await slicer.slice(indices, opts);
|
||||
widget.traces = traces;
|
||||
widget.trace_single = single;
|
||||
return true;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -355,6 +355,27 @@ class Widget {
|
|||
}
|
||||
}
|
||||
|
||||
getVisualState() {
|
||||
return {
|
||||
edges: this.outline ? true : false,
|
||||
wires: this.wire ? true : false,
|
||||
opacity: this.getMaterial().opacity
|
||||
};
|
||||
}
|
||||
|
||||
setVisualState({ edges, wires, opacity}) {
|
||||
this.cache.vizstate = this.getVisualState();
|
||||
this.setEdges(edges ?? false);
|
||||
this.setWireframe(wires ?? false);
|
||||
this.setOpacity(opacity ?? 1);
|
||||
}
|
||||
|
||||
restoreVisualState() {
|
||||
if (this.cache.vizstate) {
|
||||
this.setVisualState(this.cache.vizstate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} value
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
let terms = {
|
||||
COPYRIGHT: "Copyright (C) Stewart Allen <sa@grid.space> - All Rights Reserved",
|
||||
LICENSE: "See the license.md file included with the source distribution",
|
||||
VERSION: (is_self ? self : this).debug_version || "4.1.9"
|
||||
VERSION: (is_self ? self : this).debug_version || "4.2.0"
|
||||
};
|
||||
|
||||
if (typeof(module) === 'object') {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "Kiri:Moto 4.1.9",
|
||||
"name": "Kiri:Moto 4.2.0",
|
||||
"short_name": "Kiri:Moto",
|
||||
"description": "Slicer for 3D printers, CNC mills, laser cutters and more",
|
||||
"start_url": "/kiri/",
|
||||
|
|
|
|||
Loading…
Reference in a new issue