diff --git a/release.md b/release.md
index 2eb7d8ad..57dc47fb 100644
--- a/release.md
+++ b/release.md
@@ -6,6 +6,7 @@ Full docs @ https://docs.grid.space/projects/kiri-moto
# Release 3.9
* more graceful handling of security contexts blocking SharedArrayBuffer
+* CAM add dogbones support to traces ops
* CAM add scripted contour filtering (to be extended to other ops)
* CAM add calculation of taper length from angle
* CAM add open poly-line offsetting with trace op
diff --git a/src/kiri-mode/cam/client.js b/src/kiri-mode/cam/client.js
index cd29cfb4..f3dc994d 100644
--- a/src/kiri-mode/cam/client.js
+++ b/src/kiri-mode/cam/client.js
@@ -1378,6 +1378,8 @@ CAM.init = function(kiri, api) {
plunge: 'camTracePlunge',
bottom: 'camTraceBottom',
offover: 'camTraceOffOver',
+ dogbone: 'camTraceDogbone',
+ revbone: 'camTraceDogbone',
select: 'camTraceMode'
}).inputs = {
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
@@ -1393,8 +1395,10 @@ CAM.init = function(kiri, api) {
down: UC.newInput(LANG.cc_sdwn_s, {title:LANG.cc_sdwn_l, convert:UC.toFloat, units:true}),
thru: UC.newInput(LANG.cc_thru_s, {title:LANG.cc_thru_l, convert:UC.toFloat, units:true}),
offover: UC.newInput(LANG.cc_offd_s, {title:LANG.cc_offd_l, convert:UC.toFloat, units:true, show:() => poppedRec.offset !== "none"}),
- sep: UC.newBlank({class:"pop-sep", modes:MCAM, show:zBottom, poop:true }),
+ sep: UC.newBlank({class:"pop-sep", modes:MCAM, poop:true }),
bottom: UC.newBoolean(LANG.cf_botm_s, undefined, {title:LANG.cf_botm_l, show:(op,conf) => conf.process.camZBottom}),
+ dogbone: UC.newBoolean(LANG.co_dogb_s, undefined, {title:LANG.co_dogb_l}),
+ revbone: UC.newBoolean(LANG.co_dogr_s, undefined, {title:LANG.co_dogr_l, show:(op) => poppedRec.dogbone}),
sep: UC.newBlank({class:"pop-sep"}),
menu: UC.newRow([
UC.newButton(undefined, func.traceAdd, {icon:''}),
diff --git a/src/kiri-mode/cam/ops.js b/src/kiri-mode/cam/ops.js
index 8f80fc0e..67c43092 100644
--- a/src/kiri-mode/cam/ops.js
+++ b/src/kiri-mode/cam/ops.js
@@ -1000,6 +1000,9 @@ class OpTrace extends CamOp {
return slice;
}
function followZ(poly) {
+ if (op.dogbones) {
+ CAM.addDogbones(poly, toolDiam / 5, !op.revbone);
+ }
let z = poly.getZ();
let slice = newSliceOut(z);
slice.camTrace = { tool, rate, plunge };
diff --git a/src/kiri-mode/cam/slice.js b/src/kiri-mode/cam/slice.js
index f0d4dc1c..c5547e13 100644
--- a/src/kiri-mode/cam/slice.js
+++ b/src/kiri-mode/cam/slice.js
@@ -292,18 +292,25 @@ CAM.addDogbones = function(poly, dist, reverse) {
if (Array.isArray(poly)) {
return poly.forEach(p => CAM.addDogbones(p, dist));
}
- if (poly.open) return;
+ // if (poly.open) return;
+ let open = poly.open;
let isCW = poly.isClockwise();
if (reverse || poly.parent) isCW = !isCW;
let oldpts = poly.points.slice();
let lastpt = oldpts[oldpts.length - 1];
let lastsl = lastpt.slopeTo(oldpts[0]).toUnit();
let newpts = [ ];
- for (let i=0; i 45) {
let newa = base.newSlopeFromAngle(lastsl.angle + bdiff);
newpts.push(lastpt.projectOnSlope(newa, dist));
diff --git a/web/kiri/lang/en.js b/web/kiri/lang/en.js
index e4c2a54d..a38b1bc9 100644
--- a/web/kiri/lang/en.js
+++ b/web/kiri/lang/en.js
@@ -496,6 +496,8 @@ self.kiri.lang['en-us'] = {
co_menu: "outline",
co_dogb_s: "dogbones",
co_dogb_l: ["insert dogbone cuts","into inside corners"],
+ co_dogr_s: "reverse bones",
+ co_dogr_l: ["reverse dogbone direction"],
co_clrt_s: "clear top",
co_clrt_l: ["cut starting at the top of stock","when stock is enabled"],
co_wide_s: "wide cutout",