add Mesh:Tool SVG import options for extrusion depth and boolean repair

This commit is contained in:
Stewart Allen 2022-10-30 21:23:20 -04:00
commit 5df0262d8f
4 changed files with 90 additions and 15 deletions

View file

@ -4,6 +4,7 @@ Full docs @ https://docs.grid.space/projects/kiri-moto
# Release 3.7
* add Mesh:Tool SVG import options for extrusion depth and boolean repair
* replace jscad/modeling with Manifold project for faster mesh boolean
* various CAM gcode, preview, animation fixes (3 axis)
* various mobile touch, file load fixes

View file

@ -16,19 +16,19 @@ if (load.File) return;
gapp.register('load.file', []);
const types = {
stl(data, file, resolve, reject) {
stl(data, file, resolve, reject, opt = {}) {
resolve([{
mesh: new load.STL().parse(data), file
}]);
},
obj(data, file, resolve, reject) {
obj(data, file, resolve, reject, opt = {}) {
resolve(load.OBJ.parse(data).map(m => {
return { mesh: m.toFloat32(), file: nameOf(file, m.name, 1) }
}));
},
"3mf"(data, file, resolve, reject) {
"3mf"(data, file, resolve, reject, opt = {}) {
let i = 1;
load.TMF.parseAsync(data).then((meshes) => {
resolve(meshes.map(m => {
@ -37,12 +37,13 @@ const types = {
});
},
svg(data, file, resolve, reject) {
resolve(load.SVG.parse(data).map(m => { return {mesh: m.toFloat32(), file} }));
svg(data, file, resolve, reject, opt = {}) {
resolve(load.SVG.parse(data, opt).map(m => { return {mesh: m.toFloat32(), file} }));
},
png(data, file, resolve, reject) {
png(data, file, resolve, reject, opt = {}) {
load.PNG.parse(data, {
...opt,
done(data) {
resolve({ mesh: data, file });
}
@ -72,7 +73,7 @@ function load_data(data, file, ext, opt = {}) {
return new Promise((resolve, reject) => {
let fn = types[ext];
if (fn) {
fn(data, file, resolve, reject);
fn(data, file, resolve, reject, opt);
} else {
reject(`unknown file type: "${ext}" from ${file}`);
}

View file

@ -17,17 +17,29 @@ function parseAsync(text, opt) {
}
function parse(text, opt = {}) {
let objs = [];
let data = new THREE.SVGLoader().parse(text);
let paths = data.paths;
let xmlat = data.xml.attributes;
let depth = opt.depth || xmlat['data-km-extrude']
const fromSoup = opt.soup || false;
const objs = [];
const data = new THREE.SVGLoader().parse(text);
const paths = data.paths;
const xmlat = data.xml.attributes;
const polys = fromSoup ? [] : undefined;
const depth = opt.depth || xmlat['data-km-extrude']
|| xmlat['extrude']
|| {value: 5};
for (let i = 0; i < paths.length; i++) {
let path = paths[i];
let shapes = path.toShapes(true);
if (fromSoup) {
for (let node of shapes) {
let { shape, holes } = node.extractPoints();
for (let path of [ shape, ...holes ]) {
polys.push(base.newPolygon().addPoints(path.map(p => base.newPoint(p.x, p.y, 0))));
}
}
continue;
}
let geom = new THREE.ExtrudeGeometry(shapes, {
steps: 1,
depth: parseFloat(depth.value),
@ -49,6 +61,41 @@ function parse(text, opt = {}) {
objs.push([ ...array ]);
}
if (fromSoup) {
const nest = base.polygons.nest(polys.filter(p => {
// filter duplicates
for (let pc of polys) {
if (pc === p) {
return true;
} else {
return !pc.isEquivalent(p);
}
}
}));
let z = parseFloat(depth.value);
for (let poly of nest) {
let obj = [];
let earcut = poly.earcut();
for (let poly of earcut) {
for (let point of poly.points) {
obj.push(point.x, point.y, z);
}
for (let point of poly.points.reverse()) {
obj.push(point.x, point.y, 0);
}
}
obj.appendAll(poly.extrude(z));
for (let inner of poly.inner || []) {
obj.appendAll(inner.extrude(z));
}
objs.push(obj);
// invert y
for (let i=1, l=obj.length; i<l; i += 3) {
obj[i] = -obj[i];
}
}
}
return objs;
}

View file

@ -479,12 +479,38 @@ function load_files(files) {
mesh.api.log.emit(`loading file...`);
let api = mesh.api;
let has_image = false;
let has_svg = false;
for (let file of files) {
has_image = has_image || file.type === 'image/png';
has_svg = has_svg || file.name.toLowerCase().indexOf(".svg") > 0;
}
if (has_image) {
if (has_svg) {
api.modal.dialog({
title: `image options`,
title: `svg import`,
body: [ h.div({ class: "image-import" }, [
h.div([
h.label("extrude"),
h.input({ id: "extrude_height", value: 5, size: 4 })
]),
h.div([
h.label("repair"),
h.input({ id: "svg_repair", type: "checkbox", checked: true })
]),
h.div([
h.button({ _: "import", onclick() {
let { svg_repair, extrude_height } = api.modal.bound;
load_files_opt(files, {
soup: svg_repair.checked,
depth: extrude_height
});
api.modal.hide();
} }),
])
]) ]
});
} else if (has_image) {
api.modal.dialog({
title: `image import`,
body: [ h.div({ class: "image-import" }, [
h.div([
h.label("invert pixels"),
@ -507,7 +533,7 @@ function load_files(files) {
h.input({ id: "img_base", value: 0, size: 4 })
]),
h.div([
h.button({ _: "next", onclick() {
h.button({ _: "import", onclick() {
let { inv_image, inv_alpha, img_border, img_blur, img_base } = api.modal.bound;
load_files_opt(files, {
inv_image: inv_image.checked,