import { Coordinate } from "./coordinate.mjs";
import { NodeComponent } from "./nodeComponent.mjs";
import { Pin } from "./pin.mjs";
/**
* Class representing a TikZ transistor.
*
* @extends NodeComponent
* @property {string} instanceName - the instance name, e.g. "R1"
* @property {Pin[]} pins - top, bottom and tap pin
* @property {string[]} anchorNames - top, bottom and tap pin name
* @property {0|1|2|null} anchorNr - the selected anchor; 0=top, 1=bottom, 2=tap; null=use mid
* @property {string} nodeName - the name of the node
*/
class Transistor extends NodeComponent {
#tikzComponentName;
instanceName;
pins;
anchorNames;
anchorNr;
nodeName;
anchorCoord;
/**
* Generate a TikZ transistor (-stencil).
*
* @param {string} tikzComponentName - the tikz component name, e.g. "R"
* @param {string} [instanceName=""] - the instance name, e.g. "R1"
* @param {Pin[]} pins - top, bottom and tap pin
* @param {string[]} anchorNames - top, bottom and tap pin name
* @param {0|1|2|null} anchorNr - the selected anchor; 0=top, 1=bottom, 2=tap; null=use mid/bulk
* @param {Coordinate} [coord] - position if selected anchor == null
* @param {string} [nodeName=""] - the name of the node
* @param {number} [angle=0] - the angle to rotate the component
* @param {boolean} [mirrorX=false] - true to mirror on x axis
* @param {boolean} [mirrorY=false] - true to mirror on y axis
*/
constructor(
tikzComponentName,
instanceName = "",
pins,
anchorNames,
anchorNr,
coord = null,
nodeName = "",
angle = 0,
mirrorX = false,
mirrorY = false
) {
super(angle, mirrorX, mirrorY);
this.#tikzComponentName = tikzComponentName;
this.instanceName = instanceName;
this.pins = pins;
this.anchorNames = anchorNames;
this.anchorNr = anchorNr;
this.nodeName = nodeName;
this.anchorCoord = this.anchorNr !== null ? null : coord || new Coordinate(0, 0);
}
/**
* @typedef {object} tikzTripolOptions
* @property {number} [pgfCircRlen=1.4]
* @property {number} [width=0.7]
* @property {number} [connHeight=0.5]
* @property {number} [height=1.1]
*/
/**
* Creates a new stencil from a TikZ pgfkeys alike object
*
* @param {string} tikzComponentName
* @param {Pin[]} pins - top, bottom and tap pin
* @param {tikzTripolOptions} options
* @param {string} [nodeName=""]
*
* @returns {Transistor} the created stencil
*/
static fromStruct(tikzComponentName, anchorNames, options, nodeName = "") {
if (!Number.isFinite(options.pgfCircRlen)) options.pgfCircRlen = 1.4;
if (!Number.isFinite(options.width)) options.width = 0.7;
if (!Number.isFinite(options.connHeight)) options.connHeight = 0.5;
if (!Number.isFinite(options.height)) options.height = 1.1;
const halfHeight = options.pgfCircRlen * options.height * 0.5;
const width = options.pgfCircRlen * options.width;
const pins = [
// top
new Pin(new Coordinate(0, halfHeight)),
// bottom
new Pin(new Coordinate(0, -halfHeight)),
// tap
new Pin(new Coordinate(-width, halfHeight * options.connHeight)),
];
return new Transistor(tikzComponentName, "", pins, anchorNames, null, null, nodeName);
}
/**
* @returns {string} the read only TikZ component name
*/
get tikzComponentName() {
return this.#tikzComponentName;
}
/**
* @returns {string} the node text generated from the instanceName
*/
get nodeText() {
// EEMOS1 --> ${EEMOS}_{1}$
if (this.instanceName) {
let [_fullMatch, name, index] = this.instanceName.match(/^([a-zA-Z]+)[_-]?([0-9]+)$/) || [null, null, null];
if (name && !Number.isNaN((index = Number.parseInt(index)))) return `\${${name}}_{${index}}\$`;
else return this.instanceName.replace("_", "\\_");
} else return "";
}
/**
* @returns {Coordinate|null} the component position
*/
get coord() {
if (this.anchorNr == null) return this.anchorCoord;
else if (this.pins[this.anchorNr])
return this.pins[this.anchorNr].coord;
else return null;
}
/**
* Calculates the coordinate where the top-bottom line crosses the orthogonal tap line. This can but does not need
* to be the center point.
*
* @returns {Coordinate}
*/
get lineCrossingCoord() {
return this.pins[2].coord.orthogonalProjection(this.pins[0].coord, this.pins[1].coord);
}
/**
* Mirror coords/pins around the anchor.
*
* @param {boolean} mirrorX - true to mirror
* @param {boolean} mirrorY - true to mirror
*/
mirror(mirrorX, mirrorY) {
if (!(mirrorX || mirrorY)) return;
// Update mirror flags (xor)
this.mirrorX = this.mirrorX != mirrorX;
this.mirrorY = this.mirrorY != mirrorY;
const mirrorMid = this.coord;
for (let i = 0; i < this.pins.length; i++) {
if (i !== this.anchorCoord) {
const mirrorCoord = this.pins[i].coord;
mirrorCoord.subtract(mirrorMid);
if (mirrorX) mirrorCoord.y = -mirrorCoord.y;
if (mirrorY) mirrorCoord.x = -mirrorCoord.x;
mirrorCoord.add(mirrorMid);
}
}
}
/**
* Rotate every coord around the anchor. The rotation is counter clockwise, like the default mathematical rotation.
*
* @param {number} angle
*/
rotate(angle) {
if (!Number.isFinite(angle) || angle === 0) return;
for (let i = 0; i < this.pins.length; i++) {
if (i !== this.anchorNr) this.pins[i].coord.rotate(angle);
}
this.angle += angle;
// normalize angle
while (this.angle <= -180) this.angle += 360;
while (this.angle > 180) this.angle -= 360;
}
/**
* Move all pins/coordinates using a vector.
*
* @param {Coordinate} vector - vector to add to all coordinates
*/
translate(vector) {
[...this.pins.map((pin) => pin.coord), this.anchorCoord].forEach((coord) => coord && coord.add(vector));
}
/**
* Deep clone of the object.
*
* @returns {Transistor} the cloned transistor
*/
deepClone() {
return new Transistor(
this.tikzComponentName,
this.instanceName,
this.pins.map((pin) => pin.deepClone()),
[...this.anchorNames],
this.anchorNr,
this.coord ? this.coord.clone() : null,
this.nodeText,
this.nodeName,
this.angle,
this.mirrorX,
this.mirrorY
);
}
}
export { Transistor };