add Mesh:Tool SVG import options for extrusion depth and boolean repair
This commit is contained in:
parent
4754ee9909
commit
5df0262d8f
4 changed files with 90 additions and 15 deletions
|
|
@ -4,6 +4,7 @@ Full docs @ https://docs.grid.space/projects/kiri-moto
|
||||||
|
|
||||||
# Release 3.7
|
# 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
|
* replace jscad/modeling with Manifold project for faster mesh boolean
|
||||||
* various CAM gcode, preview, animation fixes (3 axis)
|
* various CAM gcode, preview, animation fixes (3 axis)
|
||||||
* various mobile touch, file load fixes
|
* various mobile touch, file load fixes
|
||||||
|
|
|
||||||
|
|
@ -16,19 +16,19 @@ if (load.File) return;
|
||||||
gapp.register('load.file', []);
|
gapp.register('load.file', []);
|
||||||
|
|
||||||
const types = {
|
const types = {
|
||||||
stl(data, file, resolve, reject) {
|
stl(data, file, resolve, reject, opt = {}) {
|
||||||
resolve([{
|
resolve([{
|
||||||
mesh: new load.STL().parse(data), file
|
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 => {
|
resolve(load.OBJ.parse(data).map(m => {
|
||||||
return { mesh: m.toFloat32(), file: nameOf(file, m.name, 1) }
|
return { mesh: m.toFloat32(), file: nameOf(file, m.name, 1) }
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
"3mf"(data, file, resolve, reject) {
|
"3mf"(data, file, resolve, reject, opt = {}) {
|
||||||
let i = 1;
|
let i = 1;
|
||||||
load.TMF.parseAsync(data).then((meshes) => {
|
load.TMF.parseAsync(data).then((meshes) => {
|
||||||
resolve(meshes.map(m => {
|
resolve(meshes.map(m => {
|
||||||
|
|
@ -37,12 +37,13 @@ const types = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
svg(data, file, resolve, reject) {
|
svg(data, file, resolve, reject, opt = {}) {
|
||||||
resolve(load.SVG.parse(data).map(m => { return {mesh: m.toFloat32(), file} }));
|
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, {
|
load.PNG.parse(data, {
|
||||||
|
...opt,
|
||||||
done(data) {
|
done(data) {
|
||||||
resolve({ mesh: data, file });
|
resolve({ mesh: data, file });
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +73,7 @@ function load_data(data, file, ext, opt = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let fn = types[ext];
|
let fn = types[ext];
|
||||||
if (fn) {
|
if (fn) {
|
||||||
fn(data, file, resolve, reject);
|
fn(data, file, resolve, reject, opt);
|
||||||
} else {
|
} else {
|
||||||
reject(`unknown file type: "${ext}" from ${file}`);
|
reject(`unknown file type: "${ext}" from ${file}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,17 +17,29 @@ function parseAsync(text, opt) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function parse(text, opt = {}) {
|
function parse(text, opt = {}) {
|
||||||
let objs = [];
|
const fromSoup = opt.soup || false;
|
||||||
let data = new THREE.SVGLoader().parse(text);
|
const objs = [];
|
||||||
let paths = data.paths;
|
const data = new THREE.SVGLoader().parse(text);
|
||||||
let xmlat = data.xml.attributes;
|
const paths = data.paths;
|
||||||
let depth = opt.depth || xmlat['data-km-extrude']
|
const xmlat = data.xml.attributes;
|
||||||
|
const polys = fromSoup ? [] : undefined;
|
||||||
|
const depth = opt.depth || xmlat['data-km-extrude']
|
||||||
|| xmlat['extrude']
|
|| xmlat['extrude']
|
||||||
|| {value: 5};
|
|| {value: 5};
|
||||||
|
|
||||||
|
|
||||||
for (let i = 0; i < paths.length; i++) {
|
for (let i = 0; i < paths.length; i++) {
|
||||||
let path = paths[i];
|
let path = paths[i];
|
||||||
let shapes = path.toShapes(true);
|
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, {
|
let geom = new THREE.ExtrudeGeometry(shapes, {
|
||||||
steps: 1,
|
steps: 1,
|
||||||
depth: parseFloat(depth.value),
|
depth: parseFloat(depth.value),
|
||||||
|
|
@ -49,6 +61,41 @@ function parse(text, opt = {}) {
|
||||||
objs.push([ ...array ]);
|
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;
|
return objs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -479,12 +479,38 @@ function load_files(files) {
|
||||||
mesh.api.log.emit(`loading file...`);
|
mesh.api.log.emit(`loading file...`);
|
||||||
let api = mesh.api;
|
let api = mesh.api;
|
||||||
let has_image = false;
|
let has_image = false;
|
||||||
|
let has_svg = false;
|
||||||
for (let file of files) {
|
for (let file of files) {
|
||||||
has_image = has_image || file.type === 'image/png';
|
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({
|
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" }, [
|
body: [ h.div({ class: "image-import" }, [
|
||||||
h.div([
|
h.div([
|
||||||
h.label("invert pixels"),
|
h.label("invert pixels"),
|
||||||
|
|
@ -507,7 +533,7 @@ function load_files(files) {
|
||||||
h.input({ id: "img_base", value: 0, size: 4 })
|
h.input({ id: "img_base", value: 0, size: 4 })
|
||||||
]),
|
]),
|
||||||
h.div([
|
h.div([
|
||||||
h.button({ _: "next", onclick() {
|
h.button({ _: "import", onclick() {
|
||||||
let { inv_image, inv_alpha, img_border, img_blur, img_base } = api.modal.bound;
|
let { inv_image, inv_alpha, img_border, img_blur, img_base } = api.modal.bound;
|
||||||
load_files_opt(files, {
|
load_files_opt(files, {
|
||||||
inv_image: inv_image.checked,
|
inv_image: inv_image.checked,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue