qucs_s/qucs/spicecomponents/spicelibcomp.cpp
Vadim Kuznetsov 6adc782cff Implement attaching of SPICE libraries to Qucs XML libraries
SPICE libraries referenced by SpiceLibComp are copied to the the
subdirectory when creating a library from project.
2025-01-19 14:31:32 +03:00

264 lines
8.2 KiB
C++

/***************************************************************************
spicelibcomp.cpp
--------------
begin : Tue Mar 08 2016
copyright : (C) 2016 by Vadim Kuznetsov
email : ra3xdh@gmail.com
***************************************************************************/
/***************************************************************************
* *
* This program 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 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "spicelibcomp.h"
#include "main.h"
#include "misc.h"
#include "node.h"
#include "extsimkernels/spicecompat.h"
#include <QTextStream>
#include <QFileInfo>
#include <QMutex>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
SpiceLibComp::SpiceLibComp()
{
Type = isComponent; // both analog and digital
Description = QObject::tr("SPICE library device. You can attach symbol patterns to it.");
Simulator = spicecompat::simSpice;
QStringList patterns;
misc::getSymbolPatternsList(patterns);
QString p_str = "[auto";
if (!patterns.isEmpty()) p_str += "," + patterns.join(",");
p_str += "]";
Props.append(new Property("File", "", false, QObject::tr("SpiceLibrary file")));
Props.append(new Property("Device", "", false, QObject::tr("Subcircuit entry (.SUBCKT) name")));
Props.append(new Property("SymPattern", "auto", false, p_str));
Props.append(new Property("Params", "", false, QObject::tr("Extra parameters list")));
Props.append(new Property("PinAssign", "", false, QObject::tr("Pins assignment")));
Model = "SpLib";
Name = "X";
SpiceModel = "X";
// Do NOT call createSymbol() here. But create port to let it rotate.
Ports.append(new Port(0, 0, false));
}
// ---------------------------------------------------------------------
Component* SpiceLibComp::newOne()
{
SpiceLibComp *p = new SpiceLibComp();
p->Props.front()->Value = Props.front()->Value;
p->recreate(0);
return p;
}
// -------------------------------------------------------
Element* SpiceLibComp::info(QString& Name, char* &BitmapFile, bool getNewOne)
{
Name = QObject::tr("SPICE library device");
BitmapFile = (char *) "spicelibcomp";
if(getNewOne) {
SpiceLibComp *p = new SpiceLibComp();
p->recreate(0); // createSymbol() is NOT called in constructor !!!
return p;
}
return 0;
}
// ---------------------------------------------------------------------
// Makes the schematic symbol SpiceLibComp with the correct number
// of ports.
void SpiceLibComp::createSymbol()
{
int No;
QString FileName;
QString symname = misc::properAbsFileName(Props.at(2)->Value, containingSchematic);
if (QFileInfo::exists(symname)) {
FileName = symname;
} else {
FileName = QucsSettings.BinDir;
FileName += QStringLiteral("/../share/" QUCS_NAME "/symbols/%1.sym").arg(Props.at(2)->Value);
}
// Default symbol: LM358 in opamps.lib ---> opamps/LM358.sym
QString LibName = misc::properAbsFileName(Props.at(0)->Value, containingSchematic);
QString DefSym = LibName;
QFileInfo inf(LibName); // Remove extension
int l = inf.suffix().size();
DefSym.chop(l+1);
DefSym += "/" + Props.at(1)->Value + ".sym";
QString CommonSym = inf.canonicalPath() + "/" + inf.baseName() + "/" + inf.baseName() + ".sym";
tx = INT_MIN;
ty = INT_MIN;
if(loadSymbol(FileName) > 0) { // try to load SpiceLibComp symbol
removeUnusedPorts();
} else if(loadSymbol(DefSym) > 0) {
removeUnusedPorts();
} else if(loadSymbol(CommonSym) > 0) { // CommonSymbol for all library
removeUnusedPorts();
} else {
QStringList pins;
No = spicecompat::getPins(LibName,Props.at(1)->Value,pins);
Ports.clear();
remakeSymbol(No,pins); // no symbol was found -> create standard symbol
}
}
void SpiceLibComp::removeUnusedPorts()
{
if(tx == INT_MIN) tx = x1+4;
if(ty == INT_MIN) ty = y2+4;
// remove unused ports
QMutableListIterator<Port *> ip(Ports);
Port *pp;
while (ip.hasNext()) {
pp = ip.next();
if(!pp->avail) {
pp = ip.peekNext();
ip.remove();
}
}
}
// ---------------------------------------------------------------------
void SpiceLibComp::remakeSymbol(int No, QStringList &pin_names)
{
QString nam;
int h = 30*((No-1)/2) + 15;
Lines.append(new qucs::Line(-15, -h, 15, -h,QPen(Qt::darkBlue,2)));
Lines.append(new qucs::Line( 15, -h, 15, h,QPen(Qt::darkBlue,2)));
Lines.append(new qucs::Line(-15, h, 15, h,QPen(Qt::darkBlue,2)));
Lines.append(new qucs::Line(-15, -h,-15, h,QPen(Qt::darkBlue,2)));
Texts.append(new Text(-10, -6,"lib"));
int i=0, y = 15-h;
while(i<No) {
i++;
Lines.append(new qucs::Line(-30, y,-15, y,QPen(Qt::darkBlue,2)));
Ports.append(new Port(-30, y));
if (i<=pin_names.count()) nam = pin_names.at(i-1);
Texts.append(new Text(-25,y-14,nam));
if(i == No) break;
i++;
Lines.append(new qucs::Line( 15, y, 30, y,QPen(Qt::darkBlue,2)));
Ports.append(new Port( 30, y));
if (i<=pin_names.count()) nam = pin_names.at(i-1);
Texts.append(new Text( 19,y-14,nam));
y += 60;
}
x1 = -30; y1 = -h-2;
x2 = 30; y2 = h+2;
tx = x1+4;
ty = y2+4;
}
// ---------------------------------------------------------------------
// Loads the symbol for the SpiceLibComp from the schematic file and
// returns the number of painting elements.
int SpiceLibComp::loadSymbol(const QString& DocName)
{
QString FileString;
QFile symfile(DocName);
if (symfile.open(QIODevice::ReadOnly)) {
QTextStream ts(&symfile);
FileString = ts.readAll();
symfile.close();
} else return -1;
QString Line;
QTextStream stream(&FileString, QIODevice::ReadOnly);
// read content *************************
while(!stream.atEnd()) {
Line = stream.readLine();
if(Line == "<Symbol>") break;
}
x1 = y1 = INT_MAX;
x2 = y2 = INT_MIN;
int z=0, Result;
while(!stream.atEnd()) {
Line = stream.readLine();
if(Line == "</Symbol>") {
x1 -= 4; // enlarge component boundings a little
x2 += 4;
y1 -= 4;
y2 += 4;
return z; // return number of ports
}
Line = Line.trimmed();
if(Line.at(0) != '<') return -5;
if(Line.at(Line.length()-1) != '>') return -6;
Line = Line.mid(1, Line.length()-2); // cut off start and end character
if (Line.startsWith(".ID")) continue; // Do not adjust properties for SpiceLibComp
Result = analyseLine(Line, 4);
if(Result < 0) return -7; // line format error
z += Result;
}
return -8; // field not closed
}
QString SpiceLibComp::spice_netlist(spicecompat::SpiceDialect dialect /* = spicecompat::SPICEDefault */)
{
Q_UNUSED(dialect);
QString s = QStringLiteral("X%1 ").arg(Name);
QString pins = getProperty("PinAssign")->Value;
QString sym = getProperty("SymPattern")->Value;
if (sym == "auto" || pins.isEmpty()) {
for (Port *p1 : Ports) {
s += " " + spicecompat::normalize_node_name(p1->Connection->Name);
}
} else {
QStringList pin_nums = pins.split(";");
for (int i = 0; i < pin_nums.count(); i++) {
int pn = pin_nums.at(i).toInt();
Port *pp = Ports.at(pn-1);
s += " " + spicecompat::normalize_node_name(pp->Connection->Name);
}
}
s += QStringLiteral(" %1 %2\n").arg(Props.at(1)->Value).arg(Props.at(3)->Value);
return s;
}
QString SpiceLibComp::cdl_netlist()
{
return spice_netlist(spicecompat::CDL);
}
QString SpiceLibComp::getSpiceLibrary()
{
if (isActive != COMP_IS_ACTIVE) return QString();
QString f = misc::properAbsFileName(Props.at(0)->Value, containingSchematic);
QString s = QStringLiteral(".INCLUDE \"%1\"\n").arg(f);
return s;
}
QStringList SpiceLibComp::getSpiceLibraryFiles()
{
QString f = misc::properAbsFileName(getProperty("File")->Value, containingSchematic);
QStringList files;
files.append(f);
return files;
}