diff --git a/src/kiri/init.js b/src/kiri/init.js
index 136adc8e..4409b7a5 100644
--- a/src/kiri/init.js
+++ b/src/kiri/init.js
@@ -1545,12 +1545,18 @@
camDrillSep: UC.newBlank({class:"pop-sep"}),
camDrillReg: UC.newSelect(LANG.cd_regi_s, {modes:CAM, title:LANG.cd_regi_l}, "drillreg"),
camTabs: UC.newGroup(LANG.ct_menu, null, {modes:CAM, marker:true}),
- camTabsAngle: UC.newInput(LANG.ct_angl_s, {title:LANG.ct_angl_l, convert:UC.toInt, bound:UC.bound(0,360), modes:CAM}),
- camTabsCount: UC.newInput(LANG.ct_numb_s, {title:LANG.ct_numb_l, convert:UC.toInt, bound:UC.bound(1,20), modes:CAM}),
camTabsWidth: UC.newInput(LANG.ct_wdth_s, {title:LANG.ct_wdth_l, convert:UC.toFloat, bound:UC.bound(0.005,100), modes:CAM, units:true}),
camTabsHeight: UC.newInput(LANG.ct_hght_s, {title:LANG.ct_hght_l, convert:UC.toFloat, bound:UC.bound(0.005,100), modes:CAM, units:true}),
camTabSep: UC.newBlank({class:"pop-sep"}),
- camTabsOn: UC.newBoolean(LANG.enable, onBooleanClick, {title:LANG.ct_nabl_l, modes:CAM}),
+ camTabsAngle: UC.newInput(LANG.ct_angl_s, {title:LANG.ct_angl_l, convert:UC.toInt, bound:UC.bound(0,360), modes:CAM}),
+ camTabsCount: UC.newInput(LANG.ct_numb_s, {title:LANG.ct_numb_l, convert:UC.toInt, bound:UC.bound(1,20), modes:CAM}),
+ camTabsOn: UC.newBoolean(LANG.ct_nabl_s, onBooleanClick, {title:LANG.ct_nabl_l, modes:CAM}),
+ camTabSep: UC.newBlank({class:"pop-sep"}),
+ camTabsManual: UC.newRow([
+ (UI.tabAdd = UC.newButton(undefined, onButtonClick, {icon:''})),
+ (UI.tabDun = UC.newButton(undefined, onButtonClick, {icon:''})),
+ (UI.tabClr = UC.newButton(undefined, onButtonClick, {icon:''}))
+ ], {modes:CAM, class:"ext-buttons f-row"}),
camSep1: UC.newGroup(null, null, {modes:CAM, class:"set-sep"}),
camStock: UC.newGroup(LANG.cs_menu, null, {modes:CAM}),
diff --git a/src/mode/cam/client.js b/src/mode/cam/client.js
index 933cc1f4..8fcb86e8 100644
--- a/src/mode/cam/client.js
+++ b/src/mode/cam/client.js
@@ -15,9 +15,12 @@
isCamMode,
isParsed,
camStock,
- API, SPACE, STACKS, MODES, VIEWS, UI, UC;
+ func = {},
+ API, FDM, SPACE, STACKS, MODES, VIEWS, UI, UC;
CAM.init = function(kiri, api) {
+ FDM = KIRI.driver.FDM;
+
// console.log({kiri,api})
STACKS = api.const.STACKS;
SPACE = api.const.SPACE;
@@ -72,7 +75,9 @@
api.event.on("preview.end", () => {
isParsed = false;
- if (camStock) STACKS.getStack("bounds").button("animate", animate);
+ if (isCamMode && camStock) {
+ STACKS.getStack("bounds").button("animate", animate);
+ }
});
api.event.on("code.loaded", (info) => {
@@ -81,8 +86,163 @@
STACKS.getStack("parse", SPACE.platform.world).button("animate", animate);
}
});
+
+ api.event.on("button.click", target => {
+ switch (target) {
+ case api.ui.tabAdd:
+ return func.tadd();
+ case api.ui.tabDun:
+ return func.tdone();
+ case api.ui.tabClr:
+ api.uc.confirm("clear tabs?").then(ok => {
+ if (ok) func.tclear();
+ });
+ break;
+ }
+ });
+
+ api.event.on("cam.tabs.add", func.tadd = () => {
+ alert = api.show.alert("<esc> key cancels editing tabs");
+ api.feature.hover = true;
+ });
+ api.event.on("cam.tabs.done", func.tdone = () => {
+ delbox('intZ');
+ delbox('intW');
+ delbox('supp');
+ api.hide.alert(alert);
+ api.feature.hover = false;
+ });
+ api.event.on("cam.tabs.clear", func.tclear = () => {
+ func.sdone();
+ // clearAllWidgetSupports();
+ // API.conf.save();
+ });
+ api.event.on("slice.begin", () => {
+ if (isCamMode) {
+ func.sdone();
+ // updateVisiblity();
+ }
+ });
+ api.event.on("key.esc", () => {
+ if (isCamMode) {
+ func.sdone()
+ }
+ });
+ api.event.on("widget.rotate", rot => {
+ if (!isCamMode) {
+ return;
+ }
+ const {widget, x, y, z} = rot;
+ if (x || y) {
+ // clearWidgetSupports(widget);
+ } else {
+ let ann = API.widgets.annotate(widget.id);
+ let tabs = ann.support || [];
+ // tabs.forEach(sup => {
+ // let wsup = widget.tabs[sup.id];
+ // let vc = new THREE.Vector3(sup.x, sup.y, sup.z);
+ // let m4 = new THREE.Matrix4();
+ // m4 = m4.makeRotationFromEuler(new THREE.Euler(x || 0, y || 0, z || 0));
+ // vc.applyMatrix4(m4);
+ // wsup.box.position.x = wsup.x = sup.x = vc.x;
+ // wsup.box.position.y = wsup.y = sup.y = vc.y;
+ // wsup.box.position.z = wsup.z = sup.z = vc.z;
+ // });
+ }
+ });
+ api.event.on("mouse.hover.up", int => {
+ if (!isCamMode) {
+ return;
+ }
+ delbox('supp');
+ if (lastTab) {
+ const {widget, box, id} = lastTab;
+ widget.adds.remove(box);
+ widget.mesh.remove(box);
+ delete widget.tabs[id];
+ let ta = API.widgets.annotate(widget.id).tab;
+ let ix = 0;
+ ta.forEach((rec,i) => {
+ if (rec.id === id) {
+ ix = i;
+ }
+ });
+ ta.splice(ix,1);
+ API.conf.save();
+ return;
+ }
+ if (!iw) return;
+ let ip = iw.track.pos;
+ let wa = api.widgets.annotate(iw.id);
+ let wt = (wa.tab = wa.tab || []);
+ let x = ic.x - ip.x, y = -ic.z - ip.y, z = 0, id = Date.now();
+ let rec = {x, y, z, dw:10, dh:10, id};
+ wt.push(Object.clone(rec));
+ addWidgetTab(iw, rec);
+ API.conf.save();
+ });
+ const zaxis = {x: 0, y: 0, z: 1};
+ let lastTab, tab, iw, ic;
+ api.event.on("mouse.hover", data => {
+ if (!isCamMode) {
+ return;
+ }
+ delbox('supp');
+ const { int, type, point } = data;
+ const object = int ? int.object : null;
+ const tab = int ? object.tab : null;
+ if (lastTab) {
+ lastTab.box.material.color.r = 0;
+ lastTab = null;
+ }
+ if (tab) {
+ tab.box.material.color.r = 0.5;
+ lastTab = tab;
+ return;
+ }
+ if (type !== 'widget') {
+ iw = null;
+ return;
+ }
+ let n = int.face.normal;
+ iw = int.object.widget;
+ ic = int.point;
+ // only near vertical faces
+ if (Math.abs(n.z) > 0.1) {
+ return;
+ }
+ const { track } = iw;
+ const { stock, bounds, process } = API.conf.get();
+ const { camTabsWidth, camTabsHeight } = process;
+ const zp = stock.z - track.box.d + process.camZBottom - process.camZTopOffset + camTabsHeight / 2;
+ ic.x += n.x * 2.5;
+ ic.z -= n.y * 2.5;
+ ic.y = zp; // swap in world space y,z
+ const q = new THREE.Quaternion().setFromAxisAngle(zaxis, Math.atan2(n.y, n.x));
+ addbox({x:ic.x, y:ic.y, z:ic.z}, 0x0000dd, 'supp', {
+ x:5, y:camTabsWidth, z:camTabsHeight
+ }, { rotate: q });
+ });
};
+ function addWidgetTab(widget, pos) {
+ const { x, y, z, dw, dh, id } = pos;
+ const tabs = widget.tabs = (widget.tabs || {});
+ // prevent duplicate restore from repeated settings load calls
+ if (!tabs[id]) {
+ pos.box = addbox(
+ { x, y, z }, 0x0000dd, 'supp',
+ { x:dw, y:dw, z:dh }, { group: widget.mesh }
+ );
+ pos.box.tab = Object.assign({widget}, pos);
+ tabs[id] = pos;
+ widget.adds.push(pos.box);
+ }
+ }
+
+ function addbox() { return FDM.addbox(...arguments)};
+ function delbox() { return FDM.delbox(...arguments)};
+
function animate() {
isAnimate = true;
API.widgets.opacity(isParsed ? 0 : 0.75);
diff --git a/src/mode/fdm/client.js b/src/mode/fdm/client.js
index 7194fc72..62255bc7 100644
--- a/src/mode/fdm/client.js
+++ b/src/mode/fdm/client.js
@@ -11,13 +11,16 @@
SPACE, API,
p1, p2, iw,
lastMode, lastView, lastPillar,
+ isFdmMode = false,
alert = [],
+ boxes = {},
func = {};
FDM.init = function(kiri, api) {
API = api;
SPACE = api.const.SPACE;
api.event.on("mode.set", mode => {
+ isFdmMode = mode === 'FDM';
lastMode = mode;
updateVisiblity();
});
@@ -64,13 +67,22 @@
API.conf.save();
});
api.event.on("slice.begin", () => {
+ if (!isFdmMode) {
+ return;
+ }
func.sdone();
updateVisiblity();
});
api.event.on("key.esc", () => {
+ if (!isFdmMode) {
+ return;
+ }
func.sdone()
});
api.event.on("widget.rotate", rot => {
+ if (!isFdmMode) {
+ return;
+ }
const {widget, x, y, z} = rot;
if (x || y) {
clearWidgetSupports(widget);
@@ -90,6 +102,9 @@
}
});
api.event.on("mouse.hover.up", int => {
+ if (!isFdmMode) {
+ return;
+ }
delbox('supp');
if (lastPillar) {
const {widget, box, id} = lastPillar;
@@ -121,6 +136,9 @@
API.conf.save();
});
api.event.on("mouse.hover", data => {
+ if (!isFdmMode) {
+ return;
+ }
// delbox('intZ');
// delbox('intW');
// addbox(point, 0xff0000, 'intZ');
@@ -198,7 +216,10 @@
const sups = widget.sups = (widget.sups || {});
// prevent duplicate restore from repeated settings load calls
if (!sups[id]) {
- pos.box = addbox({x, y, z}, 0x0000dd, 'supp', { x:dw, y:dw, z:dh }, widget.mesh);
+ pos.box = addbox(
+ { x, y, z }, 0x0000dd, 'supp',
+ { x:dw, y:dw, z:dh }, { group: widget.mesh }
+ );
pos.box.pillar = Object.assign({widget}, pos);
sups[id] = pos;
widget.adds.push(pos.box);
@@ -232,17 +253,16 @@
delete API.widgets.annotate(widget.id).support;
}
- function delbox(name, group) {
- const old = SPACE[`__${name}`];
+ function delbox(name) {
+ const old = boxes[name];
if (old) {
- (group || SPACE.scene).remove(old);
+ old.groupTo.remove(old);
}
}
- function addbox(point, color, name, dim = {x:1,y:1,z:1}, group) {
+ function addbox(point, color, name, dim = {x:1,y:1,z:1}, opt = {}) {
delbox(name);
- name = `__${name}`;
- const box = SPACE[name] = new THREE.Mesh(
+ const box = boxes[name] = new THREE.Mesh(
new THREE.BoxGeometry(dim.x, dim.y, dim.z),
new THREE.MeshPhongMaterial({
transparent: true,
@@ -253,8 +273,21 @@
box.position.x = point.x;
box.position.y = point.y;
box.position.z = point.z;
- (group || SPACE.scene).add(box);
+
+ const group = opt.group || SPACE.scene
+ group.add(box);
+ box.groupTo = group;
+
+ if (opt.rotate) {
+ opt.matrix = new THREE.Matrix4().makeRotationFromQuaternion(opt.rotate);
+ }
+ if (opt.matrix) {
+ box.geometry.applyMatrix4(opt.matrix);
+ }
return box;
}
+ FDM.delbox = delbox;
+ FDM.addbox = addbox;
+
})();
diff --git a/web/kiri/lang/en.js b/web/kiri/lang/en.js
index f7d2fe94..930cefe2 100644
--- a/web/kiri/lang/en.js
+++ b/web/kiri/lang/en.js
@@ -349,7 +349,8 @@ kiri.lang['en-us'] = {
ct_wdth_l: "width in workspace units",
ct_hght_s: "height",
ct_hght_l: "height in workspace units",
- ct_nabl_l: "enable or disable tabs\ntab generation skipped when\ninside only mode enabled",
+ ct_nabl_s: "auto",
+ ct_nabl_l: "auto generate radial tabs\nprojected from part center\nusing count and angle offset",
// OUTPUT
ou_menu: "output",