From 54ffc506bd8184af2314cee9fda9a7af7f5aa7a5 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Thu, 25 Jul 2024 15:52:17 -0400 Subject: [PATCH] first sketch union --- src/geo/polygon.js | 13 +++++++ src/mesh/api.js | 68 +++++++++++++++++++++++++++++++++---- src/mesh/build.js | 34 +++++++++++++++++-- src/mesh/sketch.js | 85 ++++++++++++++++++++++++++++++++++++---------- 4 files changed, 172 insertions(+), 28 deletions(-) diff --git a/src/geo/polygon.js b/src/geo/polygon.js index 572bf99c..935e0827 100644 --- a/src/geo/polygon.js +++ b/src/geo/polygon.js @@ -110,6 +110,19 @@ class Polygon { return this; } + toObject() { + return { + points: this.toArray(), + inner: this.inner?.map(i => i.toArray()) + }; + } + + fromObject(obj) { + this.fromArray(obj.points); + this.inner = obj.inner?.map(a => newPolygon().fromArray(a)) + return this; + } + matches(poly) { let tarr = Array.isArray(poly) ? poly : poly.toArray(); let parr = this.toArray(); diff --git a/src/mesh/api.js b/src/mesh/api.js index 176c61c3..35e71b70 100644 --- a/src/mesh/api.js +++ b/src/mesh/api.js @@ -62,6 +62,10 @@ const selection = { return mdl; }, + sketch() { + return selection.sketches()[0]; + }, + sketches() { return selection.list(true).filter(s => s.type === 'sketch'); }, @@ -130,11 +134,10 @@ const selection = { // remove all delete() { - let list = selection.list(true); - if (list[0]?.type === 'sketch' && list[0].selection_delete()) { + if (sketch.selection.delete()) { return; } - for (let s of list) { + for (let s of selection.list()) { selection.remove(s); tools.remove(s); s.showBounds(false); @@ -160,9 +163,8 @@ const selection = { }, clear() { - let list = selection.list(true); - if (list[0]?.type === 'sketch' && list[0].selection_clear()) { - return false; + if (sketch.selection.clear()) { + return; } for (let s of selection.list()) { selection.remove(s); @@ -442,8 +444,60 @@ let sketch = { extrude() { selection.sketches(true).forEach(sketch => { - sketch.extrude(); + sketch.extrude(...arguments); }); + }, + + selection: { + get one() { + return selection.sketch(); + }, + + list() { + return selection.sketches(); + }, + + clear() { + let list = selection.list(true); + return list[0]?.type === 'sketch' && list[0].selection.clear(); + }, + + delete() { + let list = selection.list(true); + return list[0]?.type === 'sketch' && list[0].selection.delete(); + } + }, + + boolean: { + group() { + sketch.selection.one?.boolean.group(); + }, + + ungroup() { + sketch.selection.one?.boolean.ungroup(); + }, + + union() { + sketch.selection.one?.boolean.union(); + }, + + intersect() { + sketch.selection.one?.boolean.intersect(); + }, + + difference() { + sketch.selection.one?.boolean.difference(); + } + }, + + pattern: { + circle() { + sketch.selection.one?.pattern.circle(); + }, + + grid() { + sketch.selection.one?.pattern.grid(); + } } }; diff --git a/src/mesh/build.js b/src/mesh/build.js index 6763c959..964a9a82 100644 --- a/src/mesh/build.js +++ b/src/mesh/build.js @@ -352,7 +352,7 @@ function ui_build() { h.bind(logger, [ h.div({ id: 'logtext' }) ]); // a few shortcuts to api calls - let { file, selection, mode, tool, prefs, add } = api; + let { file, selection, mode, tool, sketch, prefs, add } = api; // top/center mode selector h.bind(modes, [ h.div([ @@ -415,7 +415,7 @@ function ui_build() { h.button({ _: 'wireframe', onclick() { api.wireframe() } }), ]) ]), - h.div([ + h.div({ class: "sketch-off" }, [ h.div({ _: "boolean", class: "head" }), h.div({ class: "back"}), h.div({ class: "pop"}, [ @@ -425,7 +425,19 @@ function ui_build() { h.button({ _: 'difference', onclick: tool.difference }), ]) ]), - h.div([ + h.div({ class: "sketch-on" }, [ + h.div({ _: "boolean", class: "head" }), + h.div({ class: "back"}), + h.div({ class: "pop"}, [ + h.button({ _: 'union', onclick: sketch.boolean.union }), + h.button({ _: 'intersect', onclick: sketch.boolean.intersect }), + h.button({ _: 'difference', onclick: sketch.boolean.difference }), + h.hr(), + h.button({ _: 'group', onclick: sketch.boolean.union }), + h.button({ _: 'ungroup', onclick: sketch.boolean.union }), + ]) + ]), + h.div({ class: "sketch-off" }, [ h.div({ _: "pattern", class: "head" }), h.div({ class: "back"}), h.div({ class: "pop"}, [ @@ -433,6 +445,22 @@ function ui_build() { h.button({ _: 'grid', onclick() { api.pattern.grid() } }), ]) ]), + h.div({ class: "sketch-on" }, [ + h.div({ _: "pattern", class: "head" }), + h.div({ class: "back"}), + h.div({ class: "pop"}, [ + h.button({ _: 'circular', onclick() { sketch.pattern.circle() } }), + h.button({ _: 'grid', onclick() { sketch.pattern.grid() } }), + ]) + ]), + h.div({ class: "sketch-on" }, [ + h.div({ _: "extrude", class: "head" }), + h.div({ class: "back"}), + h.div({ class: "pop"}, [ + h.button({ _: 'sketch', onclick() { sketch.extrude() } }), + h.button({ _: 'selection', onclick() { sketch.extrude({ selection: true }) } }), + ]) + ]), h.div({ class: "sketch-off" }, [ h.div({ _: "models", class: "head" }), h.div({ class: "back"}), diff --git a/src/mesh/sketch.js b/src/mesh/sketch.js index 49911c21..fc1a5d66 100644 --- a/src/mesh/sketch.js +++ b/src/mesh/sketch.js @@ -5,6 +5,7 @@ // dep: add.array // dep: add.three // dep: geo.polygon +// dep: geo.polygons // dep: moto.license // dep: moto.client // dep: mesh.object @@ -23,6 +24,7 @@ const { newPolygon } = base; const mapp = mesh; const worker = moto.client.fn; +const POLYS = base.polygons; const material = { normal: new MeshBasicMaterial({ color: 0x888888, side: DoubleSide, transparent: true, opacity: 0.25 }), @@ -131,28 +133,62 @@ mesh.sketch = class MeshSketch extends mesh.object { }).reverse(); } - get selected_items() { - return this.items.filter(i => i.selected); + get selection() { + let sketch = this; + return { + // for boolean and patterning, we need the polygon artifacts + mesh_items() { + return sketch.group.children.filter(c => c.sketch_item && c.sketch_item.selected); + }, + + // return true if any selections were cleared + clear() { + let sel = sketch.items.filter(i => i.selected); + if (sel.length) { + sel.forEach(s => s.selected = false); + sketch.render(); + } + return sel.length; + }, + + // return true if any selections were deleted + delete() { + let sel = sketch.items.filter(i => i.selected); + if (sel.length) { + sketch.items = sketch.items.filter(i => !i.selected); + sketch.render(); + } + return sel.length; + } + } } - // return true if any selections were cleared - selection_clear() { - let sel = this.items.filter(i => i.selected); - if (sel.length) { - sel.forEach(s => s.selected = false); - this.render(); + get boolean() { + let sketch = this; + return { + union() { + let items = sketch.selection.mesh_items(); + let polys = items.map(i => i.sketch_item.poly); + let union = POLYS.union(polys, 0, true); + console.log('sketch union', union); + sketch.selection.delete(); + for (let poly of union) { + sketch.add_polygon({ poly }); + } + } } - return sel.length; } - // return true if any selections were deleted - selection_delete() { - let sel = this.items.filter(i => i.selected); - if (sel.length) { - this.items = this.items.filter(i => !i.selected); - this.render(); + get pattern() { + return { + circle() { + console.log('sketch pattern circle'); + }, + + grid() { + console.log('sketch pattern grid'); + } } - return sel.length; } rename(newname) { @@ -250,6 +286,13 @@ mesh.sketch = class MeshSketch extends mesh.object { this.render(); } + add_polygon(opt = {}) { + log(this.file || this.id, '| add polygon'); + Object.assign(opt, { center: {x:0, y:0, z:0} }, opt); + this.items.push({ type: "polygon", ...opt, ...opt.poly.toObject() }); + this.render(); + } + // render items unto the group object render() { let { group } = this; @@ -264,9 +307,12 @@ mesh.sketch = class MeshSketch extends mesh.object { } extrude(opt = {}) { - let { z } = opt; + let { z, selection } = opt; let models = []; - for (let item of this.group.children.filter(c => c.sketch_item)) { + let items = this.group.children + .filter(c => c.sketch_item) + .filter(c => !selection || c.sketch_item.selected); + for (let item of items) { let vert = item.sketch_item.extrude(z || 10); let nmdl = new mesh.model({ file: "item", mesh: vert.toFloat32() }); models.push(nmdl); @@ -318,6 +364,9 @@ class SketchItem { poly = newPolygon().centerCircle(center, radius, points).annotate({ item } ); } else if (type === 'rectangle') { poly = newPolygon().centerRectangle(center, width, height).annotate({ item }); + } else if (type === 'polygon') { + poly = newPolygon().fromObject(item); + poly.move(center); } else { throw `invalid sketch type: ${type}`; }