convert bulk of root kiri packages to gapp.register() loading

This commit is contained in:
Stewart Allen 2022-02-20 22:47:04 -05:00
commit 57a2129dba
33 changed files with 3092 additions and 3030 deletions

View file

@ -5,7 +5,7 @@
// dep: kiri.api
gapp.register("kiri.alerts", [], (root, exports) => {
const { kiri } = self;
const { kiri } = root;
const { api } = kiri;
let alerts = [];

View file

@ -9,7 +9,7 @@
// use: kiri.settings
gapp.register("kiri.api", [], (root, exports) => {
const { data, kiri, noop } = self;
const { data, kiri, noop } = root;
const { consts, utils } = kiri;
const { areEqual, parseOpt, encodeOpt, ajax, o2js, js2o } = utils;
const { LISTS } = consts;

View file

@ -6,387 +6,388 @@
// dep: geo.base
gapp.register("kiri.codec", [], (root, exports) => {
const BASE = self.base, KIRI = self.kiri, handlers = {};
const freeMem = true;
const zeroOut = true;
const { base, kiri } = root;
const handlers = {};
const freeMem = true;
const zeroOut = true;
const codec = exports({
undef: undefined,
encode: encode,
decode: decode,
registerDecoder: registerDecoder,
allocFloat32Array: allocFloat32Array,
encodePointArray,
decodePointArray,
toCodable
});
const codec = exports({
undef: undefined,
encode: encode,
decode: decode,
registerDecoder: registerDecoder,
allocFloat32Array: allocFloat32Array,
encodePointArray,
decodePointArray,
toCodable
});
function toCodable(object) {
switch (typeof object) {
function toCodable(object) {
switch (typeof object) {
case 'function':
return undefined;
case 'string':
case 'number':
return object;
case 'object':
if (Array.isArray(object)) {
return object.map(v => toCodable(v));
}
break;
}
let o = {};
for (let [key, val] of Object.entries(object)) {
switch (typeof val) {
case 'function':
return undefined;
case 'string':
case 'number':
return object;
break;
case 'object':
if (Array.isArray(object)) {
return object.map(v => toCodable(v));
if (Array.isArray(val)) {
val = val.map(v => toCodable(v));
} else {
val = toCodable(val);
}
default:
o[key] = val;
break;
}
let o = {};
for (let [key, val] of Object.entries(object)) {
switch (typeof val) {
case 'function':
break;
case 'object':
if (Array.isArray(val)) {
val = val.map(v => toCodable(v));
} else {
val = toCodable(val);
}
default:
o[key] = val;
break;
}
}
return o;
}
return o;
}
function allocFloat32Array(arg) {
if (arg.byteLength) {
// already a float array
return arg;
}
if (Array.isArray(arg)) {
// create float array from array
return new Float32Array(arg);
}
// usually a number (size) for array
function allocFloat32Array(arg) {
if (arg.byteLength) {
// already a float array
return arg;
}
if (Array.isArray(arg)) {
// create float array from array
return new Float32Array(arg);
}
// usually a number (size) for array
return new Float32Array(arg);
}
function encode(o, state) {
state = state || {};
if (o === null) return null;
if (o === undefined) return undefined;
if (Array.isArray(o)) {
let arr = new Array(o.length), i=0;
while (i < o.length) arr[i] = encode(o[i++], state);
return arr;
}
switch (typeof(o)) {
case 'string':
case 'number':
return o;
case 'object':
if (o.encode) return o.encode(state);
return genOEncode(o, state);
}
return null;
function encode(o, state) {
state = state || {};
if (o === null) return null;
if (o === undefined) return undefined;
if (Array.isArray(o)) {
let arr = new Array(o.length), i=0;
while (i < o.length) arr[i] = encode(o[i++], state);
return arr;
}
function decode(o, state) {
state = state || {};
if (o === null) return null;
if (o === undefined) return undefined;
if (Array.isArray(o)) {
for (let i=0; i < o.length; i++) o[i] = decode(o[i],state);
switch (typeof(o)) {
case 'string':
case 'number':
return o;
}
switch (typeof(o)) {
case 'string':
case 'number':
return o;
case 'object':
if (o.type && handlers[o.type]) return handlers[o.type](o,state);
return genODecode(o, state);
}
return null;
case 'object':
if (o.encode) return o.encode(state);
return genOEncode(o, state);
}
return null;
}
function registerDecoder(type, handler) {
handlers[type] = handler;
function decode(o, state) {
state = state || {};
if (o === null) return null;
if (o === undefined) return undefined;
if (Array.isArray(o)) {
for (let i=0; i < o.length; i++) o[i] = decode(o[i],state);
return o;
}
function genOEncode(o, state) {
if (o instanceof Float32Array) return o;
let out = {};
for (let k in o) {
if (o.hasOwnProperty(k)) out[k] = encode(o[k], state);
}
return out;
switch (typeof(o)) {
case 'string':
case 'number':
return o;
case 'object':
if (o.type && handlers[o.type]) return handlers[o.type](o,state);
return genODecode(o, state);
}
return null;
}
function genODecode(o, state) {
if (o instanceof Float32Array) return o;
let out = {};
for (let k in o) {
if (o.hasOwnProperty(k)) out[k] = decode(o[k], state);
}
return out;
function registerDecoder(type, handler) {
handlers[type] = handler;
}
function genOEncode(o, state) {
if (o instanceof Float32Array) return o;
let out = {};
for (let k in o) {
if (o.hasOwnProperty(k)) out[k] = encode(o[k], state);
}
return out;
}
/** ******************************************************************
* Object Class CODEC Functions
******************************************************************* */
function genODecode(o, state) {
if (o instanceof Float32Array) return o;
let out = {};
for (let k in o) {
if (o.hasOwnProperty(k)) out[k] = decode(o[k], state);
}
return out;
}
KIRI.Widget.prototype.encode = function(state) {
const json = state._json_;
const geo = this.getGeoVertices();
const coded = {
type: 'widget',
id: this.id,
ver: 1, // for better future encodings
json: json, // safe for JSON (float32array mess)
group: this.group.id,
track: this.track,
geo: json ? Array.from(geo) : geo
};
return coded;
/** ******************************************************************
* Object Class CODEC Functions
******************************************************************* */
kiri.Widget.prototype.encode = function(state) {
const json = state._json_;
const geo = this.getGeoVertices();
const coded = {
type: 'widget',
id: this.id,
ver: 1, // for better future encodings
json: json, // safe for JSON (float32array mess)
group: this.group.id,
track: this.track,
geo: json ? Array.from(geo) : geo
};
return coded;
};
registerDecoder('widget', function(v, state) {
const id = v.id,
group = v.group || id,
track = v.track || undefined,
widget = KIRI.newWidget(id, KIRI.Widget.Groups.forid(group));
widget.loadVertices(v.json ? v.geo.toFloat32() : v.geo);
widget.saved = Date.now();
if (track && track.pos) {
widget.track = track;
widget.move(track.pos.x, track.pos.y, track.pos.z, true);
}
return widget;
});
registerDecoder('widget', function(v, state) {
const id = v.id,
group = v.group || id,
track = v.track || undefined,
widget = kiri.newWidget(id, kiri.Widget.Groups.forid(group));
widget.loadVertices(v.json ? v.geo.toFloat32() : v.geo);
widget.saved = Date.now();
if (track && track.pos) {
widget.track = track;
widget.move(track.pos.x, track.pos.y, track.pos.z, true);
}
return widget;
});
KIRI.Slice.prototype.encode = function(state) {
const rv = {
type: 'slice',
z: this.z,
index: this.index,
layers: encode(this.layers, state)
};
// aggressively free memory
if (freeMem) this.layers = undefined;
return rv;
kiri.Slice.prototype.encode = function(state) {
const rv = {
type: 'slice',
z: this.z,
index: this.index,
layers: encode(this.layers, state)
};
// aggressively free memory
if (freeMem) this.layers = undefined;
return rv;
};
registerDecoder('slice', function(v, state) {
let slice = KIRI.newSlice(v.z, state.mesh ? state.mesh.newGroup() : null);
registerDecoder('slice', function(v, state) {
let slice = kiri.newSlice(v.z, state.mesh ? state.mesh.newGroup() : null);
slice.index = v.index;
slice.layers = decode(v.layers, state)
slice.index = v.index;
slice.layers = decode(v.layers, state)
return slice;
});
return slice;
});
KIRI.Layers.prototype.encode = function(state) {
let enc = {
type: 'layers',
layers: Object.keys(this.layers),
data: Object.values(this.layers).map(layer => {
const e = {
polys: encode(layer.polys, state),
lines: encodePointArray(layer.lines, state),
faces: codec.allocFloat32Array(layer.faces),
cface: layer.cface || codec.undef,
color: layer.color,
paths: layer.paths.map(lp => {
const pe = {
z: lp.z,
index: lp.index,
faces: codec.allocFloat32Array(lp.faces)
};
if (zeroOut && state.zeros && pe.faces.length) {
state.zeros.push(pe.faces.buffer);
}
return pe;
}),
cpath: layer.cpath || codec.undef,
off: layer.off
};
if (zeroOut && state.zeros && e.faces.length) {
state.zeros.push(e.faces.buffer);
}
// console.log('-->',layer,e);
return e;
})
};
// aggressively free memory
if (freeMem) this.init();
return enc;
};
registerDecoder('layers', function(v, state) {
let render = new KIRI.Layers();
for (let i=0; i<v.layers.length; i++) {
const d = render.layers[v.layers[i]] = {
polys: decode(v.data[i].polys, state),
cpoly: v.data[i].cpoly,
lines: decodePointArray(v.data[i].lines),
faces: codec.allocFloat32Array(v.data[i].faces),
cface: v.data[i].cface,
color: v.data[i].color,
paths: v.data[i].paths.map(lp => {
return {
kiri.Layers.prototype.encode = function(state) {
let enc = {
type: 'layers',
layers: Object.keys(this.layers),
data: Object.values(this.layers).map(layer => {
const e = {
polys: encode(layer.polys, state),
lines: encodePointArray(layer.lines, state),
faces: codec.allocFloat32Array(layer.faces),
cface: layer.cface || codec.undef,
color: layer.color,
paths: layer.paths.map(lp => {
const pe = {
z: lp.z,
index: lp.index,
faces: codec.allocFloat32Array(lp.faces)
};
if (zeroOut && state.zeros && pe.faces.length) {
state.zeros.push(pe.faces.buffer);
}
return pe;
}),
cpath: v.data[i].cpath,
off: v.data[i].off
cpath: layer.cpath || codec.undef,
off: layer.off
};
// fixup null -> Infinity in material counts (JSON stringify sucks)
if (d.cface) {
d.cface.forEach(rec => {
if (rec.count === null) {
rec.count = Infinity;
}
});
if (zeroOut && state.zeros && e.faces.length) {
state.zeros.push(e.faces.buffer);
}
if (d.cpoly) {
d.cpoly.forEach(rec => {
if (rec.count === null) {
rec.count = Infinity;
}
});
}
if (d.cpath) {
d.cpath.forEach(rec => {
if (rec.count === null) {
rec.count = Infinity;
}
});
}
// console.log('<--',d);
}
return render;
});
KIRI.Top.prototype.encode = function(state) {
let obj = {
type: 'top',
poly: encode(this.poly, state)
};
if (state.full) {
obj.last = encode(this.last, state);
obj.shells = encode(this.shells, state);
obj.fill_off = encode(this.fill_off, state);
obj.fill_lines = encode(this.fill_lines, state);
}
return obj;
// console.log('-->',layer,e);
return e;
})
};
// aggressively free memory
if (freeMem) this.init();
return enc;
};
registerDecoder('top', function(v, state) {
let top = KIRI.newTop(decode(v.poly, state));
if (state.full) {
top.last = decode(v.last, state);
top.shells = decode(v.shells, state);
top.fill_off = decode(v.fill_off, state);
top.fill_lines = decode(v.fill_lines, state);
registerDecoder('layers', function(v, state) {
let render = new kiri.Layers();
for (let i=0; i<v.layers.length; i++) {
const d = render.layers[v.layers[i]] = {
polys: decode(v.data[i].polys, state),
cpoly: v.data[i].cpoly,
lines: decodePointArray(v.data[i].lines),
faces: codec.allocFloat32Array(v.data[i].faces),
cface: v.data[i].cface,
color: v.data[i].color,
paths: v.data[i].paths.map(lp => {
return {
z: lp.z,
index: lp.index,
faces: codec.allocFloat32Array(lp.faces)
};
}),
cpath: v.data[i].cpath,
off: v.data[i].off
};
// fixup null -> Infinity in material counts (JSON stringify sucks)
if (d.cface) {
d.cface.forEach(rec => {
if (rec.count === null) {
rec.count = Infinity;
}
});
}
return top;
if (d.cpoly) {
d.cpoly.forEach(rec => {
if (rec.count === null) {
rec.count = Infinity;
}
});
}
if (d.cpath) {
d.cpath.forEach(rec => {
if (rec.count === null) {
rec.count = Infinity;
}
});
}
// console.log('<--',d);
}
return render;
});
kiri.Top.prototype.encode = function(state) {
let obj = {
type: 'top',
poly: encode(this.poly, state)
};
if (state.full) {
obj.last = encode(this.last, state);
obj.shells = encode(this.shells, state);
obj.fill_off = encode(this.fill_off, state);
obj.fill_lines = encode(this.fill_lines, state);
}
return obj;
};
registerDecoder('top', function(v, state) {
let top = kiri.newTop(decode(v.poly, state));
if (state.full) {
top.last = decode(v.last, state);
top.shells = decode(v.shells, state);
top.fill_off = decode(v.fill_off, state);
top.fill_lines = decode(v.fill_lines, state);
}
return top;
});
function encodePointArray(points, state, z) {
if (!points) return null;
let array = codec.allocFloat32Array(points.length * 3),
pos = 0;
points.forEach(function(point) {
if (state.rotate) {
if (state.centerz) {
point.z -= state.centerz;
}
let v = new THREE.Vector3(point.x, point.y, point.z);
v.applyMatrix4(state.rotate);
point = {x: v.x, y: v.y, z: v.z};
if (state.centerz) {
point.z += state.centerz;
}
if (state.movez) {
point.z -= state.movez;
}
}
array[pos++] = point.x;
array[pos++] = point.y;
array[pos++] = z !== undefined ? z : point.z;
});
function encodePointArray(points, state, z) {
if (!points) return null;
let array = codec.allocFloat32Array(points.length * 3),
pos = 0;
points.forEach(function(point) {
if (state.rotate) {
if (state.centerz) {
point.z -= state.centerz;
}
let v = new THREE.Vector3(point.x, point.y, point.z);
v.applyMatrix4(state.rotate);
point = {x: v.x, y: v.y, z: v.z};
if (state.centerz) {
point.z += state.centerz;
}
if (state.movez) {
point.z -= state.movez;
}
}
array[pos++] = point.x;
array[pos++] = point.y;
array[pos++] = z !== undefined ? z : point.z;
});
if (zeroOut && state.zeros) {
state.zeros.push(array.buffer);
}
return array;
if (zeroOut && state.zeros) {
state.zeros.push(array.buffer);
}
function decodePointArray(array) {
if (!array) return null;
return array;
}
let vid = 0,
pid = 0,
points = new Array(array.length/3);
function decodePointArray(array) {
if (!array) return null;
while (vid < array.length) {
points[pid++] = BASE.newPoint(array[vid++], array[vid++], array[vid++]);
}
let vid = 0,
pid = 0,
points = new Array(array.length/3);
return points;
while (vid < array.length) {
points[pid++] = base.newPoint(array[vid++], array[vid++], array[vid++]);
}
BASE.Polygon.prototype.encode = function(state) {
if (!state.poly) state.poly = {};
return points;
}
let cached = state.poly[this.id];
base.Polygon.prototype.encode = function(state) {
if (!state.poly) state.poly = {};
if (cached) {
return { type: 'poly', ref: this.id };
}
let cached = state.poly[this.id];
state.poly[this.id] = this;
return {
type: 'poly',
id: this.id,
array: encodePointArray(this.points, state, this.z),
inner: encode(this.inner, state),
parent: encode(this.parent, state),
depth: this.depth,
color: this.color,
open: this.open
};
if (cached) {
return { type: 'poly', ref: this.id };
}
state.poly[this.id] = this;
return {
type: 'poly',
id: this.id,
array: encodePointArray(this.points, state, this.z),
inner: encode(this.inner, state),
parent: encode(this.parent, state),
depth: this.depth,
color: this.color,
open: this.open
};
};
registerDecoder('poly', function(v, state) {
if (!state.poly) state.poly = {};
registerDecoder('poly', function(v, state) {
if (!state.poly) state.poly = {};
if (v.ref) return state.poly[v.ref];
if (v.ref) return state.poly[v.ref];
let poly = BASE.newPolygon(),
vid = 0;
let poly = base.newPolygon(),
vid = 0;
// if passed a normal array, convert to float32
if (v.array.toFloat32) {
v.array = v.array.toFloat32();
}
// if passed a normal array, convert to float32
if (v.array.toFloat32) {
v.array = v.array.toFloat32();
}
while (vid < v.array.length) {
poly.push(BASE.newPoint(v.array[vid++], v.array[vid++], v.array[vid++]));
}
while (vid < v.array.length) {
poly.push(base.newPoint(v.array[vid++], v.array[vid++], v.array[vid++]));
}
poly.id = v.id;
poly.open = v.open;
poly.id = v.id;
poly.open = v.open;
state.poly[v.id] = poly;
state.poly[v.id] = poly;
poly.inner = decode(v.inner, state);
poly.parent = decode(v.parent, state);
poly.depth = v.depth;
poly.color = v.color;
poly.inner = decode(v.inner, state);
poly.parent = decode(v.parent, state);
poly.depth = v.depth;
poly.color = v.color;
return poly;
});
return poly;
});
});

File diff suppressed because it is too large Load diff

View file

@ -2,9 +2,7 @@
"use strict";
(function () {
const { kiri } = self;
gapp.register("kiri.consts", [], (root, exports) => {
const COLOR = {
wireframe: 0x444444,
@ -124,7 +122,7 @@ const PMODES = {
const SEED = 'kiri-seed';
kiri.consts = {
exports({
PMODES,
COLOR,
LISTS,
@ -132,6 +130,6 @@ kiri.consts = {
VIEWS,
SEED,
beltfact: Math.cos(Math.PI / 4)
};
});
})();
});

View file

@ -1,171 +1,179 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
kiri.load(function(API) {
let stack = [];
let stpos = 0;
// dep: kiri.api
// use: kiri.selection
// use: moto.space
gapp.register("kiri.do", [], (root, events) => {
let event = API.event;
let moved = {x:0, y:0};
let msgid;
const { kiri, moto } = root;
const { api } = kiri;
const { space } = moto;
const { event } = api;
event.on("init-done", () => {
$('undo').onclick = undo;
$('redo').onclick = redo;
});
let stack = [];
let stpos = 0;
let moved = { x: 0, y: 0 };
let msgid;
let undo = API.doit.undo = function() {
if (stpos) {
action(stack[--stpos].undo);
} else {
message('nothing to undo');
}
};
event.on("init-done", () => {
$('undo').onclick = undo;
$('redo').onclick = redo;
});
let redo = API.doit.redo = function() {
if (stpos < stack.length) {
action(stack[stpos++].redo);
} else {
message('nothing to redo');
}
};
let clear = API.doit.clear = function() {
stack = [];
stpos = 0;
moved = {x:0, y:0};
updateButtons();
};
function updateButtons() {
let isArrange = API.view.get() === kiri.consts.VIEWS.ARRANGE;
$('doit').style.display = isArrange && stack.length ? 'flex' : 'none';
$('undo').disabled = stpos === 0;
$('redo').disabled = stpos == stack.length;
let undo = api.doit.undo = function() {
if (stpos) {
action(stack[--stpos].undo);
} else {
message('nothing to undo');
}
};
function message(txt) {
if (msgid) {
API.hide.alert(msgid);
}
msgid = API.show.alert(txt);
let redo = api.doit.redo = function() {
if (stpos < stack.length) {
action(stack[stpos++].redo);
} else {
message('nothing to redo');
}
};
function action(rec) {
switch (rec.type) {
case 'move':
for (let w of rec.widgets) {
w._move(rec.dist.x, rec.dist.y, 0);
}
break;
case 'rotate':
for (let w of rec.widgets) {
w.rotate(rec.x, rec.y, rec.z);
}
break;
case 'scale':
for (let w of rec.widgets) {
w._scale(rec.x, rec.y, rec.z);
}
API.selection.update_info();
break;
}
API.space.update();
updateButtons();
let clear = api.doit.clear = function() {
stack = [];
stpos = 0;
moved = {x:0, y:0};
updateButtons();
};
function updateButtons() {
let isArrange = api.view.get() === kiri.consts.VIEWS.ARRANGE;
$('doit').style.display = isArrange && stack.length ? 'flex' : 'none';
$('undo').disabled = stpos === 0;
$('redo').disabled = stpos == stack.length;
}
function message(txt) {
if (msgid) {
api.hide.alert(msgid);
}
msgid = api.show.alert(txt);
}
function pushActions(ur) {
stack.length = stpos++;
stack.push(ur);
updateButtons();
}
event.on([
'platform.layout',
'widget.add',
'widget.delete'
], (widget) => {
clear();
});
event.on('view.set', mode => {
updateButtons();
});
// selection event (store position)
event.on('selection.rotate', (rec) => {
let type = "rotate";
let widgets = API.selection.widgets(true);
if (!widgets.length) {
return;
}
let {x, y, z} = rec;
if (!(x || y || z)) {
return;
}
pushActions({
redo: {
type,
widgets,
x, y, z
},
undo: {
type,
widgets,
x: -x, y: -y, z: -z
function action(rec) {
switch (rec.type) {
case 'move':
for (let w of rec.widgets) {
w._move(rec.dist.x, rec.dist.y, 0);
}
})
});
event.on('selection.scale', (rec) => {
let [x, y, z] = rec;
// ignore scale close to 1
if (Math.abs(1 - (x * y * z)) < 0.0001) {
return;
}
let widgets = API.selection.widgets(true);
if (!widgets.length) {
return;
}
let type = "scale";
pushActions({
redo: {
type,
widgets,
x, y, z
},
undo: {
type,
widgets,
x: 1/x, y: 1/y, z: 1/z
break;
case 'rotate':
for (let w of rec.widgets) {
w.rotate(rec.x, rec.y, rec.z);
}
});
});
// selection moved (accumulator)
event.on('selection.drag', (delta) => {
moved.x += delta.x;
moved.y += delta.y;
});
// move complete (store updated position)
event.on('mouse.drag.done', () => {
let widgets = API.selection.widgets(true);
if (!widgets.length) {
return;
}
let type = "move";
pushActions({
redo: {
type,
widgets,
dist: moved
},
undo: {
type,
widgets,
dist: {x: -moved.x, y: -moved.y}
break;
case 'scale':
for (let w of rec.widgets) {
w._scale(rec.x, rec.y, rec.z);
}
});
moved = {x:0, y:0};
api.selection.update_info();
break;
}
space.update();
updateButtons();
}
function pushActions(ur) {
stack.length = stpos++;
stack.push(ur);
updateButtons();
}
event.on([
'platform.layout',
'widget.add',
'widget.delete'
], (widget) => {
clear();
});
event.on('view.set', mode => {
updateButtons();
});
// selection event (store position)
event.on('selection.rotate', (rec) => {
let type = "rotate";
let widgets = api.selection.widgets(true);
if (!widgets.length) {
return;
}
let {x, y, z} = rec;
if (!(x || y || z)) {
return;
}
pushActions({
redo: {
type,
widgets,
x, y, z
},
undo: {
type,
widgets,
x: -x, y: -y, z: -z
}
})
});
event.on('selection.scale', (rec) => {
let [x, y, z] = rec;
// ignore scale close to 1
if (Math.abs(1 - (x * y * z)) < 0.0001) {
return;
}
let widgets = api.selection.widgets(true);
if (!widgets.length) {
return;
}
let type = "scale";
pushActions({
redo: {
type,
widgets,
x, y, z
},
undo: {
type,
widgets,
x: 1/x, y: 1/y, z: 1/z
}
});
});
// selection moved (accumulator)
event.on('selection.drag', (delta) => {
moved.x += delta.x;
moved.y += delta.y;
});
// move complete (store updated position)
event.on('mouse.drag.done', () => {
let widgets = api.selection.widgets(true);
if (!widgets.length) {
return;
}
let type = "move";
pushActions({
redo: {
type,
widgets,
dist: moved
},
undo: {
type,
widgets,
dist: {x: -moved.x, y: -moved.y}
}
});
moved = {x:0, y:0};
});
});

File diff suppressed because it is too large Load diff

View file

@ -2,221 +2,223 @@
"use strict";
(function() {
// dep: main.kiri
// use: data.index
gapp.register("kiri.files", [], (root, exports) => {
const { kiri } = self;
const { kiri } = self;
class Files {
constructor(indexdb, options) {
let store = this;
this.db = indexdb;
this.files = {};
this.listeners = [];
this.options = options || {};
this.deferredHandler = null;
this.concurrent = 0;
this.refresh();
}
setOptions(options) {
this.options = options;
return this;
}
get index() {
return this.db;
}
refresh() {
let store = this;
store.db.get('files', function(files) {
if (files) {
store.files = files;
notifyFileListeners(store);
}
});
};
wipe() {
let key, files = this.files;
for (key in files) {
if (files.hasOwnProperty(key)) this.deleteFile(key);
}
};
fileList() {
return this.files;
};
addFileListener(listener) {
if (!this.listeners.contains(listener)) {
this.listeners.push(listener);
listener(this.files);
}
};
removeFileListener(listener) {
this.listeners.remove(listener);
};
decimate(vertices, callback) {
let options = this.options;
if (vertices.length < (options.threshold || 500000)) {
return callback(vertices);
}
kiri.client.decimate(vertices, this.options, function(reply) {
callback(reply);
});
};
setDeferredHandler(handler) {
this.deferredHandler = handler;
};
putDeferred(name, mark) {
// triggers refresh callback
this.files[name] = {
deferred: mark
};
saveFileList(this);
};
/**
* @param {String} name
* @param {Float32Array} vertices
* @param {Function} [callback]
*/
putFile(name, vertices, callback) {
let mark = Date.now();
let store = this;
let pdb = store.db.promise();
store.concurrent++;
function ondone(error) {
if (callback) callback(error);
if (--store.concurrent === 0) {
notifyFileListeners(store);
}
}
pdb.put(`file-${name}`, vertices)
.then(() => {
store.files[name] = {
vertices: vertices.length / 3,
updated: new Date().getTime()
};
return pdb.put('files', store.files);
})
.then(() => {
return new Promise((resolve,reject) => {
store.decimate(vertices, resolve);
});
})
.then(decimated => {
return pdb.put(`fdec-${name}`, decimated)
})
.then(ondone)
.catch(ondone);
};
rename(name, newname, callback) {
if (!this.files[name]) return callback({error: 'no such file'});
if (!newname || newname == name) return callback({error: 'invalid new name'});
let done = 0;
let error = [];
let store = this;
function complete(ok, err) {
if (err) error.push(err);
if (++done === 2) {
store.files[newname] = store.files[name];
delete store.files[name];
saveFileList(store);
store.db.remove(`fdec-${name}`);
store.db.remove(`file-${name}`);
callback(error.length ? {error} : {});
}
}
store.db.get(`fdec-${name}`, (vertices) => {
if (!vertices) return complete(false, 'no decimation');
store.db.put(`fdec-${newname}`, vertices, complete);
});
store.db.get(`file-${name}`, (vertices) => {
if (!vertices) return complete(false, 'no raw file');
store.db.put(`file-${newname}`, vertices, complete);
});
};
/**
* @param {String} name
* @param {Function} callback
*/
getFile(name, callback) {
let store = this,
rec = store.files[name];
if (rec && rec.deferred) {
if (store.deferredHandler) return store.deferredHandler(rec.deferred, name, callback);
return callback();
}
this.db.get('fdec-'+name, function(vertices) {
if (vertices) {
callback(vertices);
} else {
store.db.get('file-'+name, function(vertices) {
if (vertices) {
store.decimate(vertices, function(decimated) {
store.db.put('fdec-'+name, decimated);
callback(vertices);
});
} else {
return callback();
}
});
}
});
};
/**
* @param {String} name
* @param {Function} callback
*/
deleteFile(name, callback) {
let store = this;
if (store.files[name]) {
delete store.files[name];
store.db.remove('fdec-'+name);
store.db.remove('file-'+name, function(ok) {
saveFileList(store);
if (callback) callback(ok);
});
return;
}
if (callback) callback(false);
};
deleteFilter(fn, from, to) {
this.db.keys(keys => {
for (let key of keys) {
if (fn(key)) {
this.db.remove(key);
}
}
}, from, to);
}
class Files {
constructor(indexdb, options) {
let store = this;
this.db = indexdb;
this.files = {};
this.listeners = [];
this.options = options || {};
this.deferredHandler = null;
this.concurrent = 0;
this.refresh();
}
function saveFileList(store) {
store.db.put('files', store.files);
notifyFileListeners(store);
setOptions(options) {
this.options = options;
return this;
}
function notifyFileListeners(store) {
for (let i=0; i<store.listeners.length; i++) {
store.listeners[i](store.files);
}
get index() {
return this.db;
}
kiri.openFiles = function(indexdb, options) {
return new Files(indexdb, options);
refresh() {
let store = this;
store.db.get('files', function(files) {
if (files) {
store.files = files;
notifyFileListeners(store);
}
});
};
})();
wipe() {
let key, files = this.files;
for (key in files) {
if (files.hasOwnProperty(key)) this.deleteFile(key);
}
};
fileList() {
return this.files;
};
addFileListener(listener) {
if (!this.listeners.contains(listener)) {
this.listeners.push(listener);
listener(this.files);
}
};
removeFileListener(listener) {
this.listeners.remove(listener);
};
decimate(vertices, callback) {
let options = this.options;
if (vertices.length < (options.threshold || 500000)) {
return callback(vertices);
}
kiri.client.decimate(vertices, this.options, function(reply) {
callback(reply);
});
};
setDeferredHandler(handler) {
this.deferredHandler = handler;
};
putDeferred(name, mark) {
// triggers refresh callback
this.files[name] = {
deferred: mark
};
saveFileList(this);
};
/**
* @param {String} name
* @param {Float32Array} vertices
* @param {Function} [callback]
*/
putFile(name, vertices, callback) {
let mark = Date.now();
let store = this;
let pdb = store.db.promise();
store.concurrent++;
function ondone(error) {
if (callback) callback(error);
if (--store.concurrent === 0) {
notifyFileListeners(store);
}
}
pdb.put(`file-${name}`, vertices)
.then(() => {
store.files[name] = {
vertices: vertices.length / 3,
updated: new Date().getTime()
};
return pdb.put('files', store.files);
})
.then(() => {
return new Promise((resolve,reject) => {
store.decimate(vertices, resolve);
});
})
.then(decimated => {
return pdb.put(`fdec-${name}`, decimated)
})
.then(ondone)
.catch(ondone);
};
rename(name, newname, callback) {
if (!this.files[name]) return callback({error: 'no such file'});
if (!newname || newname == name) return callback({error: 'invalid new name'});
let done = 0;
let error = [];
let store = this;
function complete(ok, err) {
if (err) error.push(err);
if (++done === 2) {
store.files[newname] = store.files[name];
delete store.files[name];
saveFileList(store);
store.db.remove(`fdec-${name}`);
store.db.remove(`file-${name}`);
callback(error.length ? {error} : {});
}
}
store.db.get(`fdec-${name}`, (vertices) => {
if (!vertices) return complete(false, 'no decimation');
store.db.put(`fdec-${newname}`, vertices, complete);
});
store.db.get(`file-${name}`, (vertices) => {
if (!vertices) return complete(false, 'no raw file');
store.db.put(`file-${newname}`, vertices, complete);
});
};
/**
* @param {String} name
* @param {Function} callback
*/
getFile(name, callback) {
let store = this,
rec = store.files[name];
if (rec && rec.deferred) {
if (store.deferredHandler) return store.deferredHandler(rec.deferred, name, callback);
return callback();
}
this.db.get('fdec-'+name, function(vertices) {
if (vertices) {
callback(vertices);
} else {
store.db.get('file-'+name, function(vertices) {
if (vertices) {
store.decimate(vertices, function(decimated) {
store.db.put('fdec-'+name, decimated);
callback(vertices);
});
} else {
return callback();
}
});
}
});
};
/**
* @param {String} name
* @param {Function} callback
*/
deleteFile(name, callback) {
let store = this;
if (store.files[name]) {
delete store.files[name];
store.db.remove('fdec-'+name);
store.db.remove('file-'+name, function(ok) {
saveFileList(store);
if (callback) callback(ok);
});
return;
}
if (callback) callback(false);
};
deleteFilter(fn, from, to) {
this.db.keys(keys => {
for (let key of keys) {
if (fn(key)) {
this.db.remove(key);
}
}
}, from, to);
}
}
function saveFileList(store) {
store.db.put('files', store.files);
notifyFileListeners(store);
}
function notifyFileListeners(store) {
for (let i=0; i<store.listeners.length; i++) {
store.listeners[i](store.files);
}
}
kiri.openFiles = function(indexdb, options) {
return new Files(indexdb, options);
};
});

View file

@ -2,7 +2,14 @@
"use strict";
(function () {
// use: load.stl
// use: load.svg
// use: kiri.api
// use: kiri.feature
// use: kiri.platform
// use: kiri.settings
// use: kiri.widget
gapp.register("kiri.frame", [], (root, exports) => {
// add frame message api listener
window.addEventListener('message', msg => {
@ -113,4 +120,4 @@ window.addEventListener('message', msg => {
}
});
})();
});

View file

@ -2,9 +2,11 @@
"use strict";
(function () {
// dep: kiri.api
// dep: moto.space
gapp.register("kiri.function", [], (root, exports) => {
const { data, kiri, noop } = self;
const { kiri, noop } = root;
const { api, client, consts, utils } = kiri;
const { space } = moto;
const { COLOR, PMODES } = consts;
@ -59,6 +61,11 @@ function prepareSlices(callback, scale = 1, offset = 0) {
const slicing = api.widgets.all()
.slice()
.filter(w => !w.track.ignore && !w.meta.disabled);
if (slicing.length === 0) {
return api.show.alert('nothing to slice');
}
const totalv = slicing.map(w => w.getVertices().count).reduce((a,v) => a + v);
const defvert = totalv / slicing.length;
const track = {};
@ -413,4 +420,4 @@ const functions = api.function = {
clear_progress() { complete = {} }
};
})();
});

View file

@ -2,11 +2,15 @@
"use strict";
(function() {
// dep: geo.base
// dep: kiri.api
// dep: kiri.stats
// dep: kiri.consts
// dep: kiri.platform
// dep: kiri.selection
gapp.register("kiri.init", [], (root, exports) => {
if (self.kiri.init) return;
const { base, kiri, moto } = self;
const { base, kiri } = root;
const { api, catalog, conf, consts, space } = kiri;
const { sdb, stats, js2o, o2js, platform, selection, ui, uc } = api;
const { VIEWS, MODES, SEED } = consts;
@ -2287,11 +2291,9 @@
uc.hoverPop(ui.ltact, { group: hpops, target: $('pop-slice') });
uc.hoverPop(ui.render, { group: hpops, target: $('pop-render'), sticky: false });
uc.hoverPop(ui.edit, { group: hpops, target: $('pop-tools'), sticky: false });
// 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-mode'), { group: hpops, target: $('mode-info') } );
uc.hoverPop($('app-name'), { group: hpops, target: $('app-info') } );
uc.onBlur([
@ -2925,4 +2927,4 @@
}
}
})();
});

View file

@ -2,81 +2,80 @@
"use strict";
(function() {
gapp.register("kiri.lang", [], (root, exports) => {
if (self.kiri.lang) return;
const { kiri } = root;
const LANG = kiri.lang = { current: {} };
const KDFL = 'en-us';
const KIRI = self.kiri, LANG = self.kiri.lang = { current: {} };
const KDFL = 'en-us';
let lset = navigator.language.toLocaleLowerCase();
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 'es': return 'es-es';
case 'fr': return 'fr-fr';
case 'pl': return 'pl-pl';
case 'pt': return 'pt-pt';
case 'zh': return 'zh';
}
LANG.map = function(key) {
if (!key) {
return KDFL;
};
LANG.get = function() {
return lset;
};
LANG.set = function() {
let map, key, keys = [...arguments];
// provide default if none given
if (keys.length === 0) {
keys = [lset, lset.split('-')[0], 'en'];
}
for (let i=0; i<keys.length; i++)
{
key = keys[i]
if (!(map = LANG[key])) {
continue;
}
lset = key;
let missing = [];
let invalid = [];
// default to EN values when missing
Object.keys(LANG.en).forEach(key => {
if (!map[key]) {
map[key] = LANG.en[key];
missing.push(key);
}
});
// update current map from chosen map
Object.keys(map).forEach(key => {
if (LANG.en[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);
}
});
if (missing.length) {
console.log(`language map "${key}" missing keys [${missing.length}]: ${missing.join(', ')}`);
}
if (invalid.length) {
console.log(`language map "${key}" invalid keys [${invalid.length}]: ${invalid.join(', ')}`);
}
return key;
}
return undefined;
}
let tok = key.split('-');
switch (tok[0]) {
case 'da': return 'da-dk';
case 'de': return 'de-de';
case 'en': return 'en-us';
case 'es': return 'es-es';
case 'fr': return 'fr-fr';
case 'pl': return 'pl-pl';
case 'pt': return 'pt-pt';
case 'zh': return 'zh';
}
return KDFL;
};
})();
LANG.get = function() {
return lset;
};
LANG.set = function() {
let map, key, keys = [...arguments];
// provide default if none given
if (keys.length === 0) {
keys = [lset, lset.split('-')[0], 'en'];
}
for (let i=0; i<keys.length; i++)
{
key = keys[i]
if (!(map = LANG[key])) {
continue;
}
lset = key;
let missing = [];
let invalid = [];
// default to EN values when missing
Object.keys(LANG.en).forEach(key => {
if (!map[key]) {
map[key] = LANG.en[key];
missing.push(key);
}
});
// update current map from chosen map
Object.keys(map).forEach(key => {
if (LANG.en[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);
}
});
if (missing.length) {
console.log(`language map "${key}" missing keys [${missing.length}]: ${missing.join(', ')}`);
}
if (invalid.length) {
console.log(`language map "${key}" invalid keys [${invalid.length}]: ${invalid.join(', ')}`);
}
return key;
}
return undefined;
}
});

View file

@ -2,354 +2,359 @@
"use strict";
(function() {
let KIRI = self.kiri,
BASE = self.base,
POLY = BASE.polygons,
newPolygon = BASE.newPolygon;
// dep: geo.base
// dep: geo.polygon
// dep: geo.polygons
gapp.register("kiri.layers", [], (root, exports) => {
class Layers {
constructor() {
this.init();
}
const { base, kiri } = root;
const { polygons, newPolygon } = base;
init() {
this.layers = {};
this.profiles = {};
this.stats = {
contour: 0,
flat_line: 0,
flat_poly: 0,
line_poly: 0,
line: 0
const POLY = base.polygons;
class Layers {
constructor() {
this.init();
}
init() {
this.layers = {};
this.profiles = {};
this.stats = {
contour: 0,
flat_line: 0,
flat_poly: 0,
line_poly: 0,
line: 0
};
}
getLayer(layer) {
return this.layers[layer];
}
setLayer(layer, colors, off) {
let layers = this.layers;
if (typeof(colors) === 'number') {
colors = {
line: colors,
face: colors,
opacity: 1
};
}
this.current = layers[layer] = layers[layer] || {
off: off === true,
lines: [],
polys: [], // colors are an attribute on polygons
faces: [],
cface: undefined, // face color indices
paths: [],
cpath: undefined, // path colors indices
color: colors || {
check: 0,
line: 0,
face: 0,
opacity: 1
},
};
return this;
}
getLayer(layer) {
return this.layers[layer];
}
addLine(p1, p2) {
this.current.lines.push(p1, p2);
return this;
}
setLayer(layer, colors, off) {
let layers = this.layers;
if (typeof(colors) === 'number') {
colors = {
line: colors,
face: colors,
opacity: 1
};
}
this.current = layers[layer] = layers[layer] || {
off: off === true,
lines: [],
polys: [], // colors are an attribute on polygons
faces: [],
cface: undefined, // face color indices
paths: [],
cpath: undefined, // path colors indices
color: colors || {
check: 0,
line: 0,
face: 0,
opacity: 1
},
};
return this;
}
addLine(p1, p2) {
this.current.lines.push(p1, p2);
return this;
}
addLines(lines, options) {
if (options) {
options.open = true;
const polys = [];
for (let i=0; i<lines.length-1; i += 2) {
polys.push(new BASE.Polygon()
.append(lines[i])
.append(lines[i+1])
.setOpen());
}
return this.addPolys(polys, options);
}
addLines(lines, options) {
if (options) {
options.open = true;
const polys = [];
for (let i=0; i<lines.length-1; i += 2) {
this.addLine(lines[i], lines[i+1]);
this.stats.line++;
polys.push(new base.Polygon()
.append(lines[i])
.append(lines[i+1])
.setOpen());
}
return this.addPolys(polys, options);
}
for (let i=0; i<lines.length-1; i += 2) {
this.addLine(lines[i], lines[i+1]);
this.stats.line++;
}
return this;
}
addPoly(poly, options) {
return this.addPolys([poly], options);
}
addPolys(polys, options) {
if (polys.length === 0) {
return this;
}
addPoly(poly, options) {
return this.addPolys([poly], options);
if (options && options.clean) {
polys = polys.map(p => p.clean(true));
}
addPolys(polys, options) {
if (polys.length === 0) {
return this;
}
if (options && options.clean) {
polys = polys.map(p => p.clean(true));
}
if (options && options.flat) {
return this.addFlats(polys, options);
}
if (options && !options.thin) {
return this.addPaths(polys, options);
}
polys = flat(polys);
if (options) {
polys.forEach(p => {
if (options.z !== undefined) {
p.setZ(options.z);
}
switch (typeof(options.color)) {
case 'number': p.color = options.color; break;
case 'object': p.color = options.color.line; break;
}
});
}
this.current.polys.appendAll(polys);
this.stats.line_poly += polys.length;
return this;
if (options && options.flat) {
return this.addFlats(polys, options);
}
if (options && !options.thin) {
return this.addPaths(polys, options);
}
polys = flat(polys);
if (options) {
polys.forEach(p => {
if (options.z !== undefined) {
p.setZ(options.z);
}
switch (typeof(options.color)) {
case 'number': p.color = options.color; break;
case 'object': p.color = options.color.line; break;
}
});
}
this.current.polys.appendAll(polys);
this.stats.line_poly += polys.length;
return this;
}
// z planar closed polygonal areas
addAreas(polys, options) {
const faces = this.current.faces;
polys = Array.isArray(polys) ? polys : [ polys ];
polys.forEach(poly => {
// z planar closed polygonal areas
addAreas(polys, options) {
const faces = this.current.faces;
polys = Array.isArray(polys) ? polys : [ polys ];
polys.forEach(poly => {
poly.earcut().forEach(ep => {
ep.forEachPoint(p => { faces.push(p.x, p.y, p.z) });
});
});
if (options && options.outline) {
this.addPolys(polys.clone(true));
}
}
// misleading name. these are flat offset paths (old style rendering)
addFlats(polys, options) {
const opts = options || {};
const offset = opts.offset || 1;
polys = flat(polys);
if (!polys.length) {
return;
}
const z = polys[0].getZ(), faces = this.current.faces;
const off_opt = { z, flat: true };
polys.forEach(poly => {
let exp = off_opt.outs = [];
if (poly.isOpen()) {
// shorten line by offset
const pp = poly.points, pl = pp.length;
pp[0] = pp[0].offsetPointTo(pp[1], offset);
pp[pl-1] = pp[pl-1].offsetPointTo(pp[pl-2], offset);
exp.appendAll(POLY.expand_lines(poly, offset * 0.9, z));
// force re-nest if offsets cause ends to join
exp = POLY.flatten(exp,[],true);
this.stats.flat_line = 0;
} else if (offset) {
POLY.offset([poly], offset * 0.9, off_opt);
POLY.offset([poly], -offset * 0.9, off_opt);
this.stats.flat_poly = 0;
}
if (opts.outline) {
this.addPolys(exp.clone());
}
const faceidx = faces.length / 3;
POLY.nest(exp).forEach((poly,i) => {
poly.earcut().forEach(ep => {
ep.forEachPoint(p => { faces.push(p.x, p.y, p.z) });
});
});
if (options && options.outline) {
this.addPolys(polys.clone(true));
const cur = this.current;
const color = opts.color ?
(typeof(opts.color) === 'number' ? { line: opts.color, face: opts.color } : opts.color) :
cur.color;
if (!cur.cface) {
cur.cface = [ Object.assign({ start: faceidx, count: Infinity }, color) ];
} else {
// rewrite last color count if color or opacity have changed
const pc = cur.cface[cur.cface.length - 1];
if (pc.face !== color.face || pc.opacity !== color.opacity) {
pc.count = faceidx;
cur.cface.push(Object.assign({ start: faceidx, count: Infinity }, color));
}
}
});
return this;
}
addPaths(polys, options) {
const opts = options || {};
const height = opts.height || 1;
const offset = opts.offset || 1;
polys = flat(polys);
if (!polys.length) {
return;
}
// misleading name. these are flat offset paths (old style rendering)
addFlats(polys, options) {
const opts = options || {};
const offset = opts.offset || 1;
polys = flat(polys);
if (!polys.length) {
return;
}
const z = polys[0].getZ(), faces = this.current.faces;
const off_opt = { z, flat: true };
polys.forEach(poly => {
let exp = off_opt.outs = [];
if (poly.isOpen()) {
// shorten line by offset
const pp = poly.points, pl = pp.length;
pp[0] = pp[0].offsetPointTo(pp[1], offset);
pp[pl-1] = pp[pl-1].offsetPointTo(pp[pl-2], offset);
exp.appendAll(POLY.expand_lines(poly, offset * 0.9, z));
// force re-nest if offsets cause ends to join
exp = POLY.flatten(exp,[],true);
this.stats.flat_line = 0;
} else if (offset) {
POLY.offset([poly], offset * 0.9, off_opt);
POLY.offset([poly], -offset * 0.9, off_opt);
this.stats.flat_poly = 0;
const profiles = this.profiles;
const prokey = `${offset}x${height}`;
if (!profiles[prokey]) {
const profile = new THREE.Shape();
profile.moveTo(-offset, -height);
profile.lineTo(-offset, height);
profile.lineTo( offset, height);
profile.lineTo( offset, -height);
profiles[prokey] = profile;
}
const profile = profiles[prokey].clone();
polys.forEach(poly => {
const contour = [];
poly = poly.debur(0.05);
if (!poly) return;
poly = poly.miter();
poly.points.forEach(p => {
contour.push(new THREE.Vector2(p.x, p.y));
});
const {index, faces} = ProfiledContourGeometry(profile, contour, poly.isClosed());
const cur = this.current;
const one = cur.paths[0];
if (one) {
// merge all contour geometry for massive speed gain
const add = one.faces.length / 3;
for (let i=0; i<index.length; i++) {
index[i] += add;
}
if (opts.outline) {
this.addPolys(exp.clone());
}
const faceidx = faces.length / 3;
POLY.nest(exp).forEach((poly,i) => {
poly.earcut().forEach(ep => {
ep.forEachPoint(p => { faces.push(p.x, p.y, p.z) });
});
});
const cur = this.current;
const color = opts.color ?
(typeof(opts.color) === 'number' ? { line: opts.color, face: opts.color } : opts.color) :
cur.color;
if (!cur.cface) {
cur.cface = [ Object.assign({ start: faceidx, count: Infinity }, color) ];
} else {
const feces = new Float32Array(one.faces.length + faces.length);
const indln = one.index.length;
feces.set(one.faces);
feces.set(faces, one.faces.length);
one.faces = feces;
one.index.appendAll(index);
// allow changing colors
if (opts.color) {
if (!cur.cpath) {
cur.cpath = [ Object.assign({ start: 0, count: indln - 1 }, cur.color) ];
}
// rewrite last color count if color or opacity have changed
const pc = cur.cface[cur.cface.length - 1];
if (pc.face !== color.face || pc.opacity !== color.opacity) {
pc.count = faceidx;
cur.cface.push(Object.assign({ start: faceidx, count: Infinity }, color));
const pc = cur.cpath[cur.cpath.length - 1];
if (pc.face !== opts.color.face || pc.opacity !== opts.color.opacity) {
pc.count = indln;
cur.cpath.push(Object.assign({ start: indln, count: Infinity }, opts.color));
}
}
});
return this;
}
addPaths(polys, options) {
const opts = options || {};
const height = opts.height || 1;
const offset = opts.offset || 1;
polys = flat(polys);
if (!polys.length) {
return;
}
const profiles = this.profiles;
const prokey = `${offset}x${height}`;
if (!profiles[prokey]) {
const profile = new THREE.Shape();
profile.moveTo(-offset, -height);
profile.lineTo(-offset, height);
profile.lineTo( offset, height);
profile.lineTo( offset, -height);
profiles[prokey] = profile;
}
const profile = profiles[prokey].clone();
polys.forEach(poly => {
const contour = [];
poly = poly.debur(0.05);
if (!poly) return;
poly = poly.miter();
poly.points.forEach(p => {
contour.push(new THREE.Vector2(p.x, p.y));
});
const {index, faces} = ProfiledContourGeometry(profile, contour, poly.isClosed());
const cur = this.current;
const one = cur.paths[0];
if (one) {
// merge all contour geometry for massive speed gain
const add = one.faces.length / 3;
for (let i=0; i<index.length; i++) {
index[i] += add;
}
const feces = new Float32Array(one.faces.length + faces.length);
const indln = one.index.length;
feces.set(one.faces);
feces.set(faces, one.faces.length);
one.faces = feces;
one.index.appendAll(index);
// allow changing colors
if (opts.color) {
if (!cur.cpath) {
cur.cpath = [ Object.assign({ start: 0, count: indln - 1 }, cur.color) ];
}
// rewrite last color count if color or opacity have changed
const pc = cur.cpath[cur.cpath.length - 1];
if (pc.face !== opts.color.face || pc.opacity !== opts.color.opacity) {
pc.count = indln;
cur.cpath.push(Object.assign({ start: indln, count: Infinity }, opts.color));
}
}
} else {
cur.paths.push({ index, faces, z: opts.z || poly.getZ() });
if (opts.color) {
cur.cpath = [ Object.assign({ start: 0, count: Infinity }, opts.color) ];
}
} else {
cur.paths.push({ index, faces, z: opts.z || poly.getZ() });
if (opts.color) {
cur.cpath = [ Object.assign({ start: 0, count: Infinity }, opts.color) ];
}
this.stats.contour++;
});
return this;
}
}
function flat(polys) {
if (Array.isArray(polys)) {
return POLY.flatten(polys.clone(true), [], true);
} else {
return POLY.flatten([polys.clone(true)], [], true);
}
}
function ProfiledContourGeometry(profileShape, contour, contourClosed) {
contourClosed = contourClosed !== undefined ? contourClosed : true;
const profileGeometry = new THREE.ShapeBufferGeometry(profileShape);
profileGeometry.rotateX(Math.PI * .5);
const profile = profileGeometry.attributes.position;
const faces = new Float32Array(profile.count * contour.length * 3);
for (let i = 0; i < contour.length; i++) {
const v1 = new THREE.Vector2().subVectors(contour[i - 1 < 0 ? contour.length - 1 : i - 1], contour[i]);
const v2 = new THREE.Vector2().subVectors(contour[i + 1 == contour.length ? 0 : i + 1], contour[i]);
const angle = v2.angle() - v1.angle();
const halfAngle = angle * .5;
let hA = halfAngle;
let tA = v2.angle() + Math.PI * .5;
if (!contourClosed){
if (i == 0 || i == contour.length - 1) {hA = Math.PI * .5;}
if (i == contour.length - 1) {tA = v1.angle() - Math.PI * .5;}
}
this.stats.contour++;
});
return this;
}
}
const shift = Math.tan(hA - Math.PI * .5);
const shiftMatrix = new THREE.Matrix4().set(
1, 0, 0, 0,
-shift, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);
function flat(polys) {
if (Array.isArray(polys)) {
return POLY.flatten(polys.clone(true), [], true);
} else {
return POLY.flatten([polys.clone(true)], [], true);
}
}
const tempAngle = tA;
const rotationMatrix = new THREE.Matrix4().set(
Math.cos(tempAngle), -Math.sin(tempAngle), 0, 0,
Math.sin(tempAngle), Math.cos(tempAngle), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);
function ProfiledContourGeometry(profileShape, contour, contourClosed) {
const translationMatrix = new THREE.Matrix4().set(
1, 0, 0, contour[i].x,
0, 1, 0, contour[i].y,
0, 0, 1, 0,
0, 0, 0, 1,
);
contourClosed = contourClosed !== undefined ? contourClosed : true;
const cloneProfile = profile.clone();
cloneProfile.applyMatrix4(shiftMatrix);
cloneProfile.applyMatrix4(rotationMatrix);
cloneProfile.applyMatrix4(translationMatrix);
const profileGeometry = new THREE.ShapeBufferGeometry(profileShape);
profileGeometry.rotateX(Math.PI * .5);
faces.set(cloneProfile.array, cloneProfile.count * i * 3);
const profile = profileGeometry.attributes.position;
const faces = new Float32Array(profile.count * contour.length * 3);
for (let i = 0; i < contour.length; i++) {
const v1 = new THREE.Vector2().subVectors(contour[i - 1 < 0 ? contour.length - 1 : i - 1], contour[i]);
const v2 = new THREE.Vector2().subVectors(contour[i + 1 == contour.length ? 0 : i + 1], contour[i]);
const angle = v2.angle() - v1.angle();
const halfAngle = angle * .5;
let hA = halfAngle;
let tA = v2.angle() + Math.PI * .5;
if (!contourClosed){
if (i == 0 || i == contour.length - 1) {hA = Math.PI * .5;}
if (i == contour.length - 1) {tA = v1.angle() - Math.PI * .5;}
}
const index = [];
const lastCorner = contourClosed == false ? contour.length - 1: contour.length;
const shift = Math.tan(hA - Math.PI * .5);
const shiftMatrix = new THREE.Matrix4().set(
1, 0, 0, 0,
-shift, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);
for (let i = 0; i < lastCorner; i++) {
for (let j = 0; j < profile.count; j++) {
const currCorner = i;
const nextCorner = i + 1 == contour.length ? 0 : i + 1;
const currPoint = j;
const nextPoint = j + 1 == profile.count ? 0 : j + 1;
const tempAngle = tA;
const rotationMatrix = new THREE.Matrix4().set(
Math.cos(tempAngle), -Math.sin(tempAngle), 0, 0,
Math.sin(tempAngle), Math.cos(tempAngle), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);
const a = nextPoint + profile.count * currCorner;
const b = currPoint + profile.count * currCorner;
const c = currPoint + profile.count * nextCorner;
const d = nextPoint + profile.count * nextCorner;
const translationMatrix = new THREE.Matrix4().set(
1, 0, 0, contour[i].x,
0, 1, 0, contour[i].y,
0, 0, 1, 0,
0, 0, 0, 1,
);
index.push(a, b, d);
index.push(b, c, d);
}
}
const cloneProfile = profile.clone();
cloneProfile.applyMatrix4(shiftMatrix);
cloneProfile.applyMatrix4(rotationMatrix);
cloneProfile.applyMatrix4(translationMatrix);
if (!contourClosed) {
// cheating because we know the profile length is 4 (for now)
const p1 = 0 + profile.count * 0;
const p2 = 1 + profile.count * 0;
const p3 = 2 + profile.count * 0;
const p4 = 3 + profile.count * 0;
index.push(p1, p2, p3);
index.push(p1, p3, p4);
const lc = lastCorner;
const p5 = 0 + profile.count * lc;
const p6 = 1 + profile.count * lc;
const p7 = 2 + profile.count * lc;
const p8 = 3 + profile.count * lc;
index.push(p7, p6, p5);
index.push(p8, p7, p5);
}
return {index, faces};
faces.set(cloneProfile.array, cloneProfile.count * i * 3);
}
self.kiri.Layers = Layers;
})();
const index = [];
const lastCorner = contourClosed == false ? contour.length - 1: contour.length;
for (let i = 0; i < lastCorner; i++) {
for (let j = 0; j < profile.count; j++) {
const currCorner = i;
const nextCorner = i + 1 == contour.length ? 0 : i + 1;
const currPoint = j;
const nextPoint = j + 1 == profile.count ? 0 : j + 1;
const a = nextPoint + profile.count * currCorner;
const b = currPoint + profile.count * currCorner;
const c = currPoint + profile.count * nextCorner;
const d = nextPoint + profile.count * nextCorner;
index.push(a, b, d);
index.push(b, c, d);
}
}
if (!contourClosed) {
// cheating because we know the profile length is 4 (for now)
const p1 = 0 + profile.count * 0;
const p2 = 1 + profile.count * 0;
const p3 = 2 + profile.count * 0;
const p4 = 3 + profile.count * 0;
index.push(p1, p2, p3);
index.push(p1, p3, p4);
const lc = lastCorner;
const p5 = 0 + profile.count * lc;
const p6 = 1 + profile.count * lc;
const p7 = 2 + profile.count * lc;
const p8 = 3 + profile.count * lc;
index.push(p7, p6, p5);
index.push(p8, p7, p5);
}
return {index, faces};
}
kiri.Layers = Layers;
});

View file

@ -2,9 +2,9 @@
"use strict";
(function () {
gapp.register("kiri.main", [], (root, exports) => {
const { base, data, load, kiri, moto, noop } = self;
const { base, data, load, kiri, moto, noop } = root;
const { api, consts, lang, Widget, newWidget, utils, stats } = kiri;
const { areEqual, parseOpt, encodeOpt, ajax, o2js, js2o, ls2o } = utils;
const { feature, platform, selection, settings } = api;
@ -943,4 +943,5 @@
// upon restore, seed presets
api.event.emit('preset', api.conf.dbo());
})();
});

View file

@ -7,24 +7,12 @@
* http://codeincomplete.com/posts/2011/5/7/bin_packing/
*/
(function () {
gapp.register("kiri.pack", [], (root, exports) => {
let kiri = self.kiri = self.kiri || {};
if (kiri.Pack) return;
const { kiri } = root;
kiri.Pack = Packer;
kiri.Sort = function (a, b) {
let aa = (a.w * a.h);
let ab = (b.w * b.h);
if (Math.abs(aa-ab) < 1) {
return (b.w / b.h) - (a.w / a.h);
} else {
return ab - aa;
}
};
function Packer(w, h, spacing) {
class Packer {
constructor(w, h, spacing) {
this.root = { x: 0, y: 0, w: w, h: h };
this.max = { w: 0, h: 0 };
this.packed = false;
@ -32,68 +20,77 @@
this.pad = this.spacing / 2;
}
Packer.prototype = {
simple: function (blocks) {
let n = 0, block, x = 0, y = 0, mh = 0;
while (n < blocks.length) {
block = blocks[n++];
block.fit = { x, y };
this.max.w = Math.max(this.max.w, x + block.w);
this.max.h = Math.max(this.max.h, y + block.h);
mh = Math.max(mh, block.h);
x += block.w + this.spacing;
if (x > this.root.w) {
x = 0;
y += mh + this.spacing;
mh = 0;
}
if (y > this.root.h) {
return this;
}
simple(blocks) {
let n = 0, block, x = 0, y = 0, mh = 0;
while (n < blocks.length) {
block = blocks[n++];
block.fit = { x, y };
this.max.w = Math.max(this.max.w, x + block.w);
this.max.h = Math.max(this.max.h, y + block.h);
mh = Math.max(mh, block.h);
x += block.w + this.spacing;
if (x > this.root.w) {
x = 0;
y += mh + this.spacing;
mh = 0;
}
this.packed = true;
return this;
},
fit: function (blocks, simple) {
if (simple) {
return this.simple(blocks);
if (y > this.root.h) {
return this;
}
let n = 0, node, block, w, h;
while (n < blocks.length) {
block = blocks[n++];
w = block.w + this.spacing;
h = block.h + this.spacing;
if (node = this.findNode(this.root, w, h)) {
block.fit = this.splitNode(node, w, h);
} else {
return this;
}
}
this.packed = true;
return this;
},
findNode: function (root, w, h) {
if (root.used) {
return this.findNode(root.right, w, h) || this.findNode(root.down, w, h);
} else if (w <= root.w && h <= root.h) {
return root;
} else {
return null;
}
},
splitNode: function (node, w, h) {
node.used = true;
node.down = { x: node.x, y: node.y + h, w: node.w, h: node.h - h };
node.right = { x: node.x + w, y: node.y, w: node.w - w, h: h };
this.max.w = Math.max(this.max.w, node.x + w);
this.max.h = Math.max(this.max.h, node.y + h);
return node;
}
this.packed = true;
return this;
}
};
fit(blocks, simple) {
if (simple) {
return this.simple(blocks);
}
let n = 0, node, block, w, h;
while (n < blocks.length) {
block = blocks[n++];
w = block.w + this.spacing;
h = block.h + this.spacing;
if (node = this.findNode(this.root, w, h)) {
block.fit = this.splitNode(node, w, h);
} else {
return this;
}
}
this.packed = true;
return this;
}
})();
findNode(root, w, h) {
if (root.used) {
return this.findNode(root.right, w, h) || this.findNode(root.down, w, h);
} else if (w <= root.w && h <= root.h) {
return root;
} else {
return null;
}
}
splitNode(node, w, h) {
node.used = true;
node.down = { x: node.x, y: node.y + h, w: node.w, h: node.h - h };
node.right = { x: node.x + w, y: node.y, w: node.w - w, h: h };
this.max.w = Math.max(this.max.w, node.x + w);
this.max.h = Math.max(this.max.h, node.y + h);
return node;
}
}
// kiri.Sort = function (a, b) {
// let aa = (a.w * a.h);
// let ab = (b.w * b.h);
// if (Math.abs(aa - ab) < 1) {
// return (b.w / b.h) - (a.w / a.h);
// } else {
// return ab - aa;
// }
// };
kiri.Pack = Packer;
});

View file

@ -2,9 +2,14 @@
"use strict";
(function () {
// dep: moto.space
// dep: kiri.api
// dep: kiri.consts
// dep: kiri.utils
// dep: kiri.widget
gapp.register("kiri.platform", [], (root, exports) => {
const { kiri, moto, load } = self;
const { kiri, moto, load } = root;
const { api, consts, driver, utils, newWidget, Widget } = kiri;
const { ajax, js2o } = utils;
const { space } = moto;
@ -796,4 +801,4 @@ const platform = api.platform = {
clear() { api.space.clear(); api.space.save(true) }
};
})();
});

View file

@ -2,7 +2,12 @@
"use strict";
(function() {
// dep: geo.base
// dep: geo.point
// dep: geo.polygon
// dep: geo.paths
// dep: kiri.consts
gapp.register("kiri.print", [], (root, evets) => {
const { base, kiri } = self;
const { paths, util, newPoint, Polygon } = base;
@ -10,10 +15,6 @@ const { tip2tipEmit } = paths;
const { numOrDefault } = util;
const { beltfact } = kiri.consts;
kiri.newPrint = function(settings, widgets, id) {
return new Print(settings, widgets, id);
};
class Print {
constructor(settings, widgets, id) {
this.id = id || new Date().getTime().toString(36);
@ -523,7 +524,6 @@ class Print {
done({ output: scope.output });
}
}
class Output {
@ -550,6 +550,13 @@ class Output {
}
}
kiri.Print = Print;
function newPrint(settings, widgets, id) {
return new Print(settings, widgets, id);
};
})();
gapp.overlay(kiri, {
Print,
newPrint
});
});

View file

@ -2,9 +2,13 @@
"use strict";
(function () {
// dep: moto.space
// dep: kiri.api
// dep: kiri.consts
// dep: kiri.utils
gapp.register("kiri.selection", [], (root, exports) => {
const { data, kiri, moto, noop } = self;
const { kiri, moto, noop } = self;
const { api, consts, utils } = kiri;
const { space } = moto;
@ -293,4 +297,4 @@ const selection = api.selection = {
meshes() { return selectedMeshes.slice() },
};
}());
});

View file

@ -2,7 +2,12 @@
"use strict";
(function () {
// dep: kiri.api
// dep: kiri.conf
// dep: kiri.utils
// dep: moto.space
// dep: data.local
gapp.register("kiri.settings", [], (root, exports) => {
const { data, kiri, moto, noop } = self;
const { api, conf, consts, utils } = kiri;
@ -147,6 +152,7 @@ function updateSettings(opt = {}) {
$('mode-device').innerText = device.deviceName;
$('mode-profile').innerText = `${cproc[mode]}${same ? '' : ' *'}`;
$('mode-info').style.display = 'flex';
}
function updateSettingsFromFields(setrec, uirec = api.ui, changes) {
@ -544,7 +550,7 @@ function settingsImport(data, ask) {
if (isWork) {
api.platform.clear();
kiri.codec.decode(data.work).forEach(widget => {
platform.add(widget, 0, true);
api.platform.add(widget, 0, true);
});
if (data.view) {
space.view.load(data.view);
@ -696,4 +702,4 @@ api.settings = {
mode() { return settings.mode },
};
})();
});

View file

@ -2,30 +2,25 @@
"use strict";
(function() {
// dep: geo.base
// dep: geo.polygons
gapp.register("kiri.slice", [], (root, exports) => {
const KIRI = self.kiri,
PRO = Slice.prototype,
BASE = self.base,
UTIL = BASE.util,
POLY = BASE.polygons,
NOKEY = BASE.key.NONE;
const { base, kiri } = root;
const { key, polygons } = base;
const { util } = base;
KIRI.Top = Top;
KIRI.Slice = Slice;
KIRI.newTop = newTop;
KIRI.newSlice = newSlice;
const POLY = base.polygons;
const NOKEY = key.NONE;
/**
* Object encapsulates a z-slice from an object. This code is shared by the
* client and the worker thread. As such, the view layers are ignored in the
* worker code paths.
*
* @param {number} z offset from ground
* @param {THREE.Group} [view] optional view parent object for layers
* @constructor
*/
function Slice(z, view) {
/**
* Object encapsulates a z-slice from an object. This code is shared by the
* client and the worker thread. As such, the view layers are ignored in the
* worker code paths.
*/
class Slice {
constructor (z, view) {
this.z = z; // z-index
this.index = 0; // slice index
this.lines = null; // slice raw
@ -38,63 +33,18 @@
this.layers = null; // will replace most of the layer output data
}
/**
* Represents a top-level (outer) polygon in a slice. Slices may contain
* multiple tops each with nested structures. Top objects contain cached
* and computed objects for quick access for rendering and dependent computations.
*
* @param {Polygon} polygon
* @constructor
*/
function Top(polygon) {
this.poly = polygon; // outline poly
}
Top.prototype.clone = function(deep) {
let top = new Top(this.poly.clone(deep));
return top;
};
/**
* return innermost traces under a given top. for FDM, this represents
* the outline shell that the fill touches. used by Print, Laser
*/
Top.prototype.innerShells = function() {
let shells = this.shells,
array = [];
if (shells) shells.forEach(function(p) {
if (p.inner) array.appendAll(p.inner);
});
return array;
};
/**
* Returns shell polygons of a given depth
* used by Print (FDM)
*
* @param {Polygon[]} out array to populate
* @returns {Polygon[]} array of top polygons
*/
Top.prototype.shellsAtDepth = function(depth) {
return this.shells ? this.shells.filter(poly => poly.depth === depth) : [];
};
/** ******************************************************************
* Slice Prototype Functions
******************************************************************* */
/**
* return Layers object for this slice. creates it if necessary.
*/
PRO.output = function() {
output() {
if (this.layers) return this.layers;
return this.layers = new KIRI.Layers();
return this.layers = new kiri.Layers();
};
/**
* returns a cloned slice the option of a deep clone on the top polys
*/
PRO.clone = function(deep) {
clone(deep) {
const from = this, slice = newSlice(from.z, from.view);
from.tops.forEach(function(top) {
slice.addTop(top.poly.clone(deep));
@ -102,16 +52,16 @@
return slice;
};
PRO.topPolys = function() {
topPolys() {
return this.tops.map(top => top.poly);
};
PRO.topSimples = function() {
topSimples() {
return this.tops.map(top => top.simple);
};
// FDM top intersect optimization
PRO.topPolysFlat = function() {
topPolysFlat() {
if (this.topFlatPolys) {
return this.topFlatPolys;
}
@ -119,7 +69,7 @@
};
// FDM retract path routing using first shell
PRO.topRouteFlat = function() {
topRouteFlat() {
if (this.topFlatRoutes) {
return this.topFlatRoutes;
}
@ -128,27 +78,27 @@
}
// CAM only
PRO.topPolyInners = function() {
topPolyInners() {
return this.tops.map(top => top.poly.inner).flat().filter(poly => poly);
};
// FDM / SLA only
PRO.topInners = function() {
topInners() {
return this.tops.map(top => top.last).flat().filter(poly => poly);
};
// FDM / SLA only
PRO.topFillOff = function() {
topFillOff() {
return this.tops.map(top => top.fill_off).flat().filter(poly => poly);
};
// FDM only
PRO.topFill = function() {
topFill() {
return this.tops.map(top => top.fill_lines).flat().filter(poly => poly);
};
// FDM only
PRO.topShells = function() {
topShells() {
return this.tops.map(top => top.shells).flat().filter(poly => poly);
};
@ -158,7 +108,7 @@
* vertical wall regions. this allows us to eliminate expensive diffs
* and infill computation when we detect the layers are the same.
*/
PRO.fingerprint = function() {
fingerprint() {
if (this.finger) {
return this.finger;
}
@ -168,18 +118,18 @@
/**
* returns true if the layers' fingerprints are the same
*/
PRO.fingerprintSame = function(slice) {
fingerprintSame(slice) {
return slice ? POLY.fingerprintCompare(this.fingerprint(), slice.fingerprint()) : false;
};
PRO.addTops = function(polys) {
addTops(polys) {
polys.forEach(p => {
this.addTop(p);
});
return this;
}
PRO.addTop = function(poly) {
addTop(poly) {
if (poly.length) {
// standard legacy polygon
let top = new Top(poly);
@ -189,7 +139,7 @@
} else {
// frop top object passed back by slicePost()
let top = new Top(poly.poly);
top.thin_fill = poly.thin_fill ? poly.thin_fill.map(p => BASE.newPoint(p.x,p.y,p.z)) : undefined;
top.thin_fill = poly.thin_fill ? poly.thin_fill.map(p => base.newPoint(p.x,p.y,p.z)) : undefined;
top.fill_lines = poly.fill_lines;
top.fill_off = poly.fill_off;
top.last = poly.last;
@ -202,7 +152,7 @@
throw "invalid top";
};
PRO.findClosestPointTo = function(target) {
findClosestPointTo(target) {
let min, find;
if (this.tops.length) {
@ -224,7 +174,7 @@
return min;
};
PRO.xray = function(dash = 3) {
xray(dash = 3) {
// console.log('xray', this);
this.output().setLayer(`xp`, 0x888800).addPolys(this.topPolys());
this.lines.forEach((line, i) => {
@ -232,19 +182,65 @@
const color = [ 0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff ][group];
this.output().setLayer(`xl-${group}`, color).addLine(line.p1, line.p2);
});
};
}
function newTop(poly) {
return new Top(poly);
}
/**
* Represents a top-level (outer) polygon in a slice. Slices may contain
* multiple tops each with nested structures. Top objects contain cached
* and computed objects for quick access for rendering and dependent computations.
*/
class Top {
constructor(polygon) {
this.poly = polygon; // outline poly
}
clone(deep) {
let top = new Top(this.poly.clone(deep));
return top;
}
/**
* @param {number} z
* @param {THREE.Group} view
* @returns {Slice}
* return innermost traces under a given top. for FDM, this represents
* the outline shell that the fill touches. used by Print, Laser
*/
function newSlice(z, view) {
return new Slice(z, view);
innerShells() {
let shells = this.shells,
array = [];
if (shells) shells.forEach(function(p) {
if (p.inner) array.appendAll(p.inner);
});
return array;
}
})();
/**
* Returns shell polygons of a given depth
* used by Print (FDM)
*
* @param {Polygon[]} out array to populate
* @returns {Polygon[]} array of top polygons
*/
shellsAtDepth(depth) {
return this.shells ? this.shells.filter(poly => poly.depth === depth) : [];
}
}
function newTop(poly) {
return new Top(poly);
}
function newSlice(z, view) {
return new Slice(z, view);
}
gapp.overlay(kiri, {
Top,
Slice,
newTop,
newSlice
});
});

View file

@ -2,261 +2,263 @@
"use strict";
(function() {
gapp.register("kiri.stack", [], (root, exports) => {
class Stack {
constructor(view, freeMem) {
this._view = view;
this.view = view.newGroup();
this.slices = [];
this.meshes = [];
this.freeMem = freeMem;
const { kiri } = root;
class Stack {
constructor(view, freeMem) {
this._view = view;
this.view = view.newGroup();
this.slices = [];
this.meshes = [];
this.freeMem = freeMem;
}
size() {
return this.slices.length;
}
hide() {
this.view.visible = false;
}
show() {
this.view.visible = true;
}
rotate(set) {
this.view.rotation.x = -set.angle * (Math.PI/180);
this.view.position.y += -set.dy;
this.view.position.z += -set.dz;
}
destroy() {
this._view.remove(this.view);
THREE.dispose(this.view);
this.view = this.slices = this.meshes = null;
}
setFreeMem(bool) {
this.freeMem = bool;
}
setVisible(newMin, newMax) {
this.show();
for (let i=0, s=this.slices, len=s.length; i<len; i++) {
s[i].visible = i >= newMin && i <= newMax;
if (s[i].visible) this.lastVis = s[i];
}
}
size() {
return this.slices.length;
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);
}
}
hide() {
this.view.visible = false;
addLayers(layers) {
// each slice gets a group so the slice can be toggled efficiently
let group = this.view.newGroup();
this.slices.push(group);
let map = this.renderLayers(layers, group);
// by default release memory after layers are rendered
if (this.freeMem) {
layers.init();
}
// return map of layers to materials (so they can be toggled on/off)
return map;
}
show() {
this.view.visible = true;
renderLayers(layers, group) {
const map = {}
for (const [label, layer] of Object.entries(layers.layers)) {
map[label] = this.renderLayer(layer, group);
}
return map;
}
rotate(set) {
this.view.rotation.x = -set.angle * (Math.PI/180);
this.view.position.y += -set.dy;
this.view.position.z += -set.dz;
}
destroy() {
this._view.remove(this.view);
THREE.dispose(this.view);
this.view = this.slices = this.meshes = null;
}
setFreeMem(bool) {
this.freeMem = bool;
}
setVisible(newMin, newMax) {
this.show();
for (let i=0, s=this.slices, len=s.length; i<len; i++) {
s[i].visible = i >= newMin && i <= newMax;
if (s[i].visible) this.lastVis = s[i];
renderLayer(layer, group) {
function addPoly(vertices, poly) {
const points = poly.points, len = points.length;
for (let i=1; i<len; i++) {
const p1 = points[i-1], p2 = points[i];
vertices.push(new THREE.Vector3(p1.x, p1.y, p1.z));
vertices.push(new THREE.Vector3(p2.x, p2.y, p2.z));
}
if (!poly.open) {
const p1 = points[len - 1], p2 = points[0];
vertices.push(new THREE.Vector3(p1.x, p1.y, p1.z));
vertices.push(new THREE.Vector3(p2.x, p2.y, p2.z));
}
}
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);
}
}
const { polys, lines, faces, cface, paths, cpath, color, off } = layer;
const meshes = [];
const defstate = !off;
const mats = [];
mats.state = defstate;
addLayers(layers) {
// each slice gets a group so the slice can be toggled efficiently
let group = this.view.newGroup();
this.slices.push(group);
let map = this.renderLayers(layers, group);
// by default release memory after layers are rendered
if (this.freeMem) {
layers.init();
}
// return map of layers to materials (so they can be toggled on/off)
return map;
}
renderLayers(layers, group) {
const map = {}
for (const [label, layer] of Object.entries(layers.layers)) {
map[label] = this.renderLayer(layer, group);
}
return map;
}
renderLayer(layer, group) {
function addPoly(vertices, poly) {
const points = poly.points, len = points.length;
for (let i=1; i<len; i++) {
const p1 = points[i-1], p2 = points[i];
vertices.push(new THREE.Vector3(p1.x, p1.y, p1.z));
vertices.push(new THREE.Vector3(p2.x, p2.y, p2.z));
}
if (!poly.open) {
const p1 = points[len - 1], p2 = points[0];
vertices.push(new THREE.Vector3(p1.x, p1.y, p1.z));
vertices.push(new THREE.Vector3(p2.x, p2.y, p2.z));
}
}
const { polys, lines, faces, cface, paths, cpath, color, off } = layer;
const meshes = [];
const defstate = !off;
const mats = [];
mats.state = defstate;
if (polys.length || lines.length) {
const vert = []; // vertexes
const mat = []; // materials
const grp = []; // material groups
const geo = new THREE.BufferGeometry();
// map all the poly and line colors for re-use
const cmap = {}
let cidx = 0;
let last = undefined;
for (let i=0, il=polys.length; i<il; i++) {
const vl = vert.length;
const poly = polys[i];
addPoly(vert, poly);
const pc = poly.color !== undefined ? { line: poly.color, opacity: color.opacity } : color;
const pk = pc.line;
const cc = cmap[pk] = cmap[pk] || { idx: cidx++, mat: createLineMaterial(pc, mat) };
if (last !== pk) {
if (grp.length) {
// rewrite counts for last group
const prev = grp[grp.length - 1]
prev[1] = vl;
}
grp.push([vl, Infinity, cc.idx]);
last = pk;
if (polys.length || lines.length) {
const vert = []; // vertexes
const mat = []; // materials
const grp = []; // material groups
const geo = new THREE.BufferGeometry();
// map all the poly and line colors for re-use
const cmap = {}
let cidx = 0;
let last = undefined;
for (let i=0, il=polys.length; i<il; i++) {
const vl = vert.length;
const poly = polys[i];
addPoly(vert, poly);
const pc = poly.color !== undefined ? { line: poly.color, opacity: color.opacity } : color;
const pk = pc.line;
const cc = cmap[pk] = cmap[pk] || { idx: cidx++, mat: createLineMaterial(pc, mat) };
if (last !== pk) {
if (grp.length) {
// rewrite counts for last group
const prev = grp[grp.length - 1]
prev[1] = vl;
}
grp.push([vl, Infinity, cc.idx]);
last = pk;
}
// for now, line segments inherit the last color
for (let i=0, il=lines.length; i<il; i++) {
const p = lines[i];
vert.push(new THREE.Vector3(p.x, p.y, p.z));
}
// ensure at least one group using the default color settings
if (grp.length === 0) {
grp.push([0, Infinity, 0]);
mat.push(createLineMaterial(color));
}
mat.forEach(m => m.visible = defstate);
for (let i=0; i<grp.length; i++) {
const g = grp[i];
geo.addGroup(g[0], g[1], g[2]);
}
geo.setFromPoints(vert);
const segs = new THREE.LineSegments(geo, mat);
meshes.push(segs);
group.add(segs);
mats.appendAll(mat);
}
if (faces.length) {
const mat = [];
if (cface) {
cface.forEach(c => { mat.push(newMat(c, true)) });
} else {
mat.push(newMat(color, true));
}
mat.forEach(m => m.visible = defstate);
// for now, line segments inherit the last color
for (let i=0, il=lines.length; i<il; i++) {
const p = lines[i];
vert.push(new THREE.Vector3(p.x, p.y, p.z));
}
// ensure at least one group using the default color settings
if (grp.length === 0) {
grp.push([0, Infinity, 0]);
mat.push(createLineMaterial(color));
}
mat.forEach(m => m.visible = defstate);
for (let i=0; i<grp.length; i++) {
const g = grp[i];
geo.addGroup(g[0], g[1], g[2]);
}
geo.setFromPoints(vert);
const segs = new THREE.LineSegments(geo, mat);
meshes.push(segs);
group.add(segs);
mats.appendAll(mat);
}
if (faces.length) {
const mat = [];
if (cface) {
cface.forEach(c => { mat.push(newMat(c, true)) });
} else {
mat.push(newMat(color, true));
}
mat.forEach(m => m.visible = defstate);
const geo = new THREE.BufferGeometry();
if (faces.toFloat32) {
geo.setAttribute('position', new THREE.BufferAttribute(faces.toFloat32(), 3));
} else {
geo.setAttribute('position', new THREE.BufferAttribute(faces, 3));
}
// geo.computeFaceNormals();
geo.computeVertexNormals();
if (cface) {
cface.forEach((c, i) => geo.addGroup(c.start, c.count, i));
} else {
geo.addGroup(0, Infinity, 0);
}
const mesh = new THREE.Mesh(geo, mat);
mesh.castShadow = true;
mesh.receiveShadow = true;
meshes.push(mesh);
group.add(mesh);
mats.appendAll(mat);
}
if (paths.length) {
const mat = [];
if (cpath) {
cpath.forEach(c => { mat.push(newMat(c)) });
} else {
mat.push(newMat(color));
}
mat.forEach(m => m.visible = defstate);
// const mat = newMat(data);
paths.forEach((path, i) => {
const { index, faces, z, colors } = path;
const geo = new THREE.BufferGeometry();
if (faces.toFloat32) {
geo.setAttribute('position', new THREE.BufferAttribute(faces.toFloat32(), 3));
} else {
geo.setAttribute('position', new THREE.BufferAttribute(faces, 3));
}
geo.setAttribute('position', new THREE.BufferAttribute(faces, 3));
geo.setIndex(index);
// geo.computeFaceNormals();
geo.computeVertexNormals();
if (cface) {
cface.forEach((c, i) => geo.addGroup(c.start, c.count, i));
if (cpath) {
cpath.forEach((c, i) => geo.addGroup(c.start, c.count, i));
} else {
geo.addGroup(0, Infinity, 0);
}
const mesh = new THREE.Mesh(geo, mat);
mesh.position.z = z;
mesh.castShadow = true;
mesh.receiveShadow = true;
meshes.push(mesh);
group.add(mesh);
mats.appendAll(mat);
}
if (paths.length) {
const mat = [];
if (cpath) {
cpath.forEach(c => { mat.push(newMat(c)) });
} else {
mat.push(newMat(color));
}
mat.forEach(m => m.visible = defstate);
// const mat = newMat(data);
paths.forEach((path, i) => {
const { index, faces, z, colors } = path;
const geo = new THREE.BufferGeometry();
geo.setAttribute('position', new THREE.BufferAttribute(faces, 3));
geo.setIndex(index);
// geo.computeFaceNormals();
geo.computeVertexNormals();
if (cpath) {
cpath.forEach((c, i) => geo.addGroup(c.start, c.count, i));
} else {
geo.addGroup(0, Infinity, 0);
}
const mesh = new THREE.Mesh(geo, mat);
mesh.position.z = z;
mesh.castShadow = true;
mesh.receiveShadow = true;
meshes.push(mesh);
group.add(mesh);
});
mats.appendAll(mat);
}
this.new_meshes = meshes;
this.meshes.appendAll(meshes);
mats.forEach(mat => mat.visible = defstate);
return mats;
});
mats.appendAll(mat);
}
this.new_meshes = meshes;
this.meshes.appendAll(meshes);
mats.forEach(mat => mat.visible = defstate);
return mats;
}
}
self.kiri.Stack = Stack;
kiri.Stack = Stack;
let shininess = 15,
specular = 0x444444,
emissive = 0x101010,
metalness = 0,
roughness = 0.3,
newMat = createPhongMaterial;
let shininess = 15,
specular = 0x444444,
emissive = 0x101010,
metalness = 0,
roughness = 0.3,
newMat = createPhongMaterial;
function createLineMaterial(color, array) {
const opacity = color.lopacity || color.opacity || 1;
const mat = new THREE.LineBasicMaterial({
transparent: true,
opacity: opacity,
color: color.line
});
if (array) {
array.push(mat);
}
return mat;
function createLineMaterial(color, array) {
const opacity = color.lopacity || color.opacity || 1;
const mat = new THREE.LineBasicMaterial({
transparent: true,
opacity: opacity,
color: color.line
});
if (array) {
array.push(mat);
}
return mat;
}
function createStandardMaterial(color, flat) {
return new THREE.MeshStandardMaterial({
emissive,
roughness,
metalness,
transparent: color.opacity != 1,
opacity: color.opacity || 1,
color: color.face,
side: flat ? THREE.DoubleSide : THREE.FrontSide
});
}
function createStandardMaterial(color, flat) {
return new THREE.MeshStandardMaterial({
emissive,
roughness,
metalness,
transparent: color.opacity != 1,
opacity: color.opacity || 1,
color: color.face,
side: flat ? THREE.DoubleSide : THREE.FrontSide
});
}
function createPhongMaterial(color, flat) {
return new THREE.MeshPhongMaterial({
shininess,
specular,
transparent: color.opacity != 1,
opacity: color.opacity || 1,
color: color.face,
side: flat ? THREE.DoubleSide : THREE.FrontSide
});
}
function createPhongMaterial(color, flat) {
return new THREE.MeshPhongMaterial({
shininess,
specular,
transparent: color.opacity != 1,
opacity: color.opacity || 1,
color: color.face,
side: flat ? THREE.DoubleSide : THREE.FrontSide
});
}
})();
});

View file

@ -2,168 +2,172 @@
"use strict";
(function() {
// dep: kiri.stack
// use: kiri.api
// use: kiri.ui
gapp.register("kiri.stacks", [], (root, exports) => {
let KIRI = self.kiri,
freeMem = true,
stacks = {},
tallest = 0,
min = 0,
max = 0,
labels, API, UC, UI, DYN;
const { kiri } = root;
function init() {
labels = $("layers");
API = KIRI.api,
UC = API.uc,
UI = API.ui;
let freeMem = true,
stacks = {},
tallest = 0,
min = 0,
max = 0,
labels, API, UC, UI, DYN;
function init() {
labels = $("layers");
API = kiri.api,
UC = API.uc,
UI = API.ui;
}
function setFreeMem(bool) {
Object.values(stacks).forEach(stack => stack.obj.setFreeMem(bool));
freeMem = bool;
return this;
}
function clear() {
if (!API) {
init();
}
function setFreeMem(bool) {
Object.values(stacks).forEach(stack => stack.obj.setFreeMem(bool));
freeMem = bool;
return this;
// remove stacks from their views
for (const [stack, data] of Object.entries(stacks)) {
data.clear();
}
min = max = tallest = 0;
stacks = {};
DYN = UI.dyn = {};
function clear() {
if (!API) {
init();
}
// remove stacks from their views
for (const [stack, data] of Object.entries(stacks)) {
data.clear();
}
min = max = tallest = 0;
stacks = {};
DYN = UI.dyn = {};
// clear labels
UC.setGroup(labels);
labels.innerHTML = '';
}
// clear labels
UC.setGroup(labels);
labels.innerHTML = '';
}
function rotate(set) {
Object.values(stacks).forEach(stack => {
stack.obj.rotate(set);
});
}
function rotate(set) {
Object.values(stacks).forEach(stack => {
stack.obj.rotate(set);
function getStack(name) {
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 getStack(name) {
function create(name, view) {
if (stacks[name]) {
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];
}
const stack = stacks[name] = {
layers: [ ],
obj: new KIRI.Stack(view, freeMem),
add: function(layers) {
let lmap = layers.layers;
let map = stack.obj.addLayers(layers);
for (const [label, mats] of Object.entries(map)) {
if (!DYN[label]) {
let check = DYN[label] = {
group: [],
toggle: UC.newBoolean(label, (abc) => {
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;
if (color !== undefined) {
let div = document.createElement('div');
div.setAttribute('class', 'color');
div.style.backgroundColor = `#${color.toString(16).padStart(6,0)}`;
check.toggle.parentNode.appendChild(div);
check.toggle.classList.add("color");
}
const stack = stacks[name] = {
layers: [ ],
obj: new kiri.Stack(view, freeMem),
add: function(layers) {
let lmap = layers.layers;
let map = stack.obj.addLayers(layers);
for (const [label, mats] of Object.entries(map)) {
if (!DYN[label]) {
let check = DYN[label] = {
group: [],
toggle: UC.newBoolean(label, (abc) => {
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;
if (color !== undefined) {
let div = document.createElement('div');
div.setAttribute('class', 'color');
div.style.backgroundColor = `#${color.toString(16).padStart(6,0)}`;
check.toggle.parentNode.appendChild(div);
check.toggle.classList.add("color");
}
const ctrl = DYN[label];
ctrl.group.appendAll(mats);
ctrl.toggle.checked = mats.state;
}
tallest = Math.max(tallest, stack.obj.size());
},
remove: function() {
view.remove(stack.obj.view);
THREE.dispose(stack.obj.view);
},
clear: function() {
view.remove(stack.obj.view);
THREE.dispose(stack.obj.view);
},
button: function(label, action) {
UC.newRow([ UC.newButton(label, action) ]);
const ctrl = DYN[label];
ctrl.group.appendAll(mats);
ctrl.toggle.checked = mats.state;
}
};
return stack;
}
function remove(name) {
const stack = stacks[name];
if (stack) {
stack.remove();
delete stacks[name];
tallest = Math.max(tallest, stack.obj.size());
},
remove: function() {
view.remove(stack.obj.view);
THREE.dispose(stack.obj.view);
},
clear: function() {
view.remove(stack.obj.view);
THREE.dispose(stack.obj.view);
},
button: function(label, action) {
UC.newRow([ UC.newButton(label, action) ]);
}
}
function getRange() {
return {min, max, tallest};
}
function setRange(newMin, newMax) {
Object.values(stacks).forEach(stack => {
stack.obj.setVisible(newMin,newMax);
});
min = newMin;
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
};
return stack;
}
})();
function remove(name) {
const stack = stacks[name];
if (stack) {
stack.remove();
delete stacks[name];
}
}
function getRange() {
return {min, max, tallest};
}
function setRange(newMin, newMax) {
Object.values(stacks).forEach(stack => {
stack.obj.setVisible(newMin,newMax);
});
min = newMin;
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
};
});

View file

@ -2,9 +2,12 @@
"use strict";
(function () {
// dep: moto.broker
// dep: kiri.api
// dep: kiri.utils
gapp.register("kiri.stats", [], (root, exports) => {
const { data, kiri, gapp } = self;
const { data, kiri, gapp } = root;
const { broker } = gapp;
const { api, utils } = kiri;
const { js2o, o2js } = utils;
@ -55,4 +58,4 @@ class Stats {
kiri.stats = new Stats();
})();
});

View file

@ -1,100 +1,112 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
kiri.load(function(API) {
// circle
let pgeo = new THREE.CircleGeometry(8, 30);
let pmat = new THREE.MeshBasicMaterial({color: 0xff0000, opacity: 0.5, transparent: true});
let pmesh = new THREE.Mesh(pgeo, pmat);
// circle outline
let pi = pgeo.index.array;
let pp = pgeo.attributes.position.array;
let np = [];
for (let i=0; i<pi.length; i++) {
if (pi[i] * pi[i+1]) {
np.push(pp[pi[i]*3+0]);
np.push(pp[pi[i]*3+1]);
np.push(pp[pi[i]*3+2]);
np.push(pp[pi[i+1]*3+0]);
np.push(pp[pi[i+1]*3+1]);
np.push(pp[pi[i+1]*3+2]);
}
// dep: add.three
// dep: kiri.api
gapp.register("kiri.tools", [], (root, exports) => {
const { kiri } = root;
const { api } = kiri;
// circle
let pgeo = new THREE.CircleGeometry(8, 30);
let pmat = new THREE.MeshBasicMaterial({color: 0xff0000, opacity: 0.5, transparent: true});
let pmesh = new THREE.Mesh(pgeo, pmat);
// circle outline
let pi = pgeo.index.array;
let pp = pgeo.attributes.position.array;
let np = [];
for (let i=0; i<pi.length; i++) {
if (pi[i] * pi[i+1]) {
np.push(pp[pi[i]*3+0]);
np.push(pp[pi[i]*3+1]);
np.push(pp[pi[i]*3+2]);
np.push(pp[pi[i+1]*3+0]);
np.push(pp[pi[i+1]*3+1]);
np.push(pp[pi[i+1]*3+2]);
}
let wgeo = new THREE.BufferGeometry();
wgeo.setAttribute('position', new THREE.BufferAttribute(np.toFloat32(), 3));
let wmat = new THREE.LineBasicMaterial({ color: 0x883333 });
let wmesh = new THREE.LineSegments(wgeo, wmat);
pmesh.add(wmesh);
}
let alert;
let lastobj;
let lastface;
let enabled = false;
let wgeo = new THREE.BufferGeometry();
wgeo.setAttribute('position', new THREE.BufferAttribute(np.toFloat32(), 3));
function cleanup() {
if (lastobj) {
lastobj.remove(pmesh);
lastobj = undefined;
}
let wmat = new THREE.LineBasicMaterial({ color: 0x883333 });
let wmesh = new THREE.LineSegments(wgeo, wmat);
pmesh.add(wmesh);
let alert;
let lastobj;
let lastface;
let enabled = false;
function cleanup() {
if (lastobj) {
lastobj.remove(pmesh);
lastobj = undefined;
}
}
function endit() {
if (enabled) {
API.hide.alert(alert);
$('lay-flat').classList.remove('selected');
API.feature.hover = false;
enabled = false;
alert = undefined;
cleanup();
}
}
API.event.on('key.esc', endit);
API.event.on('tool.mesh.lay-flat', () => {
if (enabled) {
endit();
return;
}
if (API.feature.hover) {
console.log('lay flat cannot pre-empt hover');
return;
}
$('lay-flat').classList.add('selected');
API.feature.hover = true;
enabled = true;
alert = API.show.alert('[ESC] to end lay-flat operation', 600000);
});
API.event.on('mouse.hover', (ev) => {
if (!enabled) {
return;
}
function endit() {
if (enabled) {
api.hide.alert(alert);
$('lay-flat').classList.remove('selected');
api.feature.hover = false;
enabled = false;
alert = undefined;
cleanup();
let { int, ints, event, point, type } = ev;
if (type === 'widget') {
lastface = int.face;
let obj = lastobj = int.object;
let norm = int.face.normal;
let opos = obj.widget.track.pos;
obj.add(pmesh);
pmesh.position.x = point.x - opos.x + norm.x * 0.1;
pmesh.position.y = -point.z - opos.y + norm.y * 0.1;
pmesh.position.z = point.y + norm.z * 0.1;
let q = new THREE.Quaternion().setFromUnitVectors(
new THREE.Vector3(0,0,1),
new THREE.Vector3(norm.x,norm.y,norm.z)
);
pmesh.setRotationFromQuaternion(q);
}
});
API.event.on('mouse.hover.up', (ev) => {
if (!enabled) {
return;
}
let { int, point, object } = ev;
if (!object) {
return;
}
let q = new THREE.Quaternion().setFromUnitVectors(lastface.normal, new THREE.Vector3(0,0,-1));
API.selection.rotate(q);
}
}
api.event.on('key.esc', endit);
api.event.on('tool.mesh.lay-flat', () => {
if (enabled) {
endit();
});
return;
}
if (api.feature.hover) {
console.log('lay flat cannot pre-empt hover');
return;
}
$('lay-flat').classList.add('selected');
api.feature.hover = true;
enabled = true;
alert = api.show.alert('[ESC] to end lay-flat operation', 600000);
});
api.event.on('mouse.hover', (ev) => {
if (!enabled) {
return;
}
cleanup();
let { int, ints, event, point, type } = ev;
if (type === 'widget') {
lastface = int.face;
let obj = lastobj = int.object;
let norm = int.face.normal;
let opos = obj.widget.track.pos;
obj.add(pmesh);
pmesh.position.x = point.x - opos.x + norm.x * 0.1;
pmesh.position.y = -point.z - opos.y + norm.y * 0.1;
pmesh.position.z = point.y + norm.z * 0.1;
let q = new THREE.Quaternion().setFromUnitVectors(
new THREE.Vector3(0,0,1),
new THREE.Vector3(norm.x,norm.y,norm.z)
);
pmesh.setRotationFromQuaternion(q);
}
});
api.event.on('mouse.hover.up', (ev) => {
if (!enabled) {
return;
}
let { int, point, object } = ev;
if (!object) {
return;
}
let q = new THREE.Quaternion().setFromUnitVectors(lastface.normal, new THREE.Vector3(0,0,-1));
api.selection.rotate(q);
endit();
});
});

View file

@ -2,7 +2,7 @@
"use strict";
(function() {
gapp.register("kiri.ui", [], (root, exports) => {
if (kiri.ui) return;
@ -939,4 +939,4 @@
});
}
})();
});

View file

@ -5,9 +5,9 @@
// dep: data.local
// dep: main.kiri
// use: moto.ajax
(function () {
gapp.register("kiri.utils", [], (root, exports) => {
const { kiri, moto, data } = self;
const { kiri, moto, data } = root;
function parseOpt(ov) {
let opt = {}, kv, kva;
@ -96,7 +96,7 @@ function areEqual(o1, o2) {
return false;
}
kiri.utils = {
exports({
areEqual,
parseOpt,
encodeOpt,
@ -107,6 +107,6 @@ kiri.utils = {
avgc,
rgb,
a2c
};
});
})();
});

View file

@ -1,12 +1,10 @@
console.log/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
"use strict";
(function() {
gapp.register("kiri.widget", [], (root, exports) => {
if (self.kiri.Widget) return;
const { base, kiri } = self;
const { base, kiri } = root;
const { api, driver, utils } = kiri;
const { config, util, polygons } = base;
const { Mesh, newPoint, verticesToPoints } = base;
@ -963,4 +961,4 @@ Widget.deleteFromState = function(id,ondone) {
kiri.Widget = Widget;
kiri.newWidget = newWidget;
})();
});

View file

@ -2,9 +2,9 @@
"use strict";
(function () {
gapp.register("kiri.widgets", [], (root, exports) => {
const { data, kiri, moto, noop } = self;
const { data, kiri, moto, noop } = root;
const { api, consts, utils, newWidget, Widget } = kiri;
const { space } = moto;
@ -110,4 +110,4 @@ const widgets = api.widgets = {
for(fn) { widgets.each(fn) },
};
})();
});