Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
GCMC + Bash source code: Homage Tektronix Circuit Computer
// 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/",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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment