Drag Knife Blade Wear

Having used the same two drag knife blades intermittently over the last three-ish years, I wondered just how worn they’d gotten:

Drag Knife Blades - sides
Drag Knife Blades – sides

For scale, the cylindrical part of the blade is 1.0 mm OD.

The blade with the longer face (left above and bottom below) has seen the most use and is definitely rounded at the tip:

Drag Knife Blades - tips
Drag Knife Blades – tips

Three unused blades have sharp tips:

Drag Knife Blades - unused 60 45 30 degree
Drag Knife Blades – unused 60 45 30 degree

From the top, the (nominal) blade angles are 60°, 45°, and 30°, generally indicated by yellow, red, and blue plastic caps. However, various eBay sellers disagree on how to measure the angle (up from surface / outward from axis) and which cap colors correspond to which angles.

The unused 45° blade bracketed by the two used blades:

Drag Knife Blades - unused in center
Drag Knife Blades – unused in center

The two lower blades have angles somewhere between 30° and 45°, suggesting slack grinder and QC tolerances. If the actual angle matters to you, buy an assortment (from one seller!), measure what you get, and don’t be surprised when the results aren’t anything in particular.

Perhaps, with careful attention to alignment in a non-pivoting / collet holder, one might scribe exceedingly narrow lines.

The microphotographic setup:

Drag Knife Blades - microscope stage setup
Drag Knife Blades – microscope stage setup

That’s the back of a sheet of carbon paper (remember carbon paper?), which is deep dark gray in normal light. It’s sitting on the sheet of 100 mil grid paper providing scale for small objects, atop the microscope stage positioner, with cold white illumination from an LED ring light.

Protip: even worn blades remain lethally sharp …

MPCNC: bCNC Probe Camera Refresh

For the usual inscrutable reasons, updating bCNC killed the USB camera on the MPCNC, although it still worked fine with VLC. Rather than argue with it, I popped a more recent camera from the heap and stuck it onto the MPCNC central assembly:

bCNC - USB probe camera - attachment
bCNC – USB probe camera – attachment

This one has a nice rectangular case, although the surface might be horrible silicone that turns to snot after a few years. The fancy silver snout rotates to focus the lens from a few millimeters to infinity … and beyond!

If you think it looks a bit off-kilter, you’re absolutely right:

bCNC - USB probe camera - off-axis alignment
bCNC – USB probe camera – off-axis alignment

The lens image reflected in a mirror on the platform shows the optical axis has nothing whatsoever to do with the camera case or lens snout:

bCNC - USB probe camera - off-axis reflection
bCNC – USB probe camera – off-axis reflection

Remember, the mirror reflects the lens image back to itself only when the optical axis is perpendicular to the mirror. With the mirror flat on the platform, the lens must be directly above it.

Because the MPCNC camera rides at a constant height over the platform, the actual focus & scale depends on the material thickness, but this should be typical:

bCNC - USB Probe Camera - scale - screenshot
bCNC – USB Probe Camera – scale – screenshot

It set up a Tek Circuit Computer test deck within 0.2 mm and the other two within 0.1 mm, so it’s close enough.

The image looks a whole lot better: cheap USB cameras just keep improving …

ACM Poughkeepsie Presentation: Algorithmic Art

In the unlikely event you’re in Poughkeepsie this evening, I’ll be doing a talk on my Algorithmic Art for the Poughkeepsie ACM chapter, with a look at the HPGL and G-Code transforming math into motion:

Superformula - triangle burst - detail
Superformula – triangle burst – detail

The PDF of the “slides” lacks my patter, but the embedded linkies will carry you to the blog posts & background information:

See you there! [grin]

MPCNC Drag Knife Holder: Lock Screw

While calibrating the MPCNC’s probe camera offset for the drag knife holder, this happened:

Drag Knife - vertical escape
Drag Knife – vertical escape

Well, at least it’s centered on the target:

Drag Knife - vertical escape - detail
Drag Knife – vertical escape – detail

This happened a few times before, because my fingers don’t fit neatly inside the drag knife holder to tighten the lock ring:

Drag Knife - LM12UU ground shaft - assembled
Drag Knife – LM12UU ground shaft – assembled

[Update: The lock ring keeps the holder at a fixed position inside the 12 mm shaft and doesn’t affect the blade directly. When the ring works loose, the threaded holder can rotate to expose more blade and, in this case, stab deeper into the target. ]

So I turned & knurled an aluminum ring, then tapped a 3×0.5 mm hole for a lock screw plucked from the Drawer o’ Random M3 Screws:

Drag Knife - lock screw - side
Drag Knife – lock screw – side

A view looking along the screw shows a bit more detail around the spring:

Drag Knife - lock screw - front
Drag Knife – lock screw – front

The general idea is to set the blade extension, then tighten the lock screw to hold it in place, without relying on the original brass lock ring, shown here while cutting a boss for the spring:

Drag Knife - turning spring recess
Drag Knife – turning spring recess

The lock screw’s knurled handle just barely kisses the NPCNC’s black tool holder ring, so my guesstimated measurements were a bit off. Clamping the knife holder one itsy higher in the tool holder solved the problem.

I cranked on 300 g of spring preload and, squashed like that, the spring’s rate is now 75 g/mm. Cutting at Z=-1 mm should suffice for laminated paper slide rule decks.

The original sizing doodle:

Drag Knife Holder - lock screw ring doodle
Drag Knife Holder – lock screw ring doodle

The short 18 mm section clears the inside of the LM12UU bearing, although it could be a millimeter shorter. The 19 mm section comes from the 3/4 inch aluminum rod I used, skim-cut to clean it up.

If I ever remake this thing, it needs a major re-think to get all the dimensions flying in formation again.

Raspberry Pi: Adding a PIXEL Desktop Launcher

The Raspberry Pi’s Raspbian PIXEL Desktop UI (not to be confused with the Google Pixel phone) descends from LXDE, with all the advantages & disadvantages that entails. One nuisance seems to be the inability to create a launcher for a non-standard program.

The stock task bar (or whatever it’s called) has a few useful launchers and you can add a launcher for a program installed through the usual Add/Remove Software function, as shown by the VLC icon:

LXDE launcher icons
LXDE launcher icons

Adding a bCNC launcher requires a bit of legerdemain, because it’s not found in the RPi repositories. Instead, install bCNC according to its directions:

… install various pre-requisites as needed …
pip2 install --upgrade git+https://github.com/vlachoudis/bCNC 

Which is also how you upgrade to the latest & greatest version, as needed.

You then launch bCNC from inside a terminal:

python2 -m bCNC

The installation includes all the bits & pieces required to create a launcher; they’re just not in the right places.

So put them there:

sudo cp ./.local/lib/python2.7/site-packages/bCNC/bCNC.png /usr/share/icons/
sudo cp .local/lib/python2.7/site-packages/bCNC/bCNC.desktop /usr/share/applications/bCNC.desktop

The bCNC.desktop file looks like this:

[Desktop Entry]
Version=1.0
Type=Application
Name=bCNC
Comment=bCNC Controller
Exec=bCNC
Icon=bCNC.png
Path=
Terminal=true
StartupNotify=false
Name[en_US]=bCNC

Set Terminal=false if you don’t want a separate terminal window and don’t care about any of the messages bCNC writes to the console during its execution. However, those messages may provide the only hint about happened as bCNC falls off the rails.

With all that in place, it turns out LXDE creates a user-specific panel configuration file only when you change the default system panel configuration. Add a VLC launcher to create the local ~/.config/lxpanel/LXDE-pi/panels/panel file.

With that ball rolled, then add the bCNC launcher:

nano .config/lxpanel/LXDE-pi/panels/panel
… add this stanza …
Plugin {
  type=launchbar
  Config {
    Button {
      id=bCNC.desktop
    }
  }
}

Log out, log back in again, and the bCNC icon should appear:

LXDE launcher icons - additions
LXDE launcher icons – additions

Click it and away you go:

bCNC - Running from LXDE Launcher
bCNC – Running from LXDE Launcher

At least you (and I) will start closer to the goal when something else changes …

Drag Knife Cuttery: Entry & Exit Moves

The first pass at cutting laminated decks for the Homage Tektronix Circuit Computer left little uncut snippets at the starting point of the cut. The point of the drag knife blade trundles along behind the cutting edge and, when the ending point equals the starting point, leaves an un-cut sliver as it’s retracted vertically:

Drag Knife - LM12UU - knife blade detail
Drag Knife – LM12UU – knife blade detail

The knife blade isn’t aligned in any particular direction, so it can leave a nick on either side as it enters the deck vertically at the start of the cut.

Gradually entering the deck along the cut line gives the blade enough time to swivel around to the proper alignment before it gets down to serious cutting. Continuing the final cut past the starting point then allows the blade to recut anything remaining from the entry move.

The middle and top decks have windows exposing the scales:

Tek CC - radial text example
Tek CC – radial text example

The paths are basically two arcs connected by semicircular cuts, but with ramps on each end recutting the entry and exit paths:

Top Deck - Window Cut Path
Top Deck – Window Cut Path

The entry path in the upper left slants downward from the TravelZ level of 1.5 (-ish) mm to Z=0, with the nose of the blade holder flush against the surface and the blade sunk to its full length. The vertical path to Z=-2 (-ish) increases the cutting pressure from roughly the preload value to preload + 2*(spring rate), so the blade won’t ride up under the cutting forces.

The path then goes completely around the window at Z=-2, then ramps up to the TravelZ level again.

All of which produces a neat cutout that sticks to the Cricut mat when I peel the rest of the deck off:

Tek CC - MPCNC drag knife
Tek CC – MPCNC drag knife

That’s a middle deck before I started laminating them, but you get the general idea.

The GCMC code (extracted from the complete lump) looks like this:

  local WindowArc = 54deg;

  local ac = -17 * ScaleArc + ScaleRT/2;   // center of window arc
  local r0 = DeckRad - ScaleHeight;        // outer
  local r1 = DeckRad - 2 * ScaleHeight;    // inner

  local aw = WindowArc - to_deg(atan(ScaleHeight,(r0 + r1)/2));    // window arc minus endcaps

  p0 = r0 * [cos(ac + aw/2),sin(ac + aw/2),-];
  p1 = r0 * [cos(ac - aw/2),sin(ac - aw/2),-];
  local p2 = r1 * [cos(ac - aw/2),sin(ac - aw/2),-];
  local p3 = r1 * [cos(ac + aw/2),sin(ac + aw/2),-];

  goto(p3);
  arc_cw(p0 +| [-,-,0],ScaleHeight/2);    // blade enters surface
  move([-,-,KnifeZ]);                     // apply pressure

  arc_cw(p1,r0);                          // smallest arc
  arc_cw(p2,ScaleHeight/2);               // half a circle
  arc_ccw(p3,r1);
  arc_cw(p0,ScaleHeight/2);

  arc_cw(p1 +| [-,-,TravelZ],r0);         // exit from cut

  goto([0,0,-]);
  goto([-,-,SafeZ]);

Having measured the angular position of the window and its size on the original Tek CC, I compute the coordinates of the four points where the semicircular “end caps” meet the longer arcs, then connect the dots with arc_xx() functions to generate the G-Code commands. As always, using the proper radius signs requires trial & error.

While I was at it, I added entry & exit moves for the deck’s central pivot hole and outer perimeter.

I’m pretty sure the right CAM package would take care of that, but GCMC operates well below the CAM level.

Homage Tektronix Circuit Computer: Pen Plotter Version

A reproduction circular slide rule from the mid-1960s may not be the cutting edge of consumer demand, but the pen version of a Tektronix Circuit Computer came out pretty well:

Homage Tektronix Circuit Computer - green on white laminated
Homage Tektronix Circuit Computer – green on white laminated

A Bash script compiles the GCMC code with eight different parameter combinations to produce pairs of G-Code files to draw (“engrave” being aspirational) and cut (“mill”, likewise) the three decks and the cursor.

The CNC 3018XL with a Pilot V5RT pen draws the deck scales on white paper:

Pilot V5RT holder - installed
Pilot V5RT holder – installed

Better paper definitely produces better results, so I must rummage through the Big Box o’ Paper to see what lies within. Laminating the decks improves their durability and matches the original Tek surface finish.

The MPCNC with a drag knife blade cuts through a laminated deck like butter:

Tek CC - MPCNC drag knife
Tek CC – MPCNC drag knife

Setting the XY origin to dead center on each deck requires carefully calibrating the USB video camera, with the end result accurate to maybe ±0.1 mm around the entire perimeter. Both machines move equal linear distances along both axes, which was definitely comforting.

Having made half a dozen cursors from various bits of acrylic, none of which look particularly good, demonstrates my engraving hand is too weak for a complete slide rule:

Tek Circuit Computer - cursor hairline
Tek Circuit Computer – cursor hairline

With logarithmic scales in hand, however, adapting the GCMC source code to produce general-purpose circular slide rules with only two decks and smaller diameters may be the way to improve my engraving-fu, as a full-scale Tektronix Circuit Computer would chew up three square-foot plastic sheets.

A general-purpose slide rule would need multi-color (well, at least bi-color) labels and digits for red “inverse” scales to remind you (well, me) they read backwards. Some slipsticks use left-slanting italics, left-pointing markers (“<2”), or other weirdness, but they’re all different.

An early small-scale version engraved on ABS came out OK, modulo poor ink fill:

Tek CC bottom - ABS 160g 2400mm-min
Tek CC bottom – ABS 160g 2400mm-min

Engraving the decks on hard drive platters doesn’t count:

Tek CC - bottom deck - scaled to HD platter
Tek CC – bottom deck – scaled to HD platter

All in all, it’s been an interesting exercise and, as you may have guessed, will become a Digital Machinist column.

The GCMC and Bash source code as a GitHub Gist:

// Tektronix Circuit Computer Reproduction
// Ed Nisley KE4ZNU - 2019-11
//-----
// Library routines
include("tracepath.inc.gcmc");
include("engrave.inc.gcmc");
TekOD = to_mm(7.75in); // orginal Tek Circuit Computer diameter
FALSE = 0;
TRUE = 1;
//-----
// Command line parameters
// -D various useful tidbits
// add unit to speeds and depths: 2000mm / -3.00mm / etc
if (!isdefined("BaseOD")) {
BaseOD = TekOD;
}
comment("Base OD: ",BaseOD);
SizeRatio = BaseOD / TekOD; // overall scaling for different base diameters
comment(" scale factor: ",SizeRatio);
if (!isdefined("SelectPart")) {
SelectPart = "Bottom";
}
comment("Part: ",SelectPart);
if (!isdefined("Operation")) {
Operation = "Engrave";
}
comment("Operation: ",Operation);
if (!isdefined("ScaleSpeed")) {
ScaleSpeed = 2400mm;
}
if (!isdefined("TextSpeed")) {
TextSpeed = 2400mm;
}
// Engraving & drag knife force is proportional to depth, but you must know the coefficent!
if (!isdefined("EngraveZ")) {
EngraveZ = -1.0mm;
}
if (!isdefined("KnifeZ")) {
KnifeZ = -2.0mm;
}
if (!isdefined("KnifeSpeed")) {
KnifeSpeed = 1000mm;
}
//-----
// Define useful constants
SafeZ = 10.00mm; // above all obstructions
TravelZ = 1.00mm; // within engraving area
//-----
// Overall values
ScaleHeight = to_inch(3.0/8.0) * SizeRatio; // scale-to-scale distance
WindowHeight = ScaleHeight; // cutout window opening
DeckBottomOD = BaseOD; // deck sizes depend on scale height
DeckMiddleOD = DeckBottomOD - 2*ScaleHeight;
DeckTopOD = DeckMiddleOD - 2*(ScaleHeight + WindowHeight);
ScaleArc = 18deg; // angular length of one decade: +CCW
ScaleExdent = 0.20; // log spacing at end of scales to identifiers
Scale2Pi = log10(2*pi()) * ScaleArc; // angular offset for scales using 2*pi
ScaleRT = log10(2.197225) * ScaleArc; // angular offset for risetime
TauAngle = 150deg; // arbitrary offset to 1.0 on tau scales
TitleAngle = -50deg; // ... to Tek title, then +180deg to logo
INWARD = -1; // text and tick alignment (used as integers)
OUTWARD = 1;
TEXT_LEFT = -1; // text justification
TEXT_CENTERED = 0;
TEXT_RIGHT = 1;
TextFont = FONT_HSANS_1_RS;
TitleTextSize = 3.1 * SizeRatio * [1.0mm,1.0mm];
LegendTextSize = 1.8 * SizeRatio * [1.0mm,1.0mm];
ScaleTextSize = 1.4 * SizeRatio * [1.0mm,1.0mm];
//----
// Define tick layout for scales
// Numeric values = scale position, tick length
// These are not algorithmic!
TickMajor = 3.2mm * SizeRatio; // length of tick marks
TickMid = 1.9mm * SizeRatio;
TickMinor = 1.2mm * SizeRatio;
TickScaleNarrow = {
[1.0,TickMajor],
[1.1,TickMinor],[1.2,TickMinor],[1.3,TickMinor],[1.4,TickMinor],
[1.5,TickMid],
[1.6,TickMinor],[1.7,TickMinor],[1.8,TickMinor],[1.9,TickMinor],
[2.0,TickMajor],
[2.2,TickMinor],[2.4,TickMinor],[2.6,TickMinor],[2.8,TickMinor],
[3.0,TickMajor],
[3.2,TickMinor],[3.4,TickMinor],[3.6,TickMinor],[3.8,TickMinor],
[4.0,TickMajor],
[4.5,TickMinor],
[5.0,TickMajor],
[5.5,TickMinor],
[6.0,TickMajor],
[6.5,TickMinor],
[7.0,TickMajor],
[7.5,TickMinor],
[8.0,TickMajor],
[8.5,TickMinor],
[9.0,TickMajor],
[9.5,TickMinor]
};
TickScaleWide = {
[1.0,TickMajor],
[1.1,TickMinor],[1.2,TickMinor],[1.3,TickMinor],[1.4,TickMinor],
[1.5,TickMid],
[1.6,TickMinor],[1.7,TickMinor],[1.8,TickMinor],[1.9,TickMinor],
[2.0,TickMajor],
[2.1,TickMinor],[2.2,TickMinor],[2.3,TickMinor],[2.4,TickMinor],
[2.5,TickMid],
[2.6,TickMinor],[2.7,TickMinor],[2.8,TickMinor],[2.9,TickMinor],
[3.0,TickMajor],
[3.2,TickMinor],[3.4,TickMinor],[3.6,TickMinor],[3.8,TickMinor],
[4.0,TickMajor],
[4.2,TickMinor],[4.4,TickMinor],[4.6,TickMinor],[4.8,TickMinor],
[5.0,TickMajor],
[5.5,TickMinor],
[6.0,TickMajor],
[6.5,TickMinor],
[7.0,TickMajor],
[7.5,TickMinor],
[8.0,TickMajor],
[8.5,TickMinor],
[9.0,TickMajor],
[9.5,TickMinor]
};
TickLabels = [1,2,5]; // labels only these ticks, must be integers
TickGap = 0.50 * ScaleTextSize.y; // gap between text and ticks
PivotOD = 5.0mm; // center bolt OD
Legend1 = "Ed Nisley - KE4ZNU";
Legend2 = "softsolder.com";
//-----------------------------------------------------------------------------
// Text & Scale Engraving
//-----
// Write text on a radial line
function RadialText(TextPath,CenterPt,Radius,Angle,Justify,Orient) {
local pl = TextPath[-1].x; // path length
local ji = (Justify == TEXT_LEFT) ? 0mm : // justification, assume OUTWARD
(Justify == TEXT_CENTERED) ? -pl/2 :
(Justify == TEXT_RIGHT) ? -pl :
0mm;
if (Orient == INWARD) {
TextPath = rotate_xy(TextPath,180deg);
ji = -ji;
}
TextPath += [Radius + ji,0mm];
return rotate_xy(TextPath,Angle) + CenterPt;
}
//-----
// Draw a radial legend
// Offset in units of char height: 0 = baseline on radius, +/- = above/below
function RadialLegend(Text,Center,Radius,Angle,Justify,Orient,Offset) {
local tp = scale(typeset(Text,TextFont),LegendTextSize) + [0mm,Offset * LegendTextSize.y];
local tpr = RadialText(tp,Center,Radius,Angle,Justify,Orient);
feedrate(TextSpeed);
engrave(tpr,TravelZ,EngraveZ);
}
//-----
// Bend text around an arc
function ArcText(TextPath,CenterPt,Radius,BaseAngle,Justify,Orient) {
local pl = TextPath[-1].x; // path length
local c = 2*pi()*Radius;
local ta = to_deg(360 * pl / c); // subtended angle
local ja = (Justify == TEXT_LEFT ? 0deg : // assume OUTWARD
(Justify == TEXT_CENTERED) ? -ta / 2 :
(Justify == TEXT_RIGHT) ? -ta :
0deg);
ja = BaseAngle + Orient*ja;
local ArcPath = {};
local pt,r,a;
foreach(TextPath; pt) {
if (!isundef(pt.x) && !isundef(pt.y) && isundef(pt.z)) { // XY motion, no Z
r = (Orient == OUTWARD) ? Radius - pt.y : Radius + pt.y;
a = Orient * 360deg * (pt.x / c) + ja;
ArcPath += {[r*cos(a) + CenterPt.x, r*sin(a) + CenterPt.y,-]};
}
elif (isundef(pt.x) && isundef(pt.y) && !isundef(pt.z)) { // no XY, Z up/down
ArcPath += {pt};
}
else {
error("ArcText - Point is not pure XY or pure Z: " + to_string(pt));
}
}
return ArcPath;
}
//-----
// Draw scale legend
function ArcLegend(Text,Radius,Angle,Orient) {
local tp = scale(typeset(Text,TextFont),LegendTextSize);
local tpa = ArcText(tp,[0mm,0mm],Radius,Angle,TEXT_CENTERED,Orient);
feedrate(TextSpeed);
engrave(tpa,TravelZ,EngraveZ);
}
//-----
// Draw a decade of ticks & labels
// ArcLength > 0 = CCW, < 0 = CW
// UnitOnly forces just the unit tick, so as to allow creating the last tick of the scale
function DrawTicks(Radius,TickMarks,TickOrient,UnitAngle,ArcLength,Decade,LabelOrient,UnitOnly) {
feedrate(ScaleSpeed);
local a,r0,r1,p0,p1;
if (Decade == 1 || UnitOnly) { // draw unit marker
a = UnitAngle;
r0 = Radius + TickOrient * (TickMajor + 2*TickGap + ScaleTextSize.y);
p0 = r0 * [cos(a),sin(a)];
r1 = Radius + TickOrient * (ScaleHeight - 2*TickGap);
p1 = r1 * [cos(a),sin(a)];
goto(p0);
move([-,-,EngraveZ]);
move(p1);
goto([-,-,TravelZ]);
}
local ticklist = UnitOnly ? {TickMarks[0]} : TickMarks;
local tick;
foreach(ticklist; tick) {
a = UnitAngle + ArcLength * log10(tick[0]);
p0 = Radius * [cos(a), sin(a)];
p1 = (Radius + TickOrient*tick[1]) * [cos(a), sin(a)];
goto(p0);
move([-,-,EngraveZ]);
move(p1);
goto([-,-,TravelZ]);
}
feedrate(TextSpeed); // draw scale values
local lrad = Radius + TickOrient * (TickMajor + TickGap);
if (TickOrient == INWARD) {
if (LabelOrient == INWARD) {
lrad -= ScaleTextSize.y; // inward ticks + inward labels = offset inward
}
}
else {
if (LabelOrient == OUTWARD) {
lrad += ScaleTextSize.y; // outward ticks + outward labels = offset outward
}
}
ticklist = UnitOnly ? [TickLabels[0]] : TickLabels;
local ltext,lpath,tpa;
foreach(ticklist; tick) {
ltext = to_string(Decade * to_int(tick));
lpath = scale(typeset(ltext,TextFont),ScaleTextSize);
a = UnitAngle + ArcLength * log10(tick);
tpa = ArcText(lpath,[0mm,0mm],lrad,a,TEXT_CENTERED,LabelOrient);
engrave(tpa,TravelZ,EngraveZ);
}
}
//-----
// Mark key locations
function MarkPivot() {
comment("Mark center point");
feedrate(ScaleSpeed);
if (TRUE) {
goto([-,-,SafeZ]);
goto([PivotOD/2,0,-]);
move([-,-,EngraveZ]);
circle_cw([0,0]); // outline pivot
move([-PivotOD/2,0,-]); // draw X line
goto([-,-,TravelZ]);
goto([0,PivotOD/2,-]);
move([-,-,EngraveZ]);
move ([0,-PivotOD/2,-]); // draw Y line
goto([-,-,TravelZ]);
}
}
//-----
// Draw attribution
function DrawAttribution(AttribRad) {
comment("Attribution at: ",AttribRad);
feedrate(TextSpeed);
local tp,tpa;
if (Legend1) {
tp = scale(typeset(Legend1,TextFont),TitleTextSize);
tpa = ArcText(tp,[0mm,0mm],AttribRad,0deg,TEXT_CENTERED,OUTWARD);
feedrate(TextSpeed);
engrave(tpa,TravelZ,EngraveZ);
}
if (Legend2) {
tp = scale(typeset(Legend2,TextFont),TitleTextSize);
tpa = ArcText(tp,[0mm,0mm],AttribRad,180deg,TEXT_CENTERED,OUTWARD);
feedrate(TextSpeed);
engrave(tpa,TravelZ,EngraveZ);
}
if (FALSE) { // test code to verify ArcText
comment("ArcText test");
ctr = [0mm,0mm];
tp = scale(typeset("Right Inward",TextFont),ScaleTextSize);
tpa = ArcText(tp,ctr,30mm,45deg,TEXT_RIGHT,INWARD);
engrave(tpa,TravelZ,EngraveZ);
tp = scale(typeset("Right Outward",TextFont),ScaleTextSize);
tpa = ArcText(tp,ctr,30mm,45deg,TEXT_RIGHT,OUTWARD);
engrave(tpa,TravelZ,EngraveZ);
tp = scale(typeset("Center Inward",TextFont),ScaleTextSize);
tpa = ArcText(tp,ctr,20mm,45deg,TEXT_CENTERED,INWARD);
engrave(tpa,TravelZ,EngraveZ);
tp = scale(typeset("Center Outward",TextFont),ScaleTextSize);
tpa = ArcText(tp,ctr,20mm,45deg,TEXT_CENTERED,OUTWARD);
engrave(tpa,TravelZ,EngraveZ);
tp = scale(typeset("Left Inward",TextFont),ScaleTextSize);
tpa = ArcText(tp,ctr,10mm,45deg,TEXT_LEFT,INWARD);
engrave(tpa,TravelZ,EngraveZ);
tp = scale(typeset("Left Outward",TextFont),ScaleTextSize);
tpa = ArcText(tp,ctr,10mm,45deg,TEXT_LEFT,OUTWARD);
engrave(tpa,TravelZ,EngraveZ);
goto([0mm,0mm,-]);
move([40mm,40mm,-]);
}
if (FALSE) { // test code to verify RadialText
comment("RadialText test");
ctr = [0mm,0mm];
r = 20mm;
a = 0deg;
tp = scale(typeset("Left Inward",TextFont),LegendTextSize);
tpr = RadialText(tp,ctr,r,a,TEXT_LEFT,INWARD);
feedrate(TextSpeed);
engrave(tpr,TravelZ,EngraveZ);
tp = scale(typeset("Left Outward",TextFont),LegendTextSize);
tpr = RadialText(tp,ctr,r,a,TEXT_LEFT,OUTWARD);
feedrate(TextSpeed);
engrave(tpr,TravelZ,EngraveZ);
a = 90deg;
tp = scale(typeset("Right Inward",TextFont),LegendTextSize);
tpr = RadialText(tp,ctr,r,a,TEXT_RIGHT,INWARD);
feedrate(TextSpeed);
engrave(tpr,TravelZ,EngraveZ);
tp = scale(typeset("Right Outward",TextFont),LegendTextSize);
tpr = RadialText(tp,ctr,r,a,TEXT_RIGHT,OUTWARD);
feedrate(TextSpeed);
engrave(tpr,TravelZ,EngraveZ);
a = 180deg;
tp = scale(typeset("Center Inward",TextFont),LegendTextSize);
tpr = RadialText(tp,ctr,r,a,TEXT_CENTERED,INWARD);
feedrate(TextSpeed);
engrave(tpr,TravelZ,EngraveZ);
tp = scale(typeset("Center Outward",TextFont),LegendTextSize);
tpr = RadialText(tp,ctr,r,a,TEXT_CENTERED,OUTWARD);
feedrate(TextSpeed);
engrave(tpr,TravelZ,EngraveZ);
a = 270deg;
RadialLegend("Offset to radius",ctr,r,a,TEXT_CENTERED,INWARD,-0.5);
goto(ctr);
move([0,-2*r,EngraveZ]);
goto([r,0mm,-]);
circle_cw(ctr);
}
}
//-----------------------------------------------------------------------------
// Deck Engraving
//----------
// Engrave bottom deck
function EngraveBottom() {
// Mark center pivot
MarkPivot();
comment("Inductance scale");
Radius = DeckRad - ScaleHeight;
MinLog = -9;
MaxLog = 6;
Arc = -ScaleArc;
dec = 1;
offset = 0deg;
for (logval = MinLog; logval < MaxLog; logval++) {
a = offset + logval * Arc;
DrawTicks(Radius,TickScaleNarrow,OUTWARD,a,Arc,dec,INWARD,FALSE);
dec = (dec == 100) ? 1 : 10 * dec;
}
a = offset + MaxLog * Arc;
DrawTicks(Radius,TickScaleNarrow,OUTWARD,a,Arc,1000,INWARD,TRUE);
r = Radius + TickMajor + 2*TickGap + LegendTextSize.y;
logval = MinLog + 1.5;
a = offset + logval * Arc;
ArcLegend("nH - nanohenry x10^-9",r,a,INWARD);
logval += 3;
a = offset + logval * Arc;
ArcLegend("μH - microhenry x10^-6",r,a,INWARD);
logval += 3;
a = offset + logval * Arc;
ArcLegend("mH - millihenry x10^-3",r,a,INWARD);
logval += 3;
a = offset + logval * Arc;
ArcLegend("H - henry",r,a,INWARD);
logval += 3;
a = offset + logval * Arc;
ArcLegend("kH - kilohenry x10^3",r,a,INWARD);
r = Radius + TickMajor + TickGap;
logval = MinLog - ScaleExdent; // scale identifiers
a = offset + logval * Arc;
tp = scale(typeset("L Scale →",TextFont),LegendTextSize);
tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_RIGHT,INWARD);
engrave(tpa,TravelZ,EngraveZ);
logval = MaxLog + ScaleExdent;
a = offset + logval * Arc;
tp = scale(typeset("← L Scale",TextFont),LegendTextSize);
tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_LEFT,INWARD);
engrave(tpa,TravelZ,EngraveZ);
comment("Inductive frequency scale");
Radius = DeckRad - 2*ScaleHeight;
MinLog = 0;
MaxLog = 9;
Arc = 2*ScaleArc; // double-length scale for square roots
dec = 1;
offset = -(18 * ScaleArc - Scale2Pi); // using 18 degree arc length
for (logval = MinLog; logval < MaxLog; logval++) {
a = offset + logval * Arc;
DrawTicks(Radius,TickScaleWide,OUTWARD,a,Arc,dec,OUTWARD,FALSE);
dec = (dec == 100) ? 1 : 10 * dec;
}
a = offset + MaxLog * Arc;
DrawTicks(Radius,TickScaleWide,OUTWARD,a,Arc,1000,OUTWARD,TRUE);
feedrate(TextSpeed); // draw prefix legends
r = Radius + TickMajor + 2*TickGap + 2*LegendTextSize.y;
logval = MinLog + 0.5;
for (i = 0; i < 3; i++) {
a = offset + (i + logval) * Arc;
ArcLegend("Hz - hertz",r,a,OUTWARD);
}
for (i = 3; i < 6; i++) {
a = offset + (i + logval) * Arc;
ArcLegend("kHz - kilohertz x10^3",r,a,OUTWARD);
}
for (i = 6; i < 9; i++) {
a = offset + (i + logval) * Arc;
ArcLegend("MHz - megahertz x10^6",r,a,OUTWARD);
}
r = Radius + TickMajor + TickGap + LegendTextSize.y;
logval = MinLog - 0.5; // scale identifier
a = offset + logval * Arc;
ArcLegend("←------- FL Scale -------→",r,a,OUTWARD);
comment("Inductive TC / Risetime scale");
Radius = DeckRad - 3*ScaleHeight;
MinLog = -12;
MaxLog = 3;
Arc = -ScaleArc;
dec = 1;
offset = -TauAngle;
for (logval = MinLog; logval < MaxLog; logval++) {
a = offset + logval * Arc;
DrawTicks(Radius,TickScaleNarrow,OUTWARD,a,Arc,dec,INWARD,FALSE);
dec = (dec == 100) ? 1 : 10 * dec;
}
a = offset + MaxLog * Arc;
DrawTicks(Radius,TickScaleNarrow,OUTWARD,a,Arc,1000,INWARD,TRUE);
feedrate(TextSpeed); // prefix legends
r = Radius + TickMajor + 2*TickGap + LegendTextSize.y;
logval = MinLog + 1.5;
a = offset + logval * Arc;
ArcLegend("ps - picosecond x10^-12",r,a,INWARD);
logval += 3;
a = offset + logval * Arc;
ArcLegend("ns - nanosecond x10^-9",r,a,INWARD);
logval += 3;
a = offset + logval * Arc;
ArcLegend("μs - microsecond x10^-6",r,a,INWARD);
logval += 3;
a = offset + logval * Arc;
ArcLegend("ms - millisecond x10^-3",r,a,INWARD);
logval += 3;
a = offset + logval * Arc;
ArcLegend("s - second",r,a,INWARD);
r = Radius + TickMajor + TickGap;
logval = MinLog - ScaleExdent; // scale identifiers
a = offset + logval * Arc;
tp = scale(typeset("τL Scale →",TextFont),LegendTextSize);
tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_RIGHT,INWARD);
engrave(tpa,TravelZ,EngraveZ);
logval = MaxLog + ScaleExdent;
a = offset + logval * Arc;
tp = scale(typeset("← τL Scale",TextFont),LegendTextSize);
tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_LEFT,INWARD);
engrave(tpa,TravelZ,EngraveZ);
//-----
// Add construction notes
comment("Attribution begins");
r = DeckTopOD/2 - 2*ScaleHeight - WindowHeight;
DrawAttribution(r);
if (FALSE) {
t = "Disk OD: " + to_string(DeckBottomOD) + " " +
to_string(DeckMiddleOD) + " " +
to_string(DeckTopOD) + " mm";
tp = scale(typeset(t,TextFont),LegendTextSize);
tpa = ArcText(tp,[0mm,0mm],r,90deg,TEXT_CENTERED,OUTWARD);
engrave(tpa,TravelZ,EngraveZ);
}
goto([-,-,SafeZ]); // done, so get out of the way
goto([0,0,-]);
comment("Bottom deck ends");
}
//----------
// Engrave middle deck
function EngraveMiddle() {
// Mark center pivot
MarkPivot();
comment("Capacitance scale");
Radius = DeckRad;
MinLog = -15;
MaxLog = 0;
Arc = ScaleArc;
dec = 1;
offset = 0deg;
for (logval = MinLog; logval < MaxLog; logval++) {
a = offset + logval * Arc;
DrawTicks(Radius,TickScaleNarrow,INWARD,a,Arc,dec,INWARD,FALSE);
dec = (dec == 100) ? 1 : 10 * dec;
}
a = offset + MaxLog * Arc;
DrawTicks(Radius,TickScaleNarrow,INWARD,a,Arc,1000,INWARD,TRUE);
r = Radius - ScaleHeight + TickGap;
logval = MinLog + 1.5;
a = offset + logval * Arc;
ArcLegend("fF - femtofarad x10^-15",r,a,INWARD);
logval += 3;
a = offset + logval * Arc;
ArcLegend("pF - picofarad x10^-12",r,a,INWARD);
logval += 2.5; // offset for L/R window;
a = offset + logval * Arc;
ArcLegend("nF - nanofarad x10^-9",r,a,INWARD);
logval += 4; // … likewise
a = offset + logval * Arc;
ArcLegend("μF - microfarad x10^-6",r,a,INWARD);
logval += 2.5; // … restore normal spacing
a = offset + logval * Arc;
ArcLegend("mF - millifarad x10^-3",r,a,INWARD);
r = Radius - ScaleHeight - TickGap - LegendTextSize.y; // into blank space
logval = MinLog; // scale identifiers
a = offset + logval * Arc;
tp = scale(typeset("←--- C Scale",TextFont),LegendTextSize);
tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_RIGHT,INWARD);
engrave(tpa,TravelZ,EngraveZ);
logval = MinLog + 6;
a = offset + logval * Arc;
tp = scale(typeset("←--- C Scale ---→",TextFont),LegendTextSize);
tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_CENTERED,INWARD);
engrave(tpa,TravelZ,EngraveZ);
logval = MaxLog;
a = offset + logval * Arc;
tp = scale(typeset("C Scale ---→",TextFont),LegendTextSize);
tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_LEFT,INWARD);
engrave(tpa,TravelZ,EngraveZ);
comment("Capacitive TC / risetime scale");
Radius = DeckRad - 4*ScaleHeight;
MinLog = -12;
MaxLog = 3;
Arc = ScaleArc;
dec = 1;
offset = 3 * ScaleArc;
for (logval = MinLog; logval < MaxLog; logval++) {
a = offset + logval * Arc;
DrawTicks(Radius,TickScaleNarrow,OUTWARD,a,Arc,dec,INWARD,FALSE);
dec = (dec == 100) ? 1 : 10 * dec;
}
a = offset + MaxLog * Arc;
DrawTicks(Radius,TickScaleNarrow,OUTWARD,a,Arc,1000,INWARD,TRUE);
r = Radius + TickMajor + 2*TickGap + LegendTextSize.y;
logval = MinLog + 1.5;
a = offset + logval * Arc;
ArcLegend("ps - picosecond x10^-12",r,a,INWARD);
logval += 3;
a = offset + logval * Arc;
ArcLegend("ns - nanosecond x10^-9",r,a,INWARD);
logval += 3;
a = offset + logval * Arc;
ArcLegend("μs - microsecond x10^-6",r,a,INWARD);
logval += 3;
a = offset + logval * Arc;
ArcLegend("ms - millisecond x10^-3",r,a,INWARD);
logval += 3;
a = offset + logval * Arc;
ArcLegend("s - second",r,a,INWARD);
r = Radius + TickMajor + TickGap;
logval = MinLog - ScaleExdent; // scale identifiers
a = offset + logval * Arc;
tp = scale(typeset("← τC Scale",TextFont),LegendTextSize);
tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_LEFT,INWARD);
engrave(tpa,TravelZ,EngraveZ);
logval = MaxLog + ScaleExdent;
a = offset + logval * Arc;
tp = scale(typeset("τC Scale →",TextFont),LegendTextSize);
tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_RIGHT,INWARD);
engrave(tpa,TravelZ,EngraveZ);
logval = MinLog - 2.5;
a = offset + logval * Arc;
tp = scale(typeset("←--- τC Scale ---→",TextFont),LegendTextSize);
tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_CENTERED,INWARD);
engrave(tpa,TravelZ,EngraveZ);
comment("Inductive frequency scale legend");
r = DeckRad - ScaleHeight - ScaleTextSize.y;
a = 58deg; // arbitrary text placement
ArcLegend("FL Scale",r,a,OUTWARD);
comment("Index for resonance calculations");
Index = -(18*ScaleArc + Scale2Pi); // negative to read reciprocal of product
r = DeckRad - 1.5*ScaleHeight + 0.5*LegendTextSize.y;
ArcLegend("Frequency",r,Index,OUTWARD);
r = DeckRad - ScaleHeight - LegendTextSize.y;
ArcLegend("⇑",(r - TickGap),Index,INWARD);
r = DeckRad - 2*ScaleHeight + LegendTextSize.y;
ArcLegend("⇑",(r + TickGap),Index,OUTWARD);
r0 = DeckRad - ScaleHeight;
r1 = r0 - TickMajor;
goto(r0 * [cos(Index),sin(Index)]);
move([-,-,EngraveZ]);
move(r1 * [cos(Index),sin(Index)]);
goto([-,-,TravelZ]);
r0 = DeckRad - 2*ScaleHeight;
r1 = r0 + TickMajor;
goto(r0 * [cos(Index),sin(Index)]);
move([-,-,EngraveZ]);
move(r1 * [cos(Index),sin(Index)]);
goto([-,-,TravelZ]);
//-----
// Draw the attribution
comment("Attribution begins");
r = DeckTopOD/2 - 2*ScaleHeight - WindowHeight;
DrawAttribution(r);
goto([-,-,SafeZ]); // done, so get out of the way
goto([0,0,-]);
comment("Middle deck ends");
}
//----------
// Engrave top deck
function EngraveTop() {
// Mark center pivot
MarkPivot();
comment("Resistance scale");
Radius = DeckRad;
MinLog = -1;
MaxLog = 8;
Arc = -ScaleArc;
dec = 100;
offset = 0deg;
for (logval = MinLog; logval < MaxLog; logval++) {
a = offset + logval * Arc;
DrawTicks(Radius,TickScaleNarrow,INWARD,a,Arc,dec,INWARD,FALSE);
dec = (dec == 100) ? 1 : 10 * dec;
}
a = offset + MaxLog * Arc;
DrawTicks(Radius,TickScaleNarrow,INWARD,a,Arc,100,INWARD,TRUE);
r = Radius - ScaleHeight + TickGap;
logval = MinLog + 0.5;
a = offset + logval * Arc;
ArcLegend("mΩ - milliohm",r,a,INWARD);
logval = MinLog + 2.5;
a = offset + logval * Arc;
ArcLegend("Ω - ohm",r,a,INWARD);
logval += 3;
a = offset + logval * Arc;
ArcLegend("kΩ - kilohm x10^3",r,a,INWARD);
logval = MaxLog - 1;
a = offset + logval * Arc;
ArcLegend("MΩ - megohm x10^6",r,a,INWARD);
r = Radius - ScaleHeight - TickGap - LegendTextSize.y;
logval = MinLog + 4;
a = offset + logval * Arc;
ArcLegend("←--- R XC XL Scale ---→",r,a,INWARD);
comment("Capacitive frequency scale");
Radius = DeckRad;
MinLog = 0;
MaxLog = 9;
Arc = ScaleArc;
dec = 1;
offset = 18 * -ScaleArc;
for (logval = MinLog; logval < MaxLog; logval++) {
a = offset + logval * Arc;
DrawTicks(Radius,TickScaleNarrow,INWARD,a,Arc,dec,OUTWARD,FALSE);
dec = (dec == 100) ? 1 : 10 * dec;
}
a = offset + MaxLog * Arc;
DrawTicks(Radius,TickScaleNarrow,INWARD,a,Arc,1000,OUTWARD,TRUE);
r = Radius - (TickMajor + 2*TickGap + LegendTextSize.y);
logval = MinLog + 1.5;
a = offset + logval * Arc;
ArcLegend("Hz - hertz",r,a,OUTWARD);
logval += 3;
a = offset + logval * Arc;
ArcLegend("kHz - kilohertz x10^3",r,a,OUTWARD);
logval += 3;
a = offset + logval * Arc;
ArcLegend("MHz - megahertz x10^6",r,a,OUTWARD);
r = Radius - ScaleHeight - TickGap - LegendTextSize.y;
logval = MaxLog - 3;
a = offset + logval * Arc;
ArcLegend("←--- FC Scale ---→",r,a,OUTWARD);
comment("RC Circuit Pointers");
local ctr = [0mm,0mm];
r0 = DeckRad - 2*ScaleHeight;
r1 = r0 - ScaleHeight;
a = -(17 * ScaleArc);
goto(r0 * [cos(a),sin(a)]);
move([-,-,EngraveZ]);
move(r1 * [cos(a),sin(a)]);
goto([-,-,TravelZ]);
ArcLegend("⇓",(r0 - TickGap),a,OUTWARD);
RadialLegend(" Time Constant",ctr,r1,a,TEXT_LEFT,INWARD,-0.5);
a += ScaleRT;
goto(r0 * [cos(a),sin(a)]);
move([-,-,EngraveZ]);
move(r1 * [cos(a),sin(a)]);
goto([-,-,TravelZ]);
ArcLegend("⇓",(r0 - TickGap),a,OUTWARD);
RadialLegend(" Risetime",ctr,r1,a,TEXT_LEFT,INWARD,-0.5);
a -= ScaleRT/2;
RadialLegend(" RC",ctr,r0 - 2*ScaleTextSize.y,a,TEXT_LEFT,INWARD,-0.5);
comment("L/R Circuit Pointers");
r0 = DeckRad;
r1 = r0 - ScaleHeight;
a = -TauAngle;
goto(r0 * [cos(a),sin(a)]);
move([-,-,EngraveZ]);
move(r1 * [cos(a),sin(a)]);
goto([-,-,TravelZ]);
ArcLegend("⇓",(r0 - TickGap),a,OUTWARD);
RadialLegend("Time Constant ",ctr,r1,a,TEXT_RIGHT,OUTWARD,-0.5);
a -= ScaleRT;
goto(r0 * [cos(a),sin(a)]);
move([-,-,EngraveZ]);
move(r1 * [cos(a),sin(a)]);
goto([-,-,TravelZ]);
ArcLegend("⇓",(r0 - TickGap),a,OUTWARD);
RadialLegend("Risetime ",ctr,r1,a,TEXT_RIGHT,OUTWARD,-0.5);
a += ScaleRT/2;
RadialLegend("L/R ",ctr,r0 - 2*ScaleTextSize.y,a,TEXT_RIGHT,OUTWARD,-0.5);
comment("Title and logo");
feedrate(TextSpeed);
r = 0.65*DeckRad;
tp = scale(typeset("Homage",TextFont),TitleTextSize);
tpa = ArcText(tp,[0mm,0mm],r,TitleAngle,TEXT_CENTERED,INWARD);
engrave(tpa,TravelZ,EngraveZ);
r -= 1.5*TitleTextSize.y;
tp = scale(typeset("Tektronix",TextFont),TitleTextSize);
tpa = ArcText(tp,[0mm,0mm],r,TitleAngle,TEXT_CENTERED,INWARD);
engrave(tpa,TravelZ,EngraveZ);
r -= 1.5*TitleTextSize.y;
tp = scale(typeset("Circuit Computer",TextFont),TitleTextSize);
tpa = ArcText(tp,[0mm,0mm],r,TitleAngle,TEXT_CENTERED,INWARD);
engrave(tpa,TravelZ,EngraveZ);
r -= 1.5*TitleTextSize.y;
if (TRUE) {
tp = scale(typeset("TEK 003-023",TextFont),LegendTextSize);
}
else {
tp = scale(typeset("https://vintagetek.org/tektronix-circuit-computer/&quot;,TextFont),LegendTextSize);
}
tpa = ArcText(tp,[0mm,0mm],r,TitleAngle,TEXT_CENTERED,INWARD);
engrave(tpa,TravelZ,EngraveZ);
r = 0.3*DeckRad;
a = TitleAngle + 180deg;
tp = scale(typeset("Ed Nisley",TextFont),LegendTextSize);
tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_CENTERED,OUTWARD);
engrave(tpa,TravelZ,EngraveZ);
r += 1.5*TitleTextSize.y;
tp = scale(typeset("KE4ZNU",TextFont),LegendTextSize);
tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_CENTERED,OUTWARD);
engrave(tpa,TravelZ,EngraveZ);
r += 1.5*TitleTextSize.y;
tp = scale(typeset("softsolder.com",TextFont),LegendTextSize);
tpa = ArcText(tp,[0mm,0mm],r,a,TEXT_CENTERED,OUTWARD);
engrave(tpa,TravelZ,EngraveZ);
goto([-,-,SafeZ]); // done, so get out of the way
goto([0,0,-]);
comment("Top deck ends");
}
//----------
// Engrave cursor hairline
function EngraveCursor() {
// Mark center pivot
MarkPivot();
comment("Cursor hairline");
feedrate(ScaleSpeed);
goto([-,-,TravelZ]);
repeat(2) {
goto([DeckTopOD/2 - 2.25*ScaleHeight,0,-]); // slight overlap on arrows
move([-,-,EngraveZ]);
move([DeckBottomOD/2 + ScaleHeight,0,-]);
goto([-,-,TravelZ]);
}
goto([-,-,SafeZ]); // done, so get out of the way
goto([0,0,-]);
}
//-----------------------------------------------------------------------------
// Deck milling
// Assumes adhesive clamping to avoid protrusions above work area
//-----
// Bottom deck
function MillBottom() {
comment("Mill Bottom");
feedrate(KnifeSpeed);
goto([-,-,TravelZ]);
local r = PivotOD/2;
goto([0,r,-]); // entry move to align knife
arc_cw([r,0,0],r); // blade enters surface
move([-,-,KnifeZ]); // apply cutting force
circle_cw([0,0]);
arc_cw([0,-r],r); // cut past entry point
goto([-,-,TravelZ]);
r = DeckRad;
local a = 5deg;
local p0 = r * [cos(a),sin(a),-]; // entry point
local p1 = r * [cos(-a),sin(-a),-]; // exit point
goto(p0);
arc_cw([r,0,0],r); // blade enters surface
move([-,-,KnifeZ]); // apply cutting force
circle_cw([0,0]); // cut circle
arc_cw(p1,r); // cut past entry point
goto([-,-,TravelZ]);
goto([0,0,-]);
goto([-,-,SafeZ]);
}
//-----
// Middle deck
function MillMiddle() {
FLNotchArc = 85deg; // width exposing FL scale
FLRampArc = 7deg; // … width of entry & exit ramps
FLNotchOffset = 2deg; // … start angle from 0°
comment("Mill Middle");
feedrate(KnifeSpeed);
goto([-,-,TravelZ]);
local r = PivotOD/2;
goto([0,r,-]); // entry move to align knife
arc_cw([r,0,0],r); // blade enters surface
move([-,-,KnifeZ]); // apply cutting force
circle_cw([0,0]);
arc_cw([0,-r],r); // cut past entry point
goto([-,-,TravelZ]);
// FL scale notch
local r0 = DeckRad;
local a0 = FLNotchOffset; // end of notch ramp
local p0 = r0 * [cos(a0),sin(a0),-];
local a1 = a0 + FLNotchArc; // start of notch ramp
local p1 = r0 * [cos(a1),sin(a1),-];
goto(p0);
arc_cw([r0,0,0],r0); // blade enters surface
move([-,-,KnifeZ]); // apply cutting force
arc_cw(p1,-r0); // largest arc to start of notch
local r1 = r0 - ScaleHeight;
local a3 = a1 - FLRampArc; // start of notch base
local p3 = r1 * [cos(a3),sin(a3),-];
local a4 = a0 + FLRampArc; // end of notch base
local p4 = r1 * [cos(a4),sin(a4),-];
move(p3);
arc_cw(p4,r1); // smallest arc on notch base
move(p0); // end of notch ramp
arc_cw([r0,0,-],r0); // round off corner
local p5 = r0 * [cos(-a0),sin(-a0),-]; // small overtravel past entry point
arc_cw(p5,r0);
goto([-,-,TravelZ]);
// L/R τ and RT Scale window
local WindowArc = 39deg;
ac = -6 * ScaleArc; // center of window arc
r0 = DeckRad - ScaleHeight; // outer
r1 = DeckRad - 2 * ScaleHeight; // inner
aw = WindowArc - to_deg(atan(ScaleHeight,(r0 + r1)/2)); // window arc minus endcaps
p0 = r0 * [cos(ac + aw/2),sin(ac + aw/2),-]; // endcap entry & exit
p1 = r0 * [cos(ac - aw/2),sin(ac - aw/2),-];
p2 = r1 * [cos(ac - aw/2),sin(ac - aw/2),-];
p3 = r1 * [cos(ac + aw/2),sin(ac + aw/2),-];
goto(p3); // cut entry point
arc_cw(p0 +| [-,-,0],ScaleHeight/2); // blade enters surface
move([-,-,KnifeZ]); // apply pressure
arc_cw(p1,r0); // smallest arc
arc_cw(p2,ScaleHeight/2); // half a circle
arc_ccw(p3,r1);
arc_cw(p0,ScaleHeight/2);
arc_cw(p1 +| [-,-,TravelZ],r0); // exit from cut
goto([0,0,-]);
goto([-,-,SafeZ]);
}
//-----
// Top deck
function MillTop() {
comment("Mill Top");
feedrate(KnifeSpeed);
goto([-,-,TravelZ]);
local r = PivotOD/2;
goto([0,r,-]); // entry move to align knife
arc_cw([r,0,0],r); // blade enters surface
move([-,-,KnifeZ]); // apply cutting force
circle_cw([0,0]);
arc_cw([0,-r],r); // cut past entry point
goto([-,-,TravelZ]);
r = DeckRad;
local a = 5deg;
local p0 = r * [cos(a),sin(a),-]; // entry point
local p1 = r * [cos(-a),sin(-a),-]; // exit point
goto(p0);
arc_cw([r,0,0],r); // blade enters surface
move([-,-,KnifeZ]); // apply cutting force
circle_cw([0,0]); // cut circle
arc_cw(p1,r); // cut past entry point
goto([-,-,TravelZ]);
// RC τ and RT Scale window
local WindowArc = 54deg;
local ac = -17 * ScaleArc + ScaleRT/2; // center of window arc
local r0 = DeckRad - ScaleHeight; // outer
local r1 = DeckRad - 2 * ScaleHeight; // inner
local aw = WindowArc - to_deg(atan(ScaleHeight,(r0 + r1)/2)); // window arc minus endcaps
p0 = r0 * [cos(ac + aw/2),sin(ac + aw/2),-];
p1 = r0 * [cos(ac - aw/2),sin(ac - aw/2),-];
local p2 = r1 * [cos(ac - aw/2),sin(ac - aw/2),-];
local p3 = r1 * [cos(ac + aw/2),sin(ac + aw/2),-];
goto(p3);
arc_cw(p0 +| [-,-,0],ScaleHeight/2); // blade enters surface
move([-,-,KnifeZ]); // apply pressure
arc_cw(p1,r0); // smallest arc
arc_cw(p2,ScaleHeight/2); // half a circle
arc_ccw(p3,r1);
arc_cw(p0,ScaleHeight/2);
arc_cw(p1 +| [-,-,TravelZ],r0); // exit from cut
goto([0,0,-]);
goto([-,-,SafeZ]);
}
//----------
// Cut cursor outline
CursorHubOD = 1.0in;
CursorTipWidth = to_inch(9.0/16.0);
CursorTipRadius = to_inch(1.0/16.0);
function MillCursor() {
// Mark center pivot
MarkPivot();
comment("Cursor outline");
local dr = DeckBottomOD/2;
local hr = CursorHubOD/2;
local a = atan(hr - CursorTipWidth/2,dr); // rough & ready approximation
local p0 = hr * [sin(a),cos(a),-]; // upper tangent point on hub
local c1 = [dr - CursorTipRadius,CursorTipWidth/2 - CursorTipRadius*cos(a),-];
local p1 = c1 + [CursorTipRadius*sin(a),CursorTipRadius*cos(a),-];
local p2 = c1 + [CursorTipRadius,0,-]; // around tip radius
feedrate(KnifeSpeed);
goto([-,-,TravelZ]);
goto([-hr,0,-]);
move([-,-,EngraveZ]);
repeat(3) {
arc_cw(p0,hr);
move(p1);
arc_cw(p2,CursorTipRadius);
move([p2.x,-p2.y,-]);
arc_cw([p1.x,-p1.y,-],CursorTipRadius);
move([p0.x,-p0.y,-]);
arc_cw([-hr,0,-],hr);
}
goto([-,-,SafeZ]); // done, so get out of the way
goto([0,0,-]);
}
//-----------------------------------------------------------------------------
// The actual machining sequences!
//-----
// Bottom Deck
if (SelectPart == "Bottom") {
DeckOD = DeckBottomOD;
DeckRad = DeckOD / 2;
comment(" OD: ",DeckOD);
if (Operation == "Engrave") {
EngraveBottom();
}
elif (Operation == "Mill") {
MillBottom();
}
else {
error("Invalid operation: ",Operation);
}
}
//------
// Middle Deck
if (SelectPart == "Middle") {
DeckOD = DeckMiddleOD;
DeckRad = DeckOD / 2;
comment(" OD: ",DeckOD);
if (Operation == "Engrave") {
EngraveMiddle();
}
elif (Operation == "Mill") {
MillMiddle();
}
else {
error("Invalid operation: ",Operation);
}
}
//-----
// Top Deck
if (SelectPart == "Top") {
DeckOD = DeckTopOD;
DeckRad = DeckOD / 2;
comment(" OD: ",DeckOD);
if (Operation == "Engrave") {
EngraveTop();
}
elif (Operation == "Mill") {
MillTop();
}
else {
error("Invalid operation: ",Operation);
}
}
//-----
// Cursor
if (SelectPart == "Cursor") {
DeckOD = DeckBottomOD;
DeckRad = DeckOD / 2;
comment(" OD: ",DeckOD);
if (Operation == "Engrave") {
EngraveCursor();
}
elif (Operation == "Mill") {
MillCursor();
}
else {
error("Invalid operation: ",Operation);
}
}
#!/bin/bash
# Tek Circuit Computer Engraving
# Ed Nisley KE4ZNU - 2019-11
#OD='BaseOD=118mm' # CD = 120
#OD='BaseOD=93mm' # hard drive = 95mm
#EZ='EngraveZ=-5mm' # Engraving Z
Flags='-P 3 --pedantic' # avoid leading hyphen gotcha
# Set these to match your file layout
ProjPath='/mnt/bulkdata/Project Files/Tektronix Circuit Computer/Firmware'
LibPath='/opt/gcmc/library'
Prolog='prolog.gcmc'
Epilog='epilog.gcmc'
ScriptPath=$ProjPath
Script='Tek Circuit Computer.gcmc'
#-----
# params: deck operation
function Runit {
fn=TekCC-${1}-${2}.ngc
echo "(File: "$fn")" > $fn
sel='SelectPart="'$1'"'
op='Operation="'$2'"'
echo Output: $fn
echo " "$sel
echo " "$op
if [ -e $fn ]
then rm -f $fn
fi
gcmc -D "$OD" -D "$EZ" \
-D "$sel" -D "$op" $Flags \
--include "$LibPath" --prologue "$Prolog" --epilogue "$Epilog" \
"$ScriptPath"/"$Script" >> "$fn"
}
#-----
Runit Bottom Engrave
Runit Bottom Mill
Runit Middle Engrave
Runit Middle Mill
Runit Top Engrave
Runit Top Mill
Runit Cursor Engrave
Runit Cursor Mill
view raw TekCC.sh hosted with ❤ by GitHub