submini tubes

This commit is contained in:
Bane Stojkovic
2026-11-02 17:22:19 +01:00
parent 3ba4434b66
commit b7fece1edb
8 changed files with 669 additions and 17 deletions
@@ -30,20 +30,29 @@ import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.util.Arrays;
import org.diylc.appframework.miscutils.ConfigurationManager;
import org.diylc.awt.StringUtils;
import org.diylc.common.Display;
import org.diylc.common.HorizontalAlignment;
import org.diylc.common.IPlugInPort;
import org.diylc.common.ObjectCache;
import org.diylc.common.Orientation;
import org.diylc.common.VerticalAlignment;
import org.diylc.components.AbstractLabeledComponent;
import org.diylc.components.AbstractMakerBoard;
import org.diylc.components.semiconductors.ICPinNames;
import org.diylc.components.semiconductors.PinLabels;
import org.diylc.components.transform.TO220Transformer;
import org.diylc.core.ComponentState;
import org.diylc.core.IDatasheetSupport;
import org.diylc.core.IDIYComponent;
import org.diylc.core.IDrawingObserver;
import org.diylc.core.Project;
import org.diylc.core.Theme;
import org.diylc.core.VisibilityPolicy;
import org.diylc.core.annotations.AiNote;
import org.diylc.core.annotations.ComponentDescriptor;
import org.diylc.core.annotations.EditableProperty;
import org.diylc.core.annotations.KeywordPolicy;
@@ -51,11 +60,15 @@ import org.diylc.core.measures.Size;
import org.diylc.core.measures.SizeUnit;
import org.diylc.utils.Constants;
@AiNote("The tube is drawn as seen from above, from the component side of the board. Leads are numbered as in the data book, lead 1 first. In the circular arrangement standing upright, the first control point is the body centre and the leads follow it counter-clockwise from the gap in the circle - the mirror of the data book's bottom view; there the top lead, if any, is the centre point. In-line and folded tubes have lead 1 at the first control point, and the top lead is the last control point")
@AiNote("'Pin Names' is a comma-separated list of lead names in lead order with the top lead last: P plate, G triode grid, G1/G2/G3 grids, K cathode, IS internal shield, F or H plus the lead number for filament or heater ends; sections of a multi-section tube are numbered first (1P, 2G); leads joined inside end in _1, _2; NC and IC leads are never connected. 'Pin Labels' draws the names (default), the lead numbers or nothing")
@AiNote("A datasheet model is a tube type such as 6021 or 6N16B and sets 'Pin Arrangement', 'Top Lead', 'Lead Count', 'Diameter', 'Thickness', 'Length', 'Pin Names' and 'Value'; 'Diameter' is the broad side of a flat bulb, the width seen when the tube lies folded. Set 'Value' to the same tube type together with the model")
@ComponentDescriptor(name = "Sub-Mini Tube", author = "Branislav Stojkovic", category = "Tubes",
instanceNamePrefix = "V", description = "Sub-miniature (pencil) vacuum tube",
zOrder = IDIYComponent.COMPONENT, keywordPolicy = KeywordPolicy.SHOW_VALUE,
transformer = TO220Transformer.class)
public class SubminiTube extends AbstractLabeledComponent<String> {
transformer = TO220Transformer.class, enableDatasheet = true, datasheetCreationStepCount = 2,
datasheetModelColumn = 1)
public class SubminiTube extends AbstractLabeledComponent<String> implements IDatasheetSupport {
private static final long serialVersionUID = 1L;
@@ -86,16 +99,41 @@ public class SubminiTube extends AbstractLabeledComponent<String> {
private PinArrangement leadArrangement = PinArrangement.Circular;
private boolean topLead = false;
private Size diameter = DIAMETER;
private Size thickness = null;
private Size length = LENGTH;
private PinCount pinCount = PinCount._8;
private Size leadSpacing = LEAD_SPACING;
private Color labelColor = LABEL_COLOR;
private String pinNames = null;
private PinLabels pinLabels = null;
transient private String[] parsedPinNames;
public SubminiTube() {
super();
updateControlPoints();
}
public SubminiTube(String[] model) {
this();
applyModel(model);
}
// columns: group, tube type, pin arrangement, top lead (yes/no), bulb width, bulb thickness,
// bulb length, then one name per lead in lead order with the top lead last
@Override
public void applyModel(String[] model) {
boolean topLead = "yes".equalsIgnoreCase(model[3]);
setValue(model[1]);
setPinArrangement(PinArrangement.valueOf(model[2]));
setTopLead(topLead);
setDiameter(Size.parseSize(model[4]));
setThickness(Size.parseSize(model[5]));
setLength(Size.parseSize(model[6]));
setPinCount(PinCount.valueOf("_" + (model.length - 7 - (topLead ? 1 : 0))));
setPinNames(String.join(",", Arrays.copyOfRange(model, 7, model.length)));
}
@EditableProperty
public String getValue() {
return value;
@@ -226,17 +264,18 @@ public class SubminiTube extends AbstractLabeledComponent<String> {
Point2D firstPoint = controlPoints[0];
double angleIncrement = Math.PI * 2 / (pinCount + 1);
double initialAngleOffset = angleIncrement;
double r = (pinCount + 1) * leadSpacing / (2 * Math.PI);
controlPoints = new Point2D[pinCount + 1];
double theta = initialAngleOffset + getOrientation().toRadians();
// seen from above the leads run counter-clockwise from the gap, the mirror of the data book's
// bottom view; with y pointing down that is a decreasing angle
double theta = getOrientation().toRadians() - angleIncrement;
controlPoints[0] = firstPoint;
for (int i = 0; i < pinCount; i++) {
controlPoints[i + 1] = new Point2D.Double((int) (firstPoint.getX() + Math.cos(theta) * r),
(int) (firstPoint.getY() + Math.sin(theta) * r));
theta += angleIncrement;
theta -= angleIncrement;
}
}
}
@@ -290,9 +329,16 @@ public class SubminiTube extends AbstractLabeledComponent<String> {
default:
throw new RuntimeException("Unexpected orientation: " + orientation);
}
} else {
} else if (getPinArrangement() == PinArrangement.Circular) {
body[0] = new Area(new Ellipse2D.Double(centerX - diameter / 2, centerY - diameter / 2, diameter,
diameter));
} else {
// a flat bulb stands with its broad side along the lead row
double thickness = getThickness().convertToPixels();
boolean rowAlongY = orientation == Orientation.DEFAULT || orientation == Orientation._180;
double w = rowAlongY ? thickness : diameter;
double h = rowAlongY ? diameter : thickness;
body[0] = new Area(new Ellipse2D.Double(centerX - w / 2, centerY - h / 2, w, h));
}
}
return body;
@@ -473,6 +519,67 @@ public class SubminiTube extends AbstractLabeledComponent<String> {
}
g2d.drawString(label, (int)(x + dx), (int)(y + dy));
if (getPinLabels() != PinLabels.NONE) {
drawLeadLabels(g2d, mainArea.getBounds2D());
}
}
// In a row the labels sit square to it on the side away from the body; on a standing circular
// tube they point outwards from the centre, past the edge of the bulb. The top lead leaves the far
// end of the bulb, away from the row, and is left unlabelled.
private void drawLeadLabels(Graphics2D g2d, Rectangle2D bodyBounds) {
g2d.setFont(ICPinNames.PIN_NAME_FONT);
double pinRadius = LEAD_THICKNESS.convertToPixels() / 2;
boolean circular = getPinArrangement() == PinArrangement.Circular && !folded;
int rowCount = controlPoints.length - (getTopLead() && !circular ? 1 : 0);
double alongX = 0;
double alongY = 0;
double awayX = 0;
double awayY = 0;
if (!circular && rowCount > 1) {
double rowDistance = controlPoints[0].distance(controlPoints[1]);
alongX = (controlPoints[1].getX() - controlPoints[0].getX()) / rowDistance;
alongY = (controlPoints[1].getY() - controlPoints[0].getY()) / rowDistance;
awayX = alongY;
awayY = -alongX;
double rowCenterX = (controlPoints[0].getX() + controlPoints[rowCount - 1].getX()) / 2;
double rowCenterY = (controlPoints[0].getY() + controlPoints[rowCount - 1].getY()) / 2;
if ((bodyBounds.getCenterX() - rowCenterX) * awayX
+ (bodyBounds.getCenterY() - rowCenterY) * awayY > 1) {
awayX = -awayX;
awayY = -awayY;
}
}
for (int i = 0; i < controlPoints.length; i++) {
int lead = getLeadNumber(i);
if (lead == 0 || (circular && i == 0)) {
continue;
}
String label;
if (getPinLabels() == PinLabels.NUMBERS) {
label = Integer.toString(lead);
} else {
String name = ICPinNames.get(getParsedPinNames(), lead);
if (name == null) {
continue;
}
label = AbstractMakerBoard.getDisplayPinLabel(name);
}
Point2D point = controlPoints[i];
if (circular) {
double dx = point.getX() - controlPoints[0].getX();
double dy = point.getY() - controlPoints[0].getY();
double r = Math.sqrt(dx * dx + dy * dy);
double reach = Math.max(r + pinRadius, bodyBounds.getWidth() / 2) + ICPinNames.PIN_NAME_GAP
+ g2d.getFontMetrics().stringWidth(label) / 2d;
StringUtils.drawCenteredText(g2d, label, controlPoints[0].getX() + dx / r * reach,
controlPoints[0].getY() + dy / r * reach, HorizontalAlignment.CENTER,
VerticalAlignment.CENTER);
} else if (i < rowCount) {
ICPinNames.drawOutside(g2d, label, point, awayX, awayY, bodyBounds, pinRadius);
}
}
}
@Override
@@ -581,6 +688,19 @@ public class SubminiTube extends AbstractLabeledComponent<String> {
public void setDiameter(Size diameter) {
this.diameter = diameter;
updateControlPoints();
body = null;
}
// the narrow side of a flat bulb; a round bulb, and every file saved before the property
// existed, has none and is as thick as it is wide
@EditableProperty
public Size getThickness() {
return thickness == null ? getDiameter() : thickness;
}
public void setThickness(Size thickness) {
this.thickness = thickness;
body = null;
}
@EditableProperty
@@ -590,6 +710,7 @@ public class SubminiTube extends AbstractLabeledComponent<String> {
public void setLength(Size length) {
this.length = length;
body = null;
}
@EditableProperty(name = "Lead Count")
@@ -630,6 +751,57 @@ public class SubminiTube extends AbstractLabeledComponent<String> {
return false;
}
@Override
public String getControlPointNodeName(int index) {
int lead = getLeadNumber(index);
if (lead == 0) {
return null;
}
String name = ICPinNames.get(getParsedPinNames(), lead);
return name == null ? Integer.toString(lead) : name;
}
// the data book's lead number of a control point, 0 for the centre of a standing circular tube
// without a top lead; the top lead is numbered after the last regular lead
private int getLeadNumber(int index) {
if (getPinArrangement() == PinArrangement.Circular && !folded) {
if (index == 0) {
return getTopLead() ? getPinCount().getValue() + 1 : 0;
}
return index;
}
return index + 1;
}
private String[] getParsedPinNames() {
if (parsedPinNames == null) {
parsedPinNames = ICPinNames.parse(getPinNames());
}
return parsedPinNames;
}
@EditableProperty(name = "Pin Names")
public String getPinNames() {
return pinNames;
}
public void setPinNames(String pinNames) {
this.pinNames = pinNames;
this.parsedPinNames = null;
}
@EditableProperty(name = "Pin Labels")
public PinLabels getPinLabels() {
if (pinLabels == null) {
pinLabels = PinLabels.NAMES;
}
return pinLabels;
}
public void setPinLabels(PinLabels pinLabels) {
this.pinLabels = pinLabels;
}
public static enum PinArrangement {
Inline("In-line"), Circular("Circular");
@@ -0,0 +1,35 @@
Diode,6D6A,Inline,no,7.2 mm,7.2 mm,36 mm,P,H2,H3,K
Dual Triode,6N16B,Circular,no,10.2 mm,10.2 mm,36 mm,2P,1K,1G,H4,1P,2K,2G,H8
Dual Triode,6N17B,Circular,no,10.2 mm,10.2 mm,36 mm,2P,1K,1G,H4,1P,2K,2G,H8
Dual Triode,6N21B,Circular,no,10.2 mm,10.2 mm,40 mm,1K,IS,1G,1P,H5,2K,NC,2G,2P,H10
Dual Triode,6021,Circular,no,0.4 in,0.4 in,1.375 in,2P,2G,H3,2K,1K,H6,1G,1P
Dual Triode,6111,Circular,no,0.4 in,0.4 in,1.375 in,2P,2G,H3,2K,1K,H6,1G,1P
Dual Triode,6112,Circular,no,0.4 in,0.4 in,1.375 in,2P,2G,H3,2K,1K,H6,1G,1P
Duo-Diode,5896,Circular,no,0.4 in,0.4 in,1.375 in,2P,2K,H3,IS,1P,H6,1K,NC
Pentode,1Zh18B,Circular,yes,8.5 mm,8.5 mm,42 mm,F1,F2,NC,NC,G2,G1,P
Pentode,1Zh24B,Circular,yes,8.5 mm,8.5 mm,42 mm,F1,F2,IS,G3,G2,G1,P
Pentode,1Zh29B,Circular,yes,8.5 mm,8.5 mm,40 mm,HCT,G3,G2,F4,IS,F6,G1,P
Pentode,5886,Inline,no,0.4 in,0.285 in,1.5 in,P,G2,F3,F4,NC,NC,G1
Pentode (RF/IF),6Zh1B,Inline,no,10.2 mm,10.2 mm,36 mm,P,G2,H3,H4,G3,K,G1
Pentode (RF/IF),6Zh2B,Inline,no,10.2 mm,10.2 mm,38 mm,P,G3,G2,H4,H5,K,G1
Pentode (RF/IF),5636,Circular,no,0.4 in,0.4 in,1.375 in,G1,K_1,H3,G3,P,H6,G2,K_2
Pentode (RF/IF),5678,Inline,no,0.4 in,0.285 in,1.5 in,P,G2,F3,G1,F5
Pentode (RF/IF),5840,Circular,no,0.4 in,0.4 in,1.375 in,G1,K_1,H3,K_2,P,H6,G2,K_3
Pentode (RF/IF),5899,Circular,no,0.4 in,0.4 in,1.375 in,G1,K_1,H3,K_2,P,H6,G2,K_3
Pentode (RF/IF),6205,Circular,no,0.4 in,0.4 in,1.375 in,G1,K_1,H3,G3,P,H6,G2,K_2
Pentode (RF/IF),6206,Circular,no,0.4 in,0.4 in,1.375 in,G1,K_1,H3,K_2,P,H6,G2,K_3
Power Pentode,1P24B,Circular,yes,10.5 mm,10.5 mm,45 mm,HCT,F2,G2,IS,NC,F6,G3,NC,G1,P
Power Pentode,6P30B,Circular,no,10.8 mm,10.8 mm,48 mm,K,H2,G2,P,NC,H6,G1,G3
Power Pentode,5672,Inline,no,0.385 in,0.285 in,1.5 in,P,G2,F3,G1,F5
Power Pentode,5902,Circular,no,0.4 in,0.4 in,1.75 in,G1,K_1,H3,K_2,P,H6,G2,K_3
Power Pentode,6088,Inline,no,0.385 in,0.285 in,1.5 in,P,G2,F3,G1,F5
Triode,6S6B,Inline,no,10.2 mm,10.2 mm,36 mm,P,H2,H3,G,K
Triode,6S7B,Inline,no,10.2 mm,10.2 mm,36 mm,P,H2,H3,G,K
Triode,6S31B,Circular,no,10.2 mm,10.2 mm,39 mm,K,H2,NC,NC,P,NC,H7,G,NC,NC
Triode,6S32B,Circular,no,10.2 mm,10.2 mm,30 mm,K,NC,G,NC,H5,NC,P,NC,NC,H10
Triode,5703,Inline,no,0.4 in,0.4 in,1.5 in,P,NC,H3,H4,G,K
Triode,5718,Circular,no,0.4 in,0.4 in,1.375 in,G,NC,H3,NC,K,H6,NC,P
Triode,5719,Circular,no,0.4 in,0.4 in,1.375 in,G,NC,H3,NC,K,H6,NC,P
Triode,5744,Inline,no,0.4 in,0.4 in,1.5 in,P,H2,H3,G,K
Triode,5977,Circular,no,0.4 in,0.4 in,1.375 in,G,NC,H3,NC,K,H6,NC,P
Triode,6814,Circular,no,0.4 in,0.4 in,1.375 in,G,NC,H3,NC,K,H6,NC,P
@@ -0,0 +1,87 @@
/*
DIY Layout Creator (DIYLC).
Copyright (c) 2009-2025 held jointly by the individual authors.
This file is part of DIYLC.
DIYLC is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
DIYLC is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
*/
package org.diylc.components.tube;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.diylc.netlist.Node;
import org.diylc.presenter.DatasheetService;
import org.junit.Assert;
import org.junit.Test;
public class SubminiTubeDatasheetTest {
private static final int FIRST_LEAD_COLUMN = 7;
@Test
public void everyRowApplies() {
for (String[] row : datasheet()) {
SubminiTube tube = new SubminiTube(row);
String context = String.join(",", row);
Assert.assertEquals(context, row[1], tube.getValue());
Assert.assertTrue(context, row[3].equals("yes") || row[3].equals("no"));
int leads = row.length - FIRST_LEAD_COLUMN - (tube.getTopLead() ? 1 : 0);
Assert.assertEquals(context, leads, tube.getPinCount().getValue());
}
}
@Test
public void tubesAreUnique() {
Set<String> tubes = new HashSet<String>();
for (String[] row : datasheet()) {
Assert.assertTrue("repeated tube " + row[1], tubes.add(row[1].toUpperCase()));
}
}
/** Two leads share a net only when both say so with a numbered suffix, like K_1 and K_2. */
@Test
public void leadsShareANetOnlyThroughTheSuffix() {
for (String[] row : datasheet()) {
for (int i = FIRST_LEAD_COLUMN; i < row.length; i++) {
for (int j = i + 1; j < row.length; j++) {
String rail = Node.railName(row[i]);
if (rail != null && rail.equals(Node.railName(row[j]))) {
Assert.assertTrue(row[1] + " leads " + row[i] + " and " + row[j],
row[i].matches(".*_\\d+") && row[j].matches(".*_\\d+"));
}
}
}
}
}
@Test
public void tubeIsFoundByTypeNotByGroup() {
DatasheetService service = DatasheetService.getInstance();
String[] first = datasheet().get(0);
Assert.assertEquals(1, service.findSeries(SubminiTube.class, first[1]).size());
Assert.assertTrue(service.findSeries(SubminiTube.class, first[0]).isEmpty());
}
private static List<String[]> datasheet() {
List<String[]> datasheet = DatasheetService.getInstance().loadDatasheet(SubminiTube.class);
Assert.assertNotNull(datasheet);
Assert.assertFalse(datasheet.isEmpty());
return datasheet;
}
}
@@ -0,0 +1,114 @@
/*
DIY Layout Creator (DIYLC).
Copyright (c) 2009-2025 held jointly by the individual authors.
This file is part of DIYLC.
DIYLC is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
DIYLC is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
*/
package org.diylc.components.tube;
import org.diylc.common.Orientation;
import org.diylc.components.tube.SubminiTube.PinArrangement;
import org.diylc.components.tube.SubminiTube.PinCount;
import org.diylc.core.measures.Size;
import org.diylc.core.measures.SizeUnit;
import org.junit.Assert;
import org.junit.Test;
public class SubminiTubeTest {
/** The centre of a standing circular tube is not a lead; lead 1 is the first on the circle. */
@Test
public void circularCentreIsNotALead() {
SubminiTube tube = new SubminiTube();
tube.setPinArrangement(PinArrangement.Circular);
tube.setPinCount(PinCount._8);
Assert.assertNull(tube.getControlPointNodeName(0));
Assert.assertEquals("1", tube.getControlPointNodeName(1));
Assert.assertEquals("8", tube.getControlPointNodeName(8));
}
/** Standing upright, the top lead of a circular tube rises from the centre. */
@Test
public void circularTopLeadIsTheCentre() {
SubminiTube tube = new SubminiTube();
tube.setPinArrangement(PinArrangement.Circular);
tube.setPinCount(PinCount._5);
tube.setTopLead(true);
tube.setPinNames("A,B,C,D,E,TOP");
Assert.assertEquals("TOP", tube.getControlPointNodeName(0));
Assert.assertEquals("A", tube.getControlPointNodeName(1));
}
@Test
public void inlineLeadsStartAtTheFirstPointAndEndWithTheTopLead() {
SubminiTube tube = new SubminiTube();
tube.setPinArrangement(PinArrangement.Inline);
tube.setPinCount(PinCount._3);
tube.setTopLead(true);
tube.setPinNames("K,G,,P");
Assert.assertEquals(4, tube.getControlPointCount());
Assert.assertEquals("K", tube.getControlPointNodeName(0));
// a blank name falls back to the lead number
Assert.assertEquals("3", tube.getControlPointNodeName(2));
Assert.assertEquals("P", tube.getControlPointNodeName(3));
}
@Test
public void modelSetsTheTubeAndLeavesTheMounting() {
SubminiTube tube = new SubminiTube();
tube.setOrientation(Orientation._90);
tube.setFolded(true);
tube.applyModel(new String[] {"Dual Triode", "TEST", "Inline", "yes", "10 mm", "7 mm", "40 mm",
"1P", "1G", "1K", "F4", "F5", "2K", "2G", "2P", "IS"});
Assert.assertEquals("TEST", tube.getValue());
Assert.assertEquals(PinArrangement.Inline, tube.getPinArrangement());
Assert.assertTrue(tube.getTopLead());
Assert.assertEquals(PinCount._8, tube.getPinCount());
Assert.assertEquals(new Size(10d, SizeUnit.mm), tube.getDiameter());
Assert.assertEquals(new Size(7d, SizeUnit.mm), tube.getThickness());
Assert.assertEquals(new Size(40d, SizeUnit.mm), tube.getLength());
Assert.assertEquals(Orientation._90, tube.getOrientation());
Assert.assertTrue(tube.getFolded());
Assert.assertEquals("IS", tube.getControlPointNodeName(8));
}
/** Files saved before the property existed draw a round bulb, as they always did. */
@Test
public void bulbWithoutThicknessIsRound() {
SubminiTube tube = new SubminiTube();
Assert.assertEquals(tube.getDiameter(), tube.getThickness());
}
/**
* Seen from above, leads run counter-clockwise from the gap, which faces right by default: lead 1
* is just above the gap and the last lead just below it.
*/
@Test
public void circularLeadsRunCounterClockwiseFromTheGap() {
SubminiTube tube = new SubminiTube();
tube.setPinArrangement(PinArrangement.Circular);
tube.setPinCount(PinCount._8);
double centreY = tube.getControlPoint(0).getY();
double centreX = tube.getControlPoint(0).getX();
Assert.assertTrue(tube.getControlPoint(1).getY() < centreY);
Assert.assertTrue(tube.getControlPoint(1).getX() > centreX);
Assert.assertTrue(tube.getControlPoint(8).getY() > centreY);
Assert.assertTrue(tube.getControlPoint(8).getX() > centreX);
}
}
@@ -165,6 +165,6 @@ tube.DiodeSymbol = Directly heated, Heaters
tube.DuoDiodeSymbol = Directly heated, Heaters
tube.Jumbo4PinTubeSocket = fixed
tube.PentodeSymbol = Heaters, Suppressor grid
tube.SubminiTube = Folded, Lead Count, Pin Arrangement
tube.SubminiTube = Folded, Lead Count, Pin Arrangement, Top Lead
tube.TriodeSymbol = Directly heated, Heaters
tube.TubeSocket = Base